C · Serving

Serving

#include <ucall/types.h>#include <ucall/server.h> 1.0.0-rc.1 · 60202332

Initialize the server and register routes before polling. ucall_server_take_call() drives one loop with a bounded wait; zero timeout performs one nonblocking progress round. ucall_server_take_calls() drives it continuously until ucall_server_stop() or failure. An interface’s listen_descriptor accepts on a listening socket the caller opened, such as one systemd passed in, instead of binding address; UCall accepts on duplicates and never closes it. Routing is exact: empty segments are segments and a trailing slash is significant. A path no route takes answers 404, other verbs’ routes answer 405 with allow, GET routes serve HEAD, OPTIONS answers 204 with allow, and Expect: 100-continue gets its interim response once the request is next and its body is wanted. An interface at its connection budget evicts its least recently progressed idle keep-alive connection for a new peer. A server interface’s max_pipelined_requests bounds the requests one connection carries at once; zero means one, and replies leave in request order. Servers support immutable routes, size and latency based application batching, retained request views, and content, error, vectored and streamed replies. The metrics_* configuration reports StatsD counters. The first ucall_server_stop() drains: listeners and idle connections close, and every held request gets its reply before its connection closes; a second stop cuts what is still in flight. Stop the server, join its polling threads, and finish application use of retained requests before freeing it. Native I/O retirement during teardown can block.

API

Types

ucall_io_backend_tA concrete I/O implementation; automatic chooses an available native implementation.
types.h:56 ↗
enum ucall_io_backend_t

Values

ucall_io_backend_automatic_k0Initialization picks the fastest backend this machine and its limits allow.
ucall_io_backend_posix_kBlocking sockets and files, polled with poll, on any POSIX system.
ucall_io_backend_linux_epollaio_kLinux epoll for sockets and Linux AIO for files, the path where io_uring is blocked.
ucall_io_backend_linux_uring612_kio_uring as Linux 6.12, the older LTS line, provides it.
ucall_io_backend_linux_uring618_kio_uring as Linux 6.18, the newer LTS line, provides it, with multishot receives.
ucall_io_backend_bsd_kqueue_kkqueue on FreeBSD, with FreeBSD AIO for files, and on macOS.
ucall_io_backend_windows_iocp_kI/O completion ports on Windows.

Spellings

char const * ucall_io_backend_name(ucall_io_backend_t backend);
ucall_io_backend_t ucall_io_backend_from_name(ucall_bytes_view_t name);
ucall_server_tOpaque server handle.
types.h:142 ↗
typedef struct ucall_server_handle_t * ucall_server_t;

See also

  • Deals with ucall_received_http_request_t, individually or in counted arrays.
  • Use with ucall_server_init() and ucall_server_free() for lifetime.
  • Use ucall_server_add_http_endpoint() and ucall_server_take_calls() for logic.
ucall_http_batch_endpoint_tOne batch endpoint of a server, drained by one application thread; lives until ucall_server_free().
types.h:163 ↗
typedef struct ucall_http_batch_endpoint_handle_t * ucall_http_batch_endpoint_t;
ucall_verb_tHTTP request methods/verbs for endpoint routing.
types.h:209 ↗
enum ucall_verb_t

Values

ucall_verb_unknown_k0Unrecognized or not yet parsed.
ucall_verb_get_kHTTP "GET" method for retrieving resources.
ucall_verb_post_kHTTP "POST" method for creating resources.
ucall_verb_put_kHTTP "PUT" method for updating resources.
ucall_verb_delete_kHTTP "DELETE" method for removing resources.
ucall_verb_patch_kHTTP "PATCH" method for partial updates.
ucall_verb_head_kHTTP "HEAD" method for retrieving headers only.
ucall_verb_options_kHTTP "OPTIONS" method for retrieving supported methods.
ucall_verb_any_kMatches every method.
ucall_log_level_tSeverity of a log line, from the most verbose up.
types.h:539 ↗
enum ucall_log_level_t

Values

ucall_log_level_debug_kDetail for debugging.
ucall_log_level_info_kNormal lifecycle events.
ucall_log_level_warning_kSomething unexpected that the library recovered from.
ucall_log_level_error_kAn operation failed.
ucall_log_level_fatal_kThe server or client cannot continue.
ucall_log_format_tShape of each log line.
types.h:558 ↗
enum ucall_log_format_t

Values

ucall_log_format_human_kHuman-readable unit-normalized lines.
ucall_log_format_json_kNewline-delimited JSONs documents.
ucall_log_callback_tReceives one formatted log line, without a trailing newline.
types.h:576 ↗
typedef void(*) ucall_log_callback_t(ucall_log_level_t level, ucall_bytes_view_t line, void *context);

Called on whichever thread produced the line, including application polling threads. The callback must be thread-safe and return quickly.

ucall_log_tThe single log sink of a client or a server.
types.h:582 ↗
struct ucall_log_t

UCall writes nothing anywhere on its own: every line goes to callback, and without one the library stays silent.

Fields

callbackucall_log_callback_tReceives every line at or above level.
contextvoid *Passed verbatim to callback.
formatucall_log_format_tLine format.
levelucall_log_level_tLines below this severity are never formatted.
ucall_network_interface_tOne virtualized network interface, and every policy applying to what arrives on it.
server.h:26 ↗
struct ucall_network_interface_t

An interface is an address the server answers on, together with the security posture and admission limits for the connections it accepts. Several of them live under one server, which is what lets a single server answer VPN-internal Noise traffic and public plaintext at once, across as many physical devices as the machine has.

Encryption and admission live on the interface rather than on the server, so one server serves plaintext on loopback for health checks while requiring Noise on the wire - without standing up a second server to do it.

Fields

addressucall_bytes_view_tListening address in "host:port" form, required unless listen_descriptor is set; the resolved options report the bound address, or an AF_UNIX socket's path.
listen_descriptorintptr_t const *A listening stream socket to accept on instead of binding address, such as one systemd or a supervisor passed in, or NULL to bind.
device_nameucall_bytes_view_tPhysical device to pin this interface to with SO_BINDTODEVICE, such as "eth0", or empty to bind by address alone.
securityucall_security_tEncryption for connections accepted here.
noise_private_keyucall_noise_key_t const *Server identity for handshakes.
noise_peer_keyucall_noise_key_t const *The initiator's static public key, which KK hashes before the first message.
peer_authorizerucall_peer_authorizer_tVets client static keys.
peer_authorizer_contextvoid *Passed verbatim to peer_authorizer.
event_loopssize_tEvent loops accepting on this interface.
max_concurrent_connectionssize_tRequired open-connection budget across this interface's loops.
max_pipelined_requestssize_tRequests one connection may have in flight before UCall stops reading from it; zero means one.
max_request_bytessize_tLargest request head and buffered body; past it the reply is 413 and the connection lingers closed.
reply_directoriesucall_bytes_view_t const *Directories that path parts of replies to this interface's requests resolve beneath, by position, each copied during ucall_server_init().
reply_directories_countsize_tEntries in reply_directories.
ucall_server_options_tConfiguration parameters for the UCall server.
server.h:103 ↗
struct ucall_server_options_t

Fields

interfacesucall_network_interface_t const *Interfaces to answer on.
interfaces_countsize_tEntries in interfaces.
logucall_log_tWhere log lines go.
metrics_addressucall_bytes_view_tStatsD UDP endpoint "host:port"; empty disables emission.
metrics_sample_ratefloatFraction of finished requests whose latency is reported, in (0.0, 1.0].
metrics_prefixucall_bytes_view_tMetric name prefix; empty takes "ucall".
metrics_tagsucall_bytes_view_t const *Global tags, each "key:value".
metrics_tags_countsize_tEntries in metrics_tags.
metrics_interval_microsecondssize_tMicroseconds between one event loop's datagrams.
backenducall_io_backend_tRequested I/O backend; automatic chooses an available native backend.
cpu_classucall_cpu_class_tCeiling on the CPU class: the newest compiled class under it runs, and unknown takes the initialization thread's capabilities.

See also

Initialize with ucall_server_init().

ucall_event_loop_statistics_tCounters of one event loop since initialization; divided by requests, they price one call.
server.h:271 ↗
struct ucall_event_loop_statistics_t

Only the loop's polling owner advances them. Byte counters cover the bytes UCall itself moves, never the copies the kernel makes between socket buffers and user memory.

Fields

pollsuint64_tEngine polls the loop issued, each taking zero or more completions.
empty_pollsuint64_tEngine polls that took no completion.
kernel_entersuint64_tSystem calls: io_uring enters, or other backends' waits, registrations and socket calls.
submissionsuint64_tOperations queued: io_uring submission entries, or requests another backend accepted.
completionsuint64_tResults taken: io_uring completion entries, or operations another backend finished.
cancellationsuint64_tPending operations, or io_uring multishot receives, the engine asked to cancel.
acceptsuint64_tConnections accepted and handed to this loop.
requestsuint64_tHTTP requests parsed and dispatched to an endpoint or an error reply.
receive_rearmsuint64_tMultishot receives armed again on a socket that already streamed, only on io_uring 6.18.
bytes_receiveduint64_tBytes the kernel reported received on this loop's connections, ciphertext under Noise.
bytes_sentuint64_tBytes the kernel reported sent from this loop's connections, ciphertext under Noise.
bytes_copieduint64_tBytes UCall copied: receive appends, reply bodies, Noise records and parked receives.
zero_copy_sendsuint64_tSends that left without copying: reply segments and file windows.
zero_copy_fallbacksuint64_tZero-copy sends the kernel copied anyway or refused to pin, as loopback always does.
copying_sendsuint64_tSends that copied instead of pinning: the socket was demoted or refused zero-copy.
file_reads_asynchronousuint64_tFile reads the kernel finishes later: io_uring, Linux or FreeBSD AIO, or overlapped I/O.
file_reads_blockinguint64_tFile reads the loop issued itself and blocked on, as the posix backend and macOS do.
bytes_readuint64_tBytes the kernel reported read from files into the loop's file windows.
file_window_overlapsuint64_tFile reads submitted while an earlier window of the same stream waited to send or sent.
linger_resetsuint64_tLingering closes reset at their deadline because the kernel still held what they sent.
ready_passesuint64_tPasses the loop made over a ready connection, each advancing it as far as it goes.
replied_dequeuesuint64_tReplied exchanges the loop took from its queue to adopt their reply segments.
refused_requestsuint64_tRequests the loop answered itself with 400, 404, 405, 413, 501, 503 or 505.
handshake_failuresuint64_tConnections whose Noise handshake failed.
authorizer_refusalsuint64_tConnections whose authenticated peer key the interface's authorizer refused.
ucall_batch_endpoint_statistics_tCounters of one batch endpoint since registration; they say how large its batches were and why each fired.
server.h:437 ↗
struct ucall_batch_endpoint_statistics_t

Only the endpoint's polling owner advances them. Batches released by a stop fire no callback and count nowhere.

Fields

batchesuint64_tCallbacks invoked.
requestsuint64_tRequests delivered across those callbacks.
filled_batchesuint64_tBatches that fired on reaching max_batch_size.
ready_batchesuint64_tBatches that fired on reaching min_batch_size below the maximum.
expired_batchesuint64_tBatches that fired below min_batch_size once max_latency_microseconds passed.

Functions

ucall_server_initInitializes the server state.
server.h:182 ↗
void ucall_server_init(ucall_server_init_t *arguments);

Parameters

serverucall_server_t *Output for the new server.
outcomeucall_outcome_t *Synchronous initialization result.
optionsucall_server_options_t const *Requested configuration.
resolveducall_server_options_t *Optional output for options with every default filled in, including each interface's event_loops.

Notes

Call during setup only, before any loop is polled.

Statuses

  • ucall_status_invalid_argument_k An output, the configuration or its interfaces are missing, or a metrics setting, an address, a device name, a security mode with the key or authorizer it needs, a connection budget or the reply directories are unusable.
  • ucall_status_unsupported_k This machine does not run the requested CPU class, no I/O backend serves the configuration on it, a device is named outside Linux, or an interface asks Windows for several loops.
  • ucall_status_connection_failed_k The metrics address or a listening address does not resolve, or no socket binds to it.
  • ucall_status_not_found_k A reply directory does not exist.
  • ucall_status_overflow_k The connection and pipelining budgets overflow their counters.
ucall_server_freeFrees the server state after its polling callers have returned.
server.h:194 ↗
void ucall_server_free(ucall_server_t server);

Does nothing for NULL.

Parameters

serverinServer from ucall_server_init(), or NULL.

Notes

Stop and join polling threads, and finish application use of retained requests before teardown. Remaining request handles become invalid. Native I/O retirement can block, and on a thread other than a polling owner it waits for the kernel to end that owner's ring, which a forked child that never called exec prolongs until it exits. Returns without freeing while a client bound to it lives; free those first.

ucall_server_stopRequests shutdown and wakes blocked polling callers and batch polling owners.
server.h:208 ↗
void ucall_server_stop(ucall_server_t server);

The first call drains: the server stops accepting, closes idle connections, and answers the requests it already holds with connection: close, closing each connection after its last reply. Polling callers return once their loop has no connection left, and batch polling owners once every loop has returned. Any later call, or ucall_server_free(), cuts instead: replies still in flight are abandoned and their peers reset.

Safe from an endpoint callback or a separate thread.

Parameters

serverinServer from ucall_server_init(), or NULL.
ucall_server_take_callAdvances one server loop and invokes ready callbacks on the calling thread.
server.h:240 ↗
void ucall_server_take_call(ucall_server_take_call_t *arguments);

Callbacks run synchronously; the timeout cannot preempt them.

Parameters

serverucall_server_tInitialized server whose routes are ready.
outcomeucall_outcome_t *Outcome of this polling round.
event_loop_indexsize_tZero-based loop index; each loop has one polling owner.
timeout_microsecondssize_tMaximum wait; zero performs one bounded nonblocking progress round.

Statuses

ucall_server_take_callsDrives one server loop until ucall_server_stop() or an I/O failure.
server.h:265 ↗
void ucall_server_take_calls(ucall_server_take_calls_t *arguments);

Parameters

serverucall_server_tInitialized server whose routes are ready.
outcomeucall_outcome_t *Final outcome after polling stops or fails.
event_loop_indexsize_tZero-based loop index; each loop has one polling owner.

Statuses

ucall_server_event_loop_statisticsCopies one loop's counters, from any thread whose CPU runs the server's CPU class.
server.h:376 ↗
void ucall_server_event_loop_statistics(
    ucall_server_event_loop_statistics_t *arguments);

Each counter is read on its own, so a snapshot of a running loop is not one instant.

Parameters

serverucall_server_tInitialized server.
outcomeucall_outcome_t *The verb's status, listed on the verb.
event_loop_indexsize_tZero-based loop index.
statisticsucall_event_loop_statistics_t *Output for a snapshot of the loop's counters, zeroed on failure.

Statuses

ucall_server_batch_endpoint_statisticsCopies one batch endpoint's counters from any thread on a CPU of the server's class.
server.h:482 ↗
void ucall_server_batch_endpoint_statistics(
    ucall_server_batch_endpoint_statistics_t *arguments);

Each counter is read on its own, so a snapshot of a polled endpoint is not one instant.

Parameters

serverucall_server_tInitialized server that registered endpoint.
outcomeucall_outcome_t *The verb's status, listed on the verb.
endpointucall_http_batch_endpoint_tEndpoint from ucall_server_add_http_batch_endpoint().
statisticsucall_batch_endpoint_statistics_t *Output for a snapshot of the endpoint's counters, zeroed on failure.

Statuses

ucall_server_add_http_endpointRegisters a function callback to be triggered when a matching request arrives.
server.h:541 ↗
void ucall_server_add_http_endpoint(
    ucall_server_add_http_endpoint_t *arguments);

The uri_template field supports URI template syntax (RFC 6570 subset):

  • "/users/{id}" matches "/users/123" or "/users/abc"
  • "/posts/{year}/{month}" matches "/posts/2024/03"
  • "/files/{*path}" matches "/files/a/b/c.txt" (greedy, captures "a/b/c.txt")

Routing precedence: Literals match before template variables, before greedy wildcards.

Parameters

serverucall_server_tThe server this call operates on.
outcomeucall_outcome_t *The verb's status, listed on the verb.
uri_templateucall_bytes_view_tURI template, with an explicit byte length.
verbucall_verb_tHTTP method for this endpoint.
callbackucall_received_http_request_callback_tFunction pointer to the callback.
contextvoid *Optional payload/tag for metadata (e.g., for CPython runtime).
body_deliveryucall_body_delivery_tWhether the callback runs once the body is buffered, or at headers time with the body pulled through ucall_server_request_body_chunk().

See also

RFC 6570 Section 3.2.2, simple string expansion: https://www.rfc-editor.org/rfc/rfc6570#section-3.2.2

Notes

Call during setup only, before any loop is polled.

Statuses

ucall_server_add_http_batch_endpointRegisters a batching callback to collect multiple requests and process them together.
server.h:624 ↗
void ucall_server_add_http_batch_endpoint(
    ucall_server_add_http_batch_endpoint_t *arguments);

Very handy for batch-processing and high-latency operations (e.g., GPU inference).

Unlike client-side batching, which packs several calls into one request, the server aggregates independent requests from different sources and dispatches them together.

Batching behavior:

  • Batch size < min_batch_size: Wait for more requests (up to max_latency_microseconds).
  • Batch size >= min_batch_size: Dispatch immediately (optimal utilization).
  • Batch size >= max_batch_size: Force dispatch (prevent unbounded growth).

Requests from every event loop share one queue per batch endpoint. The callback receives a counted array of request handles on the thread polling that endpoint through ucall_server_take_batch() or ucall_server_take_batches(), never on an event loop. Registration returns the endpoint's handle, so each endpoint can have its own polling thread. Copy the request handles into application-owned storage to process them later; received bytes need no copy.

Parameters

serverucall_server_tThe server this call operates on.
outcomeucall_outcome_t *The verb's status, listed on the verb.
uri_templateucall_bytes_view_tURI template, with an explicit byte length.
verbucall_verb_tHTTP method for this endpoint.
callbackucall_received_http_batch_callback_tFunction pointer to the batch callback.
contextvoid *Optional payload/tag for metadata (e.g., for CPython runtime).
min_batch_sizesize_tOptimal batch size.
max_batch_sizesize_tMaximum requests per batch.
max_latency_microsecondssize_tMaximum wait time for partial batches.
endpointucall_http_batch_endpoint_t *Receives the endpoint that ucall_server_take_batch() and ucall_server_take_batches() poll; required.

Notes

Call during setup only, before any loop is polled.

Statuses