LCOV - code coverage report
Current view: top level - waltz/grpc - fd_grpc_client.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 3 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 0 -

          Line data    Source code
       1             : #ifndef HEADER_fd_src_waltz_grpc_fd_grpc_client_h
       2             : #define HEADER_fd_src_waltz_grpc_fd_grpc_client_h
       3             : 
       4             : /* fd_grpc_client.h provides an API for dispatching unary and server-
       5             :    streaming gRPC requests over HTTP/2+TLS. */
       6             : 
       7             : #include "fd_grpc_codec.h"
       8             : #include "../fd_fqdn.h"
       9             : #include "../../third_party/nanopb/pb_firedancer.h" /* pb_msgdesc_t */
      10             : #include "../tlsrec/fd_tlsrec.h"
      11             : 
      12             : struct fd_grpc_client_private;
      13             : typedef struct fd_grpc_client_private fd_grpc_client_t;
      14             : 
      15             : struct fd_grpc_h2_stream;
      16             : typedef struct fd_grpc_h2_stream fd_grpc_h2_stream_t;
      17             : 
      18             : /* FD_GRPC_CLIENT_MAX_STREAMS specifies the max number of inflight
      19             :    unary and server-streaming requests.  Note that grpc_client does
      20             :    not scale well to large numbers due to O(n) algorithms. */
      21             : 
      22           0 : #define FD_GRPC_CLIENT_MAX_STREAMS 8
      23             : 
      24             : /* FD_GRPC_DEADLINE_* identify different types of request deadlines. */
      25             : 
      26           0 : #define FD_GRPC_DEADLINE_HEADER 1 /* deadline by which Response-Headers are received */
      27           0 : #define FD_GRPC_DEADLINE_RX_END 2 /* deadline by which 'end of stream' must have been reached */
      28             : 
      29             : /* fd_grpc_client_metrics_t hold counters that are incremented by a
      30             :    grpc_client. */
      31             : 
      32             : struct fd_grpc_client_metrics {
      33             : 
      34             :   /* wakeup_cnt counts the number of times the gRPC client was polled
      35             :      for I/O. */
      36             :   ulong wakeup_cnt;
      37             : 
      38             :   /* stream_err_cnt counts the number of survivable stream errors.
      39             :      These include out-of-memory conditions and decode failures. */
      40             :   ulong stream_err_cnt;
      41             : 
      42             :   /* conn_err_cnt counts the number of connection errors that resulted
      43             :      in connection termination.  These include protocol and I/O errors. */
      44             :   ulong conn_err_cnt;
      45             : 
      46             :   /* stream_chunks_tx_cnt increments whenever a DATA frame containing
      47             :      request bytes is sent.  stream_chunks_tx_bytes counts the number of
      48             :      stream bytes sent. */
      49             :   ulong stream_chunks_tx_cnt;
      50             :   ulong stream_chunks_tx_bytes;
      51             : 
      52             :   /* stream_chunks_rx_cnt increments whenever a DATA frame containing
      53             :      response bytes is received.  stream_chunks_rx_bytes counts the
      54             :      number of stream bytes received. */
      55             :   ulong stream_chunks_rx_cnt;
      56             :   ulong stream_chunks_rx_bytes;
      57             : 
      58             :   /* requests_sent increments whenever a gRPC request finished sending. */
      59             :   ulong requests_sent;
      60             : 
      61             :   /* streams_active is the number of streams not in 'closed' state. */
      62             :   long streams_active;
      63             : 
      64             :   /* rx_wait_ticks_cum is the cumulative time in ticks that incoming
      65             :      gRPC messages were in a "waiting" state.  The waiting state begins
      66             :      when the first byte of a HTTP/2 frame is received, and ends when
      67             :      all gRPC message bytes are received.
      68             : 
      69             :      This is a rough measure of server-to-client congestion.  On a
      70             :      healthy connection, this value should be close to zero. */
      71             :   long rx_wait_ticks_cum;
      72             : 
      73             :   /* tx_wait_ticks_cum is the cumulative time in ticks that an outgoing
      74             :      message was in a "waiting" state.  The waiting state begins when
      75             :      a message is ready to be sent, and ends when all message bytes were
      76             :      handed to the TCP layer.
      77             : 
      78             :      This is a rough measure of client-to-server congestion, which can
      79             :      be caused by the TCP server receive window, TCP client congestion
      80             :      control, or HTTP/2 server flow control.  On a healthy connection,
      81             :      this value should be close to zero. */
      82             :   long tx_wait_ticks_cum;
      83             : 
      84             : };
      85             : 
      86             : typedef struct fd_grpc_client_metrics fd_grpc_client_metrics_t;
      87             : 
      88             : /* fd_grpc_client_callbacks_t is a virtual function table containing
      89             :    grpc_client->app callbacks. */
      90             : 
      91             : struct fd_grpc_client_callbacks {
      92             : 
      93             :   /* conn_established is called when the initial HTTP/2 SETTINGS
      94             :      exchange concludes.  Technically, requests can be sent before this
      95             :      point, though. */
      96             : 
      97             :   void
      98             :   (* conn_established)( void * app_ctx );
      99             : 
     100             :   /* conn_dead is called when the HTTP/2 connection ends.  To recover,
     101             :      call fd_grpc_client_reset() after the invoking call returns. */
     102             : 
     103             :   void
     104             :   (* conn_dead)( void * app_ctx,
     105             :                  uint   h2_err,
     106             :                  int    closed_by );
     107             : 
     108             :   /* tx_complete marks the completion of a tx operation. */
     109             : 
     110             :   void
     111             :   (* tx_complete)( void * app_ctx,
     112             :                    ulong  request_ctx );
     113             : 
     114             :   /* rx_start signals that the server sent back a response header
     115             :      indicating success.  rx_start is always called before the first
     116             :      call to rx_msg for that request_ctx. */
     117             : 
     118             :   void
     119             :   (* rx_start)( void * app_ctx,
     120             :                 ulong  request_ctx );
     121             : 
     122             :   /* rx_msg delivers a gRPC message.  May be called multiple times for
     123             :      the same request (server streaming). */
     124             : 
     125             :   void
     126             :   (* rx_msg)( void *       app_ctx,
     127             :               void const * protobuf,
     128             :               ulong        protobuf_sz,
     129             :               ulong        request_ctx );
     130             : 
     131             :   /* rx_end indicates that no more rx_msg callbacks will be delivered
     132             :      for a request. */
     133             : 
     134             :   void
     135             :   (* rx_end)( void *                app_ctx,
     136             :               ulong                 request_ctx,
     137             :               fd_grpc_resp_hdrs_t * resp );
     138             : 
     139             :   /* rx_timeout indicates that a request deadline was exceeded.
     140             :      deadline_kind indicates which timer fired. */
     141             : 
     142             :   void
     143             :   (* rx_timeout)( void * app_ctx,
     144             :                   ulong  request_ctx,
     145             :                   int    deadline_kind );
     146             : 
     147             :   /* ping_ack delivers an acknowledgement of a PING that was previously
     148             :      sent by fd_h2_tx_ping. */
     149             : 
     150             :   void
     151             :   (* ping_ack)( void * app_ctx );
     152             : 
     153             : };
     154             : 
     155             : typedef struct fd_grpc_client_callbacks fd_grpc_client_callbacks_t;
     156             : 
     157             : FD_PROTOTYPES_BEGIN
     158             : 
     159             : /* Constructors */
     160             : 
     161             : ulong
     162             : fd_grpc_client_align( void );
     163             : 
     164             : ulong
     165             : fd_grpc_client_footprint( ulong buf_max );
     166             : 
     167             : fd_grpc_client_t *
     168             : fd_grpc_client_new( void *                             mem,
     169             :                     fd_grpc_client_callbacks_t const * callbacks,
     170             :                     fd_grpc_client_metrics_t *         metrics,
     171             :                     void *                             app_ctx,
     172             :                     ulong                              buf_max,
     173             :                     ulong                              rng_seed );
     174             : 
     175             : void *
     176             : fd_grpc_client_delete( fd_grpc_client_t * client );
     177             : 
     178             : /* fd_grpc_client_next_deadline returns the earliest stream deadline
     179             :    (header or rx-end) across all inflight requests, 0 if a timed-out
     180             :    stream's RST_STREAM can be sent now, or LONG_MAX if no stream has a
     181             :    deadline armed. */
     182             : 
     183             : FD_FN_PURE long
     184             : fd_grpc_client_next_deadline( fd_grpc_client_t const * client );
     185             : 
     186             : /* fd_grpc_client_tx_pending returns 1 if the client has HTTP/2 frame
     187             :    bytes buffered that could not yet be written to the transport. */
     188             : 
     189             : FD_FN_PURE int
     190             : fd_grpc_client_tx_pending( fd_grpc_client_t const * client );
     191             : 
     192             : /* fd_grpc_client_tls_rx_pending returns 1 if the client holds decrypted
     193             :    TLS plaintext not yet pushed into the HTTP/2 layer.  Such bytes do
     194             :    not make the socket readable, so the caller must step the client
     195             :    without waiting for an fd event.  fd_grpc_client_tls_tx_pending
     196             :    returns 1 if encrypted TLS bytes are waiting to be written to the
     197             :    socket (e.g. a handshake flight blocked on EAGAIN).  RX continues
     198             :    while it is set; arm EPOLLOUT to learn when the send can resume. */
     199             : 
     200             : FD_FN_PURE int
     201             : fd_grpc_client_tls_rx_pending( fd_grpc_client_t const * client );
     202             : 
     203             : FD_FN_PURE int
     204             : fd_grpc_client_tls_tx_pending( fd_grpc_client_t const * client );
     205             : 
     206             : /* fd_grpc_client_tx_starved returns the number of request bytes parked
     207             :    mid-send because the peer's HTTP/2 flow-control window (stream or
     208             :    connection) is exhausted, or 0 if not starved.  Only a WINDOW_UPDATE
     209             :    from the peer can resume it. */
     210             : 
     211             : FD_FN_PURE ulong
     212             : fd_grpc_client_tx_starved( fd_grpc_client_t const * client );
     213             : 
     214             : /* fd_grpc_client_reset cancels all inflight requests and abandons the
     215             :    HTTP/2 client connection.  Config params are kept intact (e.g. host,
     216             :    port, version).  Defer reset requested by a client callback until
     217             :    the call that invoked the callback returns. */
     218             : 
     219             : void
     220             : fd_grpc_client_reset( fd_grpc_client_t * client );
     221             : 
     222             : /* fd_grpc_client_set_version sets the gRPC client's version string
     223             :    (relayed via user-agent header).  No reference to the provided string
     224             :    is kept (the content is copied out to the client object).  version
     225             :    does not have to be null-terminated.  version_len must be
     226             :    FD_GRPC_CLIENT_VERSION_LEN_MAX or less, otherwise a warning is logged
     227             :    and the client's version string remains unchanged. */
     228             : 
     229             : #define FD_GRPC_CLIENT_VERSION_LEN_MAX (63UL)
     230             : 
     231             : void
     232             : fd_grpc_client_set_version( fd_grpc_client_t * client,
     233             :                             char const *       version,
     234             :                             ulong              version_len );
     235             : 
     236             : /* fd_grpc_client_set_authority sets the authority header to the
     237             :    specified hostname and port number.  host_len should be less than
     238             :    FD_FQDN_BUF_MAX, otherwise host is truncated. */
     239             : 
     240             : void
     241             : fd_grpc_client_set_authority( fd_grpc_client_t * client,
     242             :                               char const *       host,
     243             :                               ulong              host_len,
     244             :                               ushort             port );
     245             : 
     246             : /* fd_grpc_client_rxtx_socket drives I/O against a TCP socket.
     247             :    (recvmsg(2) and sendmsg(2)).  Uses MSG_NOSIGNAL|MSG_DONTWAIT flags.
     248             : 
     249             :    Returns -1 if an error was encountered, and errno will be set.
     250             :    Returns 1 if send would block with EAGAIN.  Otherwise, returns 0. */
     251             : 
     252             : int
     253             : fd_grpc_client_rxtx_socket( fd_grpc_client_t * client,
     254             :                             int                sock_fd,
     255             :                             long               now,
     256             :                             int *              charge_busy );
     257             : 
     258             : /* fd_grpc_client_tx_flush_socket attempts one sendmsg(2) of pending
     259             :    HTTP/2 frame bytes to the socket.  Returns -1 (errno set) on a hard
     260             :    send error, 1 if send would block with EAGAIN, and 0 otherwise (a
     261             :    short write may leave bytes pending; check fd_grpc_client_tx_pending). */
     262             : 
     263             : int
     264             : fd_grpc_client_tx_flush_socket( fd_grpc_client_t * client,
     265             :                                 int                sock_fd );
     266             : 
     267             : /* fd_grpc_client_tls_flush writes pending encrypted TLS bytes to the
     268             :    socket, looping until the TLS TX buffer is empty or send blocks.
     269             :    Returns -1 (errno set) on a hard send error, 1 if send would block
     270             :    with EAGAIN (bytes remain, see fd_grpc_client_tls_tx_pending), and 0
     271             :    if the buffer is now empty. */
     272             : 
     273             : int
     274             : fd_grpc_client_tls_flush( fd_grpc_client_t * client,
     275             :                           int                sock_fd );
     276             : 
     277             : /* fd_grpc_client_rxtx_tls drives I/O against a TCP socket with
     278             :    fd_tlsrec encryption.  Handles TLS handshake, encrypts outgoing
     279             :    frames, and decrypts incoming data.
     280             : 
     281             :    Returns 0 on success and -1 if there is an unrecoverable error. */
     282             : 
     283             : int
     284             : fd_grpc_client_rxtx_tls( fd_grpc_client_t * client,
     285             :                          fd_tlsrec_conn_t * tls_conn,
     286             :                          int                sock_fd,
     287             :                          long               now,
     288             :                          int *              charge_busy );
     289             : 
     290             : /* fd_grpc_client_request_start queues a gRPC request for send.  The
     291             :    request includes one Protobuf message (unary request).  The client
     292             :    can only write one request payload at a time, but can have multiple
     293             :    requests pending for responses.
     294             : 
     295             :    path is the HTTP request path which usually follows the pattern
     296             :    '/path.to.package/Service.Function'.  If auth_token_sz is greater
     297             :    than zero, adds a request header 'authorization: Bearer *auth_token'.
     298             : 
     299             :    request_ctx is an arbitrary number used to identify the request.  It
     300             :    echoes in callbacks.
     301             : 
     302             :    fields points to a generated nanopb descriptor.  message points to a
     303             :    generated nanopb struct that the user filled in with info.  Calls
     304             :    pb_encode() internally.
     305             : 
     306             :    auth_token is an optional authorization header.  The header value is
     307             :    prepended with "Bearer ".  auth_token_sz==0 omits the auth header.
     308             : 
     309             :    is_streaming: If 0, this is a unary request and the stream is closed
     310             :    after sending the first message (END_STREAM flag set).  If non-zero,
     311             :    this is a client streaming request and the stream remains open for
     312             :    additional messages via fd_grpc_client_stream_send_msg().  The stream
     313             :    must be explicitly closed with fd_grpc_client_stream_close().
     314             : 
     315             :    Conditions for starting send:
     316             :    - The connection is not dead and the HTTP/2 handshake is complete.
     317             :    - Client has quota to open a new stream (MAX_CONCURRENT_STREAMS)
     318             :    - There is no other request still sending.
     319             :    - The message serialized size does not exceed buf_max (set in
     320             :      fd_grpc_client_new())
     321             :    - rbuf_tx is empty.  (HTTP/2 frames all flushed out to sockets) */
     322             : 
     323             : fd_grpc_h2_stream_t *
     324             : fd_grpc_client_request_start(
     325             :     fd_grpc_client_t *   client,
     326             :     char const *         path,
     327             :     ulong                path_len, /* in [0,128) */
     328             :     ulong                request_ctx,
     329             :     pb_msgdesc_t const * fields,
     330             :     void const *         message,
     331             :     char const *         auth_token,
     332             :     ulong                auth_token_sz,
     333             :     int                  is_streaming
     334             : );
     335             : 
     336             : fd_grpc_h2_stream_t *
     337             : fd_grpc_client_request_start1(
     338             :     fd_grpc_client_t *   client,
     339             :     char const *         path,
     340             :     ulong                path_len, /* in [0,128) */
     341             :     ulong                request_ctx,
     342             :     uchar const *        protobuf,
     343             :     ulong                protobuf_sz,
     344             :     char const *         auth_token,
     345             :     ulong                auth_token_sz,
     346             :     int                  is_streaming );
     347             : 
     348             : /* fd_grpc_client_stream_send_msg sends an additional message on an
     349             :    already-open client streaming request.  This function can only be
     350             :    called after fd_grpc_client_request_start() was called with
     351             :    is_streaming=1.
     352             : 
     353             :    Returns 1 on success, 0 if the operation failed (connection dead,
     354             :    buffers blocked, or encoding failure).
     355             : 
     356             :    Conditions for sending:
     357             :    - The connection is alive
     358             :    - No other send operation is in progress
     359             :    - rbuf_tx is empty
     360             :    - The message serialized size does not exceed buf_max */
     361             : 
     362             : int
     363             : fd_grpc_client_stream_send_msg(
     364             :     fd_grpc_client_t *    client,
     365             :     fd_grpc_h2_stream_t * stream,
     366             :     pb_msgdesc_t const *  fields,
     367             :     void const *          message
     368             : );
     369             : 
     370             : int
     371             : fd_grpc_client_stream_send_msg1(
     372             :     fd_grpc_client_t *    client,
     373             :     fd_grpc_h2_stream_t * stream,
     374             :     uchar const *         protobuf,
     375             :     ulong                 protobuf_sz );
     376             : 
     377             : /* fd_grpc_client_stream_close explicitly closes a client streaming
     378             :    request by sending an empty DATA frame with the END_STREAM flag.
     379             :    This signals to the server that no more messages will be sent.
     380             : 
     381             :    This function should be called after all messages have been sent via
     382             :    fd_grpc_client_stream_send_msg() to complete the client stream.
     383             : 
     384             :    Returns 1 on success, 0 if the operation failed (connection dead or
     385             :    buffers blocked).
     386             : 
     387             :    Conditions for closing:
     388             :    - The connection is alive
     389             :    - No other send operation is in progress
     390             :    - rbuf_tx is empty */
     391             : 
     392             : int
     393             : fd_grpc_client_stream_close(
     394             :     fd_grpc_client_t *    client,
     395             :     fd_grpc_h2_stream_t * stream
     396             : );
     397             : 
     398             : /* fd_grpc_client_stream_cancel aborts a request with RST_STREAM CANCEL
     399             :    and frees the stream without calling back.  Must not be called from
     400             :    a callback for the same stream.  Returns 1 on success, 0 if there is
     401             :    no TX space (retry later). */
     402             : 
     403             : int
     404             : fd_grpc_client_stream_cancel( fd_grpc_client_t *    client,
     405             :                               fd_grpc_h2_stream_t * stream );
     406             : 
     407             : /* fd_grpc_client_deadline_set sets a request deadline (used to
     408             :    configure timeouts).  deadline_kind is FD_GRPC_DEADLINE_*.  Logs
     409             :    error and aborts app if deadline_kind is unsupported.
     410             : 
     411             :    Behavior for different deadline kinds:
     412             :    - HEADER: Deadline by which gRPC Response-Headers must have been
     413             :              received
     414             :    - RX_END: Deadline by which the response stream must have been ended.
     415             :              For unary responses, this is the point at which the message
     416             :              has been fully received.  For server-streaming responses,
     417             :              it is the point at which the last message has been
     418             :              received, and there are no more messages remaining.  (Under
     419             :              the hood, this is indicated by the HTTP/2 END_STREAM flag.) */
     420             : 
     421             : void
     422             : fd_grpc_client_deadline_set( fd_grpc_h2_stream_t * stream,
     423             :                              int                   deadline_kind,
     424             :                              long                  ts_nanos );
     425             : 
     426             : /* fd_grpc_client_is_connected returns 1 if HTTP/2 SETTINGS were
     427             :    exchanged, the TLS handshake is complete (if applicable), and the
     428             :    conn hasn't died.  Otherwise, returns 0. */
     429             : 
     430             : int
     431             : fd_grpc_client_is_connected( fd_grpc_client_t * client );
     432             : 
     433             : /* fd_grpc_client_request_is_blocked returns 1 if a call to
     434             :    fd_grpc_client_request_start would certainly fail.  Reasons include
     435             :    SSL / HTTP/2 handshake not complete, or buffers blocked. */
     436             : 
     437             : int
     438             : fd_grpc_client_request_is_blocked( fd_grpc_client_t * client );
     439             : 
     440             : /* fd_grpc_client_stream_send_is_blocked returns 1 if a message cannot
     441             :    currently be sent on an already open stream (conn dead, handshake
     442             :    incomplete, tx buffer non-empty, or a send already in flight), 0
     443             :    otherwise.  Unlike fd_grpc_client_request_is_blocked it does not
     444             :    require headroom to open a new stream, so a server advertising a low
     445             :    SETTINGS_MAX_CONCURRENT_STREAMS cannot stall sends on the open
     446             :    stream. */
     447             : 
     448             : int
     449             : fd_grpc_client_stream_send_is_blocked( fd_grpc_client_t * client );
     450             : 
     451             : int
     452             : fd_grpc_client_request_stream_busy( fd_grpc_client_t * client );
     453             : 
     454             : /* Pointers to internals for testing */
     455             : 
     456             : fd_h2_rbuf_t *
     457             : fd_grpc_client_rbuf_tx( fd_grpc_client_t * client );
     458             : 
     459             : fd_h2_rbuf_t *
     460             : fd_grpc_client_rbuf_rx( fd_grpc_client_t * client );
     461             : 
     462             : fd_h2_conn_t *
     463             : fd_grpc_client_h2_conn( fd_grpc_client_t * client );
     464             : 
     465             : extern fd_h2_callbacks_t const fd_grpc_client_h2_callbacks;
     466             : 
     467             : FD_PROTOTYPES_END
     468             : 
     469             : #endif /* HEADER_fd_src_waltz_grpc_fd_grpc_client_h */

Generated by: LCOV version 1.14