| 1154 | } poll_ctx_t; |
| 1155 | |
| 1156 | static void prune_missing_project(cbm_watcher_t *w, project_state_t *s) { |
| 1157 | if (!w || !s || !s->project_name) { |
| 1158 | return; |
| 1159 | } |
| 1160 | |
| 1161 | char *project_name = cbm_strdup(s->project_name); |
| 1162 | char *root_path = cbm_strdup(s->root_path); |
| 1163 | if (!project_name || !root_path) { |
| 1164 | free(project_name); |
| 1165 | free(root_path); |
| 1166 | return; |
| 1167 | } |
| 1168 | |
| 1169 | bool removed = false; |
| 1170 | cbm_mutex_lock(&w->coordination_lock); |
| 1171 | bool mutation_acquired = |
| 1172 | !w->mutation_begin || w->mutation_begin(w->mutation_context, project_name); |
| 1173 | if (!mutation_acquired) { |
| 1174 | cbm_mutex_unlock(&w->coordination_lock); |
| 1175 | free(project_name); |
| 1176 | free(root_path); |
| 1177 | return; |
| 1178 | } |
| 1179 | cbm_mutex_lock(&w->projects_lock); |
| 1180 | project_state_t *current = cbm_ht_get(w->projects, project_name); |
| 1181 | /* Deferred free (same discipline as cbm_watcher_unwatch): this state |
| 1182 | * is referenced by the poll_once snapshot iterating us. On OOM the |
| 1183 | * watch stays registered and pruning retries on the next cycle. Re-stat |
| 1184 | * under the mutation lease so a root restored after the poll snapshot is |
| 1185 | * never pruned. */ |
| 1186 | int stat_errno = 0; |
| 1187 | uint64_t now_ms = cbm_now_ms(); |
| 1188 | bool still_eligible = |
| 1189 | current == s && strcmp(current->root_path, root_path) == 0 && |
| 1190 | current->missing_root_count >= MISSING_ROOT_DELETE_AFTER && current->first_missing_ms > 0 && |
| 1191 | now_ms - current->first_missing_ms >= (uint64_t)prune_grace_s() * CBM_MSEC_PER_SEC && |
| 1192 | root_status(root_path, &stat_errno) == ROOT_MISSING; |
| 1193 | if (still_eligible && defer_state_free(w, s)) { |
| 1194 | if (delete_cached_project_db(project_name)) { |
| 1195 | atomic_store_explicit(&s->registered, false, memory_order_release); |
| 1196 | cbm_ht_delete(w->projects, project_name); |
| 1197 | removed = true; |
| 1198 | } else { |
| 1199 | /* The state stays registered; undo the just-added deferred-free |
| 1200 | * entry so the next poll can safely retry the failed deletion. */ |
| 1201 | w->pending_free[--w->pending_free_count] = NULL; |
| 1202 | } |
| 1203 | } |
| 1204 | cbm_mutex_unlock(&w->projects_lock); |
| 1205 | |
| 1206 | if (removed && w->project_pruned) { |
| 1207 | w->project_pruned(w->mutation_context, project_name); |
| 1208 | } |
| 1209 | if (w->mutation_begin) { |
| 1210 | w->mutation_end(w->mutation_context, project_name); |
| 1211 | } |
| 1212 | cbm_mutex_unlock(&w->coordination_lock); |
| 1213 |
no test coverage detected