fix(lib): stop cbuf_put/get orphaning their mutex on cancellation, hanging vtaskmgr_fini
pthread_cond_wait() reacquires its mutex before honoring a cancellation, but mylock.c installed no cleanup handler to release it again -- a thread cancelled mid-wait terminated holding the lock forever. vtaskmgr's pthread_cancel()-based teardown could land there from three angles (dispatcher cancelled while polling the shared queue, or a vcpu racing back into it after its own shutdown signal), permanently deadlocking any other thread that touched the same queue and hanging vtaskmgr_fini()'s pthread_join() indefinitely. Reproduced live as xce_test hanging on Ctrl-D exit once a connection context was entered; gdb confirmed a vcpu thread blocked forever on a mutex owned by an already-exited thread ID. Fix: cbuf_put()/cbuf_get()/cbuf_timedget() now unlock via a pthread_cleanup_push/pop handler around the wait, so a cancelled waiter always releases the mutex. vcpu_idle()/vcpu_entry() also recognize the teardown poison pill and exit immediately instead of needlessly re-announcing into the already-dead shared queue. New test_vtaskmgr_teardown.c reproduces the race deterministically (self-pinned to one CPU, since true multi-core parallelism rarely hits the window) and bounds vtaskmgr_fini() with a timed join so a regression fails fast instead of hanging the suite. Logged as bug-1810. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
b7250c2ec6
commit
0f3919f195
9 changed files with 335 additions and 9 deletions
|
|
@ -48,6 +48,24 @@ void vcpu_idle(vcpu_t* vcpu)
|
|||
vtaskmgr_t* vtaskmgr = (vtaskmgr_t*)vcpu->owner;
|
||||
|
||||
vcpu->task = NULL;
|
||||
|
||||
/* vcpu_fini()'s teardown protocol (bind_vtask(NULL, vcpu)) can queue a
|
||||
* shutdown "poison pill" into this vcpu's OWN wbuf while it is busy
|
||||
* running a real task -- the pill then sits there until the next
|
||||
* vcpu_idle() call. Consume it directly (non-blocking) instead of
|
||||
* first re-announcing this vcpu into the SHARED vtaskmgr->wbuf: by
|
||||
* the time any vcpu_fini() runs, vtaskmgr_fini() has already
|
||||
* cancelled+joined the dispatcher thread, the sole consumer of
|
||||
* vtaskmgr->wbuf, so re-announcing here is pure wasted work at best
|
||||
* (bug-1810) -- mylock.c's cbuf_put()/cbuf_get() now install a
|
||||
* cancellation cleanup handler so a cancel landing there can no
|
||||
* longer orphan the mutex, but skipping the needless re-announce
|
||||
* entirely is still the correct, minimal behavior once a shutdown has
|
||||
* already been signaled. */
|
||||
if (cbuf_tryget(&(vcpu->wbuf), (void*)&(vcpu->task)) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cbuf_put(&(vtaskmgr->wbuf), (void*)&(vcpu->cpuid));
|
||||
cbuf_get(&(vcpu->wbuf), (void*)&(vcpu->task));
|
||||
return;
|
||||
|
|
@ -112,6 +130,20 @@ static void* vcpu_entry(void* arg)
|
|||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
} else {
|
||||
vcpu_idle(my);
|
||||
|
||||
/* vcpu_fini()'s bind_vtask(NULL, vcpu) is the ONLY caller
|
||||
* that ever delivers a NULL task through this vcpu's own
|
||||
* wbuf -- the dispatcher (vdispatcher_entry()) only calls
|
||||
* bind_vtask() when pick_eligible_task() returned non-NULL.
|
||||
* A NULL task observed here is therefore unambiguously the
|
||||
* shutdown signal: exit immediately rather than looping back
|
||||
* into vcpu_idle(), which would re-announce this vcpu into
|
||||
* the shared vtaskmgr->wbuf after vtaskmgr_fini() has
|
||||
* already torn down its sole consumer (the dispatcher
|
||||
* thread) -- see vcpu_idle()'s own comment (bug-1810). */
|
||||
if (my->task == NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_testcancel();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue