Run index_repository in a supervised worker subprocess with skip-and-continue * (Stage 3c). Returns the response string (caller frees): * - the worker's own response on a clean first run (the common path); * - after a crash/hang, the response from a clean single-threaded RECOVERY run * that quarantines the culprit file(s) — status="indexed" with them listed in * skipped[] as phase
| 7770 | * A physical CBM host never falls back to its in-process pipeline: an initial |
| 7771 | * start/protocol failure is returned as an explicit error response. */ |
| 7772 | static char *index_run_supervised(cbm_mcp_server_t *srv, const char *args) { |
| 7773 | invalidate_cached_store(srv); |
| 7774 | |
| 7775 | /* First attempt: normal parallel run. */ |
| 7776 | cbm_index_worker_result_t wr; |
| 7777 | int rc = cbm_index_spawn_worker_with_log_cancel( |
| 7778 | args, false, NULL, NULL, srv ? srv->index_log_callback : NULL, |
| 7779 | srv ? srv->index_log_context : NULL, srv ? &srv->pipeline_cancel_requested : NULL, &wr); |
| 7780 | cbm_mcp_supervised_result_disposition_t disposition = |
| 7781 | cbm_mcp_supervised_result_disposition(rc, &wr); |
| 7782 | |
| 7783 | if (disposition == CBM_MCP_SUPERVISED_RESULT_FALLBACK) { |
| 7784 | cbm_proc_outcome_t outcome = wr.outcome; |
| 7785 | cbm_index_worker_result_free(&wr); |
| 7786 | invalidate_cached_store(srv); |
| 7787 | return build_worker_failure_response(args, outcome); |
| 7788 | } |
| 7789 | if (disposition == CBM_MCP_SUPERVISED_RESULT_UNSAFE_TERMINAL) { |
| 7790 | char *failure = |
| 7791 | build_worker_unsafe_terminal_response(args, wr.outcome, wr.cancellation_requested); |
| 7792 | cbm_index_worker_result_free(&wr); |
| 7793 | invalidate_cached_store(srv); |
| 7794 | return failure; |
| 7795 | } |
| 7796 | if (disposition == CBM_MCP_SUPERVISED_RESULT_SUCCESS) { |
| 7797 | /* Clean exit → transfer the worker's response (the common path). */ |
| 7798 | char *resp = wr.response; /* transfer ownership to caller (may be NULL) */ |
| 7799 | wr.response = NULL; |
| 7800 | cbm_index_worker_result_free(&wr); |
| 7801 | invalidate_cached_store(srv); |
| 7802 | return resp; |
| 7803 | } |
| 7804 | |
| 7805 | /* Crash / hang / nonzero exit → skip-and-continue recovery. Re-run the |
| 7806 | * worker PARALLEL (there are no sequential production runs) with the |
| 7807 | * per-file marker JOURNAL armed; after each failed run the journal's |
| 7808 | * open-S set is the in-flight SUSPECT set. A file is quarantined only |
| 7809 | * when it appears in the suspect sets of TWO CONSECUTIVE failed runs |
| 7810 | * (intersection — a stale or merely unlucky in-flight file rotates out), |
| 7811 | * and only ONE file per round: the OLDEST open S in the intersection |
| 7812 | * (for a hang the oldest still-open file IS the stuck one; for a crash |
| 7813 | * it is the longest-running suspect — the best single deterministic |
| 7814 | * pick). A clean run then indexes the good files and reports the |
| 7815 | * quarantined ones as phase="crash"/"hang" skips via the ordinary |
| 7816 | * Stage-2 skip plumbing. The old design re-ran SINGLE-THREADED to keep |
| 7817 | * one exact marker; at scale that fell into the sequential crawl, went |
| 7818 | * quiet, was killed as a hang mid-pass, and the stale marker got FOUR |
| 7819 | * innocent ms-typescript fixtures quarantined one 15-minute retry at a |
| 7820 | * time. */ |
| 7821 | cbm_proc_outcome_t last_outcome = wr.outcome; |
| 7822 | cbm_index_worker_result_free(&wr); |
| 7823 | |
| 7824 | char marker_path[CBM_SZ_1K]; |
| 7825 | char quarantine_path[CBM_SZ_1K]; |
| 7826 | supervisor_tmp_path(marker_path, sizeof(marker_path), ".marker"); |
| 7827 | supervisor_tmp_path(quarantine_path, sizeof(quarantine_path), ".quarantine"); |
| 7828 | (void)remove(marker_path); |
| 7829 | /* Start the quarantine list empty (truncate any stale file). */ |
no test coverage detected