Generic Grain Extensions fail to register with BindExtension<>
- Dominant language
- C#
- Stars
- 10.9k
- Forks
- 2.1k
- Avg merge
- 14h 42m
- Merged PRs (30d)
- 354
Description
We have attempted to use Grain Extensions to automatically implement some aspects of ISimpleObservable<> behaviour on a grain for multiple types.
I have put together a reduced test-case here https://github.com/ninjaoxygen/orleans-test-extensions which also shows the same code working correctly with non-generic extensions.
Ideally, we would like the following grain code to work:
```C#
public override Task OnActivateAsync()
{
_providerRuntime.BindExtension, ITestGenericExtension>(() =>
{
return new TestGenericExtension(_logger, _grainActivationContext);
});
_providerRuntime.BindExtension, ITestGenericExtension>(() =>
{
return new TestGenericExtension(_logger, _grainActivationContext);
});
}
```
When this is run with Orleans 3.5.1, the following exception is produced:
```
fail: TestExtensions.IExampleGrain[0]
DoBinding (T's generic arguments are ) exception: Orleans.Runtime.OrleansException: Cannot find an invoker for interface *unavailable* (ID=-333839287,0xEC1A0449).
at Orleans.Runtime.GrainTypeManager.GetInvoker(Int32 interfaceId, String genericGrainType)
at Orleans.Runtime.InsideRuntimeClient.TryGetExtensionMethodInvoker(GrainTypeManager typeManager, Type handlerType)
at Orleans.Runtime.InsideRuntimeClient.TryAddExtension(IGrainExtension handler)
at Orleans.Runtime.InsideRuntimeClient.BindExtension[TExtension,TExtensionInterface](Func`1 newExtensionFunc)
at Orleans.Runtime.Providers.SiloProviderRuntime.BindExtension[TExtension,TExtensionInterface](Func`1 newExtensionFunc)
at TestExtensions.ExampleGrain.DoBinding[T]() in C:\Caviar\source\repos\test-extensions\grains\ExampleGrain.cs:line 85
```
I spent some time debugging it, from the call-site end, so came to the conclusion the missing interfaceId needs to match the generated generic interfaceId.
I tried using the TypeCodeOverrideAttribute to force a matching typecode onto the generic interface definition and patched `InsideRuntimeClient.TryGetExtensionMethodInvoker` as follows:
```C#
internal static IGrainExtensionMethodInvoker TryGetExtensionMethodInvoker(GrainTypeManager typeManager, Type handlerType)
{
var interfaces = GrainInterfaceUtils.GetRemoteInterfaces(handlerType).Values;
if (interfaces.Count != 1)
throw new InvalidOperationException($"Extension type {handlerType.FullName} implements more than one grain interface.");
var firstInterface = interfaces.First();
if (firstInterface.IsConstructedGenericType)
{
firstInterface = firstInterface.GetGenericTypeDefinition();
}
var interfaceId = GrainInterfaceUtils.GetGrainInterfaceId(firstInterface);
var invoker = typeManager.GetInvoker(interfaceId, (handlerType.IsGenericType ? TypeUtils.GenericTypeArgsString(handlerType.UnderlyingSystemType.FullName) : null));
if (invoker != null)
return (IGrainExtensionMethodInvoker)invoker;
throw new ArgumentException(
$"Provider extension handler type {handlerType} was not found in the type manager",
nameof(handlerType));
}
```
which appeared to fix most of the problems, but is actually just masking them... that seems to associate all `ITestGenericExtension` objects with the first matching object registered by `BindExtension`.
Looking at the code from here, the problem seems to be that callers will always need to use the generic interfaceId, plus the provided genericArguments, and at the point where the extensions are looked up, the bound interfaceId will need to be used instead.
I'm having trouble finding that part of the implementation where the per-grain bound extension map is looked up.
Any ideas of whether we should approach this a different way, or go ahead with trying to fix generic grain extensions?
Contributor guide
Assessment
This issue has not been assessed yet.