Yes, the ServerBuilder executor. You can track the concurrency present and fail the RPC within the interceptor. Yes, you used a thread, but for a short period of time. This is pretty easy and reasonably effective.
If you limit the number of threads, then you've moved the problem to the queue that forms. Ordinarily you'd configure the executor to throw RejectExecutionException, but that
isn't supported.
Somewhere I remember mentioning the idea of having
yet another executor, and only use it for cancelling RPCs. It could be a single thread. But that's not implemented, although it wouldn't be that hard.
We added ServerBuilder.callExecutor() which lets you change the executor for an RPC. This is great if you want to segment your services to separate pools. ServerCallExecutorSupplier is called on the ServerBuilder executor as well, because we have to look up the MethodDescriptor before we can create the ServerCall to provide context. ServerBuilder executor being used is fine from a resource usage perspective. But it can increase latency. If you don't have a ServerBuilder.fallbackHandlerRegistry() or you know it executes quickly, then you can use callExecutor(directExecutor()) and always return an executor from the ServerCallExecutorSupplier (to avoid using directExecutor() for application processing), without paying two thread hops.
For FIFO (based on RPC concurrently limit, not queue length), ServerCallExecutorSupplier could increment for each RPC and use an interceptor to decrement when complete. If this new RPC would exceed a threshold, fail the RPC, and either return null or a "cleanup" executor for the fast-running cancellation processing.
Part of the problem with RejectedExecutionException in Java is you can't implement LIFO to reduce average latencies. For LIFO, you have to get "fancy."
For LIFO (based on "queue length"), ServerCallExecutorSupplier can store call information in the returned executor to annotate the Runnables for that specific RPC. If it integrates with the executor's queue stack and wants to then kill RPCs at the bottom of the stack, it can, because it can identify where those Runnables came from and it knows the application is currently not receiving callbacks. It still has to run the Runnables to release resources, and because grpc-java doesn't support cancellation on server-side, I think you still need to pair this with an interceptor. It is probably much easier for unary RPCs since there is no need for potentially-slow application cleanup. I suspect nobody has actually tried this approach.