Prevent the native loader from being unloaded while sending telemetry#5944
Conversation
| #else | ||
| // Use dlopen to increment the reference count of the module | ||
| void* handle = 0; | ||
| Dl_info info; | ||
| if (dladdr((void*)&GetRuntimeId, &info)) | ||
| { | ||
| handle = dlopen(info.dli_fname, RTLD_LAZY); | ||
| } | ||
| #endif |
There was a problem hiding this comment.
I think yes but are we sure this works as intended on macOS?
Do we need some tests?
There was a problem hiding this comment.
OSX has dlopen so that should work 🤔
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing the following branches/commits: Execution-time benchmarks measure the whole time it takes to execute a program. And are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are shown in red. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). |
Datadog ReportBranch report: ✅ 0 Failed, 298016 Passed, 1851 Skipped, 13h 34m 21.57s Total Time ⌛ Performance Regressions vs Default Branch (1)
|
Benchmarks Report for tracer 🐌Benchmarks for #5944 compared to master:
The following thresholds were used for comparing the benchmark speeds:
Allocation changes below 0.5% are ignored. Benchmark detailsBenchmarks.Trace.ActivityBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AgentWriterBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.AspNetCoreBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.DbCommandBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ElasticsearchBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.GraphQLBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.HttpClientBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.ILoggerBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.Log4netBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.NLogBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.RedisBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.SerilogBenchmark - Same speed ✔️ Same allocations ✔️Raw results
Benchmarks.Trace.SpanBenchmark - Faster 🎉 Same allocations ✔️
|
| Benchmark | base/diff | Base Median (ns) | Diff Median (ns) | Modality |
|---|---|---|---|---|
| Benchmarks.Trace.SpanBenchmark.StartFinishScope‑net472 | 1.120 | 939.28 | 838.58 |
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | StartFinishSpan |
net6.0 | 397ns | 0.195ns | 0.755ns | 0.0081 | 0 | 0 | 576 B |
| master | StartFinishSpan |
netcoreapp3.1 | 596ns | 0.379ns | 1.47ns | 0.00785 | 0 | 0 | 576 B |
| master | StartFinishSpan |
net472 | 624ns | 0.719ns | 2.78ns | 0.0917 | 0 | 0 | 578 B |
| master | StartFinishScope |
net6.0 | 512ns | 0.19ns | 0.734ns | 0.00974 | 0 | 0 | 696 B |
| master | StartFinishScope |
netcoreapp3.1 | 695ns | 0.547ns | 2.12ns | 0.00935 | 0 | 0 | 696 B |
| master | StartFinishScope |
net472 | 939ns | 1.39ns | 5.38ns | 0.104 | 0 | 0 | 658 B |
| #5944 | StartFinishSpan |
net6.0 | 410ns | 0.174ns | 0.675ns | 0.00813 | 0 | 0 | 576 B |
| #5944 | StartFinishSpan |
netcoreapp3.1 | 554ns | 0.192ns | 0.719ns | 0.00783 | 0 | 0 | 576 B |
| #5944 | StartFinishSpan |
net472 | 631ns | 0.3ns | 1.16ns | 0.0915 | 0 | 0 | 578 B |
| #5944 | StartFinishScope |
net6.0 | 470ns | 0.182ns | 0.703ns | 0.0098 | 0 | 0 | 696 B |
| #5944 | StartFinishScope |
netcoreapp3.1 | 761ns | 0.269ns | 0.971ns | 0.00948 | 0 | 0 | 696 B |
| #5944 | StartFinishScope |
net472 | 838ns | 0.418ns | 1.62ns | 0.104 | 0 | 0 | 658 B |
Benchmarks.Trace.TraceAnnotationsBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | RunOnMethodBegin |
net6.0 | 648ns | 0.316ns | 1.22ns | 0.00974 | 0 | 0 | 696 B |
| master | RunOnMethodBegin |
netcoreapp3.1 | 984ns | 1.46ns | 5.65ns | 0.00932 | 0 | 0 | 696 B |
| master | RunOnMethodBegin |
net472 | 1.09μs | 1.21ns | 4.68ns | 0.105 | 0 | 0 | 658 B |
| #5944 | RunOnMethodBegin |
net6.0 | 668ns | 0.127ns | 0.493ns | 0.00972 | 0 | 0 | 696 B |
| #5944 | RunOnMethodBegin |
netcoreapp3.1 | 913ns | 0.398ns | 1.44ns | 0.00926 | 0 | 0 | 696 B |
| #5944 | RunOnMethodBegin |
net472 | 1.1μs | 2.33ns | 9.03ns | 0.104 | 0 | 0 | 658 B |
andrewlock
left a comment
There was a problem hiding this comment.
Taking your word for it, but it LGTM
…#5944) ## Summary of changes Prevent the native loader from being unloaded while sending telemetry. ## Reason for change We send telemetry when we decide not to instrument a process (for instance because of an EOL runtime). To send the telemetry, we spawn the telemetry forwarder. This is done in a background thread to avoid blocking the startup of the process. However, .NET unloads the profiler when the `CorProfilerInfo::Initialize` method returns, even if the background thread is still running, causing a segfault. ## Implementation details We increment the reference count of the module to prevent it from being unloaded by the .NET runtime. Two different implementations: - On Windows, we use `GetModuleHandleEx` to increment the reference count. Then, when we're done sending the telemetry, we use `FreeLibraryAndExitThread` to safely unload the module. - On Linux, we use `dlopen` to increment the reference count. Unfortunately there is no safe way to unload the module from within itself, so we keep it in memory. If for some reason we failed to increment the reference count, we give up on sending telemetry. ## Test coverage `OnEolFrameworkInSsi_WhenForwarderPathExists_CallsForwarderWithExpectedTelemetry` segfaults on 3.0 without this change.
…#5944 => V2) (#5957) This is a backport of #5944 to v2. ## Summary of changes Prevent the native loader from being unloaded while sending telemetry. ## Reason for change We send telemetry when we decide not to instrument a process (for instance because of an EOL runtime). To send the telemetry, we spawn the telemetry forwarder. This is done in a background thread to avoid blocking the startup of the process. However, .NET unloads the profiler when the `CorProfilerInfo::Initialize` method returns, even if the background thread is still running, causing a segfault. ## Implementation details We increment the reference count of the module to prevent it from being unloaded by the .NET runtime. Two different implementations: - On Windows, we use `GetModuleHandleEx` to increment the reference count. Then, when we're done sending the telemetry, we use `FreeLibraryAndExitThread` to safely unload the module. - On Linux, we use `dlopen` to increment the reference count. Unfortunately there is no safe way to unload the module from within itself, so we keep it in memory. If for some reason we failed to increment the reference count, we give up on sending telemetry. ## Test coverage `OnEolFrameworkInSsi_WhenForwarderPathExists_CallsForwarderWithExpectedTelemetry` segfaults on 3.0 without this change.
Summary of changes
Prevent the native loader from being unloaded while sending telemetry.
Reason for change
We send telemetry when we decide not to instrument a process (for instance because of an EOL runtime). To send the telemetry, we spawn the telemetry forwarder. This is done in a background thread to avoid blocking the startup of the process. However, .NET unloads the profiler when the
CorProfilerInfo::Initializemethod returns, even if the background thread is still running, causing a segfault.Implementation details
We increment the reference count of the module to prevent it from being unloaded by the .NET runtime.
Two different implementations:
GetModuleHandleExto increment the reference count. Then, when we're done sending the telemetry, we useFreeLibraryAndExitThreadto safely unload the module.dlopento increment the reference count. Unfortunately there is no safe way to unload the module from within itself, so we keep it in memory.If for some reason we failed to increment the reference count, we give up on sending telemetry.
Test coverage
OnEolFrameworkInSsi_WhenForwarderPathExists_CallsForwarderWithExpectedTelemetrysegfaults on 3.0 without this change.