/* * test_daemon_frontend.c — Exact JSON-RPC cancellation and maintenance. */ #include "test_framework.h" #include "test_helpers.h" #include "daemon/frontend.h" #include "daemon/ipc.h" #include "daemon/service.h" #include "daemon/version_cohort.h" #include "foundation/compat.h" #include "foundation/compat_thread.h" #include "foundation/platform.h" #include #include #include #include #ifndef _WIN32 #include #include #include #include #include #endif enum { FRONTEND_TEST_PATH_CAP = 1024 }; typedef struct { char parent[FRONTEND_TEST_PATH_CAP]; cbm_daemon_ipc_endpoint_t *endpoint; cbm_version_cohort_manager_t *manager; } frontend_maintenance_fixture_t; static bool frontend_maintenance_fixture_start(frontend_maintenance_fixture_t *fixture, const char *tag) { memset(fixture, 0, sizeof(*fixture)); /* Endpoint parents need production-shaped ancestry (LocalAppData on * Windows); temp roots are refused by the runtime ancestry validation. */ if (!th_secure_runtime_parent_new(fixture->parent, sizeof(fixture->parent), tag)) { return false; } fixture->endpoint = cbm_daemon_ipc_endpoint_new("0123456789abcdef", fixture->parent); fixture->manager = fixture->endpoint ? cbm_version_cohort_manager_new(fixture->endpoint) : NULL; return fixture->endpoint && fixture->manager; } static void frontend_maintenance_fixture_finish(frontend_maintenance_fixture_t *fixture) { while (fixture->manager && cbm_version_cohort_manager_free(&fixture->manager) != CBM_PRIVATE_FILE_LOCK_OK) { cbm_usleep(1000); } cbm_daemon_ipc_endpoint_free(fixture->endpoint); if (fixture->parent[0]) { (void)th_rmtree(fixture->parent); } memset(fixture, 0, sizeof(*fixture)); } #ifndef _WIN32 static const char FRONTEND_TEST_BUILD[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; static const char FRONTEND_TEST_CACHE[] = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; enum { /* Hang guards, not benchmarks: starved 3-4-core CI runners need several * seconds for frontend/daemon startup paths that finish instantly on a * dev machine (see the index-supervisor calibration). */ FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS = 30000, FRONTEND_EOF_TEST_CATASTROPHIC_TIMEOUT_S = 90, FRONTEND_BACKPRESSURE_MESSAGE_BYTES = 2 * 1024 * 1024, FRONTEND_BACKPRESSURE_FRONTEND_TIMEOUT_S = 90, FRONTEND_BACKPRESSURE_DAEMON_TIMEOUT_S = 90, FRONTEND_BACKPRESSURE_RUNTIME_TIMEOUT_MS = 90000, FRONTEND_BACKPRESSURE_CLEANUP_TIMEOUT_MS = 30000, /* The production queue is deliberately bounded below this count. Keep the * regression black-box: it must remain valid if the exact capacity changes * while still proving that overload cannot hide an already-pending EOF. */ FRONTEND_EOF_TEST_OVERFLOW_MESSAGES = 32, }; typedef struct { atomic_int requests; atomic_int session_cancels; atomic_int sessions_opened; atomic_int first_session_cancels; atomic_int second_session_cancels; atomic_bool block_first_request; atomic_bool first_request_started; int request_observed_fd; int session_cancel_fd; } frontend_eof_application_context_t; typedef struct { frontend_eof_application_context_t *context; atomic_bool cancelled; int session_index; } frontend_eof_application_session_t; typedef struct { int fd; bool overflow; frontend_eof_application_context_t *application; atomic_bool finished; atomic_bool succeeded; } frontend_eof_writer_t; typedef struct { char conflict_log[FRONTEND_TEST_PATH_CAP]; cbm_daemon_ipc_endpoint_t *endpoint; cbm_version_cohort_manager_t *manager; cbm_daemon_runtime_service_t *service; cbm_daemon_runtime_client_t *client; frontend_eof_application_context_t application; } frontend_eof_fixture_t; static cbm_daemon_build_identity_t frontend_test_identity(void) { cbm_daemon_build_identity_t identity = { .semantic_version = "2.4.0", .build_fingerprint = FRONTEND_TEST_BUILD, .cache_fingerprint = FRONTEND_TEST_CACHE, .protocol_abi = 3, .store_abi = 11, .feature_abi = 7, }; return identity; } static cbm_daemon_runtime_application_session_t *frontend_eof_application_session_open( void *opaque, cbm_daemon_client_id_t client_id, uint64_t authenticated_process_id) { frontend_eof_application_context_t *context = opaque; frontend_eof_application_session_t *session = calloc(1, sizeof(*session)); if (!context || !session || client_id == CBM_DAEMON_CLIENT_ID_INVALID || authenticated_process_id == 0) { free(session); return NULL; } session->context = context; session->session_index = atomic_fetch_add_explicit(&context->sessions_opened, 1, memory_order_relaxed); atomic_init(&session->cancelled, false); return (cbm_daemon_runtime_application_session_t *)session; } static cbm_daemon_runtime_application_status_t frontend_eof_application_request( void *opaque, cbm_daemon_runtime_application_session_t *opaque_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) { (void)request_token; frontend_eof_application_context_t *context = opaque; frontend_eof_application_session_t *session = (frontend_eof_application_session_t *)opaque_session; if (!context || !session || session->context != context || !response_out || !response_length_out || (request_length > 0 && !request)) { return CBM_DAEMON_RUNTIME_APPLICATION_HANDLER_ERROR; } *response_out = NULL; *response_length_out = 0; int request_index = atomic_fetch_add_explicit(&context->requests, 1, memory_order_relaxed); if (request_index == 0 && atomic_load_explicit(&context->block_first_request, memory_order_acquire)) { atomic_store_explicit(&context->first_request_started, true, memory_order_release); while (!atomic_load_explicit(&session->cancelled, memory_order_acquire)) { cbm_usleep(1000); } return CBM_DAEMON_RUNTIME_APPLICATION_CANCELLED; } if (request_length == 0) { return CBM_DAEMON_RUNTIME_APPLICATION_OK; } uint8_t *response = malloc(request_length); if (!response) { return CBM_DAEMON_RUNTIME_APPLICATION_HANDLER_ERROR; } memcpy(response, request, request_length); *response_out = response; *response_length_out = request_length; if (context->request_observed_fd >= 0) { const char marker = 'Q'; (void)write(context->request_observed_fd, &marker, 1); } return CBM_DAEMON_RUNTIME_APPLICATION_OK; } static void frontend_eof_application_request_cancel( void *opaque, cbm_daemon_runtime_application_session_t *opaque_session, cbm_daemon_runtime_application_token_t request_token) { (void)request_token; frontend_eof_application_context_t *context = opaque; frontend_eof_application_session_t *session = (frontend_eof_application_session_t *)opaque_session; if (context && session && session->context == context) { atomic_store_explicit(&session->cancelled, true, memory_order_release); } } static void frontend_eof_application_session_cancel( void *opaque, cbm_daemon_runtime_application_session_t *opaque_session) { frontend_eof_application_context_t *context = opaque; frontend_eof_application_session_t *session = (frontend_eof_application_session_t *)opaque_session; if (context && session && session->context == context) { atomic_store_explicit(&session->cancelled, true, memory_order_release); if (session->session_index == 0) { (void)atomic_fetch_add_explicit(&context->first_session_cancels, 1, memory_order_relaxed); } else if (session->session_index == 1) { (void)atomic_fetch_add_explicit(&context->second_session_cancels, 1, memory_order_relaxed); } /* Publish the total only after the exact-session counter. The daemon * fixture acquires this value before inspecting attribution. */ (void)atomic_fetch_add_explicit(&context->session_cancels, 1, memory_order_release); if (context->session_cancel_fd >= 0) { const char marker = 'C'; (void)write(context->session_cancel_fd, &marker, 1); } } } static void frontend_eof_application_session_close( void *opaque, cbm_daemon_runtime_application_session_t *opaque_session) { frontend_eof_application_context_t *context = opaque; frontend_eof_application_session_t *session = (frontend_eof_application_session_t *)opaque_session; if (context && session && session->context == context) { free(session); } } static bool frontend_eof_write_all(int fd, const char *bytes, size_t length) { size_t offset = 0; while (offset < length) { ssize_t written = write(fd, bytes + offset, length - offset); if (written > 0) { offset += (size_t)written; } else if (written < 0 && errno == EINTR) { continue; } else { return false; } } return true; } static void *frontend_eof_writer(void *opaque) { frontend_eof_writer_t *writer = opaque; static const char first[] = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"test/block\",\"params\":{}}\n"; bool ok = frontend_eof_write_all(writer->fd, first, sizeof(first) - 1); uint64_t deadline = cbm_now_ms() + FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS; while ( ok && !atomic_load_explicit(&writer->application->first_request_started, memory_order_acquire) && cbm_now_ms() < deadline) { cbm_usleep(1000); } ok = ok && atomic_load_explicit(&writer->application->first_request_started, memory_order_acquire); for (int index = 0; ok && writer->overflow && index < FRONTEND_EOF_TEST_OVERFLOW_MESSAGES; index++) { char message[160]; int written = snprintf(message, sizeof(message), "{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"test/queued\",\"params\":{}}\n", index + 2); ok = written > 0 && written < (int)sizeof(message) && frontend_eof_write_all(writer->fd, message, (size_t)written); } ok = close(writer->fd) == 0 && ok; writer->fd = -1; atomic_store_explicit(&writer->succeeded, ok, memory_order_release); atomic_store_explicit(&writer->finished, true, memory_order_release); return NULL; } static bool frontend_eof_fixture_start(frontend_eof_fixture_t *fixture, const char *parent) { memset(fixture, 0, sizeof(*fixture)); atomic_init(&fixture->application.requests, 0); atomic_init(&fixture->application.session_cancels, 0); atomic_init(&fixture->application.sessions_opened, 0); atomic_init(&fixture->application.first_session_cancels, 0); atomic_init(&fixture->application.second_session_cancels, 0); atomic_init(&fixture->application.block_first_request, true); atomic_init(&fixture->application.first_request_started, false); fixture->application.request_observed_fd = -1; fixture->application.session_cancel_fd = -1; char key[CBM_DAEMON_KEY_SIZE]; char build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE]; int log_written = snprintf(fixture->conflict_log, sizeof(fixture->conflict_log), "%s/conflicts.ndjson", parent); if (log_written <= 0 || log_written >= (int)sizeof(fixture->conflict_log) || !cbm_daemon_rendezvous_key(key) || !cbm_daemon_runtime_process_build_fingerprint((uint64_t)getpid(), build)) { return false; } fixture->endpoint = cbm_daemon_ipc_endpoint_new(key, parent); fixture->manager = fixture->endpoint ? cbm_version_cohort_manager_new(fixture->endpoint) : NULL; cbm_daemon_build_identity_t identity = { .semantic_version = "2.4.0", .build_fingerprint = build, .cache_fingerprint = FRONTEND_TEST_CACHE, .protocol_abi = 3, .store_abi = 11, .feature_abi = 7, }; cbm_daemon_runtime_application_callbacks_t callbacks = { .context = &fixture->application, .session_open = frontend_eof_application_session_open, .request = frontend_eof_application_request, .request_cancel = frontend_eof_application_request_cancel, .session_cancel = frontend_eof_application_session_cancel, .session_close = frontend_eof_application_session_close, }; cbm_daemon_runtime_service_config_t config = { .endpoint = fixture->endpoint, .identity = identity, .conflict_log_path = fixture->conflict_log, .conflict_log_cap_bytes = 64U * 1024U, .max_clients = 4, .lease_timeout_ms = 5000, .request_timeout_ms = FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS, .shutdown_timeout_ms = FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS, .application = callbacks, }; fixture->service = fixture->manager ? cbm_daemon_runtime_service_start(&config) : NULL; cbm_daemon_runtime_connect_result_t connect_result = {0}; fixture->client = fixture->service ? cbm_daemon_runtime_client_connect(fixture->endpoint, &identity, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS, &connect_result) : NULL; return fixture->client && connect_result.status == CBM_DAEMON_RUNTIME_CONNECT_ACCEPTED; } static bool frontend_eof_fixture_finish(frontend_eof_fixture_t *fixture) { bool ok = true; if (fixture->client) { ok = cbm_daemon_runtime_client_close(fixture->client, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS) && ok; fixture->client = NULL; } if (fixture->service) { if (cbm_daemon_runtime_service_state(fixture->service) != CBM_DAEMON_RUNTIME_SERVICE_EXITED) { ok = cbm_daemon_runtime_service_stop(fixture->service, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS) && ok; } ok = cbm_daemon_runtime_service_free(fixture->service) && ok; fixture->service = NULL; } if (fixture->manager) { ok = cbm_version_cohort_manager_free(&fixture->manager) == CBM_PRIVATE_FILE_LOCK_OK && ok; } cbm_daemon_ipc_endpoint_free(fixture->endpoint); fixture->endpoint = NULL; return ok; } static int frontend_eof_child_run(const char *parent, bool overflow) { frontend_eof_fixture_t fixture; if (!frontend_eof_fixture_start(&fixture, parent)) { (void)frontend_eof_fixture_finish(&fixture); return 70; } int input_pipe[2] = {-1, -1}; if (pipe(input_pipe) != 0) { (void)frontend_eof_fixture_finish(&fixture); return 71; } FILE *input = fdopen(input_pipe[0], "rb"); FILE *output = tmpfile(); frontend_eof_writer_t writer = { .fd = input_pipe[1], .overflow = overflow, .application = &fixture.application, }; atomic_init(&writer.finished, false); atomic_init(&writer.succeeded, false); cbm_thread_t writer_thread; bool writer_started = input && output && cbm_thread_create(&writer_thread, 0, frontend_eof_writer, &writer) == 0; if (!writer_started) { if (input) { (void)fclose(input); } else { (void)close(input_pipe[0]); } (void)close(input_pipe[1]); if (output) { (void)fclose(output); } (void)frontend_eof_fixture_finish(&fixture); return 72; } cbm_daemon_runtime_client_t *frontend_client = fixture.client; fixture.client = NULL; /* cbm_daemon_frontend_mcp_run consumes it. */ int result = cbm_daemon_frontend_mcp_run(frontend_client, fixture.manager, input, output); bool joined = cbm_thread_join(&writer_thread) == 0; bool writer_ok = atomic_load_explicit(&writer.finished, memory_order_acquire) && atomic_load_explicit(&writer.succeeded, memory_order_acquire); bool request_started = atomic_load_explicit(&fixture.application.first_request_started, memory_order_acquire); bool session_cancelled = atomic_load_explicit(&fixture.application.session_cancels, memory_order_acquire) > 0; bool input_closed = fclose(input) == 0; bool output_closed = fclose(output) == 0; bool streams_closed = input_closed && output_closed; bool fixture_closed = frontend_eof_fixture_finish(&fixture); bool result_matches_contract = overflow ? result < 0 : result == 0; return result_matches_contract && joined && writer_ok && request_started && session_cancelled && streams_closed && fixture_closed ? 0 : 73; } static bool frontend_eof_run_isolated(const char *tag, bool overflow) { char parent[FRONTEND_TEST_PATH_CAP]; int written = snprintf(parent, sizeof(parent), "%s/cbm-frontend-eof-%s-XXXXXX", cbm_tmpdir(), tag); if (written <= 0 || written >= (int)sizeof(parent) || !cbm_mkdtemp(parent)) { return false; } pid_t child = fork(); if (child == 0) { (void)signal(SIGALRM, SIG_DFL); /* This alarm bounds a broken isolated harness, not product behavior. * Valid startup, drain/watchdog, close, and fixture teardown can exceed * ten seconds under sanitizers while remaining within their own bounds. */ (void)alarm(FRONTEND_EOF_TEST_CATASTROPHIC_TIMEOUT_S); _exit(frontend_eof_child_run(parent, overflow)); } int status = 0; pid_t waited; do { waited = child > 0 ? waitpid(child, &status, 0) : -1; } while (waited < 0 && errno == EINTR); bool cleaned = th_rmtree(parent) == 0; bool child_ok = child > 0 && waited == child && WIFEXITED(status) && WEXITSTATUS(status) == 0; if (!child_ok) { if (child <= 0) { (void)fprintf(stderr, "frontend EOF fixture %s: fork failed\n", tag); } else if (waited != child) { (void)fprintf(stderr, "frontend EOF fixture %s: waitpid failed errno=%d\n", tag, errno); } else if (WIFSIGNALED(status)) { (void)fprintf(stderr, "frontend EOF fixture %s: child signal=%d\n", tag, WTERMSIG(status)); } else if (WIFEXITED(status)) { (void)fprintf(stderr, "frontend EOF fixture %s: child exit=%d\n", tag, WEXITSTATUS(status)); } else { (void)fprintf(stderr, "frontend EOF fixture %s: unexpected child status\n", tag); } } if (!cleaned) { (void)fprintf(stderr, "frontend EOF fixture %s: cleanup failed\n", tag); } return child_ok && cleaned; } static void frontend_test_release_lease(cbm_version_cohort_lease_t **lease) { while (lease && *lease && cbm_version_cohort_lease_release(lease) != CBM_PRIVATE_FILE_LOCK_OK) { cbm_usleep(1000); } } static bool frontend_test_release_lease_until(cbm_version_cohort_lease_t **lease, uint64_t deadline_ms) { while (lease && *lease) { if (cbm_version_cohort_lease_release(lease) == CBM_PRIVATE_FILE_LOCK_OK) { continue; } if (cbm_now_ms() >= deadline_ms) { return false; } cbm_usleep(1000); } return !lease || !*lease; } static bool frontend_test_read_byte(int fd, char *observed, uint64_t deadline_ms) { if (!observed) { return false; } *observed = '\0'; int flags = fcntl(fd, F_GETFL, 0); if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) { return false; } for (;;) { char value = '\0'; ssize_t count = read(fd, &value, 1); if (count == 1) { *observed = value; return true; } if (count == 0 || (count < 0 && errno != EAGAIN && errno != EWOULDBLOCK) || cbm_now_ms() >= deadline_ms) { return false; } cbm_usleep(1000); } } static bool frontend_test_wait_byte(int fd, char expected, uint64_t deadline_ms) { char observed = '\0'; return frontend_test_read_byte(fd, &observed, deadline_ms) && observed == expected; } typedef struct { int fd; atomic_bool finished; atomic_bool succeeded; } frontend_backpressure_writer_t; static cbm_version_cohort_quiesce_result_t frontend_test_quiesce_requested(void *context); static bool frontend_backpressure_identity(cbm_daemon_build_identity_t *identity, const char build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE]) { if (!identity || !build || strlen(build) != CBM_DAEMON_BUILD_FINGERPRINT_SIZE - 1) { return false; } *identity = (cbm_daemon_build_identity_t){ .semantic_version = "2.4.0", .build_fingerprint = build, .cache_fingerprint = FRONTEND_TEST_CACHE, .protocol_abi = 3, .store_abi = 11, .feature_abi = 7, }; return true; } static void *frontend_backpressure_writer(void *opaque) { frontend_backpressure_writer_t *writer = opaque; static const char prefix[] = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"test/large\"," "\"params\":{\"blob\":\""; static const char suffix[] = "\"}}\n"; char *message = malloc(FRONTEND_BACKPRESSURE_MESSAGE_BYTES); size_t prefix_length = sizeof(prefix) - 1; size_t suffix_length = sizeof(suffix) - 1; bool valid = message && prefix_length + suffix_length < FRONTEND_BACKPRESSURE_MESSAGE_BYTES; if (valid) { memcpy(message, prefix, prefix_length); memset(message + prefix_length, 'x', FRONTEND_BACKPRESSURE_MESSAGE_BYTES - prefix_length - suffix_length); memcpy(message + FRONTEND_BACKPRESSURE_MESSAGE_BYTES - suffix_length, suffix, suffix_length); valid = frontend_eof_write_all(writer->fd, message, FRONTEND_BACKPRESSURE_MESSAGE_BYTES); } free(message); valid = close(writer->fd) == 0 && valid; writer->fd = -1; atomic_store_explicit(&writer->succeeded, valid, memory_order_release); atomic_store_explicit(&writer->finished, true, memory_order_release); return NULL; } static int frontend_backpressure_daemon_run(const char *parent, int ready_fd, int cancel_fd, int control_fd, const char build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE]) { (void)alarm(FRONTEND_BACKPRESSURE_DAEMON_TIMEOUT_S); char key[CBM_DAEMON_KEY_SIZE]; char conflict_log[FRONTEND_TEST_PATH_CAP]; cbm_daemon_build_identity_t identity = {0}; int log_written = snprintf(conflict_log, sizeof(conflict_log), "%s/conflicts.ndjson", parent); cbm_daemon_ipc_endpoint_t *endpoint = log_written > 0 && log_written < (int)sizeof(conflict_log) && cbm_daemon_rendezvous_key(key) && frontend_backpressure_identity(&identity, build) ? cbm_daemon_ipc_endpoint_new(key, parent) : NULL; frontend_eof_application_context_t application; memset(&application, 0, sizeof(application)); atomic_init(&application.requests, 0); atomic_init(&application.session_cancels, 0); atomic_init(&application.sessions_opened, 0); atomic_init(&application.first_session_cancels, 0); atomic_init(&application.second_session_cancels, 0); atomic_init(&application.block_first_request, false); atomic_init(&application.first_request_started, false); application.request_observed_fd = ready_fd; application.session_cancel_fd = cancel_fd; cbm_daemon_runtime_application_callbacks_t callbacks = { .context = &application, .session_open = frontend_eof_application_session_open, .request = frontend_eof_application_request, .request_cancel = frontend_eof_application_request_cancel, .session_cancel = frontend_eof_application_session_cancel, .session_close = frontend_eof_application_session_close, }; cbm_daemon_runtime_service_config_t config = { .endpoint = endpoint, .identity = identity, .conflict_log_path = conflict_log, .conflict_log_cap_bytes = 64U * 1024U, .max_clients = 4, .lease_timeout_ms = 30000, .request_timeout_ms = FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS, .shutdown_timeout_ms = FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS, .application = callbacks, }; cbm_daemon_runtime_service_t *service = endpoint ? cbm_daemon_runtime_service_start(&config) : NULL; const char ready = 'R'; bool announced = service && write(ready_fd, &ready, 1) == 1; uint64_t cancellation_deadline = cbm_now_ms() + FRONTEND_BACKPRESSURE_RUNTIME_TIMEOUT_MS; while (announced && atomic_load_explicit(&application.session_cancels, memory_order_acquire) == 0 && cbm_now_ms() < cancellation_deadline) { cbm_usleep(1000); } int target_cancellations = atomic_load_explicit(&application.session_cancels, memory_order_acquire); int sentinel_cancellations_before_close = atomic_load_explicit(&application.first_session_cancels, memory_order_acquire); int target_session_cancellations = atomic_load_explicit(&application.second_session_cancels, memory_order_acquire); bool target_cancelled_exactly_once = announced && target_cancellations == 1 && sentinel_cancellations_before_close == 0 && target_session_cancellations == 1; const char checkpoint = target_cancelled_exactly_once ? 'T' : 'E'; bool checkpoint_written = write(ready_fd, &checkpoint, 1) == 1; char control = '\0'; bool shutdown_released = checkpoint_written && frontend_test_read_byte(control_fd, &control, cbm_now_ms() + FRONTEND_BACKPRESSURE_RUNTIME_TIMEOUT_MS) && control == 'S'; int final_cancellations = atomic_load_explicit(&application.session_cancels, memory_order_acquire); int sentinel_session_cancellations = atomic_load_explicit(&application.first_session_cancels, memory_order_acquire); int final_target_session_cancellations = atomic_load_explicit(&application.second_session_cancels, memory_order_acquire); bool sentinel_closed_exactly_once = final_cancellations == 2 && sentinel_session_cancellations == 1 && final_target_session_cancellations == 1; bool stopped = service && cbm_daemon_runtime_service_stop(service, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS); bool exited = stopped && cbm_daemon_runtime_service_wait_exited(service, 0); bool freed = exited && cbm_daemon_runtime_service_free(service); cbm_daemon_ipc_endpoint_free(endpoint); const char done = target_cancelled_exactly_once && shutdown_released && sentinel_closed_exactly_once && stopped && exited && freed ? 'D' : 'E'; bool finished = write(ready_fd, &done, 1) == 1; (void)close(ready_fd); (void)close(cancel_fd); (void)close(control_fd); return announced && target_cancelled_exactly_once && checkpoint_written && shutdown_released && sentinel_closed_exactly_once && stopped && exited && freed && finished ? 0 : 80; } static int frontend_backpressure_frontend_run(const char *parent, int input_fd, int input_write_fd, int output_fd, const char build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE], bool keep_input_open) { (void)alarm(FRONTEND_BACKPRESSURE_FRONTEND_TIMEOUT_S); char key[CBM_DAEMON_KEY_SIZE]; cbm_daemon_build_identity_t identity = {0}; cbm_daemon_ipc_endpoint_t *endpoint = cbm_daemon_rendezvous_key(key) && frontend_backpressure_identity(&identity, build) ? cbm_daemon_ipc_endpoint_new(key, parent) : NULL; cbm_version_cohort_manager_t *manager = endpoint ? cbm_version_cohort_manager_new(endpoint) : NULL; cbm_version_cohort_lease_t *participant = NULL; cbm_daemon_conflict_t conflict; cbm_version_cohort_status_t admitted = manager ? cbm_version_cohort_acquire(manager, &identity, cbm_now_ms() + 2000U, &participant, &conflict) : CBM_VERSION_COHORT_IO; cbm_daemon_runtime_connect_result_t connect_result = {0}; cbm_daemon_runtime_client_t *client = admitted == CBM_VERSION_COHORT_OK ? cbm_daemon_runtime_client_connect( endpoint, &identity, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS, &connect_result) : NULL; FILE *input = client ? fdopen(input_fd, "rb") : NULL; FILE *output = input ? fdopen(output_fd, "wb") : NULL; int input_hold_fd = keep_input_open && output ? dup(input_write_fd) : -1; frontend_backpressure_writer_t writer = { .fd = input_write_fd, }; atomic_init(&writer.finished, false); atomic_init(&writer.succeeded, false); cbm_thread_t writer_thread; bool writer_started = output && (!keep_input_open || input_hold_fd >= 0) && cbm_thread_create(&writer_thread, 0, frontend_backpressure_writer, &writer) == 0; if (!writer_started) { if (client) { (void)cbm_daemon_runtime_client_close(client, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS); } if (input) { (void)fclose(input); } else { (void)close(input_fd); } if (output) { (void)fclose(output); } else { (void)close(output_fd); } (void)close(input_write_fd); if (input_hold_fd >= 0) { (void)close(input_hold_fd); } frontend_test_release_lease(&participant); if (manager) { (void)cbm_version_cohort_manager_free(&manager); } cbm_daemon_ipc_endpoint_free(endpoint); return 81; } int result = cbm_daemon_frontend_mcp_run(client, manager, input, output); bool joined = cbm_thread_join(&writer_thread) == 0; bool writer_ok = atomic_load_explicit(&writer.finished, memory_order_acquire) && atomic_load_explicit(&writer.succeeded, memory_order_acquire); bool input_closed = fclose(input) == 0; bool output_closed = fclose(output) == 0; bool input_hold_closed = input_hold_fd < 0 || close(input_hold_fd) == 0; frontend_test_release_lease(&participant); bool manager_closed = cbm_version_cohort_manager_free(&manager) == CBM_PRIVATE_FILE_LOCK_OK; cbm_daemon_ipc_endpoint_free(endpoint); return result < 0 && joined && writer_ok && input_closed && output_closed && input_hold_closed && manager_closed ? 47 : 82; } static bool frontend_backpressure_run_isolated(bool maintenance) { char parent[FRONTEND_TEST_PATH_CAP]; char build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE] = {0}; int path_written = snprintf(parent, sizeof(parent), maintenance ? "%s/cbm-frontend-maintenance-backpressure-XXXXXX" : "%s/cbm-frontend-backpressure-XXXXXX", cbm_tmpdir()); int ready_pipe[2] = {-1, -1}; int cancel_pipe[2] = {-1, -1}; int control_pipe[2] = {-1, -1}; bool directory_ready = path_written > 0 && path_written < (int)sizeof(parent) && cbm_mkdtemp(parent); bool identity_ready = directory_ready && cbm_daemon_runtime_process_build_fingerprint((uint64_t)getpid(), build); bool prepared = identity_ready && pipe(ready_pipe) == 0 && pipe(cancel_pipe) == 0 && pipe(control_pipe) == 0; if (!prepared) { for (size_t index = 0; index < 2; index++) { if (ready_pipe[index] >= 0) { (void)close(ready_pipe[index]); } if (cancel_pipe[index] >= 0) { (void)close(cancel_pipe[index]); } if (control_pipe[index] >= 0) { (void)close(control_pipe[index]); } } if (directory_ready) { (void)th_rmtree(parent); } return false; } pid_t daemon = prepared ? fork() : -1; if (daemon == 0) { (void)signal(SIGALRM, SIG_DFL); (void)close(ready_pipe[0]); (void)close(cancel_pipe[0]); (void)close(control_pipe[1]); _exit(frontend_backpressure_daemon_run(parent, ready_pipe[1], cancel_pipe[1], control_pipe[0], build)); } if (prepared) { (void)close(ready_pipe[1]); (void)close(cancel_pipe[1]); (void)close(control_pipe[0]); } char announced_marker = '\0'; /* The daemon child runs cbm_daemon_runtime_service_start before it can * announce, and that includes the cohort-claim path whose own internal * budget is HOST_DAEMON_CLAIM_TIMEOUT_MS (12s). An announce deadline * below that budget SIGKILLs a slow-but-legitimate startup — on * oversubscribed CI runners this fired every round (announced=0, * daemon_signal=9) while fast local machines never saw it. Keep the * deadline comfortably above the daemon's own worst-case budget. */ bool announced_read = daemon > 0 && frontend_test_read_byte(ready_pipe[0], &announced_marker, cbm_now_ms() + 30000U); bool announced = announced_read && announced_marker == 'R'; /* This raw runtime client intentionally owns no cohort lease. It is a * sentinel for session isolation: maintenance must remove the target * frontend without touching this independently authenticated session. Its * completed admission precedes the target fork, so the daemon fixture can * attribute session-cancel callbacks to sentinel index 0 and target index * 1 rather than relying on a global callback count. */ char sentinel_key[CBM_DAEMON_KEY_SIZE]; cbm_daemon_build_identity_t sentinel_identity = {0}; cbm_daemon_ipc_endpoint_t *sentinel_endpoint = announced && cbm_daemon_rendezvous_key(sentinel_key) && frontend_backpressure_identity(&sentinel_identity, build) ? cbm_daemon_ipc_endpoint_new(sentinel_key, parent) : NULL; cbm_daemon_runtime_connect_result_t sentinel_connect = {0}; cbm_daemon_runtime_client_t *sentinel = sentinel_endpoint ? cbm_daemon_runtime_client_connect(sentinel_endpoint, &sentinel_identity, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS, &sentinel_connect) : NULL; bool sentinel_ready = sentinel && sentinel_connect.status == CBM_DAEMON_RUNTIME_CONNECT_ACCEPTED; if (!sentinel_ready && daemon > 0) { (void)kill(daemon, SIGKILL); } int input_pipe[2] = {-1, -1}; int output_pipe[2] = {-1, -1}; bool input_ready = sentinel_ready && pipe(input_pipe) == 0; bool frontend_pipes = input_ready && pipe(output_pipe) == 0; if (input_ready && !frontend_pipes) { (void)close(input_pipe[0]); (void)close(input_pipe[1]); input_pipe[0] = -1; input_pipe[1] = -1; } pid_t frontend = frontend_pipes ? fork() : -1; if (frontend == 0) { (void)signal(SIGALRM, SIG_DFL); (void)close(output_pipe[0]); (void)close(ready_pipe[0]); (void)close(cancel_pipe[0]); (void)close(control_pipe[1]); _exit(frontend_backpressure_frontend_run(parent, input_pipe[0], input_pipe[1], output_pipe[1], build, maintenance)); } if (frontend_pipes) { (void)close(input_pipe[0]); (void)close(input_pipe[1]); (void)close(output_pipe[1]); } char request_marker = '\0'; bool request_read = frontend > 0 && frontend_test_read_byte(ready_pipe[0], &request_marker, cbm_now_ms() + 4000U); bool request_observed = request_read && request_marker == 'Q'; char output_marker = '\0'; bool output_observed = !maintenance || (request_observed && frontend_test_read_byte(output_pipe[0], &output_marker, cbm_now_ms() + 4000U)); cbm_daemon_ipc_endpoint_t *mutation_endpoint = NULL; cbm_version_cohort_manager_t *mutation_manager = NULL; cbm_version_cohort_lease_t *mutation = NULL; cbm_version_cohort_quiesce_result_t quiesce = CBM_VERSION_COHORT_QUIESCE_NOT_NEEDED; cbm_version_cohort_status_t mutation_status = CBM_VERSION_COHORT_OK; if (maintenance) { char mutation_key[CBM_DAEMON_KEY_SIZE]; mutation_endpoint = output_observed && cbm_daemon_rendezvous_key(mutation_key) ? cbm_daemon_ipc_endpoint_new(mutation_key, parent) : NULL; mutation_manager = mutation_endpoint ? cbm_version_cohort_manager_new(mutation_endpoint) : NULL; mutation_status = mutation_manager ? cbm_version_cohort_reserve_for_mutation( mutation_manager, cbm_now_ms() + 8000U, frontend_test_quiesce_requested, NULL, &quiesce, &mutation) : CBM_VERSION_COHORT_IO; if (mutation_status != CBM_VERSION_COHORT_OK && frontend > 0) { (void)kill(frontend, SIGKILL); } } int frontend_status = 0; pid_t frontend_waited; if (frontend > 0) { do { frontend_waited = waitpid(frontend, &frontend_status, 0); } while (frontend_waited < 0 && errno == EINTR); } else { frontend_waited = -1; } char cancel_marker = '\0'; bool cancel_read = request_observed && frontend_test_read_byte(cancel_pipe[0], &cancel_marker, cbm_now_ms() + 4000U); bool cancelled = cancel_read && cancel_marker == 'C'; char checkpoint_marker = '\0'; bool checkpoint_read = frontend_test_read_byte(ready_pipe[0], &checkpoint_marker, cbm_now_ms() + 4000U); bool target_cancelled_exactly_once = checkpoint_read && checkpoint_marker == 'T'; bool sentinel_usable = cancelled && target_cancelled_exactly_once && sentinel_ready && cbm_daemon_runtime_client_heartbeat(sentinel, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS); bool sentinel_closed = sentinel && cbm_daemon_runtime_client_close(sentinel, FRONTEND_EOF_TEST_REQUEST_TIMEOUT_MS); sentinel = NULL; cbm_daemon_ipc_endpoint_free(sentinel_endpoint); sentinel_endpoint = NULL; const char shutdown = 'S'; bool shutdown_sent = checkpoint_read && write(control_pipe[1], &shutdown, 1) == 1; if (control_pipe[1] >= 0) { (void)close(control_pipe[1]); control_pipe[1] = -1; } char done_marker = '\0'; bool done_read = shutdown_sent && frontend_test_read_byte(ready_pipe[0], &done_marker, cbm_now_ms() + 4000U); bool daemon_done = done_read && done_marker == 'D'; if (!daemon_done && daemon > 0) { (void)kill(daemon, SIGKILL); } int daemon_status = 0; pid_t daemon_waited; do { daemon_waited = daemon > 0 ? waitpid(daemon, &daemon_status, 0) : -1; } while (daemon_waited < 0 && errno == EINTR); uint64_t cleanup_now = cbm_now_ms(); uint64_t cleanup_deadline = cleanup_now > UINT64_MAX - FRONTEND_BACKPRESSURE_CLEANUP_TIMEOUT_MS ? UINT64_MAX : cleanup_now + FRONTEND_BACKPRESSURE_CLEANUP_TIMEOUT_MS; bool mutation_released = frontend_test_release_lease_until(&mutation, cleanup_deadline); while (mutation_released && mutation_manager && cbm_now_ms() < cleanup_deadline && cbm_version_cohort_manager_free(&mutation_manager) != CBM_PRIVATE_FILE_LOCK_OK) { cbm_usleep(1000); } bool mutation_manager_closed = mutation_manager == NULL; cbm_daemon_ipc_endpoint_free(mutation_endpoint); if (output_pipe[0] >= 0) { (void)close(output_pipe[0]); } if (ready_pipe[0] >= 0) { (void)close(ready_pipe[0]); } if (cancel_pipe[0] >= 0) { (void)close(cancel_pipe[0]); } if (control_pipe[1] >= 0) { (void)close(control_pipe[1]); } bool cleaned = prepared && th_rmtree(parent) == 0; bool frontend_exited_boundedly = frontend_waited == frontend && WIFEXITED(frontend_status) && (maintenance ? WEXITSTATUS(frontend_status) == EXIT_SUCCESS : (WEXITSTATUS(frontend_status) == EXIT_FAILURE || WEXITSTATUS(frontend_status) == 47)); bool daemon_clean = daemon_waited == daemon && WIFEXITED(daemon_status) && WEXITSTATUS(daemon_status) == 0; bool maintenance_completed = !maintenance || (output_observed && mutation_status == CBM_VERSION_COHORT_OK && quiesce == CBM_VERSION_COHORT_QUIESCE_REQUESTED); bool passed = prepared && announced && sentinel_ready && frontend_pipes && frontend > 0 && request_observed && maintenance_completed && frontend_exited_boundedly && cancelled && target_cancelled_exactly_once && sentinel_usable && sentinel_closed && shutdown_sent && daemon_done && daemon_clean && mutation_released && mutation_manager_closed && cleaned; if (!passed) { bool frontend_reaped = frontend > 0 && frontend_waited == frontend; bool daemon_reaped = daemon > 0 && daemon_waited == daemon; int frontend_exit = frontend_reaped && WIFEXITED(frontend_status) ? WEXITSTATUS(frontend_status) : -1; int frontend_signal = frontend_reaped && WIFSIGNALED(frontend_status) ? WTERMSIG(frontend_status) : 0; int daemon_exit = daemon_reaped && WIFEXITED(daemon_status) ? WEXITSTATUS(daemon_status) : -1; int daemon_signal = daemon_reaped && WIFSIGNALED(daemon_status) ? WTERMSIG(daemon_status) : 0; fprintf(stderr, "frontend backpressure fixture failed: maintenance=%d prepared=%d daemon_pid=%ld " "announced=%d announced_marker=0x%02x sentinel_ready=%d frontend_pipes=%d " "frontend_pid=%ld frontend_reaped=%d frontend_status=0x%x " "frontend_exit=%d frontend_signal=%d request_read=%d " "request_marker=0x%02x output_observed=%d output_marker=0x%02x " "mutation_status=%d quiesce=%d cancel_read=%d cancel_marker=0x%02x " "checkpoint_read=%d checkpoint_marker=0x%02x sentinel_usable=%d " "sentinel_closed=%d shutdown_sent=%d " "done_read=%d done_marker=0x%02x daemon_reaped=%d " "daemon_status=0x%x daemon_exit=%d daemon_signal=%d " "mutation_released=%d mutation_manager_closed=%d cleaned=%d\n", maintenance, prepared, (long)daemon, announced, (unsigned int)(unsigned char)announced_marker, sentinel_ready, frontend_pipes, (long)frontend, frontend_reaped, frontend_status, frontend_exit, frontend_signal, request_read, (unsigned int)(unsigned char)request_marker, output_observed, (unsigned int)(unsigned char)output_marker, mutation_status, quiesce, cancel_read, (unsigned int)(unsigned char)cancel_marker, checkpoint_read, (unsigned int)(unsigned char)checkpoint_marker, sentinel_usable, sentinel_closed, shutdown_sent, done_read, (unsigned int)(unsigned char)done_marker, daemon_reaped, daemon_status, daemon_exit, daemon_signal, mutation_released, mutation_manager_closed, cleaned); } return passed; } static cbm_version_cohort_quiesce_result_t frontend_test_quiesce_requested(void *context) { (void)context; return CBM_VERSION_COHORT_QUIESCE_REQUESTED; } static bool frontend_test_cancel_active(void *context) { int fd = *(int *)context; const char marker = 'C'; return write(fd, &marker, 1) == 1; } static bool frontend_test_cancel_and_mark(void *context) { atomic_bool *cancelled = context; atomic_store_explicit(cancelled, true, memory_order_release); return true; } #endif TEST(daemon_frontend_recognizes_exact_cancellation_notification) { ASSERT_TRUE(cbm_daemon_frontend_is_cancellation_notification( "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/cancelled\"," "\"params\":{\"requestId\":7}}")); ASSERT_TRUE(cbm_daemon_frontend_is_cancellation_notification( "{ \"params\": {}, \"method\": \"notifications/cancelled\", " "\"jsonrpc\": \"2.0\" }")); PASS(); } /* RED on the whole-session cancellation shortcut: merely recognizing the * notification method is not authority to close a session. The requestId * must match the exact numeric/string identity currently being executed. */ TEST(daemon_frontend_correlates_cancellation_to_exact_request) { ASSERT_TRUE(cbm_daemon_frontend_cancellation_matches_request( "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/cancelled\"," "\"params\":{\"requestId\":7}}", 7, NULL)); ASSERT_FALSE(cbm_daemon_frontend_cancellation_matches_request( "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/cancelled\"," "\"params\":{\"requestId\":8}}", 7, NULL)); ASSERT_TRUE(cbm_daemon_frontend_cancellation_matches_request( "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/cancelled\"," "\"params\":{\"requestId\":\"request-7\"}}", -1, "request-7")); ASSERT_FALSE(cbm_daemon_frontend_cancellation_matches_request( "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/cancelled\"," "\"params\":{\"requestId\":7}}", -1, "7")); ASSERT_FALSE(cbm_daemon_frontend_cancellation_matches_request( "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/cancelled\"," "\"params\":{}}", 7, NULL)); ASSERT_FALSE( cbm_daemon_frontend_cancellation_matches_request("{\"jsonrpc\":\"2.0\",\"id\":9," "\"method\":\"notifications/cancelled\"," "\"params\":{\"requestId\":7}}", 7, NULL)); PASS(); } TEST(daemon_frontend_ignores_cancellation_text_in_string_content) { ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification( "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\"," "\"params\":{\"name\":\"search_graph\",\"arguments\":{\"query\":" "\"notifications/cancelled\"}}}")); ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification( "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\"," "\"params\":{\"method\":\"notifications/cancelled\"}}")); PASS(); } TEST(daemon_frontend_rejects_non_notification_cancellation_shapes) { ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification( "{\"jsonrpc\":\"2.0\",\"id\":3," "\"method\":\"notifications/cancelled\"}")); ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification( "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/cancelled-extra\"}")); ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification( "{\"jsonrpc\":\"2.0\",\"method\":\"prefix/notifications/cancelled\"}")); ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification( "{\"jsonrpc\":\"2.0\",\"params\":{\"method\":" "\"notifications/cancelled\"}}")); ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification("not-json")); ASSERT_FALSE(cbm_daemon_frontend_is_cancellation_notification(NULL)); PASS(); } #ifndef _WIN32 /* RED: an MCP frontend's main thread can remain blocked forever in stdio while * install/update/uninstall owns maintenance intent. The already-present * frontend worker must observe that native intent and terminate this stateless * child, releasing its cohort lease and kernel IPC ownership. A deliberately * invalid client pointer is safe only if the maintenance path exits before * ordinary EOF/session-close handling, which also pins that exact path. */ TEST(daemon_frontend_maintenance_exits_while_stdio_reader_is_blocked) { frontend_maintenance_fixture_t fixture; ASSERT_TRUE(frontend_maintenance_fixture_start(&fixture, "blocked-stdio")); int input_pipe[2] = {-1, -1}; int ready_pipe[2] = {-1, -1}; bool pipes_ready = pipe(input_pipe) == 0 && pipe(ready_pipe) == 0; pid_t child = pipes_ready ? fork() : -1; if (child == 0) { (void)close(input_pipe[1]); (void)close(ready_pipe[0]); cbm_daemon_ipc_endpoint_t *endpoint = cbm_daemon_ipc_endpoint_new("0123456789abcdef", fixture.parent); cbm_version_cohort_manager_t *manager = endpoint ? cbm_version_cohort_manager_new(endpoint) : NULL; cbm_version_cohort_lease_t *participant = NULL; cbm_daemon_conflict_t conflict; cbm_daemon_build_identity_t identity = frontend_test_identity(); cbm_version_cohort_status_t admitted = manager ? cbm_version_cohort_acquire(manager, &identity, cbm_now_ms() + 2000U, &participant, &conflict) : CBM_VERSION_COHORT_IO; FILE *input = admitted == CBM_VERSION_COHORT_OK ? fdopen(input_pipe[0], "rb") : NULL; FILE *output = input ? tmpfile() : NULL; const char ready = 'R'; bool announced = output && write(ready_pipe[1], &ready, 1) == 1; (void)close(ready_pipe[1]); if (!announced) { _exit(70); } int result = cbm_daemon_frontend_mcp_run((cbm_daemon_runtime_client_t *)(uintptr_t)1, manager, input, output); (void)result; _exit(71); } if (pipes_ready) { (void)close(input_pipe[0]); (void)close(ready_pipe[1]); } bool announced = child > 0 && frontend_test_wait_byte(ready_pipe[0], 'R', cbm_now_ms() + 2000U); cbm_version_cohort_lease_t *mutation = NULL; cbm_version_cohort_quiesce_result_t quiesce = CBM_VERSION_COHORT_QUIESCE_NOT_NEEDED; cbm_version_cohort_status_t status = announced ? cbm_version_cohort_reserve_for_mutation(fixture.manager, cbm_now_ms() + 3000U, frontend_test_quiesce_requested, NULL, &quiesce, &mutation) : CBM_VERSION_COHORT_IO; if (status != CBM_VERSION_COHORT_OK && child > 0) { (void)kill(child, SIGKILL); } int child_status = 0; bool waited = child > 0 && waitpid(child, &child_status, 0) == child; if (pipes_ready) { (void)close(input_pipe[1]); (void)close(ready_pipe[0]); } frontend_test_release_lease(&mutation); frontend_maintenance_fixture_finish(&fixture); ASSERT_TRUE(pipes_ready); ASSERT_TRUE(child > 0); ASSERT_TRUE(announced); ASSERT_EQ(status, CBM_VERSION_COHORT_OK); ASSERT_EQ(quiesce, CBM_VERSION_COHORT_QUIESCE_REQUESTED); ASSERT_TRUE(waited); ASSERT_TRUE(WIFEXITED(child_status)); ASSERT_EQ(WEXITSTATUS(child_status), 0); PASS(); } /* RED: local CLI/worker work has no standing daemon session to interrupt. Its * command-lifetime monitor must request cooperative cancellation once, allow a * bounded grace, then hard-exit the isolated process so SQLite/native locks * roll back and the mutation barrier can prove that every lifetime participant * is gone. */ TEST(daemon_local_participant_monitor_cancels_then_bounds_active_operation) { frontend_maintenance_fixture_t fixture; ASSERT_TRUE(frontend_maintenance_fixture_start(&fixture, "local-monitor")); int ready_pipe[2] = {-1, -1}; int cancel_pipe[2] = {-1, -1}; bool pipes_ready = pipe(ready_pipe) == 0 && pipe(cancel_pipe) == 0; pid_t child = pipes_ready ? fork() : -1; if (child == 0) { (void)close(ready_pipe[0]); (void)close(cancel_pipe[0]); cbm_daemon_ipc_endpoint_t *endpoint = cbm_daemon_ipc_endpoint_new("0123456789abcdef", fixture.parent); cbm_version_cohort_manager_t *manager = endpoint ? cbm_version_cohort_manager_new(endpoint) : NULL; cbm_version_cohort_lease_t *participant = NULL; cbm_daemon_conflict_t conflict; cbm_daemon_build_identity_t identity = frontend_test_identity(); cbm_version_cohort_status_t admitted = manager ? cbm_version_cohort_acquire(manager, &identity, cbm_now_ms() + 2000U, &participant, &conflict) : CBM_VERSION_COHORT_IO; cbm_daemon_maintenance_monitor_t *monitor = admitted == CBM_VERSION_COHORT_OK ? cbm_daemon_maintenance_monitor_start(manager, frontend_test_cancel_active, &cancel_pipe[1], 37, "test-local-operation") : NULL; const char ready = 'R'; bool announced = monitor && write(ready_pipe[1], &ready, 1) == 1; (void)close(ready_pipe[1]); if (!announced) { _exit(72); } for (;;) { cbm_usleep(100000); } } if (pipes_ready) { (void)close(ready_pipe[1]); (void)close(cancel_pipe[1]); } bool announced = child > 0 && frontend_test_wait_byte(ready_pipe[0], 'R', cbm_now_ms() + 2000U); cbm_version_cohort_lease_t *mutation = NULL; cbm_version_cohort_quiesce_result_t quiesce = CBM_VERSION_COHORT_QUIESCE_NOT_NEEDED; cbm_version_cohort_status_t status = announced ? cbm_version_cohort_reserve_for_mutation(fixture.manager, cbm_now_ms() + 5000U, frontend_test_quiesce_requested, NULL, &quiesce, &mutation) : CBM_VERSION_COHORT_IO; bool cancelled = status == CBM_VERSION_COHORT_OK && frontend_test_wait_byte(cancel_pipe[0], 'C', cbm_now_ms() + 1000U); if (status != CBM_VERSION_COHORT_OK && child > 0) { (void)kill(child, SIGKILL); } int child_status = 0; bool waited = child > 0 && waitpid(child, &child_status, 0) == child; if (pipes_ready) { (void)close(ready_pipe[0]); (void)close(cancel_pipe[0]); } frontend_test_release_lease(&mutation); frontend_maintenance_fixture_finish(&fixture); ASSERT_TRUE(pipes_ready); ASSERT_TRUE(child > 0); ASSERT_TRUE(announced); ASSERT_EQ(status, CBM_VERSION_COHORT_OK); ASSERT_EQ(quiesce, CBM_VERSION_COHORT_QUIESCE_REQUESTED); ASSERT_TRUE(cancelled); ASSERT_TRUE(waited); ASSERT_TRUE(WIFEXITED(child_status)); ASSERT_EQ(WEXITSTATUS(child_status), 37); PASS(); } /* RED: supervised process-tree cancellation permits one second of graceful * shutdown followed by one second of forced containment. The maintenance * observer must not _Exit the owning process before that bounded supervisor * window can finish and join the observer normally. */ TEST(daemon_local_participant_monitor_allows_supervisor_containment_window) { frontend_maintenance_fixture_t fixture; ASSERT_TRUE(frontend_maintenance_fixture_start(&fixture, "monitor-window")); int ready_pipe[2] = {-1, -1}; bool pipe_ready = pipe(ready_pipe) == 0; pid_t child = pipe_ready ? fork() : -1; if (child == 0) { (void)close(ready_pipe[0]); cbm_daemon_ipc_endpoint_t *endpoint = cbm_daemon_ipc_endpoint_new("0123456789abcdef", fixture.parent); cbm_version_cohort_manager_t *manager = endpoint ? cbm_version_cohort_manager_new(endpoint) : NULL; cbm_version_cohort_lease_t *participant = NULL; cbm_daemon_conflict_t conflict; cbm_daemon_build_identity_t identity = frontend_test_identity(); cbm_version_cohort_status_t admitted = manager ? cbm_version_cohort_acquire(manager, &identity, cbm_now_ms() + 2000U, &participant, &conflict) : CBM_VERSION_COHORT_IO; atomic_bool cancelled; atomic_init(&cancelled, false); cbm_daemon_maintenance_monitor_t *monitor = admitted == CBM_VERSION_COHORT_OK ? cbm_daemon_maintenance_monitor_start(manager, frontend_test_cancel_and_mark, &cancelled, 38, "test-supervisor-window") : NULL; const char ready = 'R'; bool announced = monitor && write(ready_pipe[1], &ready, 1) == 1; (void)close(ready_pipe[1]); if (!announced) { _exit(72); } while (!atomic_load_explicit(&cancelled, memory_order_acquire)) { cbm_usleep(1000); } /* Model the maximum graceful + forced-settle supervisor bounds. */ cbm_usleep(2100U * 1000U); bool stopped = cbm_daemon_maintenance_monitor_stop(&monitor); frontend_test_release_lease(&participant); while (manager && cbm_version_cohort_manager_free(&manager) != CBM_PRIVATE_FILE_LOCK_OK) { cbm_usleep(1000); } cbm_daemon_ipc_endpoint_free(endpoint); _exit(stopped ? 0 : 73); } if (pipe_ready) { (void)close(ready_pipe[1]); } bool announced = child > 0 && frontend_test_wait_byte(ready_pipe[0], 'R', cbm_now_ms() + 2000U); cbm_version_cohort_lease_t *mutation = NULL; cbm_version_cohort_quiesce_result_t quiesce = CBM_VERSION_COHORT_QUIESCE_NOT_NEEDED; cbm_version_cohort_status_t status = announced ? cbm_version_cohort_reserve_for_mutation(fixture.manager, cbm_now_ms() + 5000U, frontend_test_quiesce_requested, NULL, &quiesce, &mutation) : CBM_VERSION_COHORT_IO; if (status != CBM_VERSION_COHORT_OK && child > 0) { (void)kill(child, SIGKILL); } int child_status = 0; bool waited = child > 0 && waitpid(child, &child_status, 0) == child; if (pipe_ready) { (void)close(ready_pipe[0]); } frontend_test_release_lease(&mutation); frontend_maintenance_fixture_finish(&fixture); ASSERT_TRUE(pipe_ready); ASSERT_TRUE(child > 0); ASSERT_TRUE(announced); ASSERT_EQ(status, CBM_VERSION_COHORT_OK); ASSERT_EQ(quiesce, CBM_VERSION_COHORT_QUIESCE_REQUESTED); ASSERT_TRUE(waited); ASSERT_TRUE(WIFEXITED(child_status)); ASSERT_EQ(WEXITSTATUS(child_status), 0); PASS(); } /* RED: once a long request is active, a buffered writer can fill the bounded * frontend queue and close its pipe. Waiting indefinitely for queue capacity * prevents the sole reader from observing that already-pending EOF, so neither * the request nor its daemon session is cancelled. Overload must instead fail * the frontend promptly and close/cancel the authenticated session. */ TEST(daemon_frontend_over_capacity_input_cannot_hide_eof_behind_active_request) { ASSERT_TRUE(frontend_eof_run_isolated("overflow", true)); PASS(); } /* Clean EOF is the normal MCP-session ownership boundary. Accepted work gets a * bounded drain opportunity; work still active at the deadline is cancelled * with that exact session, while the frontend itself reports a clean close. */ TEST(daemon_frontend_eof_drain_timeout_cancels_and_returns_success) { ASSERT_TRUE(frontend_eof_run_isolated("timeout", false)); PASS(); } /* A daemon response can finish its IPC exchange and then block forever while * writing to an agent that stopped reading stdout. EOF must still bound the * thin frontend process. Keep the daemon in a separate child so the frontend's * terminal watchdog closes a real kernel connection; the daemon must observe * that close, cancel the exact session, and shut down normally. */ TEST(daemon_frontend_stdout_backpressure_eof_fail_stops_and_cancels_session) { ASSERT_TRUE(frontend_backpressure_run_isolated(false)); PASS(); } /* Maintenance must not depend on either MCP stdio thread making progress. * Keep stdin open, fill stdout with a completed daemon response, then publish * activation intent. The independent monitor must release the cohort lease and * kernel session within its bound; the daemon must cancel that exact session. */ TEST(daemon_frontend_stdout_backpressure_maintenance_stops_and_cancels_session) { ASSERT_TRUE(frontend_backpressure_run_isolated(true)); PASS(); } #endif TEST(daemon_local_participant_monitor_joins_before_manager_teardown) { frontend_maintenance_fixture_t fixture; ASSERT_TRUE(frontend_maintenance_fixture_start(&fixture, "monitor-join")); cbm_daemon_maintenance_monitor_t *monitor = cbm_daemon_maintenance_monitor_start( fixture.manager, NULL, NULL, EXIT_FAILURE, "test-idle-command"); bool started = monitor != NULL; bool stopped = started && cbm_daemon_maintenance_monitor_stop(&monitor); bool consumed = monitor == NULL; frontend_maintenance_fixture_finish(&fixture); ASSERT_TRUE(started); ASSERT_TRUE(stopped); ASSERT_TRUE(consumed); PASS(); } SUITE(daemon_frontend) { RUN_TEST(daemon_frontend_recognizes_exact_cancellation_notification); RUN_TEST(daemon_frontend_correlates_cancellation_to_exact_request); RUN_TEST(daemon_frontend_ignores_cancellation_text_in_string_content); RUN_TEST(daemon_frontend_rejects_non_notification_cancellation_shapes); #ifndef _WIN32 RUN_TEST(daemon_frontend_maintenance_exits_while_stdio_reader_is_blocked); RUN_TEST(daemon_local_participant_monitor_cancels_then_bounds_active_operation); RUN_TEST(daemon_local_participant_monitor_allows_supervisor_containment_window); RUN_TEST(daemon_frontend_over_capacity_input_cannot_hide_eof_behind_active_request); RUN_TEST(daemon_frontend_eof_drain_timeout_cancels_and_returns_success); RUN_TEST(daemon_frontend_stdout_backpressure_eof_fail_stops_and_cancels_session); RUN_TEST(daemon_frontend_stdout_backpressure_maintenance_stops_and_cancels_session); #endif RUN_TEST(daemon_local_participant_monitor_joins_before_manager_teardown); }