Liking cljdoc? Tell your friends :D

clj-grpc.server

A gRPC server over non-shaded Netty, from a service value and plain functions.

(-> (server {:services [{:service greeter/Greeter
                         :handlers {:say-hello (fn [req] ...)}}]
             :port 8080})
    start)

Handler shapes, by method type — requests and responses are protobuf Messages; compose with the generated proto->X / X->proto at the edges:

:unary (fn [request] response) :server-streaming (fn [request send!]) — call send! per message, return to complete :client-streaming (fn [respond!]) -> {:on-next (fn [msg]) ... :on-complete (fn [])} — call respond! once with the response, usually from :on-complete :bidi (fn [send! close!]) -> {:on-next ... :on-complete ... :on-ready ...} — send! per message, close! to finish

Outbound flow control, for the streaming-out shapes. send! returns whether the transport wants more: false means grpc-java is now buffering this call's messages in memory because the client is not reading them fast enough. .onNext never blocks and never refuses, so a producer faster than its consumer will otherwise grow that buffer without bound — a channel or queue in front of it bounds the producer, not the wire. Most handlers can ignore the value: a reply per request cannot outrun anything. A handler that generates messages on its own schedule should not.

To wait rather than poll, a :bidi handler declares :on-ready in the map it returns; grpc calls it when a full call has drained. THE HANDLER'S OWN THREAD MUST NOT WAIT FOR IT. grpc serializes every callback for a call — :on-next, :on-ready, :on-complete — so a thread that is running one of them and blocks for another deadlocks the call. The shape that works is a producer on its own thread (a virtual thread is the cheap way), parked on something :on-ready delivers to; :on-ready itself only hands over the signal. examples/src/example/echo/async.clj is that, over core.async.

:server-streaming has the return value but no :on-ready, and the same rule is why: its handler IS the callback, so nothing could deliver the signal while it waited. A server-streaming handler that must respect backpressure belongs in the :bidi shape.

A thrown exception in any handler becomes Status/INTERNAL with the message attached; throw an io.grpc.StatusRuntimeException to control the status.

opts: :services [{:service Service :handlers {kebab-key fn}} ...] :address SocketAddress | port | {:unix path} | "unix:///path" :port used when :address is absent; default $PORT, else 8080 — the Knative convention :transport :auto (default) | :epoll | :nio — UDS requires epoll :health true (default) — grpc health service, wired for probes :reflection false (default) — server reflection (v1) :executor java.util.concurrent.Executor for handlers, or :direct to run them ON the Netty event loop — measured ~29% off unary latency, and a sharp edge: a handler that blocks on a direct executor stalls the transport for every connection sharing that loop. Opt in only for handlers that provably never block. :interceptors [io.grpc.ServerInterceptor ...] :permit-keepalive {:time-ms n :without-calls bool} — the pings this server ACCEPTS. gRPC's default permit is 5 minutes and calls-only; a client pinging faster gets GOAWAY too_many_pings, so a server whose clients keep connections warm (Knative, LBs) must lower this to match. clj-grpc.knative pairs the two. :max-inbound-message-size bytes :worker-threads n — Netty event loops for the connections; default 0 = Netty's 2 × cores, which assumes the loops run the handlers. Under the virtual-thread default they only do I/O and compete with the carriers: measured on a 4-core host, 2 loops instead of 8 were +14–21% streamed messages per second for an eight-connection server, nil for one connection. About half the cores is a good value for a virtual-thread server; leave the default for :direct. :initial-flow-control-window bytes — where grpc-netty's HTTP/2 window starts (its default 1 MiB; BDP auto-tuning stays on). :inbound-credits n — for :client-streaming and :bidi handlers, ask the transport for n messages at a time instead of grpc-java's one per delivered message. Each request is a hop from the handler's thread back to the event loop; measured on a 4-core host, batching them is +27–40% streamed messages per second on the virtual-thread executor at −23–35% CPU per message, and the batch size does not matter above a handful (8, 32 and 128 measure the same). Backpressure is unchanged in kind — the server still bounds what it has asked for — but up to n messages may be in flight to a handler at once, so a handler that must see exactly one at a time keeps the default. Default: nil (grpc-java's auto-request). :tls {:cert-chain File/path :private-key File/path}; absent means h2c (plaintext HTTP/2), which is what Knative speaks

Handlers run on virtual threads by default (:executor overrides): Clojure handlers block — that is the model — and grpc's default shared pool is sized for handlers that never do.

A gRPC server over non-shaded Netty, from a service value and plain
functions.

    (-> (server {:services [{:service greeter/Greeter
                             :handlers {:say-hello (fn [req] ...)}}]
                 :port 8080})
        start)

Handler shapes, by method type — requests and responses are protobuf
Messages; compose with the generated proto->X / X->proto at the edges:

  :unary            (fn [request] response)
  :server-streaming (fn [request send!]) — call send! per message, return to
                    complete
  :client-streaming (fn [respond!]) -> {:on-next (fn [msg]) ...
                                        :on-complete (fn [])}
                    — call respond! once with the response, usually from
                    :on-complete
  :bidi             (fn [send! close!]) -> {:on-next ... :on-complete ...
                                            :on-ready ...}
                    — send! per message, close! to finish

Outbound flow control, for the streaming-out shapes. `send!` returns whether
the transport wants more: false means grpc-java is now buffering this call's
messages in memory because the client is not reading them fast enough.
`.onNext` never blocks and never refuses, so a producer faster than its
consumer will otherwise grow that buffer without bound — a channel or queue
in front of it bounds the producer, not the wire. Most handlers can ignore
the value: a reply per request cannot outrun anything. A handler that
generates messages on its own schedule should not.

To wait rather than poll, a :bidi handler declares :on-ready in the map it
returns; grpc calls it when a full call has drained. THE HANDLER'S OWN
THREAD MUST NOT WAIT FOR IT. grpc serializes every callback for a call —
:on-next, :on-ready, :on-complete — so a thread that is running one of them
and blocks for another deadlocks the call. The shape that works is a
producer on its own thread (a virtual thread is the cheap way), parked on
something :on-ready delivers to; :on-ready itself only hands over the
signal. `examples/src/example/echo/async.clj` is that, over core.async.

:server-streaming has the return value but no :on-ready, and the same rule
is why: its handler IS the callback, so nothing could deliver the signal
while it waited. A server-streaming handler that must respect backpressure
belongs in the :bidi shape.

A thrown exception in any handler becomes Status/INTERNAL with the message
attached; throw an io.grpc.StatusRuntimeException to control the status.

opts:
  :services     [{:service Service :handlers {kebab-key fn}} ...]
  :address      SocketAddress | port | {:unix path} | "unix:///path"
  :port         used when :address is absent; default $PORT, else 8080 —
                the Knative convention
  :transport    :auto (default) | :epoll | :nio — UDS requires epoll
  :health       true (default) — grpc health service, wired for probes
  :reflection   false (default) — server reflection (v1)
  :executor     java.util.concurrent.Executor for handlers, or :direct to
                run them ON the Netty event loop — measured ~29% off unary
                latency, and a sharp edge: a handler that blocks on a direct
                executor stalls the transport for every connection sharing
                that loop. Opt in only for handlers that provably never
                block.
  :interceptors [io.grpc.ServerInterceptor ...]
  :permit-keepalive {:time-ms n :without-calls bool} — the pings this server
                ACCEPTS. gRPC's default permit is 5 minutes and calls-only;
                a client pinging faster gets GOAWAY too_many_pings, so a
                server whose clients keep connections warm (Knative, LBs)
                must lower this to match. clj-grpc.knative pairs the two.
  :max-inbound-message-size bytes
  :worker-threads n — Netty event loops for the connections; default 0 =
                Netty's 2 × cores, which assumes the loops run the
                handlers. Under the virtual-thread default they only do
                I/O and compete with the carriers: measured on a 4-core
                host, 2 loops instead of 8 were +14–21% streamed messages
                per second for an eight-connection server, nil for one
                connection. About half the cores is a good value for a
                virtual-thread server; leave the default for :direct.
  :initial-flow-control-window bytes — where grpc-netty's HTTP/2 window
                starts (its default 1 MiB; BDP auto-tuning stays on).
  :inbound-credits n — for :client-streaming and :bidi handlers, ask the
                transport for n messages at a time instead of grpc-java's
                one per delivered message. Each request is a hop from the
                handler's thread back to the event loop; measured on a
                4-core host, batching them is +27–40% streamed messages per
                second on the virtual-thread executor at −23–35% CPU per
                message, and the batch size does not matter above a handful
                (8, 32 and 128 measure the same). Backpressure is unchanged
                in kind — the server still bounds what it has asked for — but
                up to n messages may be in flight to a handler at once, so
                a handler that must see exactly one at a time keeps the
                default. Default: nil (grpc-java's auto-request).
  :tls          {:cert-chain File/path :private-key File/path}; absent means
                h2c (plaintext HTTP/2), which is what Knative speaks

Handlers run on virtual threads by default (:executor overrides): Clojure
handlers block — that is the model — and grpc's default shared pool is sized
for handlers that never do.
raw docstring

await-terminationclj

(await-termination {:keys [server]})
source

portclj

(port {:keys [server]})
source

serverclj

(server {:keys [services address port transport health reflection executor
                interceptors tls permit-keepalive max-inbound-message-size
                inbound-credits worker-threads initial-flow-control-window]
         :or {health true}})

Build (without starting) a server. Returns {:server io.grpc.Server :health HealthStatusManager-or-nil :address SocketAddress}.

Build (without starting) a server. Returns {:server io.grpc.Server
:health HealthStatusManager-or-nil :address SocketAddress}.
sourceraw docstring

service-definitionclj

(service-definition svc)
(service-definition {:keys [service handlers]} opts)

A dynamic ServerServiceDefinition from a service value and a handlers map. Methods without a handler are omitted and answer UNIMPLEMENTED, which is gRPC's own semantics for them. opts: :inbound-credits, as for server.

A dynamic ServerServiceDefinition from a service value and a handlers map.
Methods without a handler are omitted and answer UNIMPLEMENTED, which is
gRPC's own semantics for them. opts: :inbound-credits, as for `server`.
sourceraw docstring

shutdownclj

(shutdown s)
(shutdown {:keys [server health owned-executor] :as s} {:keys [grace-ms]})

Graceful by default; :grace-ms bounds the drain, then forces. The health service (when present) enters its terminal NOT_SERVING state first, so load balancers stop routing before the listener closes — the drain order Kubernetes rollouts assume.

Graceful by default; :grace-ms bounds the drain, then forces. The health
service (when present) enters its terminal NOT_SERVING state first, so
load balancers stop routing before the listener closes — the drain order
Kubernetes rollouts assume.
sourceraw docstring

startclj

(start {:keys [server] :as s})
source

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close