Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep Dotnet Core Grpc Server running as a console application?

I am trying to keep a Grpc server running as a console daemon. This gRPC server is a microservice that runs in a docker container.

All of the examples I can find make use of the following:

Console.ReadKey();

This does indeed block the main thread and keeps it running but does not work in docker with the following error:

"Cannot read keys when either application does not have a console or when console input has been redirected. Try Console.Read."

Now I could probably try to find a workaround for docker specifically, but this still doesn't feel right. Does anyone know of a good "production ready" way to keep the service running?

like image 793
Mitch Dart Avatar asked Aug 31 '17 19:08

Mitch Dart


2 Answers

Use ManualResetEvent to block the main thread until you receive a shutdown event.

For example in a trivial situation:

class Program
{
  public static ManualResetEvent Shutdown = new ManualResetEvent(false);

  static void Main(string[] args)
  {
    Task.Run(() =>
    {
      Console.WriteLine("Waiting in other thread...");
      Thread.Sleep(2000);
      Shutdown.Set();
    });

    Console.WriteLine("Waiting for signal");
    Shutdown.WaitOne();

    Console.WriteLine("Resolved");
  }
}

For example, in your case, I imagine something like:

using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Helloworld;

namespace GreeterServer
{
  class GreeterImpl : Greeter.GreeterBase
  {
    // Server side handler of the SayHello RPC
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
      Program.Shutdown.Set(); // <--- Signals the main thread to continue 
      return Task.FromResult(new HelloReply {Message = "Hello " + request.Name});
    }
  }

  class Program
  {
    const int Port = 50051;

    public static ManualResetEvent Shutdown = new ManualResetEvent(false);

    public static void Main(string[] args)
    {
      Server server = new Server
      {
        Services = {Greeter.BindService(new GreeterImpl())},
        Ports = {new ServerPort("localhost", Port, ServerCredentials.Insecure)}
      };
      server.Start();

      Shutdown.WaitOne(); // <--- Waits for ever or signal received

      server.ShutdownAsync().Wait();
    }
  }
}
like image 27
Doug Avatar answered Sep 20 '22 15:09

Doug


You can now use Microsoft.Extensions.Hosting pacakge which is a hosting and startup infrastructures for both asp.net core and console application.

Like asp.net core, you can use the HostBuilder API to start building gRPC host and setting it up. The following code is to get a console application that keeps running until it is stopped (for example using Control-C):

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder();

         // register your configuration and services.
        ....

        await hostBuilder.RunConsoleAsync();
    }
}

In order to run the gRPC service, you need to start/stop Grpc.Core.Server in a hosted service. A hosted service is basically a piece of code that is run by the host when the host itself is started and the same for when it is stopped. This is represented in the IHostedService interface. That's to say, implement a GrpcHostedService to override the interface:

using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Hosting;

namespace Grpc.Host
{
    public class GrpcHostedService: IHostedService
    {
        private Server _server;

        public GrpcHostedService(Server server)
        {
            _server = server;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _server.Start();
            return Task.CompletedTask;
        }

        public async Task StopAsync(CancellationToken cancellationToken) => await _server.ShutdownAsync();
    }
}

It's simple really. We get an GrpcHostedService instance injected through dependency injection and run StartAsync on it when host is started. When the host is stopped we run StopAsync so that we can gracefully shut everything down including Grpc server.

Then go back to the Program.cs and make some changes:

public class Program
{
    public static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder()
             // Add configuration, logging, ...
            .ConfigureServices((hostContext, services) =>
            {
                // Better to use Dependency Injection for GreeterImpl
                Server server = new Server
                {
                    Services = {Greeter.BindService(new GreeterImpl())},
                    Ports = {new ServerPort("localhost", 5000, ServerCredentials.Insecure)}
                };
                services.AddSingleton<Server>(server);
                services.AddSingleton<IHostedService, GrpcHostedService>();
            });

        await hostBuilder.RunConsoleAsync();
    }
}

By doing this, the generic host will automatically run StartAsync on our hosted service, which in turn will call StartAsync on the Server instance, essentially start the gRPC server.

When we shut down the host with Control-C, the generic host will automatically call StopAsync on our hosted service, which again will call StopAsync on the Server instance which will do some clean up.

For other configuration in HostBuilder, you can see this blog.

like image 146
Feiyu Zhou Avatar answered Sep 16 '22 15:09

Feiyu Zhou