polyphony 0.29 → 0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +15 -10
- data/docs/getting-started/tutorial.md +3 -3
- data/docs/index.md +2 -3
- data/docs/{technical-overview → main-concepts}/concurrency.md +62 -15
- data/docs/{technical-overview → main-concepts}/design-principles.md +21 -8
- data/docs/{technical-overview → main-concepts}/exception-handling.md +80 -38
- data/docs/{technical-overview → main-concepts}/extending.md +4 -3
- data/docs/{technical-overview → main-concepts}/fiber-scheduling.md +3 -3
- data/docs/{technical-overview.md → main-concepts.md} +2 -2
- data/examples/core/xx-at_exit.rb +29 -0
- data/examples/core/xx-fork-terminate.rb +27 -0
- data/examples/core/xx-pingpong.rb +18 -0
- data/examples/core/xx-stop.rb +20 -0
- data/ext/gyro/async.c +1 -1
- data/ext/gyro/extconf.rb +0 -3
- data/ext/gyro/gyro.c +7 -8
- data/ext/gyro/gyro.h +2 -0
- data/ext/gyro/queue.c +6 -6
- data/ext/gyro/selector.c +32 -1
- data/ext/gyro/thread.c +55 -9
- data/ext/gyro/timer.c +1 -0
- data/lib/polyphony/core/exceptions.rb +4 -1
- data/lib/polyphony/core/global_api.rb +1 -6
- data/lib/polyphony/core/thread_pool.rb +3 -3
- data/lib/polyphony/extensions/core.rb +7 -1
- data/lib/polyphony/extensions/fiber.rb +159 -72
- data/lib/polyphony/extensions/io.rb +2 -4
- data/lib/polyphony/extensions/openssl.rb +0 -17
- data/lib/polyphony/extensions/thread.rb +46 -22
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +20 -18
- data/test/coverage.rb +1 -1
- data/test/helper.rb +7 -3
- data/test/test_fiber.rb +285 -72
- data/test/test_global_api.rb +7 -52
- data/test/test_io.rb +8 -0
- data/test/test_signal.rb +1 -0
- data/test/test_thread.rb +76 -56
- data/test/test_thread_pool.rb +27 -5
- data/test/test_throttler.rb +1 -0
- metadata +12 -12
- data/lib/polyphony/core/supervisor.rb +0 -114
- data/lib/polyphony/line_reader.rb +0 -82
- data/test/test_gyro.rb +0 -25
- data/test/test_supervisor.rb +0 -180
data/ext/gyro/gyro.c
CHANGED
@@ -47,11 +47,11 @@ VALUE SYM_fiber_switchpoint;
|
|
47
47
|
VALUE SYM_fiber_terminate;
|
48
48
|
|
49
49
|
|
50
|
-
|
51
|
-
//
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
static VALUE Gyro_break_set(VALUE self) {
|
51
|
+
// break_flag = 1;
|
52
|
+
ev_break(Gyro_Selector_current_thread_ev_loop(), EVBREAK_ALL);
|
53
|
+
return Qnil;
|
54
|
+
}
|
55
55
|
|
56
56
|
// static VALUE Gyro_break_get(VALUE self) {
|
57
57
|
// return (break_flag == 0) ? Qfalse : Qtrue;
|
@@ -82,7 +82,6 @@ static VALUE Gyro_unref(VALUE self) {
|
|
82
82
|
}
|
83
83
|
|
84
84
|
static VALUE Gyro_suspend(VALUE self) {
|
85
|
-
rb_ivar_set(self, ID_runnable_value, Qnil);
|
86
85
|
VALUE ret = Thread_switch_fiber(rb_thread_current());
|
87
86
|
|
88
87
|
if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
|
@@ -117,7 +116,7 @@ static VALUE Fiber_state(VALUE self) {
|
|
117
116
|
return SYM_waiting;
|
118
117
|
}
|
119
118
|
|
120
|
-
|
119
|
+
void Gyro_schedule_fiber(VALUE fiber, VALUE value) {
|
121
120
|
VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
|
122
121
|
if (thread != Qnil) {
|
123
122
|
Thread_schedule_fiber(thread, fiber, value);
|
@@ -140,7 +139,7 @@ void Init_Gyro() {
|
|
140
139
|
rb_define_singleton_method(mGyro, "unref", Gyro_unref, 0);
|
141
140
|
rb_define_singleton_method(mGyro, "trace", Gyro_trace, 1);
|
142
141
|
|
143
|
-
|
142
|
+
rb_define_singleton_method(mGyro, "break!", Gyro_break_set, 0);
|
144
143
|
// rb_define_singleton_method(mGyro, "break?", Gyro_break_get, 0);
|
145
144
|
|
146
145
|
rb_define_global_function("snooze", Gyro_snooze, 0);
|
data/ext/gyro/gyro.h
CHANGED
@@ -48,7 +48,9 @@ VALUE Thread_switch_fiber(VALUE thread);
|
|
48
48
|
VALUE Fiber_await();
|
49
49
|
VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
|
50
50
|
VALUE Thread_post_fork(VALUE thread);
|
51
|
+
VALUE Gyro_Selector_break_out_of_ev_loop(VALUE self);
|
51
52
|
|
53
|
+
VALUE Gyro_Queue_push(VALUE self, VALUE value);
|
52
54
|
|
53
55
|
#define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
|
54
56
|
#define INSPECT(...) (rb_funcall(rb_cObject, rb_intern("p"), __VA_ARGS__))
|
data/ext/gyro/queue.c
CHANGED
@@ -75,7 +75,9 @@ VALUE Gyro_Queue_shift(VALUE self) {
|
|
75
75
|
|
76
76
|
VALUE async = rb_funcall(cGyro_Async, ID_new, 0);
|
77
77
|
rb_ary_push(queue->wait_queue, async);
|
78
|
-
|
78
|
+
VALUE ret = Gyro_Async_await(async);
|
79
|
+
RB_GC_GUARD(async);
|
80
|
+
return ret;
|
79
81
|
}
|
80
82
|
|
81
83
|
VALUE Gyro_Queue_shift_no_wait(VALUE self) {
|
@@ -85,7 +87,7 @@ VALUE Gyro_Queue_shift_no_wait(VALUE self) {
|
|
85
87
|
return rb_ary_shift(queue->queue);
|
86
88
|
}
|
87
89
|
|
88
|
-
VALUE
|
90
|
+
VALUE Gyro_Queue_shift_each(VALUE self) {
|
89
91
|
struct Gyro_Queue *queue;
|
90
92
|
GetGyro_Queue(self, queue);
|
91
93
|
|
@@ -97,9 +99,7 @@ VALUE Gyro_Queue_shift_all(VALUE self) {
|
|
97
99
|
for (long i = 0; i < len; i++) {
|
98
100
|
rb_yield(RARRAY_AREF(old_queue, i));
|
99
101
|
}
|
100
|
-
|
101
|
-
// rb_yield(rb_ary_shift(old_queue));
|
102
|
-
// }
|
102
|
+
RB_GC_GUARD(old_queue);
|
103
103
|
return self;
|
104
104
|
}
|
105
105
|
else {
|
@@ -135,7 +135,7 @@ void Init_Gyro_Queue() {
|
|
135
135
|
|
136
136
|
rb_define_method(cGyro_Queue, "shift_no_wait", Gyro_Queue_shift_no_wait, 0);
|
137
137
|
|
138
|
-
rb_define_method(cGyro_Queue, "shift_each",
|
138
|
+
rb_define_method(cGyro_Queue, "shift_each", Gyro_Queue_shift_each, 0);
|
139
139
|
rb_define_method(cGyro_Queue, "clear", Gyro_Queue_clear, 0);
|
140
140
|
rb_define_method(cGyro_Queue, "empty?", Gyro_Queue_empty_p, 0);
|
141
141
|
}
|
data/ext/gyro/selector.c
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
struct Gyro_Selector {
|
4
4
|
struct ev_loop *ev_loop;
|
5
|
-
long
|
5
|
+
long run_no_wait_count;
|
6
|
+
int ev_loop_running;
|
7
|
+
struct ev_async async;
|
6
8
|
};
|
7
9
|
|
8
10
|
VALUE cGyro_Selector = Qnil;
|
@@ -13,6 +15,7 @@ static void Gyro_Selector_mark(void *ptr) {
|
|
13
15
|
|
14
16
|
static void Gyro_Selector_free(void *ptr) {
|
15
17
|
struct Gyro_Selector *selector = ptr;
|
18
|
+
ev_async_stop(selector->ev_loop, &selector->async);
|
16
19
|
if (selector->ev_loop && !ev_is_default_loop(selector->ev_loop)) {
|
17
20
|
// printf("Selector garbage collected before being stopped!\n");
|
18
21
|
ev_loop_destroy(selector->ev_loop);
|
@@ -67,6 +70,11 @@ long Gyro_Selector_pending_count(VALUE self) {
|
|
67
70
|
return ev_pending_count(selector->ev_loop);
|
68
71
|
}
|
69
72
|
|
73
|
+
void dummy_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
|
74
|
+
// This callback does nothing, the selector's async is used solely for waking
|
75
|
+
// up the event loop.
|
76
|
+
}
|
77
|
+
|
70
78
|
static VALUE Gyro_Selector_initialize(VALUE self, VALUE thread) {
|
71
79
|
struct Gyro_Selector *selector;
|
72
80
|
GetGyro_Selector(self, selector);
|
@@ -75,6 +83,9 @@ static VALUE Gyro_Selector_initialize(VALUE self, VALUE thread) {
|
|
75
83
|
selector->ev_loop = use_default_loop ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
|
76
84
|
selector->run_no_wait_count = 0;
|
77
85
|
|
86
|
+
ev_async_init(&selector->async, dummy_async_callback);
|
87
|
+
ev_async_start(selector->ev_loop, &selector->async);
|
88
|
+
|
78
89
|
ev_run(selector->ev_loop, EVRUN_NOWAIT);
|
79
90
|
|
80
91
|
return Qnil;
|
@@ -86,7 +97,9 @@ inline VALUE Gyro_Selector_run(VALUE self, VALUE current_fiber) {
|
|
86
97
|
if (selector->ev_loop) {
|
87
98
|
selector->run_no_wait_count = 0;
|
88
99
|
FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
|
100
|
+
selector->ev_loop_running = 1;
|
89
101
|
ev_run(selector->ev_loop, EVRUN_ONCE);
|
102
|
+
selector->ev_loop_running = 0;
|
90
103
|
FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
|
91
104
|
}
|
92
105
|
return Qnil;
|
@@ -118,6 +131,23 @@ VALUE Gyro_Selector_stop(VALUE self) {
|
|
118
131
|
return Qnil;
|
119
132
|
}
|
120
133
|
|
134
|
+
VALUE Gyro_Selector_break_out_of_ev_loop(VALUE self) {
|
135
|
+
struct Gyro_Selector *selector;
|
136
|
+
GetGyro_Selector(self, selector);
|
137
|
+
|
138
|
+
if (selector->ev_loop_running) {
|
139
|
+
// Since the loop will run until at least one event has occurred, we signal
|
140
|
+
// the associated async watcher, which will cause the ev loop to return. In
|
141
|
+
// contrast to using `ev_break` to break out of the loop, which should be
|
142
|
+
// called from the same thread (from within the ev_loop), using an
|
143
|
+
// `ev_async` allows us to interrupt the event loop across threads.
|
144
|
+
ev_async_send(selector->ev_loop, &selector->async);
|
145
|
+
return Qtrue;
|
146
|
+
}
|
147
|
+
|
148
|
+
return Qnil;
|
149
|
+
}
|
150
|
+
|
121
151
|
inline static VALUE Gyro_Selector_wait_readable(VALUE self, VALUE io) {
|
122
152
|
VALUE watcher = IO_read_watcher(io);
|
123
153
|
return Gyro_IO_await(watcher);
|
@@ -143,4 +173,5 @@ void Init_Gyro_Selector() {
|
|
143
173
|
rb_define_method(cGyro_Selector, "wait_readable", Gyro_Selector_wait_readable, 1);
|
144
174
|
rb_define_method(cGyro_Selector, "wait_writable", Gyro_Selector_wait_writable, 1);
|
145
175
|
rb_define_method(cGyro_Selector, "wait_timeout", Gyro_Selector_wait_timeout, 1);
|
176
|
+
rb_define_method(cGyro_Selector, "break_out_of_ev_loop", Gyro_Selector_break_out_of_ev_loop, 0);
|
146
177
|
}
|
data/ext/gyro/thread.c
CHANGED
@@ -53,30 +53,33 @@ static VALUE Thread_stop_event_selector(VALUE self) {
|
|
53
53
|
if (selector != Qnil) {
|
54
54
|
rb_funcall(selector, ID_stop, 0);
|
55
55
|
}
|
56
|
+
// Nullify the selector in order to prevent running the
|
57
|
+
// selector after the thread is done running.
|
58
|
+
rb_ivar_set(self, ID_ivar_event_selector, Qnil);
|
56
59
|
|
57
60
|
return self;
|
58
61
|
}
|
59
62
|
|
60
|
-
|
63
|
+
VALUE Thread_ref(VALUE self) {
|
61
64
|
VALUE count = rb_ivar_get(self, ID_fiber_ref_count);
|
62
65
|
int new_count = NUM2INT(count) + 1;
|
63
66
|
rb_ivar_set(self, ID_fiber_ref_count, INT2NUM(new_count));
|
64
67
|
return self;
|
65
68
|
}
|
66
69
|
|
67
|
-
|
70
|
+
VALUE Thread_unref(VALUE self) {
|
68
71
|
VALUE count = rb_ivar_get(self, ID_fiber_ref_count);
|
69
72
|
int new_count = NUM2INT(count) - 1;
|
70
73
|
rb_ivar_set(self, ID_fiber_ref_count, INT2NUM(new_count));
|
71
74
|
return self;
|
72
75
|
}
|
73
76
|
|
74
|
-
|
77
|
+
int Thread_fiber_ref_count(VALUE self) {
|
75
78
|
VALUE count = rb_ivar_get(self, ID_fiber_ref_count);
|
76
79
|
return NUM2INT(count);
|
77
80
|
}
|
78
81
|
|
79
|
-
|
82
|
+
void Thread_fiber_reset_ref_count(VALUE self) {
|
80
83
|
rb_ivar_set(self, ID_fiber_ref_count, INT2NUM(0));
|
81
84
|
}
|
82
85
|
|
@@ -97,7 +100,10 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
|
97
100
|
return stats;
|
98
101
|
}
|
99
102
|
|
100
|
-
|
103
|
+
VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
104
|
+
if (rb_fiber_alive_p(fiber) != Qtrue) {
|
105
|
+
return self;
|
106
|
+
}
|
101
107
|
FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
|
102
108
|
// if fiber is already scheduled, just set the scheduled value, then return
|
103
109
|
rb_ivar_set(fiber, ID_runnable_value, value);
|
@@ -108,13 +114,24 @@ inline VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
108
114
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
109
115
|
rb_ary_push(queue, fiber);
|
110
116
|
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
117
|
+
|
118
|
+
if (rb_thread_current() != self) {
|
119
|
+
// if the fiber scheduling is done across threads, we need to make sure the
|
120
|
+
// target thread is woken up in case it is in the middle of running its
|
121
|
+
// event selector. Otherwise it's gonna be stuck waiting for an event to
|
122
|
+
// happen, not knowing that it there's already a fiber ready to run in its
|
123
|
+
// run queue.
|
124
|
+
VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
|
125
|
+
if (selector != Qnil) {
|
126
|
+
Gyro_Selector_break_out_of_ev_loop(selector);
|
127
|
+
}
|
128
|
+
}
|
111
129
|
return self;
|
112
130
|
}
|
113
131
|
|
114
132
|
VALUE Thread_switch_fiber(VALUE self) {
|
115
|
-
VALUE current_fiber;
|
133
|
+
VALUE current_fiber = rb_fiber_current();
|
116
134
|
if (__tracing_enabled__) {
|
117
|
-
current_fiber = rb_fiber_current();
|
118
135
|
if (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse) {
|
119
136
|
rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_switchpoint, current_fiber);
|
120
137
|
}
|
@@ -170,7 +187,7 @@ VALUE Thread_post_fork(VALUE self) {
|
|
170
187
|
return self;
|
171
188
|
}
|
172
189
|
|
173
|
-
|
190
|
+
VALUE Fiber_await() {
|
174
191
|
VALUE thread = rb_thread_current();
|
175
192
|
Thread_ref(thread);
|
176
193
|
VALUE ret = Thread_switch_fiber(thread);
|
@@ -179,10 +196,37 @@ inline VALUE Fiber_await() {
|
|
179
196
|
return ret;
|
180
197
|
}
|
181
198
|
|
182
|
-
|
199
|
+
VALUE Thread_current_event_selector() {
|
183
200
|
return rb_ivar_get(rb_thread_current(), ID_ivar_event_selector);
|
184
201
|
}
|
185
202
|
|
203
|
+
VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE resume_obj) {
|
204
|
+
VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
|
205
|
+
Thread_schedule_fiber(self, rb_fiber_current(), resume_obj);
|
206
|
+
|
207
|
+
if (Gyro_Selector_break_out_of_ev_loop(selector) == Qnil) {
|
208
|
+
// we're not inside the ev_loop, so we just do a switchpoint
|
209
|
+
Thread_switch_fiber(self);
|
210
|
+
}
|
211
|
+
|
212
|
+
return self;
|
213
|
+
}
|
214
|
+
|
215
|
+
VALUE Thread_join_perform(VALUE self) {
|
216
|
+
if (!RTEST(rb_funcall(self, rb_intern("alive?"), 0))) {
|
217
|
+
return self;
|
218
|
+
}
|
219
|
+
|
220
|
+
VALUE async = rb_funcall(cGyro_Async, ID_new, 0);
|
221
|
+
VALUE wait_queue = rb_ivar_get(self, rb_intern("@join_wait_queue"));
|
222
|
+
|
223
|
+
Gyro_Queue_push(wait_queue, async);
|
224
|
+
|
225
|
+
VALUE ret = Gyro_Async_await(async);
|
226
|
+
RB_GC_GUARD(async);
|
227
|
+
return ret;
|
228
|
+
}
|
229
|
+
|
186
230
|
void Init_Thread() {
|
187
231
|
cQueue = rb_const_get(rb_cObject, rb_intern("Queue"));
|
188
232
|
|
@@ -196,10 +240,12 @@ void Init_Thread() {
|
|
196
240
|
rb_define_method(rb_cThread, "stop_event_selector", Thread_stop_event_selector, 0);
|
197
241
|
rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
|
198
242
|
rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
|
243
|
+
rb_define_method(rb_cThread, "break_out_of_ev_loop", Thread_fiber_break_out_of_ev_loop, 1);
|
199
244
|
|
200
245
|
rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
|
201
246
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
202
247
|
|
248
|
+
rb_define_method(rb_cThread, "join_perform", Thread_join_perform, 0);
|
203
249
|
|
204
250
|
ID_create_event_selector = rb_intern("create_event_selector");
|
205
251
|
ID_empty = rb_intern("empty?");
|
data/ext/gyro/timer.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
export :Interrupt, :MoveOn, :Cancel
|
3
|
+
export :Interrupt, :MoveOn, :Cancel, :Terminate
|
4
4
|
|
5
5
|
# Common exception class for interrupting fibers. These exceptions allow
|
6
6
|
# control of fibers. Interrupt exceptions can encapsulate a value and thus
|
@@ -24,3 +24,6 @@ class MoveOn < Interrupt; end
|
|
24
24
|
# Cancel is used to interrupt a long-running blocking operation, bubbling the
|
25
25
|
# exception up through cancel scopes and supervisors.
|
26
26
|
class Cancel < Interrupt; end
|
27
|
+
|
28
|
+
# Terminate is used to interrupt a fiber once its parent fiber has terminated.
|
29
|
+
class Terminate < Interrupt; end
|
@@ -6,7 +6,6 @@ import '../extensions/core'
|
|
6
6
|
import '../extensions/fiber'
|
7
7
|
|
8
8
|
Exceptions = import '../core/exceptions'
|
9
|
-
Supervisor = import '../core/supervisor'
|
10
9
|
Throttler = import '../core/throttler'
|
11
10
|
|
12
11
|
# Global API methods to be included in ::Object
|
@@ -30,7 +29,7 @@ module API
|
|
30
29
|
end
|
31
30
|
|
32
31
|
def spin(tag = nil, &block)
|
33
|
-
Fiber.spin(tag, caller, &block)
|
32
|
+
Fiber.current.spin(tag, caller, &block)
|
34
33
|
end
|
35
34
|
|
36
35
|
def spin_loop(&block)
|
@@ -78,10 +77,6 @@ module API
|
|
78
77
|
Thread.current.fiber_unref
|
79
78
|
end
|
80
79
|
|
81
|
-
def supervise(&block)
|
82
|
-
Supervisor.new.await(&block)
|
83
|
-
end
|
84
|
-
|
85
80
|
def throttled_loop(rate, count: nil, &block)
|
86
81
|
throttler = Throttler.new(rate)
|
87
82
|
if count
|
@@ -22,9 +22,9 @@ class ThreadPool
|
|
22
22
|
def process(&block)
|
23
23
|
setup unless @task_queue
|
24
24
|
|
25
|
-
|
26
|
-
@task_queue << [block,
|
27
|
-
|
25
|
+
async = Gyro::Async.new
|
26
|
+
@task_queue << [block, async]
|
27
|
+
async.await
|
28
28
|
end
|
29
29
|
|
30
30
|
def cast(&block)
|
@@ -14,6 +14,8 @@ class ::Exception
|
|
14
14
|
|
15
15
|
alias_method :orig_initialize, :initialize
|
16
16
|
|
17
|
+
EXIT_EXCEPTION_CLASSES = [::Interrupt, ::SystemExit].freeze
|
18
|
+
|
17
19
|
def initialize(*args)
|
18
20
|
@__raising_fiber__ = Fiber.current
|
19
21
|
orig_initialize(*args)
|
@@ -21,11 +23,15 @@ class ::Exception
|
|
21
23
|
|
22
24
|
alias_method_once :orig_backtrace, :backtrace
|
23
25
|
def backtrace
|
24
|
-
unless @first_backtrace_call
|
26
|
+
unless @first_backtrace_call || EXIT_EXCEPTION_CLASSES.include?(self.class)
|
25
27
|
@first_backtrace_call = true
|
26
28
|
return orig_backtrace
|
27
29
|
end
|
28
30
|
|
31
|
+
sanitized_backtrace
|
32
|
+
end
|
33
|
+
|
34
|
+
def sanitized_backtrace
|
29
35
|
if @__raising_fiber__
|
30
36
|
backtrace = orig_backtrace || []
|
31
37
|
sanitize(backtrace + @__raising_fiber__.caller)
|