polyphony 0.49.0 → 0.51.0
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/CHANGELOG.md +31 -0
- data/Gemfile.lock +3 -1
- data/TODO.md +6 -2
- data/examples/core/nested.rb +21 -0
- data/examples/core/suspend.rb +13 -0
- data/examples/core/terminate_main_fiber.rb +12 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +2 -4
- data/ext/polyphony/backend_common.h +29 -2
- data/ext/polyphony/backend_io_uring.c +111 -32
- data/ext/polyphony/backend_libev.c +79 -46
- data/ext/polyphony/fiber.c +2 -1
- data/ext/polyphony/polyphony.h +1 -0
- data/ext/polyphony/runqueue.c +6 -0
- data/ext/polyphony/runqueue_ring_buffer.c +9 -0
- data/ext/polyphony/runqueue_ring_buffer.h +1 -0
- data/ext/polyphony/thread.c +14 -0
- data/lib/polyphony.rb +1 -1
- data/lib/polyphony/adapters/process.rb +2 -0
- data/lib/polyphony/core/exceptions.rb +1 -0
- data/lib/polyphony/core/global_api.rb +1 -1
- data/lib/polyphony/core/timer.rb +63 -20
- data/lib/polyphony/extensions/core.rb +4 -4
- data/lib/polyphony/extensions/fiber.rb +11 -8
- data/lib/polyphony/extensions/io.rb +4 -0
- data/lib/polyphony/extensions/socket.rb +12 -0
- data/lib/polyphony/extensions/thread.rb +1 -2
- data/lib/polyphony/net.rb +3 -6
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -0
- data/test/helper.rb +1 -2
- data/test/test_backend.rb +25 -0
- data/test/test_fiber.rb +31 -0
- data/test/test_io.rb +53 -1
- data/test/test_signal.rb +1 -2
- data/test/test_socket.rb +34 -0
- data/test/test_timer.rb +46 -11
- metadata +19 -2
data/ext/polyphony/fiber.c
CHANGED
@@ -101,7 +101,8 @@ VALUE Fiber_await(VALUE self) {
|
|
101
101
|
}
|
102
102
|
rb_hash_aset(waiting_fibers, fiber, Qtrue);
|
103
103
|
|
104
|
-
|
104
|
+
VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
105
|
+
result = Backend_wait_event(backend, Qnil);
|
105
106
|
|
106
107
|
rb_hash_delete(waiting_fibers, fiber);
|
107
108
|
RAISE_IF_EXCEPTION(result);
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -84,6 +84,7 @@ void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule);
|
|
84
84
|
void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule);
|
85
85
|
runqueue_entry Runqueue_shift(VALUE self);
|
86
86
|
void Runqueue_delete(VALUE self, VALUE fiber);
|
87
|
+
int Runqueue_index_of(VALUE self, VALUE fiber);
|
87
88
|
void Runqueue_clear(VALUE self);
|
88
89
|
long Runqueue_len(VALUE self);
|
89
90
|
int Runqueue_empty_p(VALUE self);
|
data/ext/polyphony/runqueue.c
CHANGED
@@ -74,6 +74,12 @@ void Runqueue_delete(VALUE self, VALUE fiber) {
|
|
74
74
|
runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
75
75
|
}
|
76
76
|
|
77
|
+
int Runqueue_index_of(VALUE self, VALUE fiber) {
|
78
|
+
Runqueue_t *runqueue;
|
79
|
+
GetRunqueue(self, runqueue);
|
80
|
+
return runqueue_ring_buffer_index_of(&runqueue->entries, fiber);
|
81
|
+
}
|
82
|
+
|
77
83
|
void Runqueue_clear(VALUE self) {
|
78
84
|
Runqueue_t *runqueue;
|
79
85
|
GetRunqueue(self, runqueue);
|
@@ -80,6 +80,15 @@ void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
|
|
80
80
|
}
|
81
81
|
}
|
82
82
|
|
83
|
+
int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
|
84
|
+
for (unsigned int i = 0; i < buffer->count; i++) {
|
85
|
+
unsigned int idx = (buffer->head + i) % buffer->size;
|
86
|
+
if (buffer->entries[idx].fiber == fiber)
|
87
|
+
return i;
|
88
|
+
}
|
89
|
+
return -1;
|
90
|
+
}
|
91
|
+
|
83
92
|
void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
|
84
93
|
buffer->count = buffer->head = buffer->tail = 0;
|
85
94
|
}
|
@@ -27,5 +27,6 @@ void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VAL
|
|
27
27
|
void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
|
28
28
|
|
29
29
|
void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
|
30
|
+
int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
|
30
31
|
|
31
32
|
#endif /* RUNQUEUE_RING_BUFFER_H */
|
data/ext/polyphony/thread.c
CHANGED
@@ -59,6 +59,18 @@ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
|
|
59
59
|
}
|
60
60
|
}
|
61
61
|
|
62
|
+
VALUE Thread_fiber_scheduling_index(VALUE self, VALUE fiber) {
|
63
|
+
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
64
|
+
|
65
|
+
return INT2NUM(Runqueue_index_of(runqueue, fiber));
|
66
|
+
}
|
67
|
+
|
68
|
+
VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
|
69
|
+
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
70
|
+
Runqueue_delete(runqueue, fiber);
|
71
|
+
return self;
|
72
|
+
}
|
73
|
+
|
62
74
|
VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
63
75
|
schedule_fiber(self, fiber, value, 0);
|
64
76
|
return self;
|
@@ -135,6 +147,8 @@ void Init_Thread() {
|
|
135
147
|
rb_define_method(rb_cThread, "schedule_fiber_with_priority",
|
136
148
|
Thread_schedule_fiber_with_priority, 2);
|
137
149
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
150
|
+
rb_define_method(rb_cThread, "fiber_scheduling_index", Thread_fiber_scheduling_index, 1);
|
151
|
+
rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
|
138
152
|
|
139
153
|
rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
|
140
154
|
|
data/lib/polyphony.rb
CHANGED
data/lib/polyphony/core/timer.rb
CHANGED
@@ -11,12 +11,45 @@ module Polyphony
|
|
11
11
|
def stop
|
12
12
|
@fiber.stop
|
13
13
|
end
|
14
|
+
|
15
|
+
def sleep(duration)
|
16
|
+
fiber = Fiber.current
|
17
|
+
@timeouts[fiber] = {
|
18
|
+
interval: duration,
|
19
|
+
target_stamp: now + duration
|
20
|
+
}
|
21
|
+
Thread.current.backend.wait_event(true)
|
22
|
+
ensure
|
23
|
+
@timeouts.delete(fiber)
|
24
|
+
end
|
25
|
+
|
26
|
+
def after(interval, &block)
|
27
|
+
spin do
|
28
|
+
self.sleep interval
|
29
|
+
block.()
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def every(interval)
|
34
|
+
fiber = Fiber.current
|
35
|
+
@timeouts[fiber] = {
|
36
|
+
interval: interval,
|
37
|
+
target_stamp: now + interval,
|
38
|
+
recurring: true
|
39
|
+
}
|
40
|
+
while true
|
41
|
+
Thread.current.backend.wait_event(true)
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
ensure
|
45
|
+
@timeouts.delete(fiber)
|
46
|
+
end
|
14
47
|
|
15
|
-
def cancel_after(
|
48
|
+
def cancel_after(interval, with_exception: Polyphony::Cancel)
|
16
49
|
fiber = Fiber.current
|
17
50
|
@timeouts[fiber] = {
|
18
|
-
|
19
|
-
target_stamp:
|
51
|
+
interval: interval,
|
52
|
+
target_stamp: now + interval,
|
20
53
|
exception: with_exception
|
21
54
|
}
|
22
55
|
yield
|
@@ -24,12 +57,12 @@ module Polyphony
|
|
24
57
|
@timeouts.delete(fiber)
|
25
58
|
end
|
26
59
|
|
27
|
-
def move_on_after(
|
60
|
+
def move_on_after(interval, with_value: nil)
|
28
61
|
fiber = Fiber.current
|
29
62
|
@timeouts[fiber] = {
|
30
|
-
|
31
|
-
target_stamp:
|
32
|
-
|
63
|
+
interval: interval,
|
64
|
+
target_stamp: now + interval,
|
65
|
+
exception: [Polyphony::MoveOn, with_value]
|
33
66
|
}
|
34
67
|
yield
|
35
68
|
rescue Polyphony::MoveOn => e
|
@@ -37,36 +70,46 @@ module Polyphony
|
|
37
70
|
ensure
|
38
71
|
@timeouts.delete(fiber)
|
39
72
|
end
|
40
|
-
|
73
|
+
|
41
74
|
def reset
|
42
75
|
record = @timeouts[Fiber.current]
|
43
76
|
return unless record
|
44
77
|
|
45
|
-
record[:target_stamp] =
|
78
|
+
record[:target_stamp] = now + record[:interval]
|
46
79
|
end
|
47
|
-
|
80
|
+
|
48
81
|
private
|
49
82
|
|
83
|
+
def now
|
84
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
85
|
+
end
|
86
|
+
|
50
87
|
def timeout_exception(record)
|
51
88
|
case (exception = record[:exception])
|
52
|
-
when
|
53
|
-
|
54
|
-
when
|
55
|
-
|
89
|
+
when Array
|
90
|
+
exception[0].new(exception[1])
|
91
|
+
when Class
|
92
|
+
exception.new
|
93
|
+
else
|
94
|
+
RuntimeError.new(exception)
|
56
95
|
end
|
57
96
|
end
|
58
97
|
|
59
98
|
def update
|
60
|
-
|
61
|
-
|
99
|
+
return if @timeouts.empty?
|
100
|
+
|
62
101
|
@timeouts.each do |fiber, record|
|
63
102
|
next if record[:target_stamp] > now
|
64
103
|
|
65
|
-
|
66
|
-
|
67
|
-
|
104
|
+
value = record[:exception] ? timeout_exception(record) : record[:value]
|
105
|
+
fiber.schedule value
|
106
|
+
|
107
|
+
next unless record[:recurring]
|
108
|
+
|
109
|
+
while record[:target_stamp] <= now
|
110
|
+
record[:target_stamp] += record[:interval]
|
111
|
+
end
|
68
112
|
end
|
69
|
-
# elapsed&.each { |f| @timeouts.delete(f) }
|
70
113
|
end
|
71
114
|
end
|
72
115
|
end
|
@@ -12,11 +12,11 @@ class ::Exception
|
|
12
12
|
attr_accessor :__disable_sanitized_backtrace__
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_accessor :source_fiber, :
|
15
|
+
attr_accessor :source_fiber, :raising_fiber
|
16
16
|
|
17
17
|
alias_method :orig_initialize, :initialize
|
18
18
|
def initialize(*args)
|
19
|
-
@
|
19
|
+
@raising_fiber = Fiber.current
|
20
20
|
orig_initialize(*args)
|
21
21
|
end
|
22
22
|
|
@@ -31,10 +31,10 @@ class ::Exception
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def sanitized_backtrace
|
34
|
-
return sanitize(orig_backtrace) unless @
|
34
|
+
return sanitize(orig_backtrace) unless @raising_fiber
|
35
35
|
|
36
36
|
backtrace = orig_backtrace || []
|
37
|
-
sanitize(backtrace + @
|
37
|
+
sanitize(backtrace + @raising_fiber.caller)
|
38
38
|
end
|
39
39
|
|
40
40
|
POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
|
@@ -245,8 +245,12 @@ module Polyphony
|
|
245
245
|
end
|
246
246
|
|
247
247
|
def shutdown_all_children(graceful = false)
|
248
|
-
|
249
|
-
|
248
|
+
return unless @children
|
249
|
+
|
250
|
+
@children.keys.each do |c|
|
251
|
+
c.terminate(graceful)
|
252
|
+
c.await
|
253
|
+
end
|
250
254
|
end
|
251
255
|
end
|
252
256
|
|
@@ -312,6 +316,8 @@ module Polyphony
|
|
312
316
|
@running = false
|
313
317
|
inform_dependants(result, uncaught_exception)
|
314
318
|
ensure
|
319
|
+
# Prevent fiber from being resumed after terminating
|
320
|
+
@thread.fiber_unschedule(self)
|
315
321
|
Thread.current.switch_fiber
|
316
322
|
end
|
317
323
|
|
@@ -319,13 +325,10 @@ module Polyphony
|
|
319
325
|
# the children are shut down, it is returned along with the uncaught_exception
|
320
326
|
# flag set. Otherwise, it returns the given arguments.
|
321
327
|
def finalize_children(result, uncaught_exception)
|
322
|
-
|
323
|
-
shutdown_all_children
|
324
|
-
rescue Exception => e
|
325
|
-
result = e
|
326
|
-
uncaught_exception = true
|
327
|
-
end
|
328
|
+
shutdown_all_children
|
328
329
|
[result, uncaught_exception]
|
330
|
+
rescue Exception => e
|
331
|
+
[e, true]
|
329
332
|
end
|
330
333
|
|
331
334
|
def inform_dependants(result, uncaught_exception)
|
@@ -220,6 +220,10 @@ class ::IO
|
|
220
220
|
Thread.current.backend.read_loop(self, &block)
|
221
221
|
end
|
222
222
|
|
223
|
+
def feed_loop(receiver, method, &block)
|
224
|
+
Thread.current.backend.feed_loop(self, receiver, method, &block)
|
225
|
+
end
|
226
|
+
|
223
227
|
# alias_method :orig_read, :read
|
224
228
|
# def read(length = nil, outbuf = nil)
|
225
229
|
# if length
|
@@ -31,6 +31,10 @@ class ::Socket
|
|
31
31
|
end
|
32
32
|
alias_method :read_loop, :recv_loop
|
33
33
|
|
34
|
+
def feed_loop(receiver, method, &block)
|
35
|
+
Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
|
36
|
+
end
|
37
|
+
|
34
38
|
def recvfrom(maxlen, flags = 0)
|
35
39
|
@read_buffer ||= +''
|
36
40
|
while true
|
@@ -146,6 +150,10 @@ class ::TCPSocket
|
|
146
150
|
end
|
147
151
|
alias_method :read_loop, :recv_loop
|
148
152
|
|
153
|
+
def feed_loop(receiver, method, &block)
|
154
|
+
Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
|
155
|
+
end
|
156
|
+
|
149
157
|
def send(mesg, flags = 0)
|
150
158
|
Thread.current.backend.send(self, mesg)
|
151
159
|
end
|
@@ -227,6 +235,10 @@ class ::UNIXSocket
|
|
227
235
|
end
|
228
236
|
alias_method :read_loop, :recv_loop
|
229
237
|
|
238
|
+
def feed_loop(receiver, method, &block)
|
239
|
+
Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
|
240
|
+
end
|
241
|
+
|
230
242
|
def send(mesg, flags = 0)
|
231
243
|
Thread.current.backend.send(self, mesg)
|
232
244
|
end
|
@@ -41,8 +41,7 @@ class ::Thread
|
|
41
41
|
|
42
42
|
def finalize(result)
|
43
43
|
unless Fiber.current.children.empty?
|
44
|
-
Fiber.current.
|
45
|
-
Fiber.current.await_all_children
|
44
|
+
Fiber.current.shutdown_all_children
|
46
45
|
end
|
47
46
|
@finalization_mutex.synchronize do
|
48
47
|
@terminated = true
|
data/lib/polyphony/net.rb
CHANGED
@@ -8,10 +8,7 @@ module Polyphony
|
|
8
8
|
module Net
|
9
9
|
class << self
|
10
10
|
def tcp_connect(host, port, opts = {})
|
11
|
-
socket =
|
12
|
-
addr = ::Socket.sockaddr_in(port, host)
|
13
|
-
s.connect(addr)
|
14
|
-
end
|
11
|
+
socket = TCPSocket.new(host, port)
|
15
12
|
if opts[:secure_context] || opts[:secure]
|
16
13
|
secure_socket(socket, opts[:secure_context], opts.merge(host: host))
|
17
14
|
else
|
@@ -23,7 +20,7 @@ module Polyphony
|
|
23
20
|
host ||= '0.0.0.0'
|
24
21
|
raise 'Port number not specified' unless port
|
25
22
|
|
26
|
-
socket =
|
23
|
+
socket = listening_socket_from_options(host, port, opts)
|
27
24
|
if opts[:secure_context] || opts[:secure]
|
28
25
|
secure_server(socket, opts[:secure_context], opts)
|
29
26
|
else
|
@@ -31,7 +28,7 @@ module Polyphony
|
|
31
28
|
end
|
32
29
|
end
|
33
30
|
|
34
|
-
def
|
31
|
+
def listening_socket_from_options(host, port, opts)
|
35
32
|
::Socket.new(:INET, :STREAM).tap do |s|
|
36
33
|
s.reuse_addr if opts[:reuse_addr]
|
37
34
|
s.dont_linger if opts[:dont_linger]
|
data/lib/polyphony/version.rb
CHANGED
data/polyphony.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_development_dependency 'rubocop', '0.85.1'
|
29
29
|
s.add_development_dependency 'pry', '0.13.1'
|
30
30
|
|
31
|
+
s.add_development_dependency 'msgpack', '1.4.2'
|
31
32
|
s.add_development_dependency 'pg', '1.1.4'
|
32
33
|
s.add_development_dependency 'redis', '4.1.0'
|
33
34
|
s.add_development_dependency 'hiredis', '0.6.3'
|
data/test/helper.rb
CHANGED
@@ -57,8 +57,7 @@ class MiniTest::Test
|
|
57
57
|
|
58
58
|
def teardown
|
59
59
|
# trace "* teardown #{self.name}"
|
60
|
-
Fiber.current.
|
61
|
-
Fiber.current.await_all_children
|
60
|
+
Fiber.current.shutdown_all_children
|
62
61
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
63
62
|
rescue => e
|
64
63
|
puts e
|
data/test/test_backend.rb
CHANGED
@@ -100,6 +100,31 @@ class BackendTest < MiniTest::Test
|
|
100
100
|
assert_equal [:ready, 'foo', 'bar', :done], buf
|
101
101
|
end
|
102
102
|
|
103
|
+
def test_read_loop_terminate
|
104
|
+
i, o = IO.pipe
|
105
|
+
|
106
|
+
buf = []
|
107
|
+
parent = spin do
|
108
|
+
f = spin do
|
109
|
+
buf << :ready
|
110
|
+
@backend.read_loop(i) { |d| buf << d }
|
111
|
+
buf << :done
|
112
|
+
end
|
113
|
+
suspend
|
114
|
+
end
|
115
|
+
|
116
|
+
# writing always causes snoozing
|
117
|
+
o << 'foo'
|
118
|
+
sleep 0.01
|
119
|
+
o << 'bar'
|
120
|
+
sleep 0.01
|
121
|
+
|
122
|
+
parent.stop
|
123
|
+
|
124
|
+
parent.await
|
125
|
+
assert_equal [:ready, 'foo', 'bar'], buf
|
126
|
+
end
|
127
|
+
|
103
128
|
def test_accept_loop
|
104
129
|
server = TCPServer.new('127.0.0.1', 1234)
|
105
130
|
|