--- id: "bug-1756" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "worker" severity: "unknown" timestamp: "2026-09-02T02:40:00Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: ["src/worker/src/resource_sync.c"] tags: ["worker-rejoin-resync", "task-4.4", "resource_sync-c", "resource_sync_destroy", "resource_sync_stop", "asan", "double-join", "pthread", "thread-lifecycle", "companion-fix", "requirement-3.8"] related: ["bug-1519", "bug-1757"] legacy: true --- # bug-1756: worker-rejoin-resync task 4.4 (Requirement 3.8, resource_sync.c): AddressSanitizer abort "Joining… ## Description worker-rejoin-resync task 4.4 (Requirement 3.8, resource_sync.c): AddressSanitizer abort "Joining already joined thread, aborting." while building the task's own new resync_pending-gate tests for resource_sync_thread(). Discovered live: the new tests are the first callers ever to exercise resource_sync_create()/resource_sync_start()/resource_sync_stop()/resource_sync_destroy() as a full lifecycle from a test binary (the two pre-existing tests only ever called collect_load_indices() directly). Each test calls resource_sync_stop(sync) then resource_sync_destroy(sync) back-to-back -- the exact same sequence xcecd.c's own real shutdown path (xcecd_cleanup(), src/worker/src/xcecd.c:580-582) already uses in production. ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause resource_sync_stop() calls pthread_join(sync->thread, NULL) but never resets sync->thread to 0 (or any invalid sentinel) afterward. resource_sync_destroy() unconditionally calls resource_sync_stop() again as its first action, with no guard on whether the module was already stopped -- so a caller pattern of stop() then destroy() (used verbatim by both xcecd_cleanup() in production and the new task 4.4 tests) pthread_join()s the same already-joined thread handle a second time. This is undefined behavior; under ASAN it aborts hard instead of silently misbehaving. connection_handler.c's analogous network_handler_destroy() already guards its own equivalent second stop call with `if (handler->running)`, showing this exact hazard is a known pattern elsewhere in the same daemon that resource_sync.c's destroy simply never adopted. ## Fix FIXED (2026-09-02, worker-rejoin-resync task 4.4, in-boundary companion fix -- resource_sync.c was already in this task's file boundary and the double-join blocked the task's own new tests from ever completing). resource_sync_destroy() now guards its internal resource_sync_stop() call with `if (atomic_load(&sync->running))`, mirroring network_handler_destroy()'s established idiom exactly -- if the caller already stopped the module explicitly, destroy() no longer re-joins the same thread handle. Verified via 5 consecutive full runs of the rebuilt resource_sync_test binary under ASAN: 5 Tests, 0 Failures each run, no abort (previously deterministic abort on the very first run reaching a stop()+destroy() pair). Full worker suite (meson test --suite worker) also green: 7/7 OK, no regressions, including controller_connector_test and xcecd_resync_test (task 4.3's own suites). ## Verification _Not recorded (migrated from buglog.json)._ ## References - Related: bug-1519, bug-1757