dotnet / orleans

Cloud Native application framework for .NET
https://docs.microsoft.com/dotnet/orleans
MIT License
10.06k stars 2.03k forks source link

Not support generic type on Grain. #4012

Closed darting closed 9 months ago

darting commented 6 years ago

Hi there, is there any way to support generic type on an Grain? e.g.

    type GameGrain<'GameState, 'GameAction> (engine: IGameEngine<'GameState, 'GameAction>) = 
        inherit Grain ()

        interface IGame<'GameState, 'GameAction> with
            member __.GetState () =
                engine.Zero () |> Task.FromResult

            member __.Play prevState action = 
                engine.Reducer prevState action |> Task.FromResult

** https://github.com/darting/orleans-fsharp/blob/master/src/Grains/Library.fs#L19-L27

If I try to get a grain uses:

let grain = client.GetGrain<IGame<Game1.GameState, Game1.GameAction>> "darting"

** https://github.com/darting/orleans-fsharp/blob/master/src/Client/Program.fs#L21 Orleans will throw exception

Cannot instantiate generic class Grains.Say.GameGrain`2[[System.Int32, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Games.Game1+GameAction, Games, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]
System.ArgumentException: An item with the same key has already been added. Key: Grains.Say.GameGrain`2
   at System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(Object key)
   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at Orleans.Runtime.GrainTypeManager.get_Item(String className)

here is my test repo https://github.com/darting/orleans-fsharp/blob/master/src/Client/Program.fs#L19 https://github.com/darting/orleans-fsharp/blob/master/src/Grains/Library.fs#L19 https://github.com/darting/orleans-fsharp/blob/master/src/Host/Program.fs#L12

Thanks

ReubenBond commented 6 years ago

Thank you for reporting, @darting. I'll try to address it soon

darting commented 6 years ago

hi @ReubenBond , I added a new module for testing but seems got another exceptions.

the code as below: https://github.com/darting/orleans-fsharp/blob/master/src/Games/Adventure.fs

https://github.com/darting/orleans-fsharp/blob/master/src/Client/Program.fs#L67

the exception error:

Unhandled Exception: System.AggregateException: One or more errors occurred. (Named type "Games.Games.Games.Adventure.PlayerStore.State" is invalid: Type string "Games.Games.Games.Adventure.PlayerStore.State" cannot be resolved.) ---> System.TypeAccessException: Named type "Games.Games.Games.Adventure.PlayerStore.State" is invalid: Type string "Games.Games.Games.Adventure.PlayerStore.State" cannot be resolved.
   at Orleans.Serialization.BinaryTokenStreamReaderExtensinons.ReadSpecifiedTypeHeader(IBinaryTokenStreamReader this, SerializationManager serializationManager)
   at Orleans.Serialization.BinaryTokenStreamReaderExtensinons.ReadFullTypeHeader(IBinaryTokenStreamReader this, SerializationManager serializationManager, Type expected)
   at Orleans.Serialization.BinaryTokenStreamReaderExtensinons.ReadSpecifiedTypeHeader(IBinaryTokenStreamReader this, SerializationManager serializationManager)
   at Orleans.Serialization.SerializationManager.DeserializeInner(Type expected, IDeserializationContext context)
   at Orleans.Serialization.BuiltInTypes.DeserializeOrleansResponse(Type expected, IDeserializationContext context)
   at Orleans.Serialization.SerializationManager.DeserializeInner(Type expected, IDeserializationContext context)
   at Orleans.Serialization.SerializationManager.Deserialize(Type t, IBinaryTokenStreamReader stream)
   at Orleans.Runtime.Message.GetDeserializedBody(SerializationManager serializationManager)
   at Orleans.Runtime.GrainReferenceRuntime.ResponseCallback(Message message, TaskCompletionSource`1 context)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Orleans.OrleansTaskExtentions.<<ToTypedTask>g__ConvertAsync4_0>d`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Program.worker4@80-2.Invoke(Unit unitVar0) in /orleans-fsharp/src/Client/Program.fs:line 80

to run the client project should enable this line: https://github.com/darting/orleans-fsharp/blob/master/src/Client/Program.fs#L113

Thanks

ReubenBond commented 6 years ago

@darting add the new module to application parts

darting commented 6 years ago

hi @ReubenBond , hmm why? for other modules I never add to application parts.

And I found that the error message telling (Named type "Games.Games.Games.Adventure.PlayerStore.State" is invalid: Type string "Games.Games.Games.Adventure.PlayerStore.State" cannot be resolved.).

But actually my type should be Games.Adventure.PlayerStore.State https://github.com/darting/orleans-fsharp/blob/master/src/Games/Adventure.fs#L32

ReubenBond commented 6 years ago

I'm taking a look at this today. I couldn't get your example to build/run, but I tried testing this class and it worked for me:

public interface IGame<TState, TAction> : IGrainWithStringKey
{
    Task<TState> Go(TAction act);
}

public class GameGrain<TState, TAction> : Orleans.Grain, IGame<TState, TAction>
{
    public Task<TState> Go(TAction act) => Task.FromResult(default(TState));
}

// Then I call the Go method in a startup task.
darting commented 6 years ago

@ReubenBond Hi, I was test on my Macbook, I will try to make a clear project. Will update here later.

darting commented 6 years ago

Hi @ReubenBond , I had create two branch for two cases; https://github.com/darting/orleans-fsharp/tree/testcase-1 https://github.com/darting/orleans-fsharp/tree/testcase-2

they got diff exceptions, the only difference between them is here: https://github.com/darting/orleans-fsharp/blob/testcase-1/src/Grains/Library.fs#L19 https://github.com/darting/orleans-fsharp/blob/testcase-2/src/Grains/Library.fs#L19

Hopefully they are clear.

Thanks

PS: already update to orleans 2.0-rc1

games commented 6 years ago

hi, @ReubenBond, Had you try like this in C#?

public interface IGame<TState, TAction> : IGrainWithStringKey
{
    Task<TState> Go(TState prevState, TAction act);
}

public interface IReducer<TState, TAction> {
   Task<TState> Handle(TState prevState, TAction act);
}

public class Reducer1 : IReducer<String, Reducer1Action> {
  // todo
}

public class Reducer2 : IReducer<Int32, Reducer2Action> {
  // todo
}

public class GameGrain<TState, TAction> : Orleans.Grain, IGame<TState, TAction>
{
   IReducer<TState, TAction> _reducer;
    public GameGrain(IReducer<TState, TAction> reducer) {
       _reducer = reducer;
    }
    public Task<TState> Go(TState prevState, TAction act) => _reducer.Handle(prevState, act);
}

And inject these two reducers to serviceCollection:

services.AddSingleton<IReducer<String, Reducer1Action>>(x => new Reducer1());
services.AddSingleton<IReducer<Int32, Reducer2Action>>(x => new Reducer2());

Call from Client:

var game1 = client.GetGrain<IGame<String, Reducer1Action>> ("reducer1");
var game2 = client.GetGrain<IGame<Int32, Reducer2Action>> ("reducer2");

Sorry it is too late here, I will try it myself tmw. Thanks.

ReubenBond commented 6 years ago

Oh, I understand, so the issue isn't with generic grains themselves, but with their generic parameters being used in constructor arguments. Thanks for the clarification, I'll take another look

ReubenBond commented 6 years ago

@darting am I doing this wrong? This is working for me:

[Fact]
public async Task CanUseGenericArgumentsInConstructor()
{
    var grain = this.fixture.GrainFactory.GetGrain<IReducerGameGrain<string, Reducer1Action>>("reducer1");
    Assert.NotNull(await grain.Go("378", new Reducer1Action()));
    var grain2 = this.fixture.GrainFactory.GetGrain<IReducerGameGrain<int, Reducer2Action>>("reducer2");
    Assert.NotEqual(0, await grain2.Go(378, new Reducer2Action()));
}

[Serializable]
public class Reducer1Action { }

[Serializable]
public class Reducer2Action { }

public class Reducer1 : IReducer<string, Reducer1Action>
{
    public Task<string> Handle(string prevState, Reducer1Action act) => Task.FromResult(prevState + act);
}

public class Reducer2 : IReducer<Int32, Reducer2Action>
{
    public Task<int> Handle(int prevState, Reducer2Action act) => Task.FromResult(prevState + act.ToString().Length);
}

public interface IReducerGameGrain<TState, TAction> : IGrainWithStringKey
{
    Task<TState> Go(TState prevState, TAction act);
}

public class ReducerGameGrain<TState, TAction> : Grain, IReducerGameGrain<TState, TAction>
{
    private readonly IReducer<TState, TAction> reducer;

    public ReducerGameGrain(IReducer<TState, TAction> reducer)
    {
        this.reducer = reducer;
    }

    public Task<TState> Go(TState prevState, TAction act) => this.reducer.Handle(prevState, act);
}
darting commented 6 years ago

hi @ReubenBond, I think in C# it is work well but F# doesn't work.

you can take a look this branch: https://github.com/darting/orleans-fsharp/tree/testcase-3

> cd src/Host
> dotnet run
> cd src/Client
> dotnet run
ReubenBond commented 6 years ago

Thanks for the sample. I'm debugging it now. It appears to be an issue with how nested generic types are formatted

ReubenBond commented 6 years ago

As a workaround until we can rectify this, @darting, you can use a namespace instead of a module so that GameGrain isn't encoded as a nested type.

darting commented 6 years ago

Wow , thanks.. use a namespace it is working

darting commented 6 years ago

hi @sergeybykov , is that means for v2 orleans will not fix the nested generic types issue on F#? since my project is pure f# just wondering if will encounter other issue?

Thanks

darting commented 6 years ago

@ReubenBond

I think I found another issue. Lets say:

I have an interface:

type IGame<'GameState, 'GameAction> = 
    abstract Reducer : 'GameState -> 'GameAction -> Task<'GameState>

And I have a grain :

type GameGrain<'State, 'Action> (store : IGameStore<'State, 'Action>) = 
    interface IGameActor<'State, 'Action> with
        member this.Reducer prev act = Task.FromResult prev

It is not work !!!

The diff is the generics type name is diff. In the interface I used 'GameState but for grain I used 'State

It will tell me that

Cannot find an implementation class for grain interface, Make sure the grain assembly was correctly deployed and loaded in the silo.
michaelsg commented 3 years ago
type IGame<'GameState, 'GameAction> = 
    abstract Reducer : 'GameState -> 'GameAction -> Task<'GameState>

@darting All my Orleans code is in F# so I ran into this early on. In F# we can get away with making interfaces with un-tupled arguments, but Orleans doesn't understand these. Also, give your parameters names.

type IGame<'GameState, 'GameAction> = 
     inherit IGrainWithSomeSortaKey
     abstract Reducer : state:'GameState * action:'GameAction -> Task<'GameState>

Give that a try.

ReubenBond commented 9 months ago

I believe this is fixed now (in v7.2.4+). If not, please open a new issue and reference this one.