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

Server Streaming Call does not complete when connection is closed #5

Open
gplwhite opened this issue Nov 29, 2024 · 1 comment · May be fixed by #6
Open

Server Streaming Call does not complete when connection is closed #5

gplwhite opened this issue Nov 29, 2024 · 1 comment · May be fixed by #6

Comments

@gplwhite
Copy link

I realize this project is probably somewhat abandoned, but I've just discovered it and am hoping to use it in a legacy .NetFramework scenario. It seems to offer a simple (and working) implementation of GRPC over NamedPipes, ideal for client<->server IPC on the local machine.

However, in my initial prototyping I've ran into a problem.
I have a server that exposes a streaming operation the sends status updates to the client; the client listens and processes the status updates.
I'm trying to make the client resilient, so that if the server goes away, the client will periodically attempt to reconnect and reestablish the status listening.
However the IAsyncEnumerable implementation that the client uses to listen for the server updates does not return if the server connection ends unexpectedly. Essentially this means the client doesn't realise the server has gone - and it just waits endlessly.

Server.cs

var server = new LiteServer();
server.ServiceBinder.Bind(new MyService());
_ = _server.ListenAsync(ConnectionFactory.ListenNamedPipe("MY_NAMED_PIPE"));

MyService.cs

public class MyService
{

        public IAsyncEnumerable<DeviceStatusEvent> SubscribeStatusAsync(CallContext context = default)
        {
            return SubscribeToStatusEvents(context.CancellationToken);
        }

        private async IAsyncEnumerable<StatusEvent> SubscribeToStatusEvents([EnumeratorCancellation] CancellationToken cancellationToken)
        {
            // Setup continuous publishing of any status events
            while (!cancellationToken.IsCancellationRequested)
            {
                DeviceStatusEvent statusEvent;
                try
                {
                    statusEvent = await queue.Reader.ReadAsync(cancellationToken);
                }
                catch (OperationCanceledException e) when (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                yield return statusEvent;
            }
        }
}

Client.cs


        public async Task ListenForServerUpdatesAsync(CancellationToken shutdownToken)
        {
            while (true)
            {
                if (shutdownToken.IsCancellationRequested)
                    break;
                
                try
                {
                    using var channel = await ConnectionFactory.ConnectNamedPipe("MY_NAMED_PIPE").CreateChannelAsync(shutdownToken);

                    var service = channel.CreateGrpcService<IMyService>();
                    var statusEvents = service.SubscribeStatusAsync();

                    await foreach (var statusEvent in statusEvents)
                    {
                        // Process statusEvent
                    }
                }
                catch (OperationCanceledException) when (shutdownToken.IsCancellationRequested)
                {
                    break;
                }
                catch (Exception)
                {
                }

                await Task.Delay(TimeSpan.FromSeconds(10), shutdownToken);
            }
        }

If the process hosting the server is terminated, the async foreach in the client blocks indefinitely.

@zachrybaker
Copy link

zachrybaker commented Nov 30, 2024 via email

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