summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common.mk51
-rw-r--r--ext/-test-/postponed_job/postponed_job.c94
-rw-r--r--ext/-test-/tracepoint/gc_hook.c42
-rw-r--r--ext/ripper/depend1
-rw-r--r--ext/socket/depend15
-rw-r--r--gc.c12
-rw-r--r--include/ruby/debug.h146
-rw-r--r--inits.c1
-rw-r--r--rjit.c9
-rw-r--r--test/-ext-/postponed_job/test_postponed_job.rb64
-rw-r--r--thread.c1
-rw-r--r--vm.c8
-rw-r--r--vm_core.h9
-rw-r--r--vm_trace.c272
14 files changed, 556 insertions, 169 deletions
diff --git a/common.mk b/common.mk
index c2bf43194a..fda660b69c 100644
--- a/common.mk
+++ b/common.mk
@@ -3002,6 +3002,7 @@ class.$(OBJEXT): {$(VPATH)}vm_debug.h
class.$(OBJEXT): {$(VPATH)}vm_opts.h
class.$(OBJEXT): {$(VPATH)}vm_sync.h
compar.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+compar.$(OBJEXT): $(hdrdir)/ruby/version.h
compar.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
compar.$(OBJEXT): $(top_srcdir)/internal/compar.h
compar.$(OBJEXT): $(top_srcdir)/internal/compilers.h
@@ -3185,6 +3186,7 @@ compile.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
compile.$(OBJEXT): $(CCAN_DIR)/list/list.h
compile.$(OBJEXT): $(CCAN_DIR)/str/str.h
compile.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+compile.$(OBJEXT): $(hdrdir)/ruby/version.h
compile.$(OBJEXT): $(top_srcdir)/internal/array.h
compile.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
compile.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -3653,6 +3655,7 @@ cont.$(OBJEXT): $(CCAN_DIR)/list/list.h
cont.$(OBJEXT): $(CCAN_DIR)/str/str.h
cont.$(OBJEXT): $(hdrdir)/ruby.h
cont.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+cont.$(OBJEXT): $(hdrdir)/ruby/version.h
cont.$(OBJEXT): $(top_srcdir)/internal/array.h
cont.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
cont.$(OBJEXT): $(top_srcdir)/internal/compilers.h
@@ -4238,6 +4241,7 @@ dir.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
dir.$(OBJEXT): $(CCAN_DIR)/list/list.h
dir.$(OBJEXT): $(CCAN_DIR)/str/str.h
dir.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+dir.$(OBJEXT): $(hdrdir)/ruby/version.h
dir.$(OBJEXT): $(top_srcdir)/internal/array.h
dir.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
dir.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -5739,14 +5743,22 @@ enc/utf_8.$(OBJEXT): {$(VPATH)}oniguruma.h
enc/utf_8.$(OBJEXT): {$(VPATH)}regenc.h
enc/utf_8.$(OBJEXT): {$(VPATH)}st.h
enc/utf_8.$(OBJEXT): {$(VPATH)}subst.h
+encoding.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
+encoding.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
+encoding.$(OBJEXT): $(CCAN_DIR)/list/list.h
+encoding.$(OBJEXT): $(CCAN_DIR)/str/str.h
encoding.$(OBJEXT): $(hdrdir)/ruby.h
encoding.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+encoding.$(OBJEXT): $(hdrdir)/ruby/version.h
+encoding.$(OBJEXT): $(top_srcdir)/internal/array.h
+encoding.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
encoding.$(OBJEXT): $(top_srcdir)/internal/class.h
encoding.$(OBJEXT): $(top_srcdir)/internal/compilers.h
encoding.$(OBJEXT): $(top_srcdir)/internal/enc.h
encoding.$(OBJEXT): $(top_srcdir)/internal/encoding.h
encoding.$(OBJEXT): $(top_srcdir)/internal/error.h
encoding.$(OBJEXT): $(top_srcdir)/internal/gc.h
+encoding.$(OBJEXT): $(top_srcdir)/internal/imemo.h
encoding.$(OBJEXT): $(top_srcdir)/internal/inits.h
encoding.$(OBJEXT): $(top_srcdir)/internal/load.h
encoding.$(OBJEXT): $(top_srcdir)/internal/object.h
@@ -5757,6 +5769,7 @@ encoding.$(OBJEXT): $(top_srcdir)/internal/variable.h
encoding.$(OBJEXT): $(top_srcdir)/internal/vm.h
encoding.$(OBJEXT): $(top_srcdir)/internal/warnings.h
encoding.$(OBJEXT): {$(VPATH)}assert.h
+encoding.$(OBJEXT): {$(VPATH)}atomic.h
encoding.$(OBJEXT): {$(VPATH)}backward/2/assume.h
encoding.$(OBJEXT): {$(VPATH)}backward/2/attributes.h
encoding.$(OBJEXT): {$(VPATH)}backward/2/bool.h
@@ -5773,6 +5786,7 @@ encoding.$(OBJEXT): {$(VPATH)}defines.h
encoding.$(OBJEXT): {$(VPATH)}encindex.h
encoding.$(OBJEXT): {$(VPATH)}encoding.c
encoding.$(OBJEXT): {$(VPATH)}encoding.h
+encoding.$(OBJEXT): {$(VPATH)}id.h
encoding.$(OBJEXT): {$(VPATH)}id_table.h
encoding.$(OBJEXT): {$(VPATH)}intern.h
encoding.$(OBJEXT): {$(VPATH)}internal.h
@@ -5924,16 +5938,24 @@ encoding.$(OBJEXT): {$(VPATH)}internal/value_type.h
encoding.$(OBJEXT): {$(VPATH)}internal/variable.h
encoding.$(OBJEXT): {$(VPATH)}internal/warning_push.h
encoding.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
+encoding.$(OBJEXT): {$(VPATH)}method.h
encoding.$(OBJEXT): {$(VPATH)}missing.h
+encoding.$(OBJEXT): {$(VPATH)}node.h
encoding.$(OBJEXT): {$(VPATH)}onigmo.h
encoding.$(OBJEXT): {$(VPATH)}oniguruma.h
encoding.$(OBJEXT): {$(VPATH)}regenc.h
encoding.$(OBJEXT): {$(VPATH)}ruby_assert.h
+encoding.$(OBJEXT): {$(VPATH)}ruby_atomic.h
+encoding.$(OBJEXT): {$(VPATH)}rubyparser.h
encoding.$(OBJEXT): {$(VPATH)}shape.h
encoding.$(OBJEXT): {$(VPATH)}st.h
encoding.$(OBJEXT): {$(VPATH)}subst.h
+encoding.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
+encoding.$(OBJEXT): {$(VPATH)}thread_native.h
encoding.$(OBJEXT): {$(VPATH)}util.h
+encoding.$(OBJEXT): {$(VPATH)}vm_core.h
encoding.$(OBJEXT): {$(VPATH)}vm_debug.h
+encoding.$(OBJEXT): {$(VPATH)}vm_opts.h
encoding.$(OBJEXT): {$(VPATH)}vm_sync.h
enum.$(OBJEXT): $(hdrdir)/ruby/ruby.h
enum.$(OBJEXT): $(top_srcdir)/internal/array.h
@@ -6139,6 +6161,7 @@ enumerator.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
enumerator.$(OBJEXT): $(CCAN_DIR)/list/list.h
enumerator.$(OBJEXT): $(CCAN_DIR)/str/str.h
enumerator.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+enumerator.$(OBJEXT): $(hdrdir)/ruby/version.h
enumerator.$(OBJEXT): $(top_srcdir)/internal/array.h
enumerator.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
enumerator.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -6352,6 +6375,7 @@ error.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
error.$(OBJEXT): $(CCAN_DIR)/list/list.h
error.$(OBJEXT): $(CCAN_DIR)/str/str.h
error.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+error.$(OBJEXT): $(hdrdir)/ruby/version.h
error.$(OBJEXT): $(top_srcdir)/internal/array.h
error.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
error.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -6567,6 +6591,7 @@ eval.$(OBJEXT): $(CCAN_DIR)/list/list.h
eval.$(OBJEXT): $(CCAN_DIR)/str/str.h
eval.$(OBJEXT): $(hdrdir)/ruby.h
eval.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+eval.$(OBJEXT): $(hdrdir)/ruby/version.h
eval.$(OBJEXT): $(top_srcdir)/internal/array.h
eval.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
eval.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -6806,6 +6831,7 @@ file.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
file.$(OBJEXT): $(CCAN_DIR)/list/list.h
file.$(OBJEXT): $(CCAN_DIR)/str/str.h
file.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+file.$(OBJEXT): $(hdrdir)/ruby/version.h
file.$(OBJEXT): $(top_srcdir)/internal/array.h
file.$(OBJEXT): $(top_srcdir)/internal/class.h
file.$(OBJEXT): $(top_srcdir)/internal/compilers.h
@@ -7010,6 +7036,7 @@ gc.$(OBJEXT): $(CCAN_DIR)/list/list.h
gc.$(OBJEXT): $(CCAN_DIR)/str/str.h
gc.$(OBJEXT): $(hdrdir)/ruby.h
gc.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+gc.$(OBJEXT): $(hdrdir)/ruby/version.h
gc.$(OBJEXT): $(top_srcdir)/internal/array.h
gc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
gc.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -7457,6 +7484,7 @@ hash.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
hash.$(OBJEXT): $(CCAN_DIR)/list/list.h
hash.$(OBJEXT): $(CCAN_DIR)/str/str.h
hash.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+hash.$(OBJEXT): $(hdrdir)/ruby/version.h
hash.$(OBJEXT): $(top_srcdir)/internal/array.h
hash.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
hash.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -7839,6 +7867,7 @@ io.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
io.$(OBJEXT): $(CCAN_DIR)/list/list.h
io.$(OBJEXT): $(CCAN_DIR)/str/str.h
io.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+io.$(OBJEXT): $(hdrdir)/ruby/version.h
io.$(OBJEXT): $(top_srcdir)/internal/array.h
io.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
io.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -8061,6 +8090,7 @@ io_buffer.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
io_buffer.$(OBJEXT): $(CCAN_DIR)/list/list.h
io_buffer.$(OBJEXT): $(CCAN_DIR)/str/str.h
io_buffer.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+io_buffer.$(OBJEXT): $(hdrdir)/ruby/version.h
io_buffer.$(OBJEXT): $(top_srcdir)/internal/array.h
io_buffer.$(OBJEXT): $(top_srcdir)/internal/bignum.h
io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h
@@ -8252,6 +8282,7 @@ iseq.$(OBJEXT): $(CCAN_DIR)/list/list.h
iseq.$(OBJEXT): $(CCAN_DIR)/str/str.h
iseq.$(OBJEXT): $(hdrdir)/ruby.h
iseq.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+iseq.$(OBJEXT): $(hdrdir)/ruby/version.h
iseq.$(OBJEXT): $(top_srcdir)/internal/array.h
iseq.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
iseq.$(OBJEXT): $(top_srcdir)/internal/bits.h
@@ -8501,6 +8532,7 @@ load.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
load.$(OBJEXT): $(CCAN_DIR)/list/list.h
load.$(OBJEXT): $(CCAN_DIR)/str/str.h
load.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+load.$(OBJEXT): $(hdrdir)/ruby/version.h
load.$(OBJEXT): $(top_srcdir)/internal/array.h
load.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
load.$(OBJEXT): $(top_srcdir)/internal/bits.h
@@ -9203,6 +9235,7 @@ marshal.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
marshal.$(OBJEXT): $(CCAN_DIR)/list/list.h
marshal.$(OBJEXT): $(CCAN_DIR)/str/str.h
marshal.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+marshal.$(OBJEXT): $(hdrdir)/ruby/version.h
marshal.$(OBJEXT): $(top_srcdir)/internal/array.h
marshal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
marshal.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -10636,6 +10669,7 @@ object.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
object.$(OBJEXT): $(CCAN_DIR)/list/list.h
object.$(OBJEXT): $(CCAN_DIR)/str/str.h
object.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+object.$(OBJEXT): $(hdrdir)/ruby/version.h
object.$(OBJEXT): $(top_srcdir)/internal/array.h
object.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
object.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -11061,6 +11095,7 @@ parse.$(OBJEXT): $(CCAN_DIR)/list/list.h
parse.$(OBJEXT): $(CCAN_DIR)/str/str.h
parse.$(OBJEXT): $(hdrdir)/ruby.h
parse.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+parse.$(OBJEXT): $(hdrdir)/ruby/version.h
parse.$(OBJEXT): $(top_srcdir)/internal/array.h
parse.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
parse.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -12318,6 +12353,7 @@ proc.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
proc.$(OBJEXT): $(CCAN_DIR)/list/list.h
proc.$(OBJEXT): $(CCAN_DIR)/str/str.h
proc.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+proc.$(OBJEXT): $(hdrdir)/ruby/version.h
proc.$(OBJEXT): $(top_srcdir)/internal/array.h
proc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
proc.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -12530,6 +12566,7 @@ process.$(OBJEXT): $(CCAN_DIR)/list/list.h
process.$(OBJEXT): $(CCAN_DIR)/str/str.h
process.$(OBJEXT): $(hdrdir)/ruby.h
process.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+process.$(OBJEXT): $(hdrdir)/ruby/version.h
process.$(OBJEXT): $(top_srcdir)/internal/array.h
process.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
process.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -12755,6 +12792,7 @@ ractor.$(OBJEXT): $(CCAN_DIR)/list/list.h
ractor.$(OBJEXT): $(CCAN_DIR)/str/str.h
ractor.$(OBJEXT): $(hdrdir)/ruby.h
ractor.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+ractor.$(OBJEXT): $(hdrdir)/ruby/version.h
ractor.$(OBJEXT): $(top_srcdir)/internal/array.h
ractor.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
ractor.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -13181,6 +13219,7 @@ random.$(OBJEXT): {$(VPATH)}thread_native.h
random.$(OBJEXT): {$(VPATH)}vm_core.h
random.$(OBJEXT): {$(VPATH)}vm_opts.h
range.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+range.$(OBJEXT): $(hdrdir)/ruby/version.h
range.$(OBJEXT): $(top_srcdir)/internal/array.h
range.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
range.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -15842,6 +15881,7 @@ shape.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
shape.$(OBJEXT): $(CCAN_DIR)/list/list.h
shape.$(OBJEXT): $(CCAN_DIR)/str/str.h
shape.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+shape.$(OBJEXT): $(hdrdir)/ruby/version.h
shape.$(OBJEXT): $(top_srcdir)/internal/array.h
shape.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
shape.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -16050,6 +16090,7 @@ signal.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
signal.$(OBJEXT): $(CCAN_DIR)/list/list.h
signal.$(OBJEXT): $(CCAN_DIR)/str/str.h
signal.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+signal.$(OBJEXT): $(hdrdir)/ruby/version.h
signal.$(OBJEXT): $(top_srcdir)/internal/array.h
signal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
signal.$(OBJEXT): $(top_srcdir)/internal/compilers.h
@@ -16255,6 +16296,7 @@ signal.$(OBJEXT): {$(VPATH)}vm_core.h
signal.$(OBJEXT): {$(VPATH)}vm_debug.h
signal.$(OBJEXT): {$(VPATH)}vm_opts.h
sprintf.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+sprintf.$(OBJEXT): $(hdrdir)/ruby/version.h
sprintf.$(OBJEXT): $(top_srcdir)/internal/bignum.h
sprintf.$(OBJEXT): $(top_srcdir)/internal/bits.h
sprintf.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -16798,6 +16840,7 @@ string.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
string.$(OBJEXT): $(CCAN_DIR)/list/list.h
string.$(OBJEXT): $(CCAN_DIR)/str/str.h
string.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+string.$(OBJEXT): $(hdrdir)/ruby/version.h
string.$(OBJEXT): $(top_srcdir)/internal/array.h
string.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
string.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -17050,6 +17093,7 @@ struct.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
struct.$(OBJEXT): $(CCAN_DIR)/list/list.h
struct.$(OBJEXT): $(CCAN_DIR)/str/str.h
struct.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+struct.$(OBJEXT): $(hdrdir)/ruby/version.h
struct.$(OBJEXT): $(top_srcdir)/internal/array.h
struct.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
struct.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -17260,6 +17304,7 @@ symbol.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
symbol.$(OBJEXT): $(CCAN_DIR)/list/list.h
symbol.$(OBJEXT): $(CCAN_DIR)/str/str.h
symbol.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+symbol.$(OBJEXT): $(hdrdir)/ruby/version.h
symbol.$(OBJEXT): $(top_srcdir)/internal/array.h
symbol.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
symbol.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -17476,6 +17521,7 @@ thread.$(OBJEXT): $(CCAN_DIR)/list/list.h
thread.$(OBJEXT): $(CCAN_DIR)/str/str.h
thread.$(OBJEXT): $(hdrdir)/ruby.h
thread.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+thread.$(OBJEXT): $(hdrdir)/ruby/version.h
thread.$(OBJEXT): $(top_srcdir)/internal/array.h
thread.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
thread.$(OBJEXT): $(top_srcdir)/internal/bits.h
@@ -18269,6 +18315,7 @@ variable.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
variable.$(OBJEXT): $(CCAN_DIR)/list/list.h
variable.$(OBJEXT): $(CCAN_DIR)/str/str.h
variable.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+variable.$(OBJEXT): $(hdrdir)/ruby/version.h
variable.$(OBJEXT): $(top_srcdir)/internal/array.h
variable.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
variable.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -18691,6 +18738,7 @@ vm.$(OBJEXT): $(CCAN_DIR)/list/list.h
vm.$(OBJEXT): $(CCAN_DIR)/str/str.h
vm.$(OBJEXT): $(hdrdir)/ruby.h
vm.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+vm.$(OBJEXT): $(hdrdir)/ruby/version.h
vm.$(OBJEXT): $(top_srcdir)/internal/array.h
vm.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
vm.$(OBJEXT): $(top_srcdir)/internal/bignum.h
@@ -18944,6 +18992,7 @@ vm_backtrace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
vm_backtrace.$(OBJEXT): $(CCAN_DIR)/list/list.h
vm_backtrace.$(OBJEXT): $(CCAN_DIR)/str/str.h
vm_backtrace.$(OBJEXT): $(hdrdir)/ruby/ruby.h
+vm_backtrace.$(OBJEXT): $(hdrdir)/ruby/version.h
vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/array.h
vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/class.h
@@ -19562,6 +19611,7 @@ vm_trace.$(OBJEXT): $(hdrdir)/ruby.h
vm_trace.$(OBJEXT): $(hdrdir)/ruby/ruby.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/array.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
+vm_trace.$(OBJEXT): $(top_srcdir)/internal/bits.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/class.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/compilers.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/gc.h
@@ -19570,6 +19620,7 @@ vm_trace.$(OBJEXT): $(top_srcdir)/internal/imemo.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/serial.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/symbol.h
+vm_trace.$(OBJEXT): $(top_srcdir)/internal/thread.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/variable.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/vm.h
vm_trace.$(OBJEXT): $(top_srcdir)/internal/warnings.h
diff --git a/ext/-test-/postponed_job/postponed_job.c b/ext/-test-/postponed_job/postponed_job.c
index fa57bef6f5..844412aebe 100644
--- a/ext/-test-/postponed_job/postponed_job.c
+++ b/ext/-test-/postponed_job/postponed_job.c
@@ -1,6 +1,29 @@
#include "ruby.h"
#include "ruby/debug.h"
+// We're testing deprecated things, don't print the compiler warnings
+#if 0
+
+#elif defined(_MSC_VER)
+#pragma warning(disable : 4996)
+
+#elif defined(__INTEL_COMPILER)
+#pragma warning(disable : 1786)
+
+#elif defined(__clang__)
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+#elif defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+#elif defined(__SUNPRO_CC)
+#pragma error_messages (off,symdeprecated)
+
+#else
+// :FIXME: improve here for your compiler.
+
+#endif
+
static int counter;
static void
@@ -58,6 +81,22 @@ pjob_call_direct(VALUE self, VALUE obj)
return self;
}
+static void pjob_noop_callback(void *data) { }
+
+static VALUE
+pjob_register_one_same(VALUE self)
+{
+ rb_gc_start();
+ int r1 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL);
+ int r2 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL);
+ int r3 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL);
+ VALUE ary = rb_ary_new();
+ rb_ary_push(ary, INT2FIX(r1));
+ rb_ary_push(ary, INT2FIX(r2));
+ rb_ary_push(ary, INT2FIX(r3));
+ return ary;
+}
+
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
@@ -86,6 +125,57 @@ pjob_register_in_c_thread(VALUE self, VALUE obj)
}
#endif
+static void
+pjob_preregistered_callback(void *data)
+{
+ VALUE ary = (VALUE)data;
+ Check_Type(ary, T_ARRAY);
+ rb_ary_push(ary, INT2FIX(counter));
+}
+
+static VALUE
+pjob_preregister_and_call_with_sleep(VALUE self, VALUE obj)
+{
+ counter = 0;
+ rb_postponed_job_handle_t h = rb_postponed_job_preregister(pjob_preregistered_callback, (void *)obj);
+ counter++;
+ rb_postponed_job_trigger(h);
+ rb_thread_sleep(0);
+ counter++;
+ rb_postponed_job_trigger(h);
+ rb_thread_sleep(0);
+ counter++;
+ rb_postponed_job_trigger(h);
+ rb_thread_sleep(0);
+ return self;
+}
+
+static VALUE
+pjob_preregister_and_call_without_sleep(VALUE self, VALUE obj)
+{
+ counter = 0;
+ rb_postponed_job_handle_t h = rb_postponed_job_preregister(pjob_preregistered_callback, (void *)obj);
+ counter = 3;
+ rb_postponed_job_trigger(h);
+ rb_postponed_job_trigger(h);
+ rb_postponed_job_trigger(h);
+ return self;
+}
+
+static VALUE
+pjob_preregister_multiple_times(VALUE self)
+{
+ int r1 = rb_postponed_job_preregister(pjob_noop_callback, NULL);
+ int r2 = rb_postponed_job_preregister(pjob_noop_callback, NULL);
+ int r3 = rb_postponed_job_preregister(pjob_noop_callback, NULL);
+ VALUE ary = rb_ary_new();
+ rb_ary_push(ary, INT2FIX(r1));
+ rb_ary_push(ary, INT2FIX(r2));
+ rb_ary_push(ary, INT2FIX(r3));
+ return ary;
+
+}
+
void
Init_postponed_job(VALUE self)
{
@@ -93,8 +183,12 @@ Init_postponed_job(VALUE self)
rb_define_module_function(mBug, "postponed_job_register", pjob_register, 1);
rb_define_module_function(mBug, "postponed_job_register_one", pjob_register_one, 1);
rb_define_module_function(mBug, "postponed_job_call_direct", pjob_call_direct, 1);
+ rb_define_module_function(mBug, "postponed_job_register_one_same", pjob_register_one_same, 0);
#ifdef HAVE_PTHREAD_H
rb_define_module_function(mBug, "postponed_job_register_in_c_thread", pjob_register_in_c_thread, 1);
#endif
+ rb_define_module_function(mBug, "postponed_job_preregister_and_call_with_sleep", pjob_preregister_and_call_with_sleep, 1);
+ rb_define_module_function(mBug, "postponed_job_preregister_and_call_without_sleep", pjob_preregister_and_call_without_sleep, 1);
+ rb_define_module_function(mBug, "postponed_job_preregister_multiple_times", pjob_preregister_multiple_times, 0);
}
diff --git a/ext/-test-/tracepoint/gc_hook.c b/ext/-test-/tracepoint/gc_hook.c
index a3f4e7f68a..76417e4007 100644
--- a/ext/-test-/tracepoint/gc_hook.c
+++ b/ext/-test-/tracepoint/gc_hook.c
@@ -2,6 +2,11 @@
#include "ruby/debug.h"
static int invoking; /* TODO: should not be global variable */
+static VALUE gc_start_proc;
+static VALUE gc_end_proc;
+static rb_postponed_job_handle_t invoking_proc_pjob;
+static bool pjob_execute_gc_start_proc_p;
+static bool pjob_execute_gc_end_proc_p;
static VALUE
invoke_proc_ensure(VALUE _)
@@ -17,23 +22,35 @@ invoke_proc_begin(VALUE proc)
}
static void
-invoke_proc(void *data)
+invoke_proc(void *unused)
{
- VALUE proc = (VALUE)data;
- invoking += 1;
- rb_ensure(invoke_proc_begin, proc, invoke_proc_ensure, 0);
+ if (pjob_execute_gc_start_proc_p) {
+ pjob_execute_gc_start_proc_p = false;
+ invoking += 1;
+ rb_ensure(invoke_proc_begin, gc_start_proc, invoke_proc_ensure, 0);
+ }
+ if (pjob_execute_gc_end_proc_p) {
+ pjob_execute_gc_end_proc_p = false;
+ invoking += 1;
+ rb_ensure(invoke_proc_begin, gc_end_proc, invoke_proc_ensure, 0);
+ }
}
static void
gc_start_end_i(VALUE tpval, void *data)
{
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
if (0) {
- rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
fprintf(stderr, "trace: %s\n", rb_tracearg_event_flag(tparg) == RUBY_INTERNAL_EVENT_GC_START ? "gc_start" : "gc_end");
}
if (invoking == 0) {
- rb_postponed_job_register(0, invoke_proc, data);
+ if (rb_tracearg_event_flag(tparg) == RUBY_INTERNAL_EVENT_GC_START) {
+ pjob_execute_gc_start_proc_p = true;
+ } else {
+ pjob_execute_gc_end_proc_p = true;
+ }
+ rb_postponed_job_trigger(invoking_proc_pjob);
}
}
@@ -54,8 +71,13 @@ set_gc_hook(VALUE module, VALUE proc, rb_event_flag_t event, const char *tp_str,
if (!rb_obj_is_proc(proc)) {
rb_raise(rb_eTypeError, "trace_func needs to be Proc");
}
+ if (event == RUBY_INTERNAL_EVENT_GC_START) {
+ gc_start_proc = proc;
+ } else {
+ gc_end_proc = proc;
+ }
- tpval = rb_tracepoint_new(0, event, gc_start_end_i, (void *)proc);
+ tpval = rb_tracepoint_new(0, event, gc_start_end_i, 0);
rb_ivar_set(module, tp_key, tpval);
rb_tracepoint_enable(tpval);
}
@@ -82,4 +104,10 @@ Init_gc_hook(VALUE module)
{
rb_define_module_function(module, "after_gc_start_hook=", set_after_gc_start, 1);
rb_define_module_function(module, "after_gc_exit_hook=", start_after_gc_exit, 1);
+ rb_gc_register_address(&gc_start_proc);
+ rb_gc_register_address(&gc_end_proc);
+ invoking_proc_pjob = rb_postponed_job_preregister(invoke_proc, NULL);
+ if (invoking_proc_pjob == POSTPONED_JOB_HANDLE_INVALID) {
+ rb_raise(rb_eStandardError, "could not preregister invoke_proc");
+ }
}
diff --git a/ext/ripper/depend b/ext/ripper/depend
index a2de09f280..a18b67abe3 100644
--- a/ext/ripper/depend
+++ b/ext/ripper/depend
@@ -566,6 +566,7 @@ ripper.o: $(hdrdir)/ruby/st.h
ripper.o: $(hdrdir)/ruby/subst.h
ripper.o: $(hdrdir)/ruby/thread_native.h
ripper.o: $(hdrdir)/ruby/util.h
+ripper.o: $(hdrdir)/ruby/version.h
ripper.o: $(top_srcdir)/ccan/check_type/check_type.h
ripper.o: $(top_srcdir)/ccan/container_of/container_of.h
ripper.o: $(top_srcdir)/ccan/list/list.h
diff --git a/ext/socket/depend b/ext/socket/depend
index 675520c0b5..3db153bb1c 100644
--- a/ext/socket/depend
+++ b/ext/socket/depend
@@ -186,6 +186,7 @@ ancdata.o: $(hdrdir)/ruby/subst.h
ancdata.o: $(hdrdir)/ruby/thread.h
ancdata.o: $(hdrdir)/ruby/thread_native.h
ancdata.o: $(hdrdir)/ruby/util.h
+ancdata.o: $(hdrdir)/ruby/version.h
ancdata.o: $(top_srcdir)/ccan/check_type/check_type.h
ancdata.o: $(top_srcdir)/ccan/container_of/container_of.h
ancdata.o: $(top_srcdir)/ccan/list/list.h
@@ -394,6 +395,7 @@ basicsocket.o: $(hdrdir)/ruby/subst.h
basicsocket.o: $(hdrdir)/ruby/thread.h
basicsocket.o: $(hdrdir)/ruby/thread_native.h
basicsocket.o: $(hdrdir)/ruby/util.h
+basicsocket.o: $(hdrdir)/ruby/version.h
basicsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
basicsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
basicsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -602,6 +604,7 @@ constants.o: $(hdrdir)/ruby/subst.h
constants.o: $(hdrdir)/ruby/thread.h
constants.o: $(hdrdir)/ruby/thread_native.h
constants.o: $(hdrdir)/ruby/util.h
+constants.o: $(hdrdir)/ruby/version.h
constants.o: $(top_srcdir)/ccan/check_type/check_type.h
constants.o: $(top_srcdir)/ccan/container_of/container_of.h
constants.o: $(top_srcdir)/ccan/list/list.h
@@ -811,6 +814,7 @@ ifaddr.o: $(hdrdir)/ruby/subst.h
ifaddr.o: $(hdrdir)/ruby/thread.h
ifaddr.o: $(hdrdir)/ruby/thread_native.h
ifaddr.o: $(hdrdir)/ruby/util.h
+ifaddr.o: $(hdrdir)/ruby/version.h
ifaddr.o: $(top_srcdir)/ccan/check_type/check_type.h
ifaddr.o: $(top_srcdir)/ccan/container_of/container_of.h
ifaddr.o: $(top_srcdir)/ccan/list/list.h
@@ -1019,6 +1023,7 @@ init.o: $(hdrdir)/ruby/subst.h
init.o: $(hdrdir)/ruby/thread.h
init.o: $(hdrdir)/ruby/thread_native.h
init.o: $(hdrdir)/ruby/util.h
+init.o: $(hdrdir)/ruby/version.h
init.o: $(top_srcdir)/ccan/check_type/check_type.h
init.o: $(top_srcdir)/ccan/container_of/container_of.h
init.o: $(top_srcdir)/ccan/list/list.h
@@ -1227,6 +1232,7 @@ ipsocket.o: $(hdrdir)/ruby/subst.h
ipsocket.o: $(hdrdir)/ruby/thread.h
ipsocket.o: $(hdrdir)/ruby/thread_native.h
ipsocket.o: $(hdrdir)/ruby/util.h
+ipsocket.o: $(hdrdir)/ruby/version.h
ipsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
ipsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
ipsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -1435,6 +1441,7 @@ option.o: $(hdrdir)/ruby/subst.h
option.o: $(hdrdir)/ruby/thread.h
option.o: $(hdrdir)/ruby/thread_native.h
option.o: $(hdrdir)/ruby/util.h
+option.o: $(hdrdir)/ruby/version.h
option.o: $(top_srcdir)/ccan/check_type/check_type.h
option.o: $(top_srcdir)/ccan/container_of/container_of.h
option.o: $(top_srcdir)/ccan/list/list.h
@@ -1643,6 +1650,7 @@ raddrinfo.o: $(hdrdir)/ruby/subst.h
raddrinfo.o: $(hdrdir)/ruby/thread.h
raddrinfo.o: $(hdrdir)/ruby/thread_native.h
raddrinfo.o: $(hdrdir)/ruby/util.h
+raddrinfo.o: $(hdrdir)/ruby/version.h
raddrinfo.o: $(top_srcdir)/ccan/check_type/check_type.h
raddrinfo.o: $(top_srcdir)/ccan/container_of/container_of.h
raddrinfo.o: $(top_srcdir)/ccan/list/list.h
@@ -1851,6 +1859,7 @@ socket.o: $(hdrdir)/ruby/subst.h
socket.o: $(hdrdir)/ruby/thread.h
socket.o: $(hdrdir)/ruby/thread_native.h
socket.o: $(hdrdir)/ruby/util.h
+socket.o: $(hdrdir)/ruby/version.h
socket.o: $(top_srcdir)/ccan/check_type/check_type.h
socket.o: $(top_srcdir)/ccan/container_of/container_of.h
socket.o: $(top_srcdir)/ccan/list/list.h
@@ -2059,6 +2068,7 @@ sockssocket.o: $(hdrdir)/ruby/subst.h
sockssocket.o: $(hdrdir)/ruby/thread.h
sockssocket.o: $(hdrdir)/ruby/thread_native.h
sockssocket.o: $(hdrdir)/ruby/util.h
+sockssocket.o: $(hdrdir)/ruby/version.h
sockssocket.o: $(top_srcdir)/ccan/check_type/check_type.h
sockssocket.o: $(top_srcdir)/ccan/container_of/container_of.h
sockssocket.o: $(top_srcdir)/ccan/list/list.h
@@ -2267,6 +2277,7 @@ tcpserver.o: $(hdrdir)/ruby/subst.h
tcpserver.o: $(hdrdir)/ruby/thread.h
tcpserver.o: $(hdrdir)/ruby/thread_native.h
tcpserver.o: $(hdrdir)/ruby/util.h
+tcpserver.o: $(hdrdir)/ruby/version.h
tcpserver.o: $(top_srcdir)/ccan/check_type/check_type.h
tcpserver.o: $(top_srcdir)/ccan/container_of/container_of.h
tcpserver.o: $(top_srcdir)/ccan/list/list.h
@@ -2475,6 +2486,7 @@ tcpsocket.o: $(hdrdir)/ruby/subst.h
tcpsocket.o: $(hdrdir)/ruby/thread.h
tcpsocket.o: $(hdrdir)/ruby/thread_native.h
tcpsocket.o: $(hdrdir)/ruby/util.h
+tcpsocket.o: $(hdrdir)/ruby/version.h
tcpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
tcpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
tcpsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -2683,6 +2695,7 @@ udpsocket.o: $(hdrdir)/ruby/subst.h
udpsocket.o: $(hdrdir)/ruby/thread.h
udpsocket.o: $(hdrdir)/ruby/thread_native.h
udpsocket.o: $(hdrdir)/ruby/util.h
+udpsocket.o: $(hdrdir)/ruby/version.h
udpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
udpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
udpsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -2891,6 +2904,7 @@ unixserver.o: $(hdrdir)/ruby/subst.h
unixserver.o: $(hdrdir)/ruby/thread.h
unixserver.o: $(hdrdir)/ruby/thread_native.h
unixserver.o: $(hdrdir)/ruby/util.h
+unixserver.o: $(hdrdir)/ruby/version.h
unixserver.o: $(top_srcdir)/ccan/check_type/check_type.h
unixserver.o: $(top_srcdir)/ccan/container_of/container_of.h
unixserver.o: $(top_srcdir)/ccan/list/list.h
@@ -3099,6 +3113,7 @@ unixsocket.o: $(hdrdir)/ruby/subst.h
unixsocket.o: $(hdrdir)/ruby/thread.h
unixsocket.o: $(hdrdir)/ruby/thread_native.h
unixsocket.o: $(hdrdir)/ruby/util.h
+unixsocket.o: $(hdrdir)/ruby/version.h
unixsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
unixsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
unixsocket.o: $(top_srcdir)/ccan/list/list.h
diff --git a/gc.c b/gc.c
index ce5ae34dc8..6050fd522a 100644
--- a/gc.c
+++ b/gc.c
@@ -952,6 +952,7 @@ typedef struct rb_objspace {
#endif
rb_darray(VALUE *) weak_references;
+ rb_postponed_job_handle_t finalize_deferred_pjob;
} rb_objspace_t;
@@ -1425,6 +1426,8 @@ PRINTF_ARGS(static void gc_report_body(int level, rb_objspace_t *objspace, const
static const char *obj_info(VALUE obj);
static const char *obj_type_name(VALUE obj);
+static void gc_finalize_deferred(void *dmy);
+
/*
* 1 - TSC (H/W Time Stamp Counter)
* 2 - getrusage
@@ -1906,6 +1909,10 @@ rb_objspace_alloc(void)
rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t));
objspace->flags.measure_gc = 1;
malloc_limit = gc_params.malloc_limit_min;
+ objspace->finalize_deferred_pjob = rb_postponed_job_preregister(gc_finalize_deferred, objspace);
+ if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) {
+ rb_bug("Could not preregister postponed job for GC");
+ }
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
rb_size_pool_t *size_pool = &size_pools[i];
@@ -4527,9 +4534,8 @@ gc_finalize_deferred(void *dmy)
static void
gc_finalize_deferred_register(rb_objspace_t *objspace)
{
- if (rb_postponed_job_register_one(0, gc_finalize_deferred, objspace) == 0) {
- rb_bug("gc_finalize_deferred_register: can't register finalizer.");
- }
+ /* will enqueue a call to gc_finalize_deferred */
+ rb_postponed_job_trigger(objspace->finalize_deferred_pjob);
}
static int pop_mark_stack(mark_stack_t *stack, VALUE *data);
diff --git a/include/ruby/debug.h b/include/ruby/debug.h
index e173f16e25..88bd721230 100644
--- a/include/ruby/debug.h
+++ b/include/ruby/debug.h
@@ -10,6 +10,7 @@
* modify this file, provided that the conditions mentioned in the
* file COPYING are met. Consult the file for details.
*/
+#include "ruby/internal/attr/deprecated.h"
#include "ruby/internal/attr/nonnull.h"
#include "ruby/internal/attr/returns_nonnull.h"
#include "ruby/internal/dllexport.h"
@@ -615,48 +616,151 @@ VALUE rb_tracearg_object(rb_trace_arg_t *trace_arg);
/*
* Postponed Job API
- * rb_postponed_job_register and rb_postponed_job_register_one are
- * async-signal-safe and used via SIGPROF by the "stackprof" RubyGem
+ *
+ * This API is designed to be called from contexts where it is not safe to run Ruby
+ * code (e.g. because they do not hold the GVL or because GC is in progress), and
+ * defer a callback to run in a context where it _is_ safe. The primary intended
+ * users of this API is for sampling profilers like the "stackprof" gem; these work
+ * by scheduling the periodic delivery of a SIGPROF signal, and inside the C-level
+ * signal handler, deferring a job to collect a Ruby backtrace when it is next safe
+ * to do so.
+ *
+ * Historically, this API provided two functions `rb_postponed_job_register` and
+ * `rb_postponed_job_register_one`, which claimed to be fully async-signal-safe and
+ * would call back the provided `func` and `data` at an appropriate time. However,
+ * these functions were subject to race conditions which could cause crashes when
+ * racing with Ruby's internal use of them.
+ *
+ * Therefore, this API has now been changed, and now requires that jobs scheduled
+ * from a signal handler context are pre-registered in advance into a fixed-size
+ * table. This table is quite small (it only has 32 entries on most systems)
+ * and so gems should generally only preregister one or two funcs. This process is
+ * managed by the `rb_postponed_job_preregister` and `rb_postponed_job_trigger`
+ * functions.
+ *
+ * We also provide the old `rb_postponed_job_register` and
+ * `rb_postponed_job_register_one` functions for backwards compatability, but with
+ * changed semantics; `rb_postponed_job_register` now behaves the same as
+ * `rb_postponed_job_register_once`. These changes should remain compatible with all
+ * of the observed in-the-wild usages of the postponed job APIs, which almost all
+ * use the _one API and pass `0` for data anyway.
*/
+
/**
* Type of postponed jobs.
*
- * @param[in,out] arg What was passed to rb_postponed_job_register().
+ * @param[in,out] arg What was passed to `rb_postponed_job_preregister`
*/
typedef void (*rb_postponed_job_func_t)(void *arg);
/**
- * Registers a postponed job.
+ * The type of a handle returned from `rb_postponed_job_preregister` and
+ * passed to `rb_postponed_job_trigger`
+ */
+typedef unsigned int rb_postponed_job_handle_t;
+#define POSTPONED_JOB_HANDLE_INVALID ((rb_postponed_job_handle_t)UINT_MAX)
+
+/**
+ * Pre-registers a func in Ruby's postponed job preregistration table,
+ * returning an opaque handle which can be used to trigger the job later. Generally,
+ * this function will be called during the initialization routine of an extension.
+ *
+ * The returned handle can be used later to call `rb_postponed_job_trigger`. This will
+ * cause Ruby to call back into the registered `func` with `data` at a later time, in
+ * a context where the GVL is held and it is safe to perform Ruby allocations.
+ *
+ * If the given func was already pre-registered, this method will overwrite the
+ * stored data with the newly passed data, and return the same handle instance as
+ * was previously returned.
+ *
+ * If this function is called concurrently with the same `func`, then the stored data
+ * could be the value from either call (but will definitely be one of them).
+ *
+ * If this function is called to update the data concurrently with a call to
+ * `rb_postponed_job_trigger` on the same handle, it's undefined whether `func` will
+ * be called with the old data or the new data.
+ *
+ * Although the current implementation of this method is in fact async-signal-safe and
+ * has defined semantics when called concurrently on the same `func`, a future Ruby
+ * version might require that this method be called under the GVL; thus, programs which
+ * aim to be forward-compatible should call this method whilst holding the GVL.
+ *
+ * @param[in] func The function to be pre-registered
+ * @param[in] data The data to be pre-registered
+ * @retval POSTPONED_JOB_HANDLE_INVALID The job table is full; this registration
+ * did not succeed and no further registration will do so for
+ * the lifetime of the program.
+ * @retval otherwise A handle which can be passed to `rb_postponed_job_trigger`
+ */
+rb_postponed_job_handle_t rb_postponed_job_preregister(rb_postponed_job_func_t func, void *data);
+
+/**
+ * Triggers a pre-registered job registered with rb_postponed_job_preregister,
+ * scheduling it for execution the next time the Ruby VM checks for interrupts.
+ * The context in which the job is called in holds the GVL and is safe to perform
+ * Ruby allocations within (i.e. it is not during GC).
+ *
+ * This method is async-signal-safe and can be called from any thread, at any
+ * time, including in signal handlers.
+ *
+ * If this method is called multiple times, Ruby will coalesce this into only
+ * one call to the job the next time it checks for interrupts.
+ *
+ * @params[in] h A handle returned from rb_postponed_job_preregister
+ */
+void rb_postponed_job_trigger(rb_postponed_job_handle_t h);
+
+/**
+ * Schedules the given `func` to be called with `data` when Ruby next checks for
+ * interupts. If this function is called multiple times in between Ruby checking
+ * for interrupts, then `func` will be called only once with the `data` vlaue from
+ * the first call to this function.
*
- * There are situations when running a ruby program is not possible. For
- * instance when a program is in a signal handler; for another instance when
- * the GC is busy. On such situations however, there might be needs to do
- * something. We cannot but defer such operations until we are 100% sure it is
- * safe to execute them. This mechanism is called postponed jobs. This
- * function registers a new one. The registered job would eventually gets
- * executed.
+ * Like `rb_postponed_job_trigger`, the context in which the job is called
+ * holds the GVL and can allocate Ruby objects.
*
- * @param[in] flags (Unused) reserved for future extensions.
+ * This method essentially has the same semantics as:
+ *
+ * ```
+ * rb_postponed_job_trigger(rb_postponed_job_preregister(func, data));
+ * ```
+ *
+ * @note Prevoius versions of Ruby promised that the (`func`, `data`) pairs would
+ * be executed as many times as they were registered with this function; in
+ * reality this was always subject to race conditions and this function no
+ * longer provides this guarantee. Instead, we only promise that `func` will
+ * be called once.
+ *
+ * @deprecated This interface implies that arbitrarily many `func`'s can be enqueued
+ * over the lifetime of the program, whilst in reality the registration
+ * slots for postponed jobs are a finite resource. This is made clearer
+ * by the `rb_postponed_job_preregister` and `rb_postponed_job_trigger`
+ * functions, and a future version of Ruby might delete this function.
+ *
+ * @param[in] flags Unused and ignored.
* @param[in] func Job body.
* @param[in,out] data Passed as-is to `func`.
- * @retval 0 Postponed job buffer is full. Failed.
- * @retval otherwise Opaque return value.
- * @post The passed job is postponed.
+ * @retval 0 Postponed job registration table is full. Failed.
+ * @retval 1 Registration succeeded.
+ * @post The passed job will run on the next interrupt check.
*/
+ RBIMPL_ATTR_DEPRECATED(("use rb_postponed_job_preregister and rb_postponed_job_trigger"))
int rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data);
/**
- * Identical to rb_postponed_job_register(), except it additionally checks for
- * duplicated registration. In case the passed job is already in the postponed
- * job buffer this function does nothing.
+ * Identical to `rb_postponed_job_register`
+ *
+ * @deprecated This is deprecated for the same reason as `rb_postponed_job_register`
*
- * @param[in] flags (Unused) reserved for future extensions.
+ * @param[in] flags Unused and ignored.
* @param[in] func Job body.
* @param[in,out] data Passed as-is to `func`.
- * @retval 0 Postponed job buffer is full. Failed.
- * @retval otherwise Opaque return value.
+ * @retval 0 Postponed job registration table is full. Failed.
+ * @retval 1 Registration succeeded.
+ * @post The passed job will run on the next interrupt check.
*/
+ RBIMPL_ATTR_DEPRECATED(("use rb_postponed_job_preregister and rb_postponed_job_trigger"))
int rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data);
/** @} */
diff --git a/inits.c b/inits.c
index 46530bb6d0..a114b780aa 100644
--- a/inits.c
+++ b/inits.c
@@ -22,7 +22,6 @@ rb_call_inits(void)
{
CALL(default_shapes);
CALL(Thread_Mutex);
- CALL(vm_postponed_job);
CALL(RandomSeedCore);
CALL(encodings);
CALL(sym);
diff --git a/rjit.c b/rjit.c
index 4ce6af4d4b..1ce02649de 100644
--- a/rjit.c
+++ b/rjit.c
@@ -101,6 +101,9 @@ VALUE rb_rjit_raw_samples = 0;
// Line numbers for --rjit-trace-exits
VALUE rb_rjit_line_samples = 0;
+// Postponed job handle for triggering rjit_iseq_update_references
+static rb_postponed_job_handle_t rjit_iseq_update_references_pjob;
+
// A default threshold used to add iseq to JIT.
#define DEFAULT_CALL_THRESHOLD 10
// Size of executable memory block in MiB.
@@ -301,7 +304,7 @@ rb_rjit_iseq_update_references(struct rb_iseq_constant_body *const body)
// Asynchronously hook the Ruby code to avoid allocation during GC.compact.
// Using _one because it's too slow to invalidate all for each ISEQ. Thus
// not giving an ISEQ pointer.
- rb_postponed_job_register_one(0, rjit_iseq_update_references, NULL);
+ rb_postponed_job_trigger(rjit_iseq_update_references_pjob);
}
void
@@ -429,6 +432,10 @@ rb_rjit_init(const struct rb_rjit_options *opts)
rb_rjit_enabled = false;
return;
}
+ rjit_iseq_update_references_pjob = rb_postponed_job_preregister(rjit_iseq_update_references, NULL);
+ if (rjit_iseq_update_references_pjob == POSTPONED_JOB_HANDLE_INVALID) {
+ rb_bug("Could not preregister postponed job for RJIT");
+ }
rb_mRJITC = rb_const_get(rb_mRJIT, rb_intern("C"));
VALUE rb_cRJITCompiler = rb_const_get(rb_mRJIT, rb_intern("Compiler"));
rb_RJITCompiler = rb_funcall(rb_cRJITCompiler, rb_intern("new"), 0);
diff --git a/test/-ext-/postponed_job/test_postponed_job.rb b/test/-ext-/postponed_job/test_postponed_job.rb
index fee0172d11..6831ef7b37 100644
--- a/test/-ext-/postponed_job/test_postponed_job.rb
+++ b/test/-ext-/postponed_job/test_postponed_job.rb
@@ -2,34 +2,62 @@
require 'test/unit'
require '-test-/postponed_job'
-module Bug
- def self.postponed_job_call_direct_wrapper(*args)
- postponed_job_call_direct(*args)
+class TestPostponed_job < Test::Unit::TestCase
+ def test_preregister_and_trigger
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ Bug.postponed_job_preregister_and_call_without_sleep(counters = [])
+ # i.e. rb_postponed_job_trigger performs coalescing
+ assert_equal([3], counters)
+
+ # i.e. rb_postponed_job_trigger resets after interrupts are checked
+ Bug.postponed_job_preregister_and_call_with_sleep(counters = [])
+ assert_equal([1, 2, 3], counters)
+ RUBY
end
- def self.postponed_job_register_wrapper(*args)
- postponed_job_register(*args)
+ def test_multiple_preregistration
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ handles = Bug.postponed_job_preregister_multiple_times
+ # i.e. rb_postponed_job_preregister returns the same handle if preregistered multiple times
+ assert_equal [handles[0]], handles.uniq
+ RUBY
end
-end
-class TestPostponed_job < Test::Unit::TestCase
- def test_register
- direct, registered = [], []
- Bug.postponed_job_call_direct_wrapper(direct)
- Bug.postponed_job_register_wrapper(registered)
+ def test_legacy_register
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ direct, registered = [], []
+
+ Bug.postponed_job_call_direct(direct)
+ Bug.postponed_job_register(registered)
- assert_equal([0], direct)
- assert_equal([3], registered)
+ assert_equal([0], direct)
+ assert_equal([3], registered)
- Bug.postponed_job_register_one(ary = [])
- assert_equal [1], ary
+ Bug.postponed_job_register_one(ary = [])
+ assert_equal [1], ary
+ RUBY
+ end
+
+ def test_legacy_register_one_same
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ # Registering the same job three times should result in three of the same handle
+ handles = Bug.postponed_job_register_one_same
+ assert_equal [handles[0]], handles.uniq
+ RUBY
end
if Bug.respond_to?(:postponed_job_register_in_c_thread)
- def test_register_in_c_thread
- assert Bug.postponed_job_register_in_c_thread(ary = [])
- assert_equal [1], ary
+ def test_legacy_register_in_c_thread
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ require '-test-/postponed_job'
+ assert Bug.postponed_job_register_in_c_thread(ary = [])
+ assert_equal [1], ary
+ RUBY
end
end
end
diff --git a/thread.c b/thread.c
index 3e9493ae3c..1539153dac 100644
--- a/thread.c
+++ b/thread.c
@@ -4626,6 +4626,7 @@ rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const r
rb_vm_living_threads_init(vm);
rb_ractor_atfork(vm, th);
+ rb_vm_postponed_job_atfork();
/* may be held by RJIT threads in parent */
rb_native_mutex_initialize(&vm->workqueue_lock);
diff --git a/vm.c b/vm.c
index d7ae74a968..f247838764 100644
--- a/vm.c
+++ b/vm.c
@@ -3029,7 +3029,7 @@ ruby_vm_destruct(rb_vm_t *vm)
st_free_table(vm->static_ext_inits);
st_free_table(vm->ensure_rollback_table);
- ruby_xfree(vm->postponed_job_buffer);
+ rb_vm_postponed_job_free();
st_free_table(vm->defined_module_hash);
rb_id_table_free(vm->constant_cache);
@@ -3077,7 +3077,6 @@ ruby_vm_destruct(rb_vm_t *vm)
}
size_t rb_vm_memsize_waiting_fds(struct ccan_list_head *waiting_fds); // thread.c
-size_t rb_vm_memsize_postponed_job_buffer(void); // vm_trace.c
size_t rb_vm_memsize_workqueue(struct ccan_list_head *workqueue); // vm_trace.c
// Used for VM memsize reporting. Returns the size of the at_exit list by
@@ -3136,7 +3135,7 @@ vm_memsize(const void *ptr)
rb_st_memsize(vm->loaded_features_index) +
rb_st_memsize(vm->loading_table) +
rb_st_memsize(vm->ensure_rollback_table) +
- rb_vm_memsize_postponed_job_buffer() +
+ rb_vm_memsize_postponed_job_queue() +
rb_vm_memsize_workqueue(&vm->workqueue) +
rb_st_memsize(vm->defined_module_hash) +
vm_memsize_at_exit_list(vm->at_exit) +
@@ -4180,8 +4179,9 @@ Init_BareVM(void)
MEMZERO(th, rb_thread_t, 1);
vm_init2(vm);
- vm->objspace = rb_objspace_alloc();
+ rb_vm_postponed_job_queue_init(vm);
ruby_current_vm_ptr = vm;
+ vm->objspace = rb_objspace_alloc();
vm->negative_cme_table = rb_id_table_create(16);
vm->overloaded_cme_table = st_init_numtable();
vm->constant_cache = rb_id_table_create(0);
diff --git a/vm_core.h b/vm_core.h
index cd720fd8cf..ef770ab441 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -726,9 +726,8 @@ typedef struct rb_vm_struct {
/* relation table of ensure - rollback for callcc */
struct st_table *ensure_rollback_table;
- /* postponed_job (async-signal-safe, NOT thread-safe) */
- struct rb_postponed_job_struct *postponed_job_buffer;
- rb_atomic_t postponed_job_index;
+ /* postponed_job (async-signal-safe, and thread-safe) */
+ struct rb_postponed_job_queue *postponed_job_queue;
int src_encoding_index;
@@ -2171,6 +2170,10 @@ rb_exec_event_hook_script_compiled(rb_execution_context_t *ec, const rb_iseq_t *
}
void rb_vm_trap_exit(rb_vm_t *vm);
+void rb_vm_postponed_job_atfork(void); /* vm_trace.c */
+void rb_vm_postponed_job_free(void); /* vm_trace.c */
+size_t rb_vm_memsize_postponed_job_queue(void); /* vm_trace.c */
+void rb_vm_postponed_job_queue_init(rb_vm_t *vm); /* vm_trace.c */
RUBY_SYMBOL_EXPORT_BEGIN
diff --git a/vm_trace.c b/vm_trace.c
index 6114ef9d4e..dc4c4738b8 100644
--- a/vm_trace.c
+++ b/vm_trace.c
@@ -23,11 +23,15 @@
#include "eval_intern.h"
#include "internal.h"
+#include "internal/bits.h"
#include "internal/class.h"
+#include "internal/gc.h"
#include "internal/hash.h"
#include "internal/symbol.h"
+#include "internal/thread.h"
#include "iseq.h"
#include "rjit.h"
+#include "ruby/atomic.h"
#include "ruby/debug.h"
#include "vm_core.h"
#include "ruby/ractor.h"
@@ -1617,17 +1621,22 @@ Init_vm_trace(void)
rb_undef_alloc_func(rb_cTracePoint);
}
-typedef struct rb_postponed_job_struct {
- rb_postponed_job_func_t func;
- void *data;
-} rb_postponed_job_t;
-
-#define MAX_POSTPONED_JOB 1000
-#define MAX_POSTPONED_JOB_SPECIAL_ADDITION 24
+/*
+ * 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.
+ */
struct rb_workqueue_job {
struct ccan_list_node jnode; /* <=> vm->workqueue */
- rb_postponed_job_t job;
+ rb_postponed_job_func_t func;
+ void *data;
};
// Used for VM memsize reporting. Returns the size of a list of rb_workqueue_job
@@ -1645,52 +1654,51 @@ rb_vm_memsize_workqueue(struct ccan_list_head *workqueue)
return size;
}
-// Used for VM memsize reporting. Returns the total size of the postponed job
-// buffer that was allocated at initialization.
-size_t
-rb_vm_memsize_postponed_job_buffer(void)
-{
- return sizeof(rb_postponed_job_t) * MAX_POSTPONED_JOB;
-}
-
-void
-Init_vm_postponed_job(void)
+/*
+ * 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)
{
+ struct rb_workqueue_job *wq_job = malloc(sizeof(*wq_job));
rb_vm_t *vm = GET_VM();
- vm->postponed_job_buffer = ALLOC_N(rb_postponed_job_t, MAX_POSTPONED_JOB);
- vm->postponed_job_index = 0;
- /* workqueue is initialized when VM locks are initialized */
-}
-
-enum postponed_job_register_result {
- PJRR_SUCCESS = 0,
- PJRR_FULL = 1,
- PJRR_INTERRUPTED = 2
-};
-/* Async-signal-safe */
-static enum postponed_job_register_result
-postponed_job_register(rb_execution_context_t *ec, rb_vm_t *vm,
- unsigned int flags, rb_postponed_job_func_t func, void *data, rb_atomic_t max, rb_atomic_t expected_index)
-{
- rb_postponed_job_t *pjob;
+ if (!wq_job) return FALSE;
+ wq_job->func = func;
+ wq_job->data = data;
- if (expected_index >= max) return PJRR_FULL; /* failed */
+ rb_nativethread_lock_lock(&vm->workqueue_lock);
+ ccan_list_add_tail(&vm->workqueue, &wq_job->jnode);
+ rb_nativethread_lock_unlock(&vm->workqueue_lock);
- if (ATOMIC_CAS(vm->postponed_job_index, expected_index, expected_index+1) == expected_index) {
- pjob = &vm->postponed_job_buffer[expected_index];
- }
- else {
- return PJRR_INTERRUPTED;
- }
+ // TODO: current implementation affects only main ractor
+ RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(rb_vm_main_ractor_ec(vm));
- /* unused: pjob->flags = flags; */
- pjob->func = func;
- pjob->data = data;
+ return TRUE;
+}
- RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(ec);
+#define PJOB_PREREG_TABLE_SIZE (sizeof(rb_atomic_t) * CHAR_BIT)
+/* pre-registered jobs table, for async-safe jobs */
+typedef struct rb_postponed_job_queue {
+ struct {
+ rb_postponed_job_func_t func;
+ void *data;
+ } prereg_table[PJOB_PREREG_TABLE_SIZE];
+ /* 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 */
+ rb_atomic_t prereg_triggered_bitset;
+} rb_postponed_job_queues_t;
- return PJRR_SUCCESS;
+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));
+ pjq->prereg_triggered_bitset = 0;
+ memset(pjq->prereg_table, 0, sizeof(pjq->prereg_table));
+ vm->postponed_job_queue = pjq;
}
static rb_execution_context_t *
@@ -1701,83 +1709,113 @@ get_valid_ec(rb_vm_t *vm)
return ec;
}
-/*
- * return 0 if job buffer is full
- * Async-signal-safe
- */
-int
-rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data)
+void
+rb_vm_postponed_job_atfork(void)
{
rb_vm_t *vm = GET_VM();
- rb_execution_context_t *ec = get_valid_ec(vm);
-
- begin:
- switch (postponed_job_register(ec, vm, flags, func, data, MAX_POSTPONED_JOB, vm->postponed_job_index)) {
- case PJRR_SUCCESS : return 1;
- case PJRR_FULL : return 0;
- case PJRR_INTERRUPTED: goto begin;
- default: rb_bug("unreachable");
+ 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 */
+ if (pjq->prereg_triggered_bitset) {
+ RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(get_valid_ec(vm));
}
+
}
-/*
- * return 0 if job buffer is full
- * Async-signal-safe
- */
-int
-rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data)
+/* Frees the memory managed by the postponed job infrastructure at shutdown */
+void
+rb_vm_postponed_job_free(void)
{
rb_vm_t *vm = GET_VM();
- rb_execution_context_t *ec = get_valid_ec(vm);
- rb_postponed_job_t *pjob;
- rb_atomic_t i, index;
-
- begin:
- index = vm->postponed_job_index;
- for (i=0; i<index; i++) {
- pjob = &vm->postponed_job_buffer[i];
- if (pjob->func == func) {
- RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(ec);
- return 2;
+ 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
+rb_postponed_job_preregister(rb_postponed_job_func_t func, void *data)
+{
+ /* 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;
+ for (unsigned int i = 0; i < PJOB_PREREG_TABLE_SIZE; i++) {
+ /* Try and set this slot to equal `func` */
+ rb_postponed_job_func_t existing_func = (rb_postponed_job_func_t)RUBY_ATOMIC_PTR_CAS(pjq->prereg_table[i], NULL, (void *)func);
+ 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 */
+ RUBY_ATOMIC_PTR_EXCHANGE(pjq->prereg_table[i].data, data);
+ return (rb_postponed_job_handle_t)i;
+ } else {
+ /* Try the next slot if this one already has a func in it */
+ continue;
}
}
- switch (postponed_job_register(ec, vm, flags, func, data, MAX_POSTPONED_JOB + MAX_POSTPONED_JOB_SPECIAL_ADDITION, index)) {
- case PJRR_SUCCESS : return 1;
- case PJRR_FULL : return 0;
- case PJRR_INTERRUPTED: goto begin;
- default: rb_bug("unreachable");
- }
+
+ /* full */
+ return POSTPONED_JOB_HANDLE_INVALID;
}
-/*
- * 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)
+void
+rb_postponed_job_trigger(rb_postponed_job_handle_t h)
{
- struct rb_workqueue_job *wq_job = malloc(sizeof(*wq_job));
rb_vm_t *vm = GET_VM();
+ rb_postponed_job_queues_t *pjq = vm->postponed_job_queue;
- if (!wq_job) return FALSE;
- wq_job->job.func = func;
- wq_job->job.data = data;
+ RUBY_ATOMIC_OR(pjq->prereg_triggered_bitset, (((rb_atomic_t)1UL) << h));
+ RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(get_valid_ec(vm));
+}
- rb_nativethread_lock_lock(&vm->workqueue_lock);
- ccan_list_add_tail(&vm->workqueue, &wq_job->jnode);
- rb_nativethread_lock_unlock(&vm->workqueue_lock);
- // TODO: current implementation affects only main ractor
- RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(rb_vm_main_ractor_ec(vm));
+static int
+pjob_register_legacy_impl(unsigned int flags, rb_postponed_job_func_t func, void *data)
+{
+ /* We _know_ calling preregister from a signal handler like this is racy; what is
+ * and is not promised is very exhaustively documented in debug.h */
+ rb_postponed_job_handle_t h = rb_postponed_job_preregister(func, data);
+ if (h == POSTPONED_JOB_HANDLE_INVALID) {
+ return 0;
+ }
+ rb_postponed_job_trigger(h);
+ return 1;
+}
- return TRUE;
+int
+rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data)
+{
+ return pjob_register_legacy_impl(flags, func, data);
}
+int
+rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data)
+{
+ return pjob_register_legacy_impl(flags, func, data);
+}
+
+
void
rb_postponed_job_flush(rb_vm_t *vm)
{
+ rb_postponed_job_queues_t *pjq = GET_VM()->postponed_job_queue;
rb_execution_context_t *ec = GET_EC();
- const rb_atomic_t block_mask = POSTPONED_JOB_INTERRUPT_MASK|TRAP_INTERRUPT_MASK;
+ const rb_atomic_t block_mask = POSTPONED_JOB_INTERRUPT_MASK | TRAP_INTERRUPT_MASK;
volatile rb_atomic_t saved_mask = ec->interrupt_mask & block_mask;
VALUE volatile saved_errno = ec->errinfo;
struct ccan_list_head tmp;
@@ -1788,26 +1826,31 @@ rb_postponed_job_flush(rb_vm_t *vm)
ccan_list_append_list(&tmp, &vm->workqueue);
rb_nativethread_lock_unlock(&vm->workqueue_lock);
+ rb_atomic_t prereg_triggered_bits = RUBY_ATOMIC_EXCHANGE(pjq->prereg_triggered_bitset, 0);
+
ec->errinfo = Qnil;
/* mask POSTPONED_JOB dispatch */
ec->interrupt_mask |= block_mask;
{
EC_PUSH_TAG(ec);
if (EC_EXEC_TAG() == TAG_NONE) {
- rb_atomic_t index;
- struct rb_workqueue_job *wq_job;
-
- while ((index = vm->postponed_job_index) > 0) {
- if (ATOMIC_CAS(vm->postponed_job_index, index, index-1) == index) {
- rb_postponed_job_t *pjob = &vm->postponed_job_buffer[index-1];
- (*pjob->func)(pjob->data);
- }
+ /* execute postponed jobs */
+ while (prereg_triggered_bits) {
+ unsigned int i = bit_length(prereg_triggered_bits) - 1;
+ prereg_triggered_bits ^= ((1UL) << i); /* toggle ith bit off */
+ rb_postponed_job_func_t func = pjq->prereg_table[i].func;
+ void *data = pjq->prereg_table[i].data;
+ (func)(data);
}
+
+ /* execute workqueue jobs */
+ struct rb_workqueue_job *wq_job;
while ((wq_job = ccan_list_pop(&tmp, struct rb_workqueue_job, jnode))) {
- rb_postponed_job_t pjob = wq_job->job;
+ rb_postponed_job_func_t func = wq_job->func;
+ void *data = wq_job->data;
free(wq_job);
- (pjob.func)(pjob.data);
+ (func)(data);
}
}
EC_POP_TAG();
@@ -1816,7 +1859,8 @@ rb_postponed_job_flush(rb_vm_t *vm)
ec->interrupt_mask &= ~(saved_mask ^ block_mask);
ec->errinfo = saved_errno;
- /* don't leak memory if a job threw an exception */
+ /* If we threw an exception, there might be leftover workqueue items; carry them over
+ * to a subsequent execution of flush */
if (!ccan_list_empty(&tmp)) {
rb_nativethread_lock_lock(&vm->workqueue_lock);
ccan_list_prepend_list(&vm->workqueue, &tmp);
@@ -1824,4 +1868,10 @@ rb_postponed_job_flush(rb_vm_t *vm)
RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(GET_EC());
}
+ /* likewise with any remaining-to-be-executed bits of the preregistered postponed
+ * job table */
+ if (prereg_triggered_bits) {
+ RUBY_ATOMIC_OR(pjq->prereg_triggered_bitset, prereg_triggered_bits);
+ RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(GET_EC());
+ }
}