-
Notifications
You must be signed in to change notification settings - Fork 14
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
[wip] Allow callbacks to be registered for GVL related events #119
base: master
Are you sure you want to change the base?
Changes from 1 commit
c3fa0fd
e0c541b
4d48cfe
316c44d
cd2222b
720262b
b867e19
1f599a6
ed89a1b
5c9e48c
fed36e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -105,7 +105,7 @@ static gvl_hook_t * rb_gvl_hooks = NULL; | |
static pthread_rwlock_t rb_gvl_hooks_rw_lock = PTHREAD_RWLOCK_INITIALIZER; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder whether we should use read-write locks or just plain locks. Read-write locks will probably improve performance slightly if there are GVL hooks, but it means that the hooks must be careful to ensure their code is thread-safe. Do we care so much about being as performant as possible when there are GVL hooks vs. the convenience of ensuring there can't be race conditions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, I think that's an implied part of the contract since some of the hooks are executed outside of the GVL anyway. I'm trying first and foremost to minimize the overhead when no hook is registered, so that people don't using it aren't impacted, hence the But I'd also like to keep the overhead reasonably low when some hooks are registered so that it would be a viable API in production (for monitoring etc). Hence the read-write locks, so that hopefully we don't create too much contention. |
||
|
||
gvl_hook_t * | ||
rb_gvl_event_new(void *callback, uint32_t event) { | ||
rb_gvl_event_new(void *callback, rb_event_flag_t event) { | ||
gvl_hook_t *hook = ALLOC_N(gvl_hook_t, 1); | ||
hook->callback = callback; | ||
hook->event = event; | ||
|
@@ -155,25 +155,21 @@ rb_gvl_event_delete(gvl_hook_t * hook) { | |
} | ||
|
||
void | ||
rb_gvl_execute_hooks(uint32_t event) { | ||
if (!rb_gvl_hooks) { | ||
return; | ||
} | ||
|
||
rb_gvl_execute_hooks(rb_event_flag_t event, unsigned long waiting) { | ||
if (pthread_rwlock_rdlock(&rb_gvl_hooks_rw_lock)) { | ||
// TODO: better way to deal with error? | ||
return; | ||
} | ||
|
||
gvl_hook_t *h = rb_gvl_hooks; | ||
struct gvl_hook_event_args args = {}; | ||
|
||
do { | ||
if (h->event & event) { | ||
(*h->callback)(event, args); | ||
} | ||
} while((h = h->next)); | ||
|
||
if (rb_gvl_hooks) { | ||
gvl_hook_t *h = rb_gvl_hooks; | ||
gvl_hook_event_args_t args = { .waiting = waiting }; | ||
do { | ||
if (h->event & event) { | ||
(*h->callback)(event, args); | ||
} | ||
} while((h = h->next)); | ||
} | ||
pthread_rwlock_unlock(&rb_gvl_hooks_rw_lock); | ||
} | ||
|
||
|
@@ -366,6 +362,10 @@ gvl_acquire_common(rb_global_vm_lock_t *gvl, rb_thread_t *th) | |
"we must not be in ubf_list and GVL waitq at the same time"); | ||
|
||
list_add_tail(&gvl->waitq, &nd->node.gvl); | ||
gvl->waiting++; | ||
if (rb_gvl_hooks) { | ||
rb_gvl_execute_hooks(RUBY_INTERNAL_EVENT_GVL_ACQUIRE_ENTER, gvl->waiting); | ||
} | ||
|
||
do { | ||
if (!gvl->timer) { | ||
|
@@ -377,6 +377,7 @@ gvl_acquire_common(rb_global_vm_lock_t *gvl, rb_thread_t *th) | |
} while (gvl->owner); | ||
|
||
list_del_init(&nd->node.gvl); | ||
gvl->waiting--; | ||
|
||
if (gvl->need_yield) { | ||
gvl->need_yield = 0; | ||
|
@@ -387,6 +388,11 @@ gvl_acquire_common(rb_global_vm_lock_t *gvl, rb_thread_t *th) | |
gvl->timer_err = ETIMEDOUT; | ||
} | ||
gvl->owner = th; | ||
|
||
if (rb_gvl_hooks) { | ||
rb_gvl_execute_hooks(RUBY_INTERNAL_EVENT_GVL_ACQUIRE_EXIT, gvl->waiting); | ||
} | ||
|
||
if (!gvl->timer) { | ||
if (!designate_timer_thread(gvl) && !ubf_threads_empty()) { | ||
rb_thread_wakeup_timer_thread(-1); | ||
|
@@ -405,6 +411,10 @@ gvl_acquire(rb_global_vm_lock_t *gvl, rb_thread_t *th) | |
static const native_thread_data_t * | ||
gvl_release_common(rb_global_vm_lock_t *gvl) | ||
{ | ||
if (rb_gvl_hooks) { | ||
rb_gvl_execute_hooks(RUBY_INTERNAL_EVENT_GVL_RELEASE, gvl->waiting); | ||
} | ||
|
||
native_thread_data_t *next; | ||
gvl->owner = 0; | ||
next = list_top(&gvl->waitq, native_thread_data_t, node.ubf); | ||
|
@@ -466,6 +476,7 @@ rb_gvl_init(rb_global_vm_lock_t *gvl) | |
rb_native_cond_initialize(&gvl->switch_wait_cond); | ||
list_head_init(&gvl->waitq); | ||
gvl->owner = 0; | ||
gvl->waiting = 0; | ||
gvl->timer = 0; | ||
gvl->timer_err = ETIMEDOUT; | ||
gvl->need_yield = 0; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's just me, but I find using hex for bit flags and bit masks is much harder to read than using left shifts for bit flags and
&
ing bit flags for bit masks. 😅There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's just you :) I just figured I'd match the existing pattern.