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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/agent.md +47 -0
- data/ext/extconf.rb +17 -6
- data/ext/io/event/event.c +5 -1
- data/ext/io/event/event.h +4 -0
- data/ext/io/event/fiber.c +1 -1
- data/ext/io/event/selector/epoll.c +4 -0
- data/ext/io/event/selector/kqueue.c +4 -0
- data/ext/io/event/selector/selector.c +1 -6
- data/ext/io/event/selector/uring.c +25 -4
- data/ext/io/event/worker_pool.c +477 -0
- data/ext/io/event/worker_pool.h +8 -0
- data/ext/io/event/worker_pool_test.c +199 -0
- data/ext/io/event/worker_pool_test.h +9 -0
- data/lib/io/event/debug/selector.rb +2 -2
- data/lib/io/event/priority_heap.rb +1 -1
- data/lib/io/event/selector/select.rb +36 -105
- data/lib/io/event/support.rb +11 -18
- data/lib/io/event/timers.rb +1 -2
- data/lib/io/event/version.rb +1 -1
- data/license.md +2 -1
- data/readme.md +16 -0
- data/releases.md +38 -0
- data.tar.gz.sig +0 -0
- metadata +11 -5
- metadata.gz.sig +0 -0
@@ -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? {
|
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-
|
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.
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
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
|
|
data/lib/io/event/support.rb
CHANGED
@@ -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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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)
|
data/lib/io/event/timers.rb
CHANGED
@@ -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.
|
data/lib/io/event/version.rb
CHANGED
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.
|
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
|
-
-
|
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:
|
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:
|
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.
|
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
|