| 2173 | } |
| 2174 | |
| 2175 | static char *application_index_execute(void *context, const char *root_path, |
| 2176 | const char *args_json) { |
| 2177 | cbm_daemon_application_session_t *session = context; |
| 2178 | if (!session || !root_path || !args_json) { |
| 2179 | return NULL; |
| 2180 | } |
| 2181 | char *project_key = application_index_project_key(root_path, args_json); |
| 2182 | if (!project_key) { |
| 2183 | return cbm_mcp_text_result("failed to derive index project identity", true); |
| 2184 | } |
| 2185 | application_job_subscribe_status_t subscribe_status = APPLICATION_JOB_SUBSCRIBE_UNAVAILABLE; |
| 2186 | cbm_daemon_application_job_t *job = NULL; |
| 2187 | for (;;) { |
| 2188 | job = application_job_subscribe(session->application, project_key, root_path, args_json, |
| 2189 | &subscribe_status); |
| 2190 | if (job || (subscribe_status != APPLICATION_JOB_SUBSCRIBE_BUSY && |
| 2191 | subscribe_status != APPLICATION_JOB_SUBSCRIBE_CANCELLING)) { |
| 2192 | break; |
| 2193 | } |
| 2194 | /* Physical job limit reached (or a same-project cancel still |
| 2195 | * draining): QUEUE instead of surfacing a raw busy error. The |
| 2196 | * request thread blocks for the whole index anyway, so waiting for |
| 2197 | * a slot is the same contract — and the wait stays cancellable and |
| 2198 | * shutdown-aware (a stopping coordinator answers UNAVAILABLE, which |
| 2199 | * exits this loop with the error below). */ |
| 2200 | atomic_fetch_add_explicit(&g_application_busy_queue_waits_for_test, 1, |
| 2201 | memory_order_release); |
| 2202 | cbm_mutex_lock(&session->application->mutex); |
| 2203 | bool queued_cancelled = application_request_cancelled_locked(session); |
| 2204 | cbm_mutex_unlock(&session->application->mutex); |
| 2205 | if (queued_cancelled) { |
| 2206 | free(project_key); |
| 2207 | return cbm_mcp_text_result("index operation cancelled for this session", true); |
| 2208 | } |
| 2209 | cbm_usleep(APPLICATION_JOB_POLL_US); |
| 2210 | } |
| 2211 | free(project_key); |
| 2212 | if (!job) { |
| 2213 | const char *message = "daemon index coordinator is stopping or unavailable"; |
| 2214 | if (subscribe_status == APPLICATION_JOB_SUBSCRIBE_OPTIONS_CONFLICT) { |
| 2215 | message = "another index operation for this project is active with different options"; |
| 2216 | } else if (subscribe_status == APPLICATION_JOB_SUBSCRIBE_ALLOCATION_FAILED) { |
| 2217 | message = "daemon index coordinator could not allocate an index job"; |
| 2218 | } |
| 2219 | return cbm_mcp_text_result(message, true); |
| 2220 | } |
| 2221 | cbm_mutex_lock(&session->application->mutex); |
| 2222 | if (application_request_cancelled_locked(session)) { |
| 2223 | application_job_unsubscribe_locked(job); |
| 2224 | cbm_mutex_unlock(&session->application->mutex); |
| 2225 | return cbm_mcp_text_result("index operation cancelled for this session", true); |
| 2226 | } |
| 2227 | if (session->active_job) { |
| 2228 | application_job_unsubscribe_locked(job); |
| 2229 | cbm_mutex_unlock(&session->application->mutex); |
| 2230 | return cbm_mcp_text_result("this session already has an active index operation", true); |
| 2231 | } |
| 2232 | session->active_job = job; |
nothing calls this directly
no test coverage detected