| 1301 | } poll_ctx_t; |
| 1302 | |
| 1303 | static void prune_missing_project(cbm_watcher_t *w, project_state_t *s) { |
| 1304 | if (!w || !s || !s->project_name) { |
| 1305 | return; |
| 1306 | } |
| 1307 | |
| 1308 | char *project_name = cbm_strdup(s->project_name); |
| 1309 | char *root_path = cbm_strdup(s->root_path); |
| 1310 | if (!project_name || !root_path) { |
| 1311 | free(project_name); |
| 1312 | free(root_path); |
| 1313 | return; |
| 1314 | } |
| 1315 | |
| 1316 | bool removed = false; |
| 1317 | cbm_mutex_lock(&w->coordination_lock); |
| 1318 | bool mutation_acquired = |
| 1319 | !w->mutation_begin || w->mutation_begin(w->mutation_context, project_name); |
| 1320 | if (!mutation_acquired) { |
| 1321 | cbm_mutex_unlock(&w->coordination_lock); |
| 1322 | free(project_name); |
| 1323 | free(root_path); |
| 1324 | return; |
| 1325 | } |
| 1326 | cbm_mutex_lock(&w->projects_lock); |
| 1327 | project_state_t *current = cbm_ht_get(w->projects, project_name); |
| 1328 | /* Deferred free (same discipline as cbm_watcher_unwatch): this state |
| 1329 | * is referenced by the poll_once snapshot iterating us. On OOM the |
| 1330 | * watch stays registered and pruning retries on the next cycle. Re-stat |
| 1331 | * under the mutation lease so a root restored after the poll snapshot is |
| 1332 | * never pruned. */ |
| 1333 | int stat_errno = 0; |
| 1334 | uint64_t now_ms = cbm_now_ms(); |
| 1335 | bool still_eligible = |
| 1336 | current == s && strcmp(current->root_path, root_path) == 0 && |
| 1337 | current->missing_root_count >= MISSING_ROOT_DELETE_AFTER && current->first_missing_ms > 0 && |
| 1338 | now_ms - current->first_missing_ms >= (uint64_t)prune_grace_s() * CBM_MSEC_PER_SEC && |
| 1339 | root_status(root_path, &stat_errno) == ROOT_MISSING; |
| 1340 | if (still_eligible && defer_state_free(w, s)) { |
| 1341 | if (delete_cached_project_db(project_name)) { |
| 1342 | atomic_store_explicit(&s->registered, false, memory_order_release); |
| 1343 | cbm_ht_delete(w->projects, project_name); |
| 1344 | removed = true; |
| 1345 | } else { |
| 1346 | /* The state stays registered; undo the just-added deferred-free |
| 1347 | * entry so the next poll can safely retry the failed deletion. */ |
| 1348 | w->pending_free[--w->pending_free_count] = NULL; |
| 1349 | } |
| 1350 | } |
| 1351 | cbm_mutex_unlock(&w->projects_lock); |
| 1352 | |
| 1353 | if (removed && w->project_pruned) { |
| 1354 | w->project_pruned(w->mutation_context, project_name); |
| 1355 | } |
| 1356 | if (w->mutation_begin) { |
| 1357 | w->mutation_end(w->mutation_context, project_name); |
| 1358 | } |
| 1359 | cbm_mutex_unlock(&w->coordination_lock); |
| 1360 |
no test coverage detected