Conversation
vtrunkd installs signal handlers without SA_RESTART and processes signal each other (SIGUSR1) at runtime. A signal landing inside sem_wait() makes it return -1/EINTR; all ~90 call sites ignore the return value and proceed as if the lock was acquired, then sem_post() later. One EINTR hit raises the semaphore count above 1, breaking mutual exclusion on the shared memory and eventually deadlocking the tunnel. -O3 shortens critical sections, so processes spend far more time blocked in sem_wait, making a signal hit vastly more likely — which is why the hang appears with default O3 builds. Verified with a standalone two-process repro (shared sem_t, SIGUSR1 bomber, no SA_RESTART): buggy pattern -> 334,968 mutual-exclusion violations, final sem value 2; with the EINTR-retry wrapper -> 0 violations, sem value 1. Changes: - vtun.h: EINTR-retrying sem_wait wrapper applied via macro to all call sites (primary fix). - linkfd.c get_resend_frame(): '=' -> '==' in two conditions that were overwriting resend_frames_buf[j].seq_num under resend_buf_sem. - tunnel.c fd_server(): the usecount==0 break exited the loop holding shm_sem while the other path reached the final sem_post without it, over-posting the semaphore; track held state and acquire before teardown. - server.c: remove sem_init() on a sem_t* alias of the sem_open()'d semaphore (compile error on modern gcc, and wrong: reinitializing a process-shared semaphore in use).
|
Added one commit on top: fix: bound the fd_server teardown wait on shm_sem. The first commit made the SIGTERM teardown path in The new commit adds Separate PRs for the independent findings from review: stale semaphore reset on server start, fd_server failure handling, and removal of the dead |
Summary
vtrunkd built with the default
-O3deadlocks after some runtime; the only recovery is kill + restart (see also #63, #141, #198). Root cause: EINTR-unsafe semaphore usage combined with signal handlers installed withoutSA_RESTART, plus two secondary bugs that leak/over-postshm_semand corrupt the shared resend buffer.Root cause
vtrunkd processes signal each other (SIGUSR1) at runtime, and all handlers are installed without
SA_RESTART. A signal landing insidesem_wait()makes it return-1/EINTR. All ~90sem_waitcall sites ignore the return value and proceed as if the lock was acquired, then do the pairedsem_post()later. A single EINTR hit therefore raises the semaphore count above 1 — mutual exclusion on the shared memory is permanently broken, and the resulting shm corruption ends in the observed deadlock.Why
-O3specifically: optimization shortens the critical sections, so processes spend a much larger fraction of time blocked insidesem_wait, making a signal hit vastly more likely. The bug exists at-O0too; it just rarely fires.Verification
Standalone two-process repro (shared
sem_tin shm, SIGUSR1 bomber, handlers withoutSA_RESTART, canary-guarded critical section), built with-O3:One EINTR was enough to produce 334k mutual-exclusion violations and leave the semaphore at 2. The EINTR-retry wrapper (identical to the one this PR adds to
vtun.h) eliminates it completely.Changes
sem_waitwrapper applied via macro to all call sites (primary fix; one reviewable change instead of ~90 edits).get_resend_frame()—=→==in two conditions that were assigningresend_frames_buf[j].seq_num = *seq_numunderresend_buf_sem, corrupting the shared resend buffer (present since the initial import).fd_server()— theusecount==0breakexited the loop holdingshm_sem, while thefdserver_termpath reached the same finalsem_postwithout holding it (over-post → count above 1). Now tracks held state and acquires before teardown so the final post is always balanced.sem_init()called on asem_t*alias of thesem_open()'d semaphore: a compile error on modern gcc (-Wincompatible-pointer-typesas error) and wrong regardless — reinitializing a process-shared semaphore possibly in use.Notes
-O3on gcc 13/14. Note: gcc ≥ 10 needs-fcommondue to pre-existing tentative definitions shared across TUs (unrelated to this fix, not touched here).