From b66b5008161bb9836d40729d0f070197c54fd5a3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 8 Dec 2025 13:09:30 +0900 Subject: [PATCH 1/4] Remove postponed job APIs deprecated for 2 years --- ext/-test-/postponed_job/postponed_job.c | 80 ------------------- include/ruby/debug.h | 53 ------------ .../-ext-/postponed_job/test_postponed_job.rb | 35 -------- vm_trace.c | 27 ------- 4 files changed, 195 deletions(-) diff --git a/ext/-test-/postponed_job/postponed_job.c b/ext/-test-/postponed_job/postponed_job.c index 9ac866ae7733ce..4426fc3104ccf5 100644 --- a/ext/-test-/postponed_job/postponed_job.c +++ b/ext/-test-/postponed_job/postponed_job.c @@ -35,38 +35,6 @@ pjob_callback(void *data) rb_ary_push(ary, INT2FIX(counter)); } -static VALUE -pjob_register(VALUE self, VALUE obj) -{ - counter = 0; - rb_postponed_job_register(0, pjob_callback, (void *)obj); - rb_gc_start(); - counter++; - rb_gc_start(); - counter++; - rb_gc_start(); - counter++; - return self; -} - -static void -pjob_one_callback(void *data) -{ - VALUE ary = (VALUE)data; - Check_Type(ary, T_ARRAY); - - rb_ary_push(ary, INT2FIX(1)); -} - -static VALUE -pjob_register_one(VALUE self, VALUE obj) -{ - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - return self; -} - static VALUE pjob_call_direct(VALUE self, VALUE obj) { @@ -83,48 +51,6 @@ pjob_call_direct(VALUE self, VALUE obj) static void pjob_noop_callback(void *data) { } -static VALUE -pjob_register_one_same(VALUE self) -{ - rb_gc_start(); - int r1 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); - int r2 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); - int r3 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); - VALUE ary = rb_ary_new(); - rb_ary_push(ary, INT2FIX(r1)); - rb_ary_push(ary, INT2FIX(r2)); - rb_ary_push(ary, INT2FIX(r3)); - return ary; -} - -#ifdef HAVE_PTHREAD_H -#include - -static void * -pjob_register_in_c_thread_i(void *obj) -{ - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - rb_postponed_job_register_one(0, pjob_one_callback, (void *)obj); - return NULL; -} - -static VALUE -pjob_register_in_c_thread(VALUE self, VALUE obj) -{ - pthread_t thread; - if (pthread_create(&thread, NULL, pjob_register_in_c_thread_i, (void *)obj)) { - return Qfalse; - } - - if (pthread_join(thread, NULL)) { - return Qfalse; - } - - return Qtrue; -} -#endif - static void pjob_preregistered_callback(void *data) { @@ -216,13 +142,7 @@ void Init_postponed_job(VALUE self) { VALUE mBug = rb_define_module("Bug"); - rb_define_module_function(mBug, "postponed_job_register", pjob_register, 1); - rb_define_module_function(mBug, "postponed_job_register_one", pjob_register_one, 1); rb_define_module_function(mBug, "postponed_job_call_direct", pjob_call_direct, 1); - rb_define_module_function(mBug, "postponed_job_register_one_same", pjob_register_one_same, 0); -#ifdef HAVE_PTHREAD_H - rb_define_module_function(mBug, "postponed_job_register_in_c_thread", pjob_register_in_c_thread, 1); -#endif rb_define_module_function(mBug, "postponed_job_preregister_and_call_with_sleep", pjob_preregister_and_call_with_sleep, 1); rb_define_module_function(mBug, "postponed_job_preregister_and_call_without_sleep", pjob_preregister_and_call_without_sleep, 1); rb_define_module_function(mBug, "postponed_job_preregister_multiple_times", pjob_preregister_multiple_times, 0); diff --git a/include/ruby/debug.h b/include/ruby/debug.h index 547d5d94c45f5b..27a7a5f2c12e54 100644 --- a/include/ruby/debug.h +++ b/include/ruby/debug.h @@ -754,59 +754,6 @@ rb_postponed_job_handle_t rb_postponed_job_preregister(unsigned int flags, rb_po */ void rb_postponed_job_trigger(rb_postponed_job_handle_t h); -/** - * Schedules the given `func` to be called with `data` when Ruby next checks for - * interrupts. If this function is called multiple times in between Ruby checking - * for interrupts, then `func` will be called only once with the `data` value from - * the first call to this function. - * - * Like `rb_postponed_job_trigger`, the context in which the job is called - * holds the GVL and can allocate Ruby objects. - * - * This method essentially has the same semantics as: - * - * ``` - * rb_postponed_job_trigger(rb_postponed_job_preregister(func, data)); - * ``` - * - * @note Previous versions of Ruby promised that the (`func`, `data`) pairs would - * be executed as many times as they were registered with this function; in - * reality this was always subject to race conditions and this function no - * longer provides this guarantee. Instead, multiple calls to this function - * can be coalesced into a single execution of the passed `func`, with the - * most recent `data` registered at that time passed in. - * - * @deprecated This interface implies that arbitrarily many `func`'s can be enqueued - * over the lifetime of the program, whilst in reality the registration - * slots for postponed jobs are a finite resource. This is made clearer - * by the `rb_postponed_job_preregister` and `rb_postponed_job_trigger` - * functions, and a future version of Ruby might delete this function. - * - * @param[in] flags Unused and ignored. - * @param[in] func Job body. - * @param[in,out] data Passed as-is to `func`. - * @retval 0 Postponed job registration table is full. Failed. - * @retval 1 Registration succeeded. - * @post The passed job will run on the next interrupt check. - */ - RBIMPL_ATTR_DEPRECATED(("use rb_postponed_job_preregister and rb_postponed_job_trigger")) -int rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data); - -/** - * Identical to `rb_postponed_job_register` - * - * @deprecated This is deprecated for the same reason as `rb_postponed_job_register` - * - * @param[in] flags Unused and ignored. - * @param[in] func Job body. - * @param[in,out] data Passed as-is to `func`. - * @retval 0 Postponed job registration table is full. Failed. - * @retval 1 Registration succeeded. - * @post The passed job will run on the next interrupt check. - */ - RBIMPL_ATTR_DEPRECATED(("use rb_postponed_job_preregister and rb_postponed_job_trigger")) -int rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data); - /** @} */ /** diff --git a/test/-ext-/postponed_job/test_postponed_job.rb b/test/-ext-/postponed_job/test_postponed_job.rb index 8c2b3e95d1cdad..01d6015de15be7 100644 --- a/test/-ext-/postponed_job/test_postponed_job.rb +++ b/test/-ext-/postponed_job/test_postponed_job.rb @@ -33,39 +33,4 @@ def test_multiple_preregistration_with_new_data assert_equal [3, 4], values RUBY end - - def test_legacy_register - assert_separately([], __FILE__, __LINE__, <<-'RUBY') - require '-test-/postponed_job' - direct, registered = [], [] - - Bug.postponed_job_call_direct(direct) - Bug.postponed_job_register(registered) - - assert_equal([0], direct) - assert_equal([3], registered) - - Bug.postponed_job_register_one(ary = []) - assert_equal [1], ary - RUBY - end - - def test_legacy_register_one_same - assert_separately([], __FILE__, __LINE__, <<-'RUBY') - require '-test-/postponed_job' - # Registering the same job three times should result in three of the same handle - handles = Bug.postponed_job_register_one_same - assert_equal [handles[0]], handles.uniq - RUBY - end - - if Bug.respond_to?(:postponed_job_register_in_c_thread) - def test_legacy_register_in_c_thread - assert_separately([], __FILE__, __LINE__, <<-'RUBY') - require '-test-/postponed_job' - assert Bug.postponed_job_register_in_c_thread(ary = []) - assert_equal [1], ary - RUBY - end - end end diff --git a/vm_trace.c b/vm_trace.c index 42b9991e7141bc..1a907da8809ec3 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -1921,33 +1921,6 @@ rb_postponed_job_trigger(rb_postponed_job_handle_t h) RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(get_valid_ec(GET_VM())); } - -static int -pjob_register_legacy_impl(unsigned int flags, rb_postponed_job_func_t func, void *data) -{ - /* We _know_ calling preregister from a signal handler like this is racy; what is - * and is not promised is very exhaustively documented in debug.h */ - rb_postponed_job_handle_t h = rb_postponed_job_preregister(0, func, data); - if (h == POSTPONED_JOB_HANDLE_INVALID) { - return 0; - } - rb_postponed_job_trigger(h); - return 1; -} - -int -rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data) -{ - return pjob_register_legacy_impl(flags, func, data); -} - -int -rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data) -{ - return pjob_register_legacy_impl(flags, func, data); -} - - void rb_postponed_job_flush(rb_vm_t *vm) { From d0ef5d137fa1d128a0fcb8564c6966177313ab92 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 12 Apr 2026 16:58:02 +0900 Subject: [PATCH 2/4] Update ruby-bench to update stackprof --- .github/workflows/ubuntu.yml | 1 + .github/workflows/zjit-macos.yml | 1 + .github/workflows/zjit-ubuntu.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index d67abc2cfac2b4..7b39746b377fb7 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -245,6 +245,7 @@ jobs: repository: ruby/ruby-bench persist-credentials: false path: ruby-bench + ref: 'a46e02b01a58ca74bb2ce055b4ea561cb372d1cd' # If you want to skip failing benchmark, consider using `--excludes`. # e.g. `bench_opts: '--warmup=1 --bench=1 --excludes=railsbench,lobsters'` diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 888a0ec8009a9f..3756d59108abd9 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -219,6 +219,7 @@ jobs: persist-credentials: false repository: ruby/ruby-bench path: ruby-bench + ref: 'a46e02b01a58ca74bb2ce055b4ea561cb372d1cd' # If you want to skip failing benchmark, consider using `--excludes`. # e.g. `bench_opts: '--warmup=1 --bench=1 --excludes=railsbench,lobsters'` diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 79d1b7bea4558e..9ed3fad6934824 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -273,6 +273,7 @@ jobs: repository: ruby/ruby-bench persist-credentials: false path: ruby-bench + ref: 'a46e02b01a58ca74bb2ce055b4ea561cb372d1cd' # If you want to skip failing benchmark, consider using `--excludes`. # e.g. `bench_opts: '--warmup=1 --bench=1 --excludes=railsbench,lobsters'` From 8e96d6f792ccc840b804a4fd443d0b0064b54752 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 11 Apr 2026 15:42:19 +0200 Subject: [PATCH 3/4] rb_gc_obj_optimal_size: don't enlarge small AR table hashes Ref: https://github.com/ruby/ruby/pull/16653 Frozen hashes backed by AR tables can be smaller than 160B. --- gc.c | 7 ++++++- hash.c | 16 ---------------- internal/hash.h | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/gc.c b/gc.c index 3cf3bdebce2626..8a0d051e8f2b41 100644 --- a/gc.c +++ b/gc.c @@ -3601,7 +3601,12 @@ rb_gc_obj_optimal_size(VALUE obj) } case T_HASH: - return sizeof(struct RHash) + (RHASH_ST_TABLE_P(obj) ? sizeof(st_table) : sizeof(ar_table)); + { + if (RB_OBJ_FROZEN(obj) && RHASH_AR_TABLE_P(obj)) { + return sizeof(struct RHash) + offsetof(ar_table, pairs) + RHASH_AR_TABLE_BOUND(obj) * sizeof(ar_table_pair); + } + return sizeof(struct RHash) + (RHASH_ST_TABLE_P(obj) ? sizeof(st_table) : sizeof(ar_table)); + } default: return 0; diff --git a/hash.c b/hash.c index 751d4564d5749e..4321f479fd3e24 100644 --- a/hash.c +++ b/hash.c @@ -471,24 +471,8 @@ ar_set_entry(VALUE hash, unsigned int index, st_data_t key, st_data_t val, st_ha #define RHASH_AR_TABLE_SIZE(h) (HASH_ASSERT(RHASH_AR_TABLE_P(h)), \ RHASH_AR_TABLE_SIZE_RAW(h)) -#define RHASH_AR_TABLE_BOUND_RAW(h) \ - ((unsigned int)((RBASIC(h)->flags >> RHASH_AR_TABLE_BOUND_SHIFT) & \ - (RHASH_AR_TABLE_BOUND_MASK >> RHASH_AR_TABLE_BOUND_SHIFT))) - -#define RHASH_ST_TABLE_SET(h, s) rb_hash_st_table_set(h, s) -#define RHASH_TYPE(hash) (RHASH_AR_TABLE_P(hash) ? &objhash : RHASH_ST_TABLE(hash)->type) - #define HASH_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(HASH_DEBUG, expr, #expr) -static inline unsigned int -RHASH_AR_TABLE_BOUND(VALUE h) -{ - HASH_ASSERT(RHASH_AR_TABLE_P(h)); - const unsigned int bound = RHASH_AR_TABLE_BOUND_RAW(h); - HASH_ASSERT(bound <= RHASH_AR_TABLE_MAX_SIZE); - return bound; -} - #if HASH_DEBUG #define hash_verify(hash) hash_verify_(hash, __FILE__, __LINE__) diff --git a/internal/hash.h b/internal/hash.h index 3a5be14ad76ef3..8c5511e82baa17 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -193,4 +193,20 @@ RHASH_AR_TABLE_SIZE_RAW(VALUE h) return (unsigned)ret; } +#define RHASH_AR_TABLE_BOUND_RAW(h) \ + ((unsigned int)((RBASIC(h)->flags >> RHASH_AR_TABLE_BOUND_SHIFT) & \ + (RHASH_AR_TABLE_BOUND_MASK >> RHASH_AR_TABLE_BOUND_SHIFT))) + +#define RHASH_ST_TABLE_SET(h, s) rb_hash_st_table_set(h, s) +#define RHASH_TYPE(hash) (RHASH_AR_TABLE_P(hash) ? &objhash : RHASH_ST_TABLE(hash)->type) + +static inline unsigned int +RHASH_AR_TABLE_BOUND(VALUE h) +{ + RUBY_ASSERT(RHASH_AR_TABLE_P(h)); + const unsigned int bound = RHASH_AR_TABLE_BOUND_RAW(h); + RUBY_ASSERT(bound <= RHASH_AR_TABLE_MAX_SIZE); + return bound; +} + #endif /* INTERNAL_HASH_H */ From 6593cc52d6153c5535968a8faff6cf4ac83a11fe Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 12 Apr 2026 15:13:03 +0200 Subject: [PATCH 4/4] iseq.c: rb_estimate_iv_count handle no superclass [Bug #21992] When redefining `BasicObject#initialize` there's no super class to access. --- iseq.c | 4 +++- test/ruby/test_super.rb | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/iseq.c b/iseq.c index 759c8be6900eae..5ba26dd53bfca1 100644 --- a/iseq.c +++ b/iseq.c @@ -3027,7 +3027,9 @@ rb_estimate_iv_count(VALUE klass, const rb_iseq_t * initialize_iseq) attr_index_t count = (attr_index_t)iv_names.num_entries; VALUE superclass = rb_class_superclass(klass); - count += RCLASS_MAX_IV_COUNT(superclass); + if (!NIL_P(superclass)) { // BasicObject doesn't have a superclass + count += RCLASS_MAX_IV_COUNT(superclass); + } set_free_embedded_table(&iv_names); diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb index 25bad2242af994..39594d74be38af 100644 --- a/test/ruby/test_super.rb +++ b/test/ruby/test_super.rb @@ -760,6 +760,20 @@ def initialize assert_equal 2, inherited.test # it may read index=1 while it should be index=2 end + def test_define_initialize_in_basic_object + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class ::BasicObject + alias_method :initialize, :initialize + def initialize + @bug = "[Bug #21992]" + end + end + + assert_not_nil Object.new + end; + end + def test_super_in_basic_object assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin;