mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 08:13:37 +08:00
Fixes the 2026-08-29 daemon zombie class: a client disconnected while its application request was in flight, the request never observed cancellation, and runtime_worker_finish joined the request thread with no deadline. The disconnect path wedged forever, the generation stayed formally RUNNING, and a dead process held the endpoint pipes and UI port for nine hours while every new client timed out with no diagnostic. Four changes: - runtime: the disconnect-path reap of an in-flight application request now waits at most RUNTIME_ABANDONED_REQUEST_JOIN_TIMEOUT_MS (30 s), then logs daemon.application_request_unresponsive (peer pid, request token) and fail-stops the process. The kernel releases every native claim and the next client starts a fresh generation. Test seams expose the ceiling and replace the terminal stop with a recordable hook. - diagnostics: a transport connect that reaches a live process but gets no valid answer now resolves the holder's kernel pid (GetNamedPipeServerProcessId / SO_PEERCRED). `daemon start` classifies a mute holder as RESERVED (no doomed competitor spawns) and names the pid in its timeout message; `daemon status` prints "not responding (endpoint held by pid N)" with recovery guidance instead of "not running". - frontend: the maintenance monitor's quiet-state poll drops from 10 ms to 250 ms. Each probe revalidates the whole ancestor directory chain on Windows; at 100 Hz that burned ~20% of a core for the life of every MCP session (7762 CPU-seconds on one 9.5 h session). Detection latency spends 0.25 s of the 15 s activation drain budget. - version_cohort: the healthy fresh-claim record is renamed from "claimed_unheld" (misread as a stale-claim anomaly during the incident diagnosis) to "claimed_fresh" with prior_holder=none. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: OhOkThisIsFine <102485413+OhOkThisIsFine@users.noreply.github.com>
447 lines
25 KiB
C
447 lines
25 KiB
C
/*
|
|
* runtime.h — Mandatory per-account CBM daemon runtime.
|
|
*
|
|
* This layer combines the authenticated local IPC transport, exact-build
|
|
* HELLO policy, and connection-owned coordinator leases. It deliberately
|
|
* does not spawn the daemon process; executable bootstrap is a caller concern.
|
|
*/
|
|
#ifndef CBM_DAEMON_RUNTIME_H
|
|
#define CBM_DAEMON_RUNTIME_H
|
|
|
|
#include "daemon/daemon.h"
|
|
#include "daemon/ipc.h"
|
|
#include "daemon/service.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#define CBM_DAEMON_SEMVER_SIZE 12U
|
|
|
|
/* Detailed post-admission operation ABI. It is deliberately absent from the
|
|
* stable endpoint HELLO: an exact executable fingerprint already selects this
|
|
* layout, while conflicting generations must remain able to diagnose each
|
|
* other even when this value and every detailed payload have changed. */
|
|
#define CBM_DAEMON_RUNTIME_WIRE_ABI 1U
|
|
|
|
/* Permanent account-wide rendezvous envelope, generation zero. These numeric
|
|
* capacities and byte sizes are frozen independently of service/runtime data
|
|
* structures. A future generation must preserve both request and response
|
|
* layouts exactly at the stable endpoint.
|
|
*
|
|
* Request: u32 ABI @0, version[64] @4, build[65] @68.
|
|
* Response: u32 connect @0, u32 hello @4, u64 client @8, u64 PID @16,
|
|
* u32 conflict @24, active version[64] @28, active build[65] @92,
|
|
* requested version[64] @157, requested build[65] @221,
|
|
* message[512] @286. All integers use network byte order. */
|
|
#define CBM_DAEMON_RENDEZVOUS_ABI 1U
|
|
#define CBM_DAEMON_RENDEZVOUS_VERSION_TEXT_CAP 64U
|
|
#define CBM_DAEMON_RENDEZVOUS_BUILD_FINGERPRINT_CAP 65U
|
|
#define CBM_DAEMON_RENDEZVOUS_MESSAGE_CAP 512U
|
|
#define CBM_DAEMON_RENDEZVOUS_REQUEST_SIZE 133U
|
|
#define CBM_DAEMON_RENDEZVOUS_RESPONSE_SIZE 798U
|
|
/* Cross-version activation shutdown is a separate first-frame protocol which
|
|
* remains parseable when normal HELLO would report a version/build conflict.
|
|
* Request: u32 action @0 followed by the unchanged 133-byte rendezvous
|
|
* identity @4. Response: u32 rendezvous ABI @0, u32 accepted @4, u64 active
|
|
* client snapshot @8, u64 drained-connection snapshot @16 (the activation
|
|
* requester excludes itself). */
|
|
#define CBM_DAEMON_ACTIVATION_SHUTDOWN_REQUEST_SIZE 137U
|
|
#define CBM_DAEMON_ACTIVATION_SHUTDOWN_RESPONSE_SIZE 24U
|
|
|
|
/* STATUS/STOP wire sizes. Requests carry only the requester's claimed build
|
|
* fingerprint (kernel-peer-verified, cross-build by design). Responses are
|
|
* fixed-size with a capped committed-client pid table. */
|
|
#define CBM_DAEMON_CONTROL_REQUEST_SIZE 65U
|
|
#define CBM_DAEMON_CONTROL_CLIENT_CAP 8U
|
|
#define CBM_DAEMON_STATUS_RESPONSE_SIZE 121U
|
|
#define CBM_DAEMON_STOP_RESPONSE_SIZE 40U
|
|
#define CBM_DAEMON_RUNTIME_PROJECT_KEY_MAX 4096U
|
|
#define CBM_DAEMON_RUNTIME_APPLICATION_PAYLOAD_MAX (CBM_DAEMON_MAX_FRAME_SIZE - 16U)
|
|
|
|
/* Frame flags are operation codes. Every operation has one exact payload
|
|
* length (or an explicitly length-prefixed payload); trailing bytes are a
|
|
* protocol violation and close the connection without registering a client.
|
|
* Multi-byte integers are encoded in network byte order. */
|
|
typedef enum {
|
|
CBM_DAEMON_RUNTIME_OP_HELLO = 1,
|
|
CBM_DAEMON_RUNTIME_OP_HEARTBEAT = 2,
|
|
CBM_DAEMON_RUNTIME_OP_JOB_SUBSCRIBE = 3,
|
|
CBM_DAEMON_RUNTIME_OP_JOB_UNSUBSCRIBE = 4,
|
|
CBM_DAEMON_RUNTIME_OP_DISCONNECT = 5,
|
|
CBM_DAEMON_RUNTIME_OP_APPLICATION_REQUEST = 6,
|
|
CBM_DAEMON_RUNTIME_OP_APPLICATION_CANCEL = 7,
|
|
CBM_DAEMON_RUNTIME_OP_ACTIVATION_SHUTDOWN = 8,
|
|
/* Fire-and-forget departure notice sent by close_begin when it interrupts
|
|
* an in-flight exchange. POSIX peers additionally signal departure via
|
|
* shutdown()/EOF; Windows named pipes have no client half-close, so this
|
|
* frame is what releases admission before the handle closes. No response
|
|
* is sent: the client's receive path is already interrupted. */
|
|
CBM_DAEMON_RUNTIME_OP_CLOSE_INTENT = 9,
|
|
/* `daemon status` observability probe. Like ACTIVATION_SHUTDOWN this is a
|
|
* one-shot authenticated first frame — no cohort, no HELLO, no admission —
|
|
* so it works across build skew, which is exactly when status matters. */
|
|
CBM_DAEMON_RUNTIME_OP_STATUS = 10,
|
|
/* `daemon stop`. Same authentication shape as STATUS. Refuses while
|
|
* committed clients exist (their pids come back in the response) and
|
|
* otherwise begins the normal stopping sequence — including for a
|
|
* PERMANENT generation, which this op and process kill alone may end. */
|
|
CBM_DAEMON_RUNTIME_OP_STOP = 11,
|
|
} cbm_daemon_runtime_operation_t;
|
|
|
|
typedef enum {
|
|
CBM_DAEMON_RUNTIME_ACTIVATION_INSTALL = 1,
|
|
CBM_DAEMON_RUNTIME_ACTIVATION_UPDATE = 2,
|
|
CBM_DAEMON_RUNTIME_ACTIVATION_UNINSTALL = 3,
|
|
} cbm_daemon_runtime_activation_action_t;
|
|
|
|
typedef struct {
|
|
bool accepted;
|
|
uint64_t active_clients;
|
|
uint64_t active_connections;
|
|
} cbm_daemon_runtime_activation_result_t;
|
|
|
|
typedef struct cbm_daemon_runtime_service cbm_daemon_runtime_service_t;
|
|
typedef struct cbm_daemon_runtime_client cbm_daemon_runtime_client_t;
|
|
|
|
typedef enum {
|
|
CBM_DAEMON_RUNTIME_SERVICE_STARTING = 1,
|
|
CBM_DAEMON_RUNTIME_SERVICE_RUNNING = 2,
|
|
CBM_DAEMON_RUNTIME_SERVICE_STOPPING = 3,
|
|
CBM_DAEMON_RUNTIME_SERVICE_EXITED = 4,
|
|
} cbm_daemon_runtime_service_state_t;
|
|
|
|
/* Application requests are binary-safe and bounded. TRANSPORT_ERROR is local
|
|
* to the client API and is never emitted as a valid wire response. BUSY is
|
|
* generated by the runtime when a connection already has an in-flight request;
|
|
* CANCELLED is the correlated, request-scoped cancellation result. */
|
|
typedef enum {
|
|
CBM_DAEMON_RUNTIME_APPLICATION_TRANSPORT_ERROR = 0,
|
|
CBM_DAEMON_RUNTIME_APPLICATION_OK = 1,
|
|
CBM_DAEMON_RUNTIME_APPLICATION_BUSY = 2,
|
|
CBM_DAEMON_RUNTIME_APPLICATION_UNAVAILABLE = 3,
|
|
CBM_DAEMON_RUNTIME_APPLICATION_REJECTED = 4,
|
|
CBM_DAEMON_RUNTIME_APPLICATION_HANDLER_ERROR = 5,
|
|
CBM_DAEMON_RUNTIME_APPLICATION_CANCELLED = 6,
|
|
} cbm_daemon_runtime_application_status_t;
|
|
|
|
typedef uint64_t cbm_daemon_runtime_application_token_t;
|
|
#define CBM_DAEMON_RUNTIME_APPLICATION_TOKEN_INVALID UINT64_C(0)
|
|
|
|
typedef enum {
|
|
CBM_DAEMON_RUNTIME_CANCEL_ERROR = -1,
|
|
CBM_DAEMON_RUNTIME_CANCEL_STALE = 0,
|
|
CBM_DAEMON_RUNTIME_CANCEL_ACCEPTED = 1,
|
|
} cbm_daemon_runtime_cancel_result_t;
|
|
|
|
typedef void cbm_daemon_runtime_application_session_t;
|
|
|
|
typedef cbm_daemon_runtime_application_session_t *(*cbm_daemon_runtime_application_session_open_fn)(
|
|
void *context, cbm_daemon_client_id_t client_id, uint64_t authenticated_process_id);
|
|
|
|
/* For OK, response_out may receive a malloc-owned binary buffer which the
|
|
* runtime frees after sending; NULL is valid only for a zero-length response.
|
|
* Non-OK results must leave an empty response. The request buffer is an owned
|
|
* runtime copy and remains valid only for the duration of this callback. */
|
|
typedef cbm_daemon_runtime_application_status_t (*cbm_daemon_runtime_application_request_fn)(
|
|
void *context, cbm_daemon_runtime_application_session_t *session,
|
|
cbm_daemon_runtime_application_token_t request_token, const uint8_t *request,
|
|
uint32_t request_length, uint8_t **response_out, uint32_t *response_length_out);
|
|
|
|
/* Request cancellation is non-terminal and may arrive before request() enters.
|
|
* The exact token must therefore remain sticky until that request observes it.
|
|
* The callback must be nonblocking and safe while request() is running. */
|
|
typedef void (*cbm_daemon_runtime_application_request_cancel_fn)(
|
|
void *context, cbm_daemon_runtime_application_session_t *session,
|
|
cbm_daemon_runtime_application_token_t request_token);
|
|
|
|
/* cancel is terminal for the session and is invoked after coordinator
|
|
* ownership is released. It may run before a newly-created request thread
|
|
* enters request(), so the cancellation state must remain sticky. It must be
|
|
* nonblocking, safe to invoke while request is running, and arrange for that
|
|
* request to return promptly. open and close are synchronous and must also
|
|
* return promptly; close is invoked exactly once after any request thread has
|
|
* joined. Runtime teardown is necessarily cooperative at this callback
|
|
* boundary; it never detaches or frees a still-running callback. */
|
|
typedef void (*cbm_daemon_runtime_application_session_cancel_fn)(
|
|
void *context, cbm_daemon_runtime_application_session_t *session);
|
|
typedef void (*cbm_daemon_runtime_application_session_close_fn)(
|
|
void *context, cbm_daemon_runtime_application_session_t *session);
|
|
|
|
typedef struct {
|
|
void *context;
|
|
cbm_daemon_runtime_application_session_open_fn session_open;
|
|
cbm_daemon_runtime_application_request_fn request;
|
|
cbm_daemon_runtime_application_request_cancel_fn request_cancel;
|
|
cbm_daemon_runtime_application_session_cancel_fn session_cancel;
|
|
cbm_daemon_runtime_application_session_close_fn session_close;
|
|
} cbm_daemon_runtime_application_callbacks_t;
|
|
|
|
typedef struct {
|
|
const cbm_daemon_ipc_endpoint_t *endpoint;
|
|
cbm_daemon_build_identity_t identity;
|
|
const char *conflict_log_path;
|
|
size_t conflict_log_cap_bytes;
|
|
/* Hard cap on accepted connection threads, including sockets that have not
|
|
* completed HELLO. request_timeout_ms bounds every unauthenticated slot
|
|
* and therefore must be finite, not CBM_DAEMON_IPC_WAIT_FOREVER. */
|
|
uint32_t max_clients;
|
|
uint64_t lease_timeout_ms;
|
|
uint32_t request_timeout_ms;
|
|
/* Bounds cooperative stop and retained participant-guard cleanup. Must be
|
|
* finite, not CBM_DAEMON_IPC_WAIT_FOREVER. */
|
|
uint32_t shutdown_timeout_ms;
|
|
/* Disabled when all handlers are NULL. Otherwise all five handlers are
|
|
* required. Function pointers and context are copied; the context's owner
|
|
* must keep it alive until service_free returns true. */
|
|
cbm_daemon_runtime_application_callbacks_t application;
|
|
/* Born via `daemon start`: the service does not begin stopping when its
|
|
* last committed client disconnects; only the stop/drain ops or an
|
|
* explicit process kill end it. */
|
|
bool permanent;
|
|
} cbm_daemon_runtime_service_config_t;
|
|
|
|
typedef enum {
|
|
CBM_DAEMON_RUNTIME_CONNECT_ERROR = 0,
|
|
CBM_DAEMON_RUNTIME_CONNECT_ACCEPTED = 1,
|
|
CBM_DAEMON_RUNTIME_CONNECT_CONFLICT = 2,
|
|
CBM_DAEMON_RUNTIME_CONNECT_REJECTED = 3,
|
|
} cbm_daemon_runtime_connect_status_t;
|
|
|
|
typedef struct {
|
|
cbm_daemon_runtime_connect_status_t status;
|
|
cbm_daemon_hello_status_t hello_status;
|
|
cbm_daemon_client_id_t client_id;
|
|
/* Kernel-authenticated PID of this client as observed by the daemon. */
|
|
uint64_t authenticated_process_id;
|
|
/* Nonzero only when the transport connect succeeded but no valid response
|
|
* arrived in time: the kernel-reported PID of the process that holds the
|
|
* endpoint. A held-but-mute endpoint is a live process whose runtime is
|
|
* wedged (2026-08-29 zombie incident) — callers must report this PID
|
|
* rather than fold the failure into plain absence. */
|
|
uint64_t muted_endpoint_holder_pid;
|
|
cbm_daemon_conflict_t conflict;
|
|
char message[CBM_DAEMON_CONFLICT_MESSAGE_SIZE];
|
|
} cbm_daemon_runtime_connect_result_t;
|
|
|
|
/* The fixed HELLO request contains only rendezvous ABI, semantic version, and
|
|
* exact executable SHA-256. It has no detailed ABI, client-ID, PID, user, or
|
|
* endpoint fields. Strings are NUL-terminated and zero padded. Encoding and
|
|
* validation depend only on these stable fields; detailed identity fields may
|
|
* be zero or unknown without changing the bytes. */
|
|
bool cbm_daemon_runtime_hello_request_encode(uint8_t out[CBM_DAEMON_RENDEZVOUS_REQUEST_SIZE],
|
|
const cbm_daemon_build_identity_t *identity);
|
|
|
|
/* Resolve process_id through the OS kernel's process metadata and hash the
|
|
* executable image rather than reopening an unbound pathname. Linux hashes an
|
|
* open /proc/<pid>/exe handle anchored to one process instance; macOS accepts
|
|
* an opened path only while its vnode is present in that process's executable
|
|
* mappings before and after hashing. Windows holds one process instance and a
|
|
* stable, non-share-write image-file handle across the hash, with path and
|
|
* creation-time snapshots around it (the Windows loader's image-section
|
|
* replacement restriction supplies the final path-to-mapping binding).
|
|
* Unsupported, inaccessible, replaced, or otherwise raced images fail closed.
|
|
* The daemon hashes and retains its stable native image object at startup;
|
|
* HELLO first compares a bracketed peer's mapped-image file identity with that
|
|
* retained object. A provable identity match avoids re-hashing. Different or
|
|
* unverifiable identities fall back to this full process-image fingerprint so
|
|
* byte-identical copies remain compatible while changed copies are rejected.
|
|
* Stateful HELLO admission requires the proven or computed fingerprint to
|
|
* match both the claimed and active build fingerprints. */
|
|
bool cbm_daemon_runtime_process_build_fingerprint(uint64_t process_id,
|
|
char out[CBM_DAEMON_BUILD_FINGERPRINT_SIZE]);
|
|
|
|
/* Ask any current daemon generation to drain before install/update/uninstall.
|
|
* This is not a normal HELLO and never creates an application session. The
|
|
* kernel-authenticated peer image must match identity->build_fingerprint, but
|
|
* that build is deliberately allowed to differ from the active daemon. A true
|
|
* return means a fixed response was received; inspect result_out->accepted.
|
|
* timeout_ms must be finite. */
|
|
bool cbm_daemon_runtime_request_activation_shutdown(
|
|
const cbm_daemon_ipc_endpoint_t *endpoint, const cbm_daemon_build_identity_t *identity,
|
|
cbm_daemon_runtime_activation_action_t action, uint32_t timeout_ms,
|
|
cbm_daemon_runtime_activation_result_t *result_out);
|
|
|
|
typedef struct {
|
|
bool permanent;
|
|
bool stopping;
|
|
uint16_t committed_clients;
|
|
uint32_t daemon_pid;
|
|
uint8_t client_count; /* entries in client_pids, capped at the wire limit */
|
|
uint32_t client_pids[CBM_DAEMON_CONTROL_CLIENT_CAP];
|
|
char build_fingerprint[CBM_DAEMON_BUILD_FINGERPRINT_SIZE];
|
|
char semantic_version[CBM_DAEMON_SEMVER_SIZE];
|
|
/* Meaningful even when the status request itself FAILED: nonzero when the
|
|
* endpoint accepted the transport connect but answered nothing — the
|
|
* kernel-reported PID of the mute holder. `daemon status` uses this to
|
|
* name a zombie generation instead of reporting bare "not running". */
|
|
uint64_t muted_endpoint_holder_pid;
|
|
} cbm_daemon_runtime_status_t;
|
|
|
|
typedef struct {
|
|
bool accepted; /* stopping was initiated */
|
|
bool busy; /* refused: committed clients hold the daemon */
|
|
uint16_t committed_clients;
|
|
uint8_t client_count;
|
|
uint32_t client_pids[CBM_DAEMON_CONTROL_CLIENT_CAP];
|
|
} cbm_daemon_runtime_stop_result_t;
|
|
|
|
/* One-shot authenticated status probe (no cohort, no HELLO, no admission).
|
|
* Works across build skew. A true return means a valid response arrived. */
|
|
bool cbm_daemon_runtime_request_status(const cbm_daemon_ipc_endpoint_t *endpoint,
|
|
const cbm_daemon_build_identity_t *identity,
|
|
uint32_t timeout_ms,
|
|
cbm_daemon_runtime_status_t *status_out);
|
|
|
|
/* One-shot authenticated stop request. Refuses while committed clients exist
|
|
* (result_out lists their pids); otherwise begins the stopping sequence, for
|
|
* permanent generations too. */
|
|
bool cbm_daemon_runtime_request_stop(const cbm_daemon_ipc_endpoint_t *endpoint,
|
|
const cbm_daemon_build_identity_t *identity,
|
|
uint32_t timeout_ms,
|
|
cbm_daemon_runtime_stop_result_t *result_out);
|
|
|
|
/* Performs the complete guarded first-participant handoff, starts listening
|
|
* synchronously, then owns both that participant claim and its
|
|
* accept/connection threads. All config scalar/text data is copied. endpoint
|
|
* is borrowed only for this synchronous call and may be freed after a
|
|
* successful return. Only one service may listen at an endpoint. */
|
|
cbm_daemon_runtime_service_t *cbm_daemon_runtime_service_start(
|
|
const cbm_daemon_runtime_service_config_t *config);
|
|
|
|
/* Host-start variant for preparing inert application resources before the
|
|
* listener. The caller must acquire the endpoint lifetime reservation first;
|
|
* this lets the host defer watcher/UI/diagnostic threads until the call has
|
|
* succeeded. A successful start transfers the reservation into the service
|
|
* and clears *reservation_io. On failure it remains owned by the caller unless
|
|
* the service had already adopted it and released it during rollback. */
|
|
cbm_daemon_runtime_service_t *cbm_daemon_runtime_service_start_reserved(
|
|
const cbm_daemon_runtime_service_config_t *config,
|
|
cbm_daemon_ipc_lifetime_reservation_t **reservation_io);
|
|
|
|
cbm_daemon_runtime_service_state_t cbm_daemon_runtime_service_state(
|
|
cbm_daemon_runtime_service_t *service);
|
|
size_t cbm_daemon_runtime_service_active_clients(cbm_daemon_runtime_service_t *service);
|
|
/* Monotonic count of every admission since service start; never decremented.
|
|
* Use for "has any client ever connected" decisions — the live count above
|
|
* can read zero between two short-lived sessions. */
|
|
uint64_t cbm_daemon_runtime_service_clients_admitted_total(cbm_daemon_runtime_service_t *service);
|
|
/* Includes accepted connections still waiting for HELLO. Never exceeds the
|
|
* configured max_clients; over-cap peers receive REJECTED before close. */
|
|
size_t cbm_daemon_runtime_service_active_connections(cbm_daemon_runtime_service_t *service);
|
|
size_t cbm_daemon_runtime_service_job_subscribers(cbm_daemon_runtime_service_t *service,
|
|
const char *project_key);
|
|
uint64_t cbm_daemon_runtime_service_client_process_id(cbm_daemon_runtime_service_t *service,
|
|
cbm_daemon_client_id_t client_id);
|
|
|
|
/* Wait functions use a monotonic deadline and never sleep past timeout_ms. */
|
|
bool cbm_daemon_runtime_service_wait_for_clients(cbm_daemon_runtime_service_t *service,
|
|
size_t expected, uint32_t timeout_ms);
|
|
bool cbm_daemon_runtime_service_wait_for_connections(cbm_daemon_runtime_service_t *service,
|
|
size_t expected, uint32_t timeout_ms);
|
|
bool cbm_daemon_runtime_service_wait_exited(cbm_daemon_runtime_service_t *service,
|
|
uint32_t timeout_ms);
|
|
|
|
/* Worker supervisors report the end of two-phase cancellation through this
|
|
* API. The service cannot exit while a cancelled job remains unreaped. */
|
|
bool cbm_daemon_runtime_service_job_reaped(cbm_daemon_runtime_service_t *service,
|
|
const char *project_key);
|
|
|
|
/* Emergency/test teardown only. Normal lifetime is connection-owned: the
|
|
* final disconnect makes STOPPING terminal, drains/reaps within the configured
|
|
* bound, and exits automatically. stop is itself bounded by timeout_ms. */
|
|
bool cbm_daemon_runtime_service_stop(cbm_daemon_runtime_service_t *service, uint32_t timeout_ms);
|
|
/* Before wait_exited or a successful stop this returns false without teardown.
|
|
* Otherwise it returns true only after every thread is joined, the owned
|
|
* participant guard is released, and the service allocation is destroyed. A
|
|
* false return retains the service and all retry authority; the caller must
|
|
* retry or fail-stop the owning process. */
|
|
bool cbm_daemon_runtime_service_free(cbm_daemon_runtime_service_t *service);
|
|
|
|
/* A successful return is connection-bound. The daemon delivers an explicit
|
|
* mismatch result (and persists it) before closing a rejected connection.
|
|
* Client exchange timeouts must be finite, not CBM_DAEMON_IPC_WAIT_FOREVER. */
|
|
cbm_daemon_runtime_client_t *cbm_daemon_runtime_client_connect(
|
|
const cbm_daemon_ipc_endpoint_t *endpoint, const cbm_daemon_build_identity_t *identity,
|
|
uint32_t timeout_ms, cbm_daemon_runtime_connect_result_t *result_out);
|
|
|
|
cbm_daemon_client_id_t cbm_daemon_runtime_client_id(const cbm_daemon_runtime_client_t *client);
|
|
uint64_t cbm_daemon_runtime_client_process_id(const cbm_daemon_runtime_client_t *client);
|
|
|
|
cbm_daemon_subscription_result_t cbm_daemon_runtime_client_job_subscribe(
|
|
cbm_daemon_runtime_client_t *client, const char *project_key,
|
|
cbm_daemon_subscription_id_t *subscription_id_out, uint32_t timeout_ms);
|
|
bool cbm_daemon_runtime_client_job_unsubscribe(cbm_daemon_runtime_client_t *client,
|
|
cbm_daemon_subscription_id_t subscription_id,
|
|
uint32_t timeout_ms);
|
|
bool cbm_daemon_runtime_client_heartbeat(cbm_daemon_runtime_client_t *client, uint32_t timeout_ms);
|
|
|
|
/* Reserve a monotonically increasing token for the next application request.
|
|
* Only one unstarted reservation may exist per client. It must be consumed by
|
|
* request_tagged() (or the client must be closed) before another reservation.
|
|
* Tokens are never reused within a client connection. */
|
|
bool cbm_daemon_runtime_client_application_token_reserve(
|
|
cbm_daemon_runtime_client_t *client, cbm_daemon_runtime_application_token_t *token_out);
|
|
|
|
/* Send a one-way cancellation control for an exact reserved/active request.
|
|
* ACCEPTED means the control was queued or written; callback completion is the
|
|
* final cancellation linearization point. Stale/wrong tokens are harmless. */
|
|
cbm_daemon_runtime_cancel_result_t cbm_daemon_runtime_client_application_cancel(
|
|
cbm_daemon_runtime_client_t *client, cbm_daemon_runtime_application_token_t request_token);
|
|
|
|
/* Executes one binary application request. Calls on one client are serialized.
|
|
* response_out is malloc-owned on OK and must be freed by the caller; an empty
|
|
* OK response is represented by NULL/0. Invalid/oversized requests are rejected
|
|
* locally without poisoning an otherwise usable client. */
|
|
cbm_daemon_runtime_application_status_t cbm_daemon_runtime_client_application_request(
|
|
cbm_daemon_runtime_client_t *client, const void *request, uint32_t request_length,
|
|
uint8_t **response_out, uint32_t *response_length_out, uint32_t timeout_ms);
|
|
|
|
/* Execute using the sole outstanding token returned by token_reserve(). This
|
|
* is the cancellable frontend path; the legacy helper above reserves
|
|
* internally. */
|
|
cbm_daemon_runtime_application_status_t cbm_daemon_runtime_client_application_request_tagged(
|
|
cbm_daemon_runtime_client_t *client, cbm_daemon_runtime_application_token_t request_token,
|
|
const void *request, uint32_t request_length, uint8_t **response_out,
|
|
uint32_t *response_length_out, uint32_t timeout_ms);
|
|
|
|
/* Begin a two-phase close without freeing the client. This atomically rejects
|
|
* future exchanges and interrupts an exchange already in flight. Exactly one
|
|
* caller may begin close. After all threads that could have entered a client
|
|
* API are joined, close_finish closes/frees the retained handle. */
|
|
bool cbm_daemon_runtime_client_close_begin(cbm_daemon_runtime_client_t *client);
|
|
|
|
/* Finish a close begun by close_begin. Sends DISCONNECT when the transport was
|
|
* not interrupted, always closes/frees the local handle, and returns whether
|
|
* the daemon acknowledged DISCONNECT. The client is invalid after this call. */
|
|
bool cbm_daemon_runtime_client_close_finish(cbm_daemon_runtime_client_t *client,
|
|
uint32_t timeout_ms);
|
|
|
|
/* One-call convenience wrapper around close_begin + close_finish. Ownership on
|
|
* the server is tied to EOF as well as the explicit operation. This remains
|
|
* safe with one exchange that was already in flight, but callers with a worker
|
|
* that may be immediately about to enter an exchange must use the two-phase API
|
|
* and join that worker before close_finish. */
|
|
bool cbm_daemon_runtime_client_close(cbm_daemon_runtime_client_t *client, uint32_t timeout_ms);
|
|
|
|
#ifdef CBM_ENABLE_TEST_SEAMS
|
|
/* #1383 test seam: force peer-image verification to fail so tests can exercise
|
|
* the rejection-response path from the in-process harness. */
|
|
void cbm_daemon_runtime_force_peer_image_unverified_for_testing(bool force);
|
|
/* #1539 test seam: force the peer image to be FINGERPRINTABLE but different,
|
|
* i.e. the tamper case that must still be rejected after unverifiable images
|
|
* became admissible. */
|
|
void cbm_daemon_runtime_force_peer_image_mismatch_for_testing(bool force);
|
|
/* Abandoned-request containment seams (2026-08-29 zombie incident). The
|
|
* timeout override shrinks the join ceiling to test scale; zero restores the
|
|
* production constant. The hook replaces the terminal containment stop with a
|
|
* recordable callback so an in-process harness survives the trigger; NULL
|
|
* restores process termination. */
|
|
typedef void (*cbm_daemon_runtime_containment_hook_t)(const char *component);
|
|
void cbm_daemon_runtime_set_abandoned_request_join_timeout_for_testing(uint32_t timeout_ms);
|
|
void cbm_daemon_runtime_set_containment_hook_for_testing(
|
|
cbm_daemon_runtime_containment_hook_t hook);
|
|
#endif
|
|
|
|
#endif /* CBM_DAEMON_RUNTIME_H */
|