gRPC

JSON-RPC is a specification. It defines the request object and the response object, and it leaves the rest to us: how the calls travel, how the stubs are written, and how the arguments of each procedure are described and checked. A JSON-RPC library fills in some of these. Each library does it in its own way.

gRPC is an RPC framework that comes with all of these parts. Google released it as open source in 2015. It was the next version of Stubby, the RPC system Google had used inside the company for over ten years.

With gRPC, we describe the runner’s procedures and their arguments in a .proto file. The file is written in Protocol Buffers (Protobuf), Google’s language for describing structured data:

syntax = "proto3";

service Runner {
  rpc RunSubmission (RunRequest) returns (RunResult);
}

message RunRequest {
  string language = 1;
  string code = 2;
  repeated Test tests = 3;
}

message Test {
  string name = 1;
  repeated string args = 2;
  string expected = 3;  // written as text, so one message fits every problem
}

// RunResult is defined the same way.

service Runner lists the runner’s procedures, one on each rpc line. A message lists the fields of an argument or a result. Each field has a type and a number. repeated means a list. A language for describing an interface like this is called an interface definition language (IDL).

From this file, a tool generates the client stub for the application and the server stub for the runner. These are the parts we wrote by hand earlier for JSON-RPC. The tool can generate each one in a different language, for example JavaScript for the application and Go for the runner. We write only the code that calls the client stub, and the procedure itself, runSubmission, on the runner.

gRPC also sends the calls differently from our JSON-RPC over HTTP:

  • Binary messages. By default, gRPC uses Protobuf’s binary format, where a field is sent with its number, 3 for tests, not its name. So a message is smaller than the same message in JSON, and a program reads it faster. The downside is that a person cannot read it.
  • HTTP/2. gRPC runs over HTTP/2. Recall that HTTP/2 carries many requests and responses on one connection, so many calls in progress can share one connection.

A browser cannot call a gRPC service directly. gRPC puts the status of each call in the trailers, headers sent after the body of the response, and JavaScript in a browser cannot read them. A browser needs a variant called gRPC-Web, supported either by the server itself or by a proxy that translates between the two. For the runner, that does not matter, because only the application calls it.

For CodeDrill, I keep JSON-RPC. The runner has one procedure, and a call takes about 3 seconds of running code, so smaller messages and a shared connection would not make a submission noticeably faster. I would choose gRPC when there are many procedures, services written in different languages, or many calls a second.