Skip to content

GVL Instrumentation: pass thread_id as part of event data #6189

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

Closed
wants to merge 1 commit into from

Conversation

casperisfine
Copy link
Contributor

Context: ivoanjo/gvl-tracing#4

Some hooks may want to collect data on a per thread basis. Right now the only way to identified the concerned thread is to
use rb_nativethread_self() or similar, but even then because of the thread cache, two distinct Ruby thread may report the same native thread id.

This patch pass a thread_id as part of the event_data struct. It's a serial id generated when the thread is created.

cc @ivoanjo

@ko1 what do you think? Would this be acceptable? There is an atomic operation an overhead even if the API is not in use, but that's on thread creation, I assume it's minor compared to the overall thread creation cost?

Some hooks may want to collect data on a per thread basis.
Right now the only way to identified the concerned thread is to
use `rb_nativethread_self()` or similar, but even then because of
the thread cache, two distinct Ruby thread may report the same native
thread id.

This patch pass a `thread_id` as part of the `event_data` struct.
It's a serial id generated when the thread is created.
@tenderlove
Copy link
Member

+1, I think we'll need this when MaNy is merged. See https://bugs.ruby-lang.org/issues/19842

@tenderlove
Copy link
Member

btw, I would like a C API to fetch the thread id from the thread. I am working on a profiling API where you pass the thread (VALUE, not rb_thread_t since it is not public). I'd like to use the thread id passed to the events to look up the Ruby Thread, then pass that thread to the profiling API.

@ko1
Copy link
Contributor

ko1 commented Aug 30, 2023

Sorry for late response.

I understand the motivation so I'm happy to accept this feature, but this patch conflicts with my MN scheduler patch so please wait.

other concerns:

  • th->serial is not ID because it can be overflow (32bit unsigned int). It means that there are two or more same ID threads at a morment.
  • Also th->serial isn't counted at default. Only on USE_RUBY_DEBUG_LOG=1 (it is introduced for the debug logging). It is easy to see the log with small integers (1, 2, 3, ...). It is hard to see the log with 0x12345678, ...

So we need to clear the specification.

They are clear:

  • ID should be unique in some moments.
  • ID should should be recycled like object id.

They are not clear:

  • ID should be 32 bit integer
  • ID should be started from small integers like 0, 1, ...
  • ID should be unique in a Process
    • Thread ID should be unique between Ractors.
    • Thread ID should be unique only in Ractors (in this case, we don't need to use atomic)

My proposal:

  • ID is size_t size (== sizeof(void *))
  • return rb_thread_t *
    • it is unique in some moments between Ractors.

But I can missed the requests.

@casperisfine
Copy link
Contributor Author

  • ID should be unique in some moments.

Yes.

  • ID should should be recycled like object id.

I presume you mean, shouldn't? It probably isn't a a big deal if in rare case the ID is re-used. But ideally it wouldn't.

  • ID should be 32 bit integer

64 bit is better indeed.

  • ID should be started from small integers like 0, 1, ...

It's not strictly necessary, but in profiler context that makes for easier to read IDs, but profilers can attribute serial ids based on that later if it wants to.

  • ID should be unique in a Process

Yes.

  • Thread ID should be unique between Ractors.
  • Thread ID should be unique only in Ractors (in this case, we don't need to use atomic)

We can make them non-unique across ractors, but in this case we'll also need to pass a "ractor id".

To make it more clear, the goal is to render graphs such as this one or to display each thread in Firefox profiler (example with a single thread: https://share.firefox.dev/3DhLsFa)

@tenderlove
Copy link
Member

My proposal:

  • ID is size_t size (== sizeof(void *))

  • return rb_thread_t *

    • it is unique in some moments between Ractors.

I think this would work if we pass the rb_thread_t * or ID to the thread lifecycle hooks. Profilers can keep track of what thread / ID is alive.

ID should be unique in a Process

  • Thread ID should be unique between Ractors.

I assume rb_thread_t * must be unique between Ractors. Could we pass the rb_thread_t * to lifecycle hooks, then we don't need the ID?

Also, will Ractors fire RB_INTERNAL_THREAD_HOOK in parallel? IOW, do callbacks passed to rb_internal_thread_add_event_hook need to account for parallel execution? I think it's OK if the answer is "yes", but maybe we need documentation? 😅

@casperisfine
Copy link
Contributor Author

IOW, do callbacks passed to rb_internal_thread_add_event_hook need to account for parallel execution?

They already do. RUBY_INTERNAL_THREAD_EVENT_RESUMED is triggered with the GVL held, but the others are triggered without the GVL held.

maybe we need documentation?

Yes absolutely.

@tenderlove
Copy link
Member

IOW, do callbacks passed to rb_internal_thread_add_event_hook need to account for parallel execution?

They already do. RUBY_INTERNAL_THREAD_EVENT_RESUMED is triggered with the GVL held, but the others are triggered without the GVL held.

Sorry, I mean in the MaNy world. If a Ractor is independently allowed to make a thread at any time (in parallel with other Ractors), then the GVL doesn't help anything as the GVL should be local to that particular Ractor (the way I understand the MaNy proposal is that the GVL becomes a Ractor level lock, rather than Global). The callback passed to rb_internal_thread_add_event_hook would have to have it's own locking mechanism as it could be called from any Ractor at any time.

@casperisfine
Copy link
Contributor Author

the way I understand the MaNy proposal is that the GVL becomes a Ractor level lock, rather than Global

From my understanding, it's already the case today. But perhaps we should jut have a chat because I think I'm still unclear about what you mean.

@ivoanjo
Copy link
Contributor

ivoanjo commented Sep 7, 2023

If I may "butt in" ;) yeah there's already a Ractor-level GVL, as can be seen in these examples from using the GVL instrumentation API.

@casperisfine
Copy link
Contributor Author

Closing in favor of #8885

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants