Cannot activate the grain after clearing its state.
scalalang2 opened this issue · 5 comments
Environment
Persistent Storage Provider
: DynamoDBFramework Version
: Orleans 8.2.0
Background
The ClearStateAsync
funcction doesn't remove the entry from storage by default.
Instead it only sets the state value to null
,
If ReadStateAsync()
is invoked when the value is null,
then it occurs an error from Serializer. : Insufficient data present in buffer.
ReadStateAsync
is called when trying to activate the same grain- the serializer cannot read the value cleared by
ClearStateAsync
- the serializer cannot read the value cleared by
How to reproduce
public class TestGrain : Grain, ITestGrain
{
private readonly IPersistentState<TestState> state;
public TestGrain([PersistentState("Test")] IPersistentState<TestState> state)
{
this.state = state;
}
public async Task Test()
{
// Calling this method after clearing the state will trigger an error.
await this.state.ReadStateAsync();
if (this.state.Activated == false)
{
this.state.State.Activated = true;
await this.state.WriteStateAsync();
}
else
{
await this.state.ClearStateAsync();
}
}
}
Logs
System.InvalidOperationException: Insufficient data present in buffer.
at Orleans.Serialization.Buffers.Reader`1.ThrowInsufficientData() in /_/src/Orleans.Serialization/Buffers/Reader.cs:line 741
at Orleans.Serialization.Buffers.Reader`1.MoveNext() in /_/src/Orleans.Serialization/Buffers/Reader.cs:line 602
at Orleans.Serialization.Buffers.Reader`1.ReadByteSlow(Reader`1& reader) in /_/src/Orleans.Serialization/Buffers/Reader.cs:line 643
at Orleans.Serialization.Serializer.Deserialize[T](ReadOnlySpan`1 source) in /_/src/Orleans.Serialization/Serializer.cs:line 423
at Orleans.Serialization.Serializer.Deserialize[T](ReadOnlyMemory`1 source) in /_/src/Orleans.Serialization/Serializer.cs:line 466
at Orleans.Storage.OrleansGrainStorageSerializer.Deserialize[T](BinaryData input) in /_/src/Orleans.Core/Providers/StorageSerializer/OrleansGrainStateSerializer.cs:line 34
at Orleans.Storage.GrainStorageSerializerExtensions.Deserialize[T](IGrainStorageSerializer serializer, ReadOnlyMemory`1 input) in /_/src/Orleans.Core/Providers/IGrainStorageSerializer.cs:line 43
at Orleans.Storage.DynamoDBGrainStorage.ConvertFromStorageFormat[T](GrainStateRecord entity) in /_/src/AWS/Orleans.Persistence.DynamoDB/Provider/DynamoDBGrainStorage.cs:line 330
2024-09-30 14:17:38 [ERR] Error from storage provider DynamoDBGrainStorage.Test during ReadStateAsync for grain test/Testnull - Orleans.Storage.DynamoDBGrainStorage
I had also investigated the AzureTableStorage code
it seems that same error should be triggerred since it can be empty.
Even if this is an intentional behavior
it needs to be fixed because it's currently causing issues with the Streaming functionality.
My Suggestion.
T dataValue = default;
try
{
if(entity.State.Length > 0)
dataValue = this.options.GrainStorageSerializer.Deserialize<T>(entity.State);
}
catch (Exception exc)
{
// handle errors.
}
@ReubenBond If you agree with the solution described above comment, I'll take this job.
@scalalang2 looks good to me, but we should make the if condition also check for null
.
At the call site, we should add an else
to the if (record != null)
check to re-initialize State
to a new instance by calling IActivator.Create<T>()
. This will involve:
- Resolving
IActivatorProvider
from theIServiceProvider
in the constructor and storing it in a field - Calling
_activatorProvider.GetActivator<T>().Create()
inReadStateAsync()
(whenrecord is null
after a successful read) andClearStateAsync()
after successfully clearing state.
How does that sound?
@ReubenBond Thanks for reply,
I understood that you suggested making changes as follows, Did I understand correctly?
DynamoDBGrainStorage.ReadStateAsync
if (record != null)
{
var loadedState = ConvertFromStorageFormat<T>(record);
grainState.RecordExists = loadedState != null;
grainState.State = loadedState ?? Activator.CreateInstance<T>();
grainState.ETag = record.ETag.ToString();
}
else
{
grainState.State = _activatorProvider.GetActivator<T>().Create()
}
DynamoDBGrainStorage.ClearStateAsync
if (this.options.DeleteStateOnClear)
{
// codes
}
else
{
// codes
}
grainState.State = _activatorProvider.GetActivator<T>().Create()
Additionally, I'd like to put the issue I've noticed with Streaming Functioanlity.
Once PubSubRendezvousGrain Grain clears its state, it couldn't reactivate in subsequent operations.
-> this would be solved after changing the code as above.