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;
+}