From 30d3e2438c98af689e2bb4e1449b97c756d74353 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Mon, 17 Nov 2025 13:15:51 +0000 Subject: [PATCH 1/5] Try to restructure RuntimeMetricsWriter for .NET Framework --- .../RuntimeMetrics/RuntimeMetricsWriter.cs | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs index 62816b4962ea..c11a89f67dd9 100644 --- a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs +++ b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Threading; +using System.Threading.Tasks; using Datadog.Trace.DogStatsd; using Datadog.Trace.Logging; using Datadog.Trace.Vendors.StatsdClient; @@ -40,8 +41,11 @@ internal class RuntimeMetricsWriter : IDisposable private readonly TimeSpan _delay; +#if NETFRAMEWORK + private readonly Task _pushEventsTask; +#else private readonly Timer _timer; - +#endif private readonly IRuntimeMetricsListener _listener; private readonly bool _enableProcessMetrics; @@ -122,7 +126,11 @@ internal RuntimeMetricsWriter(IStatsdManager statsd, TimeSpan delay, bool inAzur Log.Warning(ex, "Unable to initialize runtime listener, some runtime metrics will be missing"); } +#if NETFRAMEWORK + _pushEventsTask = Task.Factory.StartNew(PushEventsLoop, TaskCreationOptions.LongRunning); +#else _timer = new Timer(_ => PushEvents(), null, delay, Timeout.InfiniteTimeSpan); +#endif } /// @@ -139,6 +147,12 @@ public void Dispose() } Log.Debug("Disposing Runtime Metrics timer"); +#if NETFRAMEWORK + if (_pushEventsTask.Wait(TimeSpan.FromMilliseconds(5_000))) + { + Log.Warning("Failed to dispose Runtime Metrics timer after 5 seconds"); + } +#else // Callbacks can occur after the Dispose() method overload has been called, // because the timer queues callbacks for execution by thread pool threads. // Using the Dispose(WaitHandle) method overload to waits until all callbacks have completed. @@ -150,7 +164,7 @@ public void Dispose() Log.Warning("Failed to dispose Runtime Metrics timer after 5 seconds"); } } - +#endif Log.Debug("Disposing other resources for Runtime Metrics"); AppDomain.CurrentDomain.FirstChanceException -= FirstChanceException; // We don't dispose runtime metrics on .NET Core because of https://github.com/dotnet/runtime/issues/103480 @@ -160,12 +174,21 @@ public void Dispose() _exceptionCounts.Clear(); } - internal void PushEvents() +#if NETFRAMEWORK + internal void PushEventsLoop() + { + while (PushEvents()) + { + } + } +#endif + + internal bool PushEvents() { if (Volatile.Read(ref _disposed) == 1) { Log.Debug("Runtime metrics is disposed and can't push new events"); - return; + return false; } var now = DateTime.UtcNow; @@ -259,6 +282,23 @@ internal void PushEvents() { var callbackExecutionDuration = DateTime.UtcNow - now; +#if NETFRAMEWORK + // Ideally we'd wait for the full time, but we need to make sure we shutdown in a relatively timely fashion + const int loopDurationMs = 200; + var newDelay = (int)(_delay - callbackExecutionDuration).TotalMilliseconds; + + // Missed it, so just reset + if (newDelay <= 0) + { + newDelay = (int)_delay.TotalMilliseconds; + } + + while (newDelay > 0 && Volatile.Read(ref _disposed) == 0) + { + Thread.Sleep(Math.Min(newDelay, loopDurationMs)); + newDelay -= loopDurationMs; + } +#else var newDelay = _delay - callbackExecutionDuration; if (newDelay < TimeSpan.Zero) @@ -273,7 +313,10 @@ internal void PushEvents() catch (ObjectDisposedException) { } +#endif } + + return true; } private static IRuntimeMetricsListener InitializeListener(IStatsdManager statsd, TimeSpan delay, bool inAzureAppServiceContext) From 07a17a2c3bcb0446e12f431e04dee999f4b524e2 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Mon, 17 Nov 2025 17:09:08 +0000 Subject: [PATCH 2/5] Handle test scenario --- .../src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs index c11a89f67dd9..ee3c5f79be20 100644 --- a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs +++ b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs @@ -127,7 +127,10 @@ internal RuntimeMetricsWriter(IStatsdManager statsd, TimeSpan delay, bool inAzur } #if NETFRAMEWORK - _pushEventsTask = Task.Factory.StartNew(PushEventsLoop, TaskCreationOptions.LongRunning); + // This delay is set to infinite in tests, so don't start the loop in that case + _pushEventsTask = delay != Timeout.InfiniteTimeSpan + ? Task.Factory.StartNew(PushEventsLoop, TaskCreationOptions.LongRunning) + : Task.CompletedTask; #else _timer = new Timer(_ => PushEvents(), null, delay, Timeout.InfiniteTimeSpan); #endif From d26e705501d4fa3df233ba18356c3e7e8e92b78c Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Mon, 24 Nov 2025 16:56:58 +0100 Subject: [PATCH 3/5] Update tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs Co-authored-by: NachoEchevarria <53266532+NachoEchevarria@users.noreply.github.com> --- tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs index ee3c5f79be20..ebaf7af7c892 100644 --- a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs +++ b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs @@ -151,7 +151,7 @@ public void Dispose() Log.Debug("Disposing Runtime Metrics timer"); #if NETFRAMEWORK - if (_pushEventsTask.Wait(TimeSpan.FromMilliseconds(5_000))) + if (!_pushEventsTask.Wait(TimeSpan.FromMilliseconds(5_000))) { Log.Warning("Failed to dispose Runtime Metrics timer after 5 seconds"); } From 608ae3c479720a268fbf812bb483828383d61f94 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Mon, 24 Nov 2025 17:09:01 +0100 Subject: [PATCH 4/5] Update tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs Co-authored-by: NachoEchevarria <53266532+NachoEchevarria@users.noreply.github.com> --- .../src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs index ebaf7af7c892..80ff2a4c5da0 100644 --- a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs +++ b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs @@ -298,8 +298,9 @@ internal bool PushEvents() while (newDelay > 0 && Volatile.Read(ref _disposed) == 0) { - Thread.Sleep(Math.Min(newDelay, loopDurationMs)); - newDelay -= loopDurationMs; + var sleepDuration = Math.Min(newDelay, loopDurationMs); + Thread.Sleep(sleepDuration); + newDelay -= sleepDuration; } #else var newDelay = _delay - callbackExecutionDuration; From ff9692fd8461baa54cb60da90cf4058f091383fb Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Tue, 25 Nov 2025 16:51:48 +0100 Subject: [PATCH 5/5] Update tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs --- tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs index 80ff2a4c5da0..7ce05e830eae 100644 --- a/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs +++ b/tracer/src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs @@ -300,7 +300,7 @@ internal bool PushEvents() { var sleepDuration = Math.Min(newDelay, loopDurationMs); Thread.Sleep(sleepDuration); - newDelay -= sleepDuration; + newDelay -= sleepDuration; } #else var newDelay = _delay - callbackExecutionDuration;