fix(daemon): retry the cohort claim across a generation handoff

With the Windows client-connection cluster fixed, the smoke's rapid
session churn exposed the next layer: each phase starts a fresh
first-session daemon, and a just-superseded generation still holds the
cohort daemon-claim EX lock for the brief window between going
client-terminal (which lets the next generation start) and releasing
its cohort locks. claim_acquire was a single non-blocking attempt, so
the new daemon failed immediately with daemon.start_failed
component=claim and the client saw "daemon could not start within
30000 ms".

The host now retries the claim on BUSY for a bounded 12 s handoff
window (well under the client's 30 s budget); a persistent BUSY still
fails as a real conflict. Cross-platform path, POSIX-syntax verified.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-07-19 00:59:16 +02:00
parent 53d867dfc1
commit 4cb05faa37
+16 -2
View File
@@ -37,6 +37,12 @@ enum {
HOST_APPLICATION_SHUTDOWN_MS = 10000,
HOST_COORDINATION_CLEANUP_MS = 500,
HOST_INITIAL_CLIENT_TIMEOUT_MS = 10000,
/* A just-superseded daemon generation can still hold the cohort claim for
* the brief window between going client-terminal (which lets the next
* generation start) and releasing its cohort locks. Wait out that handoff
* rather than failing startup on a transient BUSY. Bounded well under the
* client's 30 s daemon-start budget. */
HOST_DAEMON_CLAIM_TIMEOUT_MS = 12000,
HOST_WAIT_TICK_MS = 100,
HOST_HTTP_CONFIG_POLL_MS = 1000,
HOST_HTTP_RETRY_INITIAL_MS = 1000,
@@ -799,8 +805,16 @@ int cbm_daemon_host_run(const cbm_daemon_host_config_t *config) {
return -1;
}
cbm_version_cohort_daemon_claim_t *daemon_claim = NULL;
if (cbm_version_cohort_daemon_claim_acquire(cohort_manager, &daemon_claim) !=
CBM_VERSION_COHORT_OK) {
uint64_t claim_deadline = host_deadline_after(HOST_DAEMON_CLAIM_TIMEOUT_MS);
cbm_version_cohort_status_t claim_status =
cbm_version_cohort_daemon_claim_acquire(cohort_manager, &daemon_claim);
while (claim_status == CBM_VERSION_COHORT_BUSY && cbm_now_ms() < claim_deadline) {
/* The previous generation is releasing; retry until the claim is free
* or the handoff window elapses (a persistent BUSY = a real conflict). */
cbm_usleep(HOST_WAIT_TICK_MS * 1000);
claim_status = cbm_version_cohort_daemon_claim_acquire(cohort_manager, &daemon_claim);
}
if (claim_status != CBM_VERSION_COHORT_OK) {
cbm_log_error("daemon.start_failed", "component", "claim");
host_participant_guard_close(&participant_guard);
host_daemon_claim_close(&daemon_claim);