io-event 1.10.0 → 1.12.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.
@@ -82,7 +82,7 @@ class IO
82
82
  # Validate the heap invariant. Every element except the root must not be smaller than its parent element. Note that it MAY be equal.
83
83
  def valid?
84
84
  # Notice we skip index 0 on purpose, because it has no parent
85
- (1..(@contents.size - 1)).all? { |e| @contents[e] >= @contents[(e - 1) / 2] }
85
+ (1..(@contents.size - 1)).all? {|index| @contents[index] >= @contents[(index - 1) / 2]}
86
86
  end
87
87
 
88
88
  private
@@ -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
  # Copyright, 2023, by Math Ieu.
6
6
 
7
7
  require_relative "../interrupt"
@@ -17,6 +17,9 @@ module IO::Event
17
17
 
18
18
  @waiting = Hash.new.compare_by_identity
19
19
 
20
+ # Flag indicating whether the selector is currently blocked in a system call.
21
+ # Set to true when blocked in ::IO.select, false otherwise.
22
+ # Used by wakeup() to determine if an interrupt signal is needed.
20
23
  @blocked = false
21
24
 
22
25
  @ready = Queue.new
@@ -95,11 +98,11 @@ module IO::Event
95
98
  end
96
99
 
97
100
  # Transfer to the given fiber and raise an exception. Put the current fiber into the ready list.
98
- def raise(fiber, *arguments)
101
+ def raise(fiber, *arguments, **options)
99
102
  optional = Optional.new(Fiber.current)
100
103
  @ready.push(optional)
101
104
 
102
- fiber.raise(*arguments)
105
+ fiber.raise(*arguments, **options)
103
106
  ensure
104
107
  optional.nullify
105
108
  end
@@ -196,7 +199,7 @@ module IO::Event
196
199
  result = Fiber.blocking{buffer.read(io, 0, offset)}
197
200
 
198
201
  if result < 0
199
- if again?(result)
202
+ if length > 0 and again?(result)
200
203
  self.io_wait(fiber, io, IO::READABLE)
201
204
  else
202
205
  return result
@@ -226,7 +229,7 @@ module IO::Event
226
229
  result = Fiber.blocking{buffer.write(io, 0, offset)}
227
230
 
228
231
  if result < 0
229
- if again?(result)
232
+ if length > 0 and again?(result)
230
233
  self.io_wait(fiber, io, IO::READABLE)
231
234
  else
232
235
  return result
@@ -302,96 +305,14 @@ module IO::Event
302
305
 
303
306
  return total
304
307
  end
305
- elsif Support.fiber_scheduler_v1?
306
- # Ruby <= 3.1, limited IO::Buffer support.
307
- def io_read(fiber, _io, buffer, length, offset = 0)
308
- # We need to avoid any internal buffering, so we use a duplicated IO object:
309
- io = IO.for_fd(_io.fileno, autoclose: false)
310
-
311
- total = 0
312
-
313
- maximum_size = buffer.size - offset
314
- while maximum_size > 0
315
- case result = blocking{io.read_nonblock(maximum_size, exception: false)}
316
- when :wait_readable
317
- if length > 0
318
- self.io_wait(fiber, io, IO::READABLE)
319
- else
320
- return EWOULDBLOCK
321
- end
322
- when :wait_writable
323
- if length > 0
324
- self.io_wait(fiber, io, IO::WRITABLE)
325
- else
326
- return EWOULDBLOCK
327
- end
328
- when nil
329
- break
330
- else
331
- buffer.set_string(result, offset)
332
-
333
- size = result.bytesize
334
- total += size
335
- offset += size
336
- break if size >= length
337
- length -= size
338
- end
339
-
340
- maximum_size = buffer.size - offset
341
- end
342
-
343
- return total
344
- rescue IOError => error
345
- return -Errno::EBADF::Errno
346
- rescue SystemCallError => error
347
- return -error.errno
348
- end
349
-
350
- def io_write(fiber, _io, buffer, length, offset = 0)
351
- # We need to avoid any internal buffering, so we use a duplicated IO object:
352
- io = IO.for_fd(_io.fileno, autoclose: false)
353
-
354
- total = 0
355
-
356
- maximum_size = buffer.size - offset
357
- while maximum_size > 0
358
- chunk = buffer.get_string(offset, maximum_size)
359
- case result = blocking{io.write_nonblock(chunk, exception: false)}
360
- when :wait_readable
361
- if length > 0
362
- self.io_wait(fiber, io, IO::READABLE)
363
- else
364
- return EWOULDBLOCK
365
- end
366
- when :wait_writable
367
- if length > 0
368
- self.io_wait(fiber, io, IO::WRITABLE)
369
- else
370
- return EWOULDBLOCK
371
- end
372
- else
373
- total += result
374
- offset += result
375
- break if result >= length
376
- length -= result
377
- end
378
-
379
- maximum_size = buffer.size - offset
380
- end
381
-
382
- return total
383
- rescue IOError => error
384
- return -Errno::EBADF::Errno
385
- rescue SystemCallError => error
386
- return -error.errno
387
- end
388
-
389
- def blocking(&block)
390
- fiber = Fiber.new(blocking: true, &block)
391
- return fiber.resume(fiber)
392
- end
393
308
  end
394
309
 
310
+ # Wait for a process to change state.
311
+ #
312
+ # @parameter fiber [Fiber] The fiber to resume after waiting.
313
+ # @parameter pid [Integer] The process ID to wait for.
314
+ # @parameter flags [Integer] Flags to pass to Process::Status.wait.
315
+ # @returns [Process::Status] The status of the waited process.
395
316
  def process_wait(fiber, pid, flags)
396
317
  Thread.new do
397
318
  Process::Status.wait(pid, flags)
@@ -411,6 +332,10 @@ module IO::Event
411
332
  end
412
333
  end
413
334
 
335
+ # Wait for IO events or a timeout.
336
+ #
337
+ # @parameter duration [Numeric | Nil] The maximum time to wait, or nil for no timeout.
338
+ # @returns [Integer] The number of ready IO objects.
414
339
  def select(duration = nil)
415
340
  if pop_ready
416
341
  # If we have popped items from the ready list, they may influence the duration calculation, so we don't delay the event loop:
@@ -421,19 +346,25 @@ module IO::Event
421
346
  writable = Array.new
422
347
  priority = Array.new
423
348
 
424
- @waiting.each do |io, waiter|
425
- waiter.each do |fiber, events|
426
- if (events & IO::READABLE) > 0
427
- readable << io
428
- end
429
-
430
- if (events & IO::WRITABLE) > 0
431
- writable << io
432
- end
433
-
434
- if (events & IO::PRIORITY) > 0
435
- priority << io
349
+ @waiting.delete_if do |io, waiter|
350
+ if io.closed?
351
+ true
352
+ else
353
+ waiter.each do |fiber, events|
354
+ if (events & IO::READABLE) > 0
355
+ readable << io
356
+ end
357
+
358
+ if (events & IO::WRITABLE) > 0
359
+ writable << io
360
+ end
361
+
362
+ if (events & IO::PRIORITY) > 0
363
+ priority << io
364
+ end
436
365
  end
366
+
367
+ false
437
368
  end
438
369
  end
439
370
 
@@ -14,35 +14,28 @@ class IO
14
14
  IO.const_defined?(:Buffer)
15
15
  end
16
16
 
17
- # The basic fiber scheduler was introduced along side the IO::Buffer class.
18
- #
19
- # @returns [Boolean] Whether the IO::Buffer class is available.
20
- #
21
- # To be removed on 31 Mar 2025.
22
- def self.fiber_scheduler_v1?
23
- IO.const_defined?(:Buffer)
24
- end
25
-
26
17
  # More advanced read/write methods and blocking controls were introduced in Ruby 3.2.
27
18
  #
28
19
  # To be removed on 31 Mar 2026.
29
20
  def self.fiber_scheduler_v2?
30
- # Some interface changes were back-ported incorrectly:
31
- # https://github.com/ruby/ruby/pull/10778
32
- # Specifically "Improvements to IO::Buffer read/write/pread/pwrite."
33
- # Missing correct size calculation.
34
- return false if RUBY_VERSION >= "3.2.5"
35
-
36
- IO.const_defined?(:Buffer) and Fiber.respond_to?(:blocking) and IO::Buffer.instance_method(:read).arity == -1
21
+ if RUBY_VERSION >= "3.2"
22
+ return true if RUBY_VERSION >= "3.2.6"
23
+
24
+ # Some interface changes were back-ported incorrectly and released in 3.2.5 <https://github.com/ruby/ruby/pull/10778> - Specifically "Improvements to IO::Buffer read/write/pread/pwrite." is missing correct size calculation.
25
+ return false if RUBY_VERSION >= "3.2.5"
26
+
27
+ # Feature detection:
28
+ IO.const_defined?(:Buffer) and Fiber.respond_to?(:blocking) and IO::Buffer.instance_method(:read).arity == -1
29
+ end
37
30
  end
38
31
 
39
32
  # Updated inferfaces for read/write and IO::Buffer were introduced in Ruby 3.3, including pread/pwrite.
40
33
  #
41
34
  # To become the default 31 Mar 2026.
42
35
  def self.fiber_scheduler_v3?
36
+ return true if RUBY_VERSION >= "3.3"
37
+
43
38
  if fiber_scheduler_v2?
44
- return true if RUBY_VERSION >= "3.3"
45
-
46
39
  # Feature detection if required:
47
40
  begin
48
41
  IO::Buffer.new.slice(0, 0).write(STDOUT)
@@ -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 "priority_heap"
7
7
 
@@ -91,7 +91,6 @@ class IO
91
91
  schedule(self.now + offset.to_f, block)
92
92
  end
93
93
 
94
-
95
94
  # Compute the time interval until the next timer fires.
96
95
  #
97
96
  # @parameter now [Float] The current time.
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.10.0"
10
+ VERSION = "1.12.1"
11
11
  end
12
12
  end
data/license.md CHANGED
@@ -9,8 +9,9 @@ Copyright, 2022, by Bruno Sutic.
9
9
  Copyright, 2023, by Math Ieu.
10
10
  Copyright, 2024, by Pavel Rosický.
11
11
  Copyright, 2024, by Anthony Ross.
12
- Copyright, 2024, by Shizuo Fujita.
12
+ Copyright, 2024-2025, by Shizuo Fujita.
13
13
  Copyright, 2024, by Jean Boussier.
14
+ Copyright, 2025, by Stanislav (Stas) Katkov.
14
15
 
15
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
16
17
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -18,6 +18,22 @@ Please see the [project documentation](https://socketry.github.io/io-event/) for
18
18
 
19
19
  Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
20
20
 
21
+ ### v1.11.2
22
+
23
+ - Fix Windows build.
24
+
25
+ ### v1.11.1
26
+
27
+ - Fix `read_nonblock` when using the `URing` selector, which was not handling zero-length reads correctly. This allows reading available data without blocking.
28
+
29
+ ### v1.11.0
30
+
31
+ - [Introduce `IO::Event::WorkerPool` for off-loading blocking operations.](https://socketry.github.io/io-event/releases/index#introduce-io::event::workerpool-for-off-loading-blocking-operations.)
32
+
33
+ ### v1.10.2
34
+
35
+ - Improved consistency of handling closed IO when invoking `#select`.
36
+
21
37
  ### v1.10.0
22
38
 
23
39
  - `IO::Event::Profiler` is moved to dedicated gem: [fiber-profiler](https://github.com/socketry/fiber-profiler).
data/releases.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Releases
2
2
 
3
+ ## v1.11.2
4
+
5
+ - Fix Windows build.
6
+
7
+ ## v1.11.1
8
+
9
+ - Fix `read_nonblock` when using the `URing` selector, which was not handling zero-length reads correctly. This allows reading available data without blocking.
10
+
11
+ ## v1.11.0
12
+
13
+ ### Introduce `IO::Event::WorkerPool` for off-loading blocking operations.
14
+
15
+ The {ruby IO::Event::WorkerPool} provides a mechanism for executing blocking operations on separate OS threads while properly integrating with Ruby's fiber scheduler and GVL (Global VM Lock) management. This enables true parallelism for CPU-intensive or blocking operations that would otherwise block the event loop.
16
+
17
+ ``` ruby
18
+ # Fiber scheduler integration via blocking_operation_wait hook
19
+ class MyScheduler
20
+ def initialize
21
+ @worker_pool = IO::Event::WorkerPool.new
22
+ end
23
+
24
+ def blocking_operation_wait(operation)
25
+ @worker_pool.call(operation)
26
+ end
27
+ end
28
+
29
+ # Usage with automatic offloading
30
+ Fiber.set_scheduler(MyScheduler.new)
31
+ # Automatically offload `rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)` to a background thread:
32
+ result = some_blocking_operation()
33
+ ```
34
+
35
+ The implementation uses one or more background threads and a list of pending blocking operations. Those operations either execute through to completion or may be cancelled, which executes the "unblock function" provided to `rb_nogvl`.
36
+
37
+ ## v1.10.2
38
+
39
+ - Improved consistency of handling closed IO when invoking `#select`.
40
+
3
41
  ## v1.10.0
4
42
 
5
43
  - `IO::Event::Profiler` is moved to dedicated gem: [fiber-profiler](https://github.com/socketry/fiber-profiler).
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -10,11 +10,12 @@ authors:
10
10
  - Jean Boussier
11
11
  - Benoit Daloze
12
12
  - Bruno Sutic
13
+ - Shizuo Fujita
13
14
  - Alex Matchneer
14
15
  - Anthony Ross
15
16
  - Delton Ding
16
17
  - Pavel Rosický
17
- - Shizuo Fujita
18
+ - Stanislav (Stas) Katkov
18
19
  bindir: bin
19
20
  cert_chain:
20
21
  - |
@@ -46,13 +47,14 @@ cert_chain:
46
47
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
47
48
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
48
49
  -----END CERTIFICATE-----
49
- date: 2025-03-12 00:00:00.000000000 Z
50
+ date: 1980-01-02 00:00:00.000000000 Z
50
51
  dependencies: []
51
52
  executables: []
52
53
  extensions:
53
54
  - ext/extconf.rb
54
55
  extra_rdoc_files: []
55
56
  files:
57
+ - agent.md
56
58
  - design.md
57
59
  - ext/extconf.rb
58
60
  - ext/io/event/array.h
@@ -74,6 +76,10 @@ files:
74
76
  - ext/io/event/selector/uring.h
75
77
  - ext/io/event/time.c
76
78
  - ext/io/event/time.h
79
+ - ext/io/event/worker_pool.c
80
+ - ext/io/event/worker_pool.h
81
+ - ext/io/event/worker_pool_test.c
82
+ - ext/io/event/worker_pool_test.h
77
83
  - lib/io/event.rb
78
84
  - lib/io/event/debug/selector.rb
79
85
  - lib/io/event/interrupt.rb
@@ -101,14 +107,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
107
  requirements:
102
108
  - - ">="
103
109
  - !ruby/object:Gem::Version
104
- version: '3.1'
110
+ version: 3.2.6
105
111
  required_rubygems_version: !ruby/object:Gem::Requirement
106
112
  requirements:
107
113
  - - ">="
108
114
  - !ruby/object:Gem::Version
109
115
  version: '0'
110
116
  requirements: []
111
- rubygems_version: 3.6.2
117
+ rubygems_version: 3.6.7
112
118
  specification_version: 4
113
119
  summary: An event loop.
114
120
  test_files: []
metadata.gz.sig CHANGED
Binary file