From d9cc7621f39130c7c678af9eb72f7a28b539be9f Mon Sep 17 00:00:00 2001 From: git Date: Fri, 12 Dec 2025 06:55:08 +0000 Subject: [PATCH 1/8] Update bundled gems list as of 2025-12-12 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3e2d37c84cfda9..cd214a365b4597 100644 --- a/NEWS.md +++ b/NEWS.md @@ -275,7 +275,7 @@ The following bundled gems are added. The following bundled gems are updated. -* minitest 5.26.2 +* minitest 5.27.0 * power_assert 3.0.1 * rake 13.3.1 * test-unit 3.7.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index bdda3311d0eb07..d71137d3e11fab 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.26.2 https://github.com/minitest/minitest +minitest 5.27.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.3 https://github.com/test-unit/test-unit From 175a0d5f1ad545d5f722e959a766eeed52e29ca1 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 12 Dec 2025 09:35:55 +0100 Subject: [PATCH 2/8] [ruby/timeout] Restore original signal handler in test_timeout_in_trap_handler https://github.com/ruby/timeout/commit/4ae8631acf --- test/test_timeout.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 91940085c46ac3..fead81f571929d 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -427,9 +427,9 @@ def test_timeout_in_trap_handler rd, wr = IO.pipe - signal = Signal.list["USR1"] ? :USR1 : :TERM + signal = :TERM - trap(signal) do + original_handler = trap(signal) do begin Timeout.timeout(0.1) do sleep 1 @@ -444,9 +444,13 @@ def test_timeout_in_trap_handler end end - Process.kill signal, Process.pid + begin + Process.kill signal, Process.pid - assert_equal "OK", rd.read - rd.close + assert_equal "OK", rd.read + rd.close + ensure + trap(signal, original_handler) + end end end From 0022a87800f7355419b9bf2c1995b4c3dce832df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 02:08:25 +0000 Subject: [PATCH 3/8] Bump actions/cache in /.github/actions/setup/directories Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...a7833574556fa59680c1b7cb190c1735db73ebf0) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 916e8b5acd0c4c..21cc817cba81cb 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -100,7 +100,7 @@ runs: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: ${{ inputs.srcdir }}/.downloaded-cache key: ${{ runner.os }}-${{ runner.arch }}-downloaded-cache From cf97a14c78cf18bc98ff49b6d4b6bd337daeab60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 02:03:43 +0000 Subject: [PATCH 4/8] Bump actions/cache from 4.3.0 to 5.0.0 Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...a7833574556fa59680c1b7cb190c1735db73ebf0) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 67ea1cacae2cf1..1230af953e03c0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -88,7 +88,7 @@ jobs: - name: Restore vcpkg artifact id: restore-vcpkg - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} @@ -100,7 +100,7 @@ jobs: if: ${{ ! steps.restore-vcpkg.outputs.cache-hit }} - name: Save vcpkg artifact - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} From 7e7a1db579dad504a26e42d5f3efa5b2968389af Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 12 Dec 2025 08:55:45 +0100 Subject: [PATCH 5/8] Define Thread::ConditionVariable in thread_sync.rb It's more consistent with Mutex, but also the `#wait` method benefits from receiving the execution context instead of having to call `GET_EC`. --- thread_sync.c | 75 ++++++++++++-------------------------------------- thread_sync.rb | 44 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index 9942d08e0a5c8f..e54963a4fe3178 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -1560,21 +1560,8 @@ condvar_alloc(VALUE klass) return obj; } -/* - * Document-method: ConditionVariable::new - * - * Creates a new condition variable instance. - */ - -static VALUE -rb_condvar_initialize(VALUE self) -{ - struct rb_condvar *cv = condvar_ptr(self); - ccan_list_head_init(&cv->waitq); - return self; -} - struct sleep_call { + rb_execution_context_t *ec; VALUE mutex; VALUE timeout; }; @@ -1585,65 +1572,44 @@ static VALUE do_sleep(VALUE args) { struct sleep_call *p = (struct sleep_call *)args; - return rb_funcallv(p->mutex, id_sleep, 1, &p->timeout); + if (CLASS_OF(p->mutex) == rb_cMutex) { + return rb_mut_sleep(p->ec, p->mutex, p->timeout); + } + else { + return rb_funcallv(p->mutex, id_sleep, 1, &p->timeout); + } } -/* - * Document-method: Thread::ConditionVariable#wait - * call-seq: wait(mutex, timeout=nil) - * - * Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup. - * - * If +timeout+ is given, this method returns after +timeout+ seconds passed, - * even if no other thread doesn't signal. - * - * This method may wake up spuriously due to underlying implementation details. - * - * Returns the slept result on +mutex+. - */ - static VALUE -rb_condvar_wait(int argc, VALUE *argv, VALUE self) +rb_condvar_wait(rb_execution_context_t *ec, VALUE self, VALUE mutex, VALUE timeout) { - rb_execution_context_t *ec = GET_EC(); - struct rb_condvar *cv = condvar_ptr(self); - struct sleep_call args; - - rb_scan_args(argc, argv, "11", &args.mutex, &args.timeout); + struct sleep_call args = { + .ec = ec, + .mutex = mutex, + .timeout = timeout, + }; struct sync_waiter sync_waiter = { - .self = args.mutex, + .self = mutex, .th = ec->thread_ptr, .fiber = nonblocking_fiber(ec->fiber_ptr) }; ccan_list_add_tail(&cv->waitq, &sync_waiter.node); - return rb_ensure(do_sleep, (VALUE)&args, delete_from_waitq, (VALUE)&sync_waiter); + return rb_ec_ensure(ec, do_sleep, (VALUE)&args, delete_from_waitq, (VALUE)&sync_waiter); } -/* - * Document-method: Thread::ConditionVariable#signal - * - * Wakes up the first thread in line waiting for this lock. - */ - static VALUE -rb_condvar_signal(VALUE self) +rb_condvar_signal(rb_execution_context_t *ec, VALUE self) { struct rb_condvar *cv = condvar_ptr(self); wakeup_one(&cv->waitq); return self; } -/* - * Document-method: Thread::ConditionVariable#broadcast - * - * Wakes up all threads waiting for this lock. - */ - static VALUE -rb_condvar_broadcast(VALUE self) +rb_condvar_broadcast(rb_execution_context_t *ec, VALUE self) { struct rb_condvar *cv = condvar_ptr(self); wakeup_all(&cv->waitq); @@ -1726,13 +1692,6 @@ Init_thread_sync(void) id_sleep = rb_intern("sleep"); - rb_define_method(rb_cConditionVariable, "initialize", rb_condvar_initialize, 0); - rb_undef_method(rb_cConditionVariable, "initialize_copy"); - rb_define_method(rb_cConditionVariable, "marshal_dump", undumpable, 0); - rb_define_method(rb_cConditionVariable, "wait", rb_condvar_wait, -1); - rb_define_method(rb_cConditionVariable, "signal", rb_condvar_signal, 0); - rb_define_method(rb_cConditionVariable, "broadcast", rb_condvar_broadcast, 0); - rb_provide("thread.rb"); } diff --git a/thread_sync.rb b/thread_sync.rb index 28c70b1e9ce8fb..a722b7ec1a2595 100644 --- a/thread_sync.rb +++ b/thread_sync.rb @@ -148,4 +148,48 @@ def sleep(timeout = nil) Primitive.rb_mut_sleep(timeout) end end + + class ConditionVariable + # Document-method: ConditionVariable::new + # + # Creates a new condition variable instance. + def initialize + end + + undef_method :initialize_copy + + # :nodoc: + def marshal_dump + raise TypeError, "can't dump #{self.class}" + end + + # Document-method: Thread::ConditionVariable#signal + # + # Wakes up the first thread in line waiting for this lock. + def signal + Primitive.rb_condvar_signal + end + + # Document-method: Thread::ConditionVariable#broadcast + # + # Wakes up all threads waiting for this lock. + def broadcast + Primitive.rb_condvar_broadcast + end + + # Document-method: Thread::ConditionVariable#wait + # call-seq: wait(mutex, timeout=nil) + # + # Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup. + # + # If +timeout+ is given, this method returns after +timeout+ seconds passed, + # even if no other thread doesn't signal. + # + # This method may wake up spuriously due to underlying implementation details. + # + # Returns the slept result on +mutex+. + def wait(mutex, timeout=nil) + Primitive.rb_condvar_wait(mutex, timeout) + end + end end From ff831eb0572b2d8f794acca478ea77c7bfefbc61 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 12 Dec 2025 09:10:04 +0100 Subject: [PATCH 6/8] thead_sync.c: directly pass the execution context to yield Saves one more call to GET_EC() --- internal/vm.h | 1 + thread_sync.c | 9 +++++++-- vm_eval.c | 11 +++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/vm.h b/internal/vm.h index 7fae590d198fdd..09dfaf182e9a92 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -69,6 +69,7 @@ const char *rb_type_str(enum ruby_value_type type); VALUE rb_check_funcall_default(VALUE, ID, int, const VALUE *, VALUE); VALUE rb_check_funcall_basic_kw(VALUE, ID, VALUE, int, const VALUE*, int); VALUE rb_yield_1(VALUE val); +VALUE rb_ec_yield(struct rb_execution_context_struct *ec, VALUE val); VALUE rb_yield_force_blockarg(VALUE values); VALUE rb_lambda_call(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t bl_proc, int min_argc, int max_argc, diff --git a/thread_sync.c b/thread_sync.c index e54963a4fe3178..6af6aaddd6241d 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -650,7 +650,6 @@ rb_mutex_sleep(VALUE self, VALUE timeout) return rb_mut_sleep(GET_EC(), self, timeout); } - VALUE rb_mutex_synchronize(VALUE self, VALUE (*func)(VALUE arg), VALUE arg) { @@ -660,6 +659,12 @@ rb_mutex_synchronize(VALUE self, VALUE (*func)(VALUE arg), VALUE arg) return rb_ec_ensure(args.ec, func, arg, do_mutex_unlock_safe, (VALUE)&args); } +static VALUE +do_ec_yield(VALUE _ec) +{ + return rb_ec_yield((rb_execution_context_t *)_ec, Qundef); +} + VALUE rb_mut_synchronize(rb_execution_context_t *ec, VALUE self) { @@ -669,7 +674,7 @@ rb_mut_synchronize(rb_execution_context_t *ec, VALUE self) .ec = ec, }; do_mutex_lock(&args, 1); - return rb_ec_ensure(args.ec, rb_yield, Qundef, do_mutex_unlock_safe, (VALUE)&args); + return rb_ec_ensure(args.ec, do_ec_yield, (VALUE)ec, do_mutex_unlock_safe, (VALUE)&args); } void diff --git a/vm_eval.c b/vm_eval.c index 12bdabc3302f68..34560d704a15a5 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1379,6 +1379,17 @@ rb_yield(VALUE val) } } +VALUE +rb_ec_yield(rb_execution_context_t *ec, VALUE val) +{ + if (UNDEF_P(val)) { + return vm_yield(ec, 0, NULL, RB_NO_KEYWORDS); + } + else { + return vm_yield(ec, 1, &val, RB_NO_KEYWORDS); + } +} + #undef rb_yield_values VALUE rb_yield_values(int n, ...) From e2fe0aae43fee4815c1fc0896a2f03de35bfd873 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 12 Dec 2025 22:12:41 +1300 Subject: [PATCH 7/8] Avoid race condition in `test_without_handle_interrupt_signal_works`. (#15504) --- ext/-test-/scheduler/scheduler.c | 13 ++++++++----- .../scheduler/test_interrupt_with_scheduler.rb | 9 +++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ext/-test-/scheduler/scheduler.c b/ext/-test-/scheduler/scheduler.c index f3badceb92fcc6..f8384f597e35bb 100644 --- a/ext/-test-/scheduler/scheduler.c +++ b/ext/-test-/scheduler/scheduler.c @@ -1,5 +1,6 @@ #include "ruby/ruby.h" #include "ruby/thread.h" +#include "ruby/io.h" #include "ruby/fiber/scheduler.h" /* @@ -24,6 +25,7 @@ */ struct blocking_state { + int notify_descriptor; volatile int interrupted; }; @@ -39,14 +41,14 @@ blocking_operation(void *argument) { struct blocking_state *blocking_state = (struct blocking_state *)argument; - while (true) { - struct timeval tv = {1, 0}; // 1 second timeout. + write(blocking_state->notify_descriptor, "x", 1); + while (!blocking_state->interrupted) { + struct timeval tv = {1, 0}; // 1 second timeout. int result = select(0, NULL, NULL, NULL, &tv); if (result == -1 && errno == EINTR) { blocking_state->interrupted = 1; - return NULL; } // Otherwise, timeout -> loop again. @@ -56,9 +58,10 @@ blocking_operation(void *argument) } static VALUE -scheduler_blocking_loop(VALUE self) +scheduler_blocking_loop(VALUE self, VALUE notify) { struct blocking_state blocking_state = { + .notify_descriptor = rb_io_descriptor(notify), .interrupted = 0, }; @@ -84,5 +87,5 @@ Init_scheduler(void) VALUE mBug = rb_define_module("Bug"); VALUE mScheduler = rb_define_module_under(mBug, "Scheduler"); - rb_define_module_function(mScheduler, "blocking_loop", scheduler_blocking_loop, 0); + rb_define_module_function(mScheduler, "blocking_loop", scheduler_blocking_loop, 1); } diff --git a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb index 42a109b98b51a6..eb7a0647e581b9 100644 --- a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb +++ b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb @@ -26,23 +26,20 @@ def test_without_handle_interrupt_signal_works # Yield to the scheduler: sleep(0) - output.puts "ready" - Bug::Scheduler.blocking_loop + Bug::Scheduler.blocking_loop(output) end end output.close - assert_equal "ready\n", input.gets + assert_equal "x", input.read(1) - sleep 0.1 # Ensure the child is in the blocking loop - # $stderr.puts "Sending interrupt" Process.kill(:INT, pid) reaper = Thread.new do Process.waitpid2(pid) end - unless reaper.join(1) + unless reaper.join(10) Process.kill(:KILL, pid) end From d428d086c23219090d68eb2d027498c6ea999b89 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Fri, 12 Dec 2025 18:14:50 +0900 Subject: [PATCH 8/8] Simplify the code `thread_sched_to_waiting_common0` is no longer needed. --- thread_pthread.c | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/thread_pthread.c b/thread_pthread.c index 0a19f7f0310af1..66323598607398 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -972,33 +972,16 @@ thread_sched_wakeup_next_thread(struct rb_thread_sched *sched, rb_thread_t *th, } } -// running -> waiting -// -// to_dead: false -// th will run dedicated task. -// run another ready thread. -// to_dead: true -// th will be dead. -// run another ready thread. +// running -> dead (locked) static void -thread_sched_to_waiting_common0(struct rb_thread_sched *sched, rb_thread_t *th, bool to_dead) +thread_sched_to_dead_common(struct rb_thread_sched *sched, rb_thread_t *th) { - RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); - - if (!to_dead) native_thread_dedicated_inc(th->vm, th->ractor, th->nt); + RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated); - RUBY_DEBUG_LOG("%sth:%u", to_dead ? "to_dead " : "", rb_th_serial(th)); + RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); - bool can_switch = to_dead ? !th_has_dedicated_nt(th) : false; - thread_sched_wakeup_next_thread(sched, th, can_switch); -} + thread_sched_wakeup_next_thread(sched, th, !th_has_dedicated_nt(th)); -// running -> dead (locked) -static void -thread_sched_to_dead_common(struct rb_thread_sched *sched, rb_thread_t *th) -{ - RUBY_DEBUG_LOG("dedicated:%d", th->nt->dedicated); - thread_sched_to_waiting_common0(sched, th, true); RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_EXITED, th); } @@ -1019,8 +1002,12 @@ thread_sched_to_dead(struct rb_thread_sched *sched, rb_thread_t *th) static void thread_sched_to_waiting_common(struct rb_thread_sched *sched, rb_thread_t *th) { - RUBY_DEBUG_LOG("dedicated:%d", th->nt->dedicated); - thread_sched_to_waiting_common0(sched, th, false); + RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated); + + RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); + + native_thread_dedicated_inc(th->vm, th->ractor, th->nt); + thread_sched_wakeup_next_thread(sched, th, false); } // running -> waiting