async 2.24.0 → 2.25.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: 95c1315007ff80d78bdd1538d2b2963ed951db5aa92906ea6f22560612540cbc
4
- data.tar.gz: 03ffadcf7c2523827bd6c49340df0a846f8ab3214593d897b4359b548a765cef
3
+ metadata.gz: 44c11aa8f74922b9bc2ccce792fd3c419af4a3a261f97f2f2202e3d75a5bf8f5
4
+ data.tar.gz: e2327b997b32a42e2ebfe9a4e4950d26a12ce698ce34a89d9fe1be94b0241b1f
5
5
  SHA512:
6
- metadata.gz: 62c15e0c19e7f3277ca9e4981b0304d5f039367fb0d6b996a8c562be170b3436f66e97f33fcfd5eb0ed8ec84e760ae2b565a022604e026f3b2c797613534fd59
7
- data.tar.gz: 79c7148d35f3e06243b3845721b8a1ebcf5fd2e6244eab18ac5a0f42894088334df307c4da52d9d9c59b0d9499e457f0990022d570abf109202416cf168211a1
6
+ metadata.gz: 20961f535e5368753dffdb760d9bddf0fcf55e5910e5f6c71dba28ebb0405221d9328a8bfe5ef4e688ead71b4b5c4bde3949ce1edfa013b06af7db57adfe6d7f
7
+ data.tar.gz: d58c654852cd650978fa14fef7eaba00f4784137075960e5ad5b0b6b20ae3523912578b32b660f7d107df318cdd34945124e83231c4e574e336c5c4862a6c87c
checksums.yaml.gz.sig CHANGED
Binary file
data/lib/async/clock.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2025, by Samuel Williams.
5
5
 
6
6
  module Async
7
7
  # A convenient wrapper around the internal monotonic clock.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
4
+ # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  # The implementation lives in `queue.rb` but later we may move it here for better autoload/inference.
7
7
  require_relative "queue"
data/lib/async/queue.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2024, by Samuel Williams.
4
+ # Copyright, 2018-2025, by Samuel Williams.
5
5
  # Copyright, 2019, by Ryan Musgrave.
6
6
  # Copyright, 2020-2022, by Bruno Sutic.
7
+ # Copyright, 2025, by Jahfer Husain.
7
8
 
8
9
  require_relative "notification"
9
10
 
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2024, by Samuel Williams.
4
+ # Copyright, 2020-2025, by Samuel Williams.
5
5
  # Copyright, 2020, by Jun Jiang.
6
6
  # Copyright, 2021, by Julien Portalier.
7
+ # Copyright, 2025, by Shopify Inc.
7
8
 
8
9
  require_relative "clock"
9
10
  require_relative "task"
10
11
  require_relative "timeout"
11
- require_relative "worker_pool"
12
12
 
13
13
  require "io/event"
14
14
 
@@ -45,7 +45,29 @@ module Async
45
45
  def self.supported?
46
46
  true
47
47
  end
48
+
49
+ # Used to augment the scheduler to add support for blocking operations.
50
+ module BlockingOperationWait
51
+ # Wait for the given work to be executed.
52
+ #
53
+ # @public Since *Async v2.21* and *Ruby v3.4*.
54
+ # @asynchronous May be non-blocking.
55
+ #
56
+ # @parameter work [Proc] The work to execute on a background thread.
57
+ # @returns [Object] The result of the work.
58
+ def blocking_operation_wait(work)
59
+ @worker_pool.call(work)
60
+ end
61
+ end
62
+
63
+ private_constant :BlockingOperationWait
48
64
 
65
+ if ::IO::Event.const_defined?(:WorkerPool)
66
+ WorkerPool = ::IO::Event::WorkerPool
67
+ else
68
+ WorkerPool = nil
69
+ end
70
+
49
71
  # Create a new scheduler.
50
72
  #
51
73
  # @public Since *Async v1*.
@@ -65,14 +87,15 @@ module Async
65
87
  @idle_time = 0.0
66
88
 
67
89
  @timers = ::IO::Event::Timers.new
90
+
68
91
  if worker_pool == true
69
- @worker_pool = WorkerPool.new
92
+ @worker_pool = WorkerPool&.new
70
93
  else
71
94
  @worker_pool = worker_pool
72
95
  end
73
-
96
+
74
97
  if @worker_pool
75
- self.singleton_class.prepend(WorkerPool::BlockingOperationWait)
98
+ self.singleton_class.prepend(BlockingOperationWait)
76
99
  end
77
100
  end
78
101
 
@@ -234,7 +257,7 @@ module Async
234
257
  # @parameter blocker [Object] The object that was blocking the fiber.
235
258
  # @parameter fiber [Fiber] The fiber to unblock.
236
259
  def unblock(blocker, fiber)
237
- # $stderr.puts "unblock(#{blocker}, #{fiber})"
260
+ # Fiber.blocking{$stderr.puts "unblock(#{blocker}, #{fiber})"}
238
261
 
239
262
  # This operation is protected by the GVL:
240
263
  if selector = @selector
@@ -250,6 +273,8 @@ module Async
250
273
  #
251
274
  # @parameter duration [Numeric | Nil] The time in seconds to sleep, or if nil, indefinitely.
252
275
  def kernel_sleep(duration = nil)
276
+ # Fiber.blocking{$stderr.puts "kernel_sleep(#{duration}, #{Fiber.current})"}
277
+
253
278
  if duration
254
279
  self.block(nil, duration)
255
280
  else
@@ -348,6 +373,34 @@ module Async
348
373
  end
349
374
  end
350
375
 
376
+ # Used to defer stopping the current task until later.
377
+ class FiberInterrupt
378
+ # Create a new stop later operation.
379
+ #
380
+ # @parameter task [Task] The task to stop later.
381
+ def initialize(fiber, exception)
382
+ @fiber = fiber
383
+ @exception = exception
384
+ end
385
+
386
+ # @returns [Boolean] Whether the task is alive.
387
+ def alive?
388
+ @fiber.alive?
389
+ end
390
+
391
+ # Transfer control to the operation - this will stop the task.
392
+ def transfer
393
+ # Fiber.blocking{$stderr.puts "FiberInterrupt#transfer(#{@fiber}, #{@exception})"}
394
+ @fiber.raise(@exception)
395
+ end
396
+ end
397
+
398
+ # Raise an exception on the specified fiber, waking up the event loop if necessary.
399
+ def fiber_interrupt(fiber, exception)
400
+ # Fiber.blocking{$stderr.puts "fiber_interrupt(#{fiber}, #{exception})"}
401
+ unblock(nil, FiberInterrupt.new(fiber, exception))
402
+ end
403
+
351
404
  # Wait for the specified process ID to exit.
352
405
  #
353
406
  # @public Since *Async v2*.
@@ -361,6 +414,16 @@ module Async
361
414
  return @selector.process_wait(Fiber.current, pid, flags)
362
415
  end
363
416
 
417
+ # Wait for the specified IOs to become ready for the specified events.
418
+ #
419
+ # @public Since *Async v2.25*.
420
+ # @asynchronous May be non-blocking.
421
+ def io_select(...)
422
+ Thread.new do
423
+ ::IO.select(...)
424
+ end.value
425
+ end
426
+
364
427
  # Run one iteration of the event loop.
365
428
  #
366
429
  # When terminating the event loop, we already know we are finished. So we don't need to check the task tree. This is a logical requirement because `run_once` ignores transient tasks. For example, a single top level transient task is not enough to keep the reactor running, but during termination we must still process it in order to terminate child tasks.
data/lib/async/task.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2025, by Samuel Williams.
5
5
  # Copyright, 2017, by Kent Gruber.
6
6
  # Copyright, 2017, by Devin Christensen.
7
7
  # Copyright, 2020, by Patrik Wenger.
8
8
  # Copyright, 2023, by Math Ieu.
9
+ # Copyright, 2025, by Shigeru Nakajima.
9
10
 
10
11
  require "fiber"
11
12
  require "console"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "condition"
7
7
 
data/lib/async/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2025, by Samuel Williams.
5
5
 
6
6
  module Async
7
- VERSION = "2.24.0"
7
+ VERSION = "2.25.0"
8
8
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "../../../async/task"
7
7
  require "metrics/provider"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2024, by Samuel Williams.
5
5
 
6
6
  require_relative "../../../async/barrier"
7
7
  require "traces/provider"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "../../../async/task"
7
7
  require "traces/provider"
data/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2017-2024, by Samuel Williams.
3
+ Copyright, 2017-2025, by Samuel Williams.
4
4
  Copyright, 2017, by Kent Gruber.
5
5
  Copyright, 2017, by Devin Christensen.
6
6
  Copyright, 2018, by Sokolov Yura.
@@ -27,6 +27,11 @@ Copyright, 2023, by Emil Tin.
27
27
  Copyright, 2023, by Gert Goet.
28
28
  Copyright, 2024, by Dimitar Peychinov.
29
29
  Copyright, 2024, by Jamie McCarthy.
30
+ Copyright, 2025, by Jahfer Husain.
31
+ Copyright, 2025, by Mark Montroy.
32
+ Copyright, 2025, by Shigeru Nakajima.
33
+ Copyright, 2025, by Alan Wu.
34
+ Copyright, 2025, by Shopify Inc.
30
35
 
31
36
  Permission is hereby granted, free of charge, to any person obtaining a copy
32
37
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -35,6 +35,12 @@ Please see the [project documentation](https://socketry.github.io/async/) for mo
35
35
 
36
36
  Please see the [project releases](https://socketry.github.io/async/releases/index) for all releases.
37
37
 
38
+ ### v2.25.0
39
+
40
+ - Added support for `io_select` hook in the fiber scheduler, allowing non-blocking `IO.select` operations. This enables better integration with code that uses `IO.select` for multiplexing IO operations.
41
+ - [Use `IO::Event::WorkerPool` for Blocking Operations](https://socketry.github.io/async/releases/index#use-io::event::workerpool-for-blocking-operations)
42
+ - [Better handling of `IO#close` using `fiber_interrupt`](https://socketry.github.io/async/releases/index#better-handling-of-io#close-using-fiber_interrupt)
43
+
38
44
  ### v2.24.0
39
45
 
40
46
  - Ruby v3.1 support is dropped.
data/releases.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Releases
2
2
 
3
+ ## v2.25.0
4
+
5
+ - Added support for `io_select` hook in the fiber scheduler, allowing non-blocking `IO.select` operations. This enables better integration with code that uses `IO.select` for multiplexing IO operations.
6
+
7
+ ### Use `IO::Event::WorkerPool` for Blocking Operations
8
+
9
+ The `Async::WorkerPool` implementation has been removed in favor of using `IO::Event::WorkerPool` directly. This change simplifies the codebase by delegating worker pool functionality to the `io-event` gem, which provides a more efficient and well-tested implementation.
10
+
11
+ To enable the worker pool, you can set the `ASYNC_SCHEDULER_WORKER_POOL` environment variable to `true`. This will allow the scheduler to use a worker pool for blocking operations, which can help improve performance in applications that perform a lot of CPU-bound operations (e.g. `rb_nogvl`).
12
+
13
+ ### Better handling of `IO#close` using `fiber_interrupt`
14
+
15
+ `IO#close` interrupts fibers that are waiting on the IO using the new `fiber_interrupt` hook introduced in Ruby 3.5/4.0. This means that if you close an IO while a fiber is waiting on it, the fiber will be interrupted and will raise an `IOError`. This is a change from previous versions of Ruby, where closing an IO would not interrupt fibers waiting on it, and would instead interrupt the entire event loop (essentially a bug).
16
+
17
+ ``` ruby
18
+ r, w = IO.pipe
19
+
20
+ Async do
21
+ child = Async do
22
+ r.gets
23
+ end
24
+
25
+ r.close # This will interrupt the child fiber.
26
+ child.wait # This will raise an `IOError` because the IO was closed.
27
+ end
28
+ ```
29
+
3
30
  ## v2.24.0
4
31
 
5
32
  - Ruby v3.1 support is dropped.
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.24.0
4
+ version: 2.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -13,21 +13,26 @@ authors:
13
13
  - Emil Tin
14
14
  - Jamie McCarthy
15
15
  - Kent Gruber
16
+ - Alan Wu
16
17
  - Brian Morearty
17
18
  - Colin Kelley
18
19
  - Dimitar Peychinov
19
20
  - Gert Goet
21
+ - Jahfer Husain
20
22
  - Jiang Jinyang
21
23
  - Julien Portalier
22
24
  - Jun Jiang
23
25
  - Ken Muryoi
24
26
  - Leon Löchner
27
+ - Mark Montroy
25
28
  - Masafumi Okura
26
29
  - Masayuki Yamamoto
27
30
  - Math Ieu
28
31
  - Ryan Musgrave
29
32
  - Salim Semaoune
30
33
  - Shannon Skipper
34
+ - Shigeru Nakajima
35
+ - Shopify Inc.
31
36
  - Sokolov Yura
32
37
  - Stefan Wrobel
33
38
  - Trevor Turk
@@ -62,7 +67,7 @@ cert_chain:
62
67
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
63
68
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
64
69
  -----END CERTIFICATE-----
65
- date: 2025-05-03 00:00:00.000000000 Z
70
+ date: 1980-01-02 00:00:00.000000000 Z
66
71
  dependencies:
67
72
  - !ruby/object:Gem::Dependency
68
73
  name: console
@@ -98,42 +103,42 @@ dependencies:
98
103
  requirements:
99
104
  - - "~>"
100
105
  - !ruby/object:Gem::Version
101
- version: '1.9'
106
+ version: '1.11'
102
107
  type: :runtime
103
108
  prerelease: false
104
109
  version_requirements: !ruby/object:Gem::Requirement
105
110
  requirements:
106
111
  - - "~>"
107
112
  - !ruby/object:Gem::Version
108
- version: '1.9'
113
+ version: '1.11'
109
114
  - !ruby/object:Gem::Dependency
110
- name: traces
115
+ name: metrics
111
116
  requirement: !ruby/object:Gem::Requirement
112
117
  requirements:
113
118
  - - "~>"
114
119
  - !ruby/object:Gem::Version
115
- version: '0.15'
120
+ version: '0.12'
116
121
  type: :runtime
117
122
  prerelease: false
118
123
  version_requirements: !ruby/object:Gem::Requirement
119
124
  requirements:
120
125
  - - "~>"
121
126
  - !ruby/object:Gem::Version
122
- version: '0.15'
127
+ version: '0.12'
123
128
  - !ruby/object:Gem::Dependency
124
- name: metrics
129
+ name: traces
125
130
  requirement: !ruby/object:Gem::Requirement
126
131
  requirements:
127
132
  - - "~>"
128
133
  - !ruby/object:Gem::Version
129
- version: '0.12'
134
+ version: '0.15'
130
135
  type: :runtime
131
136
  prerelease: false
132
137
  version_requirements: !ruby/object:Gem::Requirement
133
138
  requirements:
134
139
  - - "~>"
135
140
  - !ruby/object:Gem::Version
136
- version: '0.12'
141
+ version: '0.15'
137
142
  executables: []
138
143
  extensions: []
139
144
  extra_rdoc_files: []
@@ -162,7 +167,6 @@ files:
162
167
  - lib/async/version.rb
163
168
  - lib/async/waiter.md
164
169
  - lib/async/waiter.rb
165
- - lib/async/worker_pool.rb
166
170
  - lib/kernel/async.rb
167
171
  - lib/kernel/sync.rb
168
172
  - lib/metrics/provider/async.rb
@@ -187,14 +191,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
187
191
  requirements:
188
192
  - - ">="
189
193
  - !ruby/object:Gem::Version
190
- version: '3.1'
194
+ version: '3.2'
191
195
  required_rubygems_version: !ruby/object:Gem::Requirement
192
196
  requirements:
193
197
  - - ">="
194
198
  - !ruby/object:Gem::Version
195
199
  version: '0'
196
200
  requirements: []
197
- rubygems_version: 3.6.2
201
+ rubygems_version: 3.6.7
198
202
  specification_version: 4
199
203
  summary: A concurrency framework for Ruby.
200
204
  test_files: []
metadata.gz.sig CHANGED
Binary file
@@ -1,182 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
5
-
6
- require "etc"
7
-
8
- module Async
9
- # A simple work pool that offloads work to a background thread.
10
- #
11
- # @private
12
- class WorkerPool
13
- # Used to augment the scheduler to add support for blocking operations.
14
- module BlockingOperationWait
15
- # Wait for the given work to be executed.
16
- #
17
- # @public Since *Async v2.21* and *Ruby v3.4*.
18
- # @asynchronous May be non-blocking.
19
- #
20
- # @parameter work [Proc] The work to execute on a background thread.
21
- # @returns [Object] The result of the work.
22
- def blocking_operation_wait(work)
23
- @worker_pool.call(work)
24
- end
25
- end
26
-
27
- # Execute the given work in a background thread.
28
- class Promise
29
- # Create a new promise.
30
- #
31
- # @parameter work [Proc] The work to be done.
32
- def initialize(work)
33
- @work = work
34
- @state = :pending
35
- @value = nil
36
- @guard = ::Mutex.new
37
- @condition = ::ConditionVariable.new
38
- @thread = nil
39
- end
40
-
41
- # Execute the work and resolve the promise.
42
- def call
43
- work = nil
44
-
45
- @guard.synchronize do
46
- @thread = ::Thread.current
47
-
48
- return unless work = @work
49
- end
50
-
51
- resolve(work.call)
52
- rescue Exception => error
53
- reject(error)
54
- end
55
-
56
- private def resolve(value)
57
- @guard.synchronize do
58
- @work = nil
59
- @thread = nil
60
- @value = value
61
- @state = :resolved
62
- @condition.broadcast
63
- end
64
- end
65
-
66
- private def reject(error)
67
- @guard.synchronize do
68
- @work = nil
69
- @thread = nil
70
- @value = error
71
- @state = :failed
72
- @condition.broadcast
73
- end
74
- end
75
-
76
- # Cancel the work and raise an exception in the background thread.
77
- def cancel
78
- return unless @work
79
-
80
- @guard.synchronize do
81
- @work = nil
82
- @state = :cancelled
83
- @thread&.raise(Interrupt)
84
- end
85
- end
86
-
87
- # Wait for the work to be done.
88
- #
89
- # @returns [Object] The result of the work.
90
- def wait
91
- @guard.synchronize do
92
- while @state == :pending
93
- @condition.wait(@guard)
94
- end
95
-
96
- if @state == :failed
97
- raise @value
98
- else
99
- return @value
100
- end
101
- end
102
- end
103
- end
104
-
105
- # A background worker thread.
106
- class Worker
107
- # Create a new worker.
108
- def initialize
109
- @work = ::Thread::Queue.new
110
- @thread = ::Thread.new(&method(:run))
111
- end
112
-
113
- # Execute work until the queue is closed.
114
- def run
115
- while work = @work.pop
116
- work.call
117
- end
118
- end
119
-
120
- # Close the worker thread.
121
- def close
122
- if thread = @thread
123
- @thread = nil
124
- thread.kill
125
- end
126
- end
127
-
128
- # Call the work and notify the scheduler when it is done.
129
- def call(work)
130
- promise = Promise.new(work)
131
-
132
- @work.push(promise)
133
-
134
- begin
135
- return promise.wait
136
- ensure
137
- promise.cancel
138
- end
139
- end
140
- end
141
-
142
- # Create a new work pool.
143
- #
144
- # @parameter size [Integer] The number of threads to use.
145
- def initialize(size: Etc.nprocessors)
146
- @ready = ::Thread::Queue.new
147
-
148
- size.times do
149
- @ready.push(Worker.new)
150
- end
151
- end
152
-
153
- # Close the work pool. Kills all outstanding work.
154
- def close
155
- if ready = @ready
156
- @ready = nil
157
- ready.close
158
-
159
- while worker = ready.pop
160
- worker.close
161
- end
162
- end
163
- end
164
-
165
- # Offload work to a thread.
166
- #
167
- # @parameter work [Proc] The work to be done.
168
- def call(work)
169
- if ready = @ready
170
- worker = ready.pop
171
-
172
- begin
173
- worker.call(work)
174
- ensure
175
- ready.push(worker)
176
- end
177
- else
178
- raise RuntimeError, "No worker available!"
179
- end
180
- end
181
- end
182
- end