mirror of
https://github.com/ruby/ruby.git
synced 2026-10-29 17:06:21 +08:00
thread: free a shared native thread's resources when it retires
A retiring shared thread -- ractor_sched_deq giving itself back, or a thread rejoining a full pool (nt->retiring) -- just returned from nt_start, leaking its rb_native_thread: the struct, the coroutine context, the condvars and the sigaltstack. This was the old "how to free nt and nt->altstack?" TODO, and retiring made it recurring: every pool shrink leaked one. Both exits drop the nt from the counts before leaving the loop, so nothing can reference it; the exiting thread frees it itself, disarming its sigaltstack registration first so a late signal cannot run on the freed block. Verified with SNT_IDLE_RETIRE=0 (retire on the first idle wakeup): 168 retire/free pairs in a bursty ractor workload and a clean btest run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
33124c7c2e
commit
dce74ebf04
1 file changed
+28
-2
Notes:
git
2026-08-14 16:36:02 +00:00
+28
-2
@@ -2055,6 +2055,20 @@ native_thread_destroy(struct rb_native_thread *nt)
|
||||
}
|
||||
}
|
||||
|
||||
// A retiring snt frees its own rb_native_thread as it exits. Disarm the
|
||||
// altstack registration first: a signal between the free and the thread's
|
||||
// end must not run on the freed block.
|
||||
static void
|
||||
native_thread_destroy_self(struct rb_native_thread *nt)
|
||||
{
|
||||
#ifdef USE_SIGALTSTACK
|
||||
stack_t disable = {0};
|
||||
disable.ss_flags = SS_DISABLE;
|
||||
sigaltstack(&disable, NULL);
|
||||
#endif
|
||||
native_thread_destroy(nt);
|
||||
}
|
||||
|
||||
#if defined HAVE_PTHREAD_GETATTR_NP || defined HAVE_PTHREAD_ATTR_GET_NP
|
||||
#define STACKADDR_AVAILABLE 1
|
||||
#elif defined HAVE_PTHREAD_GET_STACKADDR_NP && defined HAVE_PTHREAD_GET_STACKSIZE_NP
|
||||
@@ -2440,6 +2454,8 @@ nt_start(void *ptr)
|
||||
coroutine_initialize_main(nt->nt_context);
|
||||
}
|
||||
|
||||
bool retired = false;
|
||||
|
||||
while (1) {
|
||||
if (nt->dedicated) {
|
||||
// wait running turn
|
||||
@@ -2464,7 +2480,10 @@ nt_start(void *ptr)
|
||||
}
|
||||
else {
|
||||
RUBY_DEBUG_LOG("check next");
|
||||
if (nt->retiring) break; // came back with no room in the shared pool
|
||||
if (nt->retiring) { // came back with no room in the shared pool
|
||||
retired = true;
|
||||
break;
|
||||
}
|
||||
|
||||
rb_ractor_t *r = ractor_sched_deq(vm, NULL);
|
||||
|
||||
@@ -2505,7 +2524,8 @@ nt_start(void *ptr)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// retired: this nt is done.
|
||||
// ractor_sched_deq retired this nt.
|
||||
retired = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2516,6 +2536,12 @@ nt_start(void *ptr)
|
||||
}
|
||||
}
|
||||
|
||||
if (retired) {
|
||||
// The counts dropped this nt already; nothing can reference it now.
|
||||
RUBY_DEBUG_LOG("retired nt:%u", nt->serial);
|
||||
native_thread_destroy_self(nt);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user