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,24 @@ 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
278286void cbm_watcher_unwatch (cbm_watcher_t * w , const char * project_name ) {
279287 if (!w || !project_name ) {
280288 return ;
281289 }
290+ bool removed = false;
291+ cbm_mutex_lock (& w -> projects_lock );
282292 project_state_t * s = cbm_ht_get (w -> projects , project_name );
283293 if (s ) {
284294 cbm_ht_delete (w -> projects , project_name );
285295 state_free (s );
296+ removed = true;
297+ }
298+ cbm_mutex_unlock (& w -> projects_lock );
299+ if (removed ) {
286300 cbm_log_info ("watcher.unwatch" , "project" , project_name );
287301 }
288302}
@@ -411,17 +425,53 @@ static void poll_project(const char *key, void *val, void *ud) {
411425 s -> next_poll_ns = ctx -> now + ((int64_t )s -> interval_ms * US_PER_MS );
412426}
413427
428+ /* Callback to snapshot project state pointers into an array. */
429+ typedef struct {
430+ project_state_t * * items ;
431+ int count ;
432+ int cap ;
433+ } snapshot_ctx_t ;
434+
435+ static void snapshot_project (const char * key , void * val , void * ud ) {
436+ (void )key ;
437+ snapshot_ctx_t * sc = ud ;
438+ if (val && sc -> count < sc -> cap ) {
439+ sc -> items [sc -> count ++ ] = val ;
440+ }
441+ }
442+
414443int cbm_watcher_poll_once (cbm_watcher_t * w ) {
415444 if (!w ) {
416445 return 0 ;
417446 }
418447
448+ /* Snapshot project pointers under lock, then poll without holding it.
449+ * This keeps the critical section small — poll_project does git I/O
450+ * and may invoke index_fn which runs the full pipeline. */
451+ cbm_mutex_lock (& w -> projects_lock );
452+ int n = cbm_ht_count (w -> projects );
453+ if (n == 0 ) {
454+ cbm_mutex_unlock (& w -> projects_lock );
455+ return 0 ;
456+ }
457+ project_state_t * * snap = malloc (n * sizeof (project_state_t * ));
458+ if (!snap ) {
459+ cbm_mutex_unlock (& w -> projects_lock );
460+ return 0 ;
461+ }
462+ snapshot_ctx_t sc = {.items = snap , .count = 0 , .cap = n };
463+ cbm_ht_foreach (w -> projects , snapshot_project , & sc );
464+ cbm_mutex_unlock (& w -> projects_lock );
465+
419466 poll_ctx_t ctx = {
420467 .w = w ,
421468 .now = now_ns (),
422469 .reindexed = 0 ,
423470 };
424- cbm_ht_foreach (w -> projects , poll_project , & ctx );
471+ for (int i = 0 ; i < sc .count ; i ++ ) {
472+ poll_project (NULL , snap [i ], & ctx );
473+ }
474+ free (snap );
425475 return ctx .reindexed ;
426476}
427477
0 commit comments