polyphony 1.1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 +9 -0
- data/README.md +2 -1
- data/docs/advanced-io.md +9 -1
- 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 +1 -1
- 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 +5 -3
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)
|