From 25efbb25fad9decbaaae0dc9cdf8e789e2fb981e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 27 May 2025 17:05:50 +0200 Subject: [PATCH 01/13] poc: custom http transport for sentry-native to eliminate curl --- CHANGELOG.md | 1 + scripts/build-sentry-native.ps1 | 1 + src/Sentry/Http/HttpTransportBase.cs | 16 +---- src/Sentry/Platforms/Native/CFunctions.cs | 63 +++++++++++++++++++ .../buildTransitive/Sentry.Native.targets | 5 -- src/Sentry/SentryOptions.cs | 17 +++++ 6 files changed, 84 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dc6aa992e..4ea99c92b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - Rename MemoryInfo.AllocatedBytes to MemoryInfo.TotalAllocatedBytes ([#4243](https://github.com/getsentry/sentry-dotnet/pull/4243)) +- Replace libcurl with .NET HttpClient for sentry-native ([#4222](https://github.com/getsentry/sentry-dotnet/pull/4222)) ## 5.9.0 diff --git a/scripts/build-sentry-native.ps1 b/scripts/build-sentry-native.ps1 index a11e033b2c..8e7361901d 100644 --- a/scripts/build-sentry-native.ps1 +++ b/scripts/build-sentry-native.ps1 @@ -69,6 +69,7 @@ try -D SENTRY_SDK_NAME=sentry.native.dotnet ` -D SENTRY_BUILD_SHARED_LIBS=0 ` -D SENTRY_BACKEND=inproc ` + -D SENTRY_TRANSPORT=none ` $additionalArgs cmake ` diff --git a/src/Sentry/Http/HttpTransportBase.cs b/src/Sentry/Http/HttpTransportBase.cs index 9379980163..846e889923 100644 --- a/src/Sentry/Http/HttpTransportBase.cs +++ b/src/Sentry/Http/HttpTransportBase.cs @@ -187,20 +187,8 @@ protected internal virtual HttpRequestMessage CreateRequest(Envelope envelope) throw new InvalidOperationException("The DSN is expected to be set at this point."); } - var dsn = Dsn.Parse(_options.Dsn); - var authHeader = - $"Sentry sentry_version={_options.SentryVersion}," + - $"sentry_client={SdkVersion.Instance.Name}/{SdkVersion.Instance.Version}," + - $"sentry_key={dsn.PublicKey}" + - (dsn.SecretKey is { } secretKey ? $",sentry_secret={secretKey}" : null); - - return new HttpRequestMessage - { - RequestUri = dsn.GetEnvelopeEndpointUri(), - Method = HttpMethod.Post, - Headers = { { "X-Sentry-Auth", authHeader } }, - Content = new EnvelopeHttpContent(envelope, _options.DiagnosticLogger, _clock) - }; + var content = new EnvelopeHttpContent(envelope, _options.DiagnosticLogger, _clock); + return _options.CreateHttpRequest(content); } /// diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index 0b5465bd6b..bb1b979529 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -139,6 +139,14 @@ public static bool Init(SentryOptions options) } } + unsafe + { + var cTransport = sentry_transport_new(&nativeTransport); + sentry_transport_set_state(cTransport, GCHandle.ToIntPtr(GCHandle.Alloc(options))); + sentry_transport_set_free_func(cTransport, &nativeTransportFree); + sentry_options_set_transport(cOptions, cTransport); + } + options.DiagnosticLogger?.LogDebug("Initializing sentry native"); return 0 == sentry_init(cOptions); } @@ -364,6 +372,61 @@ internal struct sentry_value_t [DllImport("sentry-native")] private static extern void sentry_options_set_auto_session_tracking(IntPtr options, int debug); + [DllImport("sentry-native")] + private static extern void sentry_options_set_transport(IntPtr options, IntPtr transport); + + [DllImport("sentry-native")] + private static extern unsafe IntPtr sentry_transport_new(delegate* unmanaged/*[Cdecl]*/ sendFunc); + + [DllImport("sentry-native")] + private static extern void sentry_transport_set_state(IntPtr transport, IntPtr state); + + [DllImport("sentry-native")] + private static extern unsafe void sentry_transport_set_free_func(IntPtr transport, delegate* unmanaged/*[Cdecl]*/ freeFunc); + + [DllImport("sentry-native")] + private static extern IntPtr sentry_envelope_serialize(IntPtr envelope, out UIntPtr sizeOut); + + [DllImport("sentry-native")] + private static extern void sentry_envelope_free(IntPtr envelope); + + [DllImport("sentry-native")] + private static extern void sentry_free(IntPtr ptr); + + [UnmanagedCallersOnly] + private static void nativeTransport(IntPtr envelope, IntPtr state) + { + try + { + var options = GCHandle.FromIntPtr(state).Target as SentryOptions; + if (options is not null) + { + var data = sentry_envelope_serialize(envelope, out var size); + var content = new StringContent(Marshal.PtrToStringAnsi(data, (int)size)); + sentry_free(data); + + using var client = options.GetHttpClient(); + using var request = options.CreateHttpRequest(content); + client.SendAsync(request).GetAwaiter().GetResult(); + } + } + catch + { + // never allow an exception back to native code - it would crash the app + } + finally + { + sentry_envelope_free(envelope); + } + } + + [UnmanagedCallersOnly] + private static void nativeTransportFree(IntPtr state) + { + var handle = GCHandle.FromIntPtr(state); + handle.Free(); + } + [DllImport("sentry-native")] private static extern unsafe void sentry_options_set_logger(IntPtr options, delegate* unmanaged/*[Cdecl]*/ logger, IntPtr userData); diff --git a/src/Sentry/Platforms/Native/buildTransitive/Sentry.Native.targets b/src/Sentry/Platforms/Native/buildTransitive/Sentry.Native.targets index ea1fd7584e..be2bf2bcd5 100644 --- a/src/Sentry/Platforms/Native/buildTransitive/Sentry.Native.targets +++ b/src/Sentry/Platforms/Native/buildTransitive/Sentry.Native.targets @@ -36,8 +36,6 @@ - - @@ -46,13 +44,10 @@ - - - diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index f8d728e0d3..75760f7f63 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -245,6 +245,23 @@ internal HttpClient GetHttpClient() return factory.Create(this); } + internal HttpRequestMessage CreateHttpRequest(HttpContent content) + { + var authHeader = + $"Sentry sentry_version={SentryVersion}," + + $"sentry_client={SdkVersion.Instance.Name}/{SdkVersion.Instance.Version}," + + $"sentry_key={ParsedDsn.PublicKey}" + + (ParsedDsn.SecretKey is { } secretKey ? $",sentry_secret={secretKey}" : null); + + return new HttpRequestMessage + { + RequestUri = ParsedDsn.GetEnvelopeEndpointUri(), + Method = HttpMethod.Post, + Headers = { { "X-Sentry-Auth", authHeader } }, + Content = content + }; + } + /// /// Scope state processor. /// From 68f00887f7bc3f2e246f4bc74b1f0ffd90807188 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 3 Jun 2025 11:45:25 +0200 Subject: [PATCH 02/13] Replace StringContent with self-made UnmanagedHttpContent to avoid copy --- src/Sentry/Platforms/Native/CFunctions.cs | 5 +- .../Platforms/Native/UnmanagedHttpContent.cs | 63 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/Sentry/Platforms/Native/UnmanagedHttpContent.cs diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index bb1b979529..b5f42ad75e 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -391,7 +391,7 @@ internal struct sentry_value_t private static extern void sentry_envelope_free(IntPtr envelope); [DllImport("sentry-native")] - private static extern void sentry_free(IntPtr ptr); + internal static extern void sentry_free(IntPtr ptr); [UnmanagedCallersOnly] private static void nativeTransport(IntPtr envelope, IntPtr state) @@ -402,8 +402,7 @@ private static void nativeTransport(IntPtr envelope, IntPtr state) if (options is not null) { var data = sentry_envelope_serialize(envelope, out var size); - var content = new StringContent(Marshal.PtrToStringAnsi(data, (int)size)); - sentry_free(data); + using var content = new UnmanagedHttpContent(data, (int)size, options.DiagnosticLogger); using var client = options.GetHttpClient(); using var request = options.CreateHttpRequest(content); diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs new file mode 100644 index 0000000000..a4d357802c --- /dev/null +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -0,0 +1,63 @@ +using Sentry.Extensibility; + +namespace Sentry.Native; + +internal class UnmanagedHttpContent : SerializableHttpContent +{ + private readonly IntPtr _content; + private readonly int _length = 0; + private readonly IDiagnosticLogger? _logger; + + public UnmanagedHttpContent(IntPtr content, int length, IDiagnosticLogger? logger) + { + _content = content; + _length = length; + _logger = logger; + } + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) + { + try + { + unsafe + { + using var unmanagedStream = new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); + return unmanagedStream.CopyToAsync(stream); + } + } + catch (Exception e) + { + _logger?.LogError(e, "Failed to serialize unmanaged content into the network stream"); + throw; + } + } + + protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) + { + try + { + unsafe + { + using var unmanagedStream = new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); + unmanagedStream.CopyTo(stream); + } + } + catch (Exception e) + { + _logger?.LogError(e, "Failed to serialize unmanaged content into the network stream"); + throw; + } + } + + protected override bool TryComputeLength(out long length) + { + length = _length; + return false; + } + + protected override void Dispose(bool disposing) + { + C.sentry_free(_content); + base.Dispose(disposing); + } +} From ab3f90bb8b7b7898b4e24223805bcce477eb7397 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 3 Jun 2025 12:38:16 +0200 Subject: [PATCH 03/13] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03e4d241a2..fe2c97b57e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,15 +32,13 @@ For big feature it's advised to raise an issue to discuss it first. * [`pwsh`](https://github.com/PowerShell/PowerShell#get-powershell) Core version 6 or later on PATH. -* `CMake` on PATH. On Windows you can install the [C++ CMake tools for Windows](https://learn.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-170#installation). On macOS you can use your favourite package manager (e.g. `brew install cmake`). +* `CMake` on PATH. On Windows you can install the [C++ CMake tools for Windows](https://learn.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-170#installation). On macOS and Linux you can use your favourite package manager (e.g. `brew install cmake` or `apt install cmake`). * On Windows: - [.NET Framework](https://dotnet.microsoft.com/download/dotnet-framework) 4.6.2 or higher. - `Sentry.DiagnosticSource.IntegrationTests.csproj` uses [SQL LocalDb](https://docs.microsoft.com/sql/database-engine/configure-windows/sql-server-express-localdb) - [download SQL LocalDB 2019](https://download.microsoft.com/download/7/c/1/7c14e92e-bdcb-4f89-b7cf-93543e7112d1/SqlLocalDB.msi). To avoid running these tests, unload `Sentry.DiagnosticSource.IntegrationTests.csproj` from the solution. * On macOS/Linux - [Mono 6 or higher](https://www.mono-project.com/download/stable) to run the unit tests on the `net4x` targets. -* On Linux - - **curl** and **zlib** libraries (e.g. on Ubuntu: libcurl4-openssl-dev, libz-dev) to build the [sentry-native](https://github.com/getsentry/sentry-native/) module. ## .NET MAUI Requirements From c7fef879e736599ec8139bef3f588d5fbed98cde Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 3 Jun 2025 17:16:31 +0200 Subject: [PATCH 04/13] ci: no need to install libcurl4-openssl-dev anymore --- .github/workflows/build.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bad323189..b01483ed0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,12 +53,6 @@ jobs: if: ${{ !matrix.container }} uses: ./.github/actions/freediskspace - - name: Install build dependencies - if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'Linux' && !matrix.container - run: | - sudo apt update - sudo apt install libcurl4-openssl-dev - - run: scripts/build-sentry-native.ps1 if: steps.cache.outputs.cache-hit != 'true' shell: pwsh @@ -239,12 +233,6 @@ jobs: name: ${{ github.sha }} path: src - - name: Install build dependencies - if: runner.os == 'Linux' && !matrix.container - run: | - sudo apt update - sudo apt install libcurl4-openssl-dev - - name: Setup Environment uses: ./.github/actions/environment From e612efe032afc59006ad1fdc14ca5330d7037c3b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 3 Jun 2025 19:04:34 +0200 Subject: [PATCH 05/13] mark UnmanagedHttpContent sealed --- src/Sentry/Platforms/Native/UnmanagedHttpContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index a4d357802c..629c9a4cbc 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -2,7 +2,7 @@ namespace Sentry.Native; -internal class UnmanagedHttpContent : SerializableHttpContent +internal sealed class UnmanagedHttpContent : SerializableHttpContent { private readonly IntPtr _content; private readonly int _length = 0; From a1d8c137f03d7d8ae0df28336ff5d2031752d0c7 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 3 Jun 2025 19:06:01 +0200 Subject: [PATCH 06/13] fix UnmanagedHttpContent.TryComputeLength return value --- src/Sentry/Platforms/Native/UnmanagedHttpContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index 629c9a4cbc..f5bac72e39 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -52,7 +52,7 @@ protected override void SerializeToStream(Stream stream, TransportContext? conte protected override bool TryComputeLength(out long length) { length = _length; - return false; + return true; } protected override void Dispose(bool disposing) From ed1e65fee35a221f76e6ff404af6366de406e166 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 3 Jun 2025 19:08:57 +0200 Subject: [PATCH 07/13] add diagnostic log message for native transport exceptions --- src/Sentry/Platforms/Native/CFunctions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index b5f42ad75e..078a2d1e60 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -396,9 +396,9 @@ internal struct sentry_value_t [UnmanagedCallersOnly] private static void nativeTransport(IntPtr envelope, IntPtr state) { + var options = GCHandle.FromIntPtr(state).Target as SentryOptions; try { - var options = GCHandle.FromIntPtr(state).Target as SentryOptions; if (options is not null) { var data = sentry_envelope_serialize(envelope, out var size); @@ -409,9 +409,10 @@ private static void nativeTransport(IntPtr envelope, IntPtr state) client.SendAsync(request).GetAwaiter().GetResult(); } } - catch + catch (Exception e) { // never allow an exception back to native code - it would crash the app + options?.DiagnosticLogger?.LogError(e, "Exception in native transport callback. The native envelope will not be sent."); } finally { From a732c8e43ae8edb24bb48cc828984a380fd2540e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 3 Jun 2025 19:35:43 +0200 Subject: [PATCH 08/13] await async exceptions --- src/Sentry/Platforms/Native/UnmanagedHttpContent.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index f5bac72e39..cc4a118445 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -15,14 +15,18 @@ public UnmanagedHttpContent(IntPtr content, int length, IDiagnosticLogger? logge _logger = logger; } - protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) { try { + UnmanagedMemoryStream unmanagedStream; unsafe { - using var unmanagedStream = new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); - return unmanagedStream.CopyToAsync(stream); + unmanagedStream = new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); + } + using (unmanagedStream) + { + await unmanagedStream.CopyToAsync(stream).ConfigureAwait(false); } } catch (Exception e) From 8c33d71b1b6031983127be7f8cda4f4bbe4347de Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 4 Jun 2025 13:54:04 +0200 Subject: [PATCH 09/13] add unsafe CreateStream() helper --- .../Platforms/Native/UnmanagedHttpContent.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index cc4a118445..b95f357027 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -19,15 +19,8 @@ protected override async Task SerializeToStreamAsync(Stream stream, TransportCon { try { - UnmanagedMemoryStream unmanagedStream; - unsafe - { - unmanagedStream = new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); - } - using (unmanagedStream) - { - await unmanagedStream.CopyToAsync(stream).ConfigureAwait(false); - } + using var unmanagedStream = CreateStream(); + await unmanagedStream.CopyToAsync(stream).ConfigureAwait(false); } catch (Exception e) { @@ -40,11 +33,8 @@ protected override void SerializeToStream(Stream stream, TransportContext? conte { try { - unsafe - { - using var unmanagedStream = new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); - unmanagedStream.CopyTo(stream); - } + using var unmanagedStream = CreateStream(); + unmanagedStream.CopyTo(stream); } catch (Exception e) { @@ -64,4 +54,9 @@ protected override void Dispose(bool disposing) C.sentry_free(_content); base.Dispose(disposing); } + + private unsafe UnmanagedMemoryStream CreateStream() + { + return new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); + } } From 72dce3410466213d28e46267e1eab7c9ae5b12a7 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 4 Jun 2025 15:08:45 +0200 Subject: [PATCH 10/13] clear the pointer after dispose --- src/Sentry/Platforms/Native/UnmanagedHttpContent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index b95f357027..d3de3772a3 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -4,7 +4,7 @@ namespace Sentry.Native; internal sealed class UnmanagedHttpContent : SerializableHttpContent { - private readonly IntPtr _content; + private IntPtr _content; private readonly int _length = 0; private readonly IDiagnosticLogger? _logger; @@ -52,6 +52,7 @@ protected override bool TryComputeLength(out long length) protected override void Dispose(bool disposing) { C.sentry_free(_content); + _content = IntPtr.Zero; base.Dispose(disposing); } From 96cee160abf4d7168900036face08a3991896076 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 4 Jun 2025 17:24:10 +0200 Subject: [PATCH 11/13] apply discussed changes --- src/Sentry/Platforms/Native/CFunctions.cs | 15 ++++++++++++--- .../Platforms/Native/UnmanagedHttpContent.cs | 9 +++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index 078a2d1e60..3d87e5d009 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -376,13 +376,13 @@ internal struct sentry_value_t private static extern void sentry_options_set_transport(IntPtr options, IntPtr transport); [DllImport("sentry-native")] - private static extern unsafe IntPtr sentry_transport_new(delegate* unmanaged/*[Cdecl]*/ sendFunc); + private static extern unsafe IntPtr sentry_transport_new(delegate* unmanaged sendFunc); [DllImport("sentry-native")] private static extern void sentry_transport_set_state(IntPtr transport, IntPtr state); [DllImport("sentry-native")] - private static extern unsafe void sentry_transport_set_free_func(IntPtr transport, delegate* unmanaged/*[Cdecl]*/ freeFunc); + private static extern unsafe void sentry_transport_set_free_func(IntPtr transport, delegate* unmanaged freeFunc); [DllImport("sentry-native")] private static extern IntPtr sentry_envelope_serialize(IntPtr envelope, out UIntPtr sizeOut); @@ -406,9 +406,18 @@ private static void nativeTransport(IntPtr envelope, IntPtr state) using var client = options.GetHttpClient(); using var request = options.CreateHttpRequest(content); - client.SendAsync(request).GetAwaiter().GetResult(); +#if NET5_0_OR_GREATER + var response = client.Send(request); +#else + var response = client.SendAsync(request).GetAwaiter().GetResult(); +#endif + response.EnsureSuccessStatusCode(); } } + catch (HttpRequestException e) + { + options?.DiagnosticLogger?.LogError(e, "Failed to send native envelope."); + } catch (Exception e) { // never allow an exception back to native code - it would crash the app diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index d3de3772a3..a7a64c4bb8 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -15,6 +15,11 @@ public UnmanagedHttpContent(IntPtr content, int length, IDiagnosticLogger? logge _logger = logger; } + ~UnmanagedHttpContent() + { + Dispose(false); + } + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) { try @@ -51,8 +56,8 @@ protected override bool TryComputeLength(out long length) protected override void Dispose(bool disposing) { - C.sentry_free(_content); - _content = IntPtr.Zero; + IntPtr content = Interlocked.Exchange(ref _content, IntPtr.Zero); + C.sentry_free(content); base.Dispose(disposing); } From 0d64190417e9c26469b77387c38ce2fe62c7f90f Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 4 Jun 2025 19:55:39 +0200 Subject: [PATCH 12/13] throw ObjectDisposedException if already disposed --- src/Sentry/Platforms/Native/UnmanagedHttpContent.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index a7a64c4bb8..aaf24f12db 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -22,6 +22,7 @@ public UnmanagedHttpContent(IntPtr content, int length, IDiagnosticLogger? logge protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) { + ObjectDisposedException.ThrowIf(_content == IntPtr.Zero, this); try { using var unmanagedStream = CreateStream(); @@ -36,6 +37,7 @@ protected override async Task SerializeToStreamAsync(Stream stream, TransportCon protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) { + ObjectDisposedException.ThrowIf(_content == IntPtr.Zero, this); try { using var unmanagedStream = CreateStream(); From b1f0407b36e8b52aa3757b15cb7b3075bc9ac0b3 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 4 Jun 2025 20:01:45 +0200 Subject: [PATCH 13/13] ObjectDisposedException.ThrowIf() is available in net7.0+ --- src/Sentry/Platforms/Native/UnmanagedHttpContent.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs index aaf24f12db..08c7ec325b 100644 --- a/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs +++ b/src/Sentry/Platforms/Native/UnmanagedHttpContent.cs @@ -22,7 +22,7 @@ public UnmanagedHttpContent(IntPtr content, int length, IDiagnosticLogger? logge protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) { - ObjectDisposedException.ThrowIf(_content == IntPtr.Zero, this); + ThrowIfObjectDisposed(); try { using var unmanagedStream = CreateStream(); @@ -37,7 +37,7 @@ protected override async Task SerializeToStreamAsync(Stream stream, TransportCon protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) { - ObjectDisposedException.ThrowIf(_content == IntPtr.Zero, this); + ThrowIfObjectDisposed(); try { using var unmanagedStream = CreateStream(); @@ -67,4 +67,12 @@ private unsafe UnmanagedMemoryStream CreateStream() { return new UnmanagedMemoryStream((byte*)_content.ToPointer(), _length); } + + private void ThrowIfObjectDisposed() + { + if (_content == IntPtr.Zero) + { + throw new ObjectDisposedException(GetType().FullName); + } + } }