async 2.8.2 → 2.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/idler.rb +39 -0
- data/lib/async/scheduler.rb +35 -0
- data/lib/async/task.rb +46 -1
- data/lib/async/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +11 -4
- 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: 707788679dfee0d00262df41dca98842740769f25f38b4ec72f3a0fe0af466fb
|
4
|
+
data.tar.gz: 543acd4f573f1665cda010fd6b15d7d24ec14eb5de39cc873ef8b4d1b1cc7f8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6be161ee5c42ebcdc0b26ed054b749900060fab3a0601255d7fb038feb063282cd13e8917f79700fc8379996903ea7cb9796c083f348b70f85f6ae363e348ca
|
7
|
+
data.tar.gz: ded0df5f2a950f143819bfe94537b188c0e66426344f0d21e0f5e9ed240651b2cafed41d6d5da36a123793cc6406b0ffbc5b8adb1e834faddd168f96855b02c4
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/idler.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
|
+
|
6
|
+
module Async
|
7
|
+
class Idler
|
8
|
+
def initialize(maximum_load = 0.8, backoff: 0.01, parent: nil)
|
9
|
+
@maximum_load = maximum_load
|
10
|
+
@backoff = backoff
|
11
|
+
@parent = parent
|
12
|
+
end
|
13
|
+
|
14
|
+
def async(*arguments, parent: (@parent or Task.current), **options, &block)
|
15
|
+
wait
|
16
|
+
|
17
|
+
# It is crucial that we optimistically execute the child task, so that we prevent a tight loop invoking this method from consuming all available resources.
|
18
|
+
parent.async(*arguments, **options, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def wait
|
22
|
+
scheduler = Fiber.scheduler
|
23
|
+
backoff = nil
|
24
|
+
|
25
|
+
while true
|
26
|
+
load = scheduler.load
|
27
|
+
break if load < @maximum_load
|
28
|
+
|
29
|
+
if backoff
|
30
|
+
sleep(backoff)
|
31
|
+
backoff *= 2.0
|
32
|
+
else
|
33
|
+
scheduler.yield
|
34
|
+
backoff = @backoff
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/async/scheduler.rb
CHANGED
@@ -37,9 +37,33 @@ module Async
|
|
37
37
|
|
38
38
|
@blocked = 0
|
39
39
|
|
40
|
+
@busy_time = 0.0
|
41
|
+
@idle_time = 0.0
|
42
|
+
|
40
43
|
@timers = ::Timers::Group.new
|
41
44
|
end
|
42
45
|
|
46
|
+
# Compute the scheduler load according to the busy and idle times that are updated by the run loop.
|
47
|
+
# @returns [Float] The load of the scheduler. 0.0 means no load, 1.0 means fully loaded or over-loaded.
|
48
|
+
def load
|
49
|
+
total_time = @busy_time + @idle_time
|
50
|
+
|
51
|
+
# If the total time is zero, then the load is zero:
|
52
|
+
return 0.0 if total_time.zero?
|
53
|
+
|
54
|
+
# We normalize to a 1 second window:
|
55
|
+
if total_time > 1.0
|
56
|
+
ratio = 1.0 / total_time
|
57
|
+
@busy_time *= ratio
|
58
|
+
@idle_time *= ratio
|
59
|
+
|
60
|
+
# We don't need to divide here as we've already normalised it to a 1s window:
|
61
|
+
return @busy_time
|
62
|
+
else
|
63
|
+
return @busy_time / total_time
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
43
67
|
def scheduler_close
|
44
68
|
# If the execution context (thread) was handling an exception, we want to exit as quickly as possible:
|
45
69
|
unless $!
|
@@ -267,6 +291,8 @@ module Async
|
|
267
291
|
# @parameter timeout [Float | Nil] The maximum timeout, or if nil, indefinite.
|
268
292
|
# @returns [Boolean] Whether there is more work to do.
|
269
293
|
private def run_once!(timeout = 0)
|
294
|
+
start_time = Async::Clock.now
|
295
|
+
|
270
296
|
interval = @timers.wait_interval
|
271
297
|
|
272
298
|
# If there is no interval to wait (thus no timers), and no tasks, we could be done:
|
@@ -288,6 +314,15 @@ module Async
|
|
288
314
|
|
289
315
|
@timers.fire
|
290
316
|
|
317
|
+
# Compute load:
|
318
|
+
end_time = Async::Clock.now
|
319
|
+
total_duration = end_time - start_time
|
320
|
+
idle_duration = @selector.idle_duration
|
321
|
+
busy_duration = total_duration - idle_duration
|
322
|
+
|
323
|
+
@busy_time += busy_duration
|
324
|
+
@idle_time += idle_duration
|
325
|
+
|
291
326
|
# The reactor still has work to do:
|
292
327
|
return true
|
293
328
|
end
|
data/lib/async/task.rb
CHANGED
@@ -67,6 +67,8 @@ module Async
|
|
67
67
|
@status = :initialized
|
68
68
|
@result = nil
|
69
69
|
@finished = finished
|
70
|
+
|
71
|
+
@defer_stop = nil
|
70
72
|
end
|
71
73
|
|
72
74
|
def reactor
|
@@ -208,10 +210,17 @@ module Async
|
|
208
210
|
# @parameter later [Boolean] Whether to stop the task later, or immediately.
|
209
211
|
def stop(later = false)
|
210
212
|
if self.stopped?
|
211
|
-
# If
|
213
|
+
# If the task is already stopped, a `stop` state transition re-enters the same state which is a no-op. However, we will also attempt to stop any running children too. This can happen if the children did not stop correctly the first time around. Doing this should probably be considered a bug, but it's better to be safe than sorry.
|
212
214
|
return stopped!
|
213
215
|
end
|
214
216
|
|
217
|
+
# If we are deferring stop...
|
218
|
+
if @defer_stop == false
|
219
|
+
# Don't stop now... but update the state so we know we need to stop later.
|
220
|
+
@defer_stop = true
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
|
215
224
|
# If the fiber is alive, we need to stop it:
|
216
225
|
if @fiber&.alive?
|
217
226
|
if self.current?
|
@@ -239,6 +248,42 @@ module Async
|
|
239
248
|
end
|
240
249
|
end
|
241
250
|
|
251
|
+
# Defer the handling of stop. During the execution of the given block, if a stop is requested, it will be deferred until the block exits. This is useful for ensuring graceful shutdown of servers and other long-running tasks. You should wrap the response handling code in a defer_stop block to ensure that the task is stopped when the response is complete but not before.
|
252
|
+
#
|
253
|
+
# You can nest calls to defer_stop, but the stop will only be deferred until the outermost block exits.
|
254
|
+
#
|
255
|
+
# If stop is invoked a second time, it will be immediately executed.
|
256
|
+
#
|
257
|
+
# @yields {} The block of code to execute.
|
258
|
+
# @public Since `stable-v1`.
|
259
|
+
def defer_stop
|
260
|
+
# Tri-state variable for controlling stop:
|
261
|
+
# - nil: defer_stop has not been called.
|
262
|
+
# - false: defer_stop has been called and we are not stopping.
|
263
|
+
# - true: defer_stop has been called and we will stop when exiting the block.
|
264
|
+
if @defer_stop.nil?
|
265
|
+
# If we are not deferring stop already, we can defer it now:
|
266
|
+
@defer_stop = false
|
267
|
+
|
268
|
+
begin
|
269
|
+
yield
|
270
|
+
rescue Stop
|
271
|
+
# If we are exiting due to a stop, we shouldn't try to invoke stop again:
|
272
|
+
@defer_stop = nil
|
273
|
+
raise
|
274
|
+
ensure
|
275
|
+
# If we were asked to stop, we should do so now:
|
276
|
+
if @defer_stop
|
277
|
+
@defer_stop = nil
|
278
|
+
self.stop
|
279
|
+
end
|
280
|
+
end
|
281
|
+
else
|
282
|
+
# If we are deferring stop already, entering it again is a no-op.
|
283
|
+
yield
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
242
287
|
# Lookup the {Task} for the current fiber. Raise `RuntimeError` if none is available.
|
243
288
|
# @returns [Task]
|
244
289
|
# @raises[RuntimeError] If task was not {set!} for the current fiber.
|
data/lib/async/version.rb
CHANGED
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.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -61,7 +61,7 @@ cert_chain:
|
|
61
61
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
62
62
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
63
63
|
-----END CERTIFICATE-----
|
64
|
-
date: 2024-
|
64
|
+
date: 2024-03-27 00:00:00.000000000 Z
|
65
65
|
dependencies:
|
66
66
|
- !ruby/object:Gem::Dependency
|
67
67
|
name: console
|
@@ -97,14 +97,20 @@ dependencies:
|
|
97
97
|
requirements:
|
98
98
|
- - "~>"
|
99
99
|
- !ruby/object:Gem::Version
|
100
|
-
version: '1.
|
100
|
+
version: '1.5'
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.5.1
|
101
104
|
type: :runtime
|
102
105
|
prerelease: false
|
103
106
|
version_requirements: !ruby/object:Gem::Requirement
|
104
107
|
requirements:
|
105
108
|
- - "~>"
|
106
109
|
- !ruby/object:Gem::Version
|
107
|
-
version: '1.
|
110
|
+
version: '1.5'
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 1.5.1
|
108
114
|
- !ruby/object:Gem::Dependency
|
109
115
|
name: timers
|
110
116
|
requirement: !ruby/object:Gem::Requirement
|
@@ -131,6 +137,7 @@ files:
|
|
131
137
|
- lib/async/clock.rb
|
132
138
|
- lib/async/condition.md
|
133
139
|
- lib/async/condition.rb
|
140
|
+
- lib/async/idler.rb
|
134
141
|
- lib/async/list.rb
|
135
142
|
- lib/async/node.rb
|
136
143
|
- lib/async/notification.rb
|
metadata.gz.sig
CHANGED
Binary file
|