From 17276d2b06b2d93f0d21e344b6d64eb67a21cc46 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Sun, 17 May 2026 08:11:06 -0700 Subject: [PATCH 1/4] perf(grain): move runtime access to grain context Move IGrainRuntime ownership from each Grain instance to IGrainContext, retaining component and service fallbacks for custom test contexts. Runtime activations expose the configured shared runtime directly and support explicit component overrides without dictionary lookups. This removes one reference from each Grain object while preserving direct-construction behavior and unit-test injection. --- src/Orleans.Core.Abstractions/Core/Grain.cs | 32 +-- .../Core/IGrainContext.cs | 15 ++ src/Orleans.Runtime/Catalog/ActivationData.cs | 28 +- .../Catalog/GrainTypeSharedContext.cs | 22 +- .../Catalog/StatelessWorkerGrainContext.cs | 8 + .../Orleans.Core.Abstractions.cs | 2 + .../Runtime/GrainRuntimeResolutionTests.cs | 246 ++++++++++++++++++ 7 files changed, 335 insertions(+), 18 deletions(-) create mode 100644 test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs diff --git a/src/Orleans.Core.Abstractions/Core/Grain.cs b/src/Orleans.Core.Abstractions/Core/Grain.cs index 7fcb8deb06d..019b173cf08 100644 --- a/src/Orleans.Core.Abstractions/Core/Grain.cs +++ b/src/Orleans.Core.Abstractions/Core/Grain.cs @@ -23,18 +23,21 @@ public abstract partial class Grain : IGrainBase, IAddressable public GrainReference GrainReference { get { return GrainContext.GrainReference; } } - internal IGrainRuntime Runtime { get; } + internal IGrainRuntime Runtime => GrainContext?.GrainRuntime + ?? throw new InvalidOperationException("Grain was created outside of the Orleans creation process and no runtime was specified."); /// - /// Gets an object which can be used to access other grains. Null if this grain is not associated with a Runtime, such as when created directly for unit testing. + /// Gets an object which can be used to access other grains. /// + /// + /// The grain was created outside of the Orleans activation process and no runtime was provided. + /// protected IGrainFactory GrainFactory => Runtime.GrainFactory; /// /// Gets the IServiceProvider managed by the runtime. Null if this grain is not associated with a Runtime, such as when created directly for unit testing. /// - // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. - protected internal IServiceProvider ServiceProvider => GrainContext?.ActivationServices ?? Runtime?.ServiceProvider!; + protected internal IServiceProvider ServiceProvider => GrainContext?.ActivationServices ?? GrainContext?.GrainRuntime?.ServiceProvider!; internal GrainId GrainId => GrainContext.GrainId; @@ -51,12 +54,17 @@ protected Grain() : this(RuntimeContext.Current!, grainRuntime: null) /// This constructor is particularly useful for unit testing where test code can create a Grain and replace /// the IGrainIdentity and IGrainRuntime with test doubles (mocks/stubs). /// + /// + /// When is provided, it is registered as an + /// component on . + /// protected Grain(IGrainContext grainContext, IGrainRuntime? grainRuntime = null) { GrainContext = grainContext; - - // ! The runtime ensures that this is not null and Unit testing frameworks must make sure that this is not null. - Runtime = grainRuntime ?? grainContext?.ActivationServices.GetService()!; + if (grainRuntime is not null) + { + grainContext!.GrainRuntime = grainRuntime; + } } /// @@ -68,7 +76,7 @@ protected Grain(IGrainContext grainContext, IGrainRuntime? grainRuntime = null) /// A unique identifier for the current silo. /// There is no semantic content to this string, but it may be useful for logging. /// - public string RuntimeIdentity => Runtime?.SiloIdentity ?? string.Empty; + public string RuntimeIdentity => GrainContext?.GrainRuntime?.SiloIdentity ?? string.Empty; /// /// Registers a timer to send periodic callbacks to this grain. @@ -163,13 +171,7 @@ protected void DelayDeactivation(TimeSpan timeSpan) /// A cancellation token which signals when deactivation should complete promptly. public virtual Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) => Task.CompletedTask; - internal void EnsureRuntime() - { - if (Runtime == null) - { - throw new InvalidOperationException("Grain was created outside of the Orleans creation process and no runtime was specified."); - } - } + internal void EnsureRuntime() => _ = Runtime; } /// diff --git a/src/Orleans.Core.Abstractions/Core/IGrainContext.cs b/src/Orleans.Core.Abstractions/Core/IGrainContext.cs index 3e78f7435f2..decbda8fb0e 100644 --- a/src/Orleans.Core.Abstractions/Core/IGrainContext.cs +++ b/src/Orleans.Core.Abstractions/Core/IGrainContext.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Orleans.Serialization.Invocation; namespace Orleans.Runtime @@ -41,6 +42,20 @@ public interface IGrainContext : ITargetHolder, IEquatable /// IServiceProvider ActivationServices { get; } + /// + /// Gets the grain runtime, if available. + /// + /// + /// Runtime contexts provide this value directly. Custom contexts can override it by registering an + /// component or service. + /// + IGrainRuntime? GrainRuntime + { + get => GetComponent(typeof(IGrainRuntime)) as IGrainRuntime + ?? ActivationServices?.GetService(); + set => SetComponent(value); + } + /// /// Gets the observable lifecycle, which can be used to add lifecycle hooks. /// diff --git a/src/Orleans.Runtime/Catalog/ActivationData.cs b/src/Orleans.Runtime/Catalog/ActivationData.cs index 0ce9b85710e..82d16b32d9a 100644 --- a/src/Orleans.Runtime/Catalog/ActivationData.cs +++ b/src/Orleans.Runtime/Catalog/ActivationData.cs @@ -142,7 +142,20 @@ public void Start(IGrainActivator grainActivator) } public ActivationTaskScheduler ActivationTaskScheduler => _workItemGroup.TaskScheduler; - public IGrainRuntime GrainRuntime => _shared.Runtime; + public IGrainRuntime GrainRuntime + { + get + { + var extras = _extras; + return extras is null ? _shared.Runtime : Volatile.Read(ref extras.GrainRuntime) ?? _shared.Runtime; + } + } + + IGrainRuntime? IGrainContext.GrainRuntime + { + get => GrainRuntime; + set => SetComponent(value); + } public object? GrainInstance { get; private set; } public GrainAddress Address { get; private set; } public GrainReference GrainReference => _selfReference ??= _shared.GrainReferenceActivator.CreateReference(GrainId, default); @@ -327,6 +340,10 @@ private DehydrationContextHolder? DehydrationContext { result = this; } + else if (componentType == typeof(IGrainRuntime)) + { + result = GrainRuntime; + } else if (_extras is { } components && components.TryGetValue(componentType, out var resultObj)) { result = resultObj; @@ -363,6 +380,13 @@ public void SetComponent(Type componentType, object? instance) lock (this) { + if (componentType == typeof(IGrainRuntime)) + { + _extras ??= new(); + Volatile.Write(ref _extras.GrainRuntime, (IGrainRuntime?)instance); + return; + } + if (instance == null) { _extras?.Remove(componentType); @@ -2318,6 +2342,8 @@ private class ActivationDataExtra : Dictionary private const int IsDisposingFlag = 1 << 2; private byte _flags; + public IGrainRuntime? GrainRuntime; + public HashSet? Timers { get => GetValueOrDefault>(nameof(Timers)); set => SetOrRemoveValue(nameof(Timers), value); } /// diff --git a/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs b/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs index bc6475b7b3a..70178b3b5b7 100644 --- a/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs +++ b/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs @@ -21,8 +21,10 @@ namespace Orleans.Runtime; /// public sealed class GrainTypeSharedContext { + private readonly IGrainRuntime _defaultRuntime; private readonly IServiceProvider _serviceProvider; private readonly Dictionary _components = []; + private IGrainRuntime? _runtimeOverride; public GrainTypeSharedContext( GrainType grainType, @@ -56,7 +58,7 @@ public GrainTypeSharedContext( var grainDirectoryResolver = serviceProvider.GetRequiredService(); GrainDirectory = PlacementStrategy.IsUsingGrainDirectory ? grainDirectoryResolver.Resolve(grainType) : null; SchedulingOptions = schedulingOptions.Value; - Runtime = grainRuntime; + _defaultRuntime = grainRuntime; MigrationManager = _serviceProvider.GetService(); CatalogInstruments = serviceProvider.GetRequiredService(); GrainInstruments = serviceProvider.GetRequiredService(); @@ -118,6 +120,11 @@ private static TimeSpan GetCollectionAgeLimit(GrainType grainType, Type grainCla return (TComponent)Logger; } + if (typeof(TComponent) == typeof(IGrainRuntime) && Runtime is TComponent runtime) + { + return runtime; + } + if (_components is null) return default; _components.TryGetValue(typeof(TComponent), out var resultObj); return (TComponent?)resultObj; @@ -140,6 +147,11 @@ private static TimeSpan GetCollectionAgeLimit(GrainType grainType, Type grainCla return Logger; } + if (componentType == typeof(IGrainRuntime)) + { + return Runtime; + } + if (_components is null) return default; _components.TryGetValue(componentType, out var resultObj); return resultObj; @@ -151,6 +163,12 @@ private static TimeSpan GetCollectionAgeLimit(GrainType grainType, Type grainCla /// The type which can be used as a key to . public void SetComponent(TComponent? instance) { + if (typeof(TComponent) == typeof(IGrainRuntime)) + { + Volatile.Write(ref _runtimeOverride, instance as IGrainRuntime); + return; + } + if (instance == null) { _components.Remove(typeof(TComponent)); @@ -215,7 +233,7 @@ public void SetComponent(TComponent? instance) /// /// Gets the grain runtime. /// - public IGrainRuntime Runtime { get; } + public IGrainRuntime Runtime => Volatile.Read(ref _runtimeOverride) ?? _defaultRuntime; /// /// Gets the local activation migration manager. diff --git a/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs b/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs index b244328caf0..91c035d02df 100644 --- a/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs +++ b/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs @@ -77,6 +77,14 @@ public StatelessWorkerGrainContext( public GrainAddress Address { get; } + public IGrainRuntime GrainRuntime => _shared.Shared.Runtime; + + IGrainRuntime? IGrainContext.GrainRuntime + { + get => GrainRuntime; + set => _shared.Shared.SetComponent(value); + } + public IServiceProvider ActivationServices => throw new NotImplementedException(); public IGrainLifecycle ObservableLifecycle => throw new NotImplementedException(); diff --git a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs index bd4ddd6a280..4939201dc72 100644 --- a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs +++ b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs @@ -2555,6 +2555,8 @@ public partial interface IGrainContext : Orleans.Serialization.Invocation.ITarge GrainReference GrainReference { get; } + IGrainRuntime? GrainRuntime { get; set; } + IGrainLifecycle ObservableLifecycle { get; } IWorkItemScheduler Scheduler { get; } diff --git a/test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs b/test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs new file mode 100644 index 00000000000..c28a7ccd9b7 --- /dev/null +++ b/test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs @@ -0,0 +1,246 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Orleans; +using Orleans.Runtime; +using Orleans.Runtime.Placement; +using TestExtensions; +using Xunit; + +namespace UnitTests.Runtime; + +[TestSuite("BVT")] +[TestProvider("None")] +[TestArea("Runtime")] +public class GrainRuntimeResolutionTests +{ + [Fact, TestCategory("BVT")] + public void Constructor_DoesNotResolveRuntime() + { + var grainContext = new TestGrainContext(); + + _ = new TestGrain(grainContext); + + Assert.Equal(0, grainContext.GetComponentCallCount); + Assert.Equal(0, grainContext.ActivationServicesAccessCount); + } + + [Fact, TestCategory("BVT")] + public void Runtime_ContextComponent_ReturnsRuntime() + { + var grainRuntime = Substitute.For(); + var grainContext = new TestGrainContext(); + grainContext.SetComponent(grainRuntime); + var grain = new TestGrain(grainContext); + + var first = grain.Runtime; + var second = grain.Runtime; + + Assert.Same(grainRuntime, first); + Assert.Same(first, second); + Assert.Equal(2, grainContext.GetComponentCallCount); + Assert.Equal(0, grainContext.ActivationServicesAccessCount); + } + + [Fact, TestCategory("BVT")] + public async Task Runtime_ConcurrentAccess_UsesContextRuntime() + { + var grainRuntime = Substitute.For(); + var grainContext = new TestGrainContext(); + grainContext.SetComponent(grainRuntime); + var grain = new TestGrain(grainContext); + + var results = await Task.WhenAll(Enumerable.Range(0, 32).Select(_ => Task.Run(() => grain.Runtime))); + + Assert.All(results, result => Assert.Same(grainRuntime, result)); + Assert.Equal(results.Length, grainContext.GetComponentCallCount); + } + + [Fact, TestCategory("BVT")] + public void Runtime_ExplicitRuntime_RegistersContextComponent() + { + var grainContext = new TestGrainContext(); + var grainRuntime = Substitute.For(); + var grain = new TestGrain(grainContext, grainRuntime); + + Assert.Same(grainRuntime, ((IGrainContext)grainContext).GrainRuntime); + Assert.Same(grainRuntime, grain.Runtime); + Assert.Equal(1, grainContext.SetComponentCallCount); + Assert.Equal(0, grainContext.ActivationServicesAccessCount); + } + + [Fact, TestCategory("BVT")] + public void Runtime_ExplicitRuntime_WorksWithMockContext() + { + var grainContext = Substitute.For(); + var grainRuntime = Substitute.For(); + var grain = new TestGrain(grainContext, grainRuntime); + + Assert.Same(grainRuntime, grainContext.GrainRuntime); + Assert.Same(grainRuntime, grain.Runtime); + } + + [Fact, TestCategory("BVT")] + public void Runtime_ContextComponent_TakesPrecedenceOverActivationServices() + { + var componentRuntime = Substitute.For(); + var serviceRuntime = Substitute.For(); + using var serviceProvider = new ServiceCollection() + .AddSingleton(serviceRuntime) + .BuildServiceProvider(); + var grainContext = new TestGrainContext(serviceProvider); + grainContext.SetComponent(componentRuntime); + var grain = new TestGrain(grainContext); + + Assert.Same(componentRuntime, grain.Runtime); + Assert.Equal(0, grainContext.ActivationServicesAccessCount); + } + + [Fact, TestCategory("BVT")] + public void Runtime_ContextComponentUnavailable_UsesActivationServices() + { + var grainRuntime = Substitute.For(); + using var serviceProvider = new ServiceCollection() + .AddSingleton(grainRuntime) + .BuildServiceProvider(); + var grainContext = new TestGrainContext(serviceProvider); + var grain = new TestGrain(grainContext); + + Assert.Same(grainRuntime, grain.Runtime); + Assert.Equal(1, grainContext.GetComponentCallCount); + Assert.Equal(1, grainContext.ActivationServicesAccessCount); + } + + [Fact, TestCategory("BVT")] + public void Runtime_Unavailable_ThrowsDeterministicException() + { + using var serviceProvider = new ServiceCollection().BuildServiceProvider(); + var grain = new TestGrain(new TestGrainContext(serviceProvider)); + + var exception = Assert.Throws(() => grain.Runtime); + + Assert.Equal("Grain was created outside of the Orleans creation process and no runtime was specified.", exception.Message); + } + + [Fact, TestCategory("BVT")] + public void Runtime_NullActivationServices_ThrowsDeterministicException() + { + var grain = new TestGrain(new TestGrainContext()); + + var exception = Assert.Throws(() => grain.Runtime); + + Assert.Equal("Grain was created outside of the Orleans creation process and no runtime was specified.", exception.Message); + } + + [Fact, TestCategory("BVT")] + public void DirectConstruction_WithoutRuntime_PreservesOptionalRuntimeBehavior() + { + var grain = new TestGrain(null!); + + Assert.Null(grain.ServiceProvider); + Assert.Empty(grain.RuntimeIdentity); + } + + [Fact, TestCategory("BVT")] + public async Task Runtime_RemainsAvailableAcrossLifecycleCallbacks() + { + var grainRuntime = Substitute.For(); + var grainContext = new TestGrainContext(); + grainContext.SetComponent(grainRuntime); + var grain = new LifecycleGrain(grainContext); + + await grain.OnActivateAsync(CancellationToken.None); + await grain.OnDeactivateAsync(new(DeactivationReasonCode.ApplicationRequested, "test"), CancellationToken.None); + + Assert.Same(grainRuntime, grain.ActivationRuntime); + Assert.Same(grainRuntime, grain.DeactivationRuntime); + } + + [Fact, TestCategory("BVT")] + public void Grain_DoesNotRetainRuntimeField() + { + var runtimeFields = typeof(Grain) + .GetFields(BindingFlags.Instance | BindingFlags.NonPublic) + .Where(field => field.FieldType == typeof(IGrainRuntime)); + + Assert.Empty(runtimeFields); + } + + private sealed class TestGrain(IGrainContext grainContext, IGrainRuntime? grainRuntime = null) + : Grain(grainContext, grainRuntime); + + private sealed class LifecycleGrain(IGrainContext grainContext) : Grain(grainContext) + { + public IGrainRuntime? ActivationRuntime { get; private set; } + + public IGrainRuntime? DeactivationRuntime { get; private set; } + + public override Task OnActivateAsync(CancellationToken cancellationToken) + { + ActivationRuntime = Runtime; + return Task.CompletedTask; + } + + public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) + { + DeactivationRuntime = Runtime; + return Task.CompletedTask; + } + } + + private sealed class TestGrainContext(IServiceProvider? activationServices = null) : IGrainContext + { + private readonly Dictionary _components = []; + + public int ActivationServicesAccessCount; + public int GetComponentCallCount; + public int SetComponentCallCount; + + public GrainReference GrainReference => throw new NotImplementedException(); + public GrainId GrainId => default; + public object? GrainInstance => null; + public ActivationId ActivationId => default; + public GrainAddress Address => throw new NotImplementedException(); + public IServiceProvider ActivationServices + { + get + { + Interlocked.Increment(ref ActivationServicesAccessCount); + return activationServices!; + } + } + + public IGrainLifecycle ObservableLifecycle => throw new NotImplementedException(); + public IWorkItemScheduler Scheduler => throw new NotImplementedException(); + public Task Deactivated => Task.CompletedTask; + public PlacementStrategy PlacementStrategy => throw new NotImplementedException(); + + public object? GetComponent(Type componentType) + { + Interlocked.Increment(ref GetComponentCallCount); + return _components.TryGetValue(componentType, out var component) ? component : null; + } + + public object? GetTarget() => null; + + public void SetComponent(TComponent? value) where TComponent : class + { + Interlocked.Increment(ref SetComponentCallCount); + if (value is null) + { + _components.Remove(typeof(TComponent)); + } + else + { + _components[typeof(TComponent)] = value; + } + } + + public void ReceiveMessage(object message) => throw new NotImplementedException(); + public void Activate(Dictionary? requestContext, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + public void Deactivate(DeactivationReason deactivationReason, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + public void Rehydrate(IRehydrationContext context) => throw new NotImplementedException(); + public void Migrate(Dictionary? requestContext, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + public bool Equals(IGrainContext? other) => ReferenceEquals(this, other); + } +} From 36b45b8b1193e5d9989f789afba61392eed00309 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Fri, 28 Aug 2026 16:27:49 -0700 Subject: [PATCH 2/4] fix(grain): isolate runtime context overrides --- src/Orleans.Core.Abstractions/Core/Grain.cs | 25 +++- .../Core/IGrainContext.cs | 6 +- src/Orleans.Runtime/Catalog/ActivationData.cs | 11 +- .../Catalog/GrainTypeSharedContext.cs | 22 +--- .../Catalog/StatelessWorkerGrainContext.cs | 8 +- .../Runtime/GrainRuntimeResolutionTests.cs | 112 ++++++++++++++++++ 6 files changed, 153 insertions(+), 31 deletions(-) diff --git a/src/Orleans.Core.Abstractions/Core/Grain.cs b/src/Orleans.Core.Abstractions/Core/Grain.cs index 019b173cf08..b109507df79 100644 --- a/src/Orleans.Core.Abstractions/Core/Grain.cs +++ b/src/Orleans.Core.Abstractions/Core/Grain.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +15,8 @@ namespace Orleans; /// public abstract partial class Grain : IGrainBase, IAddressable { + private static readonly ConditionalWeakTable StandaloneRuntimes = new(); + // Do not use this directly because we currently don't provide a way to inject it; // any interaction with it will result in non unit-testable code. Any behavior that can be accessed // from within client code (including subclasses of this class), should be exposed through IGrainRuntime. @@ -23,7 +26,10 @@ public abstract partial class Grain : IGrainBase, IAddressable public GrainReference GrainReference { get { return GrainContext.GrainReference; } } - internal IGrainRuntime Runtime => GrainContext?.GrainRuntime + private IGrainRuntime? RuntimeOrDefault => GrainContext?.GrainRuntime + ?? (StandaloneRuntimes.TryGetValue(this, out var runtime) ? runtime : null); + + internal IGrainRuntime Runtime => RuntimeOrDefault ?? throw new InvalidOperationException("Grain was created outside of the Orleans creation process and no runtime was specified."); /// @@ -37,7 +43,7 @@ public abstract partial class Grain : IGrainBase, IAddressable /// /// Gets the IServiceProvider managed by the runtime. Null if this grain is not associated with a Runtime, such as when created directly for unit testing. /// - protected internal IServiceProvider ServiceProvider => GrainContext?.ActivationServices ?? GrainContext?.GrainRuntime?.ServiceProvider!; + protected internal IServiceProvider ServiceProvider => GrainContext?.ActivationServices ?? RuntimeOrDefault?.ServiceProvider!; internal GrainId GrainId => GrainContext.GrainId; @@ -55,15 +61,22 @@ protected Grain() : this(RuntimeContext.Current!, grainRuntime: null) /// the IGrainIdentity and IGrainRuntime with test doubles (mocks/stubs). /// /// - /// When is provided, it is registered as an - /// component on . + /// When is provided, it is associated with this grain and registered as an + /// component when is available. /// protected Grain(IGrainContext grainContext, IGrainRuntime? grainRuntime = null) { GrainContext = grainContext; if (grainRuntime is not null) { - grainContext!.GrainRuntime = grainRuntime; + if (grainContext is null) + { + StandaloneRuntimes.Add(this, grainRuntime); + } + else + { + grainContext.GrainRuntime = grainRuntime; + } } } @@ -76,7 +89,7 @@ protected Grain(IGrainContext grainContext, IGrainRuntime? grainRuntime = null) /// A unique identifier for the current silo. /// There is no semantic content to this string, but it may be useful for logging. /// - public string RuntimeIdentity => GrainContext?.GrainRuntime?.SiloIdentity ?? string.Empty; + public string RuntimeIdentity => RuntimeOrDefault?.SiloIdentity ?? string.Empty; /// /// Registers a timer to send periodic callbacks to this grain. diff --git a/src/Orleans.Core.Abstractions/Core/IGrainContext.cs b/src/Orleans.Core.Abstractions/Core/IGrainContext.cs index decbda8fb0e..cb2affc0d67 100644 --- a/src/Orleans.Core.Abstractions/Core/IGrainContext.cs +++ b/src/Orleans.Core.Abstractions/Core/IGrainContext.cs @@ -43,11 +43,11 @@ public interface IGrainContext : ITargetHolder, IEquatable IServiceProvider ActivationServices { get; } /// - /// Gets the grain runtime, if available. + /// Gets or sets the grain runtime associated with this context. /// /// - /// Runtime contexts provide this value directly. Custom contexts can override it by registering an - /// component or service. + /// Runtime contexts provide the activation runtime directly. Custom contexts resolve an + /// component before resolving an activation service. /// IGrainRuntime? GrainRuntime { diff --git a/src/Orleans.Runtime/Catalog/ActivationData.cs b/src/Orleans.Runtime/Catalog/ActivationData.cs index 82d16b32d9a..225cd61ec9c 100644 --- a/src/Orleans.Runtime/Catalog/ActivationData.cs +++ b/src/Orleans.Runtime/Catalog/ActivationData.cs @@ -382,7 +382,16 @@ public void SetComponent(Type componentType, object? instance) { if (componentType == typeof(IGrainRuntime)) { - _extras ??= new(); + if (_extras is null) + { + if (instance is null) + { + return; + } + + _extras = new(); + } + Volatile.Write(ref _extras.GrainRuntime, (IGrainRuntime?)instance); return; } diff --git a/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs b/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs index 70178b3b5b7..bc6475b7b3a 100644 --- a/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs +++ b/src/Orleans.Runtime/Catalog/GrainTypeSharedContext.cs @@ -21,10 +21,8 @@ namespace Orleans.Runtime; /// public sealed class GrainTypeSharedContext { - private readonly IGrainRuntime _defaultRuntime; private readonly IServiceProvider _serviceProvider; private readonly Dictionary _components = []; - private IGrainRuntime? _runtimeOverride; public GrainTypeSharedContext( GrainType grainType, @@ -58,7 +56,7 @@ public GrainTypeSharedContext( var grainDirectoryResolver = serviceProvider.GetRequiredService(); GrainDirectory = PlacementStrategy.IsUsingGrainDirectory ? grainDirectoryResolver.Resolve(grainType) : null; SchedulingOptions = schedulingOptions.Value; - _defaultRuntime = grainRuntime; + Runtime = grainRuntime; MigrationManager = _serviceProvider.GetService(); CatalogInstruments = serviceProvider.GetRequiredService(); GrainInstruments = serviceProvider.GetRequiredService(); @@ -120,11 +118,6 @@ private static TimeSpan GetCollectionAgeLimit(GrainType grainType, Type grainCla return (TComponent)Logger; } - if (typeof(TComponent) == typeof(IGrainRuntime) && Runtime is TComponent runtime) - { - return runtime; - } - if (_components is null) return default; _components.TryGetValue(typeof(TComponent), out var resultObj); return (TComponent?)resultObj; @@ -147,11 +140,6 @@ private static TimeSpan GetCollectionAgeLimit(GrainType grainType, Type grainCla return Logger; } - if (componentType == typeof(IGrainRuntime)) - { - return Runtime; - } - if (_components is null) return default; _components.TryGetValue(componentType, out var resultObj); return resultObj; @@ -163,12 +151,6 @@ private static TimeSpan GetCollectionAgeLimit(GrainType grainType, Type grainCla /// The type which can be used as a key to . public void SetComponent(TComponent? instance) { - if (typeof(TComponent) == typeof(IGrainRuntime)) - { - Volatile.Write(ref _runtimeOverride, instance as IGrainRuntime); - return; - } - if (instance == null) { _components.Remove(typeof(TComponent)); @@ -233,7 +215,7 @@ public void SetComponent(TComponent? instance) /// /// Gets the grain runtime. /// - public IGrainRuntime Runtime => Volatile.Read(ref _runtimeOverride) ?? _defaultRuntime; + public IGrainRuntime Runtime { get; } /// /// Gets the local activation migration manager. diff --git a/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs b/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs index 91c035d02df..bfc8d1fed38 100644 --- a/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs +++ b/src/Orleans.Runtime/Catalog/StatelessWorkerGrainContext.cs @@ -82,7 +82,13 @@ public StatelessWorkerGrainContext( IGrainRuntime? IGrainContext.GrainRuntime { get => GrainRuntime; - set => _shared.Shared.SetComponent(value); + set + { + if (!ReferenceEquals(value, GrainRuntime)) + { + throw new ArgumentException("The runtime for a stateless worker context is provided by its shared grain type context.", nameof(value)); + } + } } public IServiceProvider ActivationServices => throw new NotImplementedException(); diff --git a/test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs b/test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs index c28a7ccd9b7..d452e3563af 100644 --- a/test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs +++ b/test/Orleans.Core.Tests/Runtime/GrainRuntimeResolutionTests.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Orleans; @@ -141,6 +142,21 @@ public void DirectConstruction_WithoutRuntime_PreservesOptionalRuntimeBehavior() Assert.Empty(grain.RuntimeIdentity); } + [Fact, TestCategory("BVT")] + public void DirectConstruction_WithExplicitRuntime_PreservesRuntimeBehavior() + { + using var serviceProvider = new ServiceCollection().BuildServiceProvider(); + var grainRuntime = Substitute.For(); + grainRuntime.ServiceProvider.Returns(serviceProvider); + grainRuntime.SiloIdentity.Returns("test-silo"); + + var grain = new TestGrain(null!, grainRuntime); + + Assert.Same(grainRuntime, grain.Runtime); + Assert.Same(serviceProvider, grain.ServiceProvider); + Assert.Equal("test-silo", grain.RuntimeIdentity); + } + [Fact, TestCategory("BVT")] public async Task Runtime_RemainsAvailableAcrossLifecycleCallbacks() { @@ -166,6 +182,102 @@ public void Grain_DoesNotRetainRuntimeField() Assert.Empty(runtimeFields); } + [Fact, TestCategory("BVT")] + public void ActivationContext_RuntimeOverride_IsPerActivation() + { + var defaultRuntime = Substitute.For(); + var overrideRuntime = Substitute.For(); + var shared = CreateSharedContext(defaultRuntime); + var first = CreateActivationData(shared); + var second = CreateActivationData(shared); + + ((IGrainContext)first).GrainRuntime = overrideRuntime; + + Assert.Same(overrideRuntime, first.GrainRuntime); + Assert.Same(defaultRuntime, second.GrainRuntime); + Assert.Same(defaultRuntime, shared.Runtime); + } + + [Fact, TestCategory("BVT")] + public async Task ActivationContext_RuntimeOverride_IsSafelyPublished() + { + var defaultRuntime = Substitute.For(); + var overrideRuntime = Substitute.For(); + var activation = CreateActivationData(CreateSharedContext(defaultRuntime)); + var context = (IGrainContext)activation; + + var writer = Task.Run(() => + { + for (var i = 0; i < 100_000; i++) + { + context.GrainRuntime = (i & 1) == 0 ? overrideRuntime : null; + } + }, TestContext.Current.CancellationToken); + var reader = Task.Run(() => + { + for (var i = 0; i < 100_000; i++) + { + var runtime = context.GrainRuntime; + Assert.True(ReferenceEquals(runtime, defaultRuntime) || ReferenceEquals(runtime, overrideRuntime)); + } + }, TestContext.Current.CancellationToken); + + await Task.WhenAll(writer, reader); + } + + [Fact, TestCategory("BVT")] + public void ActivationContext_ClearingAbsentRuntimeOverride_DoesNotCreateExtras() + { + var activation = CreateActivationData(CreateSharedContext(Substitute.For())); + + ((IGrainContext)activation).GrainRuntime = null; + + Assert.Null(typeof(ActivationData) + .GetField("_extras", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(activation)); + } + + [Fact, TestCategory("BVT")] + public void StatelessWorkerContext_RuntimeOverride_DoesNotMutateSharedContext() + { + var defaultRuntime = Substitute.For(); + var overrideRuntime = Substitute.For(); + var shared = CreateSharedContext(defaultRuntime); + var statelessShared = (StatelessWorkerGrainTypeSharedContext)RuntimeHelpers.GetUninitializedObject(typeof(StatelessWorkerGrainTypeSharedContext)); + typeof(StatelessWorkerGrainTypeSharedContext) + .GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)! + .SetValue(statelessShared, shared); + var context = (StatelessWorkerGrainContext)RuntimeHelpers.GetUninitializedObject(typeof(StatelessWorkerGrainContext)); + typeof(StatelessWorkerGrainContext) + .GetField("_shared", BindingFlags.Instance | BindingFlags.NonPublic)! + .SetValue(context, statelessShared); + + ((IGrainContext)context).GrainRuntime = defaultRuntime; + var exception = Assert.Throws(() => ((IGrainContext)context).GrainRuntime = overrideRuntime); + + Assert.Equal("value", exception.ParamName); + Assert.Same(defaultRuntime, context.GrainRuntime); + Assert.Same(defaultRuntime, shared.Runtime); + } + + private static GrainTypeSharedContext CreateSharedContext(IGrainRuntime runtime) + { + var shared = (GrainTypeSharedContext)RuntimeHelpers.GetUninitializedObject(typeof(GrainTypeSharedContext)); + typeof(GrainTypeSharedContext) + .GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)! + .SetValue(shared, runtime); + return shared; + } + + private static ActivationData CreateActivationData(GrainTypeSharedContext shared) + { + var activation = (ActivationData)RuntimeHelpers.GetUninitializedObject(typeof(ActivationData)); + typeof(ActivationData) + .GetField("_shared", BindingFlags.Instance | BindingFlags.NonPublic)! + .SetValue(activation, shared); + return activation; + } + private sealed class TestGrain(IGrainContext grainContext, IGrainRuntime? grainRuntime = null) : Grain(grainContext, grainRuntime); From d89e735276455bf2cf0e8f6df07afd12e6e1d8ed Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Sat, 29 Aug 2026 02:52:03 -0700 Subject: [PATCH 3/4] fix(runtime): publish activation runtime overrides safely --- src/Orleans.Runtime/Catalog/ActivationData.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Orleans.Runtime/Catalog/ActivationData.cs b/src/Orleans.Runtime/Catalog/ActivationData.cs index 225cd61ec9c..d64a2a50283 100644 --- a/src/Orleans.Runtime/Catalog/ActivationData.cs +++ b/src/Orleans.Runtime/Catalog/ActivationData.cs @@ -146,7 +146,7 @@ public IGrainRuntime GrainRuntime { get { - var extras = _extras; + var extras = Volatile.Read(ref _extras); return extras is null ? _shared.Runtime : Volatile.Read(ref extras.GrainRuntime) ?? _shared.Runtime; } } @@ -382,17 +382,20 @@ public void SetComponent(Type componentType, object? instance) { if (componentType == typeof(IGrainRuntime)) { - if (_extras is null) + var extras = Volatile.Read(ref _extras); + if (extras is null) { if (instance is null) { return; } - _extras = new(); + extras = new() { GrainRuntime = (IGrainRuntime)instance }; + Volatile.Write(ref _extras, extras); + return; } - Volatile.Write(ref _extras.GrainRuntime, (IGrainRuntime?)instance); + Volatile.Write(ref extras.GrainRuntime, (IGrainRuntime?)instance); return; } From 193da57a16031ad029cd17b680e016ff4c543b42 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Sat, 29 Aug 2026 04:02:29 -0700 Subject: [PATCH 4/4] fix(grain): expose nullable service provider --- .../ActivationRepartitioning.Frontend/Program.cs | 2 +- src/Orleans.Core.Abstractions/Core/Grain.cs | 2 +- .../LogConsistency/LogConsistentGrain.cs | 12 +++++++----- src/Orleans.Journaling/DurableGrain.cs | 4 ++-- .../TestStorageProviders/StorageFaultGrain.cs | 2 +- .../Orleans.Core.Abstractions.cs | 2 +- .../Grains/TestGrains/MessageSerializationGrain.cs | 5 +++-- .../ProgrammaticSubscribe/SubscribeGrain.cs | 2 +- test/Grains/TestGrains/SimpleDIGrain.cs | 7 ++++--- .../TestInternalGrains/CollectionTestGrain.cs | 4 ++-- .../TestInternalGrains/PersistenceTestGrains.cs | 2 +- .../TestInternalGrains/PlacementTestGrain.cs | 2 +- .../SerializerPresenceTestGrain.cs | 2 +- test/Grains/TestInternalGrains/StreamingGrain.cs | 14 +++++++------- test/Grains/TestInternalGrains/TestGrain.cs | 8 ++++---- .../CustomToleranceTests.cs | 12 ++++++------ .../Grains/BlockingCallGrains.cs | 4 ++-- 17 files changed, 45 insertions(+), 41 deletions(-) diff --git a/playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/Program.cs b/playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/Program.cs index 51076b56a80..5a51e3f8de7 100644 --- a/playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/Program.cs +++ b/playground/ActivationRepartitioning/ActivationRepartitioning.Frontend/Program.cs @@ -97,7 +97,7 @@ public async ValueTask Reset() { ++_resetCount; _numForests = 0; - await ServiceProvider.GetRequiredService().ResetAsync(); + await ServiceProvider!.GetRequiredService().ResetAsync(); await GrainFactory.GetGrain(0).ResetGrainCallFrequencies(); } diff --git a/src/Orleans.Core.Abstractions/Core/Grain.cs b/src/Orleans.Core.Abstractions/Core/Grain.cs index b109507df79..ff299881763 100644 --- a/src/Orleans.Core.Abstractions/Core/Grain.cs +++ b/src/Orleans.Core.Abstractions/Core/Grain.cs @@ -43,7 +43,7 @@ public abstract partial class Grain : IGrainBase, IAddressable /// /// Gets the IServiceProvider managed by the runtime. Null if this grain is not associated with a Runtime, such as when created directly for unit testing. /// - protected internal IServiceProvider ServiceProvider => GrainContext?.ActivationServices ?? RuntimeOrDefault?.ServiceProvider!; + protected internal IServiceProvider? ServiceProvider => GrainContext?.ActivationServices ?? RuntimeOrDefault?.ServiceProvider; internal GrainId GrainId => GrainContext.GrainId; diff --git a/src/Orleans.EventSourcing/LogConsistency/LogConsistentGrain.cs b/src/Orleans.EventSourcing/LogConsistency/LogConsistentGrain.cs index 7b5cdb617f4..f04b21adf87 100644 --- a/src/Orleans.EventSourcing/LogConsistency/LogConsistentGrain.cs +++ b/src/Orleans.EventSourcing/LogConsistency/LogConsistentGrain.cs @@ -55,11 +55,12 @@ private async Task OnDeactivateState(CancellationToken ct) private Task OnSetupState(CancellationToken ct) { if (ct.IsCancellationRequested) return Task.CompletedTask; - IGrainContextAccessor grainContextAccessor = this.ServiceProvider.GetRequiredService(); - Factory protocolServicesFactory = this.ServiceProvider.GetRequiredService>(); + var serviceProvider = ServiceProvider!; + IGrainContextAccessor grainContextAccessor = serviceProvider.GetRequiredService(); + Factory protocolServicesFactory = serviceProvider.GetRequiredService>(); IGrainContext grainContext = grainContextAccessor.GrainContext!; ILogViewAdaptorFactory consistencyProvider = SetupLogConsistencyProvider(grainContext); - IGrainStorage? grainStorage = consistencyProvider.UsesStorageProvider ? GrainStorageHelpers.GetGrainStorage((grainContext?.GrainInstance!.GetType())!, this.ServiceProvider) : null; + IGrainStorage? grainStorage = consistencyProvider.UsesStorageProvider ? GrainStorageHelpers.GetGrainStorage((grainContext?.GrainInstance!.GetType())!, serviceProvider) : null; InstallLogViewAdaptor(grainContext!, protocolServicesFactory, consistencyProvider, grainStorage); return Task.CompletedTask; } @@ -91,10 +92,11 @@ private void InstallLogViewAdaptor( private ILogViewAdaptorFactory SetupLogConsistencyProvider(IGrainContext activationContext) { var attr = this.GetType().GetCustomAttributes(true).FirstOrDefault(); + var serviceProvider = ServiceProvider!; ILogViewAdaptorFactory? defaultFactory = attr != null - ? this.ServiceProvider.GetKeyedService(attr.ProviderName) - : this.ServiceProvider.GetService(); + ? serviceProvider.GetKeyedService(attr.ProviderName) + : serviceProvider.GetService(); if (attr != null && defaultFactory == null) { var errMsg = $"Cannot find consistency provider with Name={attr.ProviderName} for grain type {this.GetType().FullName}"; diff --git a/src/Orleans.Journaling/DurableGrain.cs b/src/Orleans.Journaling/DurableGrain.cs index 7ffec5d7c2a..b54ef738ffb 100644 --- a/src/Orleans.Journaling/DurableGrain.cs +++ b/src/Orleans.Journaling/DurableGrain.cs @@ -6,7 +6,7 @@ public abstract class DurableGrain : Grain, IGrainBase { protected DurableGrain() { - StateManager = ServiceProvider.GetRequiredService(); + StateManager = ServiceProvider!.GetRequiredService(); if (StateManager is ILifecycleParticipant participant) { participant.Participate(((IGrainBase)this).GrainContext.ObservableLifecycle); @@ -16,7 +16,7 @@ protected DurableGrain() protected IJournaledStateManager StateManager { get; } protected TState GetOrCreateState(string name) where TState : class, IJournaledState - => GetOrCreateState(name, static sp => sp.GetRequiredService(), ServiceProvider); + => GetOrCreateState(name, static sp => sp.GetRequiredService(), ServiceProvider!); protected TState GetOrCreateState(string name, Func createState, TArg arg) where TState : class, IJournaledState { diff --git a/src/Orleans.TestingHost/TestStorageProviders/StorageFaultGrain.cs b/src/Orleans.TestingHost/TestStorageProviders/StorageFaultGrain.cs index 9c512aeb969..860676facb4 100644 --- a/src/Orleans.TestingHost/TestStorageProviders/StorageFaultGrain.cs +++ b/src/Orleans.TestingHost/TestStorageProviders/StorageFaultGrain.cs @@ -23,7 +23,7 @@ public partial class StorageFaultGrain : Grain, IStorageFaultGrain public override async Task OnActivateAsync(CancellationToken cancellationToken) { await base.OnActivateAsync(cancellationToken); - logger = this.ServiceProvider.GetService()!.CreateLogger($"{typeof (StorageFaultGrain).FullName}-{IdentityString}-{RuntimeIdentity}"); + logger = ServiceProvider!.GetService()!.CreateLogger($"{typeof (StorageFaultGrain).FullName}-{IdentityString}-{RuntimeIdentity}"); readFaults = new(); writeFaults = new(); clearfaults = new(); diff --git a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs index 4939201dc72..a4d5d336034 100644 --- a/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs +++ b/src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs @@ -1071,7 +1071,7 @@ protected Grain(Runtime.IGrainContext grainContext, Runtime.IGrainRuntime? grain public string RuntimeIdentity { get { throw null; } } - protected internal System.IServiceProvider ServiceProvider { get { throw null; } } + protected internal System.IServiceProvider? ServiceProvider { get { throw null; } } protected void DeactivateOnIdle() { } diff --git a/test/Grains/TestGrains/MessageSerializationGrain.cs b/test/Grains/TestGrains/MessageSerializationGrain.cs index 50342e7cac0..69b746c8872 100644 --- a/test/Grains/TestGrains/MessageSerializationGrain.cs +++ b/test/Grains/TestGrains/MessageSerializationGrain.cs @@ -59,8 +59,9 @@ private async Task GetGrainOnOtherSilo() IMessageSerializationGrain otherGrain; var id = this.GetPrimaryKeyLong(); var currentSiloIdentity = await this.GetSiloIdentity(); - var silos = ServiceProvider.GetRequiredService().CurrentSnapshot.Members.Where(kv => kv.Value.Status == SiloStatus.Active).Select(kv => kv.Key).ToHashSet(); - silos.Remove(ServiceProvider.GetRequiredService().SiloAddress); + var serviceProvider = ServiceProvider!; + var silos = serviceProvider.GetRequiredService().CurrentSnapshot.Members.Where(kv => kv.Value.Status == SiloStatus.Active).Select(kv => kv.Key).ToHashSet(); + silos.Remove(serviceProvider.GetRequiredService().SiloAddress); while (true) { RequestContext.Set(IPlacementDirector.PlacementHintKey, silos.First()); diff --git a/test/Grains/TestGrains/ProgrammaticSubscribe/SubscribeGrain.cs b/test/Grains/TestGrains/ProgrammaticSubscribe/SubscribeGrain.cs index e56dfc0a2fd..680199aa6b6 100644 --- a/test/Grains/TestGrains/ProgrammaticSubscribe/SubscribeGrain.cs +++ b/test/Grains/TestGrains/ProgrammaticSubscribe/SubscribeGrain.cs @@ -14,7 +14,7 @@ public class SubscribeGrain : Grain, ISubscribeGrain { public Task CanGetSubscriptionManager(string providerName) { - return Task.FromResult(this.ServiceProvider.GetKeyedService(providerName)!.TryGetStreamSubscriptionManager(out _)); // The test configures the named provider before invoking this grain. + return Task.FromResult(ServiceProvider!.GetKeyedService(providerName)!.TryGetStreamSubscriptionManager(out _)); // The test configures the named provider before invoking this grain. } } diff --git a/test/Grains/TestGrains/SimpleDIGrain.cs b/test/Grains/TestGrains/SimpleDIGrain.cs index 060f012f75f..5768caf5ddc 100644 --- a/test/Grains/TestGrains/SimpleDIGrain.cs +++ b/test/Grains/TestGrains/SimpleDIGrain.cs @@ -65,9 +65,10 @@ public Task DoDeactivate() public Task AssertCanResolveSameServiceInstances() { - if (!ReferenceEquals(this.ServiceProvider.GetRequiredService(), this.injectedService)) throw new Exception("singleton not equal"); - if (!ReferenceEquals(this.ServiceProvider.GetRequiredService(), this.injectedScopedService)) throw new Exception("scoped not equal"); - if (!ReferenceEquals(this.ServiceProvider.GetRequiredService().GrainContext, this.originalGrainContext)) throw new Exception("scoped grain activation context not equal"); + var serviceProvider = ServiceProvider!; + if (!ReferenceEquals(serviceProvider.GetRequiredService(), this.injectedService)) throw new Exception("singleton not equal"); + if (!ReferenceEquals(serviceProvider.GetRequiredService(), this.injectedScopedService)) throw new Exception("scoped not equal"); + if (!ReferenceEquals(serviceProvider.GetRequiredService().GrainContext, this.originalGrainContext)) throw new Exception("scoped grain activation context not equal"); return Task.CompletedTask; } diff --git a/test/Grains/TestInternalGrains/CollectionTestGrain.cs b/test/Grains/TestInternalGrains/CollectionTestGrain.cs index 7e7b573a8c2..a8c996a603d 100644 --- a/test/Grains/TestInternalGrains/CollectionTestGrain.cs +++ b/test/Grains/TestInternalGrains/CollectionTestGrain.cs @@ -29,7 +29,7 @@ protected virtual ILogger Logger() public override Task OnActivateAsync(CancellationToken cancellationToken) { - logger = this.ServiceProvider.GetRequiredService() + logger = ServiceProvider!.GetRequiredService() .CreateLogger(string.Format("CollectionTestGrain {0} {1} on {2}.", GrainId, _grainContext.ActivationId, RuntimeIdentity)); logger.LogInformation("OnActivateAsync."); activated = DateTime.UtcNow; @@ -127,7 +127,7 @@ protected override ILogger Logger() public override Task OnActivateAsync(CancellationToken cancellationToken) { - logger = this.ServiceProvider.GetRequiredService() + logger = ServiceProvider!.GetRequiredService() .CreateLogger($"CollectionTestGrain {GrainId} {_grainContext.ActivationId} on {RuntimeIdentity}."); logger.LogInformation("OnActivateAsync."); counter = 0; diff --git a/test/Grains/TestInternalGrains/PersistenceTestGrains.cs b/test/Grains/TestInternalGrains/PersistenceTestGrains.cs index 0744ba0d0e0..6c0e64189e8 100644 --- a/test/Grains/TestInternalGrains/PersistenceTestGrains.cs +++ b/test/Grains/TestInternalGrains/PersistenceTestGrains.cs @@ -67,7 +67,7 @@ public Task CheckStateInit() public Task CheckProviderType() { - IGrainStorage grainStorage = GrainStorageHelpers.GetGrainStorage(GetType(), this.ServiceProvider); + IGrainStorage grainStorage = GrainStorageHelpers.GetGrainStorage(GetType(), ServiceProvider!); Assert.NotNull(grainStorage); return Task.FromResult(grainStorage.GetType().FullName!); } diff --git a/test/Grains/TestInternalGrains/PlacementTestGrain.cs b/test/Grains/TestInternalGrains/PlacementTestGrain.cs index f8cfd8da9cf..6b8f13edcb0 100644 --- a/test/Grains/TestInternalGrains/PlacementTestGrain.cs +++ b/test/Grains/TestInternalGrains/PlacementTestGrain.cs @@ -252,7 +252,7 @@ internal class DefaultPlacementGrain : Grain, IDefaultPlacementGrain { public Task GetDefaultPlacement() { - var defaultStrategy = this.ServiceProvider.GetRequiredService(); + var defaultStrategy = ServiceProvider!.GetRequiredService(); return Task.FromResult(defaultStrategy); } } diff --git a/test/Grains/TestInternalGrains/SerializerPresenceTestGrain.cs b/test/Grains/TestInternalGrains/SerializerPresenceTestGrain.cs index 3aedb9058fa..4a47e836def 100644 --- a/test/Grains/TestInternalGrains/SerializerPresenceTestGrain.cs +++ b/test/Grains/TestInternalGrains/SerializerPresenceTestGrain.cs @@ -8,7 +8,7 @@ internal class SerializerPresenceTestGrain : Grain, ISerializerPresenceTest { public Task SerializerExistsForType(Type t) { - return Task.FromResult(this.ServiceProvider.GetRequiredService().CanSerialize(t)); + return Task.FromResult(ServiceProvider!.GetRequiredService().CanSerialize(t)); } public Task TakeSerializedData(object data) diff --git a/test/Grains/TestInternalGrains/StreamingGrain.cs b/test/Grains/TestInternalGrains/StreamingGrain.cs index 12eb03dd38b..99ab1526b13 100644 --- a/test/Grains/TestInternalGrains/StreamingGrain.cs +++ b/test/Grains/TestInternalGrains/StreamingGrain.cs @@ -467,7 +467,7 @@ public Streaming_ProducerGrain(IGrainContext grainContext) public override Task OnActivateAsync(CancellationToken cancellationToken) { var activationId = _grainContext.ActivationId; - _logger = this.ServiceProvider.GetRequiredService().CreateLogger("Test.Streaming_ProducerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); + _logger = ServiceProvider!.GetRequiredService().CreateLogger("Test.Streaming_ProducerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); _logger.LogInformation("OnActivateAsync"); _producers = new List(); _cleanedUpFlag = new InterlockedFlag(); @@ -584,7 +584,7 @@ public override async Task OnActivateAsync(CancellationToken cancellationToken) { await base.OnActivateAsync(cancellationToken); var activationId = _grainContext.ActivationId; - _logger = this.ServiceProvider.GetRequiredService().CreateLogger("Test.PersistentStreaming_ProducerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); + _logger = ServiceProvider!.GetRequiredService().CreateLogger("Test.PersistentStreaming_ProducerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); _logger.LogInformation("OnActivateAsync"); if (State.Producers == null) { @@ -658,7 +658,7 @@ public Streaming_ConsumerGrain(IGrainContext grainContext) public override Task OnActivateAsync(CancellationToken cancellationToken) { var activationId = _grainContext.ActivationId; - _logger = this.ServiceProvider.GetRequiredService().CreateLogger("Test.Streaming_ConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); + _logger = ServiceProvider!.GetRequiredService().CreateLogger("Test.Streaming_ConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); _logger.LogInformation("OnActivateAsync"); _observers = new List(); return Task.CompletedTask; @@ -722,7 +722,7 @@ public override async Task OnActivateAsync(CancellationToken cancellationToken) { await base.OnActivateAsync(cancellationToken); var activationId = _grainContext.ActivationId; - _logger = this.ServiceProvider.GetRequiredService().CreateLogger("Test.PersistentStreaming_ConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); + _logger = ServiceProvider!.GetRequiredService().CreateLogger("Test.PersistentStreaming_ConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); _logger.LogInformation("OnActivateAsync"); if (State.Consumers == null) @@ -770,7 +770,7 @@ public class Streaming_Reentrant_ProducerConsumerGrain : Streaming_ProducerConsu public override async Task OnActivateAsync(CancellationToken cancellationToken) { var activationId = RuntimeContext.Current!.ActivationId; - _logger = this.ServiceProvider.GetRequiredService().CreateLogger("Test.Streaming_Reentrant_ProducerConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId) ; + _logger = ServiceProvider!.GetRequiredService().CreateLogger("Test.Streaming_Reentrant_ProducerConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId) ; _logger.LogInformation("OnActivateAsync"); await base.OnActivateAsync(cancellationToken); } @@ -786,7 +786,7 @@ public class Streaming_ProducerConsumerGrain : Grain, IStreaming_ProducerConsume public override Task OnActivateAsync(CancellationToken cancellationToken) { var activationId = RuntimeContext.Current!.ActivationId; - _logger = this.ServiceProvider.GetRequiredService().CreateLogger("Test.Streaming_ProducerConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); + _logger = ServiceProvider!.GetRequiredService().CreateLogger("Test.Streaming_ProducerConsumerGrain " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); _logger.LogInformation("OnActivateAsync"); return Task.CompletedTask; } @@ -897,7 +897,7 @@ public abstract class Streaming_ImplicitlySubscribedConsumerGrainBase : Grain, I public override Task OnActivateAsync(CancellationToken cancellationToken) { var activationId = RuntimeContext.Current!.ActivationId; - _logger = this.ServiceProvider.GetRequiredService().CreateLogger("Test.Streaming_ImplicitConsumerGrain1 " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); + _logger = ServiceProvider!.GetRequiredService().CreateLogger("Test.Streaming_ImplicitConsumerGrain1 " + RuntimeIdentity + "/" + IdentityString + "/" + activationId); _logger.LogInformation("{Type}.OnActivateAsync", GetType().FullName); _observers = new Dictionary(); return Task.CompletedTask; diff --git a/test/Grains/TestInternalGrains/TestGrain.cs b/test/Grains/TestInternalGrains/TestGrain.cs index ba96cbb8579..64ef918f56c 100644 --- a/test/Grains/TestInternalGrains/TestGrain.cs +++ b/test/Grains/TestInternalGrains/TestGrain.cs @@ -204,7 +204,7 @@ public Task GetActivationId() return Task.FromResult(_id); } - public Task GetSiloAddress() => Task.FromResult(ServiceProvider.GetRequiredService().SiloAddress); + public Task GetSiloAddress() => Task.FromResult(ServiceProvider!.GetRequiredService().SiloAddress); } internal class OneWayGrain : Grain, IOneWayGrain, ISimpleGrainObserver @@ -219,8 +219,8 @@ internal class OneWayGrain : Grain, IOneWayGrain, ISimpleGrainObserver public OneWayGrain(GrainLocator grainLocator) => this.grainLocator = grainLocator; - private ILocalGrainDirectory LocalGrainDirectory => this.ServiceProvider.GetRequiredService(); - private ILocalSiloDetails LocalSiloDetails => this.ServiceProvider.GetRequiredService(); + private ILocalGrainDirectory LocalGrainDirectory => ServiceProvider!.GetRequiredService(); + private ILocalSiloDetails LocalSiloDetails => ServiceProvider!.GetRequiredService(); public Task Notify() { @@ -264,7 +264,7 @@ public async Task GetOtherGrain() async Task GetGrainOnOtherSilo() { - var silos = ServiceProvider.GetRequiredService().CurrentSnapshot.Members.Where(kv => kv.Value.Status == SiloStatus.Active).Select(kv => kv.Key).ToHashSet(); + var silos = ServiceProvider!.GetRequiredService().CurrentSnapshot.Members.Where(kv => kv.Value.Status == SiloStatus.Active).Select(kv => kv.Key).ToHashSet(); var thisSilo = await this.GetSiloAddress(); silos.Remove(thisSilo); while (true) diff --git a/test/Orleans.Placement.Tests/ActivationRepartitioningTests/CustomToleranceTests.cs b/test/Orleans.Placement.Tests/ActivationRepartitioningTests/CustomToleranceTests.cs index 3094cd53d82..ad6b511a7b6 100644 --- a/test/Orleans.Placement.Tests/ActivationRepartitioningTests/CustomToleranceTests.cs +++ b/test/Orleans.Placement.Tests/ActivationRepartitioningTests/CustomToleranceTests.cs @@ -185,13 +185,13 @@ public async Task FirstPing(SiloAddress silo2) public Task GetAddress() => Task.FromResult(GrainContext.Address.SiloAddress!); public override Task OnActivateAsync(CancellationToken cancellationToken) { - ServiceProvider.GetRequiredService>().LogInformation("Activating {GrainId} on silo {SiloAddress}", this.GrainId, this.Runtime.SiloAddress); + ServiceProvider!.GetRequiredService>().LogInformation("Activating {GrainId} on silo {SiloAddress}", this.GrainId, this.Runtime.SiloAddress); return base.OnActivateAsync(cancellationToken); } public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) { - ServiceProvider.GetRequiredService>().LogInformation("Deactivating {GrainId} on silo {SiloAddress}. Reason: {Reason}", this.GrainId, this.Runtime.SiloAddress, reason); + ServiceProvider!.GetRequiredService>().LogInformation("Deactivating {GrainId} on silo {SiloAddress}. Reason: {Reason}", this.GrainId, this.Runtime.SiloAddress, reason); return base.OnDeactivateAsync(reason, cancellationToken); } } @@ -204,13 +204,13 @@ public class F : Grain, IF public override Task OnActivateAsync(CancellationToken cancellationToken) { - ServiceProvider.GetRequiredService>().LogInformation("Activating {GrainId} on silo {SiloAddress}", this.GrainId, this.Runtime.SiloAddress); + ServiceProvider!.GetRequiredService>().LogInformation("Activating {GrainId} on silo {SiloAddress}", this.GrainId, this.Runtime.SiloAddress); return base.OnActivateAsync(cancellationToken); } public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) { - ServiceProvider.GetRequiredService>().LogInformation("Deactivating {GrainId} on silo {SiloAddress}. Reason: {Reason}", this.GrainId, this.Runtime.SiloAddress, reason); + ServiceProvider!.GetRequiredService>().LogInformation("Deactivating {GrainId} on silo {SiloAddress}. Reason: {Reason}", this.GrainId, this.Runtime.SiloAddress, reason); return base.OnDeactivateAsync(reason, cancellationToken); } } @@ -227,13 +227,13 @@ public class X : Grain, IX public override Task OnActivateAsync(CancellationToken cancellationToken) { - ServiceProvider.GetRequiredService>().LogInformation("Activating {GrainId} on silo {SiloAddress}", this.GrainId, this.Runtime.SiloAddress); + ServiceProvider!.GetRequiredService>().LogInformation("Activating {GrainId} on silo {SiloAddress}", this.GrainId, this.Runtime.SiloAddress); return base.OnActivateAsync(cancellationToken); } public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) { - ServiceProvider.GetRequiredService>().LogInformation("Deactivating {GrainId} on silo {SiloAddress}. Reason: {Reason}", this.GrainId, this.Runtime.SiloAddress, reason); + ServiceProvider!.GetRequiredService>().LogInformation("Deactivating {GrainId} on silo {SiloAddress}. Reason: {Reason}", this.GrainId, this.Runtime.SiloAddress, reason); return base.OnDeactivateAsync(reason, cancellationToken); } } diff --git a/test/TestInfrastructure/Orleans.TestingHost.Tests/Grains/BlockingCallGrains.cs b/test/TestInfrastructure/Orleans.TestingHost.Tests/Grains/BlockingCallGrains.cs index bdb5ac6feef..6627bf46ce1 100644 --- a/test/TestInfrastructure/Orleans.TestingHost.Tests/Grains/BlockingCallGrains.cs +++ b/test/TestInfrastructure/Orleans.TestingHost.Tests/Grains/BlockingCallGrains.cs @@ -48,7 +48,7 @@ public static Task WaitForEntered(Guid key, TimeSpan timeout, Cancellation => GetState(key).Entered.WaitAsync(timeout, cancellationToken); public Task GetSiloIdentity() => - Task.FromResult(this.ServiceProvider.GetRequiredService().SiloAddress.ToString()); + Task.FromResult(ServiceProvider!.GetRequiredService().SiloAddress.ToString()); public Task BlockUntilReleased() { @@ -87,7 +87,7 @@ public async Task CallRemote(IRemoteBlockerGrain remote) public class LauncherGrain : Grain, ILauncherGrain { public Task GetSiloIdentity() => - Task.FromResult(this.ServiceProvider.GetRequiredService().SiloAddress.ToString()); + Task.FromResult(ServiceProvider!.GetRequiredService().SiloAddress.ToString()); public Task StartBlockingCall(ILocalWorkerGrain worker, IRemoteBlockerGrain remote) {