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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d1048beb05a88fed1922f32624f61bf5ed8b1e70c2bd9b0e0c17bfad60aa359
4
- data.tar.gz: 0eb19742b401a5ffb95286c766cf68e3159e3b94f57b8fd4272f1038c5dabc00
3
+ metadata.gz: e7dacbad60b89b492fe3741aa5f61240c8bc1f2d9f23e242c7eadd80cc7d9d38
4
+ data.tar.gz: a8193d5ec289670c5ca4a795e905ffd660aa57edaac8796e58c3db8b866fa340
5
5
  SHA512:
6
- metadata.gz: 9c21be88e7a5eea9e6a0380f49eabd47e06522e204b61bb97fc9085053c3e800182a51a83cec001a1bc8533a3f3ff48d1cb1b0987aa15587fbb53ea9209777b6
7
- data.tar.gz: 79ed71d369b9bda125239835befe60d2842f33e0cd3ed3b6ebadbf996dccf43cc2605b33119ceaf4ac8bb0e602671efb06665660c20e2702f983cb6a8459208c
6
+ metadata.gz: de1a9942175a329082d388b280d63c80f614a99145d0df8b6b39e7a4b31d2cb7af4a91bad643f408eeebe6b72807d3f50093528aa5bac273daef67cbb25b3b97
7
+ data.tar.gz: 206be83fe80c466b12669ad847b041e60f27c21d32f82995e7350385f50d990abe94cfbd701a86717532936d7b6ad1e495df78f45a3c0972e2c70b6647824528
@@ -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.debug(self) {"Selecting with #{@children&.size} children with interval = #{interval ? interval.round(2) : 'infinite'}..."}
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
@@ -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, :timeout, :sleep
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
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Async
24
- VERSION = "1.27.0"
24
+ VERSION = "1.28.0"
25
25
  end
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.27.0
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-13 00:00:00.000000000 Z
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.1.2
182
+ rubygems_version: 3.2.3
182
183
  signing_key:
183
184
  specification_version: 4
184
185
  summary: A concurrency framework for Ruby.