Index: test/callerforallthreads/test_caller_for_each_thread.rb =================================================================== --- test/callerforallthreads/test_caller_for_each_thread.rb (revision 0) +++ test/callerforallthreads/test_caller_for_each_thread.rb (revision 0) @@ -0,0 +1,95 @@ +# -*- ruby-indent-level: 4 -*- +require 'thread' +require 'test/unit' + +class AClassWithNestedmethods + + def an_ultra_nested_method(skip) + caller_for_all_threads skip + end + + def a_nested_method(skip) + an_ultra_nested_method skip + end + + def a_method(skip=0) + a_nested_method skip + end + +end + +class CallerForEachThreadTest < Test::Unit::TestCase + + def testCollectMeaningfulBacktraceForASingleThread + backtraces = AClassWithNestedmethods.new.a_method + backtrace = backtraces[Thread.current] + assert_not_nil backtrace + assert_equal __FILE__ + ":8:in `an_ultra_nested_method'", backtrace[0] + assert_equal __FILE__ + ":12:in `a_nested_method'", backtrace[1] + assert_equal __FILE__ + ":16:in `a_method'", backtrace[2] + assert_equal __FILE__ + ":24:in `testCollectMeaningfulBacktraceForASingleThread'", + backtrace[3] + end + + def testCanSkipFirstStackEntries + backtraces = AClassWithNestedmethods.new.a_method 2 + backtrace = backtraces[Thread.current] + assert_not_nil backtrace + assert_equal __FILE__ + ":16:in `a_method'", backtrace[0] + assert_equal __FILE__ + ":35:in `testCanSkipFirstStackEntries'", + backtrace[1] + end + + def testCollectMeaningfulBacktraceForMultipleThreads + first_thread = Thread.new do + loop do + Thread.pass + sleep 1 + end + end + + second_thread = Thread.new do + loop do + Thread.pass + sleep 1 + end + end + + backtraces = AClassWithNestedmethods.new.a_method + + backtrace = backtraces[Thread.current] + assert_not_nil backtrace + assert_match __FILE__ + ":8:in `an_ultra_nested_method'", backtrace[0] + assert_match __FILE__ + ":12:in `a_nested_method'", backtrace[1] + assert_equal __FILE__ + ":16:in `a_method'", backtrace[2] + assert_equal __FILE__ + ":58:in `testCollectMeaningfulBacktraceForMultipleThreads'", + backtrace[3] + + backtrace = backtraces[first_thread] + assert_not_nil backtrace + assert_equal __FILE__ + ":47:in `testCollectMeaningfulBacktraceForMultipleThreads'", + backtrace[0] + assert_equal __FILE__ + ":45:in `loop'", + backtrace[1] + assert_equal __FILE__ + ":45:in `testCollectMeaningfulBacktraceForMultipleThreads'", + backtrace[2] + assert_equal __FILE__ + ":44:in `initialize'",backtrace[3] + assert_equal __FILE__ + ":44:in `new'", backtrace[4] + assert_equal __FILE__ + ":44:in `testCollectMeaningfulBacktraceForMultipleThreads'", + backtrace[5] + + backtrace = backtraces[second_thread] + assert_not_nil backtrace + assert_equal __FILE__ + ":53:in `testCollectMeaningfulBacktraceForMultipleThreads'", + backtrace[0] + assert_equal __FILE__ + ":52:in `loop'", backtrace[1] + assert_equal __FILE__ + ":52:in `testCollectMeaningfulBacktraceForMultipleThreads'", + backtrace[2] + assert_equal __FILE__ + ":51:in `initialize'",backtrace[3] + assert_equal __FILE__ + ":51:in `new'", backtrace[4] + assert_equal __FILE__ + ":51:in `testCollectMeaningfulBacktraceForMultipleThreads'", + backtrace[5] + end + +end + Index: eval.c =================================================================== --- eval.c (revision 16289) +++ eval.c (working copy) @@ -7969,6 +7969,17 @@ ruby_safe_level = safe; } +/* Hash (Thread => Backtrace) used to collect backtrace for each threads. */ +static VALUE backtrace_for_each_thread; + +static int backtrace_level_for_each_thread; + +static VALUE +switch_thread_context_to_collect_backtrace(rb_thread_t next); + +static VALUE +rb_f_caller_for_all_threads(); + void Init_eval() { @@ -8014,6 +8025,7 @@ rb_define_global_function("fail", rb_f_raise, -1); rb_define_global_function("caller", rb_f_caller, -1); + rb_define_global_function("caller_for_all_threads", rb_f_caller_for_all_threads, -1); rb_define_global_function("exit", rb_f_exit, -1); rb_define_global_function("abort", rb_f_abort, -1); @@ -10206,6 +10218,7 @@ #define RESTORE_RAISE 5 #define RESTORE_SIGNAL 6 #define RESTORE_EXIT 7 +#define RESTORE_BACKTRACE 8 extern VALUE *rb_gc_stack_start; #ifdef __ia64 @@ -10313,6 +10326,15 @@ } rb_exc_raise(th_raise_exception); break; + case RESTORE_BACKTRACE: + rb_hash_aset(backtrace_for_each_thread, curr_thread->thread, + backtrace(backtrace_level_for_each_thread)); + if (curr_thread != main_thread) { + switch_thread_context_to_collect_backtrace(curr_thread->next); + } else { + /* Circled back to main thread, cycle is complete. */ + } + break; case RESTORE_NORMAL: default: break; @@ -13240,3 +13262,74 @@ argv[1] = val; rb_f_throw(2, argv); } + +static VALUE +switch_thread_context_to_collect_backtrace(rb_thread_t next) +{ + if (THREAD_SAVE_CONTEXT(curr_thread)) { + return Qnil; + } + curr_thread = next; + rb_thread_restore_context(next, RESTORE_BACKTRACE); + return Qnil; +} + + +/* + * call-seq: + * caller_for_all_threads(start=1) => array + * + * Returns the current execution stack for all threads + * ---a hash whose keys are thread instances and values + * the thread caller backtrace. + * + * Backtraces are array of hashes indicating location on the + * stack. Hash keys include ``:line'' or ``:file'' + * and ``:method'''. + * + * The optional _start_ parameter + * determines the number of initial stack entries to omit from the + * result. + * + * def a(skip) + * caller_for_all_threads(skip) + * end + * def b(skip) + * a(skip) + * end + * def c(skip) + * b(skip) + * end + * c(0) #=> ["prog:2:in `a'", "prog:5:in `b'", "prog:8:in `c'", "prog:10"] + * c(1) #=> ["prog:5:in `b'", "prog:8:in `c'", "prog:11"] + * c(2) #=> ["prog:8:in `c'", "prog:12"] + * c(3) #=> ["prog:13"] + */ +static VALUE +rb_f_caller_for_all_threads(argc, argv) + int argc; + VALUE *argv; +{ + volatile int critical; + VALUE level; + VALUE result; + + rb_scan_args(argc, argv, "01", &level); + backtrace_level_for_each_thread = NIL_P(level) ? 0 : NUM2INT(level); + if (backtrace_level_for_each_thread < 0) { + rb_raise(rb_eArgError, "negative level (%d)", backtrace_level_for_each_thread); + } + + critical = rb_thread_critical; + rb_thread_critical = Qtrue; + + backtrace_for_each_thread = rb_hash_new(); + switch_thread_context_to_collect_backtrace(main_thread->next); + + result = backtrace_for_each_thread; + backtrace_for_each_thread = Qnil; + backtrace_for_each_thread = 0; + + rb_thread_critical = critical; + return result; +}