Skip to content

Commit 87cd222

Browse files
fix: Avoid deleting the cache if there's no connection. (#610)
Co-authored-by: Bruno Garcia <[email protected]>
1 parent c44d9be commit 87cd222

File tree

4 files changed

+68
-6
lines changed

4 files changed

+68
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## vNext
44

5+
* Fix Cache deleted on HttpTransport exception. (#610) @lucas-zimerman
56
* Add SentryScopeStateProcessor #603
67
* Add net5.0 TFM to libraries #606
78

src/Sentry/Internal/Http/CachingTransport.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Net.Http;
6+
using System.Net.Sockets;
57
using System.Threading;
68
using System.Threading.Tasks;
79
using Sentry.Extensibility;
@@ -76,6 +78,10 @@ public CachingTransport(ITransport innerTransport, SentryOptions options)
7678
await _workerSignal.WaitAsync(_workerCts.Token).ConfigureAwait(false);
7779
await ProcessCacheAsync(_workerCts.Token).ConfigureAwait(false);
7880
}
81+
catch (OperationCanceledException)
82+
{
83+
throw; // Avoid logging an error.
84+
}
7985
catch (Exception ex)
8086
{
8187
_options.DiagnosticLogger?.LogError(
@@ -91,6 +97,7 @@ public CachingTransport(ITransport innerTransport, SentryOptions options)
9197
catch (OperationCanceledException)
9298
{
9399
// Worker has been shut down, it's okay
100+
_options.DiagnosticLogger?.LogDebug("Background worker of CachingTransport has shutdown.");
94101
}
95102
});
96103
}
@@ -174,17 +181,17 @@ private async ValueTask ProcessCacheAsync(CancellationToken cancellationToken =
174181
envelope.TryGetEventId()
175182
);
176183
}
177-
catch (OperationCanceledException)
184+
catch (Exception ex) when (IsRetryable(ex))
178185
{
186+
// Let the worker catch, log, wait a bit and retry.
179187
throw;
180188
}
181189
catch (Exception ex)
182190
{
183191
_options.DiagnosticLogger?.LogError(
184-
"Failed to send cached envelope: {0}",
192+
"Failed to send cached envelope: {0}, discarding cached envelope.",
185193
ex,
186-
envelopeFilePath
187-
);
194+
envelopeFilePath);
188195
}
189196

190197
// Envelope & file stream must be disposed prior to reaching this point
@@ -194,6 +201,15 @@ private async ValueTask ProcessCacheAsync(CancellationToken cancellationToken =
194201
}
195202
}
196203

204+
// Loading an Envelope only reads the headers. The payload is read lazily, so we do Disk -> Network I/O
205+
// via stream directly instead of loading the whole file in memory. For that reason capturing an envelope
206+
// from disk could raise an IOException related to Disk I/O.
207+
// For that reason, we're not retrying IOException, to avoid any disk related exception from retrying.
208+
private static bool IsRetryable(Exception exception) =>
209+
exception is OperationCanceledException // Timed-out or Shutdown triggered
210+
|| exception is HttpRequestException // Myriad of HTTP related errors
211+
|| exception is SocketException; // Network related
212+
197213
// Gets the next cache file and moves it to "processing"
198214
private async ValueTask<string?> TryPrepareNextCacheFileAsync(
199215
CancellationToken cancellationToken = default)

test/Sentry.Tests/Internals/Http/CachingTransportTests.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using System;
12
using System.IO;
23
using System.Linq;
34
using System.Net.Http;
5+
using System.Net.Sockets;
46
using System.Threading;
57
using System.Threading.Tasks;
68
using FluentAssertions;
@@ -149,7 +151,7 @@ public async Task DoesNotRetryOnNonTransientExceptions()
149151
.SendEnvelopeAsync(Arg.Any<Envelope>(), Arg.Any<CancellationToken>())
150152
.Returns(_ =>
151153
isFailing
152-
? new ValueTask(Task.FromException(new HttpRequestException()))
154+
? new ValueTask(Task.FromException(new InvalidOperationException()))
153155
: new ValueTask()
154156
);
155157

@@ -176,5 +178,48 @@ public async Task DoesNotRetryOnNonTransientExceptions()
176178
// (0 envelopes retried)
177179
_ = innerTransport.Received(0).SendEnvelopeAsync(Arg.Any<Envelope>(), Arg.Any<CancellationToken>());
178180
}
181+
182+
[Fact(Timeout = 7000)]
183+
public async Task DoesNotDeleteCacheIfConnectionWithIssue()
184+
{
185+
// Arrange
186+
using var cacheDirectory = new TempDirectory();
187+
var options = new SentryOptions { CacheDirectoryPath = cacheDirectory.Path };
188+
189+
var exception = new HttpRequestException(null, new SocketException());
190+
var receivedException = new Exception();
191+
var innerTransport = Substitute.For<ITransport>();
192+
193+
innerTransport
194+
.SendEnvelopeAsync(Arg.Any<Envelope>(), Arg.Any<CancellationToken>())
195+
.Returns(_ => new ValueTask(Task.FromException(exception)));
196+
197+
await using var transport = new CachingTransport(innerTransport, options);
198+
199+
// Can't really reliably test this with a worker
200+
await transport.StopWorkerAsync();
201+
202+
using var envelope = Envelope.FromEvent(new SentryEvent());
203+
await transport.SendEnvelopeAsync(envelope);
204+
205+
try
206+
{
207+
// Act
208+
await transport.FlushAsync();
209+
}
210+
catch (Exception he)
211+
{
212+
receivedException = he;
213+
}
214+
finally
215+
{
216+
// (transport stops failing)
217+
innerTransport.ClearReceivedCalls();
218+
await transport.FlushAsync();
219+
}
220+
// Assert
221+
Assert.Equal(exception, receivedException);
222+
Assert.True(Directory.EnumerateFiles(cacheDirectory.Path, "*", SearchOption.AllDirectories).Any());
223+
}
179224
}
180225
}

test/Sentry.Tests/SentrySdkTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public void Init_MultipleCalls_ReplacesHubWithLatest()
202202
second.Dispose();
203203
}
204204

205-
[Fact]
205+
[Fact(Skip = "Flaky")]
206206
public async Task Init_WithCache_BlocksUntilExistingCacheIsFlushed()
207207
{
208208
// Arrange

0 commit comments

Comments
 (0)