perf(runtime): reduce ActivationData async loop allocations#10071
Draft
ReubenBond wants to merge 1 commit into
Draft
perf(runtime): reduce ActivationData async loop allocations#10071ReubenBond wants to merge 1 commit into
ReubenBond wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR reduces per-iteration allocations in Orleans’ activation message loop by replacing the activation work signal with a reusable IValueTaskSource and enhancing WorkItemGroup to queue and execute non-Task callbacks directly while preserving activation scheduler behavior.
Changes:
- Introduces
WorkItemGroupWaiter(a reusableIValueTaskSource) to avoid allocating tasks per activation-loop wait. - Extends
WorkItemGroupto queueTask,SendOrPostCallback, andAction<object?>work items and to supportSynchronizationContext.Post. - Updates activation startup scheduling to post directly to the activation’s
WorkItemGroupand adjusts scheduler callback signatures for nullable state.
Show a summary per file
| File | Description |
|---|---|
| src/Orleans.Runtime/Scheduler/WorkItemGroupWaiter.cs | Adds reusable single-waiter async signal which schedules continuations via WorkItemGroup. |
| src/Orleans.Runtime/Scheduler/WorkItemGroup.cs | Changes queue from Task to multi-type work items; adds SynchronizationContext support and TaskScheduler.Current plumbing. |
| src/Orleans.Runtime/Scheduler/TaskSchedulerUtils.cs | Updates QueueAction state nullability and routes QueueWorkItem through WorkItemGroup.QueueAction. |
| src/Orleans.Runtime/Scheduler/IWorkItem.cs | Updates ExecuteWorkItem callback signature to nullable state. |
| src/Orleans.Runtime/Catalog/ActivationData.cs | Replaces SingleWaiterAutoResetEvent with WorkItemGroupWaiter and exposes WorkItemGroup for activation startup scheduling. |
| src/Orleans.Runtime/Activation/ActivationDataActivatorProvider.cs | Posts activation startup directly to WorkItemGroup instead of allocating a scheduled task. |
| src/Orleans.Core.Abstractions/Core/IGrainContext.cs | Updates IWorkItemScheduler.QueueAction nullability annotations for action/state. |
Copilot's findings
Comments suppressed due to low confidence (2)
src/Orleans.Runtime/Scheduler/WorkItemGroupWaiter.cs:141
WaitAsync()always returnsnew ValueTask(this, 0)with a constant token. Even with the single-waiter restriction, adding token/version support would make incorrect usage (double-await, awaiting after reset, etc) fail deterministically and aligns with the existingSingleWaiterAutoResetEventpattern in the repo.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ValueTask WaitAsync()
{
// Indicate that there is a waiter.
var status = Interlocked.Or(ref _status, WaitingFlag);
// If there was already a waiter, that is an error since this class is designed for use with a single waiter.
if ((status & WaitingFlag) == WaitingFlag)
{
ThrowConcurrentWaitersNotSupported();
}
// If the event was already signaled, immediately wake the waiter.
if ((status & SignaledFlag) == SignaledFlag)
{
// Reset just the status because the _continuation has not been set.
// We know that _continuation has not been set because it is only set when
// Signal() observes that the "Waiting" flag had been set but not the "Signaled" flag.
ResetStatus();
return default;
}
return new(this, 0);
}
src/Orleans.Runtime/Scheduler/WorkItemGroupWaiter.cs:113
- This new synchronization primitive is performance-critical and has subtle concurrency/lost-wakeup failure modes. There are existing scheduler-focused tests in the repo, but there’s no targeted test coverage here for key races (signal-before-wait, signal-during-OnCompleted, rapid signal/reset cycles, etc). Adding focused unit tests for
WorkItemGroupWaiterwould help prevent regressions.
/// <summary>
/// Signal the waiter.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Signal()
{
if ((_status & SignaledFlag) == SignaledFlag)
{
// The event is already signaled.
return;
}
// Set the signaled flag.
var status = Interlocked.Or(ref _status, SignaledFlag);
// If there was a waiter and the signaled flag was unset, wake the waiter now.
if ((status & SignaledFlag) != SignaledFlag && (status & WaitingFlag) == WaitingFlag)
{
// Note that in this assert we are checking the volatile _status field.
// This is a sanity check to ensure that the signaling conditions are true:
// that "Signaled" and "Waiting" flags are both set.
Debug.Assert((_status & (SignaledFlag | WaitingFlag)) == (SignaledFlag | WaitingFlag));
SignalCompletion();
}
}
- Files reviewed: 7/7 changed files
- Comments generated: 3
Comment on lines
204
to
+207
| /// </summary> | ||
| /// <param name="action">The work item.</param> | ||
| /// <param name="state">The state passed when invoking the item.</param> | ||
| void QueueAction(Action<object> action, object state); | ||
| void QueueAction(Action<object?> action, object? state); |
|
|
||
| public void QueueAction(Action action) => TaskScheduler.QueueAction(action); | ||
| public void QueueAction(Action<object> action, object state) => TaskScheduler.QueueAction(action, state); | ||
| public void QueueAction(Action action) => EnqueueWorkItem(new WorkItem((Action<object?>)(static state => ((Action)state!)()), action)); |
Comment on lines
+37
to
+50
| ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) | ||
| { | ||
| // We only support success completion (no exception/cancellation paths) | ||
| return Volatile.Read(ref _continuation) is null ? ValueTaskSourceStatus.Pending : ValueTaskSourceStatus.Succeeded; | ||
| } | ||
|
|
||
| void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) | ||
| { | ||
| if (continuation is null) | ||
| { | ||
| ThrowArgumentNullException(); | ||
| } | ||
|
|
||
| // We ignore flags (FlowExecutionContext, UseSchedulingContext) because we always schedule on WorkItemGroup |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e133cfb to
f0db409
Compare
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WorkItemGroupWaiter, a reusableIValueTaskSource, so the request loop can wait without per-iteration task allocations.WorkItemGroupto queue callbacks directly, includingSynchronizationContext.Post/QueueAction, while preserving task scheduling behavior.WorkItemGroupand updates scheduler callback signatures for nullable state.Validation
git diff --checkdotnet build src\Orleans.Runtime\Orleans.Runtime.csproj -mDependencies / notes
main; may need review/merge coordination with the ActivationData locking PR.WorkItemGroupWaiter.Microsoft Reviewers: Open in CodeFlow