Skip to content

Commit 337faa7

Browse files
Don't resend envelopes that get rejected by the server (#3938)
1 parent d197cb2 commit 337faa7

File tree

4 files changed

+57
-2
lines changed

4 files changed

+57
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- OTel activities that are marked as not recorded are no longer sent to Sentry ([#3890](https://github.com/getsentry/sentry-dotnet/pull/3890))
8+
- Fixed envelopes with oversized attachments getting stuck in __processing ([#3938](https://github.com/getsentry/sentry-dotnet/pull/3938))
89
- Unknown stack frames in profiles on .NET 8+ ([#3942](https://github.com/getsentry/sentry-dotnet/pull/3942))
910
- Deduplicate profiling stack frames ([#3941](https://github.com/getsentry/sentry-dotnet/pull/3941))
1011

src/Sentry/Internal/DiscardReason.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Sentry.Internal;
44
{
55
// See https://develop.sentry.dev/sdk/client-reports/ for list
66
public static DiscardReason BeforeSend = new("before_send");
7+
public static DiscardReason BufferOverflow = new("buffer_overflow");
78
public static DiscardReason CacheOverflow = new("cache_overflow");
89
public static DiscardReason EventProcessor = new("event_processor");
910
public static DiscardReason NetworkError = new("network_error");

src/Sentry/Internal/Http/CachingTransport.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,13 +300,22 @@ private async Task ProcessCacheAsync(CancellationToken cancellation)
300300
}
301301
}
302302

303-
private static bool IsNetworkError(Exception exception) =>
303+
private static bool IsNetworkUnavailableError(Exception exception) =>
304304
exception switch
305305
{
306+
// TODO: Could we make this more specific? Lots of these errors are unrelated to network availability.
306307
HttpRequestException or WebException or IOException or SocketException => true,
307308
_ => false
308309
};
309310

311+
private static bool IsRejectedByServer(Exception ex)
312+
{
313+
// When envelopes are too big, the server will reset the connection as soon as the maximum size is exceeded
314+
// (it doesn't wait for us to finish sending the whole envelope).
315+
return ex is SocketException { ErrorCode: 32 /* Broken pipe */ }
316+
|| (ex.InnerException is { } innerException && IsRejectedByServer(innerException));
317+
}
318+
310319
private async Task InnerProcessCacheAsync(string file, CancellationToken cancellation)
311320
{
312321
if (_options.NetworkStatusListener is { Online: false } listener)
@@ -346,8 +355,15 @@ private async Task InnerProcessCacheAsync(string file, CancellationToken cancell
346355
// Let the worker catch, log, wait a bit and retry.
347356
throw;
348357
}
349-
catch (Exception ex) when (IsNetworkError(ex))
358+
catch (Exception ex) when (IsRejectedByServer(ex))
359+
{
360+
_options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.BufferOverflow, envelope);
361+
_options.LogError(ex, "Failed to send cached envelope: {0}. The envelope is likely too big and will be discarded.", file);
362+
}
363+
catch (Exception ex) when (IsNetworkUnavailableError(ex))
350364
{
365+
// TODO: Envelopes could end up in an infinite loop here. We should consider implementing some
366+
// kind backoff strategy and a retry limit... then drop the envelopes if the limit is exceeded.
351367
if (_options.NetworkStatusListener is PollingNetworkStatusListener pollingListener)
352368
{
353369
pollingListener.Online = false;

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,43 @@ public async Task TransportResumesWhenNetworkComesBackOnline()
711711
envelopes.Should().NotBeEmpty();
712712
}
713713

714+
[Fact]
715+
public async Task FlushAsync_RejectedByServer_DiscardsEnvelope()
716+
{
717+
// Arrange
718+
var listener = Substitute.For<INetworkStatusListener>();
719+
listener.Online.Returns(_ => true);
720+
721+
using var cacheDirectory = new TempDirectory();
722+
var options = new SentryOptions
723+
{
724+
Dsn = ValidDsn,
725+
DiagnosticLogger = _logger,
726+
Debug = true,
727+
CacheDirectoryPath = cacheDirectory.Path,
728+
NetworkStatusListener = listener,
729+
ClientReportRecorder = Substitute.For<IClientReportRecorder>()
730+
};
731+
732+
using var envelope = Envelope.FromEvent(new SentryEvent());
733+
734+
var innerTransport = Substitute.For<ITransport>();
735+
innerTransport.SendEnvelopeAsync(Arg.Any<Envelope>(), Arg.Any<CancellationToken>())
736+
.Returns(_ => throw new SocketException(32 /* Bad pipe exception */));
737+
await using var transport = CachingTransport.Create(innerTransport, options, startWorker: false);
738+
739+
// Act
740+
await transport.SendEnvelopeAsync(envelope);
741+
await transport.FlushAsync();
742+
743+
// Assert
744+
foreach (var item in envelope.Items)
745+
{
746+
options.ClientReportRecorder.Received(1)
747+
.RecordDiscardedEvent(DiscardReason.BufferOverflow, item.DataCategory);
748+
}
749+
}
750+
714751
[Fact]
715752
public async Task DoesntWriteSentAtHeaderToCacheFile()
716753
{

0 commit comments

Comments
 (0)