async 2.21.0 → 2.21.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
- checksums.yaml.gz.sig +0 -0
- data/lib/async/limited_queue.rb +7 -0
- data/lib/async/queue.rb +7 -3
- data/lib/async/scheduler.rb +24 -21
- data/lib/async/task.rb +4 -1
- data/lib/async/variable.rb +8 -2
- data/lib/async/version.rb +1 -1
- data/lib/async/worker_pool.rb +182 -0
- data/lib/traces/provider/async/task.rb +7 -3
- data/readme.md +5 -0
- data/releases.md +14 -0
- data.tar.gz.sig +0 -0
- metadata +5 -8
- 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: '0821852f6bb8e8b82506934a6ad8e921270f121636453303a2fd6409cacd83e2'
|
4
|
+
data.tar.gz: 56992a10ff23bcba8dfa535f7c134ac2870a10dd62f7776ea9e109d5e14e0b05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17791af587852dbb46ab3cb6b5584c10d7073997d1ba21eabed248fe1dc6a9107902ac34cf8741efacdfa01e87d57aa515f22635310fa35697ee56acd5cd03ee
|
7
|
+
data.tar.gz: e0a7fc1d5cd13632ceaa93e9ccb41ff89ec38648d8ca6d5eb2831ea39cb014d2fb8046c3226d94d7101a5d289cecaff338fcf65c440627ae76cdd44a93e624a6
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/queue.rb
CHANGED
@@ -45,7 +45,9 @@ module Async
|
|
45
45
|
end
|
46
46
|
|
47
47
|
# Compatibility with {::Queue#push}.
|
48
|
-
|
48
|
+
def <<(item)
|
49
|
+
self.push(item)
|
50
|
+
end
|
49
51
|
|
50
52
|
# Add multiple items to the queue.
|
51
53
|
def enqueue(*items)
|
@@ -64,7 +66,9 @@ module Async
|
|
64
66
|
end
|
65
67
|
|
66
68
|
# Compatibility with {::Queue#pop}.
|
67
|
-
|
69
|
+
def pop
|
70
|
+
self.dequeue
|
71
|
+
end
|
68
72
|
|
69
73
|
# Process each item in the queue.
|
70
74
|
#
|
@@ -125,7 +129,7 @@ module Async
|
|
125
129
|
# If the queue is full, this method will block until there is space available.
|
126
130
|
#
|
127
131
|
# @parameter item [Object] The item to add to the queue.
|
128
|
-
def
|
132
|
+
def push(item)
|
129
133
|
while limited?
|
130
134
|
@full.wait
|
131
135
|
end
|
data/lib/async/scheduler.rb
CHANGED
@@ -7,6 +7,7 @@
|
|
7
7
|
|
8
8
|
require_relative "clock"
|
9
9
|
require_relative "task"
|
10
|
+
require_relative "worker_pool"
|
10
11
|
|
11
12
|
require "io/event"
|
12
13
|
|
@@ -16,6 +17,10 @@ require "resolv"
|
|
16
17
|
module Async
|
17
18
|
# Handles scheduling of fibers. Implements the fiber scheduler interface.
|
18
19
|
class Scheduler < Node
|
20
|
+
DEFAULT_WORKER_POOL = ENV.fetch("ASYNC_SCHEDULER_DEFAULT_WORKER_POOL", nil).then do |value|
|
21
|
+
value == "true" ? true : nil
|
22
|
+
end
|
23
|
+
|
19
24
|
# Raised when an operation is attempted on a closed scheduler.
|
20
25
|
class ClosedError < RuntimeError
|
21
26
|
# Create a new error.
|
@@ -37,7 +42,7 @@ module Async
|
|
37
42
|
# @public Since *Async v1*.
|
38
43
|
# @parameter parent [Node | Nil] The parent node to use for task hierarchy.
|
39
44
|
# @parameter selector [IO::Event::Selector] The selector to use for event handling.
|
40
|
-
def initialize(parent = nil, selector: nil)
|
45
|
+
def initialize(parent = nil, selector: nil, worker_pool: DEFAULT_WORKER_POOL)
|
41
46
|
super(parent)
|
42
47
|
|
43
48
|
@selector = selector || ::IO::Event::Selector.new(Fiber.current)
|
@@ -49,6 +54,15 @@ module Async
|
|
49
54
|
@idle_time = 0.0
|
50
55
|
|
51
56
|
@timers = ::IO::Event::Timers.new
|
57
|
+
if worker_pool == true
|
58
|
+
@worker_pool = WorkerPool.new
|
59
|
+
else
|
60
|
+
@worker_pool = worker_pool
|
61
|
+
end
|
62
|
+
|
63
|
+
if @worker_pool
|
64
|
+
self.singleton_class.prepend(WorkerPool::BlockingOperationWait)
|
65
|
+
end
|
52
66
|
end
|
53
67
|
|
54
68
|
# Compute the scheduler load according to the busy and idle times that are updated by the run loop.
|
@@ -112,6 +126,11 @@ module Async
|
|
112
126
|
|
113
127
|
selector&.close
|
114
128
|
|
129
|
+
worker_pool = @worker_pool
|
130
|
+
@worker_pool = nil
|
131
|
+
|
132
|
+
worker_pool&.close
|
133
|
+
|
115
134
|
consume
|
116
135
|
end
|
117
136
|
|
@@ -169,8 +188,11 @@ module Async
|
|
169
188
|
|
170
189
|
# Invoked when a fiber tries to perform a blocking operation which cannot continue. A corresponding call {unblock} must be performed to allow this fiber to continue.
|
171
190
|
#
|
172
|
-
|
191
|
+
# @public Since *Async v2*.
|
173
192
|
# @asynchronous May only be called on same thread as fiber scheduler.
|
193
|
+
#
|
194
|
+
# @parameter blocker [Object] The object that is blocking the fiber.
|
195
|
+
# @parameter timeout [Float | Nil] The maximum time to block, or if nil, indefinitely.
|
174
196
|
def block(blocker, timeout)
|
175
197
|
# $stderr.puts "block(#{blocker}, #{Fiber.current}, #{timeout})"
|
176
198
|
fiber = Fiber.current
|
@@ -338,25 +360,6 @@ module Async
|
|
338
360
|
return @selector.process_wait(Fiber.current, pid, flags)
|
339
361
|
end
|
340
362
|
|
341
|
-
# Wait for the given work to be executed.
|
342
|
-
#
|
343
|
-
# @public Since *Async v2.19* and *Ruby v3.4*.
|
344
|
-
# @asynchronous May be non-blocking.
|
345
|
-
#
|
346
|
-
# @parameter work [Proc] The work to execute on a background thread.
|
347
|
-
# @returns [Object] The result of the work.
|
348
|
-
def blocking_operation_wait(work)
|
349
|
-
thread = Thread.new(&work)
|
350
|
-
|
351
|
-
result = thread.join
|
352
|
-
|
353
|
-
thread = nil
|
354
|
-
|
355
|
-
return result
|
356
|
-
ensure
|
357
|
-
thread&.kill
|
358
|
-
end
|
359
|
-
|
360
363
|
# Run one iteration of the event loop.
|
361
364
|
#
|
362
365
|
# When terminating the event loop, we already know we are finished. So we don't need to check the task tree. This is a logical requirement because `run_once` ignores transient tasks. For example, a single top level transient task is not enough to keep the reactor running, but during termination we must still process it in order to terminate child tasks.
|
data/lib/async/task.rb
CHANGED
@@ -181,7 +181,10 @@ module Async
|
|
181
181
|
@status == :completed
|
182
182
|
end
|
183
183
|
|
184
|
-
|
184
|
+
# Alias for {#completed?}.
|
185
|
+
def complete?
|
186
|
+
self.completed?
|
187
|
+
end
|
185
188
|
|
186
189
|
# @attribute [Symbol] The status of the execution of the task, one of `:initialized`, `:running`, `:complete`, `:stopped` or `:failed`.
|
187
190
|
attr :status
|
data/lib/async/variable.rb
CHANGED
@@ -31,7 +31,10 @@ module Async
|
|
31
31
|
condition.signal(value)
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
# Alias for {#resolve}.
|
35
|
+
def value=(value)
|
36
|
+
self.resolve(value)
|
37
|
+
end
|
35
38
|
|
36
39
|
# Whether the value has been resolved.
|
37
40
|
#
|
@@ -48,6 +51,9 @@ module Async
|
|
48
51
|
return @value
|
49
52
|
end
|
50
53
|
|
51
|
-
|
54
|
+
# Alias for {#wait}.
|
55
|
+
def value
|
56
|
+
self.wait
|
57
|
+
end
|
52
58
|
end
|
53
59
|
end
|
data/lib/async/version.rb
CHANGED
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
require "etc"
|
7
|
+
|
8
|
+
module Async
|
9
|
+
# A simple work pool that offloads work to a background thread.
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
class WorkerPool
|
13
|
+
# Used to augment the scheduler to add support for blocking operations.
|
14
|
+
module BlockingOperationWait
|
15
|
+
# Wait for the given work to be executed.
|
16
|
+
#
|
17
|
+
# @public Since *Async v2.19* and *Ruby v3.4*.
|
18
|
+
# @asynchronous May be non-blocking.
|
19
|
+
#
|
20
|
+
# @parameter work [Proc] The work to execute on a background thread.
|
21
|
+
# @returns [Object] The result of the work.
|
22
|
+
def blocking_operation_wait(work)
|
23
|
+
@worker_pool.call(work)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Execute the given work in a background thread.
|
28
|
+
class Promise
|
29
|
+
# Create a new promise.
|
30
|
+
#
|
31
|
+
# @parameter work [Proc] The work to be done.
|
32
|
+
def initialize(work)
|
33
|
+
@work = work
|
34
|
+
@state = :pending
|
35
|
+
@value = nil
|
36
|
+
@guard = ::Mutex.new
|
37
|
+
@condition = ::ConditionVariable.new
|
38
|
+
@thread = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Execute the work and resolve the promise.
|
42
|
+
def call
|
43
|
+
work = nil
|
44
|
+
|
45
|
+
@guard.synchronize do
|
46
|
+
@thread = ::Thread.current
|
47
|
+
|
48
|
+
return unless work = @work
|
49
|
+
end
|
50
|
+
|
51
|
+
resolve(work.call)
|
52
|
+
rescue Exception => error
|
53
|
+
reject(error)
|
54
|
+
end
|
55
|
+
|
56
|
+
private def resolve(value)
|
57
|
+
@guard.synchronize do
|
58
|
+
@work = nil
|
59
|
+
@thread = nil
|
60
|
+
@value = value
|
61
|
+
@state = :resolved
|
62
|
+
@condition.broadcast
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private def reject(error)
|
67
|
+
@guard.synchronize do
|
68
|
+
@work = nil
|
69
|
+
@thread = nil
|
70
|
+
@value = error
|
71
|
+
@state = :failed
|
72
|
+
@condition.broadcast
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Cancel the work and raise an exception in the background thread.
|
77
|
+
def cancel
|
78
|
+
return unless @work
|
79
|
+
|
80
|
+
@guard.synchronize do
|
81
|
+
@work = nil
|
82
|
+
@state = :cancelled
|
83
|
+
@thread&.raise(Interrupt)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Wait for the work to be done.
|
88
|
+
#
|
89
|
+
# @returns [Object] The result of the work.
|
90
|
+
def wait
|
91
|
+
@guard.synchronize do
|
92
|
+
while @state == :pending
|
93
|
+
@condition.wait(@guard)
|
94
|
+
end
|
95
|
+
|
96
|
+
if @state == :failed
|
97
|
+
raise @value
|
98
|
+
else
|
99
|
+
return @value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# A background worker thread.
|
106
|
+
class Worker
|
107
|
+
# Create a new worker.
|
108
|
+
def initialize
|
109
|
+
@work = ::Thread::Queue.new
|
110
|
+
@thread = ::Thread.new(&method(:run))
|
111
|
+
end
|
112
|
+
|
113
|
+
# Execute work until the queue is closed.
|
114
|
+
def run
|
115
|
+
while work = @work.pop
|
116
|
+
work.call
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Close the worker thread.
|
121
|
+
def close
|
122
|
+
if thread = @thread
|
123
|
+
@thread = nil
|
124
|
+
thread.kill
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Call the work and notify the scheduler when it is done.
|
129
|
+
def call(work)
|
130
|
+
promise = Promise.new(work)
|
131
|
+
|
132
|
+
@work.push(promise)
|
133
|
+
|
134
|
+
begin
|
135
|
+
return promise.wait
|
136
|
+
ensure
|
137
|
+
promise.cancel
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Create a new work pool.
|
143
|
+
#
|
144
|
+
# @parameter size [Integer] The number of threads to use.
|
145
|
+
def initialize(size: Etc.nprocessors)
|
146
|
+
@ready = ::Thread::Queue.new
|
147
|
+
|
148
|
+
size.times do
|
149
|
+
@ready.push(Worker.new)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Close the work pool. Kills all outstanding work.
|
154
|
+
def close
|
155
|
+
if ready = @ready
|
156
|
+
@ready = nil
|
157
|
+
ready.close
|
158
|
+
|
159
|
+
while worker = ready.pop
|
160
|
+
worker.close
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Offload work to a thread.
|
166
|
+
#
|
167
|
+
# @parameter work [Proc] The work to be done.
|
168
|
+
def call(work)
|
169
|
+
if ready = @ready
|
170
|
+
worker = ready.pop
|
171
|
+
|
172
|
+
begin
|
173
|
+
worker.call(work)
|
174
|
+
ensure
|
175
|
+
ready.push(worker)
|
176
|
+
end
|
177
|
+
else
|
178
|
+
raise RuntimeError, "No worker available!"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -12,13 +12,17 @@ Traces::Provider(Async::Task) do
|
|
12
12
|
trace_context = Traces.trace_context
|
13
13
|
end
|
14
14
|
|
15
|
+
attributes = {
|
16
|
+
# We use the instance variable as it corresponds to the user-provided block.
|
17
|
+
"block" => @block,
|
18
|
+
"transient" => self.transient?,
|
19
|
+
}
|
20
|
+
|
15
21
|
super do
|
16
22
|
Traces.trace_context = trace_context
|
17
23
|
|
18
24
|
if annotation = self.annotation
|
19
|
-
attributes =
|
20
|
-
"annotation" => annotation
|
21
|
-
}
|
25
|
+
attributes["annotation"] = annotation
|
22
26
|
end
|
23
27
|
|
24
28
|
Traces.trace("async.task", attributes: attributes) do
|
data/readme.md
CHANGED
@@ -7,6 +7,7 @@ Async is a composable asynchronous I/O framework for Ruby based on [io-event](ht
|
|
7
7
|
> beautifully designed." *– [janko](https://github.com/janko)*
|
8
8
|
|
9
9
|
[](https://github.com/socketry/async/actions?workflow=Test)
|
10
|
+
[<img src="https://api.gitsponsors.com/api/badge/img?id=87380483" height="20"/>](https://api.gitsponsors.com/api/badge/link?p=U4gCxvzG7eUksiJSe0MSlPHWWhBYryqj6i48tx5L7/r/2NgkAToKb6dEm31bAftU3H+7BVwk3VhUBtE4GHqHJTPfWPR6xo2BQVoT15rFAGAsLFgdT2kKopIfCGV/QDOm7BrkodS2//R7NUMksAdaCQ==)
|
10
11
|
|
11
12
|
## Features
|
12
13
|
|
@@ -35,6 +36,10 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
|
|
35
36
|
|
36
37
|
Please see the [project releases](https://socketry.github.io/async/releases/index) for all releases.
|
37
38
|
|
39
|
+
### v2.21.1
|
40
|
+
|
41
|
+
- [Worker Pool](https://socketry.github.io/async/releases/index#worker-pool)
|
42
|
+
|
38
43
|
### v2.20.0
|
39
44
|
|
40
45
|
- [Traces and Metrics Providers](https://socketry.github.io/async/releases/index#traces-and-metrics-providers)
|
data/releases.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v2.21.1
|
4
|
+
|
5
|
+
### Worker Pool
|
6
|
+
|
7
|
+
Ruby 3.4 will feature a new fiber scheduler hook, `blocking_operation_wait` which allows the scheduler to redirect the work given to `rb_nogvl` to a worker pool.
|
8
|
+
|
9
|
+
The Async scheduler optionally supports this feature using a worker pool, by using the following environment variable:
|
10
|
+
|
11
|
+
ASYNC_SCHEDULER_DEFAULT_WORKER_POOL=true
|
12
|
+
|
13
|
+
This will cause the scheduler to use a worker pool for general blocking operations, rather than blocking the event loop.
|
14
|
+
|
15
|
+
It should be noted that this isn't a net win, as the overhead of using a worker pool can be significant compared to the `rb_nogvl` work. As such, it is recommended to benchmark your application with and without the worker pool to determine if it is beneficial.
|
16
|
+
|
3
17
|
## v2.20.0
|
4
18
|
|
5
19
|
### Traces and Metrics Providers
|
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.21.
|
4
|
+
version: 2.21.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -31,7 +31,6 @@ authors:
|
|
31
31
|
- Sokolov Yura
|
32
32
|
- Stefan Wrobel
|
33
33
|
- Trevor Turk
|
34
|
-
autorequire:
|
35
34
|
bindir: bin
|
36
35
|
cert_chain:
|
37
36
|
- |
|
@@ -63,7 +62,7 @@ cert_chain:
|
|
63
62
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
64
63
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
65
64
|
-----END CERTIFICATE-----
|
66
|
-
date:
|
65
|
+
date: 2025-01-30 00:00:00.000000000 Z
|
67
66
|
dependencies:
|
68
67
|
- !ruby/object:Gem::Dependency
|
69
68
|
name: console
|
@@ -113,8 +112,6 @@ dependencies:
|
|
113
112
|
- - ">="
|
114
113
|
- !ruby/object:Gem::Version
|
115
114
|
version: 1.6.5
|
116
|
-
description:
|
117
|
-
email:
|
118
115
|
executables: []
|
119
116
|
extensions: []
|
120
117
|
extra_rdoc_files: []
|
@@ -127,6 +124,7 @@ files:
|
|
127
124
|
- lib/async/condition.rb
|
128
125
|
- lib/async/console.rb
|
129
126
|
- lib/async/idler.rb
|
127
|
+
- lib/async/limited_queue.rb
|
130
128
|
- lib/async/list.rb
|
131
129
|
- lib/async/node.rb
|
132
130
|
- lib/async/notification.rb
|
@@ -141,6 +139,7 @@ files:
|
|
141
139
|
- lib/async/version.rb
|
142
140
|
- lib/async/waiter.md
|
143
141
|
- lib/async/waiter.rb
|
142
|
+
- lib/async/worker_pool.rb
|
144
143
|
- lib/async/wrapper.rb
|
145
144
|
- lib/kernel/async.rb
|
146
145
|
- lib/kernel/sync.rb
|
@@ -159,7 +158,6 @@ metadata:
|
|
159
158
|
documentation_uri: https://socketry.github.io/async/
|
160
159
|
funding_uri: https://github.com/sponsors/ioquatix/
|
161
160
|
source_code_uri: https://github.com/socketry/async.git
|
162
|
-
post_install_message:
|
163
161
|
rdoc_options: []
|
164
162
|
require_paths:
|
165
163
|
- lib
|
@@ -174,8 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
172
|
- !ruby/object:Gem::Version
|
175
173
|
version: '0'
|
176
174
|
requirements: []
|
177
|
-
rubygems_version: 3.
|
178
|
-
signing_key:
|
175
|
+
rubygems_version: 3.6.2
|
179
176
|
specification_version: 4
|
180
177
|
summary: A concurrency framework for Ruby.
|
181
178
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|