From dfb3605bbee9c3cfbc1c354594c367472f29cb35 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Wed, 28 Oct 2020 15:27:00 -0700 Subject: Add Thread.ignore_deadlock accessor Setting this to true disables the deadlock detector. It should only be used in cases where the deadlock could be broken via some external means, such as via a signal. Now that $SAFE is no longer used, replace the safe_level_ VM flag with ignore_deadlock for storing the setting. Fixes [Bug #13768] --- NEWS.md | 3 +++ test/ruby/test_thread.rb | 13 ++++++++++++ thread.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++- vm_core.h | 2 +- 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 83b7a5af9a..c7a4036a0a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -178,6 +178,9 @@ Outstanding ones only. blocking. [[Feature #16786]] * `Thread#join` invokes the scheduler hooks `block`/`unblock` in a non-blocking execution context. [[Feature #16786]] + * `Thread.ignore_deadlock` accessor for disabling the default deadlock + detection, allowing the use of signal handlers to break deadlock. + [[Bug #13768]] * Mutex diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 30a3cc784e..0af7a37cf8 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -490,6 +490,19 @@ class TestThread < Test::Unit::TestCase end; end + def test_ignore_deadlock + if /mswin|mingw/ =~ RUBY_PLATFORM + skip "can't trap a signal from another process on Windows" + end + assert_in_out_err([], <<-INPUT, %w(false :sig), [], :signal=>:INT, timeout: 1, timeout_error: nil) + p Thread.ignore_deadlock + q = Queue.new + trap(:INT){q.push :sig} + Thread.ignore_deadlock = true + p q.pop + INPUT + end + def test_status_and_stop_p a = ::Thread.new { Thread.current.report_on_exception = false diff --git a/thread.c b/thread.c index 64d9ac03dd..007a5cbc7b 100644 --- a/thread.c +++ b/thread.c @@ -3065,7 +3065,7 @@ rb_thread_abort_exc_set(VALUE thread, VALUE val) * * There is also an instance level method to set this for a specific thread, * see #report_on_exception=. - * + * */ static VALUE @@ -3113,6 +3113,52 @@ rb_thread_s_report_exc_set(VALUE self, VALUE val) } +/* + * call-seq: + * Thread.ignore_deadlock -> true or false + * + * Returns the status of the global ``ignore deadlock'' condition. + * The default is +false+, so that deadlock conditions are not ignored. + * + * See also ::ignore_deadlock=. + * + */ + +static VALUE +rb_thread_s_ignore_deadlock(VALUE _) +{ + return GET_THREAD()->vm->thread_ignore_deadlock ? Qtrue : Qfalse; +} + + +/* + * call-seq: + * Thread.ignore_deadlock = boolean -> true or false + * + * Returns the new state. + * When set to +true+, the VM will not check for deadlock conditions. + * It is only useful to set this if your application can break a + * deadlock condition via some other means, such as a signal. + * + * Thread.ignore_deadlock = true + * queue = Queue.new + * + * trap(:SIGUSR1){queue.push "Received signal"} + * + * # raises fatal error unless ignoring deadlock + * puts queue.pop + * + * See also ::ignore_deadlock. + */ + +static VALUE +rb_thread_s_ignore_deadlock_set(VALUE self, VALUE val) +{ + GET_THREAD()->vm->thread_ignore_deadlock = RTEST(val); + return val; +} + + /* * call-seq: * thr.report_on_exception -> true or false @@ -5480,6 +5526,8 @@ Init_Thread(void) rb_define_singleton_method(rb_cThread, "abort_on_exception=", rb_thread_s_abort_exc_set, 1); rb_define_singleton_method(rb_cThread, "report_on_exception", rb_thread_s_report_exc, 0); rb_define_singleton_method(rb_cThread, "report_on_exception=", rb_thread_s_report_exc_set, 1); + rb_define_singleton_method(rb_cThread, "ignore_deadlock", rb_thread_s_ignore_deadlock, 0); + rb_define_singleton_method(rb_cThread, "ignore_deadlock=", rb_thread_s_ignore_deadlock_set, 1); #if THREAD_DEBUG < 0 rb_define_singleton_method(rb_cThread, "DEBUG", rb_thread_s_debug, 0); rb_define_singleton_method(rb_cThread, "DEBUG=", rb_thread_s_debug_set, 1); @@ -5611,6 +5659,8 @@ debug_deadlock_check(rb_ractor_t *r, VALUE msg) static void rb_check_deadlock(rb_ractor_t *r) { + if (GET_THREAD()->vm->thread_ignore_deadlock) return; + int found = 0; rb_thread_t *th = NULL; int sleeper_num = rb_ractor_sleeper_thread_num(r); diff --git a/vm_core.h b/vm_core.h index 8525bfcf3e..02e777fb06 100644 --- a/vm_core.h +++ b/vm_core.h @@ -595,7 +595,7 @@ typedef struct rb_vm_struct { unsigned int running: 1; unsigned int thread_abort_on_exception: 1; unsigned int thread_report_on_exception: 1; - unsigned int safe_level_: 1; + unsigned int thread_ignore_deadlock: 1; /* object management */ VALUE mark_object_ary; -- cgit v1.2.3