NullReferenceException when activating stateless grain after upgrade to 3.6.2
- Dominant language
- C#
- Stars
- 10.9k
- Forks
- 2.1k
- Avg merge
- 15h 1m
- Merged PRs (30d)
- 345
Description
We recently upgraded from `3.5.1` to `3.6.2`.
Since then we get from time to time this exception (note that it works sometimes!):
```
System.NullReferenceException: Object reference not set to an instance of an object.
at ActivationData Orleans.Runtime.Catalog.GetOrCreateActivation(ActivationAddress address, bool newPlacement, string grainType, string genericArguments, Dictionary requestContextData, out Task activatedPromise) in /_/src/Orleans.Runtime/Catalog/Catalog.cs:line 585
at void Orleans.Runtime.Dispatcher.ReceiveMessage(Message message) in /_/src/Orleans.Runtime/Core/Dispatcher.cs:line 210
```
This grain is `stateless` and activated **a lot** of times.
It looks like this:
```
[StatelessWorker(maxLocalWorkers: 3)]
[StorageProvider(ProviderName = "datastore")]
internal class ReadOnlyProductGrain : Grain, IReadOnlyProductGrain
{
private Interfaces.ProductData.Product product;
private long? brandId;
private long? productTypeId;
private readonly ILogger logger;
public ReadOnlyProductGrain(ILogger logger)
{
this.logger = logger;
}
public override async Task OnActivateAsync()
{
try
{
if (State != null && !State.NeedsInitialization())
{
product = new Interfaces.ProductData.Product
{
Availability = State.Availability,
Price = State.Price,
IsPublic = State.IsPublic,
IsOrderingAllowed = State.IsOrderingAllowed
};
brandId = State.Brand?.Id;
productTypeId = State.ProductType.Id;
}
else
{
var productGrain = GrainFactory.GetGrain(this.GetPrimaryKeyString());
product = await productGrain.GetProduct();
brandId = await productGrain.GetBrandId();
productTypeId = await productGrain.GetProductTypeId();
}
}
catch (Exception e)
{
logger.LogError(e, "Exception during grain activation");
throw;
}
}
public Task GetBrandId()
{
DeactivateOnIdle();
return Task.FromResult(brandId);
}
public Task GetProductTypeId()
{
DeactivateOnIdle();
return Task.FromResult(productTypeId);
}
public Task GetPrice()
{
DeactivateOnIdle();
return Task.FromResult(product.Price);
}
public Task GetAvailability()
{
DeactivateOnIdle();
return Task.FromResult(product.Availability);
}
public Task IsOrderingAllowed()
{
DeactivateOnIdle();
return Task.FromResult(product.IsOrderingAllowed);
}
public Task IsPublic()
{
DeactivateOnIdle();
return Task.FromResult(product.IsPublic);
}
public Task GetProduct()
{
DeactivateOnIdle();
return Task.FromResult(product);
}
}
}
```
You may notice the many `DeactivateOnIdle` calls, because we don't want the Grain to stay activated / in memory.
So as a workaround we removed all this `DeactivateOnIdle` calls and instead added a very short time period when the graind should be deactivated:
```
.Configure(options =>
{
options.ClassSpecificCollectionAge[typeof(ReadOnlyProductGrain).FullName!] =
TimeSpan.FromSeconds(61);
})
```
So bascially the question is:
Is this a bug or are we doing something wrong? I.e. should we not call `DeactivateOnIdle` that often for a grain which is activated a lot of times?
Contributor guide
Assessment
This issue has not been assessed yet.