summaryrefslogtreecommitdiff
path: root/ext/-test-/postponed_job
diff options
context:
space:
mode:
authorKJ Tsanaktsidis <kj@kjtsanaktsidis.id.au>2023-11-19 22:54:57 +1100
committerKoichi Sasada <ko1@atdot.net>2023-12-10 15:00:37 +0900
commitf8effa209adb3ce050c100ffaffe6f3cc1508185 (patch)
treec0a672d5917a9917910679504d0f8b2d450088f7 /ext/-test-/postponed_job
parentaecbd66742f43ccfcac04ca4143fcc68ad834320 (diff)
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
Diffstat (limited to 'ext/-test-/postponed_job')
-rw-r--r--ext/-test-/postponed_job/postponed_job.c94
1 files changed, 94 insertions, 0 deletions
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);
}