async 2.8.2 → 2.10.0
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/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
|