Skip to content

Commit 44cf286

Browse files
committed
[GR-18163] Call unblock functions using JNI in CExtInterrupter
PullRequest: truffleruby/4116
2 parents c68549d + 6bbd0de commit 44cf286

File tree

15 files changed

+148
-66
lines changed

15 files changed

+148
-66
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Bug fixes:
1616
* Handle a new variable inside the `case` target expression correctly (#3377, @eregon).
1717
* The arguments of `Thread.new(*args, &block)` need to be marked as shared between multiple threads (#3179, @eregon).
1818
* Fix `Range#bsearch` and raise `TypeError` when range boundaries are non-numeric and block not passed (@andrykonchin).
19+
* Fix using the `--cpusampler` profiler when there are custom unblock functions for `rb_thread_call_without_gvl()` (#3013, @eregon).
1920

2021
Compatibility:
2122

lib/cext/ABI_check.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7
1+
8

lib/truffle/truffle/cext.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1937,9 +1937,14 @@ def rb_thread_call_without_gvl(function, data1, unblock, data2)
19371937
end
19381938

19391939
private def rb_thread_call_without_gvl_inner(function, data1, unblock, data2)
1940+
if SULONG
1941+
Truffle::Interop.to_native(unblock)
1942+
Truffle::Interop.to_native(data2)
1943+
end
1944+
19401945
Primitive.call_with_unblocking_function(Thread.current,
19411946
POINTER_TO_POINTER_WRAPPER, function, data1,
1942-
POINTER_TO_VOID_WRAPPER, unblock, data2)
1947+
Truffle::Interop.as_pointer(unblock), Truffle::Interop.as_pointer(data2))
19431948
end
19441949

19451950
def rb_iterate(iteration, iterated_object, callback, callback_arg)

spec/ruby/optional/capi/ext/thread_spec.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ static VALUE thread_spec_rb_thread_call_without_gvl(VALUE self) {
6969
}
7070

7171
/* This is unblocked by a signal. */
72-
static void* blocking_gvl_func_for_udf_io(void *data) {
72+
static void* blocking_gvl_func_for_ubf_io(void *data) {
7373
int rfd = (int)(size_t)data;
7474
char dummy;
7575

@@ -89,7 +89,7 @@ static VALUE thread_spec_rb_thread_call_without_gvl_with_ubf_io(VALUE self) {
8989
rb_raise(rb_eRuntimeError, "could not create pipe");
9090
}
9191

92-
ret = rb_thread_call_without_gvl(blocking_gvl_func_for_udf_io,
92+
ret = rb_thread_call_without_gvl(blocking_gvl_func_for_ubf_io,
9393
(void*)(size_t)fds[0], RUBY_UBF_IO, 0);
9494
close(fds[0]);
9595
close(fds[1]);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sulong(would need to enter the context to call the unblock function):C-API Thread function rb_thread_call_without_gvl runs a C function with the global lock unlocked and can be woken by a signal
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
slow:TruffleRuby C-API Thread function rb_thread_call_without_gvl is unblocked with RUBY_UBF_IO when using CPUSampler
2+
slow:TruffleRuby C-API Thread function rb_thread_call_without_gvl is unblocked with a custom unblock function when using CPUSampler
3+
sulong(safepoint inside C code instead):TruffleRuby C-API Thread function rb_thread_call_without_gvl is unblocked with RUBY_UBF_IO when using CPUSampler
4+
sulong(would need to enter the context to call the unblock function):TruffleRuby C-API Thread function rb_thread_call_without_gvl is unblocked with a custom unblock function when using CPUSampler
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
slow:Unimplemented functions in the C-API abort the process and show an error including the function name
2+
sulong(different behavior):Unimplemented functions in the C-API abort the process and show an error including the function name

spec/truffle/capi/ext/truffleruby_thread_spec.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
#include "ruby/thread.h"
1212
#include "rubyspec.h"
1313

14+
#include <errno.h>
15+
#include <time.h>
16+
#include <stdio.h>
17+
1418
#ifdef __cplusplus
1519
extern "C" {
1620
#endif
@@ -21,9 +25,90 @@ static VALUE thread_spec_rb_thread_call_without_gvl_native_function(VALUE self)
2125
return LONG2FIX(ret);
2226
}
2327

28+
static void* call_check_ints(void* arg) {
29+
rb_thread_check_ints();
30+
return NULL;
31+
}
32+
33+
static void* block_sleep(void* arg) {
34+
struct timespec remaining = { .tv_sec = 1, .tv_nsec = 0 };
35+
while (nanosleep(&remaining, &remaining) == -1 && errno == EINTR) {
36+
// Similar to how ossl_pkey.c does it
37+
rb_thread_call_with_gvl(call_check_ints, NULL);
38+
}
39+
return (void*) Qtrue;
40+
}
41+
42+
static VALUE thread_spec_rb_thread_call_without_gvl_unblock_signal(VALUE self) {
43+
return (VALUE) rb_thread_call_without_gvl(block_sleep, NULL, RUBY_UBF_IO, NULL);
44+
}
45+
46+
static void* block(void* arg) {
47+
int fd = *(int*)arg;
48+
char buffer = ' ';
49+
ssize_t r;
50+
51+
while (true) {
52+
ssize_t r = read(fd, &buffer, 1);
53+
if (r == 1) {
54+
if (buffer == 'D') { // done
55+
return (void*) Qtrue;
56+
} else if (buffer == 'U') { // unblock
57+
// Similar to how ossl_pkey.c does it
58+
rb_thread_call_with_gvl(call_check_ints, NULL);
59+
continue;
60+
} else {
61+
return (void*) rb_str_new(&buffer, 1);
62+
}
63+
} else {
64+
perror("read() in blocking function returned != 1");
65+
return (void*) Qfalse;
66+
}
67+
}
68+
}
69+
70+
static void unblock(void* arg) {
71+
int fd = *(int*)arg;
72+
char buffer = 'U';
73+
while (write(fd, &buffer, 1) == -1 && errno == EINTR) {
74+
// retry
75+
}
76+
}
77+
78+
static VALUE finish(void* arg) {
79+
int fd = *(int*)arg;
80+
81+
// Wait 1 second
82+
struct timespec remaining = { .tv_sec = 1, .tv_nsec = 0 };
83+
while (nanosleep(&remaining, &remaining) == -1 && errno == EINTR) {
84+
// Sleep the remaining amount
85+
}
86+
87+
char buffer = 'D';
88+
while (write(fd, &buffer, 1) == -1 && errno == EINTR) {
89+
// retry
90+
}
91+
return Qtrue;
92+
}
93+
94+
static VALUE thread_spec_rb_thread_call_without_gvl_unblock_custom_function(VALUE self) {
95+
int fds[2];
96+
if (pipe(fds) == -1) {
97+
rb_raise(rb_eRuntimeError, "could not create pipe");
98+
}
99+
100+
VALUE thread = rb_funcall(rb_block_proc(), rb_intern("call"), 1, INT2FIX(fds[1]));
101+
102+
rb_thread_call_without_gvl(block, &fds[0], unblock, &fds[1]);
103+
104+
return rb_funcall(thread, rb_intern("join"), 0);
105+
}
106+
24107
void Init_truffleruby_thread_spec(void) {
25108
VALUE cls = rb_define_class("CApiTruffleRubyThreadSpecs", rb_cObject);
26109
rb_define_method(cls, "rb_thread_call_without_gvl_native_function", thread_spec_rb_thread_call_without_gvl_native_function, 0);
110+
rb_define_method(cls, "rb_thread_call_without_gvl_unblock_signal", thread_spec_rb_thread_call_without_gvl_unblock_signal, 0);
111+
rb_define_method(cls, "rb_thread_call_without_gvl_unblock_custom_function", thread_spec_rb_thread_call_without_gvl_unblock_custom_function, 0);
27112
}
28113

29114
#ifdef __cplusplus

spec/truffle/capi/thread_spec.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
require_relative '../../ruby/optional/capi/spec_helper'
1010

11-
load_extension("truffleruby_thread")
11+
extension_path = load_extension("truffleruby_thread")
1212

1313
describe "TruffleRuby C-API Thread function" do
1414
before :each do
@@ -19,5 +19,21 @@
1919
it "runs a native function with the global lock unlocked" do
2020
@t.rb_thread_call_without_gvl_native_function.should == Process.pid
2121
end
22+
23+
it "is unblocked with RUBY_UBF_IO when using CPUSampler" do
24+
code = "require #{extension_path.dump}; CApiTruffleRubyThreadSpecs.new.rb_thread_call_without_gvl_unblock_signal"
25+
out = ruby_exe(code, options: '--cpusampler')
26+
out.should.include?('rb_thread_call_without_gvl_unblock_signal')
27+
out.should.include?('rb_thread_call_without_gvl')
28+
out.should.include?('rb_thread_call_with_gvl') # which checks guest safepoints
29+
end
30+
31+
it "is unblocked with a custom unblock function when using CPUSampler" do
32+
code = "require #{extension_path.dump}; CApiTruffleRubyThreadSpecs.new.rb_thread_call_without_gvl_unblock_custom_function { |fd| Thread.new { sleep 1; IO.for_fd(fd, autoclose: false).write 'D' } }"
33+
out = ruby_exe(code, options: '--cpusampler')
34+
out.should.include?('rb_thread_call_without_gvl_unblock_custom_function')
35+
out.should.include?('rb_thread_call_without_gvl')
36+
out.should.include?('rb_thread_call_with_gvl') # which checks guest safepoints
37+
end
2238
end
2339
end

spec/truffle/capi/unimplemented_spec.rb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111
extension_path = load_extension("unimplemented")
1212

1313
describe "Unimplemented functions in the C-API" do
14-
guard_not -> { Truffle::Boot.get_option('cexts-sulong') } do
15-
it "abort the process and show an error including the function name" do
16-
expected_status = platform_is(:darwin) ? :SIGABRT : 127
17-
out = ruby_exe('require ARGV[0]; CApiRbTrErrorSpecs.new.not_implemented_function("foo")', args: "#{extension_path} 2>&1", exit_status: expected_status)
18-
out.should =~ /undefined symbol: rb_str_shared_replace|Symbol not found: _rb_str_shared_replace/
19-
end
14+
it "abort the process and show an error including the function name" do
15+
expected_status = platform_is(:darwin) ? :SIGABRT : 127
16+
out = ruby_exe('require ARGV[0]; CApiRbTrErrorSpecs.new.not_implemented_function("foo")', args: "#{extension_path} 2>&1", exit_status: expected_status)
17+
out.should =~ /undefined symbol: rb_str_shared_replace|Symbol not found: _rb_str_shared_replace/
2018
end
2119
end

0 commit comments

Comments
 (0)