Skip to content

Commit f8effa2

Browse files
KJTsanaktsidisko1
authored andcommitted
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
1 parent aecbd66 commit f8effa2

File tree

14 files changed

+556
-169
lines changed

14 files changed

+556
-169
lines changed

common.mk

Lines changed: 51 additions & 0 deletions
Large diffs are not rendered by default.

ext/-test-/postponed_job/postponed_job.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
#include "ruby.h"
22
#include "ruby/debug.h"
33

4+
// We're testing deprecated things, don't print the compiler warnings
5+
#if 0
6+
7+
#elif defined(_MSC_VER)
8+
#pragma warning(disable : 4996)
9+
10+
#elif defined(__INTEL_COMPILER)
11+
#pragma warning(disable : 1786)
12+
13+
#elif defined(__clang__)
14+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
15+
16+
#elif defined(__GNUC__)
17+
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
18+
19+
#elif defined(__SUNPRO_CC)
20+
#pragma error_messages (off,symdeprecated)
21+
22+
#else
23+
// :FIXME: improve here for your compiler.
24+
25+
#endif
26+
427
static int counter;
528

629
static void
@@ -58,6 +81,22 @@ pjob_call_direct(VALUE self, VALUE obj)
5881
return self;
5982
}
6083

84+
static void pjob_noop_callback(void *data) { }
85+
86+
static VALUE
87+
pjob_register_one_same(VALUE self)
88+
{
89+
rb_gc_start();
90+
int r1 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL);
91+
int r2 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL);
92+
int r3 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL);
93+
VALUE ary = rb_ary_new();
94+
rb_ary_push(ary, INT2FIX(r1));
95+
rb_ary_push(ary, INT2FIX(r2));
96+
rb_ary_push(ary, INT2FIX(r3));
97+
return ary;
98+
}
99+
61100
#ifdef HAVE_PTHREAD_H
62101
#include <pthread.h>
63102

@@ -86,15 +125,70 @@ pjob_register_in_c_thread(VALUE self, VALUE obj)
86125
}
87126
#endif
88127

128+
static void
129+
pjob_preregistered_callback(void *data)
130+
{
131+
VALUE ary = (VALUE)data;
132+
Check_Type(ary, T_ARRAY);
133+
rb_ary_push(ary, INT2FIX(counter));
134+
}
135+
136+
static VALUE
137+
pjob_preregister_and_call_with_sleep(VALUE self, VALUE obj)
138+
{
139+
counter = 0;
140+
rb_postponed_job_handle_t h = rb_postponed_job_preregister(pjob_preregistered_callback, (void *)obj);
141+
counter++;
142+
rb_postponed_job_trigger(h);
143+
rb_thread_sleep(0);
144+
counter++;
145+
rb_postponed_job_trigger(h);
146+
rb_thread_sleep(0);
147+
counter++;
148+
rb_postponed_job_trigger(h);
149+
rb_thread_sleep(0);
150+
return self;
151+
}
152+
153+
static VALUE
154+
pjob_preregister_and_call_without_sleep(VALUE self, VALUE obj)
155+
{
156+
counter = 0;
157+
rb_postponed_job_handle_t h = rb_postponed_job_preregister(pjob_preregistered_callback, (void *)obj);
158+
counter = 3;
159+
rb_postponed_job_trigger(h);
160+
rb_postponed_job_trigger(h);
161+
rb_postponed_job_trigger(h);
162+
return self;
163+
}
164+
165+
static VALUE
166+
pjob_preregister_multiple_times(VALUE self)
167+
{
168+
int r1 = rb_postponed_job_preregister(pjob_noop_callback, NULL);
169+
int r2 = rb_postponed_job_preregister(pjob_noop_callback, NULL);
170+
int r3 = rb_postponed_job_preregister(pjob_noop_callback, NULL);
171+
VALUE ary = rb_ary_new();
172+
rb_ary_push(ary, INT2FIX(r1));
173+
rb_ary_push(ary, INT2FIX(r2));
174+
rb_ary_push(ary, INT2FIX(r3));
175+
return ary;
176+
177+
}
178+
89179
void
90180
Init_postponed_job(VALUE self)
91181
{
92182
VALUE mBug = rb_define_module("Bug");
93183
rb_define_module_function(mBug, "postponed_job_register", pjob_register, 1);
94184
rb_define_module_function(mBug, "postponed_job_register_one", pjob_register_one, 1);
95185
rb_define_module_function(mBug, "postponed_job_call_direct", pjob_call_direct, 1);
186+
rb_define_module_function(mBug, "postponed_job_register_one_same", pjob_register_one_same, 0);
96187
#ifdef HAVE_PTHREAD_H
97188
rb_define_module_function(mBug, "postponed_job_register_in_c_thread", pjob_register_in_c_thread, 1);
98189
#endif
190+
rb_define_module_function(mBug, "postponed_job_preregister_and_call_with_sleep", pjob_preregister_and_call_with_sleep, 1);
191+
rb_define_module_function(mBug, "postponed_job_preregister_and_call_without_sleep", pjob_preregister_and_call_without_sleep, 1);
192+
rb_define_module_function(mBug, "postponed_job_preregister_multiple_times", pjob_preregister_multiple_times, 0);
99193
}
100194

ext/-test-/tracepoint/gc_hook.c

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
#include "ruby/debug.h"
33

44
static int invoking; /* TODO: should not be global variable */
5+
static VALUE gc_start_proc;
6+
static VALUE gc_end_proc;
7+
static rb_postponed_job_handle_t invoking_proc_pjob;
8+
static bool pjob_execute_gc_start_proc_p;
9+
static bool pjob_execute_gc_end_proc_p;
510

611
static VALUE
712
invoke_proc_ensure(VALUE _)
@@ -17,23 +22,35 @@ invoke_proc_begin(VALUE proc)
1722
}
1823

1924
static void
20-
invoke_proc(void *data)
25+
invoke_proc(void *unused)
2126
{
22-
VALUE proc = (VALUE)data;
23-
invoking += 1;
24-
rb_ensure(invoke_proc_begin, proc, invoke_proc_ensure, 0);
27+
if (pjob_execute_gc_start_proc_p) {
28+
pjob_execute_gc_start_proc_p = false;
29+
invoking += 1;
30+
rb_ensure(invoke_proc_begin, gc_start_proc, invoke_proc_ensure, 0);
31+
}
32+
if (pjob_execute_gc_end_proc_p) {
33+
pjob_execute_gc_end_proc_p = false;
34+
invoking += 1;
35+
rb_ensure(invoke_proc_begin, gc_end_proc, invoke_proc_ensure, 0);
36+
}
2537
}
2638

2739
static void
2840
gc_start_end_i(VALUE tpval, void *data)
2941
{
42+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
3043
if (0) {
31-
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
3244
fprintf(stderr, "trace: %s\n", rb_tracearg_event_flag(tparg) == RUBY_INTERNAL_EVENT_GC_START ? "gc_start" : "gc_end");
3345
}
3446

3547
if (invoking == 0) {
36-
rb_postponed_job_register(0, invoke_proc, data);
48+
if (rb_tracearg_event_flag(tparg) == RUBY_INTERNAL_EVENT_GC_START) {
49+
pjob_execute_gc_start_proc_p = true;
50+
} else {
51+
pjob_execute_gc_end_proc_p = true;
52+
}
53+
rb_postponed_job_trigger(invoking_proc_pjob);
3754
}
3855
}
3956

@@ -54,8 +71,13 @@ set_gc_hook(VALUE module, VALUE proc, rb_event_flag_t event, const char *tp_str,
5471
if (!rb_obj_is_proc(proc)) {
5572
rb_raise(rb_eTypeError, "trace_func needs to be Proc");
5673
}
74+
if (event == RUBY_INTERNAL_EVENT_GC_START) {
75+
gc_start_proc = proc;
76+
} else {
77+
gc_end_proc = proc;
78+
}
5779

58-
tpval = rb_tracepoint_new(0, event, gc_start_end_i, (void *)proc);
80+
tpval = rb_tracepoint_new(0, event, gc_start_end_i, 0);
5981
rb_ivar_set(module, tp_key, tpval);
6082
rb_tracepoint_enable(tpval);
6183
}
@@ -82,4 +104,10 @@ Init_gc_hook(VALUE module)
82104
{
83105
rb_define_module_function(module, "after_gc_start_hook=", set_after_gc_start, 1);
84106
rb_define_module_function(module, "after_gc_exit_hook=", start_after_gc_exit, 1);
107+
rb_gc_register_address(&gc_start_proc);
108+
rb_gc_register_address(&gc_end_proc);
109+
invoking_proc_pjob = rb_postponed_job_preregister(invoke_proc, NULL);
110+
if (invoking_proc_pjob == POSTPONED_JOB_HANDLE_INVALID) {
111+
rb_raise(rb_eStandardError, "could not preregister invoke_proc");
112+
}
85113
}

ext/ripper/depend

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ ripper.o: $(hdrdir)/ruby/st.h
566566
ripper.o: $(hdrdir)/ruby/subst.h
567567
ripper.o: $(hdrdir)/ruby/thread_native.h
568568
ripper.o: $(hdrdir)/ruby/util.h
569+
ripper.o: $(hdrdir)/ruby/version.h
569570
ripper.o: $(top_srcdir)/ccan/check_type/check_type.h
570571
ripper.o: $(top_srcdir)/ccan/container_of/container_of.h
571572
ripper.o: $(top_srcdir)/ccan/list/list.h

ext/socket/depend

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ ancdata.o: $(hdrdir)/ruby/subst.h
186186
ancdata.o: $(hdrdir)/ruby/thread.h
187187
ancdata.o: $(hdrdir)/ruby/thread_native.h
188188
ancdata.o: $(hdrdir)/ruby/util.h
189+
ancdata.o: $(hdrdir)/ruby/version.h
189190
ancdata.o: $(top_srcdir)/ccan/check_type/check_type.h
190191
ancdata.o: $(top_srcdir)/ccan/container_of/container_of.h
191192
ancdata.o: $(top_srcdir)/ccan/list/list.h
@@ -394,6 +395,7 @@ basicsocket.o: $(hdrdir)/ruby/subst.h
394395
basicsocket.o: $(hdrdir)/ruby/thread.h
395396
basicsocket.o: $(hdrdir)/ruby/thread_native.h
396397
basicsocket.o: $(hdrdir)/ruby/util.h
398+
basicsocket.o: $(hdrdir)/ruby/version.h
397399
basicsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
398400
basicsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
399401
basicsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -602,6 +604,7 @@ constants.o: $(hdrdir)/ruby/subst.h
602604
constants.o: $(hdrdir)/ruby/thread.h
603605
constants.o: $(hdrdir)/ruby/thread_native.h
604606
constants.o: $(hdrdir)/ruby/util.h
607+
constants.o: $(hdrdir)/ruby/version.h
605608
constants.o: $(top_srcdir)/ccan/check_type/check_type.h
606609
constants.o: $(top_srcdir)/ccan/container_of/container_of.h
607610
constants.o: $(top_srcdir)/ccan/list/list.h
@@ -811,6 +814,7 @@ ifaddr.o: $(hdrdir)/ruby/subst.h
811814
ifaddr.o: $(hdrdir)/ruby/thread.h
812815
ifaddr.o: $(hdrdir)/ruby/thread_native.h
813816
ifaddr.o: $(hdrdir)/ruby/util.h
817+
ifaddr.o: $(hdrdir)/ruby/version.h
814818
ifaddr.o: $(top_srcdir)/ccan/check_type/check_type.h
815819
ifaddr.o: $(top_srcdir)/ccan/container_of/container_of.h
816820
ifaddr.o: $(top_srcdir)/ccan/list/list.h
@@ -1019,6 +1023,7 @@ init.o: $(hdrdir)/ruby/subst.h
10191023
init.o: $(hdrdir)/ruby/thread.h
10201024
init.o: $(hdrdir)/ruby/thread_native.h
10211025
init.o: $(hdrdir)/ruby/util.h
1026+
init.o: $(hdrdir)/ruby/version.h
10221027
init.o: $(top_srcdir)/ccan/check_type/check_type.h
10231028
init.o: $(top_srcdir)/ccan/container_of/container_of.h
10241029
init.o: $(top_srcdir)/ccan/list/list.h
@@ -1227,6 +1232,7 @@ ipsocket.o: $(hdrdir)/ruby/subst.h
12271232
ipsocket.o: $(hdrdir)/ruby/thread.h
12281233
ipsocket.o: $(hdrdir)/ruby/thread_native.h
12291234
ipsocket.o: $(hdrdir)/ruby/util.h
1235+
ipsocket.o: $(hdrdir)/ruby/version.h
12301236
ipsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
12311237
ipsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
12321238
ipsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -1435,6 +1441,7 @@ option.o: $(hdrdir)/ruby/subst.h
14351441
option.o: $(hdrdir)/ruby/thread.h
14361442
option.o: $(hdrdir)/ruby/thread_native.h
14371443
option.o: $(hdrdir)/ruby/util.h
1444+
option.o: $(hdrdir)/ruby/version.h
14381445
option.o: $(top_srcdir)/ccan/check_type/check_type.h
14391446
option.o: $(top_srcdir)/ccan/container_of/container_of.h
14401447
option.o: $(top_srcdir)/ccan/list/list.h
@@ -1643,6 +1650,7 @@ raddrinfo.o: $(hdrdir)/ruby/subst.h
16431650
raddrinfo.o: $(hdrdir)/ruby/thread.h
16441651
raddrinfo.o: $(hdrdir)/ruby/thread_native.h
16451652
raddrinfo.o: $(hdrdir)/ruby/util.h
1653+
raddrinfo.o: $(hdrdir)/ruby/version.h
16461654
raddrinfo.o: $(top_srcdir)/ccan/check_type/check_type.h
16471655
raddrinfo.o: $(top_srcdir)/ccan/container_of/container_of.h
16481656
raddrinfo.o: $(top_srcdir)/ccan/list/list.h
@@ -1851,6 +1859,7 @@ socket.o: $(hdrdir)/ruby/subst.h
18511859
socket.o: $(hdrdir)/ruby/thread.h
18521860
socket.o: $(hdrdir)/ruby/thread_native.h
18531861
socket.o: $(hdrdir)/ruby/util.h
1862+
socket.o: $(hdrdir)/ruby/version.h
18541863
socket.o: $(top_srcdir)/ccan/check_type/check_type.h
18551864
socket.o: $(top_srcdir)/ccan/container_of/container_of.h
18561865
socket.o: $(top_srcdir)/ccan/list/list.h
@@ -2059,6 +2068,7 @@ sockssocket.o: $(hdrdir)/ruby/subst.h
20592068
sockssocket.o: $(hdrdir)/ruby/thread.h
20602069
sockssocket.o: $(hdrdir)/ruby/thread_native.h
20612070
sockssocket.o: $(hdrdir)/ruby/util.h
2071+
sockssocket.o: $(hdrdir)/ruby/version.h
20622072
sockssocket.o: $(top_srcdir)/ccan/check_type/check_type.h
20632073
sockssocket.o: $(top_srcdir)/ccan/container_of/container_of.h
20642074
sockssocket.o: $(top_srcdir)/ccan/list/list.h
@@ -2267,6 +2277,7 @@ tcpserver.o: $(hdrdir)/ruby/subst.h
22672277
tcpserver.o: $(hdrdir)/ruby/thread.h
22682278
tcpserver.o: $(hdrdir)/ruby/thread_native.h
22692279
tcpserver.o: $(hdrdir)/ruby/util.h
2280+
tcpserver.o: $(hdrdir)/ruby/version.h
22702281
tcpserver.o: $(top_srcdir)/ccan/check_type/check_type.h
22712282
tcpserver.o: $(top_srcdir)/ccan/container_of/container_of.h
22722283
tcpserver.o: $(top_srcdir)/ccan/list/list.h
@@ -2475,6 +2486,7 @@ tcpsocket.o: $(hdrdir)/ruby/subst.h
24752486
tcpsocket.o: $(hdrdir)/ruby/thread.h
24762487
tcpsocket.o: $(hdrdir)/ruby/thread_native.h
24772488
tcpsocket.o: $(hdrdir)/ruby/util.h
2489+
tcpsocket.o: $(hdrdir)/ruby/version.h
24782490
tcpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
24792491
tcpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
24802492
tcpsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -2683,6 +2695,7 @@ udpsocket.o: $(hdrdir)/ruby/subst.h
26832695
udpsocket.o: $(hdrdir)/ruby/thread.h
26842696
udpsocket.o: $(hdrdir)/ruby/thread_native.h
26852697
udpsocket.o: $(hdrdir)/ruby/util.h
2698+
udpsocket.o: $(hdrdir)/ruby/version.h
26862699
udpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
26872700
udpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
26882701
udpsocket.o: $(top_srcdir)/ccan/list/list.h
@@ -2891,6 +2904,7 @@ unixserver.o: $(hdrdir)/ruby/subst.h
28912904
unixserver.o: $(hdrdir)/ruby/thread.h
28922905
unixserver.o: $(hdrdir)/ruby/thread_native.h
28932906
unixserver.o: $(hdrdir)/ruby/util.h
2907+
unixserver.o: $(hdrdir)/ruby/version.h
28942908
unixserver.o: $(top_srcdir)/ccan/check_type/check_type.h
28952909
unixserver.o: $(top_srcdir)/ccan/container_of/container_of.h
28962910
unixserver.o: $(top_srcdir)/ccan/list/list.h
@@ -3099,6 +3113,7 @@ unixsocket.o: $(hdrdir)/ruby/subst.h
30993113
unixsocket.o: $(hdrdir)/ruby/thread.h
31003114
unixsocket.o: $(hdrdir)/ruby/thread_native.h
31013115
unixsocket.o: $(hdrdir)/ruby/util.h
3116+
unixsocket.o: $(hdrdir)/ruby/version.h
31023117
unixsocket.o: $(top_srcdir)/ccan/check_type/check_type.h
31033118
unixsocket.o: $(top_srcdir)/ccan/container_of/container_of.h
31043119
unixsocket.o: $(top_srcdir)/ccan/list/list.h

gc.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,7 @@ typedef struct rb_objspace {
952952
#endif
953953

954954
rb_darray(VALUE *) weak_references;
955+
rb_postponed_job_handle_t finalize_deferred_pjob;
955956
} rb_objspace_t;
956957

957958

@@ -1425,6 +1426,8 @@ PRINTF_ARGS(static void gc_report_body(int level, rb_objspace_t *objspace, const
14251426
static const char *obj_info(VALUE obj);
14261427
static const char *obj_type_name(VALUE obj);
14271428

1429+
static void gc_finalize_deferred(void *dmy);
1430+
14281431
/*
14291432
* 1 - TSC (H/W Time Stamp Counter)
14301433
* 2 - getrusage
@@ -1906,6 +1909,10 @@ rb_objspace_alloc(void)
19061909
rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t));
19071910
objspace->flags.measure_gc = 1;
19081911
malloc_limit = gc_params.malloc_limit_min;
1912+
objspace->finalize_deferred_pjob = rb_postponed_job_preregister(gc_finalize_deferred, objspace);
1913+
if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) {
1914+
rb_bug("Could not preregister postponed job for GC");
1915+
}
19091916

19101917
for (int i = 0; i < SIZE_POOL_COUNT; i++) {
19111918
rb_size_pool_t *size_pool = &size_pools[i];
@@ -4527,9 +4534,8 @@ gc_finalize_deferred(void *dmy)
45274534
static void
45284535
gc_finalize_deferred_register(rb_objspace_t *objspace)
45294536
{
4530-
if (rb_postponed_job_register_one(0, gc_finalize_deferred, objspace) == 0) {
4531-
rb_bug("gc_finalize_deferred_register: can't register finalizer.");
4532-
}
4537+
/* will enqueue a call to gc_finalize_deferred */
4538+
rb_postponed_job_trigger(objspace->finalize_deferred_pjob);
45334539
}
45344540

45354541
static int pop_mark_stack(mark_stack_t *stack, VALUE *data);

0 commit comments

Comments
 (0)