async 2.8.1 → 2.9.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: a69de1047d88273699e4b64e4013edd63e30ad62d0fd8a05cf5bab46bd9ea12b
4
- data.tar.gz: cfe211dafcd8ffc86ee3c4e91a90bc2de919173e7a60a9bdcfb008d1b5c7b67a
3
+ metadata.gz: e93ed3ea9094d074f430f9518bf0087f57abf732bf196bed5cf8249315aac33d
4
+ data.tar.gz: 233dffa50c8bb88cd514545280e3bef088b998a1ddb58e6db68d1a7c8a268c32
5
5
  SHA512:
6
- metadata.gz: da268decbc3c7d103d7b4c245059afa04629de91be9dee4baecca1b6df8905a6dfd0797494e93907fde00978cc44f197707391f97cf3453d354cf6f28be8ec5c
7
- data.tar.gz: 14a57522fc67231c827e41be8df02aaba356a06ae700297200bfbbd9ee4579807fb8df8281d29100d15256a1cb2f662f264262fa82897ead48690e4c0423e0f7
6
+ metadata.gz: bede7ace58ac0e5f6bbefd05a89855a3f80ddf3d02a441aaa73b0a64adeb82b83d02d064121ae267b719519a6fc03f86b7b586f8af3901ae12fcfc6db2d5612c
7
+ data.tar.gz: a093ddf356f924089fc8500bee1570cac0e7a4a2e131127eeae0b65813905ae853f44e448f18ecda3d55e39d33f2c7e96c41a34f53cc02259ac8b5bef02e857a
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 $!
@@ -49,6 +73,13 @@ module Async
49
73
  self.close
50
74
  end
51
75
 
76
+ # Terminate the scheduler. We deliberately ignore interrupts here, as this code can be called from an interrupt, and we don't want to be interrupted while cleaning up.
77
+ def terminate
78
+ Thread.handle_interrupt(::Interrupt => :never) do
79
+ super
80
+ end
81
+ end
82
+
52
83
  # @public Since `stable-v1`.
53
84
  def close
54
85
  # It's critical to stop all tasks. Otherwise they might be holding on to resources which are never closed/released correctly.
@@ -260,6 +291,8 @@ module Async
260
291
  # @parameter timeout [Float | Nil] The maximum timeout, or if nil, indefinite.
261
292
  # @returns [Boolean] Whether there is more work to do.
262
293
  private def run_once!(timeout = 0)
294
+ start_time = Async::Clock.now
295
+
263
296
  interval = @timers.wait_interval
264
297
 
265
298
  # If there is no interval to wait (thus no timers), and no tasks, we could be done:
@@ -281,6 +314,15 @@ module Async
281
314
 
282
315
  @timers.fire
283
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
+
284
326
  # The reactor still has work to do:
285
327
  return true
286
328
  end
@@ -308,7 +350,7 @@ module Async
308
350
 
309
351
  begin
310
352
  # In theory, we could use Exception here to be a little bit safer, but we've only shown the case for SignalException to be a problem, so let's not over-engineer this.
311
- Thread.handle_interrupt(SignalException => :never) do
353
+ Thread.handle_interrupt(::SignalException => :never) do
312
354
  while true
313
355
  # If we are interrupted, we need to exit:
314
356
  break if self.interrupted?
@@ -318,7 +360,10 @@ module Async
318
360
  end
319
361
  end
320
362
  rescue Interrupt
321
- self.stop
363
+ Thread.handle_interrupt(::SignalException => :never) do
364
+ self.stop
365
+ end
366
+
322
367
  retry
323
368
  end
324
369
 
data/lib/async/task.rb CHANGED
@@ -208,8 +208,8 @@ module Async
208
208
  # @parameter later [Boolean] Whether to stop the task later, or immediately.
209
209
  def stop(later = false)
210
210
  if self.stopped?
211
- # If we already stopped this task... don't try to stop it again:
212
- return
211
+ # 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
+ return stopped!
213
213
  end
214
214
 
215
215
  # If the fiber is alive, we need to stop it:
@@ -304,7 +304,7 @@ module Async
304
304
  stopped = false
305
305
 
306
306
  begin
307
- # We are bnot running, but children might be so we should stop them:
307
+ # We are not running, but children might be so we should stop them:
308
308
  stop_children(true)
309
309
  rescue Stop
310
310
  stopped = true
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.1"
7
+ VERSION = "2.9.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.1
4
+ version: 2.9.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-01 00:00:00.000000000 Z
64
+ date: 2024-03-05 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