async 1.31.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/barrier.md +36 -0
- data/lib/async/barrier.rb +14 -6
- data/lib/async/clock.rb +11 -0
- data/lib/async/condition.md +31 -0
- data/lib/async/condition.rb +27 -16
- data/lib/async/node.rb +27 -12
- data/lib/async/notification.rb +4 -4
- data/lib/async/queue.rb +8 -21
- data/lib/async/reactor.rb +11 -316
- data/lib/async/scheduler.rb +235 -48
- data/lib/async/semaphore.md +41 -0
- data/lib/async/semaphore.rb +8 -24
- data/lib/async/task.rb +68 -70
- data/lib/async/version.rb +1 -1
- data/lib/async/wrapper.rb +19 -179
- data/lib/async.rb +0 -5
- data/lib/kernel/async.rb +26 -2
- data/lib/kernel/sync.rb +14 -4
- metadata +10 -10
- data/lib/async/debug/monitor.rb +0 -47
- data/lib/async/debug/selector.rb +0 -82
- data/lib/async/logger.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e5f7f4cbc1cfc595409b58cfc0b74e2a2767837fc40bae5e1a1b60e7dcd3384
|
4
|
+
data.tar.gz: 4eeb27d8248bc7993cf7758e41c8ffee3a54141d7c521e2a2805bbc8e8be3edd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0b59a751dd4ea8cd7f2a83ad76fbf4dcff30ca11629d2e7f6ba18e017cf5de06880c1a6b19a3297967793e80394e10f8873ed24aa278d2019b11eb90a002394
|
7
|
+
data.tar.gz: 1c23c709f983bc14ba1477b41b3ca7533275eb3464928c7d4d564e897fc2fb893617cde7bb6d1e90d050db9bbc1d1d2ed71c87da02e61cd559f0afef363fc567
|
@@ -0,0 +1,36 @@
|
|
1
|
+
A synchronization primitive, which allows one task to wait for a number of other tasks to complete. It can be used in conjunction with {Semaphore}.
|
2
|
+
|
3
|
+
## Example
|
4
|
+
|
5
|
+
~~~ ruby
|
6
|
+
require 'async'
|
7
|
+
require 'async/barrier'
|
8
|
+
|
9
|
+
Sync do
|
10
|
+
barrier = Async::Barrier.new
|
11
|
+
|
12
|
+
# Generate an array of 10 numbers:
|
13
|
+
numbers = 10.times.map{rand(10)}
|
14
|
+
sorted = []
|
15
|
+
|
16
|
+
# Sleep sort the numbers:
|
17
|
+
numbers.each do |number|
|
18
|
+
barrier.async do |task|
|
19
|
+
task.sleep(number)
|
20
|
+
sorted << number
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Wait for all the numbers to be sorted:
|
25
|
+
barrier.wait
|
26
|
+
|
27
|
+
Console.logger.info("Sorted", sorted)
|
28
|
+
end
|
29
|
+
~~~
|
30
|
+
|
31
|
+
### Output
|
32
|
+
|
33
|
+
~~~
|
34
|
+
0.0s info: Sorted [ec=0x104] [pid=50291]
|
35
|
+
| [0, 0, 0, 0, 1, 2, 2, 3, 6, 6]
|
36
|
+
~~~
|
data/lib/async/barrier.rb
CHANGED
@@ -23,8 +23,12 @@
|
|
23
23
|
require_relative 'task'
|
24
24
|
|
25
25
|
module Async
|
26
|
-
# A
|
26
|
+
# A synchronization primitive, which allows one task to wait for a number of other tasks to complete. It can be used in conjunction with {Semaphore}.
|
27
|
+
# @public Since `stable-v1`.
|
27
28
|
class Barrier
|
29
|
+
# Initialize the barrier.
|
30
|
+
# @parameter parent [Task | Semaphore | Nil] The parent for holding any children tasks.
|
31
|
+
# @public Since `stable-v1`.
|
28
32
|
def initialize(parent: nil)
|
29
33
|
@tasks = []
|
30
34
|
|
@@ -34,10 +38,13 @@ module Async
|
|
34
38
|
# All tasks which have been invoked into the barrier.
|
35
39
|
attr :tasks
|
36
40
|
|
41
|
+
# The number of tasks currently held by the barrier.
|
37
42
|
def size
|
38
43
|
@tasks.size
|
39
44
|
end
|
40
45
|
|
46
|
+
# Execute a child task and add it to the barrier.
|
47
|
+
# @asynchronous Executes the given block concurrently.
|
41
48
|
def async(*arguments, parent: (@parent or Task.current), **options, &block)
|
42
49
|
task = parent.async(*arguments, **options, &block)
|
43
50
|
|
@@ -46,6 +53,8 @@ module Async
|
|
46
53
|
return task
|
47
54
|
end
|
48
55
|
|
56
|
+
# Whether there are any tasks being held by the barrier.
|
57
|
+
# @returns [Boolean]
|
49
58
|
def empty?
|
50
59
|
@tasks.empty?
|
51
60
|
end
|
@@ -60,15 +69,14 @@ module Async
|
|
60
69
|
begin
|
61
70
|
task.wait
|
62
71
|
ensure
|
63
|
-
#
|
64
|
-
|
65
|
-
# Remove the task from the waiting list if it's finished:
|
66
|
-
@tasks.shift if @tasks.first == task
|
67
|
-
end
|
72
|
+
# Remove the task from the waiting list if it's finished:
|
73
|
+
@tasks.shift if @tasks.first == task
|
68
74
|
end
|
69
75
|
end
|
70
76
|
end
|
71
77
|
|
78
|
+
# Stop all tasks held by the barrier.
|
79
|
+
# @asynchronous May wait for tasks to finish executing.
|
72
80
|
def stop
|
73
81
|
# We have to be careful to avoid enumerating tasks while adding/removing to it:
|
74
82
|
tasks = @tasks.dup
|
data/lib/async/clock.rb
CHANGED
@@ -21,6 +21,8 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
module Async
|
24
|
+
# A convenient wrapper around the internal monotonic clock.
|
25
|
+
# @public Since `stable-v1`.
|
24
26
|
class Clock
|
25
27
|
# Get the current elapsed monotonic time.
|
26
28
|
def self.now
|
@@ -28,6 +30,8 @@ module Async
|
|
28
30
|
end
|
29
31
|
|
30
32
|
# Measure the execution of a block of code.
|
33
|
+
# @yields {...} The block to execute.
|
34
|
+
# @returns [Numeric] The total execution time.
|
31
35
|
def self.measure
|
32
36
|
start_time = self.now
|
33
37
|
|
@@ -36,19 +40,25 @@ module Async
|
|
36
40
|
return self.now - start_time
|
37
41
|
end
|
38
42
|
|
43
|
+
# Start measuring elapsed time from now.
|
44
|
+
# @returns [Clock]
|
39
45
|
def self.start
|
40
46
|
self.new.tap(&:start!)
|
41
47
|
end
|
42
48
|
|
49
|
+
# Create a new clock with the initial total time.
|
50
|
+
# @parameter total [Numeric] The initial clock duration.
|
43
51
|
def initialize(total = 0)
|
44
52
|
@total = total
|
45
53
|
@started = nil
|
46
54
|
end
|
47
55
|
|
56
|
+
# Start measuring a duration.
|
48
57
|
def start!
|
49
58
|
@started ||= Clock.now
|
50
59
|
end
|
51
60
|
|
61
|
+
# Stop measuring a duration and append the duration to the current total.
|
52
62
|
def stop!
|
53
63
|
if @started
|
54
64
|
@total += (Clock.now - @started)
|
@@ -58,6 +68,7 @@ module Async
|
|
58
68
|
return @total
|
59
69
|
end
|
60
70
|
|
71
|
+
# The total elapsed time including any current duration.
|
61
72
|
def total
|
62
73
|
total = @total
|
63
74
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
A synchronization primitive, which allows fibers to wait until a particular condition is (edge) triggered. Zero or more fibers can wait on a condition. When the condition is signalled, the fibers will be resumed in order.
|
2
|
+
|
3
|
+
## Example
|
4
|
+
|
5
|
+
~~~ ruby
|
6
|
+
require 'async'
|
7
|
+
|
8
|
+
Sync do
|
9
|
+
condition = Async::Condition.new
|
10
|
+
|
11
|
+
Async do
|
12
|
+
Console.logger.info "Waiting for condition..."
|
13
|
+
value = condition.wait
|
14
|
+
Console.logger.info "Condition was signalled: #{value}"
|
15
|
+
end
|
16
|
+
|
17
|
+
Async do |task|
|
18
|
+
task.sleep(1)
|
19
|
+
Console.logger.info "Signalling condition..."
|
20
|
+
condition.signal("Hello World")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
~~~
|
24
|
+
|
25
|
+
### Output
|
26
|
+
|
27
|
+
~~~
|
28
|
+
0.0s info: Waiting for condition... [ec=0x3c] [pid=47943]
|
29
|
+
1.0s info: Signalling condition... [ec=0x64] [pid=47943]
|
30
|
+
1.0s info: Condition was signalled: Hello World [ec=0x3c] [pid=47943]
|
31
|
+
~~~
|
data/lib/async/condition.rb
CHANGED
@@ -24,43 +24,54 @@ require 'fiber'
|
|
24
24
|
require_relative 'node'
|
25
25
|
|
26
26
|
module Async
|
27
|
-
# A synchronization
|
27
|
+
# A synchronization primitive, which allows fibers to wait until a particular condition is (edge) triggered.
|
28
|
+
# @public Since `stable-v1`.
|
28
29
|
class Condition
|
29
30
|
def initialize
|
30
31
|
@waiting = []
|
31
32
|
end
|
32
33
|
|
34
|
+
Queue = Struct.new(:fiber) do
|
35
|
+
def transfer(*arguments)
|
36
|
+
fiber&.transfer(*arguments)
|
37
|
+
end
|
38
|
+
|
39
|
+
def alive?
|
40
|
+
fiber&.alive?
|
41
|
+
end
|
42
|
+
|
43
|
+
def nullify
|
44
|
+
self.fiber = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private_constant :Queue
|
49
|
+
|
33
50
|
# Queue up the current fiber and wait on yielding the task.
|
34
|
-
# @
|
51
|
+
# @returns [Object]
|
35
52
|
def wait
|
36
|
-
|
37
|
-
@waiting <<
|
38
|
-
|
39
|
-
Task.yield
|
53
|
+
queue = Queue.new(Fiber.current)
|
54
|
+
@waiting << queue
|
40
55
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@waiting.delete(fiber)
|
45
|
-
raise
|
56
|
+
Fiber.scheduler.transfer
|
57
|
+
ensure
|
58
|
+
queue.nullify
|
46
59
|
end
|
47
60
|
|
48
61
|
# Is any fiber waiting on this notification?
|
49
|
-
# @
|
62
|
+
# @returns [Boolean]
|
50
63
|
def empty?
|
51
64
|
@waiting.empty?
|
52
65
|
end
|
53
66
|
|
54
67
|
# Signal to a given task that it should resume operations.
|
55
|
-
# @
|
56
|
-
# @see Task.yield which is responsible for handling value.
|
57
|
-
# @return [void]
|
68
|
+
# @parameter value [Object | Nil] The value to return to the waiting fibers.
|
58
69
|
def signal(value = nil)
|
59
70
|
waiting = @waiting
|
60
71
|
@waiting = []
|
61
72
|
|
62
73
|
waiting.each do |fiber|
|
63
|
-
|
74
|
+
Fiber.scheduler.resume(fiber, value) if fiber.alive?
|
64
75
|
end
|
65
76
|
|
66
77
|
return nil
|
data/lib/async/node.rb
CHANGED
@@ -21,7 +21,7 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
module Async
|
24
|
-
# A double linked list.
|
24
|
+
# A double linked list used for managing tasks.
|
25
25
|
class List
|
26
26
|
def initialize
|
27
27
|
# The list behaves like a list node, so @tail points to the next item (the first one) and head points to the previous item (the last one). This may be slightly confusing but it makes the interface more natural.
|
@@ -121,6 +121,7 @@ module Async
|
|
121
121
|
|
122
122
|
private_constant :List
|
123
123
|
|
124
|
+
# A list of children tasks.
|
124
125
|
class Children < List
|
125
126
|
def initialize
|
126
127
|
super
|
@@ -154,10 +155,10 @@ module Async
|
|
154
155
|
end
|
155
156
|
end
|
156
157
|
|
157
|
-
#
|
158
|
+
# A node in a tree, used for implementing the task hierarchy.
|
158
159
|
class Node
|
159
160
|
# Create a new node in the tree.
|
160
|
-
# @
|
161
|
+
# @parameter parent [Node | Nil] This node will attach to the given parent.
|
161
162
|
def initialize(parent = nil, annotation: nil, transient: false)
|
162
163
|
@parent = nil
|
163
164
|
@children = nil
|
@@ -175,15 +176,21 @@ module Async
|
|
175
176
|
end
|
176
177
|
end
|
177
178
|
|
178
|
-
#
|
179
|
-
|
179
|
+
# @returns [Node] the root node in the hierarchy.
|
180
|
+
def root
|
181
|
+
@parent&.root || self
|
182
|
+
end
|
183
|
+
|
184
|
+
# @private
|
180
185
|
attr_accessor :head
|
186
|
+
|
187
|
+
# @private
|
181
188
|
attr_accessor :tail
|
182
189
|
|
183
|
-
# @
|
190
|
+
# @attribute [Node] The parent node.
|
184
191
|
attr :parent
|
185
192
|
|
186
|
-
# @
|
193
|
+
# @attribute children [Children | Nil] Optional list of children.
|
187
194
|
attr :children
|
188
195
|
|
189
196
|
# A useful identifier for the current node.
|
@@ -215,6 +222,8 @@ module Async
|
|
215
222
|
|
216
223
|
if @annotation
|
217
224
|
"#{@object_name} #{@annotation}"
|
225
|
+
elsif line = self.backtrace(0, 1)&.first
|
226
|
+
"#{@object_name} #{line}"
|
218
227
|
else
|
219
228
|
@object_name
|
220
229
|
end
|
@@ -225,12 +234,14 @@ module Async
|
|
225
234
|
end
|
226
235
|
|
227
236
|
def to_s
|
228
|
-
"\#<#{description}>"
|
237
|
+
"\#<#{self.description}>"
|
229
238
|
end
|
230
239
|
|
240
|
+
alias inspect to_s
|
241
|
+
|
231
242
|
# Change the parent of this node.
|
232
|
-
# @
|
233
|
-
# @
|
243
|
+
# @parameter parent [Node | Nil] the parent to attach to, or nil to detach.
|
244
|
+
# @returns [Node] Itself.
|
234
245
|
def parent=(parent)
|
235
246
|
return if @parent.equal?(parent)
|
236
247
|
|
@@ -263,7 +274,7 @@ module Async
|
|
263
274
|
|
264
275
|
# Whether the node can be consumed safely. By default, checks if the
|
265
276
|
# children set is empty.
|
266
|
-
# @
|
277
|
+
# @returns [Boolean]
|
267
278
|
def finished?
|
268
279
|
@children.nil? || @children.finished?
|
269
280
|
end
|
@@ -293,7 +304,7 @@ module Async
|
|
293
304
|
end
|
294
305
|
|
295
306
|
# Traverse the tree.
|
296
|
-
# @
|
307
|
+
# @yields {|node, level| ...} The node and the level relative to the given root.
|
297
308
|
def traverse(level = 0, &block)
|
298
309
|
yield self, level
|
299
310
|
|
@@ -329,6 +340,10 @@ module Async
|
|
329
340
|
end
|
330
341
|
end
|
331
342
|
|
343
|
+
def stopped?
|
344
|
+
@children.nil?
|
345
|
+
end
|
346
|
+
|
332
347
|
def print_hierarchy(out = $stdout, backtrace: true)
|
333
348
|
self.traverse do |node, level|
|
334
349
|
indent = "\t" * level
|
data/lib/async/notification.rb
CHANGED
@@ -24,13 +24,13 @@ require_relative 'condition'
|
|
24
24
|
|
25
25
|
module Async
|
26
26
|
# A synchronization primitive, which allows fibers to wait until a notification is received. Does not block the task which signals the notification. Waiting tasks are resumed on next iteration of the reactor.
|
27
|
+
# @public Since `stable-v1`.
|
27
28
|
class Notification < Condition
|
28
29
|
# Signal to a given task that it should resume operations.
|
29
|
-
# @return [void]
|
30
30
|
def signal(value = nil, task: Task.current)
|
31
31
|
return if @waiting.empty?
|
32
32
|
|
33
|
-
|
33
|
+
Fiber.scheduler.push Signal.new(@waiting, value)
|
34
34
|
|
35
35
|
@waiting = []
|
36
36
|
|
@@ -42,9 +42,9 @@ module Async
|
|
42
42
|
true
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
45
|
+
def transfer
|
46
46
|
waiting.each do |fiber|
|
47
|
-
fiber.
|
47
|
+
fiber.transfer(value) if fiber.alive?
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
data/lib/async/queue.rb
CHANGED
@@ -24,6 +24,7 @@ require_relative 'notification'
|
|
24
24
|
|
25
25
|
module Async
|
26
26
|
# A queue which allows items to be processed in order.
|
27
|
+
# @public Since `stable-v1`.
|
27
28
|
class Queue < Notification
|
28
29
|
def initialize(parent: nil)
|
29
30
|
super()
|
@@ -42,16 +43,14 @@ module Async
|
|
42
43
|
@items.empty?
|
43
44
|
end
|
44
45
|
|
45
|
-
def
|
46
|
-
@items
|
46
|
+
def enqueue(item)
|
47
|
+
@items.push(item)
|
47
48
|
|
48
49
|
self.signal unless self.empty?
|
49
50
|
end
|
50
51
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
self.signal unless self.empty?
|
52
|
+
def <<(item)
|
53
|
+
enqueue(item)
|
55
54
|
end
|
56
55
|
|
57
56
|
def dequeue
|
@@ -75,6 +74,7 @@ module Async
|
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
77
|
+
# @public Since `stable-v1`.
|
78
78
|
class LimitedQueue < Queue
|
79
79
|
def initialize(limit = 1, **options)
|
80
80
|
super(**options)
|
@@ -86,12 +86,12 @@ module Async
|
|
86
86
|
|
87
87
|
attr :limit
|
88
88
|
|
89
|
-
# @
|
89
|
+
# @returns [Boolean] Whether trying to enqueue an item would block.
|
90
90
|
def limited?
|
91
91
|
@items.size >= @limit
|
92
92
|
end
|
93
93
|
|
94
|
-
def
|
94
|
+
def enqueue item
|
95
95
|
while limited?
|
96
96
|
@full.wait
|
97
97
|
end
|
@@ -99,19 +99,6 @@ module Async
|
|
99
99
|
super
|
100
100
|
end
|
101
101
|
|
102
|
-
def enqueue *items
|
103
|
-
while !items.empty?
|
104
|
-
while limited?
|
105
|
-
@full.wait
|
106
|
-
end
|
107
|
-
|
108
|
-
available = @limit - @items.size
|
109
|
-
@items.concat(items.shift(available))
|
110
|
-
|
111
|
-
self.signal unless self.empty?
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
102
|
def dequeue
|
116
103
|
item = super
|
117
104
|
|