Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Fabric Actor notification fails to trigger event handler after first invocation due to possible blocking #213

Open
dasiths opened this issue Jul 5, 2019 · 0 comments
Assignees
Labels

Comments

@dasiths
Copy link
Member

dasiths commented Jul 5, 2019

I've got 2 Reliable actors called GameActor and PlayerActor. The ClientApp send a message to the PlayerActor when the player makes a move. Then the PlayerActor sends a message to the GameActor to indicate a movement was made. Upon being invoked, the method in the GameActor fires a notification. This notification gets handled by the ClientApp GameEventsHandler. The ClientApp then calls a method on the GameActor to retrieve the latest player positions.

ClientApp -> PlayerActor.MoveTo() -> GameActor.NotifyPlayerMoved() ->
Fire ScoreBoardUpdated event

GameEventsHandler triggered by that event ->
GameActor.GetLatestPlayerInfo()

The problem I'm having is this. The very first time I run it, the GameEventsHandler gets triggered and it tries to call the GameActor as expected. The GameActor receives the message and returns the response expected. But the client doesn't seem to receive the message. It looks like it's blocked as it doesn't throw and error or any output. Any subsequent notifications don't get handled by the event handler at all.

GameActor

        public async Task<IList<PlayerInfo>> GetLatestPlayerInfoAsync(CancellationToken cancellationToken)
        {
            var allPlayers = await StateManager.GetStateAsync<List<string>>("players", cancellationToken);

            var tasks = allPlayers.Select(actorName =>
            {
                var playerActor = ActorProxy.Create<IPlayerActor>(new ActorId(actorName), new Uri(PlayerActorUri));
                return playerActor.GetLatestInfoAsync(cancellationToken);
            }).ToList();

            await Task.WhenAll(tasks);

            return tasks
                .Select(t => t.Result)
                .ToList();
        }

        public async Task NotifyPlayerMovedAsync(PlayerInfo lastMovement, CancellationToken cancellationToken)
        {
            var ev = GetEvent<IGameEvents>();
            ev.ScoreboardUpdated(lastMovement);
        }

PlayerActor

        public async Task MoveToAsync(int x, int y, CancellationToken cancellationToken)
        {
            var playerName = await StateManager.GetStateAsync<string>("playerName", cancellationToken);
            var playerInfo = new PlayerInfo()
            {
                LastUpdate = DateTimeOffset.Now,
                PlayerName = playerName,
                XCoordinate = x,
                YCoordinate = y
            };

            await StateManager.AddOrUpdateStateAsync("positions", new List<PlayerInfo>() { playerInfo }, (key, value) =>
            {
                value.Add(playerInfo);
                return value;
            }, cancellationToken);

            var gameName = await StateManager.GetStateAsync<string>("gameName", cancellationToken);
            var gameActor = ActorProxy.Create<IGameActor>(new ActorId(gameName), new Uri(GameActorUri));
            await gameActor.NotifyPlayerMovedAsync(playerInfo, cancellationToken);
        }

        public async Task<PlayerInfo> GetLatestInfoAsync(CancellationToken cancellationToken)
        {
            var positions = await StateManager.GetStateAsync<List<PlayerInfo>>("positions", cancellationToken);
            return positions.Last();
        }

Client

        private static async Task RunDemo(string gameName)
        {
            var rand = new Random();
            Console.WriteLine("Hit return when the service is up...");
            Console.ReadLine();
            Console.WriteLine("Enter your name:");
            var playerName = Console.ReadLine();

            Console.WriteLine("This might take a few seconds...");
            var gameActor = ActorProxy.Create<IGameActor>(new ActorId(gameName), new Uri(GameActorUri));
            await gameActor.SubscribeAsync<IGameEvents>(new GameEventsHandler(gameActor));

            var playerActorId = await gameActor.JoinGameAsync(playerName, CancellationToken.None);
            var playerActor = ActorProxy.Create<IPlayerActor>(new ActorId(playerActorId), new Uri(PlayerActorUri));

            while (true)
            {
                Console.WriteLine("Press return to move to new location...");
                Console.ReadLine();
                
                await playerActor.MoveToAsync(rand.Next(100), rand.Next(100), CancellationToken.None);
            }
        }

GameEventHandler

        public void ScoreboardUpdated(PlayerInfo lastInfo)
        {
            Console.WriteLine($"Scoreboard updated. (Last move by: {lastInfo.PlayerName})");

            var positions = _gameActor.GetLatestPlayerInfoAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); // this hangs forever

            foreach (var playerInfo in positions) // this line never gits hit
            {
                Console.WriteLine(
                    $"Position of {playerInfo.PlayerName} is ({playerInfo.XCoordinate},{playerInfo.YCoordinate})." +
                    $"\nUpdated at {playerInfo.LastUpdate}\n");
            }            
            
        }

But if I wrap the event handler logic inside a Task.Run() it seems to work.

            Task.Run(async () =>
                {
                    var positions = await _gameActor.GetLatestPlayerInfoAsync(CancellationToken.None);

                    foreach (var playerInfo in positions)
                    {
                        Console.WriteLine(
                            $"Position of {playerInfo.PlayerName} is ({playerInfo.XCoordinate},{playerInfo.YCoordinate})." +
                            $"\nUpdated at {playerInfo.LastUpdate}\n");
                    }
                }
            );

Full source code for the demo here https://github.com/dasiths/Service-Fabric-Reliable-Actors-Demo

AFAIK notifications aren't blocking and are not reliable. So I don't understand why my initial implementation doesn't work. The reentrant pattern doesn't apply here as per my understanding either. Can someone explain to me what's going on here? Is it expected behaviour or a bug?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants