Conversation
There was a problem hiding this comment.
Pull request overview
This PR streamlines event-source–based tracing in the TPL and EventSource infrastructure by simplifying IsEnabled paths, removing unused plumbing, and tightening how the default TaskScheduler and ETW/EventPipe activity IDs are handled.
Changes:
- Simplifies many
TplEventSourcecall sites and related Task/await continuation logging by relying on a cheaperEventSource.IsEnabled(EventLevel, EventKeywords)check and centralizing some repeated logic. - Adjusts
TaskScheduler/ThreadPoolTaskSchedulerinitialization so the default scheduler’s ID is explicitly set and used via a newDefaultIdconstant, reducing redundant ID creation calls while preserving existing ID semantics. - Refactors
EventSource’s enablement state (level/keywords) and removes unusedActivityIdControloverrides inEventProvider/EventPipeEventProvider, tightening the level/keyword quick checks while keeping per-session filtering behavior consistent.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
System.Threading.Tasks/TplEventSource.cs |
Simplifies IsEnabled checks to use the level/keyword overload directly and replaces a manual WriteEventCore payload for synchronous-work stop with a typed WriteEvent call. |
System.Threading.Tasks/ThreadPoolTaskScheduler.cs |
Removes the constructor that eagerly forced default scheduler ID creation, deferring to the new DefaultId-based initialization in TaskScheduler. |
System.Threading.Tasks/TaskScheduler.cs |
Makes the default scheduler strongly typed as ThreadPoolTaskScheduler, introduces DefaultId = 1, and initializes the scheduler ID counter starting from this default ID. |
System.Threading.Tasks/TaskContinuation.cs |
Updates ETW logging for await continuations to use task.ExecutingTaskScheduler?.Id ?? TaskScheduler.DefaultId, aligning with the new default-ID convention. |
System.Threading.Tasks/Task.cs |
Refactors completion causality logging to compute a single AsyncCausalityStatus, streamlines task-start/complete and wait ETW logging to use nullable TplEventSource and TaskScheduler.DefaultId, and inlines the completion-notification logging helper. |
System.Runtime.CompilerServices/TaskAwaiter.cs |
Switches wait ETW logging to use TaskScheduler.DefaultId instead of forcing access to TaskScheduler.Default.Id. |
System.Diagnostics.Tracing/EventSource.cs |
Reworks IsEnabled(EventLevel, EventKeywords) to be a fast check over m_level/m_matchAnyKeyword, refactors IsEnabledCommon to use new sentinels (EventLevel -1 and EventKeywords.All), ensures level state is reset on dispose/disable, and slightly cleans up activity-ID tracking in WriteEventWithRelatedActivityIdCore. |
System.Diagnostics.Tracing/EventProvider.cs |
Removes now-unused virtual ActivityIdControl hooks from EventProviderImpl and its ETW implementation, since activity-id control is handled directly via interop or EventPipeEventProvider. |
System.Diagnostics.Tracing/EventPipeEventProvider.cs |
Drops the override of ActivityIdControl in favor of the existing static EventActivityIdControl helper used by EventSource. |
|
Tagging subscribers to this area: @steveisok, @dotnet/area-system-diagnostics-tracing |
| } | ||
| else | ||
| { | ||
| _ = Id; |
There was a problem hiding this comment.
Relying on the implicit relationship between Id and m_taskId makes this harder to read. Why not just have a taskId local?
| RunOrQueueCompletionAction(completionAction, canInlineContinuations); | ||
| LogFinishCompletionNotification(); | ||
| return; | ||
| goto LogFinishCompletionNotification; |
There was a problem hiding this comment.
I'm not a fan of these gotos. The work being jumped to is too far away to reconcile mentally.
| internal ThreadPoolTaskScheduler() | ||
| { | ||
| _ = Id; // force ID creation of the default scheduler | ||
| } |
There was a problem hiding this comment.
What do these changes help? This code is run exactly once.
| public bool IsEnabled(EventLevel level, EventKeywords keywords) | ||
| { | ||
| return IsEnabled(level, keywords, EventChannel.None); | ||
| if (m_level < level) |
There was a problem hiding this comment.
I wouldn't try to inline a custom implementation here unless there is a clear need to do so like a perf benchmark for a meaningful scenario + a good set of unit tests validating the truth table. We've had several bugs in the past from mismatched implementations of event enable logic.
| EventChannel channel = unchecked((EventChannel)metadata.Descriptor.Channel); | ||
|
|
||
| return IsEnabledCommon(enable, currentLevel, currentMatchAnyKeyword, eventLevel, eventKeywords, channel); | ||
| return IsEnabledCommon(currentLevel == 0 ? (EventLevel)int.MaxValue : currentLevel, currentMatchAnyKeyword == 0 ? EventKeywords.All : currentMatchAnyKeyword, eventLevel, eventKeywords, channel); |
There was a problem hiding this comment.
I get the desire to avoid the special case 0 behavior on EventLevel and EventKeywords - its an annoying convention that creates an edge case for what could have been a simple comparison. However I don't think we are making things simpler globally when we change the convention in some places but keep it in others. This convention isn't just in EventSource, it also shows up in EventPipe and ETW going back 20+ years. Carving out a little area where the convention doesn't hold just invites a different kind of confusion.
I think we are better off if we accept the warts on the existing convention rather than trying to create a 2nd convention.
| { | ||
| Debug.Assert(m_eventData != null); | ||
| EventLevel commandLevel = commandArgs.level == 0 ? (EventLevel)int.MaxValue : commandArgs.level; | ||
| m_level = commandLevel > m_level ? commandLevel : m_level; |
There was a problem hiding this comment.
This logic appears different than the previous logic assuming that m_level = 0 previously is the same as m_level = MaxValue after the change.
If we start two sessions, one with commandArgs.level=1 and the next with commandArgs.level=0
Before: m_level=1 at the end
After: m_level = MaxValue at the end
The old behavior looks like a bug so I'd be happy to see it fixed, but I wouldn't want to do that in a PR that describes itself as being a refactoring. I'd also suggest fixing it staying within the existing convention so the new behavior would be m_level=0 at the end, not MaxValue.
🤖 Copilot Code Review — PR #123885Holistic AssessmentMotivation: The PR aims to streamline EventSource/TplEventSource usage by simplifying IsEnabled checks, removing dead code (ActivityIdControl), and reducing redundant operations like TaskScheduler.Default.Id access. The motivation is reasonable—reducing overhead in hot paths. Approach: The changes introduce a new sentinel convention ( Summary: Detailed Findings
|
|
@pentp given the amount of feedback, I converted the PR to draft. Please feel free to click 'ready to review' when you want us to take a look again. |
|
This pull request has been automatically marked |
IsEnabledchecks.TplEventSourceuse.