Files
ruby/thread_pthread.h
Yusuke EndohandClaude Opus 5.5 053f8da910 Always use the no-inline rb_current_ec()
Under the M:N thread scheduler, a Ruby thread in a non-main Ractor may be
resumed on a different native thread after it blocks. The C compiler
assumes that the native thread never changes within a function, so it may
compute the address of ruby_current_ec once (`__tls_get_addr` in
libruby.so) and reuse it after the switch. The address then refers to the
TLS of the previous native thread, which leads to a SEGV or to using
another thread's ec.

    8.times.map do
      Ractor.new do
        50.times do
          ",,".split(/(,)/) { sleep 0.001 }
        end
        :ok
      end
    end.map(&:value)
    #=> [BUG] Segmentation fault at 0x0000000000000030 (with Clang 21)

arm64 and ppc64le already use the out-of-line rb_current_ec()
(RB_THREAD_CURRENT_EC_NOINLINE), which prevents the compiler from caching
the address. Use it on all platforms, and remove the macro.

[Bug #22453]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:27:26 +09:00

57 lines
1.5 KiB
C

#ifndef RUBY_THREAD_PTHREAD_H
#define RUBY_THREAD_PTHREAD_H
/**********************************************************************
thread_pthread.h -
$Author$
Copyright (C) 2004-2007 Koichi Sasada
This platform runs the common scheduler; see thread_sched.h for its data
structures and thread_sched.c for the implementation.
**********************************************************************/
#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif
#define RB_NATIVETHREAD_LOCK_INIT PTHREAD_MUTEX_INITIALIZER
#define RB_NATIVETHREAD_COND_INIT PTHREAD_COND_INITIALIZER
#include "thread_sched.h"
#ifdef RB_THREAD_LOCAL_SPECIFIER
NOINLINE(void rb_current_ec_set(struct rb_execution_context_struct *));
// Under M:N threads, a thread may move to another native thread in the middle of
// a C function, so the compiler must not cache the TLS address of ruby_current_ec.
NOINLINE(struct rb_execution_context_struct *rb_current_ec(void));
// for RUBY_DEBUG_LOG()
RUBY_EXTERN RB_THREAD_LOCAL_SPECIFIER rb_atomic_t ruby_nt_serial;
#define RUBY_NT_SERIAL 1
#else
typedef pthread_key_t native_tls_key_t;
static inline void *
native_tls_get(native_tls_key_t key)
{
// return value should be checked by caller
return pthread_getspecific(key);
}
static inline void
native_tls_set(native_tls_key_t key, void *ptr)
{
if (UNLIKELY(pthread_setspecific(key, ptr) != 0)) {
rb_bug("pthread_setspecific error");
}
}
RUBY_EXTERN native_tls_key_t ruby_current_ec_key;
#endif
#endif /* RUBY_THREAD_PTHREAD_H */