async 2.23.1 → 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: 36ac96ecf26e32e4804f8e89909dcb7b5f8cdfbed24a4da1a189d027586bd0b1
4
- data.tar.gz: 35fb378abd8183a2a4950a2b57fd4ba6360c740b51a12ff3e67af5a2c443583c
3
+ metadata.gz: 44c11aa8f74922b9bc2ccce792fd3c419af4a3a261f97f2f2202e3d75a5bf8f5
4
+ data.tar.gz: e2327b997b32a42e2ebfe9a4e4950d26a12ce698ce34a89d9fe1be94b0241b1f
5
5
  SHA512:
6
- metadata.gz: 5ff90e092ac86b1dd3e5991e48d44d2b6a9abaa1b72d785d3e9419630f3f8fadd241ab21837bbd5ddd4038792e3ac970bf2aa4b2f67f5a473d4be1caa067f1d5
7
- data.tar.gz: 5b71b8eae39aee81b3918f1012c76a4306529292626bf6dc95e79ecebcfa53cda304840655eee56c3392b5a912478ff3178ab4624a2f519ad02d6ce70098f1e2
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,13 +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
- require_relative "worker_pool"
11
+ require_relative "timeout"
11
12
 
12
13
  require "io/event"
13
14
 
@@ -44,7 +45,29 @@ module Async
44
45
  def self.supported?
45
46
  true
46
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
47
64
 
65
+ if ::IO::Event.const_defined?(:WorkerPool)
66
+ WorkerPool = ::IO::Event::WorkerPool
67
+ else
68
+ WorkerPool = nil
69
+ end
70
+
48
71
  # Create a new scheduler.
49
72
  #
50
73
  # @public Since *Async v1*.
@@ -64,14 +87,15 @@ module Async
64
87
  @idle_time = 0.0
65
88
 
66
89
  @timers = ::IO::Event::Timers.new
90
+
67
91
  if worker_pool == true
68
- @worker_pool = WorkerPool.new
92
+ @worker_pool = WorkerPool&.new
69
93
  else
70
94
  @worker_pool = worker_pool
71
95
  end
72
-
96
+
73
97
  if @worker_pool
74
- self.singleton_class.prepend(WorkerPool::BlockingOperationWait)
98
+ self.singleton_class.prepend(BlockingOperationWait)
75
99
  end
76
100
  end
77
101
 
@@ -233,7 +257,7 @@ module Async
233
257
  # @parameter blocker [Object] The object that was blocking the fiber.
234
258
  # @parameter fiber [Fiber] The fiber to unblock.
235
259
  def unblock(blocker, fiber)
236
- # $stderr.puts "unblock(#{blocker}, #{fiber})"
260
+ # Fiber.blocking{$stderr.puts "unblock(#{blocker}, #{fiber})"}
237
261
 
238
262
  # This operation is protected by the GVL:
239
263
  if selector = @selector
@@ -249,6 +273,8 @@ module Async
249
273
  #
250
274
  # @parameter duration [Numeric | Nil] The time in seconds to sleep, or if nil, indefinitely.
251
275
  def kernel_sleep(duration = nil)
276
+ # Fiber.blocking{$stderr.puts "kernel_sleep(#{duration}, #{Fiber.current})"}
277
+
252
278
  if duration
253
279
  self.block(nil, duration)
254
280
  else
@@ -269,16 +295,6 @@ module Async
269
295
  ::Resolv.getaddresses(hostname)
270
296
  end
271
297
 
272
- if IO.method_defined?(:timeout)
273
- private def get_timeout(io)
274
- io.timeout
275
- end
276
- else
277
- private def get_timeout(io)
278
- nil
279
- end
280
- end
281
-
282
298
  # Wait for the specified IO to become ready for the specified events.
283
299
  #
284
300
  # @public Since *Async v2*.
@@ -295,7 +311,7 @@ module Async
295
311
  timer = @timers.after(timeout) do
296
312
  fiber.transfer
297
313
  end
298
- elsif timeout = get_timeout(io)
314
+ elsif timeout = io.timeout
299
315
  # Otherwise, if we default to the io's timeout, we raise an exception:
300
316
  timer = @timers.after(timeout) do
301
317
  fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become ready!")
@@ -320,7 +336,7 @@ module Async
320
336
  def io_read(io, buffer, length, offset = 0)
321
337
  fiber = Fiber.current
322
338
 
323
- if timeout = get_timeout(io)
339
+ if timeout = io.timeout
324
340
  timer = @timers.after(timeout) do
325
341
  fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become readable!")
326
342
  end
@@ -344,7 +360,7 @@ module Async
344
360
  def io_write(io, buffer, length, offset = 0)
345
361
  fiber = Fiber.current
346
362
 
347
- if timeout = get_timeout(io)
363
+ if timeout = io.timeout
348
364
  timer = @timers.after(timeout) do
349
365
  fiber.raise(::IO::TimeoutError, "Timeout (#{timeout}s) while waiting for IO to become writable!")
350
366
  end
@@ -357,6 +373,34 @@ module Async
357
373
  end
358
374
  end
359
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
+
360
404
  # Wait for the specified process ID to exit.
361
405
  #
362
406
  # @public Since *Async v2*.
@@ -370,6 +414,16 @@ module Async
370
414
  return @selector.process_wait(Fiber.current, pid, flags)
371
415
  end
372
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
+
373
427
  # Run one iteration of the event loop.
374
428
  #
375
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.
@@ -549,7 +603,7 @@ module Async
549
603
  # @parameter duration [Numeric] The time in seconds, in which the task should complete.
550
604
  # @parameter exception [Class] The exception class to raise.
551
605
  # @parameter message [String] The message to pass to the exception.
552
- # @yields {|duration| ...} The block to execute with a timeout.
606
+ # @yields {|timeout| ...} The block to execute with a timeout.
553
607
  def with_timeout(duration, exception = TimeoutError, message = "execution expired", &block)
554
608
  fiber = Fiber.current
555
609
 
@@ -559,7 +613,11 @@ module Async
559
613
  end
560
614
  end
561
615
 
562
- yield timer
616
+ if block.arity.zero?
617
+ yield
618
+ else
619
+ yield Timeout.new(@timers, timer)
620
+ end
563
621
  ensure
564
622
  timer&.cancel!
565
623
  end
@@ -574,7 +632,7 @@ module Async
574
632
  # @parameter message [String] The message to pass to the exception.
575
633
  # @yields {|duration| ...} The block to execute with a timeout.
576
634
  def timeout_after(duration, exception, message, &block)
577
- with_timeout(duration, exception, message) do |timer|
635
+ with_timeout(duration, exception, message) do
578
636
  yield duration
579
637
  end
580
638
  end
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"
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ module Async
7
+ # Represents a flexible timeout that can be rescheduled or extended.
8
+ # @public Since *Async v2.24*.
9
+ class Timeout
10
+ # Initialize a new timeout.
11
+ def initialize(timers, handle)
12
+ @timers = timers
13
+ @handle = handle
14
+ end
15
+
16
+ # @returns [Numeric] The time remaining until the timeout occurs, in seconds.
17
+ def duration
18
+ @handle.time - @timers.now
19
+ end
20
+
21
+ # Update the duration of the timeout.
22
+ #
23
+ # The duration is relative to the current time, e.g. setting the duration to 5 means the timeout will occur in 5 seconds from now.
24
+ #
25
+ # @parameter value [Numeric] The new duration to assign to the timeout, in seconds.
26
+ def duration=(value)
27
+ self.reschedule(@timers.now + value)
28
+ end
29
+
30
+ # Adjust the timeout by the specified duration.
31
+ #
32
+ # The duration is relative to the timeout time, e.g. adjusting the timeout by 5 increases the current duration by 5 seconds.
33
+ #
34
+ # @parameter duration [Numeric] The duration to adjust the timeout by, in seconds.
35
+ # @returns [Numeric] The new time at which the timeout will occur.
36
+ def adjust(duration)
37
+ self.reschedule(time + duration)
38
+ end
39
+
40
+ # @returns [Numeric] The time at which the timeout will occur, in seconds since {now}.
41
+ def time
42
+ @handle.time
43
+ end
44
+
45
+ # Assign a new time to the timeout, rescheduling it if necessary.
46
+ #
47
+ # @parameter value [Numeric] The new time to assign to the timeout.
48
+ # @returns [Numeric] The new time at which the timeout will occur.
49
+ def time=(value)
50
+ self.reschedule(value)
51
+ end
52
+
53
+ # @returns [Numeric] The current time in the scheduler, relative to the time of this timeout, in seconds.
54
+ def now
55
+ @timers.now
56
+ end
57
+
58
+ # Cancel the timeout, preventing it from executing.
59
+ def cancel!
60
+ @handle.cancel!
61
+ end
62
+
63
+ # @returns [Boolean] Whether the timeout has been cancelled.
64
+ def cancelled?
65
+ @handle.cancelled?
66
+ end
67
+
68
+ # Raised when attempting to reschedule a cancelled timeout.
69
+ class CancelledError < RuntimeError
70
+ end
71
+
72
+ # Reschedule the timeout to occur at the specified time.
73
+ #
74
+ # @parameter time [Numeric] The new time to schedule the timeout for.
75
+ # @returns [Numeric] The new time at which the timeout will occur.
76
+ private def reschedule(time)
77
+ if block = @handle&.block
78
+ @handle.cancel!
79
+
80
+ @handle = @timers.schedule(time, block)
81
+
82
+ return time
83
+ else
84
+ raise CancelledError, "Cannot reschedule a cancelled timeout!"
85
+ end
86
+ end
87
+ end
88
+ end
@@ -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.23.1"
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,18 @@ 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
+
44
+ ### v2.24.0
45
+
46
+ - Ruby v3.1 support is dropped.
47
+ - `Async::Wrapper` which was previously deprecated, is now removed.
48
+ - [Flexible Timeouts](https://socketry.github.io/async/releases/index#flexible-timeouts)
49
+
38
50
  ### v2.23.0
39
51
 
40
52
  - Rename `ASYNC_SCHEDULER_DEFAULT_WORKER_POOL` to `ASYNC_SCHEDULER_WORKER_POOL`.
data/releases.md CHANGED
@@ -1,5 +1,65 @@
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
+
30
+ ## v2.24.0
31
+
32
+ - Ruby v3.1 support is dropped.
33
+ - `Async::Wrapper` which was previously deprecated, is now removed.
34
+
35
+ ### Flexible Timeouts
36
+
37
+ When {ruby Async::Scheduler\#with\_timeout} is invoked with a block, it can receive a {ruby Async::Timeout} instance. This allows you to adjust or cancel the timeout while the block is executing. This is useful for long-running tasks that may need to adjust their timeout based on external factors.
38
+
39
+ ``` ruby
40
+ Async do
41
+ Async::Scheduler.with_timeout(5) do |timeout|
42
+ # Do some work that may take a while...
43
+
44
+ if some_condition
45
+ timeout.cancel! # Cancel the timeout
46
+ else
47
+ # Add 10 seconds to the current timeout:
48
+ timeout.adjust(10)
49
+
50
+ # Reduce the timeout by 10 seconds:
51
+ timeout.adjust(-10)
52
+
53
+ # Set the timeout to 10 seconds from now:
54
+ timeout.duration = 10
55
+
56
+ # Increase the current duration:
57
+ timeout.duration += 10
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
3
63
  ## v2.23.0
4
64
 
5
65
  - Rename `ASYNC_SCHEDULER_DEFAULT_WORKER_POOL` to `ASYNC_SCHEDULER_WORKER_POOL`.
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.23.1
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-03-10 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: []
@@ -157,12 +162,11 @@ files:
157
162
  - lib/async/semaphore.rb
158
163
  - lib/async/task.md
159
164
  - lib/async/task.rb
165
+ - lib/async/timeout.rb
160
166
  - lib/async/variable.rb
161
167
  - lib/async/version.rb
162
168
  - lib/async/waiter.md
163
169
  - lib/async/waiter.rb
164
- - lib/async/worker_pool.rb
165
- - lib/async/wrapper.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.19* 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
data/lib/async/wrapper.rb DELETED
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
5
- # Copyright, 2017, by Kent Gruber.
6
-
7
- warn "Async::Wrapper is deprecated and will be removed on 2025-03-31. Please use native interfaces instead.", uplevel: 1, category: :deprecated
8
-
9
- module Async
10
- # Represents an asynchronous IO within a reactor.
11
- # @deprecated With no replacement. Prefer native interfaces.
12
- class Wrapper
13
- # An exception that occurs when the asynchronous operation was cancelled.
14
- class Cancelled < StandardError
15
- end
16
-
17
- # @parameter io the native object to wrap.
18
- # @parameter reactor [Reactor] the reactor that is managing this wrapper, or not specified, it's looked up by way of {Task.current}.
19
- def initialize(io, reactor = nil)
20
- @io = io
21
- @reactor = reactor
22
-
23
- @timeout = nil
24
- end
25
-
26
- attr_accessor :reactor
27
-
28
- # Dup the underlying IO.
29
- def dup
30
- self.class.new(@io.dup)
31
- end
32
-
33
- # The underlying native `io`.
34
- attr :io
35
-
36
- # Wait for the io to become readable.
37
- def wait_readable(timeout = @timeout)
38
- @io.to_io.wait_readable(timeout) or raise TimeoutError
39
- end
40
-
41
- # Wait for the io to become writable.
42
- def wait_priority(timeout = @timeout)
43
- @io.to_io.wait_priority(timeout) or raise TimeoutError
44
- end
45
-
46
- # Wait for the io to become writable.
47
- def wait_writable(timeout = @timeout)
48
- @io.to_io.wait_writable(timeout) or raise TimeoutError
49
- end
50
-
51
- # Wait fo the io to become either readable or writable.
52
- # @parameter duration [Float] timeout after the given duration if not `nil`.
53
- def wait_any(timeout = @timeout)
54
- @io.to_io.wait(::IO::READABLE|::IO::WRITABLE|::IO::PRIORITY, timeout) or raise TimeoutError
55
- end
56
-
57
- # Close the underlying IO.
58
- def close
59
- @io.close
60
- end
61
-
62
- # Whether the underlying IO is closed.
63
- def closed?
64
- @io.closed?
65
- end
66
- end
67
- end