8000 Free rb_native_thread memory at fork by peterzhu2118 · Pull Request #13591 · ruby/ruby · GitHub
[go: up one dir, main page]

Skip to content

Free rb_native_thread memory at fork #13591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Free rb_native_thread memory at fork
We never freed any resources of rb_native_thread at fork because it would
cause it to hang. This is because it called rb_native_cond_destroy for
condition variables.  We can't call rb_native_cond_destroy here because
according to the specs of pthread_cond_destroy:

    Attempting to destroy a condition variable upon which other threads
    are currently blocked results in undefined behavior.

Specifically, glibc's pthread_cond_destroy waits on all the other listeners.
Since after forking all the threads are dead, the condition variable's
listeners will never wake up, so it will hang forever.

This commit changes it to only free the memory and none of the condition
variables.
  • Loading branch information
peterzhu2118 committed Jun 11, 2025
commit eb0f8b6bfe3176e97cacefe52749f2fbc45a6a8a
6 changes: 1 addition & 5 deletions thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -519,12 +519,8 @@ thread_cleanup_func(void *th_ptr, int atfork)
th->locking_mutex = Qfalse;
thread_cleanup_func_before_exec(th_ptr);

/*
* Unfortunately, we can't release native threading resource at fork
* because libc may have unstable locking state therefore touching
* a threading resource may cause a deadlock.
*/
if (atfork) {
native_thread_destroy_atfork(th->nt);
th->nt = NULL;
return;
}
Expand Down
6 changes: 6 additions & 0 deletions thread_none.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ ruby_mn_threads_params(void)
{
}

static void
native_thread_destroy_atfork(struct rb_native_thread *nt)
{
/* no-op */
}

static int
native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame)
{
Expand Down
25 changes: 22 additions & 3 deletions thread_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -1816,6 +1816,27 @@ native_thread_assign(struct rb_native_thread *nt, rb_thread_t *th)
th->nt = nt;
}

static void
native_thread_destroy_atfork(struct rb_native_thread *nt)
{
if (nt) {
/* We can't call rb_native_cond_destroy here because according to the
* specs of pthread_cond_destroy:
*
* Attempting to destroy a condition variable upon which other threads
* are currently blocked results in undefined behavior.
*
* Specifically, glibc's pthread_cond_destroy waits on all the other
* listeners. Since after forking all the threads are dead, the condition
* variable's listeners will never wake up, so it will hang forever.
*/

RB_ALTSTACK_FREE(nt->altstack);
ruby_xfree(nt->nt_context);
ruby_xfree(nt);
}
}

static void
native_thread_destroy(struct rb_native_thread *nt)
{
Expand All @@ -1826,9 +1847,7 @@ native_thread_destroy(struct rb_native_thread *nt)
rb_native_cond_destroy(&nt->cond.intr);
}

RB_ALTSTACK_FREE(nt->altstack);
ruby_xfree(nt->nt_context);
ruby_xfree(nt);
native_thread_destroy_atfork(nt);
}
}

Expand Down
6 changes: 6 additions & 0 deletions thread_win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,12 @@ native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame)
th->ec->machine.stack_maxsize = size - space;
}

static void
native_thread_destroy_atfork(struct rb_native_thread *nt)
{
/* no-op */
}

#ifndef InterlockedExchangePointer
#define InterlockedExchangePointer(t, v) \
(void *)InterlockedExchange((long *)(t), (long)(v))
Expand Down
Loading
0