summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--NEWS5
-rw-r--r--eval_error.c7
-rw-r--r--test/ruby/test_thread.rb56
-rw-r--r--thread.c123
-rw-r--r--vm_core.h3
6 files changed, 198 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index f926bebaac4..adcfcf87c09 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+Mon Jun 6 09:25:34 2016 Nobuyoshi Nakada <nobu@ruby-lang.org>
+
+ * thread.c (thread_start_func_2): report raised exception if
+ report_on_exception flag is set. [Feature #6647]
+
Mon Jun 6 01:36:24 2016 Kazuki Yamaguchi <k@rhe.jp>
* ext/openssl/extconf.rb: Check existence of SSL_is_server(). This
diff --git a/NEWS b/NEWS
index c95933c4a11..a8d868bdb0b 100644
--- a/NEWS
+++ b/NEWS
@@ -81,6 +81,11 @@ with all sufficient information, see the ChangeLog file or Redmine
* MatchData#named_captures [Feature #11999]
* MatchData#values_at supports named captures [Feature #9179]
+* Thread
+
+ * Thread#report_on_exception and Thread.report_on_exception
+ [Feature #6647]
+
=== Stdlib updates (outstanding ones only)
* CSV
diff --git a/eval_error.c b/eval_error.c
index b1c17b63e27..cfb82d3a248 100644
--- a/eval_error.c
+++ b/eval_error.c
@@ -86,8 +86,13 @@ set_backtrace(VALUE info, VALUE bt)
static void
error_print(rb_thread_t *th)
{
+ rb_threadptr_error_print(th, th->errinfo);
+}
+
+void
+rb_threadptr_error_print(rb_thread_t *th, VALUE errinfo)
+{
volatile VALUE errat = Qundef;
- VALUE errinfo = th->errinfo;
int raised_flag = th->raised_flag;
volatile VALUE eclass = Qundef, e = Qundef;
const char *volatile einfo;
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index f13a962c9be..c977d17ecc0 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -344,6 +344,62 @@ class TestThread < Test::Unit::TestCase
INPUT
end
+ def test_report_on_exception
+ assert_separately([], <<~"end;") #do
+ q1 = Queue.new
+ q2 = Queue.new
+
+ assert_equal(false, Thread.report_on_exception,
+ "global flags is false by default")
+ assert_equal(false, Thread.current.report_on_exception)
+
+ Thread.current.report_on_exception = true
+ assert_equal(false,
+ Thread.start {Thread.current.report_on_exception}.value,
+ "should not inherit from the parent thread")
+
+ assert_warn("", "exception should be ignored silently") {
+ th = Thread.start {
+ q1.push(Thread.current.report_on_exception)
+ raise "report 1"
+ }
+ assert_equal(false, q1.pop)
+ Thread.pass while th.alive?
+ }
+
+ assert_warn(/report 2/, "exception should be reported") {
+ th = Thread.start {
+ q1.push(Thread.current.report_on_exception = true)
+ raise "report 2"
+ }
+ assert_equal(true, q1.pop)
+ Thread.pass while th.alive?
+ }
+
+ assert_equal(false, Thread.report_on_exception)
+ assert_warn("", "the global flag should not affect already started threads") {
+ th = Thread.start {
+ q2.pop
+ q1.push(Thread.current.report_on_exception)
+ raise "report 3"
+ }
+ q2.push(Thread.report_on_exception = true)
+ assert_equal(false, q1.pop)
+ Thread.pass while th.alive?
+ }
+
+ assert_equal(true, Thread.report_on_exception)
+ assert_warn(/report 4/, "should defaults to the global flag at the start") {
+ th = Thread.start {
+ q1.push(Thread.current.report_on_exception)
+ raise "report 4"
+ }
+ assert_equal(true, q1.pop)
+ Thread.pass while th.alive?
+ }
+ end;
+ end
+
def test_status_and_stop_p
a = ::Thread.new { raise("die now") }
b = Thread.new { Thread.stop }
diff --git a/thread.c b/thread.c
index d17b663baee..7a157639ac7 100644
--- a/thread.c
+++ b/thread.c
@@ -541,6 +541,7 @@ thread_cleanup_func(void *th_ptr, int atfork)
}
static VALUE rb_threadptr_raise(rb_thread_t *, int, VALUE *);
+static VALUE rb_thread_inspect(VALUE thread);
void
ruby_thread_init_stack(rb_thread_t *th)
@@ -609,6 +610,13 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start, VALUE *register_stack_s
th->abort_on_exception || RTEST(ruby_debug)) {
/* exit on main_thread */
}
+ else if (th->report_on_exception) {
+ VALUE mesg = rb_thread_inspect(th->self);
+ rb_str_cat_cstr(mesg, " terminated with exception:\n");
+ rb_write_error_str(mesg);
+ rb_threadptr_error_print(th, errinfo);
+ errinfo = Qnil;
+ }
else {
errinfo = Qnil;
}
@@ -700,6 +708,7 @@ thread_create_core(VALUE thval, VALUE args, VALUE (*fn)(ANYARGS))
native_mutex_initialize(&th->interrupt_lock);
native_cond_initialize(&th->interrupt_cond, RB_CONDATTR_CLOCK_MONOTONIC);
+ th->report_on_exception = th->vm->thread_report_on_exception;
/* kick thread */
err = native_thread_create(th);
@@ -2595,6 +2604,116 @@ rb_thread_abort_exc_set(VALUE thread, VALUE val)
/*
* call-seq:
+ * Thread.report_on_exception -> true or false
+ *
+ * Returns the status of the global ``report on exception'' condition.
+ *
+ * The default is +false+.
+ *
+ * When set to +true+, all threads will report the exception if an
+ * exception is raised in any thread.
+ *
+ * See also ::report_on_exception=.
+ *
+ * There is also an instance level method to set this for a specific thread,
+ * see #report_on_exception.
+ */
+
+static VALUE
+rb_thread_s_report_exc(void)
+{
+ return GET_THREAD()->vm->thread_report_on_exception ? Qtrue : Qfalse;
+}
+
+
+/*
+ * call-seq:
+ * Thread.report_on_exception= boolean -> true or false
+ *
+ * When set to +true+, all threads will report the exception if an
+ * exception is raised. Returns the new state.
+ *
+ * Thread.report_on_exception = true
+ * t1 = Thread.new do
+ * puts "In new thread"
+ * raise "Exception from thread"
+ * end
+ * sleep(1)
+ * puts "In the main thread"
+ *
+ * This will produce:
+ *
+ * In new thread
+ * prog.rb:4: Exception from thread (RuntimeError)
+ * from prog.rb:2:in `initialize'
+ * from prog.rb:2:in `new'
+ * from prog.rb:2
+ * In the main thread
+ *
+ * See also ::report_on_exception.
+ *
+ * There is also an instance level method to set this for a specific thread,
+ * see #report_on_exception=.
+ */
+
+static VALUE
+rb_thread_s_report_exc_set(VALUE self, VALUE val)
+{
+ GET_THREAD()->vm->thread_report_on_exception = RTEST(val);
+ return val;
+}
+
+
+/*
+ * call-seq:
+ * thr.report_on_exception -> true or false
+ *
+ * Returns the status of the thread-local ``report on exception'' condition for
+ * this +thr+.
+ *
+ * The default is +false+.
+ *
+ * See also #report_on_exception=.
+ *
+ * There is also a class level method to set this for all threads, see
+ * ::report_on_exception.
+ */
+
+static VALUE
+rb_thread_report_exc(VALUE thread)
+{
+ rb_thread_t *th;
+ GetThreadPtr(thread, th);
+ return th->report_on_exception ? Qtrue : Qfalse;
+}
+
+
+/*
+ * call-seq:
+ * thr.report_on_exception= boolean -> true or false
+ *
+ * When set to +true+, all threads (including the main program) will
+ * report the exception if an exception is raised in this +thr+.
+ *
+ * See also #report_on_exception.
+ *
+ * There is also a class level method to set this for all threads, see
+ * ::report_on_exception=.
+ */
+
+static VALUE
+rb_thread_report_exc_set(VALUE thread, VALUE val)
+{
+ rb_thread_t *th;
+
+ GetThreadPtr(thread, th);
+ th->report_on_exception = RTEST(val);
+ return val;
+}
+
+
+/*
+ * call-seq:
* thr.group -> thgrp or nil
*
* Returns the ThreadGroup which contains the given thread, or returns +nil+
@@ -4633,6 +4752,8 @@ Init_Thread(void)
rb_define_singleton_method(rb_cThread, "list", rb_thread_list, 0);
rb_define_singleton_method(rb_cThread, "abort_on_exception", rb_thread_s_abort_exc, 0);
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);
#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);
@@ -4665,6 +4786,8 @@ Init_Thread(void)
rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0);
rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0);
rb_define_method(rb_cThread, "abort_on_exception=", rb_thread_abort_exc_set, 1);
+ rb_define_method(rb_cThread, "report_on_exception", rb_thread_report_exc, 0);
+ rb_define_method(rb_cThread, "report_on_exception=", rb_thread_report_exc_set, 1);
rb_define_method(rb_cThread, "safe_level", rb_thread_safe_level, 0);
rb_define_method(rb_cThread, "group", rb_thread_group, 0);
rb_define_method(rb_cThread, "backtrace", rb_thread_backtrace_m, -1);
diff --git a/vm_core.h b/vm_core.h
index 9c304c806bc..94b5bce87df 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -495,6 +495,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 trace_running: 1;
volatile int sleeper;
@@ -786,6 +787,7 @@ typedef struct rb_thread_struct {
/* misc */
enum method_missing_reason method_missing_reason: 8;
unsigned int abort_on_exception: 1;
+ unsigned int report_on_exception: 1;
#ifdef USE_SIGALTSTACK
void *altstack;
#endif
@@ -1149,6 +1151,7 @@ void rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th);
void rb_threadptr_pending_interrupt_clear(rb_thread_t *th);
void rb_threadptr_pending_interrupt_enque(rb_thread_t *th, VALUE v);
int rb_threadptr_pending_interrupt_active_p(rb_thread_t *th);
+void rb_threadptr_error_print(rb_thread_t *th, VALUE errinfo);
#define RUBY_VM_CHECK_INTS(th) ruby_vm_check_ints(th)
static inline void