Skip to content

Commit b5235f8

Browse files
authored
Merge branch 'master' into kr-igor/dsm-tt-v0
2 parents 4362a5b + 017e910 commit b5235f8

12 files changed

Lines changed: 102 additions & 83 deletions

File tree

.claude/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
2+
"env": {
3+
"OTEL_RESOURCE_ATTRIBUTES": "repo.owner=DataDog,repo.name=dd-trace-dotnet"
4+
},
25
"extraKnownMarketplaces": {
36
"datadog-claude-plugins": {
47
"source": {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ updates:
2929
update-types: ["version-update:semver-patch"]
3030

3131
# Mocked projects for integration dependency notifications
32+
cooldown:
33+
default-days: 2
3234
- package-ecosystem: "nuget"
3335
directory: "/tracer/dependabot/integrations"
3436
registries: "*"
@@ -41,6 +43,8 @@ updates:
4143

4244
# Azure functions explicit testing - we can't include these with our "normal" process checks
4345
# Because they aren't compatible with the dotnet msbuild approach we're using
46+
cooldown:
47+
default-days: 2
4448
- package-ecosystem: "nuget"
4549
directory: "/tracer/test/test-applications/azure-functions/Samples.AzureFunctions.V4Isolated"
4650
registries: "*"
@@ -54,6 +58,8 @@ updates:
5458
update-types: ["version-update:semver-patch"]
5559

5660
# Src libraries
61+
cooldown:
62+
default-days: 2
5763
- package-ecosystem: "nuget"
5864
directory: "/tracer/src"
5965
# This is a hacky way to get Dependabot to care primarily about
@@ -93,6 +99,8 @@ updates:
9399
# Lock Microsoft.Build.Framework for widest compatibility when instrumenting builds
94100
- dependency-name: "Microsoft.Build.Framework"
95101

102+
cooldown:
103+
default-days: 2
96104
- package-ecosystem: "github-actions"
97105
directories:
98106
- "/"
@@ -104,3 +112,5 @@ updates:
104112
gh-actions-packages:
105113
patterns:
106114
- "*"
115+
cooldown:
116+
default-days: 2

.gitlab/one-pipeline.locked.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# DO NOT EDIT THIS FILE MANUALLY
22
# This file is auto-generated by automation.
33
include:
4-
- remote: https://gitlab-templates.ddbuild.io/libdatadog/one-pipeline/ca/9b407f3b149c0a0f93d0f07211ab0ebeeadc08dee67429d0756597313e8ea1ca/one-pipeline.yml
4+
- remote: https://gitlab-templates.ddbuild.io/libdatadog/one-pipeline/ca/9cf7d7609ff62e4723c9cbc061ca2a25345ce5d6055b9acad9a13dbf736261f0/one-pipeline.yml
55

tracer/src/Datadog.Trace/.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dotnet_diagnostic.CA1846.severity = error # Prefer AsSpan over Substring
4444
dotnet_diagnostic.CA1848.severity = error # Use the LoggerMessage delegates
4545
# dotnet_diagnostic.CA1849.severity = error # Call async methods when in an async method - this likely would make sense to enable, except in most cases currently it's not an issue and intentional
4646
dotnet_diagnostic.CA1850.severity = error # Prefer static HashData method over ComputeHash - we should add it, but should be added separately
47-
# dotnet_diagnostic.CA1851.severity = error # Possible multiple enumerations of IEnumerable collection - violation in ASM they should address
47+
dotnet_diagnostic.CA1851.severity = error # Possible multiple enumerations of IEnumerable collection
4848
dotnet_diagnostic.CA1853.severity = error # Unnecessary call to Dictionary.ContainsKey(key)
4949
dotnet_diagnostic.CA1854.severity = error # Prefer IDictionary.TryGetValue
5050
dotnet_diagnostic.CA1855.severity = error # Use Span<T>.Clear() instead of Span<T>.Fill()

tracer/src/Datadog.Trace/AppSec/WafEncoding/Encoder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ private static unsafe DdwafObjectStruct ProcessIEnumerable(ref EncoderContext co
281281
}
282282
else
283283
{
284+
#pragma warning disable CA1851 // Possible multiple enumeration of collections - This _should_ be fixed, unless we verify that only non-IEnumerable types are provided
284285
var childrenCount = 0;
285286
// Let's enumerate first.
286287
foreach (var val in enumerable)
@@ -321,6 +322,7 @@ private static unsafe DdwafObjectStruct ProcessIEnumerable(ref EncoderContext co
321322
ddwafObjectStruct.NbEntries = (ulong)childrenCount;
322323
context.Buffers.Add(childrenData);
323324
}
325+
#pragma warning restore CA1851 // Possible multiple enumeration of collections
324326
}
325327

326328
return ddwafObjectStruct;
@@ -476,6 +478,7 @@ string GetItemsAsString()
476478
}
477479
else
478480
{
481+
#pragma warning disable CA1851 // Possible multiple enumeration of collections - This _should_ be fixed, unless we verify that only non-IEnumerable types are provided
479482
var itemData = childrenData;
480483
var maxChildrenCount = childrenCount;
481484

@@ -497,6 +500,7 @@ string GetItemsAsString()
497500
*(DdwafObjectStruct*)itemData = Encode(ref context, remainingDepth, elementKey, getValue(element));
498501
itemData += ObjectStructSize;
499502
}
503+
#pragma warning restore CA1851 // Possible multiple enumeration of collections
500504
}
501505

502506
ddWafObjectMap.Array = childrenData;

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/IbmMq/IbmMqHelper.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,13 @@ internal static IbmMqHeadersAdapterNoop GetHeadersAdapter(IMqMessage message)
6868

6969
var operationName = settings.Schema.Messaging.GetOutboundOperationName(MessagingSchema.OperationType.IbmMq);
7070
var (serviceName, serviceNameSource) = settings.Schema.Messaging.GetServiceNameMetadata(MessagingSchema.ServiceType.IbmMq);
71-
var tags = settings.Schema.Messaging.CreateIbmMqTags(SpanKinds.Consumer);
71+
var tags = settings.Schema.Messaging.CreateIbmMqTags(SpanKinds.Producer);
7272
var queueName = SanitizeQueueName(queue.Name);
7373
tags.TopicName = queueName;
7474

7575
scope = tracer.StartActiveInternal(
7676
operationName,
77+
tags: tags,
7778
serviceName: serviceName,
7879
serviceNameSource: serviceNameSource,
7980
finishOnClose: true);
@@ -84,7 +85,6 @@ internal static IbmMqHeadersAdapterNoop GetHeadersAdapter(IMqMessage message)
8485
var span = scope.Span;
8586
span.Type = SpanTypes.Queue;
8687
span.ResourceName = resourceName;
87-
span.SetTag(Tags.SpanKind, SpanKinds.Producer);
8888

8989
var context = new PropagationContext(span.Context, Baggage.Current);
9090
tracer.TracerManager.SpanContextPropagator.Inject(context, GetHeadersAdapter(message));
@@ -135,7 +135,7 @@ internal static IbmMqHeadersAdapterNoop GetHeadersAdapter(IMqMessage message)
135135
}
136136

137137
var (serviceName, serviceNameSource) = settings.Schema.Messaging.GetServiceNameMetadata(MessagingSchema.ServiceType.IbmMq);
138-
var tags = settings.Schema.Messaging.CreateIbmMqTags(SpanKinds.Producer);
138+
var tags = settings.Schema.Messaging.CreateIbmMqTags(SpanKinds.Consumer);
139139
var queueName = SanitizeQueueName(queue.Name);
140140
tags.TopicName = queueName;
141141
scope = tracer.StartActiveInternal(
@@ -152,7 +152,6 @@ internal static IbmMqHeadersAdapterNoop GetHeadersAdapter(IMqMessage message)
152152
var span = scope.Span;
153153
span.Type = SpanTypes.Queue;
154154
span.ResourceName = resourceName;
155-
span.SetTag(Tags.SpanKind, SpanKinds.Consumer);
156155
}
157156
catch (Exception ex)
158157
{

tracer/src/Datadog.Trace/Iast/Propagation/StringModuleImpl.cs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -214,17 +214,39 @@ public static string OnStringJoin<T>(string result, string delimiter, IEnumerabl
214214
var delimiterRanges = PropagationModuleImpl.GetTainted(taintedObjects, delimiter)?.Ranges;
215215
var delimiterHasRanges = delimiterRanges?.Length > 0;
216216
var delimiterLength = delimiter?.Length ?? 0;
217-
var valuesCount = values.Count();
218217

219-
int i = 0;
220-
foreach (var element in values)
218+
using (var e = values.GetEnumerator())
221219
{
222-
if (i >= startIndex && (count < 0 || i < startIndex + count))
220+
if (e.MoveNext())
223221
{
224-
pos = GetPositionAndUpdateRangesInStringJoin(taintedObjects, newRanges, pos, delimiterRanges, delimiterLength, element?.ToString() ?? string.Empty, delimiterHasRanges && i < valuesCount - 1);
225-
}
222+
var element = e.Current;
223+
var i = 0;
226224

227-
i++;
225+
while (true)
226+
{
227+
var hasNext = e.MoveNext();
228+
229+
if (i >= startIndex && (count < 0 || i < startIndex + count))
230+
{
231+
pos = GetPositionAndUpdateRangesInStringJoin(
232+
taintedObjects,
233+
newRanges,
234+
pos,
235+
delimiterRanges,
236+
delimiterLength,
237+
element?.ToString() ?? string.Empty,
238+
delimiterHasRanges && hasNext);
239+
}
240+
241+
if (!hasNext)
242+
{
243+
break;
244+
}
245+
246+
element = e.Current;
247+
i++;
248+
}
249+
}
228250
}
229251

230252
if (newRanges.Count > 0)

tracer/src/Datadog.Trace/LifetimeManager.cs

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ internal sealed class LifetimeManager
2727
private static LifetimeManager? _instance;
2828
private readonly ConcurrentQueue<object> _shutdownHooks = new();
2929

30+
// Signaled when RunShutdownTasks finishes. Subsequent callers wait on this
31+
// instead of returning early, preventing the runtime from tearing down the process prematurely.
32+
private readonly ManualResetEventSlim _shutdownComplete = new(false);
33+
3034
// We can be triggered by multiple shutdown paths (ProcessExit, CancelKeyPress, signal handlers, etc).
3135
// This flag ensures shutdown hooks run at most once.
3236
private int _shutdownStarted;
@@ -136,35 +140,18 @@ public void RunShutdownTasks(Exception? exception = null)
136140
// Ensure shutdown runs once even if multiple events fire.
137141
if (Interlocked.Exchange(ref _shutdownStarted, 1) != 0)
138142
{
143+
// Shutdown already started — wait for it to finish instead of returning immediately.
144+
// This prevents the runtime from tearing down the process (e.g. after ProcessExit returns)
145+
// before hooks have completed.
146+
_shutdownComplete.Wait();
139147
return;
140148
}
141149

142-
#if NET6_0_OR_GREATER
143-
// Unregister our termination handlers once shutdown begins (best-effort).
144-
// This avoids re-entrancy and keeps the intent clear: after shutdown starts, we don't want to
145-
// initiate additional termination paths.
146-
try
147-
{
148-
_sigtermRegistration?.Dispose();
149-
_sigtermRegistration = null;
150-
}
151-
catch (Exception ex)
152-
{
153-
// Best-effort: logging during shutdown should never prevent shutdown from continuing.
154-
Log.Warning(ex, "Failed to dispose SIGTERM termination signal handler registration.");
155-
}
156-
157-
try
158-
{
159-
_sighupRegistration?.Dispose();
160-
_sighupRegistration = null;
161-
}
162-
catch (Exception ex)
163-
{
164-
// Best-effort: logging during shutdown should never prevent shutdown from continuing.
165-
Log.Warning(ex, "Failed to dispose SIGHUP termination signal handler registration.");
166-
}
167-
#endif
150+
// Note: we intentionally do NOT dispose signal registrations here.
151+
// They must stay alive so that duplicate signals arriving during shutdown
152+
// are still handled (and canceled) by our handler, preventing the OS from
153+
// killing the process before hooks finish. They'll be cleaned up by the
154+
// GC/finalizer when the process exits.
168155

169156
try
170157
{
@@ -210,6 +197,10 @@ public void RunShutdownTasks(Exception? exception = null)
210197
{
211198
// Swallow as there's nothing we can with it anyway
212199
}
200+
finally
201+
{
202+
_shutdownComplete.Set();
203+
}
213204

214205
static void SetSynchronizationContext(SynchronizationContext? context)
215206
{
@@ -263,49 +254,27 @@ private void TryRegisterTerminationSignalHandlers()
263254

264255
private void TerminationSignalHandler(PosixSignalContext context)
265256
{
266-
// Ensure this handler initiates termination at most once.
267257
if (Interlocked.Exchange(ref _terminationExitInitiated, 1) != 0)
268258
{
269-
// Another signal already initiated termination; do nothing.
259+
// Duplicate signal while shutdown is in progress.
260+
// Wait for the first handler to finish running shutdown tasks.
261+
_shutdownComplete.Wait();
270262
return;
271263
}
272264

265+
// Calling Environment.Exit(0); caused an issue in Microsoft Orleans (look https://github.com/DataDog/dd-trace-dotnet/issues/8165)
266+
// The Posix signals registration mechanism doesn't use a normal MulticastDelegate kind of list; it's using a HashSet<Token> internally.
267+
// meaning that the call order is not deterministic, creating a flaky behavior between all the handlers.
268+
// The fact that there's no way to guarantee that we are the last handler means that we cannot force the exit of the process to raise
269+
// the finalization events calls because that means other handlers will not be called, for that reason we will just proceed with a manual
270+
// cleanup of our tasks without forcing the exit so other handlers can be executed as well.
271+
272+
// First signal: run shutdown tasks synchronously before the handler returns.
273+
// We intentionally do NOT set context.Cancel here — after the handler returns,
274+
// the runtime/OS will perform the default action (terminate the process with
275+
// exit code 143), which is the desired behavior once hooks have completed.
273276
try
274277
{
275-
// On Unix, Cancel prevents the OS default handler from immediately terminating the process.
276-
// (On Windows, SIGTERM/SIGHUP can't be canceled.)
277-
if (!OperatingSystem.IsWindows())
278-
{
279-
// See PosixSignalRegistration.Create remarks:
280-
// https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.posixsignalregistration.create
281-
context.Cancel = true; // Keep the process alive long enough to take the managed shutdown path.
282-
}
283-
}
284-
catch (Exception ex)
285-
{
286-
// Best-effort. If we can't cancel default handling, still attempt a managed exit.
287-
Log.Warning(ex, "Failed to cancel default termination signal handling. Graceful shutdown may not run.");
288-
}
289-
290-
// Intentionally do NOT call RunShutdownTasks() directly here.
291-
//
292-
// Reason: pre-.NET 10 behavior was "termination signal => graceful managed exit => ProcessExit event".
293-
// Our existing shutdown flow is attached to AppDomain.CurrentDomain.ProcessExit (CurrentDomain_ProcessExit),
294-
// so we initiate a managed shutdown and let ProcessExit invoke RunShutdownTasks just like before.
295-
//
296-
// Supporting runtime source references:
297-
// - Environment.Exit is an internal runtime call:
298-
// https://github.com/dotnet/runtime/blob/main/src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs
299-
// - ProcessExit is raised by the runtime via AppDomain.OnProcessExit():
300-
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/AppDomain.cs
301-
try
302-
{
303-
// Calling Environment.Exit(0); caused an issue in Microsoft Orleans (look https://github.com/DataDog/dd-trace-dotnet/issues/8165)
304-
// The Posix signals registration mechanism doesn't use a normal MulticastDelegate kind of list; it's using a HashSet<Token> internally.
305-
// meaning that the call order is not deterministic, creating a flaky behavior between all the handlers.
306-
// The fact that there's no way to guarantee that we are the last handler means that we cannot force the exit of the process to raise
307-
// the finialization events calls because that means other handlers will not be called, for that reason we will just proceed with a manual
308-
// cleanup of our tasks without forcing the exit so other handlers can be executed as well.
309278
RunShutdownTasks();
310279
}
311280
catch (Exception ex)

tracer/src/Datadog.Trace/TracerManager.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -783,13 +783,14 @@ private static async Task RunShutdownTasksAsync(TracerManager instance, Timer he
783783
await instance.Telemetry.DisposeAsync().ConfigureAwait(false);
784784
}
785785

786+
Log.Debug("Disposing RuntimeMetrics");
786787
instance.RuntimeMetrics?.Dispose();
787788

788-
// Fire-and-forget: on master the old sync Dispose() was already
789-
// fire-and-forget internally (Task.Run). DisposeAsync flushes buffers
790-
// and drains worker threads which can take several seconds, longer than
791-
// the window between repeated termination signals on .NET 10.
792-
instance.Statsd?.DisposeAsync().ContinueWith(t => Log.Error(t.Exception, "Error waiting for StatsD disposal"), TaskContinuationOptions.OnlyOnFaulted);
789+
if (instance.Statsd is { } statsd)
790+
{
791+
Log.Debug("Disposing StatsdManager");
792+
await statsd.DisposeAsync().ConfigureAwait(false);
793+
}
793794

794795
Log.Debug("Finished waiting for disposals.");
795796
}

tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/LifetimeManager/TerminationSignalTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
using Xunit;
1414
using Xunit.Abstractions;
1515

16-
#if NET10_0_OR_GREATER
16+
#if NET8_0_OR_GREATER
1717

1818
namespace Datadog.Trace.ClrProfiler.IntegrationTests.LifetimeManager;
1919

@@ -28,7 +28,7 @@ public TerminationSignalTests(ITestOutputHelper output)
2828
{
2929
}
3030

31-
[SkippableFact(Skip = "There are timing issues in the shutdown after adding DisposeAsync")]
31+
[SkippableFact]
3232
public async Task SigtermTriggersShutdownOnce_WhenRepeated()
3333
{
3434
await RunSigtermTestAsync(signalCount: 2, usePublishWithRid: false);
@@ -74,6 +74,7 @@ private async Task RunSigtermTestAsync(int signalCount, bool usePublishWithRid)
7474

7575
SetEnvironmentVariable("DD_LIFETIME_READY_FILE", readyFile);
7676
SetEnvironmentVariable("DD_LIFETIME_SHUTDOWN_FILE", shutdownFile);
77+
SetEnvironmentVariable("DD_LIFETIME_SHUTDOWN_DELAY_MS", "1000");
7778

7879
using var process = await StartSample(agent, "--wait", packageVersion: string.Empty, aspNetCorePort: 0, usePublishWithRID: usePublishWithRid);
7980
using var helper = new ProcessHelper(process);

0 commit comments

Comments
 (0)