async 2.44.1 → 2.45.1

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: 0a98ba175586bacb13a3f00ccb770083208e4c8381cbaf17ce4f59fdfc56a85a
4
- data.tar.gz: 6a8614f33cbfa4230e66e12750308a1a339d9a2d6fe8a624b015498c5bc60928
3
+ metadata.gz: f51489757a93062c6aa4ccf5ad1f3c7367c4a374166dc704d42168f13f539c86
4
+ data.tar.gz: 056c11094c9ea4bffc8c5b808a50a3af4bc699b24497cd3f9c8ea3afe3745cfb
5
5
  SHA512:
6
- metadata.gz: 0d3c4cd7e58f0eb601e37ffbf8d415c6b66349f268f2b9bec37ed4a945744409ecdb21f13e101cacba2794ea1dd3e6dd3be7a5cf7ca6cc5e67cd557bd65cd11e
7
- data.tar.gz: dc2c1d73dc53df633260e279a20f8cbcb4c40c94b362fc383495e020f750807624c2de5bff446732585b5e2b4121b0ec382de65983db3af23301e937b96b4074
6
+ metadata.gz: 62c77f5b6dbb84291d2435e0098965d6d78b6dd906f8fd538adb5139240f18dc50484c89b0b7f0772aa0ad85139d3c31a38cbe36425d38f21dec7a310d22cae2
7
+ data.tar.gz: 5e356c8c7754bccece84388bb95ed6f9860924279d5f084673b62c6d0a1b549bdabb19e47b0b05748444b2f43e37d21addf70ed01b39aa5f994531b7676b4648
checksums.yaml.gz.sig CHANGED
Binary file
@@ -307,12 +307,15 @@ module Async
307
307
  # @parameter io [IO] The IO object to wait on.
308
308
  # @parameter events [Integer] The events to wait for, e.g. `IO::READABLE`, `IO::WRITABLE`, etc.
309
309
  # @parameter timeout [Float | Nil] The maximum time to wait, or if nil, indefinitely.
310
+ # @returns [Integer | false] The subset of events that are ready, or `false` if the timeout expires.
310
311
  def io_wait(io, events, timeout = nil)
311
312
  fiber = Fiber.current
313
+ expired = false
312
314
 
313
315
  if timeout
314
316
  # If an explicit timeout is specified, we expect that the user will handle it themselves:
315
317
  timer = @timers.after(timeout) do
318
+ expired = true
316
319
  fiber.transfer
317
320
  end
318
321
  elsif timeout = io.timeout
@@ -322,22 +325,28 @@ module Async
322
325
  end
323
326
  end
324
327
 
325
- return @selector.io_wait(fiber, io, events)
328
+ # A selector wait may return a falsy result when the fiber is resumed without the requested IO becoming ready. For example, a deferred unblock from a previous blocking operation may arrive after the fiber has moved on to this wait. Retry these stale or spurious wake-ups without resetting the original timer.
329
+ until result = @selector.io_wait(fiber, io, events)
330
+ # If the original timer resumed the fiber, the false result represents the timeout rather than a spurious wake-up:
331
+ return false if expired
332
+ end
333
+
334
+ return result
326
335
  ensure
327
336
  timer&.cancel!
328
337
  end
329
338
 
330
339
  if ::IO::Event::Support.buffer?
331
- # Read from the specified IO into the buffer.
340
+ # Read at most the specified number of bytes from the IO into the buffer.
332
341
  #
333
342
  # @public Since *Async v2* and Ruby with `IO::Buffer` support.
334
343
  # @asynchronous May be non-blocking.
335
344
  #
336
345
  # @parameter io [IO] The IO object to read from.
337
346
  # @parameter buffer [IO::Buffer] The buffer to read into.
338
- # @parameter length [Integer] The minimum number of bytes to read.
339
347
  # @parameter offset [Integer] The offset within the buffer to read into.
340
- def io_read(io, buffer, length, offset = 0)
348
+ # @parameter length [Integer] The maximum number of bytes to read.
349
+ def io_read(io, buffer, offset, length)
341
350
  fiber = Fiber.current
342
351
 
343
352
  if timeout = io.timeout
@@ -346,22 +355,22 @@ module Async
346
355
  end
347
356
  end
348
357
 
349
- @selector.io_read(fiber, io, buffer, length, offset)
358
+ @selector.io_read(fiber, io, buffer, offset, length)
350
359
  ensure
351
360
  timer&.cancel!
352
361
  end
353
362
 
354
363
  if RUBY_ENGINE != "ruby" || RUBY_VERSION >= "3.3.1"
355
- # Write the specified buffer to the IO.
364
+ # Write at most the specified number of bytes from the buffer to the IO.
356
365
  #
357
366
  # @public Since *Async v2* and *Ruby v3.3.1* with `IO::Buffer` support.
358
367
  # @asynchronous May be non-blocking.
359
368
  #
360
369
  # @parameter io [IO] The IO object to write to.
361
370
  # @parameter buffer [IO::Buffer] The buffer to write from.
362
- # @parameter length [Integer] The minimum number of bytes to write.
363
371
  # @parameter offset [Integer] The offset within the buffer to write from.
364
- def io_write(io, buffer, length, offset = 0)
372
+ # @parameter length [Integer] The maximum number of bytes to write.
373
+ def io_write(io, buffer, offset, length)
365
374
  fiber = Fiber.current
366
375
 
367
376
  if timeout = io.timeout
@@ -370,7 +379,7 @@ module Async
370
379
  end
371
380
  end
372
381
 
373
- @selector.io_write(fiber, io, buffer, length, offset)
382
+ @selector.io_write(fiber, io, buffer, offset, length)
374
383
  ensure
375
384
  timer&.cancel!
376
385
  end
@@ -415,7 +424,14 @@ module Async
415
424
  # @returns [Process::Status] A process status instance.
416
425
  # @asynchronous May be non-blocking..
417
426
  def process_wait(pid, flags)
418
- return @selector.process_wait(Fiber.current, pid, flags)
427
+ fiber = Fiber.current
428
+
429
+ # A native process wait may be interrupted before the child exits. io-event reports this as `false` and leaves the retry policy to the scheduler. Retry only `false`, since `nil` is a legitimate result for `Process::WNOHANG`.
430
+ while true
431
+ status = @selector.process_wait(fiber, pid, flags)
432
+
433
+ return status unless status == false
434
+ end
419
435
  end
420
436
 
421
437
  # Wait for the specified IOs to become ready for the specified events.
data/lib/async/version.rb CHANGED
@@ -5,5 +5,5 @@
5
5
 
6
6
  # @namespace
7
7
  module Async
8
- VERSION = "2.44.1"
8
+ VERSION = "2.45.1"
9
9
  end
data/readme.md CHANGED
@@ -35,6 +35,14 @@ 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.45.1
39
+
40
+ - Fixed `Scheduler#io_wait` returning `nil` instead of `false` when an explicit timeout expired. Native callers such as `Socket#connect` with `connect_timeout:` distinguish a timeout by checking for `false`, so the `nil` caused `TypeError: no implicit conversion from nil to integer` instead of the intended `IO::TimeoutError`.
41
+
42
+ ### v2.45.0
43
+
44
+ - Fixed scheduler I/O and process waits returning prematurely after stale or interrupted wake-ups. I/O waits now preserve their original timeout, while blocking process waits retry and non-blocking `Process::WNOHANG` waits still return `nil`.
45
+
38
46
  ### v2.44.0
39
47
 
40
48
  - Fixed scheduler cleanup after forking while other fibers are blocked.
@@ -68,16 +76,6 @@ Please see the [project releases](https://socketry.github.io/async/releases/inde
68
76
  - Rename `Task#stop` to `Task#cancel` for better clarity and consistency with common concurrency terminology. The old `stop` method is still available as an alias for backward compatibility, but it is recommended to use `cancel` going forward.
69
77
  - Forward arguments from `Task#wait` -\> `Promise#wait`, so `task.wait(timeout: N)` is supported.
70
78
 
71
- ### v2.37.0
72
-
73
- - Introduce `Async::Loop` for robust, time-aligned loops.
74
- - Add support for `Async::Promise#wait(timeout: N)`.
75
-
76
- ### v2.36.0
77
-
78
- - Introduce `Task#wait_all` which recursively waits for all children and self, excepting the current task.
79
- - Introduce `Task#join` as an alias for `Task#wait` for compatibility with `Thread#join` and similar interfaces.
80
-
81
79
  ## See Also
82
80
 
83
81
  - [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client/server.
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v2.45.1
4
+
5
+ - Fixed `Scheduler#io_wait` returning `nil` instead of `false` when an explicit timeout expired. Native callers such as `Socket#connect` with `connect_timeout:` distinguish a timeout by checking for `false`, so the `nil` caused `TypeError: no implicit conversion from nil to integer` instead of the intended `IO::TimeoutError`.
6
+
7
+ ## v2.45.0
8
+
9
+ - Fixed scheduler I/O and process waits returning prematurely after stale or interrupted wake-ups. I/O waits now preserve their original timeout, while blocking process waits retry and non-blocking `Process::WNOHANG` waits still return `nil`.
10
+
3
11
  ## v2.44.0
4
12
 
5
13
  - Fixed scheduler cleanup after forking while other fibers are blocked.
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.44.1
4
+ version: 2.45.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -109,14 +109,14 @@ dependencies:
109
109
  requirements:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: '1.11'
112
+ version: '1.21'
113
113
  type: :runtime
114
114
  prerelease: false
115
115
  version_requirements: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - "~>"
118
118
  - !ruby/object:Gem::Version
119
- version: '1.11'
119
+ version: '1.21'
120
120
  executables: []
121
121
  extensions: []
122
122
  extra_rdoc_files: []
metadata.gz.sig CHANGED
Binary file