Skip to content

[PROF-15412] Import Ruby 4.0.6 headers and bump gem version to 3.5.3#28

Merged
ivoanjo merged 2 commits into
masterfrom
ivoanjo/prof-15412-ruby-4_0_6-support
Jul 15, 2026
Merged

[PROF-15412] Import Ruby 4.0.6 headers and bump gem version to 3.5.3#28
ivoanjo merged 2 commits into
masterfrom
ivoanjo/prof-15412-ruby-4_0_6-support

Conversation

@ivoanjo

@ivoanjo ivoanjo commented Jul 15, 2026

Copy link
Copy Markdown
Member

What does this PR do?

This PR imports the Ruby 4.0.6 headers and bumps the gem version from 3.5.2 to 3.5.3 so we can make a new release.

Motivation:

The Ruby 4.0.6 headers are almost the same as the 4.0.0 headers (see below), yet the change to add is_ready in rb_thread_sched_item shifted the in-memory layouts of things inside rb_thread_struct which broke the profiler, see DataDog/dd-trace-rb#6045 .

Importing the new headers fixes the issue.

Additional Notes:

Here's the full diff between the 4.0.0 and 4.0.6 headers.

$ diff -uw ruby-4.0.0-p0/ ruby-4.0.6-p0/
Common subdirectories: ruby-4.0.0-p0/ccan and ruby-4.0.6-p0/ccan
diff -uw ruby-4.0.0-p0/id_table.h ruby-4.0.6-p0/id_table.h
--- ruby-4.0.0-p0/id_table.h    2026-01-07 15:43:22.492338852 +0000
+++ ruby-4.0.6-p0/id_table.h    2026-07-15 09:07:09.200855897 +0100
@@ -47,6 +47,16 @@

 extern const rb_data_type_t rb_managed_id_table_type;

+VALUE rb_marked_id_table_new(size_t capa);
+int rb_marked_id_table_insert(VALUE table, ID id, VALUE val);
+VALUE rb_marked_id_table_dup(VALUE table);
+
+// alisases
+#define rb_marked_id_table_size             rb_managed_id_table_size
+#define rb_marked_id_table_lookup           rb_managed_id_table_lookup
+#define rb_marked_id_table_foreach          rb_managed_id_table_foreach
+#define rb_marked_id_table_foreach_values   rb_managed_id_table_foreach_values
+
 RUBY_SYMBOL_EXPORT_BEGIN
 size_t rb_id_table_size(const struct rb_id_table *tbl);
 RUBY_SYMBOL_EXPORT_END
Common subdirectories: ruby-4.0.0-p0/internal and ruby-4.0.6-p0/internal
Common subdirectories: ruby-4.0.0-p0/prism and ruby-4.0.6-p0/prism
diff -uw ruby-4.0.0-p0/thread_pthread.h ruby-4.0.6-p0/thread_pthread.h
--- ruby-4.0.0-p0/thread_pthread.h      2026-01-07 15:43:22.497873080 +0000
+++ ruby-4.0.6-p0/thread_pthread.h      2026-07-15 09:07:09.202007892 +0100
@@ -56,6 +56,9 @@
         // connected to ractor->threads.sched.reqdyq
         // locked by ractor->threads.sched.lock
         struct ccan_list_node readyq;
+        // Indicates whether thread is on the readyq.
+        // There is no clear relationship between this and th->status.
+        bool is_ready;

         // connected to vm->ractor.sched.timeslice_threads
         // locked by vm->ractor.sched.lock
@@ -127,6 +130,11 @@
     struct rb_thread_struct *lock_owner;
 #endif
     struct rb_thread_struct *running; // running thread or NULL
+    // Most recently running thread or NULL. If this thread wakes up before the newly running
+    // thread completes the transfer of control, it can interrupt and resume running.
+    // The new thread clears this field when it takes control.
+    struct rb_thread_struct *runnable_hot_th;
+    int runnable_hot_th_waiting;
     bool is_running;
     bool is_running_timeslice;
     bool enable_mn_threads;
diff -uw ruby-4.0.0-p0/vm_core.h ruby-4.0.6-p0/vm_core.h
--- ruby-4.0.0-p0/vm_core.h     2026-01-07 15:43:22.497873080 +0000
+++ ruby-4.0.6-p0/vm_core.h     2026-07-15 09:07:09.202386196 +0100
@@ -763,6 +763,7 @@
     const VALUE special_exceptions[ruby_special_error_count];

     /* Ruby Box */
+    rb_box_t *master_box;
     rb_box_t *root_box;
     rb_box_t *main_box;

@@ -2233,6 +2234,7 @@
 int rb_signal_exec(rb_thread_t *th, int sig);
 void rb_threadptr_check_signal(rb_thread_t *mth);
 void rb_threadptr_signal_raise(rb_thread_t *th, int sig);
+void rb_threadptr_interrupt_raise(rb_thread_t *th);
 void rb_threadptr_signal_exit(rb_thread_t *th);
 int rb_threadptr_execute_interrupts(rb_thread_t *, int);
 void rb_threadptr_interrupt(rb_thread_t *th);

How to test the change?

I've locally tested this change by building the .gem, and then trying it with both Ruby 4.0.5 (picks up the 4.0.0 headers correctly) and Ruby 4.0.6 (picks up the new headers).

With this version, the profiler specs are back to green.

ivoanjo added 2 commits July 15, 2026 09:27
These are almost the same as the 4.0.0 headers, yet the change to add
`is_ready` in `rb_thread_sched_item` shifted the in-memory layouts of
things inside `rb_thread_struct` which broke the profiler.

```diff
$ diff -uw ruby-4.0.0-p0/ ruby-4.0.6-p0/
Common subdirectories: ruby-4.0.0-p0/ccan and ruby-4.0.6-p0/ccan
diff -uw ruby-4.0.0-p0/id_table.h ruby-4.0.6-p0/id_table.h
--- ruby-4.0.0-p0/id_table.h	2026-01-07 15:43:22.492338852 +0000
+++ ruby-4.0.6-p0/id_table.h	2026-07-15 09:07:09.200855897 +0100
@@ -47,6 +47,16 @@

 extern const rb_data_type_t rb_managed_id_table_type;

+VALUE rb_marked_id_table_new(size_t capa);
+int rb_marked_id_table_insert(VALUE table, ID id, VALUE val);
+VALUE rb_marked_id_table_dup(VALUE table);
+
+// alisases
+#define rb_marked_id_table_size             rb_managed_id_table_size
+#define rb_marked_id_table_lookup           rb_managed_id_table_lookup
+#define rb_marked_id_table_foreach          rb_managed_id_table_foreach
+#define rb_marked_id_table_foreach_values   rb_managed_id_table_foreach_values
+
 RUBY_SYMBOL_EXPORT_BEGIN
 size_t rb_id_table_size(const struct rb_id_table *tbl);
 RUBY_SYMBOL_EXPORT_END
Common subdirectories: ruby-4.0.0-p0/internal and ruby-4.0.6-p0/internal
Common subdirectories: ruby-4.0.0-p0/prism and ruby-4.0.6-p0/prism
diff -uw ruby-4.0.0-p0/thread_pthread.h ruby-4.0.6-p0/thread_pthread.h
--- ruby-4.0.0-p0/thread_pthread.h	2026-01-07 15:43:22.497873080 +0000
+++ ruby-4.0.6-p0/thread_pthread.h	2026-07-15 09:07:09.202007892 +0100
@@ -56,6 +56,9 @@
         // connected to ractor->threads.sched.reqdyq
         // locked by ractor->threads.sched.lock
         struct ccan_list_node readyq;
+        // Indicates whether thread is on the readyq.
+        // There is no clear relationship between this and th->status.
+        bool is_ready;

         // connected to vm->ractor.sched.timeslice_threads
         // locked by vm->ractor.sched.lock
@@ -127,6 +130,11 @@
     struct rb_thread_struct *lock_owner;
 #endif
     struct rb_thread_struct *running; // running thread or NULL
+    // Most recently running thread or NULL. If this thread wakes up before the newly running
+    // thread completes the transfer of control, it can interrupt and resume running.
+    // The new thread clears this field when it takes control.
+    struct rb_thread_struct *runnable_hot_th;
+    int runnable_hot_th_waiting;
     bool is_running;
     bool is_running_timeslice;
     bool enable_mn_threads;
diff -uw ruby-4.0.0-p0/vm_core.h ruby-4.0.6-p0/vm_core.h
--- ruby-4.0.0-p0/vm_core.h	2026-01-07 15:43:22.497873080 +0000
+++ ruby-4.0.6-p0/vm_core.h	2026-07-15 09:07:09.202386196 +0100
@@ -763,6 +763,7 @@
     const VALUE special_exceptions[ruby_special_error_count];

     /* Ruby Box */
+    rb_box_t *master_box;
     rb_box_t *root_box;
     rb_box_t *main_box;

@@ -2233,6 +2234,7 @@
 int rb_signal_exec(rb_thread_t *th, int sig);
 void rb_threadptr_check_signal(rb_thread_t *mth);
 void rb_threadptr_signal_raise(rb_thread_t *th, int sig);
+void rb_threadptr_interrupt_raise(rb_thread_t *th);
 void rb_threadptr_signal_exit(rb_thread_t *th);
 int rb_threadptr_execute_interrupts(rb_thread_t *, int);
 void rb_threadptr_interrupt(rb_thread_t *th);
```

@TonyCTHsu TonyCTHsu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivoanjo

ivoanjo commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

btw, the doc is outdated. Perhaps use this pull request to update

https://github.com/DataDog/datadog-ruby_core_source/blob/master/README.md?plain=1#L7

You mean the "3.3+" part? I kinda read that as >= 3.3, e.g. anything after that Ruby, including Ruby 4.

(I don't mind updating it if you still find it confusing, just clarifying what you're pointing out as wrong)

@eregon

eregon commented Jul 15, 2026

Copy link
Copy Markdown
Member

So for curiosity it's because of ruby/ruby@21a2595
and that doesn't really seem a bug but more of a performance improvement so shouldn't have been backported generally-speaking,
except it was explicitly asked because of a perf regression: https://bugs.ruby-lang.org/issues/21685#note-16.

@TonyCTHsu

Copy link
Copy Markdown
Contributor

You mean the "3.3+" part? I kinda read that as >= 3.3, e.g. anything after that Ruby, including Ruby 4.

You are absolutely right! I am blind!

@ivoanjo

ivoanjo commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

So for curiosity it's because of ruby/ruby@21a2595
and that doesn't really seem a bug but more of a performance improvement so shouldn't have been backported generally-speaking,
except it was explicitly asked because of a perf regression: https://bugs.ruby-lang.org/issues/21685#note-16.

Unfortunately this is the downside of depending on some internal APIs -- I guess this is our regular reminder that it's probably a good idea to ask for public APIs to access some of this information ;)

Personally I think that backport is worth it, even if it's a bit outside of the usual norm :D

You mean the "3.3+" part? I kinda read that as >= 3.3, e.g. anything after that Ruby, including Ruby 4.

You are absolutely right! I am blind!

👀 👀 👀

Ack, I'll leave it as-is.

@ivoanjo
ivoanjo merged commit b327ccb into master Jul 15, 2026
37 checks passed
@ivoanjo
ivoanjo deleted the ivoanjo/prof-15412-ruby-4_0_6-support branch July 15, 2026 09:01
@eregon

eregon commented Jul 15, 2026

Copy link
Copy Markdown
Member

Just to double-check:

@ivoanjo

ivoanjo commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

Is adding the headers in that folder enough? How are they selected?

Yes. The logic lives in https://github.com/DataDog/datadog-ruby_core_source/blob/master/lib/datadog/ruby_core_source.rb#L13 .

You can see the logic working when you run bundle exec rake clean compile on dd-trace-rb:

with 4.0.5

creating extconf.h
checking for vm_core.h... no
checking for vm_core.h... no
**************************************************************************
No source for ruby-4.0.5-p0 (revision 64336ffd0ee9e1f4c05891695a3d7b49cb709721) provided with
datadog-ruby_core_source gem. Falling back to ruby-4.0.0-p0.
**************************************************************************
Using datadog-ruby_core_source headers from /home/ivo.anjo/.rvm/gems/ruby-4.0.5/gems/datadog-ruby_core_source-3.5.3/lib/datadog/ruby_core_source/ruby-4.0.0-p0
checking for vm_core.h... yes
checking for iseq.h... yes
checking for ractor_core.h... yes
checking for whether -Wunused-parameter is accepted as CFLAGS... no
creating Makefile

with 4.0.6:

Using datadog-ruby_core_source headers from /home/ivo.anjo/.rvm/gems/ruby-4.0.6/gems/datadog-ruby_core_source-3.5.3/lib/datadog/ruby_core_source/ruby-4.0.6-p0
checking for vm_core.h... yes
checking for iseq.h... yes

I supposed you followed https://github.com/DataDog/datadog-ruby_core_source#usage (rake add_source, find_includes.rb)?

Yes, exactly.

If you run those two steps, the result is the same output as this PR.

@ivoanjo

ivoanjo commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

Quick update:

hayat01sh1da pushed a commit to hayat01sh1da/dd-trace-rb that referenced this pull request Jul 15, 2026
**What does this PR do?**

This PR bumps the minimum version of the datadog-ruby_core_source gem to
the latest version (3.5.3), having the usual diff (e.g.
DataDog#5278 was the previous
update).

**Motivation:**

Doing this bump forces updates to the datadog gem to also pull in the
latest version of the datadog-ruby_core_source gem. This ensures users
have the best experience and don't e.g. run into issues because somehow
they're running the latest version of the gem with an outdated version
of the headers.

Specifically version 3.5.3 adds support for Ruby 4.0.6, fixing
DataDog#6045 .

**Additional Notes:**

N/A

**How to test the change?**

This will fix the "test-asan" check failing in master (e.g.
https://github.com/DataDog/dd-trace-rb/actions/runs/29363868729 )
because test-asan is running on 4.0.6, so green CI is good.

Also, I've locally tested with both Ruby 4.0.5 and 4.0.6 (see
DataDog/datadog-ruby_core_source#28 ).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants