2012-08-15 08:39:10 +04:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
vm_trace.c -
|
|
|
|
|
|
|
|
$Author: ko1 $
|
|
|
|
created at: Tue Aug 14 19:37:09 2012
|
|
|
|
|
|
|
|
Copyright (C) 1993-2012 Yukihiro Matsumoto
|
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
|
|
/*
|
2014-01-19 06:14:45 +04:00
|
|
|
* This file include two parts:
|
2012-08-15 08:39:10 +04:00
|
|
|
*
|
|
|
|
* (1) set_trace_func internal mechanisms
|
|
|
|
* and C level API
|
|
|
|
*
|
|
|
|
* (2) Ruby level API
|
|
|
|
* (2-1) set_trace_func API
|
|
|
|
* (2-2) TracePoint API (not yet)
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2019-12-04 11:16:30 +03:00
|
|
|
#include "eval_intern.h"
|
2014-11-15 14:49:06 +03:00
|
|
|
#include "internal.h"
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
#include "internal/bits.h"
|
2023-02-15 12:42:52 +03:00
|
|
|
#include "internal/class.h"
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
#include "internal/gc.h"
|
2019-12-04 11:16:30 +03:00
|
|
|
#include "internal/hash.h"
|
|
|
|
#include "internal/symbol.h"
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
#include "internal/thread.h"
|
2019-12-04 11:16:30 +03:00
|
|
|
#include "iseq.h"
|
2023-03-07 10:17:25 +03:00
|
|
|
#include "rjit.h"
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
#include "ruby/atomic.h"
|
2012-11-20 16:57:49 +04:00
|
|
|
#include "ruby/debug.h"
|
2012-08-15 08:39:10 +04:00
|
|
|
#include "vm_core.h"
|
2020-12-19 00:38:58 +03:00
|
|
|
#include "ruby/ractor.h"
|
2021-08-26 00:00:45 +03:00
|
|
|
#include "yjit.h"
|
2019-12-04 11:16:30 +03:00
|
|
|
|
2019-11-07 12:22:08 +03:00
|
|
|
#include "builtin.h"
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2021-12-28 01:08:59 +03:00
|
|
|
static VALUE sym_default;
|
|
|
|
|
2012-08-15 08:39:10 +04:00
|
|
|
/* (1) trace mechanisms */
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
typedef struct rb_event_hook_struct {
|
2012-11-20 16:57:49 +04:00
|
|
|
rb_event_hook_flag_t hook_flags;
|
2012-08-16 15:41:24 +04:00
|
|
|
rb_event_flag_t events;
|
|
|
|
rb_event_hook_func_t func;
|
|
|
|
VALUE data;
|
|
|
|
struct rb_event_hook_struct *next;
|
2017-11-15 16:21:24 +03:00
|
|
|
|
|
|
|
struct {
|
|
|
|
rb_thread_t *th;
|
2018-11-26 23:16:14 +03:00
|
|
|
unsigned int target_line;
|
2017-11-15 16:21:24 +03:00
|
|
|
} filter;
|
2012-08-16 15:41:24 +04:00
|
|
|
} rb_event_hook_t;
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
typedef void (*rb_event_hook_raw_arg_func_t)(VALUE data, const rb_trace_arg_t *arg);
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
#define MAX_EVENT_NUM 32
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
void
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_hook_list_mark(rb_hook_list_t *hooks)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2012-08-16 15:41:24 +04:00
|
|
|
rb_event_hook_t *hook = hooks->hooks;
|
|
|
|
|
|
|
|
while (hook) {
|
|
|
|
rb_gc_mark(hook->data);
|
|
|
|
hook = hook->next;
|
|
|
|
}
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2023-01-19 22:47:17 +03:00
|
|
|
void
|
|
|
|
rb_hook_list_mark_and_update(rb_hook_list_t *hooks)
|
|
|
|
{
|
|
|
|
rb_event_hook_t *hook = hooks->hooks;
|
|
|
|
|
|
|
|
while (hook) {
|
|
|
|
rb_gc_mark_and_move(&hook->data);
|
|
|
|
hook = hook->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-16 12:34:03 +03:00
|
|
|
static void clean_hooks(rb_hook_list_t *list);
|
2018-11-26 21:16:39 +03:00
|
|
|
|
|
|
|
void
|
|
|
|
rb_hook_list_free(rb_hook_list_t *hooks)
|
|
|
|
{
|
2021-12-12 20:15:05 +03:00
|
|
|
hooks->need_clean = true;
|
|
|
|
|
|
|
|
if (hooks->running == 0) {
|
2023-12-16 12:34:03 +03:00
|
|
|
clean_hooks(hooks);
|
2021-12-12 20:15:05 +03:00
|
|
|
}
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
/* ruby_vm_event_flags management */
|
|
|
|
|
2021-08-24 01:22:14 +03:00
|
|
|
void rb_clear_attr_ccs(void);
|
2023-03-09 19:30:30 +03:00
|
|
|
void rb_clear_bf_ccs(void);
|
2021-08-24 01:22:14 +03:00
|
|
|
|
2017-11-14 15:58:36 +03:00
|
|
|
static void
|
2021-12-14 04:24:27 +03:00
|
|
|
update_global_event_hook(rb_event_flag_t prev_events, rb_event_flag_t new_events)
|
2017-11-14 15:58:36 +03:00
|
|
|
{
|
2021-12-14 04:24:27 +03:00
|
|
|
rb_event_flag_t new_iseq_events = new_events & ISEQ_TRACE_EVENTS;
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_event_flag_t enabled_iseq_events = ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS;
|
2022-11-13 20:51:19 +03:00
|
|
|
bool first_time_iseq_events_p = new_iseq_events & ~enabled_iseq_events;
|
|
|
|
bool enable_c_call = (prev_events & RUBY_EVENT_C_CALL) == 0 && (new_events & RUBY_EVENT_C_CALL);
|
|
|
|
bool enable_c_return = (prev_events & RUBY_EVENT_C_RETURN) == 0 && (new_events & RUBY_EVENT_C_RETURN);
|
2023-03-09 19:30:30 +03:00
|
|
|
bool enable_call = (prev_events & RUBY_EVENT_CALL) == 0 && (new_events & RUBY_EVENT_CALL);
|
|
|
|
bool enable_return = (prev_events & RUBY_EVENT_RETURN) == 0 && (new_events & RUBY_EVENT_RETURN);
|
2017-11-17 09:24:55 +03:00
|
|
|
|
2022-11-14 02:13:13 +03:00
|
|
|
// Modify ISEQs or CCs to enable tracing
|
2022-11-13 20:51:19 +03:00
|
|
|
if (first_time_iseq_events_p) {
|
|
|
|
// write all ISeqs only when new events are added for the first time
|
2017-12-11 22:17:25 +03:00
|
|
|
rb_iseq_trace_set_all(new_iseq_events | enabled_iseq_events);
|
2017-11-14 16:25:11 +03:00
|
|
|
}
|
2022-11-14 02:13:13 +03:00
|
|
|
// if c_call or c_return is activated
|
|
|
|
else if (enable_c_call || enable_c_return) {
|
|
|
|
rb_clear_attr_ccs();
|
2021-08-24 01:22:14 +03:00
|
|
|
}
|
2023-03-09 19:30:30 +03:00
|
|
|
else if (enable_call || enable_return) {
|
|
|
|
rb_clear_bf_ccs();
|
|
|
|
}
|
2017-11-17 09:24:55 +03:00
|
|
|
|
2021-12-14 04:24:27 +03:00
|
|
|
ruby_vm_event_flags = new_events;
|
|
|
|
ruby_vm_event_enabled_global_flags |= new_events;
|
|
|
|
rb_objspace_set_event_hook(new_events);
|
2021-08-26 21:58:13 +03:00
|
|
|
|
2022-11-14 02:13:13 +03:00
|
|
|
// Invalidate JIT code as needed
|
2022-11-13 20:51:19 +03:00
|
|
|
if (first_time_iseq_events_p || enable_c_call || enable_c_return) {
|
2022-11-11 01:12:38 +03:00
|
|
|
// Invalidate all code when ISEQs are modified to use trace_* insns above.
|
2022-11-13 20:51:19 +03:00
|
|
|
// Also invalidate when enabling c_call or c_return because generated code
|
|
|
|
// never fires these events.
|
2021-08-26 21:58:13 +03:00
|
|
|
// Internal events fire inside C routines so don't need special handling.
|
2022-11-11 01:12:38 +03:00
|
|
|
// Do this after event flags updates so other ractors see updated vm events
|
|
|
|
// when they wake up.
|
2021-10-02 01:38:39 +03:00
|
|
|
rb_yjit_tracing_invalidate_all();
|
2023-03-07 10:17:25 +03:00
|
|
|
rb_rjit_tracing_invalidate_all(new_iseq_events);
|
2021-08-26 21:58:13 +03:00
|
|
|
}
|
2017-11-14 15:58:36 +03:00
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
/* add/remove hooks */
|
|
|
|
|
|
|
|
static rb_event_hook_t *
|
2012-11-20 16:57:49 +04:00
|
|
|
alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data, rb_event_hook_flag_t hook_flags)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2013-11-26 12:41:44 +04:00
|
|
|
rb_event_hook_t *hook;
|
|
|
|
|
|
|
|
if ((events & RUBY_INTERNAL_EVENT_MASK) && (events & ~RUBY_INTERNAL_EVENT_MASK)) {
|
|
|
|
rb_raise(rb_eTypeError, "Can not specify normal event and internal event simultaneously.");
|
|
|
|
}
|
|
|
|
|
|
|
|
hook = ALLOC(rb_event_hook_t);
|
2012-08-16 15:41:24 +04:00
|
|
|
hook->hook_flags = hook_flags;
|
|
|
|
hook->events = events;
|
|
|
|
hook->func = func;
|
|
|
|
hook->data = data;
|
2017-11-15 16:21:24 +03:00
|
|
|
|
|
|
|
/* no filters */
|
2018-11-26 23:16:14 +03:00
|
|
|
hook->filter.th = NULL;
|
|
|
|
hook->filter.target_line = 0;
|
2017-11-15 16:21:24 +03:00
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
return hook;
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
static void
|
2018-11-26 21:16:39 +03:00
|
|
|
hook_list_connect(VALUE list_owner, rb_hook_list_t *list, rb_event_hook_t *hook, int global_p)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2021-12-14 04:24:27 +03:00
|
|
|
rb_event_flag_t prev_events = list->events;
|
2012-08-16 15:41:24 +04:00
|
|
|
hook->next = list->hooks;
|
|
|
|
list->hooks = hook;
|
|
|
|
list->events |= hook->events;
|
2018-11-26 21:16:39 +03:00
|
|
|
|
|
|
|
if (global_p) {
|
|
|
|
/* global hooks are root objects at GC mark. */
|
2021-12-14 04:24:27 +03:00
|
|
|
update_global_event_hook(prev_events, list->events);
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
RB_OBJ_WRITTEN(list_owner, Qundef, hook->data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
connect_event_hook(const rb_execution_context_t *ec, rb_event_hook_t *hook)
|
|
|
|
{
|
2020-12-19 00:38:58 +03:00
|
|
|
rb_hook_list_t *list = rb_ec_ractor_hooks(ec);
|
2018-11-26 21:16:39 +03:00
|
|
|
hook_list_connect(Qundef, list, hook, TRUE);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-11-15 16:21:24 +03:00
|
|
|
rb_threadptr_add_event_hook(const rb_execution_context_t *ec, rb_thread_t *th,
|
|
|
|
rb_event_hook_func_t func, rb_event_flag_t events, VALUE data, rb_event_hook_flag_t hook_flags)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2012-08-16 15:41:24 +04:00
|
|
|
rb_event_hook_t *hook = alloc_event_hook(func, events, data, hook_flags);
|
2017-11-15 16:21:24 +03:00
|
|
|
hook->filter.th = th;
|
|
|
|
connect_event_hook(ec, hook);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
void
|
|
|
|
rb_thread_add_event_hook(VALUE thval, rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
rb_threadptr_add_event_hook(GET_EC(), rb_thread_ptr(thval), func, events, data, RUBY_EVENT_HOOK_FLAG_SAFE);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
void
|
|
|
|
rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2021-09-28 16:46:47 +03:00
|
|
|
rb_add_event_hook2(func, events, data, RUBY_EVENT_HOOK_FLAG_SAFE);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-11-20 16:57:49 +04:00
|
|
|
rb_thread_add_event_hook2(VALUE thval, rb_event_hook_func_t func, rb_event_flag_t events, VALUE data, rb_event_hook_flag_t hook_flags)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
rb_threadptr_add_event_hook(GET_EC(), rb_thread_ptr(thval), func, events, data, hook_flags);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-11-20 16:57:49 +04:00
|
|
|
rb_add_event_hook2(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data, rb_event_hook_flag_t hook_flags)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2012-08-22 09:12:31 +04:00
|
|
|
rb_event_hook_t *hook = alloc_event_hook(func, events, data, hook_flags);
|
2017-11-15 16:21:24 +03:00
|
|
|
connect_event_hook(GET_EC(), hook);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2017-11-16 05:47:58 +03:00
|
|
|
static void
|
2023-12-16 12:34:03 +03:00
|
|
|
clean_hooks(rb_hook_list_t *list)
|
2017-11-16 05:47:58 +03:00
|
|
|
{
|
|
|
|
rb_event_hook_t *hook, **nextp = &list->hooks;
|
2021-12-14 04:24:27 +03:00
|
|
|
rb_event_flag_t prev_events = list->events;
|
2021-12-12 20:15:05 +03:00
|
|
|
|
|
|
|
VM_ASSERT(list->running == 0);
|
|
|
|
VM_ASSERT(list->need_clean == true);
|
|
|
|
|
2017-11-16 05:47:58 +03:00
|
|
|
list->events = 0;
|
2021-12-12 20:15:05 +03:00
|
|
|
list->need_clean = false;
|
2017-11-16 05:47:58 +03:00
|
|
|
|
|
|
|
while ((hook = *nextp) != 0) {
|
|
|
|
if (hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED) {
|
|
|
|
*nextp = hook->next;
|
|
|
|
xfree(hook);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
list->events |= hook->events; /* update active events */
|
|
|
|
nextp = &hook->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-12 20:15:05 +03:00
|
|
|
if (list->is_local) {
|
|
|
|
if (list->events == 0) {
|
|
|
|
/* local events */
|
|
|
|
ruby_xfree(list);
|
|
|
|
}
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
|
|
|
else {
|
2021-12-12 20:15:05 +03:00
|
|
|
update_global_event_hook(prev_events, list->events);
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
2017-11-16 05:47:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2023-12-16 12:34:03 +03:00
|
|
|
clean_hooks_check(rb_hook_list_t *list)
|
2017-11-16 05:47:58 +03:00
|
|
|
{
|
2021-12-12 20:15:05 +03:00
|
|
|
if (UNLIKELY(list->need_clean)) {
|
2018-11-26 21:16:54 +03:00
|
|
|
if (list->running == 0) {
|
2023-12-16 12:34:03 +03:00
|
|
|
clean_hooks(list);
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
2017-11-16 05:47:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 16:21:24 +03:00
|
|
|
#define MATCH_ANY_FILTER_TH ((rb_thread_t *)1)
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
/* if func is 0, then clear all funcs */
|
2012-08-15 08:39:10 +04:00
|
|
|
static int
|
2017-11-15 16:21:24 +03:00
|
|
|
remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2020-12-19 00:38:58 +03:00
|
|
|
rb_hook_list_t *list = rb_ec_ractor_hooks(ec);
|
2012-08-16 15:41:24 +04:00
|
|
|
int ret = 0;
|
|
|
|
rb_event_hook_t *hook = list->hooks;
|
|
|
|
|
2012-08-15 08:39:10 +04:00
|
|
|
while (hook) {
|
|
|
|
if (func == 0 || hook->func == func) {
|
2017-11-15 16:21:24 +03:00
|
|
|
if (hook->filter.th == filter_th || filter_th == MATCH_ANY_FILTER_TH) {
|
2022-11-15 07:24:08 +03:00
|
|
|
if (UNDEF_P(data) || hook->data == data) {
|
2017-11-15 16:21:24 +03:00
|
|
|
hook->hook_flags |= RUBY_EVENT_HOOK_FLAG_DELETED;
|
|
|
|
ret+=1;
|
2021-12-12 20:15:05 +03:00
|
|
|
list->need_clean = true;
|
2017-11-15 16:21:24 +03:00
|
|
|
}
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
hook = hook->next;
|
|
|
|
}
|
|
|
|
|
2023-12-16 12:34:03 +03:00
|
|
|
clean_hooks_check(list);
|
2012-08-16 15:41:24 +04:00
|
|
|
return ret;
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2017-11-15 16:21:24 +03:00
|
|
|
rb_threadptr_remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
return remove_event_hook(ec, filter_th, func, data);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
rb_thread_remove_event_hook(VALUE thval, rb_event_hook_func_t func)
|
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
return rb_threadptr_remove_event_hook(GET_EC(), rb_thread_ptr(thval), func, Qundef);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
rb_thread_remove_event_hook_with_data(VALUE thval, rb_event_hook_func_t func, VALUE data)
|
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
return rb_threadptr_remove_event_hook(GET_EC(), rb_thread_ptr(thval), func, data);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
int
|
|
|
|
rb_remove_event_hook(rb_event_hook_func_t func)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
return remove_event_hook(GET_EC(), NULL, func, Qundef);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
rb_remove_event_hook_with_data(rb_event_hook_func_t func, VALUE data)
|
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
return remove_event_hook(GET_EC(), NULL, func, data);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2017-11-15 16:21:24 +03:00
|
|
|
void
|
|
|
|
rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec)
|
|
|
|
{
|
|
|
|
rb_threadptr_remove_event_hook(ec, rb_ec_thread_ptr(ec), 0, Qundef);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2020-03-11 17:37:14 +03:00
|
|
|
void
|
|
|
|
rb_ec_clear_all_trace_func(const rb_execution_context_t *ec)
|
|
|
|
{
|
|
|
|
rb_threadptr_remove_event_hook(ec, MATCH_ANY_FILTER_TH, 0, Qundef);
|
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
/* invoke hooks */
|
|
|
|
|
2013-11-26 13:42:04 +04:00
|
|
|
static void
|
2017-11-07 07:01:13 +03:00
|
|
|
exec_hooks_body(const rb_execution_context_t *ec, rb_hook_list_t *list, const rb_trace_arg_t *trace_arg)
|
2013-11-26 13:42:04 +04:00
|
|
|
{
|
|
|
|
rb_event_hook_t *hook;
|
|
|
|
|
|
|
|
for (hook = list->hooks; hook; hook = hook->next) {
|
2017-11-15 16:21:24 +03:00
|
|
|
if (!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED) &&
|
|
|
|
(trace_arg->event & hook->events) &&
|
2018-11-26 23:16:14 +03:00
|
|
|
(LIKELY(hook->filter.th == 0) || hook->filter.th == rb_ec_thread_ptr(ec)) &&
|
|
|
|
(LIKELY(hook->filter.target_line == 0) || (hook->filter.target_line == (unsigned int)rb_vm_get_sourceline(ec->cfp)))) {
|
2013-11-26 13:42:04 +04:00
|
|
|
if (!(hook->hook_flags & RUBY_EVENT_HOOK_FLAG_RAW_ARG)) {
|
|
|
|
(*hook->func)(trace_arg->event, hook->data, trace_arg->self, trace_arg->id, trace_arg->klass);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
(*((rb_event_hook_raw_arg_func_t)hook->func))(hook->data, trace_arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
static int
|
2018-11-26 21:16:39 +03:00
|
|
|
exec_hooks_precheck(const rb_execution_context_t *ec, rb_hook_list_t *list, const rb_trace_arg_t *trace_arg)
|
2013-11-26 13:42:04 +04:00
|
|
|
{
|
2017-11-16 05:47:58 +03:00
|
|
|
if (list->events & trace_arg->event) {
|
2018-11-26 21:16:39 +03:00
|
|
|
list->running++;
|
2017-11-16 05:47:58 +03:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return FALSE;
|
2013-11-26 13:42:04 +04:00
|
|
|
}
|
2017-11-16 05:47:58 +03:00
|
|
|
}
|
2015-11-08 02:10:55 +03:00
|
|
|
|
2017-11-16 05:47:58 +03:00
|
|
|
static void
|
2018-11-26 21:16:39 +03:00
|
|
|
exec_hooks_postcheck(const rb_execution_context_t *ec, rb_hook_list_t *list)
|
2017-11-16 05:47:58 +03:00
|
|
|
{
|
2018-11-26 21:16:39 +03:00
|
|
|
list->running--;
|
2023-12-16 12:34:03 +03:00
|
|
|
clean_hooks_check(list);
|
2013-11-26 13:42:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-11-26 21:16:39 +03:00
|
|
|
exec_hooks_unprotected(const rb_execution_context_t *ec, rb_hook_list_t *list, const rb_trace_arg_t *trace_arg)
|
2013-11-26 13:42:04 +04:00
|
|
|
{
|
2018-11-26 21:16:39 +03:00
|
|
|
if (exec_hooks_precheck(ec, list, trace_arg) == 0) return;
|
2017-11-07 07:01:13 +03:00
|
|
|
exec_hooks_body(ec, list, trace_arg);
|
2018-11-26 21:16:39 +03:00
|
|
|
exec_hooks_postcheck(ec, list);
|
2013-11-26 13:42:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2018-11-26 21:16:39 +03:00
|
|
|
exec_hooks_protected(rb_execution_context_t *ec, rb_hook_list_t *list, const rb_trace_arg_t *trace_arg)
|
2012-08-16 15:41:24 +04:00
|
|
|
{
|
2017-06-23 10:25:52 +03:00
|
|
|
enum ruby_tag_type state;
|
2012-08-16 15:41:24 +04:00
|
|
|
volatile int raised;
|
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
if (exec_hooks_precheck(ec, list, trace_arg) == 0) return 0;
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2017-11-07 07:01:13 +03:00
|
|
|
raised = rb_ec_reset_raised(ec);
|
2012-08-16 15:41:24 +04:00
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
/* TODO: Support !RUBY_EVENT_HOOK_FLAG_SAFE hooks */
|
2012-08-16 15:41:24 +04:00
|
|
|
|
2017-11-07 07:01:13 +03:00
|
|
|
EC_PUSH_TAG(ec);
|
2017-10-26 14:02:13 +03:00
|
|
|
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
|
2017-11-07 07:01:13 +03:00
|
|
|
exec_hooks_body(ec, list, trace_arg);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
2017-10-26 14:02:13 +03:00
|
|
|
EC_POP_TAG();
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
exec_hooks_postcheck(ec, list);
|
2017-11-16 05:47:58 +03:00
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
if (raised) {
|
2017-11-07 08:12:39 +03:00
|
|
|
rb_ec_set_raised(ec);
|
2012-08-16 15:41:24 +04:00
|
|
|
}
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
return state;
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2021-07-09 01:23:39 +03:00
|
|
|
// pop_p: Whether to pop the frame for the TracePoint when it throws.
|
2023-03-07 08:34:31 +03:00
|
|
|
void
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_exec_event_hooks(rb_trace_arg_t *trace_arg, rb_hook_list_t *hooks, int pop_p)
|
2012-08-16 15:41:24 +04:00
|
|
|
{
|
2017-10-29 16:17:37 +03:00
|
|
|
rb_execution_context_t *ec = trace_arg->ec;
|
2013-11-26 13:42:04 +04:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
if (UNLIKELY(trace_arg->event & RUBY_INTERNAL_EVENT_MASK)) {
|
|
|
|
if (ec->trace_arg && (ec->trace_arg->event & RUBY_INTERNAL_EVENT_MASK)) {
|
|
|
|
/* skip hooks because this thread doing INTERNAL_EVENT */
|
2013-11-26 13:42:04 +04:00
|
|
|
}
|
|
|
|
else {
|
2017-10-29 16:17:37 +03:00
|
|
|
rb_trace_arg_t *prev_trace_arg = ec->trace_arg;
|
2018-11-26 21:16:39 +03:00
|
|
|
|
|
|
|
ec->trace_arg = trace_arg;
|
|
|
|
/* only global hooks */
|
2020-12-19 00:38:58 +03:00
|
|
|
exec_hooks_unprotected(ec, rb_ec_ractor_hooks(ec), trace_arg);
|
2018-11-26 21:16:39 +03:00
|
|
|
ec->trace_arg = prev_trace_arg;
|
2013-11-26 13:42:04 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2017-10-29 16:17:37 +03:00
|
|
|
if (ec->trace_arg == NULL && /* check reentrant */
|
2013-11-26 13:42:04 +04:00
|
|
|
trace_arg->self != rb_mRubyVMFrozenCore /* skip special methods. TODO: remove it. */) {
|
2017-10-29 16:17:37 +03:00
|
|
|
const VALUE errinfo = ec->errinfo;
|
|
|
|
const VALUE old_recursive = ec->local_storage_recursive_hash;
|
2023-08-07 18:32:45 +03:00
|
|
|
enum ruby_tag_type state = 0;
|
2014-11-27 04:56:38 +03:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
/* setup */
|
2017-10-29 16:17:37 +03:00
|
|
|
ec->local_storage_recursive_hash = ec->local_storage_recursive_hash_for_trace;
|
|
|
|
ec->errinfo = Qnil;
|
|
|
|
ec->trace_arg = trace_arg;
|
2014-12-10 22:35:07 +03:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
/* kick hooks */
|
|
|
|
if ((state = exec_hooks_protected(ec, hooks, trace_arg)) == TAG_NONE) {
|
|
|
|
ec->errinfo = errinfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* cleanup */
|
|
|
|
ec->trace_arg = NULL;
|
2017-10-29 16:17:37 +03:00
|
|
|
ec->local_storage_recursive_hash_for_trace = ec->local_storage_recursive_hash;
|
|
|
|
ec->local_storage_recursive_hash = old_recursive;
|
2022-07-21 19:23:58 +03:00
|
|
|
|
2013-11-26 13:42:04 +04:00
|
|
|
if (state) {
|
|
|
|
if (pop_p) {
|
2017-10-29 16:17:37 +03:00
|
|
|
if (VM_FRAME_FINISHED_P(ec->cfp)) {
|
|
|
|
ec->tag = ec->tag->prev;
|
2013-11-26 13:42:04 +04:00
|
|
|
}
|
2017-10-29 16:17:37 +03:00
|
|
|
rb_vm_pop_frame(ec);
|
2013-01-07 10:24:46 +04:00
|
|
|
}
|
2017-10-29 16:17:37 +03:00
|
|
|
EC_JUMP_TAG(ec, state);
|
2013-01-07 10:24:46 +04:00
|
|
|
}
|
2012-12-11 22:45:43 +04:00
|
|
|
}
|
2012-08-16 15:41:24 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
|
|
|
rb_suppress_tracing(VALUE (*func)(VALUE), VALUE arg)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2012-08-16 15:41:24 +04:00
|
|
|
volatile int raised;
|
2019-06-08 13:46:32 +03:00
|
|
|
volatile VALUE result = Qnil;
|
2017-12-01 15:00:10 +03:00
|
|
|
rb_execution_context_t *const ec = GET_EC();
|
|
|
|
rb_vm_t *const vm = rb_ec_vm_ptr(ec);
|
2017-06-23 10:25:52 +03:00
|
|
|
enum ruby_tag_type state;
|
2012-12-21 13:33:44 +04:00
|
|
|
rb_trace_arg_t dummy_trace_arg;
|
2013-12-04 08:05:15 +04:00
|
|
|
dummy_trace_arg.event = 0;
|
2012-12-21 13:33:44 +04:00
|
|
|
|
2017-12-01 15:00:09 +03:00
|
|
|
if (!ec->trace_arg) {
|
|
|
|
ec->trace_arg = &dummy_trace_arg;
|
2017-11-16 05:47:58 +03:00
|
|
|
}
|
2012-08-16 15:41:24 +04:00
|
|
|
|
2017-11-07 07:01:13 +03:00
|
|
|
raised = rb_ec_reset_raised(ec);
|
2012-08-16 15:41:24 +04:00
|
|
|
|
2017-11-07 07:01:13 +03:00
|
|
|
EC_PUSH_TAG(ec);
|
2017-12-01 15:00:10 +03:00
|
|
|
if (LIKELY((state = EC_EXEC_TAG()) == TAG_NONE)) {
|
2012-08-16 15:41:24 +04:00
|
|
|
result = (*func)(arg);
|
|
|
|
}
|
2017-12-01 15:00:10 +03:00
|
|
|
else {
|
|
|
|
(void)*&vm; /* suppress "clobbered" warning */
|
|
|
|
}
|
2017-10-26 14:02:13 +03:00
|
|
|
EC_POP_TAG();
|
2012-08-16 15:41:24 +04:00
|
|
|
|
|
|
|
if (raised) {
|
2017-11-07 07:01:13 +03:00
|
|
|
rb_ec_reset_raised(ec);
|
2012-08-16 15:41:24 +04:00
|
|
|
}
|
2012-12-21 13:33:44 +04:00
|
|
|
|
2017-12-01 15:00:09 +03:00
|
|
|
if (ec->trace_arg == &dummy_trace_arg) {
|
|
|
|
ec->trace_arg = NULL;
|
2017-11-16 05:47:58 +03:00
|
|
|
}
|
2012-08-16 15:41:24 +04:00
|
|
|
|
|
|
|
if (state) {
|
2017-08-02 06:02:22 +03:00
|
|
|
#if defined RUBY_USE_SETJMPEX && RUBY_USE_SETJMPEX
|
|
|
|
RB_GC_GUARD(result);
|
|
|
|
#endif
|
2017-11-07 07:01:13 +03:00
|
|
|
EC_JUMP_TAG(ec, state);
|
2012-08-16 15:41:24 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALUE klass);
|
|
|
|
|
|
|
|
/* (2-1) set_trace_func (old API) */
|
|
|
|
|
|
|
|
/*
|
2023-09-04 03:23:31 +03:00
|
|
|
* call-seq:
|
|
|
|
* set_trace_func(proc) -> proc
|
|
|
|
* set_trace_func(nil) -> nil
|
2012-08-15 08:39:10 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* Establishes _proc_ as the handler for tracing, or disables
|
|
|
|
* tracing if the parameter is +nil+.
|
2012-11-30 10:15:59 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* *Note:* this method is obsolete, please use TracePoint instead.
|
2013-02-09 08:14:09 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* _proc_ takes up to six parameters:
|
2012-11-30 10:15:59 +04:00
|
|
|
*
|
2023-09-04 04:20:04 +03:00
|
|
|
* * an event name string
|
|
|
|
* * a filename string
|
2023-09-04 03:23:31 +03:00
|
|
|
* * a line number
|
2023-09-04 04:20:04 +03:00
|
|
|
* * a method name symbol, or nil
|
|
|
|
* * a binding, or nil
|
|
|
|
* * the class, module, or nil
|
2012-11-30 10:15:59 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* _proc_ is invoked whenever an event occurs.
|
2012-11-30 10:15:59 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* Events are:
|
2012-11-30 10:15:59 +04:00
|
|
|
*
|
2023-09-04 04:20:04 +03:00
|
|
|
* <code>"c-call"</code>:: call a C-language routine
|
|
|
|
* <code>"c-return"</code>:: return from a C-language routine
|
|
|
|
* <code>"call"</code>:: call a Ruby method
|
|
|
|
* <code>"class"</code>:: start a class or module definition
|
|
|
|
* <code>"end"</code>:: finish a class or module definition
|
|
|
|
* <code>"line"</code>:: execute code on a new line
|
|
|
|
* <code>"raise"</code>:: raise an exception
|
|
|
|
* <code>"return"</code>:: return from a Ruby method
|
2012-11-30 10:15:59 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* Tracing is disabled within the context of _proc_.
|
2012-08-15 08:39:10 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* class Test
|
|
|
|
* def test
|
|
|
|
* a = 1
|
|
|
|
* b = 2
|
|
|
|
* end
|
|
|
|
* end
|
2012-08-15 08:39:10 +04:00
|
|
|
*
|
2023-09-04 04:20:04 +03:00
|
|
|
* set_trace_func proc { |event, file, line, id, binding, class_or_module|
|
|
|
|
* printf "%8s %s:%-2d %16p %14p\n", event, file, line, id, class_or_module
|
2023-09-04 03:23:31 +03:00
|
|
|
* }
|
|
|
|
* t = Test.new
|
|
|
|
* t.test
|
2012-08-15 08:39:10 +04:00
|
|
|
*
|
2023-09-04 03:23:31 +03:00
|
|
|
* Produces:
|
|
|
|
*
|
2023-09-04 04:20:04 +03:00
|
|
|
* c-return prog.rb:8 :set_trace_func Kernel
|
|
|
|
* line prog.rb:11 nil nil
|
|
|
|
* c-call prog.rb:11 :new Class
|
|
|
|
* c-call prog.rb:11 :initialize BasicObject
|
|
|
|
* c-return prog.rb:11 :initialize BasicObject
|
|
|
|
* c-return prog.rb:11 :new Class
|
|
|
|
* line prog.rb:12 nil nil
|
|
|
|
* call prog.rb:2 :test Test
|
|
|
|
* line prog.rb:3 :test Test
|
|
|
|
* line prog.rb:4 :test Test
|
|
|
|
* return prog.rb:5 :test Test
|
2012-08-15 08:39:10 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
set_trace_func(VALUE obj, VALUE trace)
|
|
|
|
{
|
|
|
|
rb_remove_event_hook(call_trace_func);
|
|
|
|
|
|
|
|
if (NIL_P(trace)) {
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rb_obj_is_proc(trace)) {
|
|
|
|
rb_raise(rb_eTypeError, "trace_func needs to be Proc");
|
|
|
|
}
|
|
|
|
|
|
|
|
rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL, trace);
|
|
|
|
return trace;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-11-15 16:21:24 +03:00
|
|
|
thread_add_trace_func(rb_execution_context_t *ec, rb_thread_t *filter_th, VALUE trace)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
|
|
|
if (!rb_obj_is_proc(trace)) {
|
|
|
|
rb_raise(rb_eTypeError, "trace_func needs to be Proc");
|
|
|
|
}
|
|
|
|
|
2017-11-15 16:21:24 +03:00
|
|
|
rb_threadptr_add_event_hook(ec, filter_th, call_trace_func, RUBY_EVENT_ALL, trace, RUBY_EVENT_HOOK_FLAG_SAFE);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* thr.add_trace_func(proc) -> proc
|
|
|
|
*
|
|
|
|
* Adds _proc_ as a handler for tracing.
|
2013-02-09 08:14:09 +04:00
|
|
|
*
|
|
|
|
* See Thread#set_trace_func and Kernel#set_trace_func.
|
2012-08-15 08:39:10 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
thread_add_trace_func_m(VALUE obj, VALUE trace)
|
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
thread_add_trace_func(GET_EC(), rb_thread_ptr(obj), trace);
|
2012-08-15 08:39:10 +04:00
|
|
|
return trace;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* call-seq:
|
|
|
|
* thr.set_trace_func(proc) -> proc
|
|
|
|
* thr.set_trace_func(nil) -> nil
|
|
|
|
*
|
|
|
|
* Establishes _proc_ on _thr_ as the handler for tracing, or
|
|
|
|
* disables tracing if the parameter is +nil+.
|
2013-02-09 08:14:09 +04:00
|
|
|
*
|
|
|
|
* See Kernel#set_trace_func.
|
2012-08-15 08:39:10 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
static VALUE
|
2017-06-28 07:49:30 +03:00
|
|
|
thread_set_trace_func_m(VALUE target_thread, VALUE trace)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2017-11-15 16:21:24 +03:00
|
|
|
rb_execution_context_t *ec = GET_EC();
|
2017-06-28 07:49:30 +03:00
|
|
|
rb_thread_t *target_th = rb_thread_ptr(target_thread);
|
2013-01-29 11:50:27 +04:00
|
|
|
|
2017-11-15 16:21:24 +03:00
|
|
|
rb_threadptr_remove_event_hook(ec, target_th, call_trace_func, Qundef);
|
2012-08-15 08:39:10 +04:00
|
|
|
|
|
|
|
if (NIL_P(trace)) {
|
|
|
|
return Qnil;
|
|
|
|
}
|
2017-06-28 07:49:30 +03:00
|
|
|
else {
|
2017-11-15 16:21:24 +03:00
|
|
|
thread_add_trace_func(ec, target_th, trace);
|
2017-06-28 07:49:30 +03:00
|
|
|
return trace;
|
|
|
|
}
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
get_event_name(rb_event_flag_t event)
|
|
|
|
{
|
|
|
|
switch (event) {
|
2012-08-22 09:12:31 +04:00
|
|
|
case RUBY_EVENT_LINE: return "line";
|
|
|
|
case RUBY_EVENT_CLASS: return "class";
|
|
|
|
case RUBY_EVENT_END: return "end";
|
|
|
|
case RUBY_EVENT_CALL: return "call";
|
|
|
|
case RUBY_EVENT_RETURN: return "return";
|
|
|
|
case RUBY_EVENT_C_CALL: return "c-call";
|
|
|
|
case RUBY_EVENT_C_RETURN: return "c-return";
|
|
|
|
case RUBY_EVENT_RAISE: return "raise";
|
2012-08-15 08:39:10 +04:00
|
|
|
default:
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
static ID
|
|
|
|
get_event_id(rb_event_flag_t event)
|
|
|
|
{
|
|
|
|
ID id;
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
#define C(name, NAME) case RUBY_EVENT_##NAME: CONST_ID(id, #name); return id;
|
|
|
|
C(line, LINE);
|
|
|
|
C(class, CLASS);
|
|
|
|
C(end, END);
|
|
|
|
C(call, CALL);
|
|
|
|
C(return, RETURN);
|
|
|
|
C(c_call, C_CALL);
|
|
|
|
C(c_return, C_RETURN);
|
|
|
|
C(raise, RAISE);
|
2012-11-30 02:28:16 +04:00
|
|
|
C(b_call, B_CALL);
|
|
|
|
C(b_return, B_RETURN);
|
|
|
|
C(thread_begin, THREAD_BEGIN);
|
|
|
|
C(thread_end, THREAD_END);
|
2015-08-21 12:51:01 +03:00
|
|
|
C(fiber_switch, FIBER_SWITCH);
|
2018-12-06 16:42:32 +03:00
|
|
|
C(script_compiled, SCRIPT_COMPILED);
|
2023-08-01 11:25:20 +03:00
|
|
|
C(rescue, RESCUE);
|
2012-08-22 09:12:31 +04:00
|
|
|
#undef C
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-14 15:58:36 +03:00
|
|
|
static void
|
|
|
|
get_path_and_lineno(const rb_execution_context_t *ec, const rb_control_frame_t *cfp, rb_event_flag_t event, VALUE *pathp, int *linep)
|
|
|
|
{
|
|
|
|
cfp = rb_vm_get_ruby_level_next_cfp(ec, cfp);
|
|
|
|
|
|
|
|
if (cfp) {
|
|
|
|
const rb_iseq_t *iseq = cfp->iseq;
|
|
|
|
*pathp = rb_iseq_path(iseq);
|
2022-07-21 19:23:58 +03:00
|
|
|
|
2017-11-14 15:58:36 +03:00
|
|
|
if (event & (RUBY_EVENT_CLASS |
|
2023-08-01 11:25:20 +03:00
|
|
|
RUBY_EVENT_CALL |
|
|
|
|
RUBY_EVENT_B_CALL)) {
|
2017-11-14 15:58:36 +03:00
|
|
|
*linep = FIX2INT(rb_iseq_first_lineno(iseq));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*linep = rb_vm_get_sourceline(cfp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*pathp = Qnil;
|
|
|
|
*linep = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
static void
|
|
|
|
call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
|
2012-08-15 08:39:10 +04:00
|
|
|
{
|
2015-10-31 04:02:26 +03:00
|
|
|
int line;
|
2017-11-14 15:58:36 +03:00
|
|
|
VALUE filename;
|
2012-08-16 15:41:24 +04:00
|
|
|
VALUE eventname = rb_str_new2(get_event_name(event));
|
2012-08-15 08:39:10 +04:00
|
|
|
VALUE argv[6];
|
2017-10-28 14:11:17 +03:00
|
|
|
const rb_execution_context_t *ec = GET_EC();
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2017-11-14 15:58:36 +03:00
|
|
|
get_path_and_lineno(ec, ec->cfp, event, &filename, &line);
|
|
|
|
|
2012-08-17 06:13:31 +04:00
|
|
|
if (!klass) {
|
2017-10-28 14:11:17 +03:00
|
|
|
rb_ec_frame_method_id_and_class(ec, &id, 0, &klass);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
2012-08-16 15:41:24 +04:00
|
|
|
|
2012-08-15 08:39:10 +04:00
|
|
|
if (klass) {
|
|
|
|
if (RB_TYPE_P(klass, T_ICLASS)) {
|
|
|
|
klass = RBASIC(klass)->klass;
|
|
|
|
}
|
2024-03-06 19:04:22 +03:00
|
|
|
else if (RCLASS_SINGLETON_P(klass)) {
|
2023-02-15 12:42:52 +03:00
|
|
|
klass = RCLASS_ATTACHED_OBJECT(klass);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
argv[0] = eventname;
|
|
|
|
argv[1] = filename;
|
|
|
|
argv[2] = INT2FIX(line);
|
|
|
|
argv[3] = id ? ID2SYM(id) : Qnil;
|
2022-04-07 05:14:03 +03:00
|
|
|
argv[4] = Qnil;
|
|
|
|
if (self && (filename != Qnil) &&
|
|
|
|
event != RUBY_EVENT_C_CALL &&
|
|
|
|
event != RUBY_EVENT_C_RETURN &&
|
|
|
|
(VM_FRAME_RUBYFRAME_P(ec->cfp) && imemo_type_p((VALUE)ec->cfp->iseq, imemo_iseq))) {
|
|
|
|
argv[4] = rb_binding_new();
|
|
|
|
}
|
2012-08-15 08:39:10 +04:00
|
|
|
argv[5] = klass ? klass : Qnil;
|
|
|
|
|
2012-08-16 15:41:24 +04:00
|
|
|
rb_proc_call_with_block(proc, 6, argv, Qnil);
|
2012-08-15 08:39:10 +04:00
|
|
|
}
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
/* (2-2) TracePoint API */
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
static VALUE rb_cTracePoint;
|
|
|
|
|
|
|
|
typedef struct rb_tp_struct {
|
|
|
|
rb_event_flag_t events;
|
2014-10-03 00:59:40 +04:00
|
|
|
int tracing; /* bool */
|
2012-08-22 09:12:31 +04:00
|
|
|
rb_thread_t *target_th;
|
2018-11-26 21:16:39 +03:00
|
|
|
VALUE local_target_set; /* Hash: target ->
|
|
|
|
* Qtrue (if target is iseq) or
|
|
|
|
* Qfalse (if target is bmethod)
|
|
|
|
*/
|
2012-11-20 16:57:49 +04:00
|
|
|
void (*func)(VALUE tpval, void *data);
|
|
|
|
void *data;
|
2012-08-22 09:12:31 +04:00
|
|
|
VALUE proc;
|
2020-12-19 00:38:58 +03:00
|
|
|
rb_ractor_t *ractor;
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE self;
|
2012-08-22 09:12:31 +04:00
|
|
|
} rb_tp_t;
|
|
|
|
|
|
|
|
static void
|
|
|
|
tp_mark(void *ptr)
|
|
|
|
{
|
2015-07-15 11:29:22 +03:00
|
|
|
rb_tp_t *tp = ptr;
|
|
|
|
rb_gc_mark(tp->proc);
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_gc_mark(tp->local_target_set);
|
2015-07-15 11:29:22 +03:00
|
|
|
if (tp->target_th) rb_gc_mark(tp->target_th->self);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static const rb_data_type_t tp_data_type = {
|
|
|
|
"tracepoint",
|
2023-11-22 17:24:25 +03:00
|
|
|
{
|
|
|
|
tp_mark,
|
|
|
|
RUBY_TYPED_DEFAULT_FREE,
|
|
|
|
NULL, // Nothing allocated externally, so don't need a memsize function
|
|
|
|
},
|
|
|
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
|
2012-08-22 09:12:31 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
tp_alloc(VALUE klass)
|
|
|
|
{
|
|
|
|
rb_tp_t *tp;
|
|
|
|
return TypedData_Make_Struct(klass, rb_tp_t, &tp_data_type, tp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static rb_event_flag_t
|
|
|
|
symbol2event_flag(VALUE v)
|
|
|
|
{
|
2015-04-26 02:45:02 +03:00
|
|
|
ID id;
|
2017-10-26 10:23:23 +03:00
|
|
|
VALUE sym = rb_to_symbol_type(v);
|
2015-04-26 02:55:34 +03:00
|
|
|
const rb_event_flag_t RUBY_EVENT_A_CALL =
|
|
|
|
RUBY_EVENT_CALL | RUBY_EVENT_B_CALL | RUBY_EVENT_C_CALL;
|
|
|
|
const rb_event_flag_t RUBY_EVENT_A_RETURN =
|
|
|
|
RUBY_EVENT_RETURN | RUBY_EVENT_B_RETURN | RUBY_EVENT_C_RETURN;
|
2012-08-22 09:12:31 +04:00
|
|
|
|
|
|
|
#define C(name, NAME) CONST_ID(id, #name); if (sym == ID2SYM(id)) return RUBY_EVENT_##NAME
|
|
|
|
C(line, LINE);
|
|
|
|
C(class, CLASS);
|
|
|
|
C(end, END);
|
|
|
|
C(call, CALL);
|
|
|
|
C(return, RETURN);
|
|
|
|
C(c_call, C_CALL);
|
|
|
|
C(c_return, C_RETURN);
|
|
|
|
C(raise, RAISE);
|
2012-11-30 02:28:16 +04:00
|
|
|
C(b_call, B_CALL);
|
|
|
|
C(b_return, B_RETURN);
|
|
|
|
C(thread_begin, THREAD_BEGIN);
|
|
|
|
C(thread_end, THREAD_END);
|
2015-08-21 12:51:01 +03:00
|
|
|
C(fiber_switch, FIBER_SWITCH);
|
2018-12-06 16:42:32 +03:00
|
|
|
C(script_compiled, SCRIPT_COMPILED);
|
2023-08-01 11:25:20 +03:00
|
|
|
C(rescue, RESCUE);
|
2018-12-06 16:42:32 +03:00
|
|
|
|
|
|
|
/* joke */
|
2015-04-26 02:55:34 +03:00
|
|
|
C(a_call, A_CALL);
|
|
|
|
C(a_return, A_RETURN);
|
2012-08-22 09:12:31 +04:00
|
|
|
#undef C
|
2014-07-30 17:17:45 +04:00
|
|
|
rb_raise(rb_eArgError, "unknown event: %"PRIsVALUE, rb_sym2str(sym));
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static rb_tp_t *
|
|
|
|
tpptr(VALUE tpval)
|
|
|
|
{
|
|
|
|
rb_tp_t *tp;
|
|
|
|
TypedData_Get_Struct(tpval, rb_tp_t, &tp_data_type, tp);
|
|
|
|
return tp;
|
|
|
|
}
|
|
|
|
|
2012-12-21 13:33:44 +04:00
|
|
|
static rb_trace_arg_t *
|
|
|
|
get_trace_arg(void)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2017-10-26 17:44:09 +03:00
|
|
|
rb_trace_arg_t *trace_arg = GET_EC()->trace_arg;
|
2012-12-21 13:33:44 +04:00
|
|
|
if (trace_arg == 0) {
|
2012-08-22 09:12:31 +04:00
|
|
|
rb_raise(rb_eRuntimeError, "access from outside");
|
|
|
|
}
|
2012-12-21 13:33:44 +04:00
|
|
|
return trace_arg;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
2012-11-29 10:43:31 +04:00
|
|
|
struct rb_trace_arg_struct *
|
|
|
|
rb_tracearg_from_tracepoint(VALUE tpval)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return get_trace_arg();
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
2013-05-27 04:21:02 +04:00
|
|
|
rb_event_flag_t
|
|
|
|
rb_tracearg_event_flag(rb_trace_arg_t *trace_arg)
|
|
|
|
{
|
|
|
|
return trace_arg->event;
|
|
|
|
}
|
|
|
|
|
2012-11-29 10:43:31 +04:00
|
|
|
VALUE
|
|
|
|
rb_tracearg_event(rb_trace_arg_t *trace_arg)
|
|
|
|
{
|
|
|
|
return ID2SYM(get_event_id(trace_arg->event));
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
2012-11-27 03:01:45 +04:00
|
|
|
static void
|
2012-11-27 03:25:21 +04:00
|
|
|
fill_path_and_lineno(rb_trace_arg_t *trace_arg)
|
2012-11-27 03:01:45 +04:00
|
|
|
{
|
2022-11-15 07:24:08 +03:00
|
|
|
if (UNDEF_P(trace_arg->path)) {
|
2017-11-14 15:58:36 +03:00
|
|
|
get_path_and_lineno(trace_arg->ec, trace_arg->cfp, trace_arg->event, &trace_arg->path, &trace_arg->lineno);
|
2012-11-27 03:01:45 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_lineno(rb_trace_arg_t *trace_arg)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2012-11-29 10:43:31 +04:00
|
|
|
fill_path_and_lineno(trace_arg);
|
|
|
|
return INT2FIX(trace_arg->lineno);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_path(rb_trace_arg_t *trace_arg)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2012-11-29 10:43:31 +04:00
|
|
|
fill_path_and_lineno(trace_arg);
|
|
|
|
return trace_arg->path;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fill_id_and_klass(rb_trace_arg_t *trace_arg)
|
|
|
|
{
|
2012-11-27 03:01:45 +04:00
|
|
|
if (!trace_arg->klass_solved) {
|
|
|
|
if (!trace_arg->klass) {
|
* vm_trace.c (tracepoint_attr_callee_id, rb_tracearg_callee_id):
add TracePoint#callee_id. [ruby-core:77241] [Feature #12747]
* cont.c, eval.c, gc.c, include/ruby/intern.h, insns.def, thread.c,
vm.c, vm_backtrace.c, vm_core.h, vm_eval.c, vm_insnhelper.c, vm_trace.c: ditto.
* test/ruby/test_settracefunc.rb: tests for above.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@56593 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2016-11-05 16:15:27 +03:00
|
|
|
rb_vm_control_frame_id_and_class(trace_arg->cfp, &trace_arg->id, &trace_arg->called_id, &trace_arg->klass);
|
2012-11-27 03:01:45 +04:00
|
|
|
}
|
2012-08-22 09:12:31 +04:00
|
|
|
|
2012-11-27 03:01:45 +04:00
|
|
|
if (trace_arg->klass) {
|
|
|
|
if (RB_TYPE_P(trace_arg->klass, T_ICLASS)) {
|
|
|
|
trace_arg->klass = RBASIC(trace_arg->klass)->klass;
|
|
|
|
}
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-11-27 03:01:45 +04:00
|
|
|
else {
|
|
|
|
trace_arg->klass = Qnil;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-11-27 03:01:45 +04:00
|
|
|
|
|
|
|
trace_arg->klass_solved = 1;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-03 08:10:41 +03:00
|
|
|
VALUE
|
|
|
|
rb_tracearg_parameters(rb_trace_arg_t *trace_arg)
|
|
|
|
{
|
2021-06-16 16:07:05 +03:00
|
|
|
switch (trace_arg->event) {
|
2018-06-03 08:10:41 +03:00
|
|
|
case RUBY_EVENT_CALL:
|
|
|
|
case RUBY_EVENT_RETURN:
|
|
|
|
case RUBY_EVENT_B_CALL:
|
|
|
|
case RUBY_EVENT_B_RETURN: {
|
|
|
|
const rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(trace_arg->ec, trace_arg->cfp);
|
|
|
|
if (cfp) {
|
|
|
|
int is_proc = 0;
|
|
|
|
if (VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_BLOCK && !VM_FRAME_LAMBDA_P(cfp)) {
|
|
|
|
is_proc = 1;
|
|
|
|
}
|
|
|
|
return rb_iseq_parameters(cfp->iseq, is_proc);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case RUBY_EVENT_C_CALL:
|
|
|
|
case RUBY_EVENT_C_RETURN: {
|
|
|
|
fill_id_and_klass(trace_arg);
|
|
|
|
if (trace_arg->klass && trace_arg->id) {
|
|
|
|
const rb_method_entry_t *me;
|
|
|
|
VALUE iclass = Qnil;
|
2023-01-04 21:15:59 +03:00
|
|
|
me = rb_method_entry_without_refinements(trace_arg->klass, trace_arg->called_id, &iclass);
|
2018-06-03 08:10:41 +03:00
|
|
|
return rb_unnamed_parameters(rb_method_entry_arity(me));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case RUBY_EVENT_RAISE:
|
|
|
|
case RUBY_EVENT_LINE:
|
|
|
|
case RUBY_EVENT_CLASS:
|
|
|
|
case RUBY_EVENT_END:
|
2018-12-06 16:42:32 +03:00
|
|
|
case RUBY_EVENT_SCRIPT_COMPILED:
|
2023-08-01 11:25:20 +03:00
|
|
|
case RUBY_EVENT_RESCUE:
|
2018-06-03 08:10:41 +03:00
|
|
|
rb_raise(rb_eRuntimeError, "not supported by this event");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_method_id(rb_trace_arg_t *trace_arg)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2012-11-29 10:43:31 +04:00
|
|
|
fill_id_and_klass(trace_arg);
|
|
|
|
return trace_arg->id ? ID2SYM(trace_arg->id) : Qnil;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
* vm_trace.c (tracepoint_attr_callee_id, rb_tracearg_callee_id):
add TracePoint#callee_id. [ruby-core:77241] [Feature #12747]
* cont.c, eval.c, gc.c, include/ruby/intern.h, insns.def, thread.c,
vm.c, vm_backtrace.c, vm_core.h, vm_eval.c, vm_insnhelper.c, vm_trace.c: ditto.
* test/ruby/test_settracefunc.rb: tests for above.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@56593 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2016-11-05 16:15:27 +03:00
|
|
|
VALUE
|
|
|
|
rb_tracearg_callee_id(rb_trace_arg_t *trace_arg)
|
|
|
|
{
|
|
|
|
fill_id_and_klass(trace_arg);
|
|
|
|
return trace_arg->called_id ? ID2SYM(trace_arg->called_id) : Qnil;
|
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_defined_class(rb_trace_arg_t *trace_arg)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2012-11-29 10:43:31 +04:00
|
|
|
fill_id_and_klass(trace_arg);
|
|
|
|
return trace_arg->klass;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_binding(rb_trace_arg_t *trace_arg)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
|
|
|
rb_control_frame_t *cfp;
|
2022-12-22 08:02:43 +03:00
|
|
|
switch (trace_arg->event) {
|
|
|
|
case RUBY_EVENT_C_CALL:
|
|
|
|
case RUBY_EVENT_C_RETURN:
|
|
|
|
return Qnil;
|
|
|
|
}
|
2017-10-29 16:17:37 +03:00
|
|
|
cfp = rb_vm_get_binding_creatable_next_cfp(trace_arg->ec, trace_arg->cfp);
|
2013-02-05 10:04:59 +04:00
|
|
|
|
2022-04-07 05:14:03 +03:00
|
|
|
if (cfp && imemo_type_p((VALUE)cfp->iseq, imemo_iseq)) {
|
2017-10-29 16:17:37 +03:00
|
|
|
return rb_vm_make_binding(trace_arg->ec, cfp);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_self(rb_trace_arg_t *trace_arg)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2012-11-29 10:43:31 +04:00
|
|
|
return trace_arg->self;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_return_value(rb_trace_arg_t *trace_arg)
|
2012-11-20 13:48:24 +04:00
|
|
|
{
|
2012-11-30 02:28:16 +04:00
|
|
|
if (trace_arg->event & (RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN | RUBY_EVENT_B_RETURN)) {
|
2012-11-20 13:48:24 +04:00
|
|
|
/* ok */
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rb_raise(rb_eRuntimeError, "not supported by this event");
|
|
|
|
}
|
2022-11-15 07:24:08 +03:00
|
|
|
if (UNDEF_P(trace_arg->data)) {
|
2018-01-31 03:19:33 +03:00
|
|
|
rb_bug("rb_tracearg_return_value: unreachable");
|
2012-11-24 08:12:30 +04:00
|
|
|
}
|
2012-11-29 10:43:31 +04:00
|
|
|
return trace_arg->data;
|
2012-11-20 13:48:24 +04:00
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_tracearg_raised_exception(rb_trace_arg_t *trace_arg)
|
2012-11-20 13:48:24 +04:00
|
|
|
{
|
2023-08-01 11:25:20 +03:00
|
|
|
if (trace_arg->event & (RUBY_EVENT_RAISE | RUBY_EVENT_RESCUE)) {
|
2012-11-20 13:48:24 +04:00
|
|
|
/* ok */
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rb_raise(rb_eRuntimeError, "not supported by this event");
|
|
|
|
}
|
2022-11-15 07:24:08 +03:00
|
|
|
if (UNDEF_P(trace_arg->data)) {
|
2018-01-31 03:19:33 +03:00
|
|
|
rb_bug("rb_tracearg_raised_exception: unreachable");
|
2013-05-27 04:21:02 +04:00
|
|
|
}
|
|
|
|
return trace_arg->data;
|
|
|
|
}
|
|
|
|
|
2018-12-06 16:42:32 +03:00
|
|
|
VALUE
|
2018-12-12 18:45:06 +03:00
|
|
|
rb_tracearg_eval_script(rb_trace_arg_t *trace_arg)
|
2018-12-06 16:42:32 +03:00
|
|
|
{
|
|
|
|
VALUE data = trace_arg->data;
|
|
|
|
|
|
|
|
if (trace_arg->event & (RUBY_EVENT_SCRIPT_COMPILED)) {
|
2018-12-06 16:42:35 +03:00
|
|
|
/* ok */
|
2018-12-06 16:42:32 +03:00
|
|
|
}
|
|
|
|
else {
|
2018-12-06 16:42:35 +03:00
|
|
|
rb_raise(rb_eRuntimeError, "not supported by this event");
|
2018-12-06 16:42:32 +03:00
|
|
|
}
|
2022-11-15 07:24:08 +03:00
|
|
|
if (UNDEF_P(data)) {
|
2018-12-06 16:42:32 +03:00
|
|
|
rb_bug("rb_tracearg_raised_exception: unreachable");
|
|
|
|
}
|
|
|
|
if (rb_obj_is_iseq(data)) {
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
VM_ASSERT(RB_TYPE_P(data, T_ARRAY));
|
|
|
|
/* [src, iseq] */
|
|
|
|
return RARRAY_AREF(data, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VALUE
|
2018-12-12 18:45:06 +03:00
|
|
|
rb_tracearg_instruction_sequence(rb_trace_arg_t *trace_arg)
|
2018-12-06 16:42:32 +03:00
|
|
|
{
|
|
|
|
VALUE data = trace_arg->data;
|
|
|
|
|
|
|
|
if (trace_arg->event & (RUBY_EVENT_SCRIPT_COMPILED)) {
|
2018-12-06 16:42:35 +03:00
|
|
|
/* ok */
|
2018-12-06 16:42:32 +03:00
|
|
|
}
|
|
|
|
else {
|
2018-12-06 16:42:35 +03:00
|
|
|
rb_raise(rb_eRuntimeError, "not supported by this event");
|
2018-12-06 16:42:32 +03:00
|
|
|
}
|
2022-11-15 07:24:08 +03:00
|
|
|
if (UNDEF_P(data)) {
|
2018-12-06 16:42:32 +03:00
|
|
|
rb_bug("rb_tracearg_raised_exception: unreachable");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rb_obj_is_iseq(data)) {
|
|
|
|
return rb_iseqw_new((const rb_iseq_t *)data);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
VM_ASSERT(RB_TYPE_P(data, T_ARRAY));
|
|
|
|
VM_ASSERT(rb_obj_is_iseq(RARRAY_AREF(data, 1)));
|
|
|
|
|
|
|
|
/* [src, iseq] */
|
|
|
|
return rb_iseqw_new((const rb_iseq_t *)RARRAY_AREF(data, 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-27 04:21:02 +04:00
|
|
|
VALUE
|
|
|
|
rb_tracearg_object(rb_trace_arg_t *trace_arg)
|
|
|
|
{
|
2013-05-27 14:28:25 +04:00
|
|
|
if (trace_arg->event & (RUBY_INTERNAL_EVENT_NEWOBJ | RUBY_INTERNAL_EVENT_FREEOBJ)) {
|
2013-05-27 04:21:02 +04:00
|
|
|
/* ok */
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rb_raise(rb_eRuntimeError, "not supported by this event");
|
|
|
|
}
|
2022-11-15 07:24:08 +03:00
|
|
|
if (UNDEF_P(trace_arg->data)) {
|
2018-01-31 03:19:33 +03:00
|
|
|
rb_bug("rb_tracearg_object: unreachable");
|
2012-11-24 08:12:30 +04:00
|
|
|
}
|
2012-11-29 10:43:31 +04:00
|
|
|
return trace_arg->data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_event(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_event(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_lineno(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_lineno(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_path(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_path(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
2018-06-03 08:10:41 +03:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_parameters(rb_execution_context_t *ec, VALUE tpval)
|
2018-06-03 08:10:41 +03:00
|
|
|
{
|
|
|
|
return rb_tracearg_parameters(get_trace_arg());
|
|
|
|
}
|
|
|
|
|
2012-11-29 10:43:31 +04:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_method_id(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_method_id(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
* vm_trace.c (tracepoint_attr_callee_id, rb_tracearg_callee_id):
add TracePoint#callee_id. [ruby-core:77241] [Feature #12747]
* cont.c, eval.c, gc.c, include/ruby/intern.h, insns.def, thread.c,
vm.c, vm_backtrace.c, vm_core.h, vm_eval.c, vm_insnhelper.c, vm_trace.c: ditto.
* test/ruby/test_settracefunc.rb: tests for above.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@56593 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2016-11-05 16:15:27 +03:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_callee_id(rb_execution_context_t *ec, VALUE tpval)
|
* vm_trace.c (tracepoint_attr_callee_id, rb_tracearg_callee_id):
add TracePoint#callee_id. [ruby-core:77241] [Feature #12747]
* cont.c, eval.c, gc.c, include/ruby/intern.h, insns.def, thread.c,
vm.c, vm_backtrace.c, vm_core.h, vm_eval.c, vm_insnhelper.c, vm_trace.c: ditto.
* test/ruby/test_settracefunc.rb: tests for above.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@56593 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2016-11-05 16:15:27 +03:00
|
|
|
{
|
|
|
|
return rb_tracearg_callee_id(get_trace_arg());
|
|
|
|
}
|
|
|
|
|
2012-11-29 10:43:31 +04:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_defined_class(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_defined_class(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_binding(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_binding(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_self(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_self(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_return_value(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_return_value(get_trace_arg());
|
2012-11-29 10:43:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_raised_exception(rb_execution_context_t *ec, VALUE tpval)
|
2012-11-29 10:43:31 +04:00
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
return rb_tracearg_raised_exception(get_trace_arg());
|
2012-11-20 13:48:24 +04:00
|
|
|
}
|
|
|
|
|
2018-12-06 16:42:32 +03:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_eval_script(rb_execution_context_t *ec, VALUE tpval)
|
2018-12-06 16:42:32 +03:00
|
|
|
{
|
2018-12-12 18:45:06 +03:00
|
|
|
return rb_tracearg_eval_script(get_trace_arg());
|
2018-12-06 16:42:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_attr_instruction_sequence(rb_execution_context_t *ec, VALUE tpval)
|
2018-12-06 16:42:32 +03:00
|
|
|
{
|
2018-12-12 18:45:06 +03:00
|
|
|
return rb_tracearg_instruction_sequence(get_trace_arg());
|
2018-12-06 16:42:32 +03:00
|
|
|
}
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
static void
|
|
|
|
tp_call_trace(VALUE tpval, rb_trace_arg_t *trace_arg)
|
|
|
|
{
|
|
|
|
rb_tp_t *tp = tpptr(tpval);
|
|
|
|
|
2012-12-21 13:33:44 +04:00
|
|
|
if (tp->func) {
|
|
|
|
(*tp->func)(tpval, tp->data);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-12-21 13:33:44 +04:00
|
|
|
else {
|
2020-12-19 00:38:58 +03:00
|
|
|
if (tp->ractor == NULL || tp->ractor == GET_RACTOR()) {
|
|
|
|
rb_proc_call_with_block((VALUE)tp->proc, 1, &tpval, Qnil);
|
|
|
|
}
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
|
|
|
rb_tracepoint_enable(VALUE tpval)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2013-01-29 11:51:00 +04:00
|
|
|
rb_tp_t *tp;
|
|
|
|
tp = tpptr(tpval);
|
2012-08-22 09:12:31 +04:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
if (tp->local_target_set != Qfalse) {
|
2019-03-28 02:56:14 +03:00
|
|
|
rb_raise(rb_eArgError, "can't nest-enable a targeting TracePoint");
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
|
|
|
|
2023-11-22 12:12:23 +03:00
|
|
|
if (tp->tracing) {
|
|
|
|
return Qundef;
|
|
|
|
}
|
|
|
|
|
2012-11-20 15:05:20 +04:00
|
|
|
if (tp->target_th) {
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_thread_add_event_hook2(tp->target_th->self, (rb_event_hook_func_t)tp_call_trace, tp->events, tpval,
|
|
|
|
RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
else {
|
2012-11-29 10:43:31 +04:00
|
|
|
rb_add_event_hook2((rb_event_hook_func_t)tp_call_trace, tp->events, tpval,
|
|
|
|
RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-11-20 15:05:20 +04:00
|
|
|
tp->tracing = 1;
|
|
|
|
return Qundef;
|
|
|
|
}
|
2012-08-22 09:12:31 +04:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
static const rb_iseq_t *
|
|
|
|
iseq_of(VALUE target)
|
|
|
|
{
|
|
|
|
VALUE iseqv = rb_funcall(rb_cISeq, rb_intern("of"), 1, target);
|
|
|
|
if (NIL_P(iseqv)) {
|
|
|
|
rb_raise(rb_eArgError, "specified target is not supported");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return rb_iseqw_to_iseq(iseqv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const rb_method_definition_t *rb_method_def(VALUE method); /* proc.c */
|
|
|
|
|
|
|
|
static VALUE
|
2018-11-26 23:16:14 +03:00
|
|
|
rb_tracepoint_enable_for_target(VALUE tpval, VALUE target, VALUE target_line)
|
2018-11-26 21:16:39 +03:00
|
|
|
{
|
|
|
|
rb_tp_t *tp = tpptr(tpval);
|
|
|
|
const rb_iseq_t *iseq = iseq_of(target);
|
2021-07-14 02:01:09 +03:00
|
|
|
int n = 0;
|
2018-11-26 23:16:14 +03:00
|
|
|
unsigned int line = 0;
|
2021-07-14 02:01:09 +03:00
|
|
|
bool target_bmethod = false;
|
2018-11-26 21:16:39 +03:00
|
|
|
|
|
|
|
if (tp->tracing > 0) {
|
2019-03-28 02:56:14 +03:00
|
|
|
rb_raise(rb_eArgError, "can't nest-enable a targeting TracePoint");
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
|
|
|
|
2018-11-26 23:16:14 +03:00
|
|
|
if (!NIL_P(target_line)) {
|
|
|
|
if ((tp->events & RUBY_EVENT_LINE) == 0) {
|
|
|
|
rb_raise(rb_eArgError, "target_line is specified, but line event is not specified");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
line = NUM2UINT(target_line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
VM_ASSERT(tp->local_target_set == Qfalse);
|
2023-11-22 00:33:19 +03:00
|
|
|
RB_OBJ_WRITE(tpval, &tp->local_target_set, rb_obj_hide(rb_ident_hash_new()));
|
2018-11-26 21:16:39 +03:00
|
|
|
|
|
|
|
/* bmethod */
|
|
|
|
if (rb_obj_is_method(target)) {
|
|
|
|
rb_method_definition_t *def = (rb_method_definition_t *)rb_method_def(target);
|
|
|
|
if (def->type == VM_METHOD_TYPE_BMETHOD &&
|
|
|
|
(tp->events & (RUBY_EVENT_CALL | RUBY_EVENT_RETURN))) {
|
2021-07-15 02:44:26 +03:00
|
|
|
if (def->body.bmethod.hooks == NULL) {
|
|
|
|
def->body.bmethod.hooks = ZALLOC(rb_hook_list_t);
|
2024-01-19 18:26:24 +03:00
|
|
|
def->body.bmethod.hooks->is_local = true;
|
2021-07-15 02:44:26 +03:00
|
|
|
}
|
2018-11-26 23:16:14 +03:00
|
|
|
rb_hook_list_connect_tracepoint(target, def->body.bmethod.hooks, tpval, 0);
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_hash_aset(tp->local_target_set, target, Qfalse);
|
2021-07-14 02:01:09 +03:00
|
|
|
target_bmethod = true;
|
2018-11-26 21:16:39 +03:00
|
|
|
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-14 02:01:09 +03:00
|
|
|
/* iseq */
|
|
|
|
n += rb_iseq_add_local_tracepoint_recursively(iseq, tp->events, tpval, line, target_bmethod);
|
|
|
|
rb_hash_aset(tp->local_target_set, (VALUE)iseq, Qtrue);
|
|
|
|
|
2023-03-09 19:30:30 +03:00
|
|
|
if ((tp->events & (RUBY_EVENT_CALL | RUBY_EVENT_RETURN)) &&
|
2024-01-17 04:31:26 +03:00
|
|
|
iseq->body->builtin_attrs & BUILTIN_ATTR_SINGLE_NOARG_LEAF) {
|
2023-03-09 19:30:30 +03:00
|
|
|
rb_clear_bf_ccs();
|
|
|
|
}
|
2021-07-14 02:01:09 +03:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
if (n == 0) {
|
|
|
|
rb_raise(rb_eArgError, "can not enable any hooks");
|
|
|
|
}
|
|
|
|
|
2021-10-02 01:38:39 +03:00
|
|
|
rb_yjit_tracing_invalidate_all();
|
2023-03-07 10:17:25 +03:00
|
|
|
rb_rjit_tracing_invalidate_all(tp->events);
|
2021-08-26 00:00:45 +03:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
ruby_vm_event_local_num++;
|
|
|
|
|
|
|
|
tp->tracing = 1;
|
|
|
|
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
disable_local_event_iseq_i(VALUE target, VALUE iseq_p, VALUE tpval)
|
|
|
|
{
|
|
|
|
if (iseq_p) {
|
|
|
|
rb_iseq_remove_local_tracepoint_recursively((rb_iseq_t *)target, tpval);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* bmethod */
|
|
|
|
rb_method_definition_t *def = (rb_method_definition_t *)rb_method_def(target);
|
|
|
|
rb_hook_list_t *hooks = def->body.bmethod.hooks;
|
|
|
|
VM_ASSERT(hooks != NULL);
|
|
|
|
rb_hook_list_remove_tracepoint(hooks, tpval);
|
2021-12-12 20:15:05 +03:00
|
|
|
|
|
|
|
if (hooks->events == 0) {
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_hook_list_free(def->body.bmethod.hooks);
|
2021-12-12 20:15:05 +03:00
|
|
|
def->body.bmethod.hooks = NULL;
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ST_CONTINUE;
|
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
|
|
|
rb_tracepoint_disable(VALUE tpval)
|
2012-11-20 15:05:20 +04:00
|
|
|
{
|
2013-01-29 11:51:00 +04:00
|
|
|
rb_tp_t *tp;
|
|
|
|
|
|
|
|
tp = tpptr(tpval);
|
2012-11-20 15:05:20 +04:00
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
if (tp->local_target_set) {
|
|
|
|
rb_hash_foreach(tp->local_target_set, disable_local_event_iseq_i, tpval);
|
2023-11-22 00:33:19 +03:00
|
|
|
RB_OBJ_WRITE(tpval, &tp->local_target_set, Qfalse);
|
2018-11-26 21:16:39 +03:00
|
|
|
ruby_vm_event_local_num--;
|
2012-11-20 15:05:20 +04:00
|
|
|
}
|
|
|
|
else {
|
2018-11-26 21:16:39 +03:00
|
|
|
if (tp->target_th) {
|
|
|
|
rb_thread_remove_event_hook_with_data(tp->target_th->self, (rb_event_hook_func_t)tp_call_trace, tpval);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rb_remove_event_hook_with_data((rb_event_hook_func_t)tp_call_trace, tpval);
|
|
|
|
}
|
2012-11-20 15:05:20 +04:00
|
|
|
}
|
|
|
|
tp->tracing = 0;
|
2018-12-29 19:44:09 +03:00
|
|
|
tp->target_th = NULL;
|
2012-11-20 15:05:20 +04:00
|
|
|
return Qundef;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
void
|
2018-11-26 23:16:14 +03:00
|
|
|
rb_hook_list_connect_tracepoint(VALUE target, rb_hook_list_t *list, VALUE tpval, unsigned int target_line)
|
2018-11-26 21:16:39 +03:00
|
|
|
{
|
|
|
|
rb_tp_t *tp = tpptr(tpval);
|
2024-08-16 17:27:28 +03:00
|
|
|
rb_event_hook_t *hook = alloc_event_hook((rb_event_hook_func_t)tp_call_trace, tp->events & ISEQ_TRACE_EVENTS, tpval,
|
2018-11-26 21:16:39 +03:00
|
|
|
RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG);
|
2018-11-26 23:16:14 +03:00
|
|
|
hook->filter.target_line = target_line;
|
2018-11-26 21:16:39 +03:00
|
|
|
hook_list_connect(target, list, hook, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
rb_hook_list_remove_tracepoint(rb_hook_list_t *list, VALUE tpval)
|
|
|
|
{
|
|
|
|
rb_event_hook_t *hook = list->hooks;
|
|
|
|
rb_event_flag_t events = 0;
|
|
|
|
|
|
|
|
while (hook) {
|
|
|
|
if (hook->data == tpval) {
|
|
|
|
hook->hook_flags |= RUBY_EVENT_HOOK_FLAG_DELETED;
|
2021-12-12 20:15:05 +03:00
|
|
|
list->need_clean = true;
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
2021-12-12 20:15:05 +03:00
|
|
|
else if ((hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED) == 0) {
|
2018-11-26 21:16:39 +03:00
|
|
|
events |= hook->events;
|
|
|
|
}
|
|
|
|
hook = hook->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
list->events = events;
|
|
|
|
}
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_enable_m(rb_execution_context_t *ec, VALUE tpval, VALUE target, VALUE target_line, VALUE target_thread)
|
2012-11-20 15:05:20 +04:00
|
|
|
{
|
|
|
|
rb_tp_t *tp = tpptr(tpval);
|
2012-12-06 07:13:50 +04:00
|
|
|
int previous_tracing = tp->tracing;
|
2018-11-26 21:16:39 +03:00
|
|
|
|
2021-12-28 01:08:59 +03:00
|
|
|
if (target_thread == sym_default) {
|
|
|
|
if (rb_block_given_p() && NIL_P(target) && NIL_P(target_line)) {
|
|
|
|
target_thread = rb_thread_current();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
target_thread = Qnil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-29 19:44:09 +03:00
|
|
|
/* check target_thread */
|
|
|
|
if (RTEST(target_thread)) {
|
|
|
|
if (tp->target_th) {
|
|
|
|
rb_raise(rb_eArgError, "can not override target_thread filter");
|
|
|
|
}
|
|
|
|
tp->target_th = rb_thread_ptr(target_thread);
|
2023-11-22 00:33:19 +03:00
|
|
|
|
|
|
|
RUBY_ASSERT(tp->target_th->self == target_thread);
|
|
|
|
RB_OBJ_WRITTEN(tpval, Qundef, target_thread);
|
2018-12-29 19:44:09 +03:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
tp->target_th = NULL;
|
|
|
|
}
|
|
|
|
|
2018-11-26 21:16:39 +03:00
|
|
|
if (NIL_P(target)) {
|
2018-11-26 23:16:14 +03:00
|
|
|
if (!NIL_P(target_line)) {
|
|
|
|
rb_raise(rb_eArgError, "only target_line is specified");
|
|
|
|
}
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_tracepoint_enable(tpval);
|
|
|
|
}
|
|
|
|
else {
|
2018-11-26 23:16:14 +03:00
|
|
|
rb_tracepoint_enable_for_target(tpval, target, target_line);
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
2012-12-06 07:13:50 +04:00
|
|
|
|
2012-11-20 15:05:20 +04:00
|
|
|
if (rb_block_given_p()) {
|
2017-10-26 09:58:09 +03:00
|
|
|
return rb_ensure(rb_yield, Qundef,
|
2012-12-06 07:13:50 +04:00
|
|
|
previous_tracing ? rb_tracepoint_enable : rb_tracepoint_disable,
|
|
|
|
tpval);
|
2012-11-20 15:05:20 +04:00
|
|
|
}
|
|
|
|
else {
|
2021-08-02 06:06:44 +03:00
|
|
|
return RBOOL(previous_tracing);
|
2012-11-20 15:05:20 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_disable_m(rb_execution_context_t *ec, VALUE tpval)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
|
|
|
rb_tp_t *tp = tpptr(tpval);
|
2012-12-06 07:13:50 +04:00
|
|
|
int previous_tracing = tp->tracing;
|
|
|
|
|
2012-11-20 15:05:20 +04:00
|
|
|
if (rb_block_given_p()) {
|
2018-11-26 21:16:39 +03:00
|
|
|
if (tp->local_target_set != Qfalse) {
|
2019-03-28 02:56:14 +03:00
|
|
|
rb_raise(rb_eArgError, "can't disable a targeting TracePoint in a block");
|
2018-11-26 21:16:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
rb_tracepoint_disable(tpval);
|
|
|
|
return rb_ensure(rb_yield, Qundef,
|
2012-12-06 07:13:50 +04:00
|
|
|
previous_tracing ? rb_tracepoint_enable : rb_tracepoint_disable,
|
|
|
|
tpval);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
else {
|
2018-11-26 21:16:39 +03:00
|
|
|
rb_tracepoint_disable(tpval);
|
2021-08-02 06:06:44 +03:00
|
|
|
return RBOOL(previous_tracing);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-11-20 15:05:20 +04:00
|
|
|
}
|
2012-08-22 09:12:31 +04:00
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
|
|
|
rb_tracepoint_enabled_p(VALUE tpval)
|
2012-11-20 15:05:20 +04:00
|
|
|
{
|
|
|
|
rb_tp_t *tp = tpptr(tpval);
|
2021-08-02 06:06:44 +03:00
|
|
|
return RBOOL(tp->tracing);
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
2019-11-07 12:22:08 +03:00
|
|
|
static VALUE
|
|
|
|
tracepoint_enabled_p(rb_execution_context_t *ec, VALUE tpval)
|
|
|
|
{
|
|
|
|
return rb_tracepoint_enabled_p(tpval);
|
|
|
|
}
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
static VALUE
|
2012-11-20 16:57:49 +04:00
|
|
|
tracepoint_new(VALUE klass, rb_thread_t *target_th, rb_event_flag_t events, void (func)(VALUE, void*), void *data, VALUE proc)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
2012-11-20 15:05:20 +04:00
|
|
|
VALUE tpval = tp_alloc(klass);
|
2012-08-22 09:12:31 +04:00
|
|
|
rb_tp_t *tp;
|
|
|
|
TypedData_Get_Struct(tpval, rb_tp_t, &tp_data_type, tp);
|
|
|
|
|
2023-11-22 00:33:19 +03:00
|
|
|
RB_OBJ_WRITE(tpval, &tp->proc, proc);
|
2020-12-19 00:38:58 +03:00
|
|
|
tp->ractor = rb_ractor_shareable_p(proc) ? NULL : GET_RACTOR();
|
2012-11-20 16:57:49 +04:00
|
|
|
tp->func = func;
|
|
|
|
tp->data = data;
|
2012-08-22 09:12:31 +04:00
|
|
|
tp->events = events;
|
2012-11-20 16:57:49 +04:00
|
|
|
tp->self = tpval;
|
2012-08-22 09:12:31 +04:00
|
|
|
|
|
|
|
return tpval;
|
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
VALUE
|
2012-12-21 15:22:54 +04:00
|
|
|
rb_tracepoint_new(VALUE target_thval, rb_event_flag_t events, void (*func)(VALUE, void *), void *data)
|
2012-11-20 16:57:49 +04:00
|
|
|
{
|
2017-06-28 07:49:30 +03:00
|
|
|
rb_thread_t *target_th = NULL;
|
|
|
|
|
2012-12-21 15:22:54 +04:00
|
|
|
if (RTEST(target_thval)) {
|
2017-06-28 07:49:30 +03:00
|
|
|
target_th = rb_thread_ptr(target_thval);
|
2012-12-21 15:22:54 +04:00
|
|
|
/* TODO: Test it!
|
|
|
|
* Warning: This function is not tested.
|
|
|
|
*/
|
2012-11-20 16:57:49 +04:00
|
|
|
}
|
|
|
|
return tracepoint_new(rb_cTracePoint, target_th, events, func, data, Qundef);
|
|
|
|
}
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_new_s(rb_execution_context_t *ec, VALUE self, VALUE args)
|
2012-08-22 09:12:31 +04:00
|
|
|
{
|
|
|
|
rb_event_flag_t events = 0;
|
2019-11-07 12:22:08 +03:00
|
|
|
long i;
|
|
|
|
long argc = RARRAY_LEN(args);
|
2012-08-22 09:12:31 +04:00
|
|
|
|
|
|
|
if (argc > 0) {
|
2019-11-07 12:22:08 +03:00
|
|
|
for (i=0; i<argc; i++) {
|
|
|
|
events |= symbol2event_flag(RARRAY_AREF(args, i));
|
|
|
|
}
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
else {
|
2012-11-30 02:28:16 +04:00
|
|
|
events = RUBY_EVENT_TRACEPOINT_ALL;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!rb_block_given_p()) {
|
2017-11-10 01:04:47 +03:00
|
|
|
rb_raise(rb_eArgError, "must be called with a block");
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
|
|
|
|
2012-11-20 16:57:49 +04:00
|
|
|
return tracepoint_new(self, 0, events, 0, 0, rb_block_proc());
|
2012-11-20 15:05:20 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_trace_s(rb_execution_context_t *ec, VALUE self, VALUE args)
|
2012-11-20 15:05:20 +04:00
|
|
|
{
|
2019-11-07 12:22:08 +03:00
|
|
|
VALUE trace = tracepoint_new_s(ec, self, args);
|
2012-11-20 16:57:49 +04:00
|
|
|
rb_tracepoint_enable(trace);
|
2012-11-20 15:05:20 +04:00
|
|
|
return trace;
|
2012-08-22 09:12:31 +04:00
|
|
|
}
|
2012-08-15 08:39:10 +04:00
|
|
|
|
2012-12-01 21:47:37 +04:00
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_inspect(rb_execution_context_t *ec, VALUE self)
|
2012-12-01 21:47:37 +04:00
|
|
|
{
|
|
|
|
rb_tp_t *tp = tpptr(self);
|
2017-10-26 17:44:09 +03:00
|
|
|
rb_trace_arg_t *trace_arg = GET_EC()->trace_arg;
|
2012-12-01 21:47:37 +04:00
|
|
|
|
2012-12-21 13:33:44 +04:00
|
|
|
if (trace_arg) {
|
|
|
|
switch (trace_arg->event) {
|
2012-12-01 21:47:37 +04:00
|
|
|
case RUBY_EVENT_LINE:
|
|
|
|
{
|
2012-12-21 13:33:44 +04:00
|
|
|
VALUE sym = rb_tracearg_method_id(trace_arg);
|
2012-12-01 21:47:37 +04:00
|
|
|
if (NIL_P(sym))
|
2020-06-22 06:28:30 +03:00
|
|
|
break;
|
2024-01-19 10:03:38 +03:00
|
|
|
return rb_sprintf("#<TracePoint:%"PRIsVALUE" %"PRIsVALUE":%d in '%"PRIsVALUE"'>",
|
2012-12-21 13:33:44 +04:00
|
|
|
rb_tracearg_event(trace_arg),
|
|
|
|
rb_tracearg_path(trace_arg),
|
|
|
|
FIX2INT(rb_tracearg_lineno(trace_arg)),
|
2012-12-01 21:47:37 +04:00
|
|
|
sym);
|
|
|
|
}
|
|
|
|
case RUBY_EVENT_CALL:
|
|
|
|
case RUBY_EVENT_C_CALL:
|
|
|
|
case RUBY_EVENT_RETURN:
|
|
|
|
case RUBY_EVENT_C_RETURN:
|
2024-01-19 10:03:38 +03:00
|
|
|
return rb_sprintf("#<TracePoint:%"PRIsVALUE" '%"PRIsVALUE"' %"PRIsVALUE":%d>",
|
2012-12-21 13:33:44 +04:00
|
|
|
rb_tracearg_event(trace_arg),
|
|
|
|
rb_tracearg_method_id(trace_arg),
|
|
|
|
rb_tracearg_path(trace_arg),
|
|
|
|
FIX2INT(rb_tracearg_lineno(trace_arg)));
|
2012-12-01 21:47:37 +04:00
|
|
|
case RUBY_EVENT_THREAD_BEGIN:
|
|
|
|
case RUBY_EVENT_THREAD_END:
|
|
|
|
return rb_sprintf("#<TracePoint:%"PRIsVALUE" %"PRIsVALUE">",
|
2012-12-21 13:33:44 +04:00
|
|
|
rb_tracearg_event(trace_arg),
|
|
|
|
rb_tracearg_self(trace_arg));
|
2012-12-01 21:47:37 +04:00
|
|
|
default:
|
2020-06-22 06:28:30 +03:00
|
|
|
break;
|
2012-12-01 21:47:37 +04:00
|
|
|
}
|
2020-08-06 05:56:24 +03:00
|
|
|
return rb_sprintf("#<TracePoint:%"PRIsVALUE" %"PRIsVALUE":%d>",
|
2020-06-22 06:28:30 +03:00
|
|
|
rb_tracearg_event(trace_arg),
|
|
|
|
rb_tracearg_path(trace_arg),
|
|
|
|
FIX2INT(rb_tracearg_lineno(trace_arg)));
|
2012-12-01 21:47:37 +04:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return rb_sprintf("#<TracePoint:%s>", tp->tracing ? "enabled" : "disabled");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-13 13:01:21 +04:00
|
|
|
static void
|
|
|
|
tracepoint_stat_event_hooks(VALUE hash, VALUE key, rb_event_hook_t *hook)
|
|
|
|
{
|
|
|
|
int active = 0, deleted = 0;
|
|
|
|
|
|
|
|
while (hook) {
|
|
|
|
if (hook->hook_flags & RUBY_EVENT_HOOK_FLAG_DELETED) {
|
|
|
|
deleted++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
active++;
|
|
|
|
}
|
|
|
|
hook = hook->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
rb_hash_aset(hash, key, rb_ary_new3(2, INT2FIX(active), INT2FIX(deleted)));
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
2019-11-07 12:22:08 +03:00
|
|
|
tracepoint_stat_s(rb_execution_context_t *ec, VALUE self)
|
2014-06-13 13:01:21 +04:00
|
|
|
{
|
|
|
|
rb_vm_t *vm = GET_VM();
|
|
|
|
VALUE stat = rb_hash_new();
|
|
|
|
|
2020-12-19 00:38:58 +03:00
|
|
|
tracepoint_stat_event_hooks(stat, vm->self, rb_ec_ractor_hooks(ec)->hooks);
|
2014-06-13 13:01:21 +04:00
|
|
|
/* TODO: thread local hooks */
|
|
|
|
|
|
|
|
return stat;
|
|
|
|
}
|
|
|
|
|
2021-12-08 21:50:17 +03:00
|
|
|
static VALUE
|
|
|
|
disallow_reentry(VALUE val)
|
|
|
|
{
|
|
|
|
rb_trace_arg_t *arg = (rb_trace_arg_t *)val;
|
|
|
|
rb_execution_context_t *ec = GET_EC();
|
|
|
|
if (ec->trace_arg != NULL) rb_bug("should be NULL, but %p", (void *)ec->trace_arg);
|
|
|
|
ec->trace_arg = arg;
|
|
|
|
return Qnil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static VALUE
|
|
|
|
tracepoint_allow_reentry(rb_execution_context_t *ec, VALUE self)
|
|
|
|
{
|
|
|
|
const rb_trace_arg_t *arg = ec->trace_arg;
|
|
|
|
if (arg == NULL) rb_raise(rb_eRuntimeError, "No need to allow reentrance.");
|
|
|
|
ec->trace_arg = NULL;
|
|
|
|
return rb_ensure(rb_yield, Qnil, disallow_reentry, (VALUE)arg);
|
|
|
|
}
|
|
|
|
|
2019-11-08 10:24:24 +03:00
|
|
|
#include "trace_point.rbinc"
|
2019-11-07 12:22:08 +03:00
|
|
|
|
2012-08-15 08:39:10 +04:00
|
|
|
/* This function is called from inits.c */
|
|
|
|
void
|
|
|
|
Init_vm_trace(void)
|
|
|
|
{
|
2021-12-28 01:08:59 +03:00
|
|
|
sym_default = ID2SYM(rb_intern_const("default"));
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
/* trace_func */
|
2012-08-15 08:39:10 +04:00
|
|
|
rb_define_global_function("set_trace_func", set_trace_func, 1);
|
|
|
|
rb_define_method(rb_cThread, "set_trace_func", thread_set_trace_func_m, 1);
|
|
|
|
rb_define_method(rb_cThread, "add_trace_func", thread_add_trace_func_m, 1);
|
|
|
|
|
2012-08-22 09:12:31 +04:00
|
|
|
rb_cTracePoint = rb_define_class("TracePoint", rb_cObject);
|
|
|
|
rb_undef_alloc_func(rb_cTracePoint);
|
|
|
|
}
|
2012-11-29 10:43:31 +04:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/*
|
|
|
|
* Ruby actually has two separate mechanisms for enqueueing work from contexts
|
|
|
|
* where it is not safe to run Ruby code, to run later on when it is safe. One
|
|
|
|
* is async-signal-safe but more limited, and accessed through the
|
|
|
|
* `rb_postponed_job_preregister` and `rb_postponed_job_trigger` functions. The
|
|
|
|
* other is more flexible but cannot be used in signal handlers, and is accessed
|
|
|
|
* through the `rb_workqueue_register` function.
|
|
|
|
*
|
|
|
|
* The postponed job functions form part of Ruby's extension API, but the
|
|
|
|
* workqueue functions are for internal use only.
|
|
|
|
*/
|
2013-10-10 08:56:32 +04:00
|
|
|
|
2018-11-30 06:56:29 +03:00
|
|
|
struct rb_workqueue_job {
|
2022-03-30 10:36:31 +03:00
|
|
|
struct ccan_list_node jnode; /* <=> vm->workqueue */
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
rb_postponed_job_func_t func;
|
|
|
|
void *data;
|
2018-11-30 06:56:29 +03:00
|
|
|
};
|
|
|
|
|
2022-01-11 20:47:22 +03:00
|
|
|
// Used for VM memsize reporting. Returns the size of a list of rb_workqueue_job
|
|
|
|
// structs. Defined here because the struct definition lives here as well.
|
|
|
|
size_t
|
2022-03-30 10:36:31 +03:00
|
|
|
rb_vm_memsize_workqueue(struct ccan_list_head *workqueue)
|
2022-01-11 20:47:22 +03:00
|
|
|
{
|
|
|
|
struct rb_workqueue_job *work = 0;
|
|
|
|
size_t size = 0;
|
|
|
|
|
2022-03-30 10:36:31 +03:00
|
|
|
ccan_list_for_each(workqueue, work, jnode) {
|
2022-01-11 20:47:22 +03:00
|
|
|
size += sizeof(struct rb_workqueue_job);
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/*
|
|
|
|
* thread-safe and called from non-Ruby thread
|
|
|
|
* returns FALSE on failure (ENOMEM), TRUE otherwise
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
rb_workqueue_register(unsigned flags, rb_postponed_job_func_t func, void *data)
|
2013-10-10 08:56:32 +04:00
|
|
|
{
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
struct rb_workqueue_job *wq_job = malloc(sizeof(*wq_job));
|
2013-10-10 08:56:32 +04:00
|
|
|
rb_vm_t *vm = GET_VM();
|
2013-10-22 10:24:54 +04:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
if (!wq_job) return FALSE;
|
|
|
|
wq_job->func = func;
|
|
|
|
wq_job->data = data;
|
2018-10-23 06:47:45 +03:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
rb_nativethread_lock_lock(&vm->workqueue_lock);
|
|
|
|
ccan_list_add_tail(&vm->workqueue, &wq_job->jnode);
|
|
|
|
rb_nativethread_lock_unlock(&vm->workqueue_lock);
|
2018-10-23 06:47:45 +03:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
// TODO: current implementation affects only main ractor
|
|
|
|
RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(rb_vm_main_ractor_ec(vm));
|
2013-10-10 08:56:32 +04:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
return TRUE;
|
|
|
|
}
|
2013-05-27 01:30:44 +04:00
|
|
|
|
2023-12-11 07:00:43 +03:00
|
|
|
#define PJOB_TABLE_SIZE (sizeof(rb_atomic_t) * CHAR_BIT)
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* pre-registered jobs table, for async-safe jobs */
|
|
|
|
typedef struct rb_postponed_job_queue {
|
|
|
|
struct {
|
|
|
|
rb_postponed_job_func_t func;
|
|
|
|
void *data;
|
2023-12-11 07:00:43 +03:00
|
|
|
} table[PJOB_TABLE_SIZE];
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* Bits in this are set when the corresponding entry in prereg_table has non-zero
|
|
|
|
* triggered_count; i.e. somebody called rb_postponed_job_trigger */
|
2023-12-11 07:00:43 +03:00
|
|
|
rb_atomic_t triggered_bitset;
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
} rb_postponed_job_queues_t;
|
2013-10-10 08:56:32 +04:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
void
|
|
|
|
rb_vm_postponed_job_queue_init(rb_vm_t *vm)
|
|
|
|
{
|
|
|
|
/* use mimmalloc; postponed job registration is a dependency of objspace, so this gets
|
|
|
|
* called _VERY_ early inside Init_BareVM */
|
|
|
|
rb_postponed_job_queues_t *pjq = ruby_mimmalloc(sizeof(rb_postponed_job_queues_t));
|
2023-12-11 07:00:43 +03:00
|
|
|
pjq->triggered_bitset = 0;
|
|
|
|
memset(pjq->table, 0, sizeof(pjq->table));
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
vm->postponed_job_queue = pjq;
|
2013-10-22 10:24:54 +04:00
|
|
|
}
|
|
|
|
|
2021-11-08 21:37:17 +03:00
|
|
|
static rb_execution_context_t *
|
|
|
|
get_valid_ec(rb_vm_t *vm)
|
|
|
|
{
|
2021-11-23 03:29:29 +03:00
|
|
|
rb_execution_context_t *ec = rb_current_execution_context(false);
|
2021-11-08 21:37:17 +03:00
|
|
|
if (ec == NULL) ec = rb_vm_main_ractor_ec(vm);
|
|
|
|
return ec;
|
|
|
|
}
|
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
void
|
|
|
|
rb_vm_postponed_job_atfork(void)
|
2013-10-22 10:24:54 +04:00
|
|
|
{
|
2021-11-08 21:37:17 +03:00
|
|
|
rb_vm_t *vm = GET_VM();
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
rb_postponed_job_queues_t *pjq = vm->postponed_job_queue;
|
|
|
|
/* make sure we set the interrupt flag on _this_ thread if we carried any pjobs over
|
|
|
|
* from the other side of the fork */
|
2023-12-11 07:00:43 +03:00
|
|
|
if (pjq->triggered_bitset) {
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(get_valid_ec(vm));
|
2013-10-22 10:24:54 +04:00
|
|
|
}
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
|
2013-05-27 01:30:44 +04:00
|
|
|
}
|
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* Frees the memory managed by the postponed job infrastructure at shutdown */
|
|
|
|
void
|
|
|
|
rb_vm_postponed_job_free(void)
|
2013-05-27 01:30:44 +04:00
|
|
|
{
|
2021-11-08 21:37:17 +03:00
|
|
|
rb_vm_t *vm = GET_VM();
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
ruby_xfree(vm->postponed_job_queue);
|
|
|
|
vm->postponed_job_queue = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used for VM memsize reporting. Returns the total size of the postponed job
|
|
|
|
// queue infrastructure.
|
|
|
|
size_t
|
|
|
|
rb_vm_memsize_postponed_job_queue(void)
|
|
|
|
{
|
|
|
|
return sizeof(rb_postponed_job_queues_t);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rb_postponed_job_handle_t
|
2023-12-09 19:47:14 +03:00
|
|
|
rb_postponed_job_preregister(unsigned int flags, rb_postponed_job_func_t func, void *data)
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
{
|
|
|
|
/* The doc comments say that this function should be called under the GVL, because
|
|
|
|
* that is actually required to get the guarantee that "if a given (func, data) pair
|
|
|
|
* was already pre-registered, this method will return the same handle instance".
|
|
|
|
*
|
|
|
|
* However, the actual implementation here is called without the GVL, from inside
|
|
|
|
* rb_postponed_job_register, to support that legacy interface. In the presence
|
|
|
|
* of concurrent calls to both _preregister and _register functions on the same
|
|
|
|
* func, however, the data may get mixed up between them. */
|
|
|
|
|
|
|
|
rb_postponed_job_queues_t *pjq = GET_VM()->postponed_job_queue;
|
2023-12-11 07:00:43 +03:00
|
|
|
for (unsigned int i = 0; i < PJOB_TABLE_SIZE; i++) {
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* Try and set this slot to equal `func` */
|
2023-12-11 07:00:43 +03:00
|
|
|
rb_postponed_job_func_t existing_func = (rb_postponed_job_func_t)RUBY_ATOMIC_PTR_CAS(pjq->table[i], NULL, (void *)func);
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
if (existing_func == NULL || existing_func == func) {
|
|
|
|
/* Either this slot was NULL, and we set it to func, or, this slot was already equal to func.
|
|
|
|
* In either case, clobber the data with our data. Note that concurrent calls to
|
|
|
|
* rb_postponed_job_register with the same func & different data will result in either of the
|
|
|
|
* datas being written */
|
2023-12-11 07:00:43 +03:00
|
|
|
RUBY_ATOMIC_PTR_EXCHANGE(pjq->table[i].data, data);
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
return (rb_postponed_job_handle_t)i;
|
2024-01-07 18:50:41 +03:00
|
|
|
}
|
|
|
|
else {
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* Try the next slot if this one already has a func in it */
|
|
|
|
continue;
|
2018-10-23 06:47:48 +03:00
|
|
|
}
|
2013-05-27 01:30:44 +04:00
|
|
|
}
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
|
|
|
|
/* full */
|
|
|
|
return POSTPONED_JOB_HANDLE_INVALID;
|
2013-05-27 01:30:44 +04:00
|
|
|
}
|
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
void
|
|
|
|
rb_postponed_job_trigger(rb_postponed_job_handle_t h)
|
2018-11-30 06:56:29 +03:00
|
|
|
{
|
|
|
|
rb_vm_t *vm = GET_VM();
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
rb_postponed_job_queues_t *pjq = vm->postponed_job_queue;
|
2018-11-30 06:56:29 +03:00
|
|
|
|
2023-12-11 07:00:43 +03:00
|
|
|
RUBY_ATOMIC_OR(pjq->triggered_bitset, (((rb_atomic_t)1UL) << h));
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(get_valid_ec(vm));
|
|
|
|
}
|
2018-11-30 06:56:29 +03:00
|
|
|
|
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
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 */
|
2023-12-09 19:47:14 +03:00
|
|
|
rb_postponed_job_handle_t h = rb_postponed_job_preregister(0, func, data);
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
if (h == POSTPONED_JOB_HANDLE_INVALID) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
rb_postponed_job_trigger(h);
|
|
|
|
return 1;
|
|
|
|
}
|
2018-11-30 06:56:29 +03:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
int
|
|
|
|
rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data)
|
|
|
|
{
|
|
|
|
return pjob_register_legacy_impl(flags, func, data);
|
2018-11-30 06:56:29 +03:00
|
|
|
}
|
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-27 01:30:44 +04:00
|
|
|
void
|
|
|
|
rb_postponed_job_flush(rb_vm_t *vm)
|
|
|
|
{
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
rb_postponed_job_queues_t *pjq = GET_VM()->postponed_job_queue;
|
2017-11-06 10:44:28 +03:00
|
|
|
rb_execution_context_t *ec = GET_EC();
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
const rb_atomic_t block_mask = POSTPONED_JOB_INTERRUPT_MASK | TRAP_INTERRUPT_MASK;
|
2018-11-18 07:34:52 +03:00
|
|
|
volatile rb_atomic_t saved_mask = ec->interrupt_mask & block_mask;
|
2017-11-06 10:44:28 +03:00
|
|
|
VALUE volatile saved_errno = ec->errinfo;
|
2022-03-30 10:36:31 +03:00
|
|
|
struct ccan_list_head tmp;
|
2018-11-30 06:56:29 +03:00
|
|
|
|
2022-03-30 10:36:31 +03:00
|
|
|
ccan_list_head_init(&tmp);
|
2018-11-30 06:56:29 +03:00
|
|
|
|
|
|
|
rb_nativethread_lock_lock(&vm->workqueue_lock);
|
2022-03-30 10:36:31 +03:00
|
|
|
ccan_list_append_list(&tmp, &vm->workqueue);
|
2018-11-30 06:56:29 +03:00
|
|
|
rb_nativethread_lock_unlock(&vm->workqueue_lock);
|
2013-10-11 11:54:26 +04:00
|
|
|
|
2023-12-11 07:00:43 +03:00
|
|
|
rb_atomic_t triggered_bits = RUBY_ATOMIC_EXCHANGE(pjq->triggered_bitset, 0);
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
|
2017-11-06 10:44:28 +03:00
|
|
|
ec->errinfo = Qnil;
|
2013-10-11 11:54:26 +04:00
|
|
|
/* mask POSTPONED_JOB dispatch */
|
2017-11-06 10:44:28 +03:00
|
|
|
ec->interrupt_mask |= block_mask;
|
2013-10-11 11:54:26 +04:00
|
|
|
{
|
2017-11-06 10:44:28 +03:00
|
|
|
EC_PUSH_TAG(ec);
|
2017-12-06 06:16:08 +03:00
|
|
|
if (EC_EXEC_TAG() == TAG_NONE) {
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* execute postponed jobs */
|
2023-12-11 07:00:43 +03:00
|
|
|
while (triggered_bits) {
|
|
|
|
unsigned int i = bit_length(triggered_bits) - 1;
|
|
|
|
triggered_bits ^= ((1UL) << i); /* toggle ith bit off */
|
|
|
|
rb_postponed_job_func_t func = pjq->table[i].func;
|
|
|
|
void *data = pjq->table[i].data;
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
(func)(data);
|
2013-10-10 22:36:54 +04:00
|
|
|
}
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
|
|
|
|
/* execute workqueue jobs */
|
|
|
|
struct rb_workqueue_job *wq_job;
|
2022-03-30 10:36:31 +03:00
|
|
|
while ((wq_job = ccan_list_pop(&tmp, struct rb_workqueue_job, jnode))) {
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
rb_postponed_job_func_t func = wq_job->func;
|
|
|
|
void *data = wq_job->data;
|
2018-11-30 06:56:29 +03:00
|
|
|
|
|
|
|
free(wq_job);
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
(func)(data);
|
2018-11-30 06:56:29 +03:00
|
|
|
}
|
2013-10-10 08:56:32 +04:00
|
|
|
}
|
2017-10-26 14:02:13 +03:00
|
|
|
EC_POP_TAG();
|
2013-05-27 01:30:44 +04:00
|
|
|
}
|
2013-10-11 11:54:26 +04:00
|
|
|
/* restore POSTPONED_JOB mask */
|
2017-11-06 10:44:28 +03:00
|
|
|
ec->interrupt_mask &= ~(saved_mask ^ block_mask);
|
|
|
|
ec->errinfo = saved_errno;
|
2018-11-30 06:56:29 +03:00
|
|
|
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* If we threw an exception, there might be leftover workqueue items; carry them over
|
|
|
|
* to a subsequent execution of flush */
|
2022-03-30 10:36:31 +03:00
|
|
|
if (!ccan_list_empty(&tmp)) {
|
2018-11-30 06:56:29 +03:00
|
|
|
rb_nativethread_lock_lock(&vm->workqueue_lock);
|
2022-03-30 10:36:31 +03:00
|
|
|
ccan_list_prepend_list(&vm->workqueue, &tmp);
|
2018-11-30 06:56:29 +03:00
|
|
|
rb_nativethread_lock_unlock(&vm->workqueue_lock);
|
|
|
|
|
|
|
|
RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(GET_EC());
|
|
|
|
}
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
/* likewise with any remaining-to-be-executed bits of the preregistered postponed
|
|
|
|
* job table */
|
2023-12-11 07:00:43 +03:00
|
|
|
if (triggered_bits) {
|
|
|
|
RUBY_ATOMIC_OR(pjq->triggered_bitset, triggered_bits);
|
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from
some safety issues that can lead to interpreter crashes (see bug #1991).
Essentially, the issue is that jobs can be called with the wrong
arguments.
We made two attempts to fix this whilst keeping the promised semantics,
but:
* The first one involved masking/unmasking when flushing jobs, which
was believed to be too expensive
* The second one involved a lock-free, multi-producer, single-consumer
ringbuffer, which was too complex
The critical insight behind this third solution is that essentially the
only user of these APIs are a) internal, or b) profiling gems.
For a), none of the usages actually require variable data; they will
work just fine with the preregistration interface.
For b), generally profiling gems only call a single callback with a
single piece of data (which is actually usually just zero) for the life
of the program. The ringbuffer is complex because it needs to support
multi-word inserts of job & data (which can't be atomic); but nobody
actually even needs that functionality, really.
So, this comit:
* Introduces a pre-registration API for jobs, with a GVL-requiring
rb_postponed_job_prereigster, which returns a handle which can be
used with an async-signal-safe rb_postponed_job_trigger.
* Deprecates rb_postponed_job_register (and re-implements it on top of
the preregister function for compatability)
* Moves all the internal usages of postponed job register
pre-registration
2023-11-19 14:54:57 +03:00
|
|
|
RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(GET_EC());
|
|
|
|
}
|
2013-05-27 01:30:44 +04:00
|
|
|
}
|