Skip to content

Commit deda4ba

Browse files
authoredApr 13, 2024··
Always read grain state during activation if it has not been rehydrated (#8944)
* Reduce memory footprint of StateStorageBridge<TState>
1 parent 27e801b commit deda4ba

8 files changed

+88
-64
lines changed
 

‎src/Orleans.Runtime/Core/GrainRuntime.cs

+2-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
using System;
2-
using Microsoft.Extensions.Logging;
32
using Orleans.Core;
43
using Orleans.Timers;
54
using Orleans.Storage;
6-
using Orleans.Serialization.Serializers;
75

86
namespace Orleans.Runtime
97
{
108
internal class GrainRuntime : IGrainRuntime
119
{
12-
private readonly ILoggerFactory loggerFactory;
13-
private readonly IActivatorProvider activatorProvider;
1410
private readonly IServiceProvider serviceProvider;
1511
private readonly ITimerRegistry timerRegistry;
1612
private readonly IGrainFactory grainFactory;
@@ -19,17 +15,13 @@ public GrainRuntime(
1915
ILocalSiloDetails localSiloDetails,
2016
IGrainFactory grainFactory,
2117
ITimerRegistry timerRegistry,
22-
IServiceProvider serviceProvider,
23-
ILoggerFactory loggerFactory,
24-
IActivatorProvider activatorProvider)
18+
IServiceProvider serviceProvider)
2519
{
2620
SiloAddress = localSiloDetails.SiloAddress;
2721
SiloIdentity = SiloAddress.ToString();
2822
this.grainFactory = grainFactory;
2923
this.timerRegistry = timerRegistry;
3024
this.serviceProvider = serviceProvider;
31-
this.loggerFactory = loggerFactory;
32-
this.activatorProvider = activatorProvider;
3325
}
3426

3527
public string SiloIdentity { get; }
@@ -85,7 +77,7 @@ public IStorage<TGrainState> GetStorage<TGrainState>(IGrainContext grainContext)
8577
if (grainContext is null) throw new ArgumentNullException(nameof(grainContext));
8678
var grainType = grainContext.GrainInstance?.GetType() ?? throw new ArgumentNullException(nameof(IGrainContext.GrainInstance));
8779
IGrainStorage grainStorage = GrainStorageHelpers.GetGrainStorage(grainType, ServiceProvider);
88-
return new StateStorageBridge<TGrainState>("state", grainContext, grainStorage, this.loggerFactory, this.activatorProvider);
80+
return new StateStorageBridge<TGrainState>("state", grainContext, grainStorage);
8981
}
9082

9183
public static void CheckRuntimeContext(IGrainContext context)

‎src/Orleans.Runtime/Facet/Persistent/PersistentStateStorageFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ private static void ThrowMissingProviderException(IGrainContext context, IPersis
5656

5757
internal sealed class PersistentState<TState> : StateStorageBridge<TState>, IPersistentState<TState>, ILifecycleObserver
5858
{
59-
public PersistentState(string stateName, IGrainContext context, IGrainStorage storageProvider) : base(stateName, context, storageProvider, context.ActivationServices.GetRequiredService<ILoggerFactory>(), context.ActivationServices.GetRequiredService<IActivatorProvider>())
59+
public PersistentState(string stateName, IGrainContext context, IGrainStorage storageProvider) : base(stateName, context, storageProvider)
6060
{
6161
var lifecycle = context.ObservableLifecycle;
6262
lifecycle.Subscribe(RuntimeTypeNameFormatter.Format(GetType()), GrainLifecycleStage.SetupState, this);

‎src/Orleans.Runtime/Hosting/DefaultSiloServices.cs

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
using System.Collections.Generic;
4242
using Microsoft.Extensions.Configuration;
4343
using Orleans.Serialization.Internal;
44+
using Orleans.Core;
4445

4546
namespace Orleans.Hosting
4647
{
@@ -346,6 +347,7 @@ internal static void AddDefaultServices(ISiloBuilder builder)
346347
services.TryAddSingleton<IGrainStorageSerializer, JsonGrainStorageSerializer>();
347348
services.TryAddSingleton<IPersistentStateFactory, PersistentStateFactory>();
348349
services.TryAddSingleton(typeof(IAttributeToFactoryMapper<PersistentStateAttribute>), typeof(PersistentStateAttributeMapper));
350+
services.TryAddSingleton<StateStorageBridgeSharedMap>();
349351

350352
// IAsyncEnumerable support
351353
services.AddScoped<IAsyncEnumerableGrainExtension, AsyncEnumerableGrainExtension>();

‎src/Orleans.Runtime/Hosting/StorageProviderHostExtensions.cs

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
62
using Orleans.Storage;
7-
using System.Xml.Linq;
83
using Microsoft.Extensions.DependencyInjection;
9-
using Orleans.GrainDirectory;
104
using Orleans.Providers;
115
using Microsoft.Extensions.DependencyInjection.Extensions;
126

@@ -27,16 +21,19 @@ public static IServiceCollection AddGrainStorage<T>(this IServiceCollection coll
2721
where T : IGrainStorage
2822
{
2923
collection.AddKeyedSingleton<IGrainStorage>(name, (sp, key) => implementationFactory(sp, key as string));
24+
3025
// Check if it is the default implementation
3126
if (string.Equals(name, ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, StringComparison.Ordinal))
3227
{
3328
collection.TryAddSingleton(sp => sp.GetKeyedService<IGrainStorage>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME));
3429
}
30+
3531
// Check if the grain storage implements ILifecycleParticipant<ISiloLifecycle>
3632
if (typeof(ILifecycleParticipant<ISiloLifecycle>).IsAssignableFrom(typeof(T)))
3733
{
3834
collection.AddSingleton(s => (ILifecycleParticipant<ISiloLifecycle>)s.GetRequiredKeyedService<IGrainStorage>(name));
3935
}
36+
4037
return collection;
4138
}
4239
}

‎src/Orleans.Runtime/Storage/StateStorageBridge.cs

+70-28
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#nullable enable
22
using System;
3+
using System.Collections.Concurrent;
34
using System.Diagnostics.CodeAnalysis;
45
using System.Runtime.ExceptionServices;
56
using System.Threading.Tasks;
7+
using Microsoft.Extensions.DependencyInjection;
68
using Microsoft.Extensions.Logging;
79
using Orleans.Runtime;
810
using Orleans.Serialization.Activators;
@@ -19,11 +21,8 @@ namespace Orleans.Core
1921
/// <seealso cref="IStorage{TState}" />
2022
public class StateStorageBridge<TState> : IStorage<TState>, IGrainMigrationParticipant
2123
{
22-
private readonly string _name;
2324
private readonly IGrainContext _grainContext;
24-
private readonly IGrainStorage _store;
25-
private readonly ILogger _logger;
26-
private readonly IActivator<TState> _activator;
25+
private readonly StateStorageBridgeShared<TState> _shared;
2726
private GrainState<TState>? _grainState;
2827

2928
/// <inheritdoc/>
@@ -32,7 +31,12 @@ public TState State
3231
get
3332
{
3433
GrainRuntime.CheckRuntimeContext(RuntimeContext.Current);
35-
return GrainState.State;
34+
if (_grainState is { } grainState)
35+
{
36+
return grainState.State;
37+
}
38+
39+
return default!;
3640
}
3741

3842
set
@@ -42,28 +46,32 @@ public TState State
4246
}
4347
}
4448

45-
private GrainState<TState> GrainState => _grainState ??= new GrainState<TState>(_activator.Create());
46-
internal bool IsStateInitialized => _grainState != null;
49+
private GrainState<TState> GrainState => _grainState ??= new GrainState<TState>(_shared.Activator.Create());
50+
internal bool IsStateInitialized { get; private set; }
4751

4852
/// <inheritdoc/>
49-
public string? Etag { get => GrainState.ETag; set => GrainState.ETag = value; }
53+
public string? Etag { get => _grainState?.ETag; set => GrainState.ETag = value; }
5054

5155
/// <inheritdoc/>
52-
public bool RecordExists => GrainState.RecordExists;
56+
public bool RecordExists => IsStateInitialized switch
57+
{
58+
true => GrainState.RecordExists,
59+
_ => throw new InvalidOperationException("State has not yet been loaded")
60+
};
61+
62+
[Obsolete("Use StateStorageBridge(string, IGrainContext, IGrainStorage) instead.")]
63+
public StateStorageBridge(string name, IGrainContext grainContext, IGrainStorage store, ILoggerFactory loggerFactory, IActivatorProvider activatorProvider) : this(name, grainContext, store)
64+
{ }
5365

54-
public StateStorageBridge(string name, IGrainContext grainContext, IGrainStorage store, ILoggerFactory loggerFactory, IActivatorProvider activatorProvider)
66+
public StateStorageBridge(string name, IGrainContext grainContext, IGrainStorage store)
5567
{
5668
ArgumentNullException.ThrowIfNull(name);
5769
ArgumentNullException.ThrowIfNull(grainContext);
5870
ArgumentNullException.ThrowIfNull(store);
59-
ArgumentNullException.ThrowIfNull(loggerFactory);
60-
ArgumentNullException.ThrowIfNull(activatorProvider);
6171

62-
_logger = loggerFactory.CreateLogger(store.GetType());
63-
_name = name;
6472
_grainContext = grainContext;
65-
_store = store;
66-
_activator = activatorProvider.GetActivator<TState>();
73+
var sharedInstances = ActivatorUtilities.GetServiceOrCreateInstance<StateStorageBridgeSharedMap>(grainContext.ActivationServices);
74+
_shared = sharedInstances.Get<TState>(name, store);
6775
}
6876

6977
/// <inheritdoc />
@@ -74,7 +82,8 @@ public async Task ReadStateAsync()
7482
GrainRuntime.CheckRuntimeContext(RuntimeContext.Current);
7583

7684
var sw = ValueStopwatch.StartNew();
77-
await _store.ReadStateAsync(_name, _grainContext.GrainId, GrainState);
85+
await _shared.Store.ReadStateAsync(_shared.Name, _grainContext.GrainId, GrainState);
86+
IsStateInitialized = true;
7887
StorageInstruments.OnStorageRead(sw.Elapsed);
7988
}
8089
catch (Exception exc)
@@ -92,7 +101,7 @@ public async Task WriteStateAsync()
92101
GrainRuntime.CheckRuntimeContext(RuntimeContext.Current);
93102

94103
var sw = ValueStopwatch.StartNew();
95-
await _store.WriteStateAsync(_name, _grainContext.GrainId, GrainState);
104+
await _shared.Store.WriteStateAsync(_shared.Name, _grainContext.GrainId, GrainState);
96105
StorageInstruments.OnStorageWrite(sw.Elapsed);
97106
}
98107
catch (Exception exc)
@@ -110,12 +119,13 @@ public async Task ClearStateAsync()
110119
GrainRuntime.CheckRuntimeContext(RuntimeContext.Current);
111120

112121
var sw = ValueStopwatch.StartNew();
122+
113123
// Clear (most likely Delete) state from external storage
114-
await _store.ClearStateAsync(_name, _grainContext.GrainId, GrainState);
124+
await _shared.Store.ClearStateAsync(_shared.Name, _grainContext.GrainId, GrainState);
115125
sw.Stop();
116126

117127
// Reset the in-memory copy of the state
118-
GrainState.State = _activator.Create();
128+
GrainState.State = _shared.Activator.Create();
119129

120130
// Update counters
121131
StorageInstruments.OnStorageDelete(sw.Elapsed);
@@ -131,11 +141,11 @@ public void OnDehydrate(IDehydrationContext dehydrationContext)
131141
{
132142
try
133143
{
134-
dehydrationContext.TryAddValue($"state.{_name}", _grainState);
144+
dehydrationContext.TryAddValue(_shared.MigrationContextKey, _grainState);
135145
}
136146
catch (Exception exception)
137147
{
138-
_logger.LogError(exception, "Failed to dehydrate state named {StateName} for grain {GrainId}", _name, _grainContext.GrainId);
148+
_shared.Logger.LogError(exception, "Failed to dehydrate state named {StateName} for grain {GrainId}", _shared.Name, _grainContext.GrainId);
139149

140150
// We must throw here since we do not know that the dehydration context is in a clean state after this.
141151
throw;
@@ -146,34 +156,66 @@ public void OnRehydrate(IRehydrationContext rehydrationContext)
146156
{
147157
try
148158
{
149-
rehydrationContext.TryGetValue($"state.{_name}", out _grainState);
159+
if (rehydrationContext.TryGetValue<GrainState<TState>>(_shared.MigrationContextKey, out var grainState))
160+
{
161+
_grainState = grainState;
162+
IsStateInitialized = true;
163+
}
150164
}
151165
catch (Exception exception)
152166
{
153167
// It is ok to swallow this exception, since state rehydration is best-effort.
154-
_logger.LogError(exception, "Failed to rehydrate state named {StateName} for grain {GrainId}", _name, _grainContext.GrainId);
168+
_shared.Logger.LogError(exception, "Failed to rehydrate state named {StateName} for grain {GrainId}", _shared.Name, _grainContext.GrainId);
155169
}
156170
}
157171

158172
[DoesNotReturn]
159173
private void OnError(Exception exception, ErrorCode id, string operation)
160174
{
161175
string? errorCode = null;
162-
(_store as IRestExceptionDecoder)?.DecodeException(exception, out _, out errorCode, true);
176+
(_shared.Store as IRestExceptionDecoder)?.DecodeException(exception, out _, out errorCode, true);
163177
var errorString = errorCode is { Length: > 0 } ? $" Error: {errorCode}" : null;
164178

165179
var grainId = _grainContext.GrainId;
166-
var providerName = _store.GetType().Name;
167-
_logger.LogError((int)id, exception, "Error from storage provider {ProviderName}.{StateName} during {Operation} for grain {GrainId}{ErrorCode}", providerName, _name, operation, grainId, errorString);
180+
var providerName = _shared.Store.GetType().Name;
181+
_shared.Logger.LogError((int)id, exception, "Error from storage provider {ProviderName}.{StateName} during {Operation} for grain {GrainId}{ErrorCode}", providerName, _shared.Name, operation, grainId, errorString);
168182

169183
// If error is not specialization of OrleansException, wrap it
170184
if (exception is not OrleansException)
171185
{
172-
var errMsg = $"Error from storage provider {providerName}.{_name} during {operation} for grain {grainId}{errorString}{Environment.NewLine} {LogFormatter.PrintException(exception)}";
186+
var errMsg = $"Error from storage provider {providerName}.{_shared.Name} during {operation} for grain {grainId}{errorString}{Environment.NewLine} {LogFormatter.PrintException(exception)}";
173187
throw new OrleansException(errMsg, exception);
174188
}
175189

176190
ExceptionDispatchInfo.Throw(exception);
177191
}
178192
}
193+
194+
internal sealed class StateStorageBridgeSharedMap(ILoggerFactory loggerFactory, IActivatorProvider activatorProvider)
195+
{
196+
private readonly ConcurrentDictionary<(string Name, IGrainStorage Store, Type StateType), object> _instances = new();
197+
private readonly ILoggerFactory _loggerFactory = loggerFactory;
198+
private readonly IActivatorProvider _activatorProvider = activatorProvider;
199+
200+
public StateStorageBridgeShared<TState> Get<TState>(string name, IGrainStorage store)
201+
=> (StateStorageBridgeShared<TState>)_instances.GetOrAdd(
202+
(name, store, typeof(TState)),
203+
static (key, self) => new StateStorageBridgeShared<TState>(
204+
key.Name,
205+
key.Store,
206+
self._loggerFactory.CreateLogger(key.Store.GetType()),
207+
self._activatorProvider.GetActivator<TState>()),
208+
this);
209+
}
210+
211+
internal sealed class StateStorageBridgeShared<TState>(string name, IGrainStorage store, ILogger logger, IActivator<TState> activator)
212+
{
213+
private string? _migrationContextKey;
214+
215+
public readonly string Name = name;
216+
public readonly IGrainStorage Store = store;
217+
public readonly ILogger Logger = logger;
218+
public readonly IActivator<TState> Activator = activator;
219+
public string MigrationContextKey => _migrationContextKey ??= $"state.{Name}";
220+
}
179221
}

‎src/Orleans.Streaming/PubSub/PubSubRendezvousGrain.cs

+1-4
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,11 @@ namespace Orleans.Streams
2020
internal sealed class PubSubGrainStateStorageFactory
2121
{
2222
private readonly IServiceProvider _serviceProvider;
23-
private readonly ILoggerFactory _loggerFactory;
2423
private readonly ILogger<PubSubGrainStateStorageFactory> _logger;
2524

2625
public PubSubGrainStateStorageFactory(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
2726
{
2827
_serviceProvider = serviceProvider;
29-
_loggerFactory = loggerFactory;
3028
_logger = loggerFactory.CreateLogger<PubSubGrainStateStorageFactory>();
3129
}
3230

@@ -57,8 +55,7 @@ public StateStorageBridge<PubSubGrainState> GetStorage(PubSubRendezvousGrain gra
5755
storage = _serviceProvider.GetRequiredKeyedService<IGrainStorage>(ProviderConstants.DEFAULT_PUBSUB_PROVIDER_NAME);
5856
}
5957

60-
var activatorProvider = _serviceProvider.GetRequiredService<IActivatorProvider>();
61-
return new(nameof(PubSubRendezvousGrain), grain.GrainContext, storage, _loggerFactory, activatorProvider);
58+
return new(nameof(PubSubRendezvousGrain), grain.GrainContext, storage);
6259
}
6360
}
6461

‎src/Orleans.Transactions/State/NamedTransactionalStateStorageFactory.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
using System;
22
using Microsoft.Extensions.DependencyInjection;
3-
using Microsoft.Extensions.Logging;
43
using Orleans.Runtime;
54
using Orleans.Transactions.Abstractions;
65
using Orleans.Storage;
7-
using Orleans.Serialization.Serializers;
86

97
namespace Orleans.Transactions
108
{
119
public class NamedTransactionalStateStorageFactory : INamedTransactionalStateStorageFactory
1210
{
1311
private readonly IGrainContextAccessor contextAccessor;
14-
private readonly ILoggerFactory loggerFactory;
1512

16-
public NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor, ILoggerFactory loggerFactory)
13+
[Obsolete("Use the NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor) constructor.")]
14+
public NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : this(contextAccessor)
15+
{
16+
}
17+
18+
public NamedTransactionalStateStorageFactory(IGrainContextAccessor contextAccessor)
1719
{
1820
this.contextAccessor = contextAccessor;
19-
this.loggerFactory = loggerFactory;
2021
}
2122

2223
public ITransactionalStateStorage<TState> Create<TState>(string storageName, string stateName)
@@ -37,8 +38,7 @@ public ITransactionalStateStorage<TState> Create<TState>(string storageName, str
3738

3839
if (grainStorage != null)
3940
{
40-
IActivatorProvider activatorProvider = currentContext.ActivationServices.GetRequiredService<IActivatorProvider>();
41-
return new TransactionalStateStorageProviderWrapper<TState>(grainStorage, stateName, currentContext, this.loggerFactory, activatorProvider);
41+
return new TransactionalStateStorageProviderWrapper<TState>(grainStorage, stateName, currentContext);
4242
}
4343

4444
throw (string.IsNullOrEmpty(storageName))

‎src/Orleans.Transactions/State/TransactionalStateStorageProviderWrapper.cs

+2-8
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
44
using System.Threading.Tasks;
5-
using Microsoft.Extensions.Logging;
65
using Orleans.Core;
76
using Orleans.Runtime;
8-
using Orleans.Serialization.Serializers;
97
using Orleans.Storage;
108
using Orleans.Transactions.Abstractions;
119

@@ -17,20 +15,16 @@ internal sealed class TransactionalStateStorageProviderWrapper<TState> : ITransa
1715
{
1816
private readonly IGrainStorage grainStorage;
1917
private readonly IGrainContext context;
20-
private readonly ILoggerFactory loggerFactory;
21-
private readonly IActivatorProvider activatorProvider;
2218
private readonly string stateName;
2319

2420
private StateStorageBridge<TransactionalStateRecord<TState>>? stateStorage;
2521
[MemberNotNull(nameof(stateStorage))]
2622
private StateStorageBridge<TransactionalStateRecord<TState>> StateStorage => stateStorage ??= GetStateStorage();
2723

28-
public TransactionalStateStorageProviderWrapper(IGrainStorage grainStorage, string stateName, IGrainContext context, ILoggerFactory loggerFactory, IActivatorProvider activatorProvider)
24+
public TransactionalStateStorageProviderWrapper(IGrainStorage grainStorage, string stateName, IGrainContext context)
2925
{
3026
this.grainStorage = grainStorage;
3127
this.context = context;
32-
this.loggerFactory = loggerFactory;
33-
this.activatorProvider = activatorProvider;
3428
this.stateName = stateName;
3529
}
3630

@@ -104,7 +98,7 @@ public async Task<string> Store(string expectedETag, TransactionalStateMetaData
10498

10599
private StateStorageBridge<TransactionalStateRecord<TState>> GetStateStorage()
106100
{
107-
return new(this.stateName, context, grainStorage, loggerFactory, activatorProvider);
101+
return new(this.stateName, context, grainStorage);
108102
}
109103
}
110104

0 commit comments

Comments
 (0)
Please sign in to comment.