polyphony 1.1 → 1.2
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/.github/workflows/test.yml +1 -1
- data/.github/workflows/test_io_uring.yml +1 -1
- data/.rubocop.yml +16 -8
- data/CHANGELOG.md +13 -0
- data/README.md +2 -1
- data/docs/advanced-io.md +141 -44
- data/docs/cancellation.md +213 -0
- data/docs/readme.md +2 -1
- data/examples/core/enumerator.rb +92 -0
- data/examples/io/https_server_sni_2.rb +1 -1
- data/ext/polyphony/backend_common.c +11 -0
- data/ext/polyphony/backend_common.h +2 -0
- data/ext/polyphony/backend_io_uring.c +1 -1
- data/ext/polyphony/backend_libev.c +1 -1
- data/ext/polyphony/polyphony.h +3 -1
- data/lib/polyphony/core/debug.rb +24 -29
- data/lib/polyphony/core/exceptions.rb +0 -3
- data/lib/polyphony/core/sync.rb +0 -3
- data/lib/polyphony/core/thread_pool.rb +1 -5
- data/lib/polyphony/core/throttler.rb +0 -1
- data/lib/polyphony/core/timer.rb +7 -9
- data/lib/polyphony/extensions/exception.rb +0 -1
- data/lib/polyphony/extensions/fiber.rb +41 -28
- data/lib/polyphony/extensions/io.rb +86 -93
- data/lib/polyphony/extensions/kernel.rb +52 -16
- data/lib/polyphony/extensions/object.rb +7 -6
- data/lib/polyphony/extensions/openssl.rb +6 -8
- data/lib/polyphony/extensions/pipe.rb +5 -7
- data/lib/polyphony/extensions/socket.rb +28 -37
- data/lib/polyphony/extensions/thread.rb +2 -4
- data/lib/polyphony/extensions/timeout.rb +0 -1
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +4 -7
- data/polyphony.gemspec +2 -2
- data/test/test_fiber.rb +6 -6
- data/test/test_global_api.rb +3 -3
- data/test/test_io.rb +2 -2
- data/test/test_socket.rb +2 -2
- data/test/test_supervise.rb +1 -1
- metadata +6 -4
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
class ::Enumerator
|
7
|
+
def spin
|
8
|
+
map { |i| Object.spin { yield i } }
|
9
|
+
end
|
10
|
+
|
11
|
+
def concurrently(max_fibers: nil, &block)
|
12
|
+
return each_concurrently_with_fiber_pool(max_fibers, &block) if max_fibers
|
13
|
+
|
14
|
+
results = []
|
15
|
+
fibers = []
|
16
|
+
each_with_index do |i, idx|
|
17
|
+
fibers << Object.spin { results[idx] = block.(i) }
|
18
|
+
end
|
19
|
+
Fiber.await(fibers)
|
20
|
+
results
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def each_concurrently_with_fiber_pool(max_fibers, &block)
|
26
|
+
fiber_count = 0
|
27
|
+
results = []
|
28
|
+
workers = []
|
29
|
+
|
30
|
+
each_with_index do |i, idx|
|
31
|
+
if fiber_count < max_fibers
|
32
|
+
workers << Object.spin do
|
33
|
+
loop do
|
34
|
+
item, idx = receive
|
35
|
+
break if item == :__stop__
|
36
|
+
results[idx] = block.(item)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
fiber = workers.shift
|
42
|
+
fiber << [i, idx]
|
43
|
+
workers << fiber
|
44
|
+
end
|
45
|
+
workers.each { |f| f << :__stop__ }
|
46
|
+
Fiber.current.await_all_children
|
47
|
+
results
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
a = [1, 2, 3]
|
52
|
+
|
53
|
+
# ff = a.map do |i|
|
54
|
+
# spin do
|
55
|
+
# puts "#{Fiber.current.inspect} #{i} >>"
|
56
|
+
# sleep rand(0.1..0.2)
|
57
|
+
# puts "#{Fiber.current.inspect} #{i} <<"
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
|
61
|
+
# Fiber.await(*ff)
|
62
|
+
|
63
|
+
# puts; puts '*' * 40; puts
|
64
|
+
|
65
|
+
# ff = a.each.spin do |i|
|
66
|
+
# puts "#{Fiber.current.inspect} #{i} >>"
|
67
|
+
# sleep 0.1
|
68
|
+
# puts "#{Fiber.current.inspect} #{i} <<"
|
69
|
+
# end
|
70
|
+
|
71
|
+
# Fiber.await(*ff)
|
72
|
+
|
73
|
+
# puts; puts '*' * 40; puts
|
74
|
+
|
75
|
+
# ff = a.each.concurrently do |i|
|
76
|
+
# puts "#{Fiber.current.inspect} #{i} >>"
|
77
|
+
# sleep 0.1
|
78
|
+
# puts "#{Fiber.current.inspect} #{i} <<"
|
79
|
+
# i * 10
|
80
|
+
# end
|
81
|
+
# p ff: ff
|
82
|
+
|
83
|
+
puts; puts '*' * 40; puts
|
84
|
+
|
85
|
+
ff = a.each.concurrently(max_fibers: 2) do |i|
|
86
|
+
puts "#{Fiber.current.inspect} #{i} >>"
|
87
|
+
sleep i
|
88
|
+
puts "#{Fiber.current.inspect} #{i} <<"
|
89
|
+
i * 10
|
90
|
+
end
|
91
|
+
|
92
|
+
p ff: ff
|
@@ -28,7 +28,7 @@ server = Polyphony::Net.tcp_listen('localhost', 1234, opts)
|
|
28
28
|
puts 'Serving HTTPS on port 1234'
|
29
29
|
|
30
30
|
begin
|
31
|
-
server.accept_loop(false) do |socket|
|
31
|
+
server.accept_loop(ignore_errors: false) do |socket|
|
32
32
|
spin do
|
33
33
|
while (data = socket.gets("\n", 8192))
|
34
34
|
if data.chomp.empty?
|
@@ -8,6 +8,8 @@
|
|
8
8
|
#include "ruby/io/buffer.h"
|
9
9
|
#endif
|
10
10
|
|
11
|
+
VALUE cBackend;
|
12
|
+
|
11
13
|
inline void backend_base_initialize(struct Backend_base *base) {
|
12
14
|
runqueue_initialize(&base->runqueue);
|
13
15
|
runqueue_initialize(&base->parked_runqueue);
|
@@ -604,3 +606,12 @@ VALUE coerce_io_string_or_buffer(VALUE buf) {
|
|
604
606
|
return StringValue(buf);
|
605
607
|
}
|
606
608
|
}
|
609
|
+
|
610
|
+
inline VALUE Backend_for_current_thread(void) {
|
611
|
+
VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
612
|
+
if (backend == Qnil) {
|
613
|
+
backend = rb_funcall(cBackend, ID_new, 0);
|
614
|
+
rb_ivar_set(rb_thread_current(), ID_ivar_backend, backend);
|
615
|
+
}
|
616
|
+
return backend;
|
617
|
+
}
|
@@ -1965,7 +1965,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
|
|
1965
1965
|
}
|
1966
1966
|
|
1967
1967
|
void Init_Backend(void) {
|
1968
|
-
|
1968
|
+
cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
|
1969
1969
|
rb_define_alloc_func(cBackend, Backend_allocate);
|
1970
1970
|
|
1971
1971
|
rb_define_method(cBackend, "initialize", Backend_initialize, 0);
|
@@ -1610,7 +1610,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
|
|
1610
1610
|
void Init_Backend(void) {
|
1611
1611
|
ev_set_allocator(xrealloc);
|
1612
1612
|
|
1613
|
-
|
1613
|
+
cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
|
1614
1614
|
rb_define_alloc_func(cBackend, Backend_allocate);
|
1615
1615
|
|
1616
1616
|
rb_define_method(cBackend, "initialize", Backend_initialize, 0);
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -25,7 +25,7 @@
|
|
25
25
|
#define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
|
26
26
|
#endif
|
27
27
|
|
28
|
-
#define BACKEND() (
|
28
|
+
#define BACKEND() Backend_for_current_thread()
|
29
29
|
|
30
30
|
// SAFE is used to cast functions used in rb_ensure
|
31
31
|
#define SAFE(f) (VALUE (*)(VALUE))(f)
|
@@ -95,6 +95,8 @@ VALUE Pipe_close(VALUE self);
|
|
95
95
|
|
96
96
|
// Backend public interface
|
97
97
|
|
98
|
+
VALUE Backend_for_current_thread();
|
99
|
+
|
98
100
|
VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class);
|
99
101
|
VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class);
|
100
102
|
VALUE Backend_connect(VALUE self, VALUE io, VALUE addr, VALUE port);
|
data/lib/polyphony/core/debug.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
module ::Kernel
|
3
3
|
# Prints a trace message to `STDOUT`, bypassing the Polyphony backend.
|
4
4
|
def trace(*args)
|
5
|
-
|
5
|
+
$stdout.orig_write(format_trace(args))
|
6
6
|
end
|
7
7
|
|
8
8
|
# Formats a trace message.
|
@@ -20,12 +20,10 @@ module ::Kernel
|
|
20
20
|
end
|
21
21
|
|
22
22
|
module Polyphony
|
23
|
-
|
24
23
|
# Trace provides tools for tracing the activity of the current thread's
|
25
24
|
# backend.
|
26
25
|
module Trace
|
27
26
|
class << self
|
28
|
-
|
29
27
|
# Starts tracing, emitting events converted to hashes to the given block.
|
30
28
|
# If an IO instance is given, events are dumped to it instead.
|
31
29
|
#
|
@@ -48,7 +46,7 @@ module Polyphony
|
|
48
46
|
elsif block
|
49
47
|
->(*e) { block.(trace_event_info(e)) }
|
50
48
|
else
|
51
|
-
raise
|
49
|
+
raise 'Please provide an io or a block'
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
@@ -56,12 +54,12 @@ module Polyphony
|
|
56
54
|
#
|
57
55
|
# @param e [Array] event as emitted by the backend
|
58
56
|
# @return [Hash] event hash
|
59
|
-
def trace_event_info(
|
57
|
+
def trace_event_info(event)
|
60
58
|
{
|
61
59
|
stamp: Time.now,
|
62
|
-
event:
|
60
|
+
event: event[0]
|
63
61
|
}.merge(
|
64
|
-
send(:"event_props_#{
|
62
|
+
send(:"event_props_#{event[0]}", event)
|
65
63
|
)
|
66
64
|
end
|
67
65
|
|
@@ -69,10 +67,10 @@ module Polyphony
|
|
69
67
|
#
|
70
68
|
# @param e [Array] event array
|
71
69
|
# @return [Hash] event hash
|
72
|
-
def event_props_block(
|
70
|
+
def event_props_block(event)
|
73
71
|
{
|
74
|
-
fiber:
|
75
|
-
caller:
|
72
|
+
fiber: event[1],
|
73
|
+
caller: event[2]
|
76
74
|
}
|
77
75
|
end
|
78
76
|
|
@@ -80,7 +78,7 @@ module Polyphony
|
|
80
78
|
#
|
81
79
|
# @param e [Array] event array
|
82
80
|
# @return [Hash] event hash
|
83
|
-
def event_props_enter_poll(
|
81
|
+
def event_props_enter_poll(_event)
|
84
82
|
{}
|
85
83
|
end
|
86
84
|
|
@@ -88,7 +86,7 @@ module Polyphony
|
|
88
86
|
#
|
89
87
|
# @param e [Array] event array
|
90
88
|
# @return [Hash] event hash
|
91
|
-
def event_props_leave_poll(
|
89
|
+
def event_props_leave_poll(_event)
|
92
90
|
{}
|
93
91
|
end
|
94
92
|
|
@@ -96,11 +94,11 @@ module Polyphony
|
|
96
94
|
#
|
97
95
|
# @param e [Array] event array
|
98
96
|
# @return [Hash] event hash
|
99
|
-
def event_props_schedule(
|
97
|
+
def event_props_schedule(event)
|
100
98
|
{
|
101
|
-
fiber:
|
102
|
-
value:
|
103
|
-
caller:
|
99
|
+
fiber: event[1],
|
100
|
+
value: event[2],
|
101
|
+
caller: event[4],
|
104
102
|
source_fiber: Fiber.current
|
105
103
|
}
|
106
104
|
end
|
@@ -109,10 +107,10 @@ module Polyphony
|
|
109
107
|
#
|
110
108
|
# @param e [Array] event array
|
111
109
|
# @return [Hash] event hash
|
112
|
-
def event_props_spin(
|
110
|
+
def event_props_spin(event)
|
113
111
|
{
|
114
|
-
fiber:
|
115
|
-
caller:
|
112
|
+
fiber: event[1],
|
113
|
+
caller: event[2],
|
116
114
|
source_fiber: Fiber.current
|
117
115
|
}
|
118
116
|
end
|
@@ -121,10 +119,10 @@ module Polyphony
|
|
121
119
|
#
|
122
120
|
# @param e [Array] event array
|
123
121
|
# @return [Hash] event hash
|
124
|
-
def event_props_terminate(
|
122
|
+
def event_props_terminate(event)
|
125
123
|
{
|
126
|
-
fiber:
|
127
|
-
value:
|
124
|
+
fiber: event[1],
|
125
|
+
value: event[2]
|
128
126
|
}
|
129
127
|
end
|
130
128
|
|
@@ -132,11 +130,11 @@ module Polyphony
|
|
132
130
|
#
|
133
131
|
# @param e [Array] event array
|
134
132
|
# @return [Hash] event hash
|
135
|
-
def event_props_unblock(
|
133
|
+
def event_props_unblock(event)
|
136
134
|
{
|
137
|
-
fiber:
|
138
|
-
value:
|
139
|
-
caller:
|
135
|
+
fiber: event[1],
|
136
|
+
value: event[2],
|
137
|
+
caller: event[3]
|
140
138
|
}
|
141
139
|
end
|
142
140
|
|
@@ -170,12 +168,10 @@ module Polyphony
|
|
170
168
|
# generic_event_format
|
171
169
|
# end
|
172
170
|
|
173
|
-
|
174
171
|
# def event_format_schedule(e)
|
175
172
|
# "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s <= %<origin_fiber>s"
|
176
173
|
# end
|
177
174
|
|
178
|
-
|
179
175
|
# def event_format_unblock(e)
|
180
176
|
# "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s"
|
181
177
|
# end
|
@@ -188,7 +184,6 @@ module Polyphony
|
|
188
184
|
# "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s"
|
189
185
|
# end
|
190
186
|
|
191
|
-
|
192
187
|
# def event_format_spin(e)
|
193
188
|
# "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s <= %<origin_fiber>s"
|
194
189
|
# end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Polyphony
|
4
|
-
|
5
4
|
# Base exception class for interrupting fibers. These exceptions allow control
|
6
5
|
# of fibers. BaseException exceptions can encapsulate a value and thus provide
|
7
6
|
# a way to interrupt long-running blocking operations while still passing a
|
@@ -9,7 +8,6 @@ module Polyphony
|
|
9
8
|
# cancel scope in order to allow correct bubbling of exceptions through nested
|
10
9
|
# cancel scopes.
|
11
10
|
class BaseException < ::Exception
|
12
|
-
|
13
11
|
# Exception value, used mainly for `MoveOn` exceptions.
|
14
12
|
attr_reader :value
|
15
13
|
|
@@ -39,7 +37,6 @@ module Polyphony
|
|
39
37
|
|
40
38
|
# Interjection is used to run arbitrary code on arbitrary fibers at any point
|
41
39
|
class Interjection < BaseException
|
42
|
-
|
43
40
|
# Initializes an Interjection with the given proc.
|
44
41
|
#
|
45
42
|
# @param proc [Proc] interjection proc
|
data/lib/polyphony/core/sync.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Polyphony
|
4
|
-
|
5
4
|
# Implements mutex lock for synchronizing access to a shared resource. This
|
6
5
|
# class replaces the stock `Thread::Mutex` class.
|
7
6
|
class Mutex
|
8
|
-
|
9
7
|
# Initializes a new mutex.
|
10
8
|
def initialize
|
11
9
|
@store = Queue.new
|
@@ -130,7 +128,6 @@ module Polyphony
|
|
130
128
|
|
131
129
|
# Implements a fiber-aware ConditionVariable
|
132
130
|
class ConditionVariable
|
133
|
-
|
134
131
|
# Initializes the condition variable.
|
135
132
|
def initialize
|
136
133
|
@queue = Polyphony::Queue.new
|
@@ -3,10 +3,8 @@
|
|
3
3
|
require 'etc'
|
4
4
|
|
5
5
|
module Polyphony
|
6
|
-
|
7
6
|
# Implements a pool of threads
|
8
7
|
class ThreadPool
|
9
|
-
|
10
8
|
# The pool size.
|
11
9
|
attr_reader :size
|
12
10
|
|
@@ -79,9 +77,7 @@ module Polyphony
|
|
79
77
|
|
80
78
|
# Runs a processing loop on a worker thread.
|
81
79
|
def thread_loop
|
82
|
-
while true
|
83
|
-
run_queued_task
|
84
|
-
end
|
80
|
+
run_queued_task while true
|
85
81
|
end
|
86
82
|
|
87
83
|
# Runs the first queued task in the task queue.
|
data/lib/polyphony/core/timer.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Polyphony
|
4
|
-
|
5
4
|
# Implements a common timer for running multiple timeouts. This class may be
|
6
5
|
# used to reduce the timer granularity in case a large number of timeouts is
|
7
6
|
# used concurrently. This class basically provides the same methods as global
|
8
7
|
# methods concerned with timeouts, such as `#cancel_after`, `#every` etc.
|
9
8
|
class Timer
|
10
|
-
|
11
9
|
# Initializes a new timer with the given resolution.
|
12
10
|
#
|
13
11
|
# @param tag [any] tag to use for the timer's fiber
|
@@ -31,7 +29,7 @@ module Polyphony
|
|
31
29
|
def sleep(duration)
|
32
30
|
fiber = Fiber.current
|
33
31
|
@timeouts[fiber] = {
|
34
|
-
interval:
|
32
|
+
interval: duration,
|
35
33
|
target_stamp: now + duration
|
36
34
|
}
|
37
35
|
Polyphony.backend_wait_event(true)
|
@@ -58,9 +56,9 @@ module Polyphony
|
|
58
56
|
def every(interval)
|
59
57
|
fiber = Fiber.current
|
60
58
|
@timeouts[fiber] = {
|
61
|
-
interval
|
59
|
+
interval:,
|
62
60
|
target_stamp: now + interval,
|
63
|
-
recurring:
|
61
|
+
recurring: true
|
64
62
|
}
|
65
63
|
while true
|
66
64
|
Polyphony.backend_wait_event(true)
|
@@ -113,9 +111,9 @@ module Polyphony
|
|
113
111
|
def cancel_after(interval, with_exception: Polyphony::Cancel)
|
114
112
|
fiber = Fiber.current
|
115
113
|
@timeouts[fiber] = {
|
116
|
-
interval
|
114
|
+
interval:,
|
117
115
|
target_stamp: now + interval,
|
118
|
-
exception:
|
116
|
+
exception: with_exception
|
119
117
|
}
|
120
118
|
yield
|
121
119
|
ensure
|
@@ -166,9 +164,9 @@ module Polyphony
|
|
166
164
|
def move_on_after(interval, with_value: nil)
|
167
165
|
fiber = Fiber.current
|
168
166
|
@timeouts[fiber] = {
|
169
|
-
interval
|
167
|
+
interval:,
|
170
168
|
target_stamp: now + interval,
|
171
|
-
exception:
|
169
|
+
exception: [Polyphony::MoveOn, with_value]
|
172
170
|
}
|
173
171
|
yield
|
174
172
|
rescue Polyphony::MoveOn => e
|
@@ -119,7 +119,7 @@ class ::Fiber
|
|
119
119
|
def cancel(exception = Polyphony::Cancel)
|
120
120
|
return if @running == false
|
121
121
|
|
122
|
-
value = (Class
|
122
|
+
value = exception.is_a?(Class) ? exception.new : exception
|
123
123
|
schedule value
|
124
124
|
self
|
125
125
|
end
|
@@ -143,7 +143,7 @@ class ::Fiber
|
|
143
143
|
#
|
144
144
|
# @param graceful [bool] Whether to perform a graceful shutdown
|
145
145
|
# @return [Fiber] self
|
146
|
-
def terminate(graceful
|
146
|
+
def terminate(graceful: false)
|
147
147
|
return if @running == false
|
148
148
|
|
149
149
|
@graceful_shutdown = graceful
|
@@ -265,7 +265,7 @@ class ::Fiber
|
|
265
265
|
#
|
266
266
|
# @param graceful [bool] whether to perform a graceful termination
|
267
267
|
# @return [Fiber] self
|
268
|
-
def terminate_all_children(graceful
|
268
|
+
def terminate_all_children(graceful: false)
|
269
269
|
return self unless @children
|
270
270
|
|
271
271
|
e = Polyphony::Terminate.new
|
@@ -283,23 +283,24 @@ class ::Fiber
|
|
283
283
|
def await_all_children
|
284
284
|
return unless @children && !@children.empty?
|
285
285
|
|
286
|
-
Fiber.await(
|
286
|
+
Fiber.await(@children.keys.reject(&:dead?))
|
287
287
|
end
|
288
288
|
|
289
289
|
# Terminates and blocks until all child fibers have terminated.
|
290
290
|
#
|
291
291
|
# @return [Fiber] self
|
292
|
-
def shutdown_all_children(graceful
|
292
|
+
def shutdown_all_children(graceful: false)
|
293
293
|
return self unless @children
|
294
294
|
|
295
295
|
pending = []
|
296
|
-
@children.keys
|
296
|
+
child_fibers = @children.keys
|
297
|
+
child_fibers.each do |c|
|
297
298
|
next if c.dead?
|
298
299
|
|
299
|
-
c.terminate(graceful)
|
300
|
+
c.terminate(graceful:)
|
300
301
|
pending << c
|
301
302
|
end
|
302
|
-
Fiber.await(
|
303
|
+
Fiber.await(pending)
|
303
304
|
self
|
304
305
|
end
|
305
306
|
|
@@ -308,7 +309,8 @@ class ::Fiber
|
|
308
309
|
# @param parent [Fiber] new parent
|
309
310
|
# @return [Fiber] self
|
310
311
|
def attach_all_children_to(parent)
|
311
|
-
@children&.keys
|
312
|
+
child_fibers = @children&.keys
|
313
|
+
child_fibers&.each { |c| c.attach_to(parent) }
|
312
314
|
self
|
313
315
|
end
|
314
316
|
|
@@ -361,7 +363,7 @@ class ::Fiber
|
|
361
363
|
# @param child_fiber [Fiber] child fiber to be removed
|
362
364
|
# @return [Fiber] self
|
363
365
|
def remove_child(child_fiber)
|
364
|
-
@children
|
366
|
+
@children&.delete(child_fiber)
|
365
367
|
self
|
366
368
|
end
|
367
369
|
|
@@ -382,7 +384,7 @@ class ::Fiber
|
|
382
384
|
@parent = parent
|
383
385
|
@caller = caller
|
384
386
|
@block = block
|
385
|
-
Thread.backend.trace(:spin, self, Kernel.caller[1
|
387
|
+
Thread.backend.trace(:spin, self, Kernel.caller[1..])
|
386
388
|
schedule
|
387
389
|
self
|
388
390
|
end
|
@@ -405,7 +407,7 @@ class ::Fiber
|
|
405
407
|
finalize(e.value)
|
406
408
|
rescue Exception => e
|
407
409
|
e.source_fiber = self
|
408
|
-
finalize(e, true)
|
410
|
+
finalize(e, uncaught_exception: true)
|
409
411
|
end
|
410
412
|
|
411
413
|
# Performs setup for a "raw" Fiber created using Fiber.new. Note that this
|
@@ -446,7 +448,7 @@ class ::Fiber
|
|
446
448
|
# @param result [any] return value
|
447
449
|
# @param uncaught_exception [Exception, nil] uncaught exception
|
448
450
|
# @return [false]
|
449
|
-
def finalize(result, uncaught_exception
|
451
|
+
def finalize(result, uncaught_exception: false)
|
450
452
|
result, uncaught_exception = finalize_children(result, uncaught_exception)
|
451
453
|
Thread.backend.trace(:terminate, self, result)
|
452
454
|
@result = result
|
@@ -468,7 +470,7 @@ class ::Fiber
|
|
468
470
|
# @param uncaught_exception [Exception, nil] uncaught exception
|
469
471
|
# @return [Array] array containing result and uncaught exception if any
|
470
472
|
def finalize_children(result, uncaught_exception)
|
471
|
-
shutdown_all_children(graceful_shutdown?)
|
473
|
+
shutdown_all_children(graceful: graceful_shutdown?)
|
472
474
|
[result, uncaught_exception]
|
473
475
|
rescue Exception => e
|
474
476
|
[e, true]
|
@@ -486,7 +488,7 @@ class ::Fiber
|
|
486
488
|
end
|
487
489
|
|
488
490
|
if uncaught_exception && @parent
|
489
|
-
parent_is_monitor = @monitors&.
|
491
|
+
parent_is_monitor = @monitors&.key?(@parent)
|
490
492
|
@parent.schedule_with_priority(result) unless parent_is_monitor
|
491
493
|
end
|
492
494
|
|
@@ -532,12 +534,23 @@ class ::Fiber
|
|
532
534
|
# terminates with an uncaught exception, `Fiber.await` will await all the
|
533
535
|
# other fibers to terminate, then reraise the exception.
|
534
536
|
#
|
535
|
-
#
|
536
|
-
#
|
537
|
+
# This method can be called with multiple fibers as multiple arguments, or
|
538
|
+
# with a single array containing one or more fibers.
|
539
|
+
#
|
540
|
+
# @overload Fiber.await(f1, f2, ...)
|
541
|
+
# @param fibers [Array<Fiber>] fibers to wait for
|
542
|
+
# @return [Array<any>] return values of given fibers
|
543
|
+
# @overload Fiber.await(fibers)
|
544
|
+
# @param fibers [Array<Fiber>] fibers to wait for
|
545
|
+
# @return [Array<any>] return values of given fibers
|
537
546
|
def await(*fibers)
|
538
547
|
return [] if fibers.empty?
|
539
548
|
|
540
|
-
|
549
|
+
if (first = fibers.first).is_a?(Array)
|
550
|
+
fibers = first
|
551
|
+
end
|
552
|
+
|
553
|
+
current_fiber = Fiber.current
|
541
554
|
mailbox = current_fiber.monitor_mailbox
|
542
555
|
results = {}
|
543
556
|
fibers.each do |f|
|
@@ -553,16 +566,18 @@ class ::Fiber
|
|
553
566
|
while !fibers.empty?
|
554
567
|
(fiber, result) = mailbox.shift
|
555
568
|
next unless fibers.include?(fiber)
|
569
|
+
|
556
570
|
fibers.delete(fiber)
|
557
571
|
current_fiber.remove_child(fiber) if fiber.parent == current_fiber
|
558
572
|
if result.is_a?(Exception)
|
559
573
|
exception ||= result
|
560
|
-
fibers.each
|
574
|
+
fibers.each(&:terminate)
|
561
575
|
else
|
562
576
|
results[fiber] = result
|
563
577
|
end
|
564
578
|
end
|
565
579
|
raise exception if exception
|
580
|
+
|
566
581
|
results.values
|
567
582
|
end
|
568
583
|
alias_method :join, :await
|
@@ -576,7 +591,7 @@ class ::Fiber
|
|
576
591
|
def select(*fibers)
|
577
592
|
return nil if fibers.empty?
|
578
593
|
|
579
|
-
current_fiber =
|
594
|
+
current_fiber = Fiber.current
|
580
595
|
mailbox = current_fiber.monitor_mailbox
|
581
596
|
fibers.each do |f|
|
582
597
|
if f.dead?
|
@@ -591,11 +606,9 @@ class ::Fiber
|
|
591
606
|
next unless fibers.include?(fiber)
|
592
607
|
|
593
608
|
fibers.each { |f| f.unmonitor(current_fiber) }
|
594
|
-
if result.is_a?(Exception)
|
595
|
-
|
596
|
-
|
597
|
-
return [fiber, result]
|
598
|
-
end
|
609
|
+
raise result if result.is_a?(Exception)
|
610
|
+
|
611
|
+
return [fiber, result]
|
599
612
|
end
|
600
613
|
end
|
601
614
|
|
@@ -629,7 +642,7 @@ class ::Fiber
|
|
629
642
|
fiber.tag = :oob
|
630
643
|
fiber.thread = Thread.current
|
631
644
|
location = block.source_location
|
632
|
-
fiber.set_caller([
|
645
|
+
fiber.set_caller([location.join(':')])
|
633
646
|
end
|
634
647
|
end
|
635
648
|
|
@@ -651,11 +664,11 @@ class ::Fiber
|
|
651
664
|
restart = opts[:restart]
|
652
665
|
return nil unless block || restart
|
653
666
|
|
654
|
-
error_only =
|
667
|
+
error_only = opts[:on_error]
|
655
668
|
restart_always = (restart == :always) || (restart == true)
|
656
669
|
restart_on_error = restart == :on_error
|
657
670
|
|
658
|
-
|
671
|
+
lambda do |f, r|
|
659
672
|
is_error = r.is_a?(Exception)
|
660
673
|
block.(f, r) if block && (!error_only || is_error)
|
661
674
|
f.restart if restart_always || (restart_on_error && is_error)
|