polyphony 0.28 → 0.29
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/.rubocop.yml +0 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +23 -21
- data/Rakefile +2 -0
- data/TODO.md +0 -3
- data/docs/_includes/prevnext.html +17 -0
- data/docs/_layouts/default.html +106 -0
- data/docs/_sass/custom/custom.scss +21 -0
- data/docs/faq.md +13 -10
- data/docs/getting-started/installing.md +2 -0
- data/docs/getting-started/tutorial.md +5 -3
- data/docs/index.md +4 -5
- data/docs/technical-overview/concurrency.md +21 -19
- data/docs/technical-overview/design-principles.md +12 -20
- data/docs/technical-overview/exception-handling.md +70 -1
- data/docs/technical-overview/extending.md +1 -0
- data/docs/technical-overview/fiber-scheduling.md +109 -88
- data/docs/user-guide/all-about-timers.md +126 -0
- data/docs/user-guide/web-server.md +2 -2
- data/docs/user-guide.md +1 -1
- data/examples/core/xx-deferring-an-operation.rb +2 -2
- data/examples/core/xx-sleep-forever.rb +9 -0
- data/examples/core/xx-snooze-starve.rb +16 -0
- data/examples/core/xx-spin_error_backtrace.rb +1 -1
- data/examples/core/xx-trace.rb +1 -2
- data/examples/core/xx-worker-thread.rb +30 -0
- data/examples/io/xx-happy-eyeballs.rb +37 -0
- data/ext/gyro/gyro.c +8 -3
- data/ext/gyro/gyro.h +7 -1
- data/ext/gyro/queue.c +35 -3
- data/ext/gyro/selector.c +31 -2
- data/ext/gyro/thread.c +18 -16
- data/lib/polyphony/core/global_api.rb +0 -1
- data/lib/polyphony/core/thread_pool.rb +5 -0
- data/lib/polyphony/core/throttler.rb +0 -1
- data/lib/polyphony/extensions/fiber.rb +14 -3
- data/lib/polyphony/extensions/thread.rb +16 -4
- data/lib/polyphony/irb.rb +7 -1
- data/lib/polyphony/trace.rb +44 -11
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +1 -0
- data/test/helper.rb +1 -3
- data/test/test_async.rb +1 -1
- data/test/test_cancel_scope.rb +3 -3
- data/test/test_fiber.rb +157 -54
- data/test/test_global_api.rb +51 -1
- data/test/test_gyro.rb +4 -156
- data/test/test_io.rb +1 -1
- data/test/test_supervisor.rb +2 -2
- data/test/test_thread.rb +72 -1
- data/test/test_thread_pool.rb +6 -2
- data/test/test_throttler.rb +7 -5
- data/test/test_trace.rb +6 -6
- metadata +10 -5
- data/examples/core/xx-extended_fibers.rb +0 -150
- data/examples/core/xx-mt-scheduler.rb +0 -349
data/ext/gyro/selector.c
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
struct Gyro_Selector {
|
4
4
|
struct ev_loop *ev_loop;
|
5
|
+
long run_no_wait_count;
|
5
6
|
};
|
6
7
|
|
7
8
|
VALUE cGyro_Selector = Qnil;
|
@@ -52,6 +53,13 @@ inline struct ev_loop *Gyro_Selector_current_thread_ev_loop() {
|
|
52
53
|
return selector->ev_loop;
|
53
54
|
}
|
54
55
|
|
56
|
+
inline ev_tstamp Gyro_Selector_now(VALUE self) {
|
57
|
+
struct Gyro_Selector *selector;
|
58
|
+
GetGyro_Selector(self, selector);
|
59
|
+
|
60
|
+
return ev_now(selector->ev_loop);
|
61
|
+
}
|
62
|
+
|
55
63
|
long Gyro_Selector_pending_count(VALUE self) {
|
56
64
|
struct Gyro_Selector *selector;
|
57
65
|
GetGyro_Selector(self, selector);
|
@@ -65,19 +73,40 @@ static VALUE Gyro_Selector_initialize(VALUE self, VALUE thread) {
|
|
65
73
|
|
66
74
|
int use_default_loop = (rb_thread_current() == rb_thread_main());
|
67
75
|
selector->ev_loop = use_default_loop ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
|
76
|
+
selector->run_no_wait_count = 0;
|
77
|
+
|
78
|
+
ev_run(selector->ev_loop, EVRUN_NOWAIT);
|
68
79
|
|
69
80
|
return Qnil;
|
70
81
|
}
|
71
82
|
|
72
|
-
inline VALUE Gyro_Selector_run(VALUE self) {
|
83
|
+
inline VALUE Gyro_Selector_run(VALUE self, VALUE current_fiber) {
|
73
84
|
struct Gyro_Selector *selector;
|
74
85
|
GetGyro_Selector(self, selector);
|
75
86
|
if (selector->ev_loop) {
|
87
|
+
selector->run_no_wait_count = 0;
|
88
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
|
76
89
|
ev_run(selector->ev_loop, EVRUN_ONCE);
|
90
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
|
77
91
|
}
|
78
92
|
return Qnil;
|
79
93
|
}
|
80
94
|
|
95
|
+
inline void Gyro_Selector_run_no_wait(VALUE self, VALUE current_fiber, long runnable_count) {
|
96
|
+
struct Gyro_Selector *selector;
|
97
|
+
GetGyro_Selector(self, selector);
|
98
|
+
|
99
|
+
selector->run_no_wait_count++;
|
100
|
+
if (selector->run_no_wait_count < runnable_count || selector->run_no_wait_count < 10) {
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
|
104
|
+
selector->run_no_wait_count = 0;
|
105
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
|
106
|
+
ev_run(selector->ev_loop, EVRUN_NOWAIT);
|
107
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
|
108
|
+
}
|
109
|
+
|
81
110
|
VALUE Gyro_Selector_stop(VALUE self) {
|
82
111
|
struct Gyro_Selector *selector;
|
83
112
|
GetGyro_Selector(self, selector);
|
@@ -109,7 +138,7 @@ void Init_Gyro_Selector() {
|
|
109
138
|
rb_define_alloc_func(cGyro_Selector, Gyro_Selector_allocate);
|
110
139
|
|
111
140
|
rb_define_method(cGyro_Selector, "initialize", Gyro_Selector_initialize, 1);
|
112
|
-
rb_define_method(cGyro_Selector, "run", Gyro_Selector_run,
|
141
|
+
rb_define_method(cGyro_Selector, "run", Gyro_Selector_run, 1);
|
113
142
|
rb_define_method(cGyro_Selector, "stop", Gyro_Selector_stop, 0);
|
114
143
|
rb_define_method(cGyro_Selector, "wait_readable", Gyro_Selector_wait_readable, 1);
|
115
144
|
rb_define_method(cGyro_Selector, "wait_writable", Gyro_Selector_wait_writable, 1);
|
data/ext/gyro/thread.c
CHANGED
@@ -98,13 +98,16 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
|
98
98
|
}
|
99
99
|
|
100
100
|
inline VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
101
|
+
FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
|
101
102
|
// if fiber is already scheduled, just set the scheduled value, then return
|
102
103
|
rb_ivar_set(fiber, ID_runnable_value, value);
|
103
|
-
if (rb_ivar_get(fiber, ID_runnable)
|
104
|
-
|
105
|
-
rb_ary_push(queue, fiber);
|
106
|
-
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
104
|
+
if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
|
105
|
+
return self;
|
107
106
|
}
|
107
|
+
|
108
|
+
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
109
|
+
rb_ary_push(queue, fiber);
|
110
|
+
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
108
111
|
return self;
|
109
112
|
}
|
110
113
|
|
@@ -118,6 +121,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
118
121
|
}
|
119
122
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
120
123
|
VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
|
124
|
+
|
121
125
|
VALUE next_fiber;
|
122
126
|
|
123
127
|
while (1) {
|
@@ -125,17 +129,18 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
125
129
|
// if (break_flag != 0) {
|
126
130
|
// return Qnil;
|
127
131
|
// }
|
128
|
-
|
132
|
+
int ref_count = Thread_fiber_ref_count(self);
|
133
|
+
if (next_fiber != Qnil) {
|
134
|
+
if (ref_count > 0) {
|
135
|
+
Gyro_Selector_run_no_wait(selector, current_fiber, RARRAY_LEN(queue));
|
136
|
+
}
|
129
137
|
break;
|
130
138
|
}
|
131
|
-
|
132
|
-
|
133
|
-
rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_ev_loop_enter, current_fiber);
|
134
|
-
}
|
135
|
-
Gyro_Selector_run(selector);
|
136
|
-
if (__tracing_enabled__) {
|
137
|
-
rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_ev_loop_leave, current_fiber);
|
139
|
+
if (ref_count == 0) {
|
140
|
+
break;
|
138
141
|
}
|
142
|
+
|
143
|
+
Gyro_Selector_run(selector, current_fiber);
|
139
144
|
}
|
140
145
|
|
141
146
|
if (next_fiber == Qnil) {
|
@@ -144,10 +149,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
144
149
|
|
145
150
|
// run next fiber
|
146
151
|
VALUE value = rb_ivar_get(next_fiber, ID_runnable_value);
|
147
|
-
|
148
|
-
if (__tracing_enabled__) {
|
149
|
-
rb_funcall(rb_cObject, ID_fiber_trace, 3, SYM_fiber_run, next_fiber, value);
|
150
|
-
}
|
152
|
+
FIBER_TRACE(3, SYM_fiber_run, next_fiber, value);
|
151
153
|
|
152
154
|
rb_ivar_set(next_fiber, ID_runnable, Qnil);
|
153
155
|
RB_GC_GUARD(next_fiber);
|
@@ -55,11 +55,12 @@ module FiberMessaging
|
|
55
55
|
if @receive_waiting && @running
|
56
56
|
schedule value
|
57
57
|
else
|
58
|
-
@queued_messages ||=
|
58
|
+
@queued_messages ||= Gyro::Queue.new
|
59
59
|
@queued_messages << value
|
60
60
|
end
|
61
61
|
snooze
|
62
62
|
end
|
63
|
+
alias_method :send, :<<
|
63
64
|
|
64
65
|
def receive
|
65
66
|
if !@queued_messages || @queued_messages&.empty?
|
@@ -110,11 +111,11 @@ class ::Fiber
|
|
110
111
|
f
|
111
112
|
end
|
112
113
|
|
113
|
-
attr_accessor :tag
|
114
|
-
Fiber.current.tag = :main
|
114
|
+
attr_accessor :tag, :thread
|
115
115
|
|
116
116
|
def setup(tag, block, caller)
|
117
117
|
__fiber_trace__(:fiber_create, self)
|
118
|
+
@thread = Thread.current
|
118
119
|
@tag = tag
|
119
120
|
@calling_fiber = Fiber.current
|
120
121
|
@caller = caller
|
@@ -122,12 +123,20 @@ class ::Fiber
|
|
122
123
|
schedule
|
123
124
|
end
|
124
125
|
|
126
|
+
def setup_main_fiber
|
127
|
+
@tag = :main
|
128
|
+
@thread = Thread.current
|
129
|
+
@running = true
|
130
|
+
end
|
131
|
+
|
125
132
|
def run(first_value)
|
126
133
|
Kernel.raise first_value if first_value.is_a?(Exception)
|
127
134
|
|
128
135
|
start_execution(first_value)
|
129
136
|
rescue ::Interrupt, ::SystemExit => e
|
130
137
|
Thread.current.main_fiber.transfer e.class.new
|
138
|
+
rescue ::SignalException => e
|
139
|
+
Thread.current.main_fiber.transfer e
|
131
140
|
rescue Exceptions::MoveOn => e
|
132
141
|
finish_execution(e.value)
|
133
142
|
rescue Exception => e
|
@@ -187,3 +196,5 @@ class ::Fiber
|
|
187
196
|
end
|
188
197
|
end
|
189
198
|
end
|
199
|
+
|
200
|
+
Fiber.current.setup_main_fiber
|
@@ -4,6 +4,10 @@ Exceptions = import '../core/exceptions'
|
|
4
4
|
|
5
5
|
# Thread extensions
|
6
6
|
class ::Thread
|
7
|
+
def self.join_queue_mutex
|
8
|
+
@join_queue_mutex ||= Mutex.new
|
9
|
+
end
|
10
|
+
|
7
11
|
attr_reader :main_fiber
|
8
12
|
|
9
13
|
alias_method :orig_initialize, :initialize
|
@@ -11,10 +15,11 @@ class ::Thread
|
|
11
15
|
@join_wait_queue = Gyro::Queue.new
|
12
16
|
@block = block
|
13
17
|
orig_initialize do
|
18
|
+
Fiber.current.setup_main_fiber
|
14
19
|
setup_fiber_scheduling
|
15
20
|
block.(*args)
|
16
|
-
signal_waiters
|
17
21
|
ensure
|
22
|
+
signal_waiters
|
18
23
|
stop_event_selector
|
19
24
|
end
|
20
25
|
end
|
@@ -25,10 +30,12 @@ class ::Thread
|
|
25
30
|
|
26
31
|
alias_method :orig_join, :join
|
27
32
|
def join(timeout = nil)
|
28
|
-
return unless alive?
|
29
|
-
|
30
33
|
async = Gyro::Async.new
|
31
|
-
|
34
|
+
Thread.join_queue_mutex.synchronize do
|
35
|
+
return unless alive?
|
36
|
+
|
37
|
+
@join_wait_queue << async
|
38
|
+
end
|
32
39
|
|
33
40
|
if timeout
|
34
41
|
move_on_after(timeout) { async.await }
|
@@ -49,4 +56,9 @@ class ::Thread
|
|
49
56
|
def location
|
50
57
|
@block.source_location.join(':')
|
51
58
|
end
|
59
|
+
|
60
|
+
def <<(value)
|
61
|
+
main_fiber << value
|
62
|
+
end
|
63
|
+
alias_method :send, :<<
|
52
64
|
end
|
data/lib/polyphony/irb.rb
CHANGED
@@ -7,7 +7,13 @@ require 'polyphony'
|
|
7
7
|
# readline to return
|
8
8
|
module ::Readline
|
9
9
|
alias_method :orig_readline, :readline
|
10
|
+
|
10
11
|
def readline(*args)
|
11
|
-
|
12
|
+
async = Gyro::Async.new
|
13
|
+
Thread.new do
|
14
|
+
result = orig_readline(*args)
|
15
|
+
async.signal!(result)
|
16
|
+
end
|
17
|
+
async.await
|
12
18
|
end
|
13
19
|
end
|
data/lib/polyphony/trace.rb
CHANGED
@@ -6,9 +6,10 @@ require 'polyphony'
|
|
6
6
|
|
7
7
|
STOCK_EVENTS = %i[line call return c_call c_return b_call b_return].freeze
|
8
8
|
|
9
|
-
def new
|
9
|
+
def new(*events)
|
10
10
|
start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
11
|
-
|
11
|
+
events = STOCK_EVENTS if events.empty?
|
12
|
+
::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
|
12
13
|
end
|
13
14
|
|
14
15
|
def trace_record(trp, start_stamp)
|
@@ -16,7 +17,7 @@ def trace_record(trp, start_stamp)
|
|
16
17
|
{ stamp: stamp, self: trp.self, binding: trp.binding, event: trp.event,
|
17
18
|
fiber: tp_fiber(trp), lineno: trp.lineno, method_id: trp.method_id,
|
18
19
|
file: trp.path, parameters: tp_params(trp),
|
19
|
-
return_value: tp_return_value(trp),
|
20
|
+
return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
|
20
21
|
exception: tp_raised_exception(trp) }
|
21
22
|
end
|
22
23
|
|
@@ -36,6 +37,12 @@ def tp_return_value(trp)
|
|
36
37
|
RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
|
37
38
|
end
|
38
39
|
|
40
|
+
SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
|
41
|
+
|
42
|
+
def tp_schedule_value(trp)
|
43
|
+
SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
|
44
|
+
end
|
45
|
+
|
39
46
|
def tp_raised_exception(trp)
|
40
47
|
trp.event == :raise && trp.raised_exception
|
41
48
|
end
|
@@ -79,19 +86,45 @@ class FiberTracePoint
|
|
79
86
|
end
|
80
87
|
|
81
88
|
class << ::TracePoint
|
89
|
+
POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
|
90
|
+
|
82
91
|
alias_method :orig_new, :new
|
83
92
|
def new(*args, &block)
|
84
|
-
|
93
|
+
events_mask, fiber_events_mask = event_masks(args)
|
85
94
|
|
86
|
-
orig_new(*
|
87
|
-
|
95
|
+
orig_new(*events_mask) do |tp|
|
96
|
+
handle_tp_event(tp, fiber_events_mask, &block)
|
97
|
+
end
|
98
|
+
end
|
88
99
|
|
89
|
-
|
90
|
-
|
91
|
-
else
|
92
|
-
next if tp.path =~ polyphony_file_regexp
|
100
|
+
def handle_tp_event(tpoint, fiber_events_mask)
|
101
|
+
# next unless !$watched_fiber || Fiber.current == $watched_fiber
|
93
102
|
|
94
|
-
|
103
|
+
if tpoint.method_id == :__fiber_trace__
|
104
|
+
return if tpoint.event != :c_return
|
105
|
+
return unless fiber_events_mask.include?(tpoint.return_value[0])
|
106
|
+
|
107
|
+
tpoint = FiberTracePoint.new(tpoint)
|
108
|
+
elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
|
109
|
+
return
|
110
|
+
end
|
111
|
+
|
112
|
+
yield tpoint
|
113
|
+
end
|
114
|
+
|
115
|
+
ALL_FIBER_EVENTS = %i[
|
116
|
+
fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
|
117
|
+
fiber_ev_loop_enter fiber_ev_loop_leave
|
118
|
+
].freeze
|
119
|
+
|
120
|
+
def event_masks(events)
|
121
|
+
events.each_with_object([[], []]) do |e, masks|
|
122
|
+
case e
|
123
|
+
when /fiber_/
|
124
|
+
masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
|
125
|
+
masks[0] << :c_return unless masks[0].include?(:c_return)
|
126
|
+
else
|
127
|
+
masks[0] << e
|
95
128
|
end
|
96
129
|
end
|
97
130
|
end
|
data/lib/polyphony/version.rb
CHANGED
data/lib/polyphony.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/test_async.rb
CHANGED
data/test/test_cancel_scope.rb
CHANGED
@@ -6,7 +6,7 @@ class CancelScopeTest < MiniTest::Test
|
|
6
6
|
def test_that_cancel_scope_can_cancel_provided_block
|
7
7
|
buffer = []
|
8
8
|
Polyphony::CancelScope.new { |scope|
|
9
|
-
|
9
|
+
spin { scope.cancel! }
|
10
10
|
buffer << 1
|
11
11
|
snooze
|
12
12
|
buffer << 2
|
@@ -77,7 +77,7 @@ class CancelScopeTest < MiniTest::Test
|
|
77
77
|
scope.call {
|
78
78
|
sleep 0.005
|
79
79
|
scope.reset_timeout
|
80
|
-
sleep 0.
|
80
|
+
sleep 0.005
|
81
81
|
}
|
82
82
|
|
83
83
|
assert !scope.cancelled?
|
@@ -86,7 +86,7 @@ class CancelScopeTest < MiniTest::Test
|
|
86
86
|
def test_on_cancel
|
87
87
|
buffer = []
|
88
88
|
Polyphony::CancelScope.new { |scope|
|
89
|
-
|
89
|
+
spin { scope.cancel! }
|
90
90
|
scope.on_cancel { buffer << :cancelled }
|
91
91
|
buffer << 1
|
92
92
|
snooze
|