Repository navigation
refactor(runtime): start grain activation synchronously - #10569
ReubenBond wants to merge 29 commits into
Conversation
116e4c6 to
f496298
Compare
f496298 to
908c934
Compare
There was a problem hiding this comment.
Pull request overview
This PR refactors Orleans runtime grain activation startup so that activation begins synchronously on the activation scheduler before GrainContextActivator.CreateInstance returns, while ensuring the transition runs under a clean ExecutionContext to prevent AsyncLocal/ambient state leakage into grain construction/activation.
Changes:
- Start activation synchronously on the activation scheduler using
Task.RunSynchronously, explicitly installing the activation as the currentRuntimeContext. - Introduce
DefaultExecutionContextto obtain an ambient-state-freeExecutionContext(CoreCLR fast-path viaUnsafeAccessor, with a portable fallback). - Add focused tests covering scheduler affinity + ambient isolation during activation and validating
DefaultExecutionContextbehavior (including concurrent use).
Show a summary per file
| File | Description |
|---|---|
| test/Orleans.Runtime.Tests/GrainActivatorTests.cs | Adds a regression test ensuring synchronous activation start has scheduler affinity, correct RuntimeContext, and no RequestContext/transaction ambient leakage at construction time. |
| test/Orleans.Core.Tests/Runtime/DefaultExecutionContextTests.cs | Adds unit tests validating DefaultExecutionContext contains no ambient state and is safe under concurrent ExecutionContext.Run usage. |
| src/Orleans.Runtime/Utils/DefaultExecutionContext.cs | Adds a utility to obtain an ambient-free ExecutionContext via runtime default or a suppressed-flow capture fallback. |
| src/Orleans.Runtime/Activation/ActivationDataActivatorProvider.cs | Switches activation startup to ExecutionContext.Run(DefaultExecutionContext.Instance, ...) and runs activation start synchronously on the activation task scheduler. |
Review details
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
a868ced to
98d1b19
Compare
b116a4f to
d547e26
Compare
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae2e4673-3220-4e33-9073-c2dd36d0fed9
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae2e4673-3220-4e33-9073-c2dd36d0fed9
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae2e4673-3220-4e33-9073-c2dd36d0fed9
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae2e4673-3220-4e33-9073-c2dd36d0fed9
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae2e4673-3220-4e33-9073-c2dd36d0fed9
cf9ad5d to
ca17c11
Compare
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
Review tier: Lite
Findings: 2
| static state => | ||
| { | ||
| var context = (ActivationData)state!; | ||
| var task = new Task( | ||
| static state => ((ActivationData)state!).StartCore(), | ||
| context, | ||
| CancellationToken.None, | ||
| TaskCreationOptions.DenyChildAttach); | ||
| context._startup!.RunConstructor(task); | ||
| task.GetAwaiter().GetResult(); | ||
| }, | ||
| this); |
| { | ||
| // If this is a new worker and there is a message in scope, try to get the request context and activate the worker | ||
| var requestContext = (message as Message)?.RequestContextData ?? []; | ||
| newWorker.Activate(requestContext, CancellationToken.None); |
|
|
||
| Assert.DoesNotContain( | ||
| collector.GetMeasurementSnapshot(), | ||
| measurement => Equals(measurement.Tags["type"], grainTypeName)); |


Follow-up to #10565 and completion of the fix for #10556. This PR builds on the grain-context configuration contract merged in #10565.
IGrainContextnow exposes a one-shot startup contract directly:Start()returns a release token andAbort()cleans up an unstarted context. Built-in activation creation configures the context with scheduler execution reserved, publishes or wires it, then callsStart()to construct the grain exactly once under activation scheduler ownership. Rehydration and activation operations are queued before disposing the token releases the prequeued message loop. Failures after construction use normal deactivation and instance disposal.The construction task is created under a clean
ExecutionContextso ambient request and transactionAsyncLocalvalues do not leak into grain construction.DefaultExecutionContextcaptures a clean context usingThreadPool.UnsafeQueueUserWorkItem, failing fast if the work item cannot be queued.Custom eager contexts retain compatibility through the default no-op
Start()/Abort()implementations. Focused regression coverage verifies exactly-once startup, deferred work, abort/reuse, scheduler and runtime-context affinity across asynchronous signals, request and transaction ambient-state isolation, hosted-client streams, duplicate-activation stress, stateless-worker constructor failure, activation tracing, and direct activator behavior on bothnet8.0andnet10.0.Microsoft Reviewers: Open in CodeFlow