async 1.27.0 → 1.28.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/reactor.rb +66 -2
- data/lib/async/scheduler.rb +112 -0
- data/lib/async/task.rb +1 -1
- data/lib/async/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7dacbad60b89b492fe3741aa5f61240c8bc1f2d9f23e242c7eadd80cc7d9d38
|
4
|
+
data.tar.gz: a8193d5ec289670c5ca4a795e905ffd660aa57edaac8796e58c3db8b866fa340
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de1a9942175a329082d388b280d63c80f614a99145d0df8b6b39e7a4b31d2cb7af4a91bad643f408eeebe6b72807d3f50093528aa5bac273daef67cbb25b3b97
|
7
|
+
data.tar.gz: 206be83fe80c466b12669ad847b041e60f27c21d32f82995e7350385f50d990abe94cfbd701a86717532936d7b6ad1e495df78f45a3c0972e2c70b6647824528
|
data/lib/async/reactor.rb
CHANGED
@@ -23,6 +23,7 @@
|
|
23
23
|
require_relative 'logger'
|
24
24
|
require_relative 'task'
|
25
25
|
require_relative 'wrapper'
|
26
|
+
require_relative 'scheduler'
|
26
27
|
|
27
28
|
require 'nio'
|
28
29
|
require 'timers'
|
@@ -82,8 +83,56 @@ module Async
|
|
82
83
|
@ready = []
|
83
84
|
@running = []
|
84
85
|
|
86
|
+
if Scheduler.supported?
|
87
|
+
@scheduler = Scheduler.new(self)
|
88
|
+
else
|
89
|
+
@scheduler = nil
|
90
|
+
end
|
91
|
+
|
85
92
|
@interrupted = false
|
86
93
|
@guard = Mutex.new
|
94
|
+
@blocked = 0
|
95
|
+
@unblocked = []
|
96
|
+
end
|
97
|
+
|
98
|
+
attr :scheduler
|
99
|
+
|
100
|
+
# @reentrant Not thread safe.
|
101
|
+
def block(blocker, timeout)
|
102
|
+
fiber = Fiber.current
|
103
|
+
|
104
|
+
if timeout
|
105
|
+
timer = self.after(timeout) do
|
106
|
+
if fiber.alive?
|
107
|
+
fiber.resume(false)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
begin
|
113
|
+
@blocked += 1
|
114
|
+
Fiber.yield
|
115
|
+
ensure
|
116
|
+
@blocked -= 1
|
117
|
+
end
|
118
|
+
ensure
|
119
|
+
timer&.cancel
|
120
|
+
end
|
121
|
+
|
122
|
+
# @reentrant Thread safe.
|
123
|
+
def unblock(blocker, fiber)
|
124
|
+
@guard.synchronize do
|
125
|
+
@unblocked << fiber
|
126
|
+
@selector.wakeup
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def fiber(&block)
|
131
|
+
if @scheduler
|
132
|
+
Fiber.new(blocking: false, &block)
|
133
|
+
else
|
134
|
+
Fiber.new(&block)
|
135
|
+
end
|
87
136
|
end
|
88
137
|
|
89
138
|
def logger
|
@@ -154,7 +203,7 @@ module Async
|
|
154
203
|
|
155
204
|
def finished?
|
156
205
|
# TODO I'm not sure if checking `@running.empty?` is really required.
|
157
|
-
super && @ready.empty? && @running.empty?
|
206
|
+
super && @ready.empty? && @running.empty? && @blocked.zero?
|
158
207
|
end
|
159
208
|
|
160
209
|
# Run one iteration of the event loop.
|
@@ -174,6 +223,18 @@ module Async
|
|
174
223
|
@running.clear
|
175
224
|
end
|
176
225
|
|
226
|
+
unless @blocked.zero?
|
227
|
+
unblocked = Array.new
|
228
|
+
|
229
|
+
@guard.synchronize do
|
230
|
+
unblocked, @unblocked = @unblocked, unblocked
|
231
|
+
end
|
232
|
+
|
233
|
+
while fiber = unblocked.pop
|
234
|
+
fiber.resume if fiber.alive?
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
177
238
|
if @ready.empty?
|
178
239
|
interval = @timers.wait_interval
|
179
240
|
else
|
@@ -197,7 +258,7 @@ module Async
|
|
197
258
|
interval = timeout
|
198
259
|
end
|
199
260
|
|
200
|
-
# logger.
|
261
|
+
# logger.info(self) {"Selecting with #{@children&.size} children with interval = #{interval ? interval.round(2) : 'infinite'}..."}
|
201
262
|
if monitors = @selector.select(interval)
|
202
263
|
monitors.each do |monitor|
|
203
264
|
monitor.value.resume
|
@@ -223,6 +284,8 @@ module Async
|
|
223
284
|
def run(*arguments, **options, &block)
|
224
285
|
raise RuntimeError, 'Reactor has been closed' if @selector.nil?
|
225
286
|
|
287
|
+
@scheduler&.set!
|
288
|
+
|
226
289
|
initial_task = self.async(*arguments, **options, &block) if block_given?
|
227
290
|
|
228
291
|
while self.run_once
|
@@ -231,6 +294,7 @@ module Async
|
|
231
294
|
|
232
295
|
return initial_task
|
233
296
|
ensure
|
297
|
+
@scheduler&.clear!
|
234
298
|
logger.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."}
|
235
299
|
end
|
236
300
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'clock'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
class Scheduler
|
25
|
+
if Fiber.respond_to?(:set_scheduler)
|
26
|
+
def self.supported?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
else
|
30
|
+
def self.supported?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(reactor)
|
36
|
+
@reactor = reactor
|
37
|
+
@wrappers = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def set!
|
41
|
+
@wrappers = {}
|
42
|
+
Fiber.set_scheduler(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear!
|
46
|
+
# Because these instances are created with `autoclose: false`, this does not close the underlying file descriptor:
|
47
|
+
# @ios&.each_value(&:close)
|
48
|
+
|
49
|
+
@wrappers = nil
|
50
|
+
Fiber.set_scheduler(nil)
|
51
|
+
end
|
52
|
+
|
53
|
+
private def from_io(io)
|
54
|
+
@wrappers[io] ||= Wrapper.new(io, @reactor)
|
55
|
+
end
|
56
|
+
|
57
|
+
def io_wait(io, events, timeout = nil)
|
58
|
+
wrapper = from_io(io)
|
59
|
+
|
60
|
+
if events == IO::READABLE
|
61
|
+
if wrapper.wait_readable(timeout)
|
62
|
+
return IO::READABLE
|
63
|
+
end
|
64
|
+
elsif events == IO::WRITABLE
|
65
|
+
if wrapper.wait_writable(timeout)
|
66
|
+
return IO::WRITABLE
|
67
|
+
end
|
68
|
+
else
|
69
|
+
if wrapper.wait_any(timeout)
|
70
|
+
return events
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
return false
|
75
|
+
ensure
|
76
|
+
wrapper.reactor = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Wait for the specified process ID to exit.
|
80
|
+
# @parameter pid [Integer] The process ID to wait for.
|
81
|
+
# @parameter flags [Integer] A bit-mask of flags suitable for `Process::Status.wait`.
|
82
|
+
# @returns [Process::Status] A process status instance.
|
83
|
+
def process_wait(pid, flags)
|
84
|
+
Thread.new do
|
85
|
+
Process::Status.wait(pid, flags)
|
86
|
+
end.value
|
87
|
+
end
|
88
|
+
|
89
|
+
def kernel_sleep(duration)
|
90
|
+
@reactor.sleep(duration)
|
91
|
+
end
|
92
|
+
|
93
|
+
def block(blocker, timeout)
|
94
|
+
@reactor.block(blocker, timeout)
|
95
|
+
end
|
96
|
+
|
97
|
+
def unblock(blocker, fiber)
|
98
|
+
@reactor.unblock(blocker, fiber)
|
99
|
+
end
|
100
|
+
|
101
|
+
def close
|
102
|
+
end
|
103
|
+
|
104
|
+
def fiber(&block)
|
105
|
+
task = Task.new(&block)
|
106
|
+
|
107
|
+
task.resume
|
108
|
+
|
109
|
+
return task.fiber
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/async/task.rb
CHANGED
@@ -103,7 +103,7 @@ module Async
|
|
103
103
|
# @attr ios [Reactor] The reactor the task was created within.
|
104
104
|
attr :reactor
|
105
105
|
|
106
|
-
def_delegators :@reactor, :with_timeout, :
|
106
|
+
def_delegators :@reactor, :with_timeout, :sleep
|
107
107
|
|
108
108
|
# Yield back to the reactor and allow other fibers to execute.
|
109
109
|
def yield
|
data/lib/async/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.28.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: console
|
@@ -153,6 +153,7 @@ files:
|
|
153
153
|
- lib/async/notification.rb
|
154
154
|
- lib/async/queue.rb
|
155
155
|
- lib/async/reactor.rb
|
156
|
+
- lib/async/scheduler.rb
|
156
157
|
- lib/async/semaphore.rb
|
157
158
|
- lib/async/task.rb
|
158
159
|
- lib/async/version.rb
|
@@ -178,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
179
|
- !ruby/object:Gem::Version
|
179
180
|
version: '0'
|
180
181
|
requirements: []
|
181
|
-
rubygems_version: 3.
|
182
|
+
rubygems_version: 3.2.3
|
182
183
|
signing_key:
|
183
184
|
specification_version: 4
|
184
185
|
summary: A concurrency framework for Ruby.
|