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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 953c51a9b7552058772c90c36cc99d5a6cd26de3c32d3b424bc5929de81a1960
4
- data.tar.gz: 63b115b69ea49ba7192982de38b936fc6d07623aa6139ce51ee2000c949ad759
3
+ metadata.gz: 707788679dfee0d00262df41dca98842740769f25f38b4ec72f3a0fe0af466fb
4
+ data.tar.gz: 543acd4f573f1665cda010fd6b15d7d24ec14eb5de39cc873ef8b4d1b1cc7f8e
5
5
  SHA512:
6
- metadata.gz: 3023d786331d88fbb94c36b0ab1597f123e5794d410773b9005dfc6df8212c91d106cbeb8506c1c6725e059ac6e82ed1e77667c595d033eb2ff5ea9d9de681f2
7
- data.tar.gz: 31c4192c27d349f045fa25ba2165948630da063abe5369512def7c54d653961ee5c7d2e82ec36808910a26f81f51c23d6674e1b4993888936410d7649cbd2ec0
6
+ metadata.gz: a6be161ee5c42ebcdc0b26ed054b749900060fab3a0601255d7fb038feb063282cd13e8917f79700fc8379996903ea7cb9796c083f348b70f85f6ae363e348ca
7
+ data.tar.gz: ded0df5f2a950f143819bfe94537b188c0e66426344f0d21e0f5e9ed240651b2cafed41d6d5da36a123793cc6406b0ffbc5b8adb1e834faddd168f96855b02c4
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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
@@ -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 we already stopped this task... don't try to stop it again:
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
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
6
  module Async
7
- VERSION = "2.8.2"
7
+ VERSION = "2.10.0"
8
8
  end
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.8.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-02-24 00:00:00.000000000 Z
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.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.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