RPC API

Once the stubs are generated, each service becomes a baremodule and each RPC an exported function inside it. A call is made by handing the RPC either a gRPCChannel — reusable, and carrying the connection defaults — or a bare host, port pair, together with the request message wherever the request side is unary:

chan = gRPCChannel("localhost", 8001)response = TestService.TestRPC(chan, TestRequest(...))

How the call is driven depends on the RPC's shape:

  • Unary, synchronous — TestService.TestRPC(chan, req) blocks and returns the decoded response, throwing on error. This is the only form that returns the response directly rather than a call handle.
  • Unary, asynchronous — passing gRPCAsync() as an extra argument returns a gRPCUnaryCall handle immediately, and the response is retrieved later with fetch. Passing a Channel{gRPCAsyncChannelResponse} and an index instead collects many in-flight responses in the order they arrive.
  • Client streaming — TestService.TestClientStreamRPC(chan) returns a gRPCClientStreamCall; stream requests with put!, then fetch the single response (which also closes the request stream).
  • Server streaming — TestService.TestServerStreamRPC(chan, req) returns a gRPCServerStreamCall; consume responses by iterating the call (for resp in rpc) or with take!, then close it (or detach to cancel without waiting for the server).
  • Bidirectional — TestService.TestBidirectionalStreamRPC(chan) returns a gRPCBidirectionalStreamCall; interleave put! and take! freely, then close or detach.

Every form except the synchronous unary call returns an AbstractgRPCCall handle. The operations these handles support, and which handle types accept them, are listed under Call initialization. Connection options such as deadline, secure, and token may be set once on the channel and overridden on any individual call.

Auto-generated RPC docstrings

Every generated RPC method carries its own docstring, produced by the code generator, documenting the method's exact call signatures, its request and response types, the connection options, and copy-pasteable examples tailored to that RPC's shape (unary, client-streaming, server-streaming, or bidirectional). View it at the REPL with ?, for example ?TestService.TestRPC, once the stubs are loaded. These per-RPC docstrings are the most direct reference for the exact call syntax of a given service, complementing the generic handle operations documented below.

Call initialization

The table below lists the operations available on a call handle and which of the four call shapes each one applies to. "Async unary" is a unary call started with gRPCAsync(); a synchronous unary call returns its response directly and exposes none of these handle operations.

DescriptionNameThrowsAsync unaryClient streamServer streamBidirectional
Create a call<service.rpc>On submission (sync unary: also on failure)✓✓✓✓
Check if ready for a requestBase.isfull (Julia 1.12 or later)Never✓✓
Send requestput!(rpc, msg)If the call has ended✓✓
Signal done with requestsBase.put!(rpc[, msg], done = true)Only when sending msg✓✓
Check if response is availableBase.isreadyNever✓✓✓✓
Wait for response or end of streamBase.waitOn failure✓✓✓✓
Take responseBase.take!When ended and drained✓✓
Read response without removing itBase.fetchOn failure (streaming: when ended and drained)✓✓✓✓
Iterate responsesfor x in rpcOn failure✓✓
Check if rpc is still activeBase.isopenNever✓✓✓✓
Wait for server shutdown and report errorsBase.closeOn failure✓✓✓✓
Gracefully cancelBase.detachOn failure✓✓✓✓

For unary responses (async unary and client streaming) fetch retrieves the single response and cleans up; for streaming responses (server streaming and bidirectional) take! removes the next response and fetch returns it without removing it. Streaming responses are buffered, so any responses still queued are delivered first — the call's error, or a normal end of stream, is only raised once no responses remain.

Base.isopen — Method
isopen(rpc::AbstractgRPCCall)

Tells whether the underlying call is still active.

Use it for liveness, or to decide whether another put! is still worthwhile. Do not use it to tell whether more responses remain: responses may still be buffered or in transit after it returns false, so drain a stream by consuming responses to completion rather than guarding on this.

source
Base.put! — Method
put!(rpc::gRPCBidirectionalStreamCall, msg[; done::Bool = false])    
put!(rpc::gRPCClientStreamCall, msg[; done::Bool = false])
put!(rpc::gRPCBidirectionalStreamCall; done::Bool)
put!(rpc::gRPCClientStreamCall; done::Bool)

Sends a request message msg (if provided) over a client-streaming RPC.

If done = true, the server will be notified that the client is done sending more messages. Future calls to put! will result in an exception.

source
Base.fetch — Method
fetch(rpc::gRPCUnaryCall)
fetch(rpc::gRPCClientStreamCall)
fetch(..., Vector{UInt8})

Reads the response of rpc, cleanup resources and throw any exception caught.

If the rpc has streaming requests, the request stream will be closed.

If Vector{UInt8} is provided as the second argument, the response will be returned without decoding the proto format.

If the response of rpc is not of interest, close may be used to avoid decoding.

source
Base.wait — Method
wait(rpc::gRPCUnaryCall)
wait(rpc::gRPCClientStreamCall)

Waits for an RPC with unary response to be ready to return its response.

source
Base.wait — Method
wait(rpc::gRPCServerStreamCall)
wait(rpc::gRPCBidirectionalStreamCall)

Block until a response is available or the stream has ended, returning in both cases; a failed call throws its exception here.

You will usually want to check isready afterwards: true means a response is available to take!, false means the stream has ended.

source
Base.isready — Method
isready(rpc::gRPCUnaryCall)
isready(rpc::gRPCClientStreamCall)

Tells whether fetch(rpc) would return the response immediately, without blocking.

Use it to poll for the response. It is false both while the call runs and if it failed; use isopen(rpc) to test for completion, then fetch (which returns the response or throws the error).

source
Base.isready — Method
isready(rpc::gRPCServerStreamCall)
isready(rpc::gRPCBidirectionalStreamCall)

Tells whether a response is buffered, so take!(rpc) would return one without blocking.

Use it to consume responses without blocking (for example polling, or fan-in across several calls). Do not use it as a loop guard to drain a stream: a false does not mean the stream has ended — more responses may be in transit or arrive later. Use iteration, take a known number of responses or use a combination of wait and isready instead.

source
Base.take! — Method
take!(rpc::gRPCServerStreamCall)
take!(rpc::gRPCBidirectionalStreamCall)
take!(..., Vector{UInt8})

Remove and return a recevied response from a stream. Blocks unless a response is already available.

If Vector{UInt8} is provided as the second argument, the response will be returned without decoding the proto format.

source
Base.fetch — Method
fetch(rpc::gRPCServerStreamCall)
fetch(rpc::gRPCBidirectionalStreamCall)
fetch(..., Vector{UInt8})

Return a recevied response from a response stream. Blocks unless a response is already available.

If Vector{UInt8} is provided as the second argument, the response will be returned without decoding the proto format.

Note that fetch does not remove the response, so repeated calls will return the same value. In most scenarios, take! is the preferred option for response streams.

source
Base.iterate — Method
iterate(rpc::gRPCServerStreamCall)
iterate(rpc::gRPCBidirectionalStreamCall)

Iterate the responses of rpc, so a response stream can be drained with a for loop:

for response in rpc    # handle responseend

This is the race-free way to consume a stream of unknown length: each response is returned in turn and the loop ends cleanly once the server closes the stream. A failed call still throws from within the loop. Prefer this over guarding a take! loop with isopen/isready, which cannot do both safely.

Iteration can also be implemented manually with the following equivalent:

while true    wait(rpc)    isready(rpc) || break    response = take!(rpc)    # handle responseend
source
Base.close — Method
close(rpc::AbstractgRPCCall)

Waits for rpc to be closed by the server and throw any exception caught.

Note that close(rpc) may block forever, as it depends on the call being shut down by the server logic. In such cases, detach(rpc) may be a better option.

source
Base.detach — Method
detach(rpc::AbstractgRPCCall[; throws::Bool = true])

Gracefully cancel an in-flight request rpc and frees all associated resources.

If throws, any exception caught during the lifetime of rpc will be thrown. The stored exception is replaced with CANCELLED, which will be thrown on future calls to detach or close.

source
gRPCClient.gRPCAsync — Type
gRPCAsync()

Singleton struct for flagging that a unary RPC should be called asynchronously.

If a gRPCAsync() is not passed as an argument when invoking an RPC, the call will block until a response is ready and immediately return the response. If gRPCAsync() is provided, the call will instead return a handle to the call immediately and the response can be obtained with A subsequent call to fetch.

Example

chan = gRPCChannel(host, port)rpc = MyService.MyUnaryRPC(chan, RequestType(...), gRPCAsync())response = fetch(rpc)
source

Lifecycles

Unary

A synchronous unary call blocks and returns the decoded response directly, raising on error:

chan = gRPCChannel("localhost", 8001)response = TestService.TestRPC(chan, TestRequest(1, [1]))

Passing gRPCAsync() instead starts the call without blocking and returns a handle; fetch waits for the response (raising on error) and releases the call's resources:

rpc = TestService.TestRPC(chan, TestRequest(1, [1]), gRPCAsync())# ... do other work while the call is in flight ...response = fetch(rpc)

Client streaming

The call is opened without a request message. Stream request messages with put!, then fetch the single response; fetch closes the request stream for you, so an explicit put!(rpc; done = true) is only needed if you want to finish sending before fetching:

chan = gRPCChannel("localhost", 8001)rpc = TestService.TestClientStreamRPC(chan)for i in 1:4    put!(rpc, TestRequest(1, [i]))endresponse = fetch(rpc)

Server streaming

The request message is supplied when opening the call. Pull responses with take! as they arrive, then close the call to wait for the server to finish and surface any error (detach instead cancels without waiting). This example's server sends one response per requested element:

chan = gRPCChannel("localhost", 8001)rpc = TestService.TestServerStreamRPC(chan, TestRequest(4, [1]))for _ in 1:4    response = take!(rpc)endclose(rpc)

Taking a known number of responses is a safe pattern: take! blocks until each one arrives, so every response is received and the call never reads past the end.

When the number of responses is not known ahead of time, use iteration:

rpc = TestService.TestServerStreamRPC(chan, TestRequest(4, [1]))for response in rpc    # process responseend

Alternatively, use a combination of wait and isready:

while true    wait(rpc)    isready(rpc) || break    raw = take!(rpc)    # handle rawend

Bidirectional streaming

Requests and responses are independent, so put! and take! may be interleaved in any order. Signal the end of the request stream with done = true (or a bare put!(rpc; done = true)), and close the call when finished:

chan = gRPCChannel("localhost", 8001)rpc = TestService.TestBidirectionalStreamRPC(chan)put!(rpc, TestRequest(1, [1]))response = take!(rpc)put!(rpc, TestRequest(1, [2]); done = true)response = take!(rpc)close(rpc)