io-event 1.9.0 → 1.12.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/agent.md +47 -0
- data/ext/extconf.rb +18 -7
- data/ext/io/event/event.c +5 -4
- data/ext/io/event/event.h +4 -0
- data/ext/io/event/fiber.c +1 -1
- data/ext/io/event/selector/epoll.c +22 -0
- data/ext/io/event/selector/kqueue.c +23 -0
- data/ext/io/event/selector/selector.c +1 -6
- data/ext/io/event/selector/uring.c +44 -4
- data/ext/io/event/worker_pool.c +477 -0
- data/ext/io/event/{profiler.h → worker_pool.h} +1 -1
- data/ext/io/event/worker_pool_test.c +199 -0
- data/ext/io/event/worker_pool_test.h +9 -0
- data/lib/io/event/priority_heap.rb +1 -1
- data/lib/io/event/selector/select.rb +34 -103
- 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 +24 -3
- data/releases.md +38 -39
- data.tar.gz.sig +0 -0
- metadata +11 -8
- metadata.gz.sig +0 -0
- data/ext/io/event/profiler.c +0 -505
- data/lib/io/event/profiler.rb +0 -18
@@ -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
|
@@ -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
@@ -1,4 +1,4 @@
|
|
1
|
-
# 
|
1
|
+
# 
|
2
2
|
|
3
3
|
Provides low level cross-platform primitives for constructing event loops, with support for `select`, `kqueue`, `epoll` and `io_uring`.
|
4
4
|
|
@@ -18,13 +18,34 @@ 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
|
+
|
37
|
+
### v1.10.0
|
38
|
+
|
39
|
+
- `IO::Event::Profiler` is moved to dedicated gem: [fiber-profiler](https://github.com/socketry/fiber-profiler).
|
40
|
+
- Perform runtime checks for native selectors to ensure they are supported in the current environment. While compile-time checks determine availability, restrictions like seccomp and SELinux may still prevent them from working.
|
41
|
+
|
21
42
|
### v1.9.0
|
22
43
|
|
23
|
-
-
|
44
|
+
- Improved `IO::Event::Profiler` for detecting stalls.
|
24
45
|
|
25
46
|
### v1.8.0
|
26
47
|
|
27
|
-
-
|
48
|
+
- Detecting fibers that are stalling the event loop.
|
28
49
|
|
29
50
|
### v1.7.5
|
30
51
|
|
data/releases.md
CHANGED
@@ -1,56 +1,55 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
-
## v1.
|
3
|
+
## v1.11.2
|
4
4
|
|
5
|
-
|
5
|
+
- Fix Windows build.
|
6
6
|
|
7
|
-
|
7
|
+
## v1.11.1
|
8
8
|
|
9
|
-
|
10
|
-
|
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
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
Fiber.new do
|
15
|
-
sleep 1.0
|
16
|
-
end.transfer
|
13
|
+
### Introduce `IO::Event::WorkerPool` for off-loading blocking operations.
|
17
14
|
|
18
|
-
|
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()
|
19
33
|
```
|
20
34
|
|
21
|
-
|
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`.
|
22
36
|
|
23
|
-
|
24
|
-
|
25
|
-
-
|
37
|
+
## v1.10.2
|
38
|
+
|
39
|
+
- Improved consistency of handling closed IO when invoking `#select`.
|
40
|
+
|
41
|
+
## v1.10.0
|
42
|
+
|
43
|
+
- `IO::Event::Profiler` is moved to dedicated gem: [fiber-profiler](https://github.com/socketry/fiber-profiler).
|
44
|
+
- Perform runtime checks for native selectors to ensure they are supported in the current environment. While compile-time checks determine availability, restrictions like seccomp and SELinux may still prevent them from working.
|
45
|
+
|
46
|
+
## v1.9.0
|
26
47
|
|
27
|
-
|
48
|
+
- Improved `IO::Event::Profiler` for detecting stalls.
|
28
49
|
|
29
50
|
## v1.8.0
|
30
51
|
|
31
|
-
|
32
|
-
|
33
|
-
A new (experimental) feature for detecting fiber stalls has been added. This feature is disabled by default and can be enabled by setting the `IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD` to `true` or a floating point number representing the threshold in seconds.
|
34
|
-
|
35
|
-
When enabled, the event loop will measure and profile user code when resuming a fiber. If the fiber takes too long to return back to the event loop, the event loop will log a warning message with a profile of the fiber's execution.
|
36
|
-
|
37
|
-
> cat test.rb
|
38
|
-
#!/usr/bin/env ruby
|
39
|
-
|
40
|
-
require_relative "lib/async"
|
41
|
-
|
42
|
-
Async do
|
43
|
-
Fiber.blocking do
|
44
|
-
sleep 1
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
> IO_EVENT_SELECTOR_STALL_LOG_THRESHOLD=true bundle exec ./test.rb
|
49
|
-
Fiber stalled for 1.003 seconds
|
50
|
-
/home/samuel/Developer/socketry/async/test.rb:6 in '#<Class:Fiber>#blocking' (1s)
|
51
|
-
/home/samuel/Developer/socketry/async/test.rb:7 in 'Kernel#sleep' (1s)
|
52
|
-
|
53
|
-
There is a performance overhead to this feature, so it is recommended to only enable it when debugging performance issues.
|
52
|
+
- Detecting fibers that are stalling the event loop.
|
54
53
|
|
55
54
|
## v1.7.5
|
56
55
|
|
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.0
|
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
|
@@ -63,8 +65,6 @@ files:
|
|
63
65
|
- ext/io/event/interrupt.c
|
64
66
|
- ext/io/event/interrupt.h
|
65
67
|
- ext/io/event/list.h
|
66
|
-
- ext/io/event/profiler.c
|
67
|
-
- ext/io/event/profiler.h
|
68
68
|
- ext/io/event/selector/epoll.c
|
69
69
|
- ext/io/event/selector/epoll.h
|
70
70
|
- ext/io/event/selector/kqueue.c
|
@@ -76,12 +76,15 @@ files:
|
|
76
76
|
- ext/io/event/selector/uring.h
|
77
77
|
- ext/io/event/time.c
|
78
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
|
79
83
|
- lib/io/event.rb
|
80
84
|
- lib/io/event/debug/selector.rb
|
81
85
|
- lib/io/event/interrupt.rb
|
82
86
|
- lib/io/event/native.rb
|
83
87
|
- lib/io/event/priority_heap.rb
|
84
|
-
- lib/io/event/profiler.rb
|
85
88
|
- lib/io/event/selector.rb
|
86
89
|
- lib/io/event/selector/nonblock.rb
|
87
90
|
- lib/io/event/selector/select.rb
|
@@ -104,14 +107,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
107
|
requirements:
|
105
108
|
- - ">="
|
106
109
|
- !ruby/object:Gem::Version
|
107
|
-
version:
|
110
|
+
version: 3.2.6
|
108
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
112
|
requirements:
|
110
113
|
- - ">="
|
111
114
|
- !ruby/object:Gem::Version
|
112
115
|
version: '0'
|
113
116
|
requirements: []
|
114
|
-
rubygems_version: 3.6.
|
117
|
+
rubygems_version: 3.6.7
|
115
118
|
specification_version: 4
|
116
119
|
summary: An event loop.
|
117
120
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|