async 2.8.1 → 2.9.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 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