async 2.21.0 → 2.21.1
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/scheduler.rb +24 -21
- data/lib/async/version.rb +1 -1
- data/lib/async/worker_pool.rb +169 -0
- data/readme.md +4 -0
- data/releases.md +14 -0
- data.tar.gz.sig +0 -0
- metadata +3 -2
- 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: 6dda1de8f976c85bf6dcbd0156c27c30f85f88d5ffaf32f3397b9838aa920bab
|
4
|
+
data.tar.gz: 0f09cde08880db4456bc03e9a4351903b9a96ca7099e7305c76fdee7f8edc0ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 980cd5e5832ddd71846785e10b23d18d11cb1f0eee33ccbdf33ed40feb4ac51dd99f7cf3d7f93055d0ad52de2abe0103c1b433d41f5bac1728c04435e35655db
|
7
|
+
data.tar.gz: d9c7c1c69e49acd89738f44900502bc83aea08341b84a9f8d9e02588261889a24a935dfa6509394514a575a4b17d8dcf0ca512518e8b0e089369a8fd37b44d52
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
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/version.rb
CHANGED
@@ -0,0 +1,169 @@
|
|
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
|
+
module BlockingOperationWait
|
14
|
+
# Wait for the given work to be executed.
|
15
|
+
#
|
16
|
+
# @public Since *Async v2.19* and *Ruby v3.4*.
|
17
|
+
# @asynchronous May be non-blocking.
|
18
|
+
#
|
19
|
+
# @parameter work [Proc] The work to execute on a background thread.
|
20
|
+
# @returns [Object] The result of the work.
|
21
|
+
def blocking_operation_wait(work)
|
22
|
+
@worker_pool.call(work)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Promise
|
27
|
+
def initialize(work)
|
28
|
+
@work = work
|
29
|
+
@state = :pending
|
30
|
+
@value = nil
|
31
|
+
@guard = ::Mutex.new
|
32
|
+
@condition = ::ConditionVariable.new
|
33
|
+
@thread = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def call
|
37
|
+
work = nil
|
38
|
+
|
39
|
+
@guard.synchronize do
|
40
|
+
@thread = ::Thread.current
|
41
|
+
|
42
|
+
return unless work = @work
|
43
|
+
end
|
44
|
+
|
45
|
+
resolve(work.call)
|
46
|
+
rescue Exception => error
|
47
|
+
reject(error)
|
48
|
+
end
|
49
|
+
|
50
|
+
private def resolve(value)
|
51
|
+
@guard.synchronize do
|
52
|
+
@work = nil
|
53
|
+
@thread = nil
|
54
|
+
@value = value
|
55
|
+
@state = :resolved
|
56
|
+
@condition.broadcast
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private def reject(error)
|
61
|
+
@guard.synchronize do
|
62
|
+
@work = nil
|
63
|
+
@thread = nil
|
64
|
+
@value = error
|
65
|
+
@state = :failed
|
66
|
+
@condition.broadcast
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def cancel
|
71
|
+
return unless @work
|
72
|
+
|
73
|
+
@guard.synchronize do
|
74
|
+
@work = nil
|
75
|
+
@state = :cancelled
|
76
|
+
@thread&.raise(Interrupt)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def wait
|
81
|
+
@guard.synchronize do
|
82
|
+
while @state == :pending
|
83
|
+
@condition.wait(@guard)
|
84
|
+
end
|
85
|
+
|
86
|
+
if @state == :failed
|
87
|
+
raise @value
|
88
|
+
else
|
89
|
+
return @value
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# A handle to the work being done.
|
96
|
+
class Worker
|
97
|
+
def initialize
|
98
|
+
@work = ::Thread::Queue.new
|
99
|
+
@thread = ::Thread.new(&method(:run))
|
100
|
+
end
|
101
|
+
|
102
|
+
def run
|
103
|
+
while work = @work.pop
|
104
|
+
work.call
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def close
|
109
|
+
if thread = @thread
|
110
|
+
@thread = nil
|
111
|
+
thread.kill
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Call the work and notify the scheduler when it is done.
|
116
|
+
def call(work)
|
117
|
+
promise = Promise.new(work)
|
118
|
+
|
119
|
+
@work.push(promise)
|
120
|
+
|
121
|
+
begin
|
122
|
+
return promise.wait
|
123
|
+
ensure
|
124
|
+
promise.cancel
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Create a new work pool.
|
130
|
+
#
|
131
|
+
# @parameter size [Integer] The number of threads to use.
|
132
|
+
def initialize(size: Etc.nprocessors)
|
133
|
+
@ready = ::Thread::Queue.new
|
134
|
+
|
135
|
+
size.times do
|
136
|
+
@ready.push(Worker.new)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Close the work pool. Kills all outstanding work.
|
141
|
+
def close
|
142
|
+
if ready = @ready
|
143
|
+
@ready = nil
|
144
|
+
ready.close
|
145
|
+
|
146
|
+
while worker = ready.pop
|
147
|
+
worker.close
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Offload work to a thread.
|
153
|
+
#
|
154
|
+
# @parameter work [Proc] The work to be done.
|
155
|
+
def call(work)
|
156
|
+
if ready = @ready
|
157
|
+
worker = ready.pop
|
158
|
+
|
159
|
+
begin
|
160
|
+
worker.call(work)
|
161
|
+
ensure
|
162
|
+
ready.push(worker)
|
163
|
+
end
|
164
|
+
else
|
165
|
+
raise RuntimeError, "No worker available!"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/readme.md
CHANGED
@@ -35,6 +35,10 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
|
|
35
35
|
|
36
36
|
Please see the [project releases](https://socketry.github.io/async/releases/index) for all releases.
|
37
37
|
|
38
|
+
### v2.21.1
|
39
|
+
|
40
|
+
- [Worker Pool](https://socketry.github.io/async/releases/index#worker-pool)
|
41
|
+
|
38
42
|
### v2.20.0
|
39
43
|
|
40
44
|
- [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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -63,7 +63,7 @@ cert_chain:
|
|
63
63
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
64
64
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
65
65
|
-----END CERTIFICATE-----
|
66
|
-
date: 2024-11-
|
66
|
+
date: 2024-11-27 00:00:00.000000000 Z
|
67
67
|
dependencies:
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
69
|
name: console
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/async/version.rb
|
142
142
|
- lib/async/waiter.md
|
143
143
|
- lib/async/waiter.rb
|
144
|
+
- lib/async/worker_pool.rb
|
144
145
|
- lib/async/wrapper.rb
|
145
146
|
- lib/kernel/async.rb
|
146
147
|
- lib/kernel/sync.rb
|
metadata.gz.sig
CHANGED
Binary file
|