Skip to content

Commit fd36045

Browse files
map588claude
andcommitted
fix(watcher): add mutex to protect projects hash table from concurrent access
The watcher's projects hash table was written by the main thread (watch/unwatch) and iterated by the watcher thread (poll_once) with no synchronization. Added cbm_mutex_t to the watcher struct and wrapped all hash table operations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1d39640 commit fd36045

1 file changed

Lines changed: 14 additions & 0 deletions

File tree

src/watcher/watcher.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "foundation/log.h"
2121
#include "foundation/hash_table.h"
2222
#include "foundation/compat.h"
23+
#include "foundation/compat_thread.h"
2324
#include "foundation/compat_fs.h"
2425
#include "foundation/str_util.h"
2526

@@ -50,6 +51,7 @@ struct cbm_watcher {
5051
cbm_index_fn index_fn;
5152
void *user_data;
5253
CBMHashTable *projects; /* name → project_state_t* */
54+
cbm_mutex_t projects_lock;
5355
atomic_int stopped;
5456
};
5557

@@ -236,6 +238,7 @@ cbm_watcher_t *cbm_watcher_new(cbm_store_t *store, cbm_index_fn index_fn, void *
236238
w->index_fn = index_fn;
237239
w->user_data = user_data;
238240
w->projects = cbm_ht_create(CBM_SZ_32);
241+
cbm_mutex_init(&w->projects_lock);
239242
atomic_init(&w->stopped, 0);
240243
return w;
241244
}
@@ -244,8 +247,11 @@ void cbm_watcher_free(cbm_watcher_t *w) {
244247
if (!w) {
245248
return;
246249
}
250+
cbm_mutex_lock(&w->projects_lock);
247251
cbm_ht_foreach(w->projects, free_state_entry, NULL);
248252
cbm_ht_free(w->projects);
253+
cbm_mutex_unlock(&w->projects_lock);
254+
cbm_mutex_destroy(&w->projects_lock);
249255
free(w);
250256
}
251257

@@ -264,6 +270,7 @@ void cbm_watcher_watch(cbm_watcher_t *w, const char *project_name, const char *r
264270
}
265271

266272
/* Remove old entry first (key points to state's project_name) */
273+
cbm_mutex_lock(&w->projects_lock);
267274
project_state_t *old = cbm_ht_get(w->projects, project_name);
268275
if (old) {
269276
cbm_ht_delete(w->projects, project_name);
@@ -272,17 +279,22 @@ void cbm_watcher_watch(cbm_watcher_t *w, const char *project_name, const char *r
272279

273280
project_state_t *s = state_new(project_name, root_path);
274281
cbm_ht_set(w->projects, s->project_name, s);
282+
cbm_mutex_unlock(&w->projects_lock);
275283
cbm_log_info("watcher.watch", "project", project_name, "path", root_path);
276284
}
277285

278286
void cbm_watcher_unwatch(cbm_watcher_t *w, const char *project_name) {
279287
if (!w || !project_name) {
280288
return;
281289
}
290+
cbm_mutex_lock(&w->projects_lock);
282291
project_state_t *s = cbm_ht_get(w->projects, project_name);
283292
if (s) {
284293
cbm_ht_delete(w->projects, project_name);
285294
state_free(s);
295+
}
296+
cbm_mutex_unlock(&w->projects_lock);
297+
if (s) {
286298
cbm_log_info("watcher.unwatch", "project", project_name);
287299
}
288300
}
@@ -421,7 +433,9 @@ int cbm_watcher_poll_once(cbm_watcher_t *w) {
421433
.now = now_ns(),
422434
.reindexed = 0,
423435
};
436+
cbm_mutex_lock(&w->projects_lock);
424437
cbm_ht_foreach(w->projects, poll_project, &ctx);
438+
cbm_mutex_unlock(&w->projects_lock);
425439
return ctx.reindexed;
426440
}
427441

0 commit comments

Comments
 (0)