async 2.12.1 → 2.13.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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/condition.rb +7 -2
- data/lib/async/idler.rb +18 -0
- data/lib/async/list.rb +6 -3
- data/lib/async/node.rb +19 -3
- data/lib/async/notification.rb +2 -0
- data/lib/async/queue.rb +66 -15
- data/lib/async/reactor.rb +2 -0
- data/lib/async/scheduler.rb +26 -2
- data/lib/async/task.rb +36 -8
- data/lib/async/variable.rb +16 -0
- data/lib/async/version.rb +1 -1
- data/lib/async/waiter.rb +6 -2
- data/lib/async/wrapper.rb +3 -1
- data/lib/async.rb +1 -0
- data/readme.md +1 -8
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe1866e547e6705255fdce380d3e5532964d0b74c009579f481f3c3fb8086b6c
|
4
|
+
data.tar.gz: 5d3ac4bbbb8579cbcc85944c447dcea2e6b4b2408af9ad3829eace9934067199
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e208dc1b43979431fe3bf36acdcbebb97306bf32ec8471ea9081763d737865e47486dc798d5704ed829b27b16385f48425c4af687eda169f94e36f1c32c8ec86
|
7
|
+
data.tar.gz: d961dafc64289d00b1e99b3245577d5e549365d415e6628cba39af4a5b0d58fc15da468d880fa1796c97351ff83ba2814d073ebbab18aaeaef6a3eb50be26efb
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/condition.rb
CHANGED
@@ -11,6 +11,7 @@ module Async
|
|
11
11
|
# A synchronization primitive, which allows fibers to wait until a particular condition is (edge) triggered.
|
12
12
|
# @public Since `stable-v1`.
|
13
13
|
class Condition
|
14
|
+
# Create a new condition.
|
14
15
|
def initialize
|
15
16
|
@waiting = List.new
|
16
17
|
end
|
@@ -39,12 +40,16 @@ module Async
|
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
|
-
#
|
43
|
-
# @returns [Boolean]
|
43
|
+
# @deprecated Replaced by {#waiting?}
|
44
44
|
def empty?
|
45
45
|
@waiting.empty?
|
46
46
|
end
|
47
47
|
|
48
|
+
# @returns [Boolean] Is any fiber waiting on this notification?
|
49
|
+
def waiting?
|
50
|
+
@waiting.size > 0
|
51
|
+
end
|
52
|
+
|
48
53
|
# Signal to a given task that it should resume operations.
|
49
54
|
# @parameter value [Object | Nil] The value to return to the waiting fibers.
|
50
55
|
def signal(value = nil)
|
data/lib/async/idler.rb
CHANGED
@@ -4,13 +4,28 @@
|
|
4
4
|
# Copyright, 2024, by Samuel Williams.
|
5
5
|
|
6
6
|
module Async
|
7
|
+
# A load balancing mechanism that can be used process work when the system is idle.
|
7
8
|
class Idler
|
9
|
+
# Create a new idler.
|
10
|
+
# @public Since `stable-v2`.
|
11
|
+
#
|
12
|
+
# @parameter maximum_load [Numeric] The maximum load before we start shedding work.
|
13
|
+
# @parameter backoff [Numeric] The initial backoff time, used for delaying work.
|
14
|
+
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
|
8
15
|
def initialize(maximum_load = 0.8, backoff: 0.01, parent: nil)
|
9
16
|
@maximum_load = maximum_load
|
10
17
|
@backoff = backoff
|
11
18
|
@parent = parent
|
12
19
|
end
|
13
20
|
|
21
|
+
# Wait until the system is idle, then execute the given block in a new task.
|
22
|
+
#
|
23
|
+
# @asynchronous Executes the given block concurrently.
|
24
|
+
#
|
25
|
+
# @parameter arguments [Array] The arguments to pass to the block.
|
26
|
+
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
|
27
|
+
# @parameter options [Hash] The options to pass to the task.
|
28
|
+
# @yields {|task| ...} When the system is idle, the block will be executed in a new task.
|
14
29
|
def async(*arguments, parent: (@parent or Task.current), **options, &block)
|
15
30
|
wait
|
16
31
|
|
@@ -18,6 +33,9 @@ module Async
|
|
18
33
|
parent.async(*arguments, **options, &block)
|
19
34
|
end
|
20
35
|
|
36
|
+
# Wait until the system is idle, according to the maximum load specified.
|
37
|
+
#
|
38
|
+
# If the scheduler is overloaded, this method will sleep for an exponentially increasing amount of time.
|
21
39
|
def wait
|
22
40
|
scheduler = Fiber.scheduler
|
23
41
|
backoff = nil
|
data/lib/async/list.rb
CHANGED
@@ -13,7 +13,7 @@ module Async
|
|
13
13
|
@size = 0
|
14
14
|
end
|
15
15
|
|
16
|
-
#
|
16
|
+
# @returns [String] A short summary of the list.
|
17
17
|
def to_s
|
18
18
|
sprintf("#<%s:0x%x size=%d>", self.class.name, object_id, @size)
|
19
19
|
end
|
@@ -36,12 +36,13 @@ module Async
|
|
36
36
|
return items
|
37
37
|
end
|
38
38
|
|
39
|
-
# Points at the end of the list.
|
39
|
+
# @attribute [Node | Nil] Points at the end of the list.
|
40
40
|
attr_accessor :head
|
41
41
|
|
42
|
-
# Points at the start of the list.
|
42
|
+
# @attribute [Node | Nil] Points at the start of the list.
|
43
43
|
attr_accessor :tail
|
44
44
|
|
45
|
+
# @attribute [Integer] The number of nodes in the list.
|
45
46
|
attr :size
|
46
47
|
|
47
48
|
# A callback that is invoked when an item is added to the list.
|
@@ -64,6 +65,7 @@ module Async
|
|
64
65
|
return added(node)
|
65
66
|
end
|
66
67
|
|
68
|
+
# Prepend a node to the start of the list.
|
67
69
|
def prepend(node)
|
68
70
|
if node.head
|
69
71
|
raise ArgumentError, "Node is already in a list!"
|
@@ -224,6 +226,7 @@ module Async
|
|
224
226
|
return nil
|
225
227
|
end
|
226
228
|
|
229
|
+
# Shift the first node off the list, if it is not empty.
|
227
230
|
def shift
|
228
231
|
if node = first
|
229
232
|
remove!(node)
|
data/lib/async/node.rb
CHANGED
@@ -12,6 +12,7 @@ require_relative 'list'
|
|
12
12
|
module Async
|
13
13
|
# A list of children tasks.
|
14
14
|
class Children < List
|
15
|
+
# Create an empty list of children tasks.
|
15
16
|
def initialize
|
16
17
|
super
|
17
18
|
@transient_count = 0
|
@@ -73,7 +74,7 @@ module Async
|
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
76
|
-
# @returns [Node]
|
77
|
+
# @returns [Node] The root node in the hierarchy.
|
77
78
|
def root
|
78
79
|
@parent&.root || self
|
79
80
|
end
|
@@ -87,10 +88,10 @@ module Async
|
|
87
88
|
# @attribute [Node] The parent node.
|
88
89
|
attr :parent
|
89
90
|
|
90
|
-
# @attribute
|
91
|
+
# @attribute [Children | Nil] Optional list of children.
|
91
92
|
attr :children
|
92
93
|
|
93
|
-
# A useful identifier for the current node.
|
94
|
+
# @attribute [String | Nil] A useful identifier for the current node.
|
94
95
|
attr :annotation
|
95
96
|
|
96
97
|
# Whether this node has any children.
|
@@ -109,6 +110,9 @@ module Async
|
|
109
110
|
@transient
|
110
111
|
end
|
111
112
|
|
113
|
+
# Annotate the node with a description.
|
114
|
+
#
|
115
|
+
# @parameter annotation [String] The description to annotate the node with.
|
112
116
|
def annotate(annotation)
|
113
117
|
if block_given?
|
114
118
|
begin
|
@@ -123,6 +127,9 @@ module Async
|
|
123
127
|
end
|
124
128
|
end
|
125
129
|
|
130
|
+
# A description of the node, including the annotation and object name.
|
131
|
+
#
|
132
|
+
# @returns [String] The description of the node.
|
126
133
|
def description
|
127
134
|
@object_name ||= "#{self.class}:#{format '%#018x', object_id}#{@transient ? ' transient' : nil}"
|
128
135
|
|
@@ -135,10 +142,14 @@ module Async
|
|
135
142
|
end
|
136
143
|
end
|
137
144
|
|
145
|
+
# Provides a backtrace for nodes that have an active execution context.
|
146
|
+
#
|
147
|
+
# @returns [Array(Thread::Backtrace::Locations) | Nil] The backtrace of the node, if available.
|
138
148
|
def backtrace(*arguments)
|
139
149
|
nil
|
140
150
|
end
|
141
151
|
|
152
|
+
# @returns [String] A description of the node.
|
142
153
|
def to_s
|
143
154
|
"\#<#{self.description}>"
|
144
155
|
end
|
@@ -255,10 +266,15 @@ module Async
|
|
255
266
|
end
|
256
267
|
end
|
257
268
|
|
269
|
+
# Whether the node has been stopped.
|
258
270
|
def stopped?
|
259
271
|
@children.nil?
|
260
272
|
end
|
261
273
|
|
274
|
+
# Print the hierarchy of the task tree from the given node.
|
275
|
+
#
|
276
|
+
# @parameter out [IO] The output stream to write to.
|
277
|
+
# @parameter backtrace [Boolean] Whether to print the backtrace of each node.
|
262
278
|
def print_hierarchy(out = $stdout, backtrace: true)
|
263
279
|
self.traverse do |node, level|
|
264
280
|
indent = "\t" * level
|
data/lib/async/notification.rb
CHANGED
data/lib/async/queue.rb
CHANGED
@@ -9,68 +9,104 @@ require_relative 'notification'
|
|
9
9
|
|
10
10
|
module Async
|
11
11
|
# A queue which allows items to be processed in order.
|
12
|
+
#
|
13
|
+
# It has a compatible interface with {Notification} and {Condition}, except that it's multi-value.
|
14
|
+
#
|
12
15
|
# @public Since `stable-v1`.
|
13
|
-
class Queue
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
class Queue
|
17
|
+
# Create a new queue.
|
18
|
+
#
|
19
|
+
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
|
20
|
+
# @parameter available [Notification] The notification to use for signaling when items are available.
|
21
|
+
def initialize(parent: nil, available: Notification.new)
|
17
22
|
@items = []
|
18
23
|
@parent = parent
|
24
|
+
@available = available
|
19
25
|
end
|
20
26
|
|
27
|
+
# @attribute [Array] The items in the queue.
|
21
28
|
attr :items
|
22
29
|
|
30
|
+
# @returns [Integer] The number of items in the queue.
|
23
31
|
def size
|
24
32
|
@items.size
|
25
33
|
end
|
26
|
-
|
34
|
+
|
35
|
+
# @returns [Boolean] Whether the queue is empty.
|
27
36
|
def empty?
|
28
37
|
@items.empty?
|
29
38
|
end
|
30
39
|
|
40
|
+
# Add an item to the queue.
|
31
41
|
def <<(item)
|
32
42
|
@items << item
|
33
43
|
|
34
|
-
|
44
|
+
@available.signal unless self.empty?
|
35
45
|
end
|
36
46
|
|
47
|
+
# Add multiple items to the queue.
|
37
48
|
def enqueue(*items)
|
38
49
|
@items.concat(items)
|
39
50
|
|
40
|
-
|
51
|
+
@available.signal unless self.empty?
|
41
52
|
end
|
42
53
|
|
54
|
+
# Remove and return the next item from the queue.
|
43
55
|
def dequeue
|
44
56
|
while @items.empty?
|
45
|
-
|
57
|
+
@available.wait
|
46
58
|
end
|
47
59
|
|
48
60
|
@items.shift
|
49
61
|
end
|
50
62
|
|
51
|
-
|
63
|
+
# Process each item in the queue.
|
64
|
+
#
|
65
|
+
# @asynchronous Executes the given block concurrently for each item.
|
66
|
+
#
|
67
|
+
# @parameter arguments [Array] The arguments to pass to the block.
|
68
|
+
# @parameter parent [Interface(:async) | Nil] The parent task to use for async operations.
|
69
|
+
# @parameter options [Hash] The options to pass to the task.
|
70
|
+
# @yields {|task| ...} When the system is idle, the block will be executed in a new task.
|
71
|
+
def async(parent: (@parent or Task.current), **options, &block)
|
52
72
|
while item = self.dequeue
|
53
|
-
parent.async(item, &block)
|
73
|
+
parent.async(item, **options, &block)
|
54
74
|
end
|
55
75
|
end
|
56
76
|
|
77
|
+
# Enumerate each item in the queue.
|
57
78
|
def each
|
58
79
|
while item = self.dequeue
|
59
80
|
yield item
|
60
81
|
end
|
61
82
|
end
|
83
|
+
|
84
|
+
# Signal the queue with a value, the same as {#enqueue}.
|
85
|
+
def signal(value)
|
86
|
+
self.enqueue(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Wait for an item to be available, the same as {#dequeue}.
|
90
|
+
def wait
|
91
|
+
self.dequeue
|
92
|
+
end
|
62
93
|
end
|
63
94
|
|
95
|
+
# A queue which limits the number of items that can be enqueued.
|
64
96
|
# @public Since `stable-v1`.
|
65
97
|
class LimitedQueue < Queue
|
66
|
-
|
98
|
+
# Create a new limited queue.
|
99
|
+
#
|
100
|
+
# @parameter limit [Integer] The maximum number of items that can be enqueued.
|
101
|
+
# @parameter full [Notification] The notification to use for signaling when the queue is full.
|
102
|
+
def initialize(limit = 1, full: Notification.new, **options)
|
67
103
|
super(**options)
|
68
104
|
|
69
105
|
@limit = limit
|
70
|
-
|
71
|
-
@full = Notification.new
|
106
|
+
@full = full
|
72
107
|
end
|
73
108
|
|
109
|
+
# @attribute [Integer] The maximum number of items that can be enqueued.
|
74
110
|
attr :limit
|
75
111
|
|
76
112
|
# @returns [Boolean] Whether trying to enqueue an item would block.
|
@@ -78,6 +114,11 @@ module Async
|
|
78
114
|
@items.size >= @limit
|
79
115
|
end
|
80
116
|
|
117
|
+
# Add an item to the queue.
|
118
|
+
#
|
119
|
+
# If the queue is full, this method will block until there is space available.
|
120
|
+
#
|
121
|
+
# @parameter item [Object] The item to add to the queue.
|
81
122
|
def <<(item)
|
82
123
|
while limited?
|
83
124
|
@full.wait
|
@@ -86,7 +127,12 @@ module Async
|
|
86
127
|
super
|
87
128
|
end
|
88
129
|
|
89
|
-
|
130
|
+
# Add multiple items to the queue.
|
131
|
+
#
|
132
|
+
# If the queue is full, this method will block until there is space available.
|
133
|
+
#
|
134
|
+
# @parameter items [Array] The items to add to the queue.
|
135
|
+
def enqueue(*items)
|
90
136
|
while !items.empty?
|
91
137
|
while limited?
|
92
138
|
@full.wait
|
@@ -95,10 +141,15 @@ module Async
|
|
95
141
|
available = @limit - @items.size
|
96
142
|
@items.concat(items.shift(available))
|
97
143
|
|
98
|
-
|
144
|
+
@available.signal unless self.empty?
|
99
145
|
end
|
100
146
|
end
|
101
147
|
|
148
|
+
# Remove and return the next item from the queue.
|
149
|
+
#
|
150
|
+
# If the queue is empty, this method will block until an item is available.
|
151
|
+
#
|
152
|
+
# @returns [Object] The next item in the queue.
|
102
153
|
def dequeue
|
103
154
|
item = super
|
104
155
|
|
data/lib/async/reactor.rb
CHANGED
@@ -15,12 +15,14 @@ module Async
|
|
15
15
|
Async(...)
|
16
16
|
end
|
17
17
|
|
18
|
+
# Initialize the reactor and assign it to the current Fiber scheduler.
|
18
19
|
def initialize(...)
|
19
20
|
super
|
20
21
|
|
21
22
|
Fiber.set_scheduler(self)
|
22
23
|
end
|
23
24
|
|
25
|
+
# Close the reactor and remove it from the current Fiber scheduler.
|
24
26
|
def scheduler_close
|
25
27
|
self.close
|
26
28
|
end
|
data/lib/async/scheduler.rb
CHANGED
@@ -16,7 +16,11 @@ require 'resolv'
|
|
16
16
|
module Async
|
17
17
|
# Handles scheduling of fibers. Implements the fiber scheduler interface.
|
18
18
|
class Scheduler < Node
|
19
|
+
# Raised when an operation is attempted on a closed scheduler.
|
19
20
|
class ClosedError < RuntimeError
|
21
|
+
# Create a new error.
|
22
|
+
#
|
23
|
+
# @parameter message [String] The error message.
|
20
24
|
def initialize(message = "Scheduler is closed!")
|
21
25
|
super
|
22
26
|
end
|
@@ -28,6 +32,11 @@ module Async
|
|
28
32
|
true
|
29
33
|
end
|
30
34
|
|
35
|
+
# Create a new scheduler.
|
36
|
+
#
|
37
|
+
# @public Since `stable-v1`.
|
38
|
+
# @parameter parent [Node | Nil] The parent node to use for task hierarchy.
|
39
|
+
# @parameter selector [IO::Event::Selector] The selector to use for event handling.
|
31
40
|
def initialize(parent = nil, selector: nil)
|
32
41
|
super(parent)
|
33
42
|
|
@@ -63,6 +72,9 @@ module Async
|
|
63
72
|
end
|
64
73
|
end
|
65
74
|
|
75
|
+
# Invoked when the fiber scheduler is being closed.
|
76
|
+
#
|
77
|
+
# Executes the run loop until all tasks are finished, then closes the scheduler.
|
66
78
|
def scheduler_close
|
67
79
|
# If the execution context (thread) was handling an exception, we want to exit as quickly as possible:
|
68
80
|
unless $!
|
@@ -79,6 +91,7 @@ module Async
|
|
79
91
|
end
|
80
92
|
end
|
81
93
|
|
94
|
+
# Terminate all child tasks and close the scheduler.
|
82
95
|
# @public Since `stable-v1`.
|
83
96
|
def close
|
84
97
|
# It's critical to stop all tasks. Otherwise they might be holding on to resources which are never closed/released correctly.
|
@@ -108,6 +121,7 @@ module Async
|
|
108
121
|
@selector.nil?
|
109
122
|
end
|
110
123
|
|
124
|
+
# @returns [String] A description of the scheduler.
|
111
125
|
def to_s
|
112
126
|
"\#<#{self.description} #{@children&.size || 0} children (#{stopped? ? 'stopped' : 'running'})>"
|
113
127
|
end
|
@@ -135,10 +149,20 @@ module Async
|
|
135
149
|
@selector.push(fiber)
|
136
150
|
end
|
137
151
|
|
138
|
-
|
139
|
-
|
152
|
+
# Raise an exception on a specified fiber with the given arguments.
|
153
|
+
#
|
154
|
+
# This internally schedules the current fiber to be ready, before raising the exception, so that it will later resume execution.
|
155
|
+
#
|
156
|
+
# @parameter fiber [Fiber] The fiber to raise the exception on.
|
157
|
+
# @parameter *arguments [Array] The arguments to pass to the fiber.
|
158
|
+
def raise(...)
|
159
|
+
@selector.raise(...)
|
140
160
|
end
|
141
161
|
|
162
|
+
# Resume execution of the specified fiber.
|
163
|
+
#
|
164
|
+
# @parameter fiber [Fiber] The fiber to resume.
|
165
|
+
# @parameter arguments [Array] The arguments to pass to the fiber.
|
142
166
|
def resume(fiber, *arguments)
|
143
167
|
@selector.resume(fiber, *arguments)
|
144
168
|
end
|
data/lib/async/task.rb
CHANGED
@@ -16,15 +16,21 @@ require_relative 'condition'
|
|
16
16
|
module Async
|
17
17
|
# Raised when a task is explicitly stopped.
|
18
18
|
class Stop < Exception
|
19
|
+
# Used to defer stopping the current task until later.
|
19
20
|
class Later
|
21
|
+
# Create a new stop later operation.
|
22
|
+
#
|
23
|
+
# @parameter task [Task] The task to stop later.
|
20
24
|
def initialize(task)
|
21
25
|
@task = task
|
22
26
|
end
|
23
27
|
|
28
|
+
# @returns [Boolean] Whether the task is alive.
|
24
29
|
def alive?
|
25
30
|
true
|
26
31
|
end
|
27
32
|
|
33
|
+
# Transfer control to the operation - this will stop the task.
|
28
34
|
def transfer
|
29
35
|
@task.stop
|
30
36
|
end
|
@@ -34,6 +40,9 @@ module Async
|
|
34
40
|
# Raised if a timeout occurs on a specific Fiber. Handled gracefully by `Task`.
|
35
41
|
# @public Since `stable-v1`.
|
36
42
|
class TimeoutError < StandardError
|
43
|
+
# Create a new timeout error.
|
44
|
+
#
|
45
|
+
# @parameter message [String] The error message.
|
37
46
|
def initialize(message = "execution expired")
|
38
47
|
super
|
39
48
|
end
|
@@ -41,7 +50,11 @@ module Async
|
|
41
50
|
|
42
51
|
# @public Since `stable-v1`.
|
43
52
|
class Task < Node
|
53
|
+
# Raised when a child task is created within a task that has finished execution.
|
44
54
|
class FinishedError < RuntimeError
|
55
|
+
# Create a new finished error.
|
56
|
+
#
|
57
|
+
# @parameter message [String] The error message.
|
45
58
|
def initialize(message = "Cannot create child task within a task that has finished execution!")
|
46
59
|
super
|
47
60
|
end
|
@@ -72,14 +85,21 @@ module Async
|
|
72
85
|
@defer_stop = nil
|
73
86
|
end
|
74
87
|
|
88
|
+
# @returns [Scheduler] The scheduler for this task.
|
75
89
|
def reactor
|
76
90
|
self.root
|
77
91
|
end
|
78
92
|
|
93
|
+
# @returns [Array(Thread::Backtrace::Location) | Nil] The backtrace of the task, if available.
|
79
94
|
def backtrace(*arguments)
|
80
95
|
@fiber&.backtrace(*arguments)
|
81
96
|
end
|
82
97
|
|
98
|
+
# Annotate the task with a description.
|
99
|
+
#
|
100
|
+
# This will internally try to annotate the fiber if it is running, otherwise it will annotate the task itself.
|
101
|
+
#
|
102
|
+
# @parameter annotation [String] The description to annotate the task with.
|
83
103
|
def annotate(annotation, &block)
|
84
104
|
if @fiber
|
85
105
|
@fiber.annotate(annotation, &block)
|
@@ -88,6 +108,7 @@ module Async
|
|
88
108
|
end
|
89
109
|
end
|
90
110
|
|
111
|
+
# @returns [Object] The annotation of the task.
|
91
112
|
def annotation
|
92
113
|
if @fiber
|
93
114
|
@fiber.annotation
|
@@ -96,6 +117,7 @@ module Async
|
|
96
117
|
end
|
97
118
|
end
|
98
119
|
|
120
|
+
# @returns [String] A description of the task and it's current status.
|
99
121
|
def to_s
|
100
122
|
"\#<#{self.description} (#{@status})>"
|
101
123
|
end
|
@@ -115,10 +137,10 @@ module Async
|
|
115
137
|
Fiber.scheduler.yield
|
116
138
|
end
|
117
139
|
|
118
|
-
# @
|
140
|
+
# @attribute [Fiber] The fiber which is being used for the execution of this task.
|
119
141
|
attr :fiber
|
120
142
|
|
121
|
-
# Whether the internal fiber is alive, i.e. it
|
143
|
+
# @returns [Boolean] Whether the internal fiber is alive, i.e. it is actively executing.
|
122
144
|
def alive?
|
123
145
|
@fiber&.alive?
|
124
146
|
end
|
@@ -130,32 +152,34 @@ module Async
|
|
130
152
|
super && @block.nil? && @fiber.nil?
|
131
153
|
end
|
132
154
|
|
133
|
-
# Whether the task is running.
|
134
|
-
# @returns [Boolean]
|
155
|
+
# @returns [Boolean] Whether the task is running.
|
135
156
|
def running?
|
136
157
|
@status == :running
|
137
158
|
end
|
138
159
|
|
160
|
+
# @returns [Boolean] Whether the task failed with an exception.
|
139
161
|
def failed?
|
140
162
|
@status == :failed
|
141
163
|
end
|
142
164
|
|
143
|
-
#
|
165
|
+
# @returns [Boolean] Whether the task has been stopped.
|
144
166
|
def stopped?
|
145
167
|
@status == :stopped
|
146
168
|
end
|
147
169
|
|
148
|
-
#
|
170
|
+
# @returns [Boolean] Whether the task has completed execution and generated a result.
|
149
171
|
def completed?
|
150
172
|
@status == :completed
|
151
173
|
end
|
152
174
|
|
153
175
|
alias complete? completed?
|
154
176
|
|
155
|
-
# @
|
177
|
+
# @attribute [Symbol] The status of the execution of the fiber, one of `:initialized`, `:running`, `:complete`, `:stopped` or `:failed`.
|
156
178
|
attr :status
|
157
179
|
|
158
180
|
# Begin the execution of the task.
|
181
|
+
#
|
182
|
+
# @raises [RuntimeError] If the task is already running.
|
159
183
|
def run(*arguments)
|
160
184
|
if @status == :initialized
|
161
185
|
@status = :running
|
@@ -169,6 +193,9 @@ module Async
|
|
169
193
|
end
|
170
194
|
|
171
195
|
# Run an asynchronous task as a child of the current task.
|
196
|
+
#
|
197
|
+
# @raises [FinishedError] If the task has already finished.
|
198
|
+
# @returns [Task] The child task.
|
172
199
|
def async(*arguments, **options, &block)
|
173
200
|
raise FinishedError if self.finished?
|
174
201
|
|
@@ -293,11 +320,12 @@ module Async
|
|
293
320
|
end
|
294
321
|
|
295
322
|
# Check if there is a task defined for the current fiber.
|
296
|
-
# @returns [
|
323
|
+
# @returns [Interface(:async) | Nil]
|
297
324
|
def self.current?
|
298
325
|
Thread.current[:async_task]
|
299
326
|
end
|
300
327
|
|
328
|
+
# @returns [Boolean] Whether this task is the currently executing task.
|
301
329
|
def current?
|
302
330
|
Fiber.current.equal?(@fiber)
|
303
331
|
end
|
data/lib/async/variable.rb
CHANGED
@@ -6,12 +6,21 @@
|
|
6
6
|
require_relative 'condition'
|
7
7
|
|
8
8
|
module Async
|
9
|
+
# A synchronization primitive that allows one task to wait for another task to resolve a value.
|
9
10
|
class Variable
|
11
|
+
# Create a new variable.
|
12
|
+
#
|
13
|
+
# @parameter condition [Condition] The condition to use for synchronization.
|
10
14
|
def initialize(condition = Condition.new)
|
11
15
|
@condition = condition
|
12
16
|
@value = nil
|
13
17
|
end
|
14
18
|
|
19
|
+
# Resolve the value.
|
20
|
+
#
|
21
|
+
# Signals all waiting tasks.
|
22
|
+
#
|
23
|
+
# @parameter value [Object] The value to resolve.
|
15
24
|
def resolve(value = true)
|
16
25
|
@value = value
|
17
26
|
condition = @condition
|
@@ -22,15 +31,22 @@ module Async
|
|
22
31
|
condition.signal(value)
|
23
32
|
end
|
24
33
|
|
34
|
+
# Whether the value has been resolved.
|
35
|
+
#
|
36
|
+
# @returns [Boolean] Whether the value has been resolved.
|
25
37
|
def resolved?
|
26
38
|
@condition.nil?
|
27
39
|
end
|
28
40
|
|
41
|
+
# Wait for the value to be resolved.
|
42
|
+
#
|
43
|
+
# @returns [Object] The resolved value.
|
29
44
|
def value
|
30
45
|
@condition&.wait
|
31
46
|
return @value
|
32
47
|
end
|
33
48
|
|
49
|
+
# Alias for {#value}.
|
34
50
|
def wait
|
35
51
|
self.value
|
36
52
|
end
|
data/lib/async/version.rb
CHANGED
data/lib/async/waiter.rb
CHANGED
@@ -6,6 +6,10 @@
|
|
6
6
|
module Async
|
7
7
|
# A composable synchronization primitive, which allows one task to wait for a number of other tasks to complete. It can be used in conjunction with {Semaphore} and/or {Barrier}.
|
8
8
|
class Waiter
|
9
|
+
# Create a waiter instance.
|
10
|
+
#
|
11
|
+
# @parameter parent [Interface(:async) | Nil] The parent task to use for asynchronous operations.
|
12
|
+
# @parameter finished [Async::Condition] The condition to signal when a task completes.
|
9
13
|
def initialize(parent: nil, finished: Async::Condition.new)
|
10
14
|
@finished = finished
|
11
15
|
@done = []
|
@@ -15,8 +19,8 @@ module Async
|
|
15
19
|
|
16
20
|
# Execute a child task and add it to the waiter.
|
17
21
|
# @asynchronous Executes the given block concurrently.
|
18
|
-
def async(parent: (@parent or Task.current), &block)
|
19
|
-
parent.async do |task|
|
22
|
+
def async(parent: (@parent or Task.current), **options, &block)
|
23
|
+
parent.async(**options) do |task|
|
20
24
|
yield(task)
|
21
25
|
ensure
|
22
26
|
@done << task
|
data/lib/async/wrapper.rb
CHANGED
@@ -23,6 +23,7 @@ module Async
|
|
23
23
|
|
24
24
|
attr_accessor :reactor
|
25
25
|
|
26
|
+
# Dup the underlying IO.
|
26
27
|
def dup
|
27
28
|
self.class.new(@io.dup)
|
28
29
|
end
|
@@ -51,11 +52,12 @@ module Async
|
|
51
52
|
@io.to_io.wait(::IO::READABLE|::IO::WRITABLE|::IO::PRIORITY, timeout) or raise TimeoutError
|
52
53
|
end
|
53
54
|
|
54
|
-
# Close the
|
55
|
+
# Close the underlying IO.
|
55
56
|
def close
|
56
57
|
@io.close
|
57
58
|
end
|
58
59
|
|
60
|
+
# Whether the underlying IO is closed.
|
59
61
|
def closed?
|
60
62
|
@io.closed?
|
61
63
|
end
|
data/lib/async.rb
CHANGED
data/readme.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# 
|
2
2
|
|
3
3
|
Async is a composable asynchronous I/O framework for Ruby based on [io-event](https://github.com/socketry/io-event).
|
4
4
|
|
@@ -54,16 +54,9 @@ This project is governed by the [Contributor Covenant](https://www.contributor-c
|
|
54
54
|
|
55
55
|
## See Also
|
56
56
|
|
57
|
-
- [async-io](https://github.com/socketry/async-io) — Asynchronous networking and sockets.
|
58
57
|
- [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client/server.
|
59
|
-
- [async-process](https://github.com/socketry/async-process) — Asynchronous process spawning/waiting.
|
60
58
|
- [async-websocket](https://github.com/socketry/async-websocket) — Asynchronous client and server websockets.
|
61
59
|
- [async-dns](https://github.com/socketry/async-dns) — Asynchronous DNS resolver and server.
|
62
|
-
- [async-rspec](https://github.com/socketry/async-rspec) — Shared contexts for running async specs.
|
63
|
-
|
64
|
-
### Projects Using Async
|
65
|
-
|
66
|
-
- [ciri](https://github.com/ciri-ethereum/ciri) — An Ethereum implementation written in Ruby.
|
67
60
|
- [falcon](https://github.com/socketry/falcon) — A rack compatible server built on top of `async-http`.
|
68
61
|
- [rubydns](https://github.com/ioquatix/rubydns) — An easy to use Ruby DNS server.
|
69
62
|
- [slack-ruby-bot](https://github.com/slack-ruby/slack-ruby-bot) — A client for making slack bots.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -62,7 +62,7 @@ cert_chain:
|
|
62
62
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
63
63
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
64
64
|
-----END CERTIFICATE-----
|
65
|
-
date: 2024-
|
65
|
+
date: 2024-07-12 00:00:00.000000000 Z
|
66
66
|
dependencies:
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: console
|
@@ -172,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
172
|
- !ruby/object:Gem::Version
|
173
173
|
version: '0'
|
174
174
|
requirements: []
|
175
|
-
rubygems_version: 3.5.
|
175
|
+
rubygems_version: 3.5.9
|
176
176
|
signing_key:
|
177
177
|
specification_version: 4
|
178
178
|
summary: A concurrency framework for Ruby.
|
metadata.gz.sig
CHANGED
Binary file
|