| 145 | } |
| 146 | |
| 147 | int cbm_index_spawn_worker(const char *args_json, bool single_thread, const char *marker_file, |
| 148 | const char *quarantine_file, cbm_index_worker_result_t *result) { |
| 149 | g_spawn_count++; /* test hook (#845) — see cbm_index_supervisor_spawn_count */ |
| 150 | if (single_thread) { |
| 151 | g_spawn_st_count++; /* test hook — must stay 0: recovery is parallel-only */ |
| 152 | } |
| 153 | result->outcome = CBM_PROC_SPAWN_FAILED; |
| 154 | result->exit_code = -1; |
| 155 | result->term_signal = 0; |
| 156 | result->response = NULL; |
| 157 | |
| 158 | char self[1024] = {0}; |
| 159 | if (!cbm_http_server_resolve_binary_path(NULL, self, sizeof(self)) || !self[0]) { |
| 160 | cbm_log_warn("index.supervisor.no_self_path", "action", "degrade_in_process"); |
| 161 | return -1; |
| 162 | } |
| 163 | |
| 164 | int pid = (int)cbm_getpid(); |
| 165 | char resp_path[1024]; |
| 166 | char log_path[1024]; |
| 167 | worker_tmp_path(resp_path, sizeof(resp_path), pid, ".response"); |
| 168 | worker_tmp_path(log_path, sizeof(log_path), pid, ".log"); |
| 169 | (void)remove(resp_path); /* clear any stale file */ |
| 170 | |
| 171 | /* No --progress: the worker's DEFAULT structured logging already provides the |
| 172 | * no-progress heartbeat (INFO parallel.extract.progress every 10 files + each |
| 173 | * pass boundary — all newline-terminated → tailed → reset the quiet-timeout). |
| 174 | * --progress would be strictly worse here: it installs a REPLACE-mode sink that |
| 175 | * suppresses those default lines and emits per-file extraction as a carriage- |
| 176 | * return in-place update (no trailing '\n'), which cbm_tail_log does not count |
| 177 | * as progress. (It would not corrupt the response either — that goes to the |
| 178 | * separate --response-out file, not stdout.) */ |
| 179 | const char *argv[8]; |
| 180 | int n = 0; |
| 181 | argv[n++] = self; |
| 182 | argv[n++] = "cli"; |
| 183 | argv[n++] = "--index-worker"; |
| 184 | argv[n++] = "index_repository"; |
| 185 | argv[n++] = args_json; |
| 186 | argv[n++] = "--response-out"; |
| 187 | argv[n++] = resp_path; |
| 188 | argv[n] = NULL; |
| 189 | |
| 190 | /* Recovery-run probe knobs → inherited env for the child. Spawns are |
| 191 | * sequential, so mutating the parent's environment around a single spawn is |
| 192 | * safe. Set only the requested knobs; unset them all again after reaping so |
| 193 | * a later attempt (or the caller) starts from a clean environment. */ |
| 194 | if (single_thread) { |
| 195 | cbm_setenv("CBM_INDEX_SINGLE_THREAD", "1", 1); |
| 196 | } |
| 197 | if (marker_file && marker_file[0]) { |
| 198 | cbm_setenv("CBM_INDEX_MARKER_FILE", marker_file, 1); |
| 199 | } |
| 200 | if (quarantine_file && quarantine_file[0]) { |
| 201 | cbm_setenv("CBM_INDEX_QUARANTINE_FILE", quarantine_file, 1); |
| 202 | } |
| 203 | |
| 204 | cbm_proc_opts_t opts = {0}; |
no test coverage detected