27

grpc-java uses an executor in its ServerBuilder, which if not defined by the builder.executor() method, uses a static cached thread pool by default. What is the exact use of this executor? Does it just execute the handler methods or does it do “something else” as well?

Also, how does grpc define the netty worker EventLoopGroup? Specifically, I want to know how the worker threads are assigned to this worker group. Is there a default for the number of threads, or is it a function of the number of cores of the machine? Also, in relation to the above question, how do these netty workers work with the executor? Do they handle just the I/O - reading and writing to the channel?

Edit: Netty, by default creates (2 * number of cores) worker threads.

gravetii
  • 9,273
  • 9
  • 56
  • 75

1 Answers1

41

The Executor that you provide is what actually executes the callbacks of the rpc. This frees up the EventLoop to continue processing data on the connection. When a new message arrives from the network, it is read on the event loop, and then propagated up the stack to the executor. The executor takes the messages and passes them to your ServerCall.Listener which will actually do the processing of the data.

By default, gRPC uses a cached thread pool so that it is very easy to get started. However it is strongly recommended you provide your own executor. The reason is that the default threadpool behaves badly under load, creating new threads when the rest are busy.

In order to set up the event loop group, you call the workerEventLoopGroup method on NettyServerBuilder. gRPC is not strictly dependent on Netty (other server transports are possible) so the Netty specific builder must be used.

Carl Mastrangelo
  • 5,970
  • 1
  • 28
  • 37
  • 3
    does gRPC have a recommended executor for different scenarios since it is strongly recommended not to use default? – Matt Aug 22 '17 at 22:06
  • 7
    Yes, use a fixed size thread pool. Something like `Executors.newFixedThreadPool(16)` – Carl Mastrangelo Aug 22 '17 at 22:19
  • 1
    thanks Carl, so that implies that my `*ServiceImplBase` should be thread-safe if I add a single instance `.addService(...)` to my server and have multiple threads dispatching requests? – Matt Aug 25 '17 at 01:07
  • @user3833308 a dependency injection framework could be used to easily access a managed channel from each of the service implementations. – Joseph Orlando Jun 01 '19 at 02:51
  • 1
    You pass threadlocals by using a ServerInterceptor, and attaching/detaching the io.grpc.Context. Read from the the threadlocal on attach, and remove it upon detach. – Carl Mastrangelo Jun 04 '19 at 00:02