forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prevent ActivityId leak across threads
Fixes dotnet#51608 Previously running an async task with two or more levels of nesting on the EventSource activity stack would leave the ActivityID set on the old thread after being swapped out at a yield point. This meant that threadpool threads would often have stale ActivityIDs present when they began a new work item. This in turn produced non-sensical logs where it appeared that unrelated work items were nested within each other. Most of this change is adding tests. The ActivityIdIsZeroedOnThreadSwitchOut test is the one being fixed. The remainder of the new tests pass both before and after the change. I added those to better describe some of the existing behavior and add a little confidence that the change didn't have unintended effects elsewhere. As best I can tell the Activity tracking feature didn't have any testing previously and there is certainly still room for improvement on test coverage.
- Loading branch information
Showing
3 changed files
with
181 additions
and
4 deletions.
There are no files selected for viewing
151 changes: 151 additions & 0 deletions
151
src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.Tracing; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Xunit; | ||
|
||
namespace BasicEventSourceTests | ||
{ | ||
public class ActivityTracking | ||
{ | ||
[Fact] | ||
public void StartStopCreatesActivity() | ||
{ | ||
using ActivityEventListener l = new ActivityEventListener(); | ||
|
||
Assert.Equal(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
ActivityEventSource.Log.ExampleStart(); | ||
Assert.NotEqual(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
ActivityEventSource.Log.ExampleStop(); | ||
Assert.Equal(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
} | ||
|
||
[Fact] | ||
public async Task ActivityFlowsAsync() | ||
{ | ||
using ActivityEventListener l = new ActivityEventListener(); | ||
|
||
Assert.Equal(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
ActivityEventSource.Log.ExampleStart(); | ||
Assert.NotEqual(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
await Task.Yield(); | ||
Assert.NotEqual(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
ActivityEventSource.Log.ExampleStop(); | ||
Assert.Equal(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
} | ||
|
||
[Fact] | ||
public async Task ActivityIdIsZeroedOnThreadSwitchOut() | ||
{ | ||
using ActivityEventListener l = new ActivityEventListener(); | ||
|
||
// Run tasks on many threads. If an activity id leaks it is likely | ||
// that the thread will be sheduled to run one of our other tasks | ||
// and we can detect the non-zero id at the start of the task | ||
List<Task> tasks = new List<Task>(); | ||
for (int i = 0; i < 100; i++) | ||
{ | ||
tasks.Add(Task.Run(YieldTwoActivitiesDeep)); | ||
} | ||
|
||
await Task.WhenAll(tasks.ToArray()); | ||
} | ||
|
||
private async Task YieldTwoActivitiesDeep() | ||
{ | ||
Assert.Equal(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
ActivityEventSource.Log.ExampleStart(); | ||
ActivityEventSource.Log.Example2Start(); | ||
await Task.Yield(); | ||
ActivityEventSource.Log.Example2Stop(); | ||
ActivityEventSource.Log.ExampleStop(); | ||
Assert.Equal(Guid.Empty, EventSource.CurrentThreadActivityId); | ||
} | ||
|
||
// I don't know if this behavior is by-design or accidental. For now | ||
// I am attempting to preserve it to lower back compat risk, but in | ||
// the future we might decide it wasn't even desirable to begin with. | ||
// Compare with SetCurrentActivityIdAfterEventDoesNotFlowAsync below. | ||
[Fact] | ||
public async Task SetCurrentActivityIdBeforeEventFlowsAsync() | ||
{ | ||
using ActivityEventListener l = new ActivityEventListener(); | ||
try | ||
{ | ||
Guid g = Guid.NewGuid(); | ||
EventSource.SetCurrentThreadActivityId(g); | ||
Assert.Equal(g, EventSource.CurrentThreadActivityId); | ||
ActivityEventSource.Log.ExampleStart(); | ||
await Task.Yield(); | ||
ActivityEventSource.Log.ExampleStop(); | ||
Assert.Equal(g, EventSource.CurrentThreadActivityId); | ||
} | ||
finally | ||
{ | ||
EventSource.SetCurrentThreadActivityId(Guid.Empty); | ||
} | ||
} | ||
|
||
// I don't know if this behavior is by-design or accidental. For now | ||
// I am attempting to preserve it to lower back compat risk, but in | ||
// the future we might decide it wasn't even desirable to begin with. | ||
// Compare with SetCurrentActivityIdBeforeEventFlowsAsync above. | ||
[Fact] | ||
public async Task SetCurrentActivityIdAfterEventDoesNotFlowAsync() | ||
{ | ||
using ActivityEventListener l = new ActivityEventListener(); | ||
try | ||
{ | ||
ActivityEventSource.Log.ExampleStart(); | ||
Guid g = Guid.NewGuid(); | ||
EventSource.SetCurrentThreadActivityId(g); | ||
Assert.Equal(g, EventSource.CurrentThreadActivityId); | ||
await Task.Yield(); | ||
Assert.NotEqual(g, EventSource.CurrentThreadActivityId); | ||
ActivityEventSource.Log.ExampleStop(); | ||
} | ||
finally | ||
{ | ||
EventSource.SetCurrentThreadActivityId(Guid.Empty); | ||
} | ||
} | ||
} | ||
|
||
[EventSource(Name = "ActivityEventSource")] | ||
class ActivityEventSource : EventSource | ||
{ | ||
public static ActivityEventSource Log = new ActivityEventSource(); | ||
|
||
[Event(1)] | ||
public void ExampleStart() => WriteEvent(1); | ||
|
||
[Event(2)] | ||
public void ExampleStop() => WriteEvent(2); | ||
|
||
[Event(3)] | ||
public void Example2Start() => WriteEvent(3); | ||
|
||
[Event(4)] | ||
public void Example2Stop() => WriteEvent(4); | ||
} | ||
|
||
class ActivityEventListener : EventListener | ||
{ | ||
protected override void OnEventSourceCreated(EventSource eventSource) | ||
{ | ||
if (eventSource.Name == "System.Threading.Tasks.TplEventSource") | ||
{ | ||
EnableEvents(eventSource, EventLevel.LogAlways, (EventKeywords)0x80); | ||
} | ||
else if (eventSource.Name == "ActivityEventSource") | ||
{ | ||
EnableEvents(eventSource, EventLevel.Informational); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters