fix(runtime)!: configure grain context before construction - #10565
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a race in Orleans runtime activation where IConfigureGrainContext could run after grain construction had already been scheduled, allowing grains to be constructed with an incompletely configured context.
Changes:
- Adds an internal configuration-aware activation path (
IGrainContextActivatorWithConfiguration) so built-in activators can apply configurators before enqueueing activation work. - Updates the built-in
ActivationDataActivatorto apply configurators before schedulingActivationData.Start(...). - Adds regression tests to verify configurators run before activation is started and that configured context state is visible during grain construction.
Show a summary per file
| File | Description |
|---|---|
src/Orleans.Runtime/Activation/IGrainContextActivator.cs |
Routes activation through a configuration-aware activator path when available and introduces IGrainContextActivatorWithConfiguration. |
src/Orleans.Runtime/Activation/ActivationDataActivatorProvider.cs |
Implements the new configuration-aware path for ActivationDataActivator, applying configurators before scheduling activation. |
test/Orleans.Core.Tests/Runtime/GrainContextActivatorTests.cs |
New unit test validating configurators execute before activation is started (ordering regression). |
test/Orleans.Runtime.Tests/GrainActivatorTests.cs |
Adds an in-process test verifying configured context state is visible to the grain activator at construction time. |
Review details
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Suppressed comments (1)
test/Orleans.Core.Tests/Runtime/GrainContextActivatorTests.cs:43
- The IConfigureGrainContextProvider.TryGetConfigurator contract uses a nullable out parameter annotated with [NotNullWhen(true)]. The current signature omits the attribute and uses a non-nullable out parameter, which can produce nullability-mismatch warnings (often treated as errors in this repo).
public bool TryGetConfigurator(GrainType grainType, GrainProperties properties, out IConfigureGrainContext configurator)
{
configurator = new TestConfigureGrainContext(events);
return true;
}
- Files reviewed: 4/4 changed files
- Comments generated: 3
- Review effort level: Lite
There was a problem hiding this comment.
Review details
Suppressed comments (2)
test/Orleans.Runtime.Tests/GrainActivatorTests.cs:172
- The assignability check is reversed:
grainClass.IsAssignableFrom(typeof(ExplicitlyRegisteredSimpleDIGrain))will be false whengrainClassis a derived type, so the configurator won't be applied to derived grain implementations. Usetypeof(ExplicitlyRegisteredSimpleDIGrain).IsAssignableFrom(grainClass)instead.
if (grainClassMap.TryGetGrainClass(grainType, out var grainClass)
&& grainClass.IsAssignableFrom(typeof(ExplicitlyRegisteredSimpleDIGrain)))
{
src/Orleans.Runtime/Activation/ActivationDataActivatorProvider.cs:89
RunSynchronouslywill inline the activation task when the scheduler allows it, causing grain construction to happen synchronously duringCreateContextinstead of being enqueued asynchronously (the prior behavior) and can block the caller for the duration of grain construction. To preserve async activation while still ensuring configurators run first, start the task on the activation scheduler instead of running it synchronously.
task.RunSynchronously(context.ActivationTaskScheduler);
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Lite
d4146a3 to
33dfbd2
Compare
There was a problem hiding this comment.
Review details
Suppressed comments (1)
test/Orleans.Runtime.Tests/GrainActivatorTests.cs:172
- This type check is hard to read and easy to misinterpret:
grainClass.IsAssignableFrom(typeof(ExplicitlyRegisteredSimpleDIGrain))effectively behaves like an exact-type match given thatGrainClassMapreturns the concrete implementing type. If the intent is to match onlyExplicitlyRegisteredSimpleDIGrain, an explicit equality check is clearer and avoids ambiguity about derived types.
if (grainClassMap.TryGetGrainClass(grainType, out var grainClass)
&& grainClass.IsAssignableFrom(typeof(ExplicitlyRegisteredSimpleDIGrain)))
{
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
BREAKING CHANGE: IGrainContextActivator.CreateContext now receives the context configuration actions and must apply them before grain construction begins. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 408b1948-f836-4328-9b59-c1a6d9504551
There was a problem hiding this comment.
Review details
Suppressed comments (3)
test/Orleans.Core.Tests/Runtime/GrainContextActivatorTests.cs:30
- The nullability of the
outparameter in thisTryGetimplementation doesn’t matchIGrainContextActivatorProvider.TryGet(..., out IGrainContextActivator?), which can produce CS8767 warnings (and may fail builds if warnings are treated as errors). Update the signature to use a nullable out parameter to match the interface.
public bool TryGet(GrainType grainType, out IGrainContextActivator result)
test/Orleans.Core.Tests/Runtime/GrainContextActivatorTests.cs:39
- The nullability of the
outparameter in thisTryGetConfiguratorimplementation doesn’t matchIConfigureGrainContextProvider.TryGetConfigurator(..., out IConfigureGrainContext?), which can produce CS8767 warnings (and may fail builds if warnings are treated as errors). Update the signature to use a nullable out parameter to match the interface.
public bool TryGetConfigurator(GrainType grainType, GrainProperties properties, out IConfigureGrainContext configurator)
test/Orleans.Runtime.Tests/GrainActivatorTests.cs:172
- This
IsAssignableFromcheck is reversed.grainClass.IsAssignableFrom(typeof(ExplicitlyRegisteredSimpleDIGrain))will fail ifgrainClassis a derived type/proxy ofExplicitlyRegisteredSimpleDIGrain(since derived types are not assignable from their base). Usetypeof(ExplicitlyRegisteredSimpleDIGrain).IsAssignableFrom(grainClass)instead to reliably match the intended grain type.
if (grainClassMap.TryGetGrainClass(grainType, out var grainClass)
&& grainClass.IsAssignableFrom(typeof(ExplicitlyRegisteredSimpleDIGrain)))
{
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Lite
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 408b1948-f836-4328-9b59-c1a6d9504551
Fixes #10556
Problem
The built-in activation context creator scheduled grain construction before
IConfigureGrainContextactions ran. The activation scheduler could therefore construct a grain against an incompletely configured context.Solution
Change
IGrainContextActivator.CreateContextto receive the resolvedIConfigureGrainContextactions. Activators must apply those actions before grain construction begins. The built-in activator now configures the context before enqueueing its existing activation task, deterministically closing the race while retaining asynchronous activation startup.Stateless workers preserve their existing behavior: configurators apply to the outer stateless-worker context, while inner worker activations receive an empty configuration set.
Breaking change
IGrainContextActivator.CreateContext(GrainAddress)is replaced byCreateContext(GrainAddress, IConfigureGrainContext[]). Custom implementations must accept and apply the supplied configurators before constructing the grain.Tests
Adds a deterministic configure-before-activate ordering regression and an in-process test proving configured context state is visible during grain construction. Focused stateless-worker coverage also passes on .NET 8 and .NET 10.
Microsoft Reviewers: Open in CodeFlow