io-event 1.16.1 → 1.16.3
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/ext/extconf.rb +1 -1
- data/lib/io/event/debug/selector.rb +2 -0
- data/lib/io/event/interrupt.rb +2 -1
- data/lib/io/event/priority_heap.rb +23 -5
- data/lib/io/event/selector/select.rb +8 -0
- data/lib/io/event/selector.rb +6 -9
- data/lib/io/event/timers.rb +72 -4
- data/lib/io/event/version.rb +1 -1
- data/readme.md +8 -8
- data/releases.md +8 -0
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7823ee584acc17c23f0f464a60271449a4298733efa62623653420b9181a5201
|
|
4
|
+
data.tar.gz: c71e15d6f35113a68bf31442621d2a4b9f8e8ca8d9b0d27dae234c30005f4ae0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60764fbe8a01ce3a77d8ad796473934f41a92edf30772265d4f0abbaf89bb9b1b79e1776cd9071111e0ff2590fc9402892cbaea1e22c779f9f3fdfe5ca18b9e9
|
|
7
|
+
data.tar.gz: 452f83f2b2a7550b6aa01f1d5094b62171e2a6179d8760651c3275397be65fb653adf12c8db1c3470d5cdcdbacf88e48dd208f01b53e2f0ed50eac91d104c06e
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/ext/extconf.rb
CHANGED
|
@@ -55,7 +55,7 @@ have_func("rb_io_descriptor")
|
|
|
55
55
|
have_func("&rb_process_status_wait")
|
|
56
56
|
have_func("rb_fiber_current")
|
|
57
57
|
have_func("&rb_fiber_raise")
|
|
58
|
-
have_func("epoll_pwait2") if enable_config("epoll_pwait2", true)
|
|
58
|
+
have_func("epoll_pwait2(0, 0, 0, 0, 0)", "sys/epoll.h") if enable_config("epoll_pwait2", true)
|
|
59
59
|
|
|
60
60
|
have_header("ruby/io/buffer.h")
|
|
61
61
|
|
data/lib/io/event/interrupt.rb
CHANGED
|
@@ -20,6 +20,8 @@ module IO::Event
|
|
|
20
20
|
@input.read_nonblock(1)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
+
rescue IOError
|
|
24
|
+
# This is expected on shutdown.
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
@fiber.transfer
|
|
@@ -36,7 +38,6 @@ module IO::Event
|
|
|
36
38
|
def close
|
|
37
39
|
@input.close
|
|
38
40
|
@output.close
|
|
39
|
-
# @fiber.raise(::Interrupt)
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
|
|
@@ -10,6 +10,8 @@ class IO
|
|
|
10
10
|
# of its contents to determine priority.
|
|
11
11
|
# See <https://en.wikipedia.org/wiki/Binary_heap> for explanations of the main methods.
|
|
12
12
|
class PriorityHeap
|
|
13
|
+
HEAPIFY_INSERT_RATIO = 2
|
|
14
|
+
|
|
13
15
|
# Initializes the heap.
|
|
14
16
|
def initialize
|
|
15
17
|
# The heap is represented with an array containing a binary tree. See
|
|
@@ -79,18 +81,34 @@ class IO
|
|
|
79
81
|
return self
|
|
80
82
|
end
|
|
81
83
|
|
|
82
|
-
# Add multiple elements to the heap efficiently
|
|
83
|
-
# This is more efficient than calling push multiple times (O(n log n)).
|
|
84
|
+
# Add multiple elements to the heap efficiently.
|
|
84
85
|
#
|
|
85
86
|
# @parameter elements [Array] The elements to add to the heap.
|
|
86
87
|
# @returns [self] Returns self for method chaining.
|
|
87
88
|
def concat(elements)
|
|
88
89
|
return self if elements.empty?
|
|
89
90
|
|
|
90
|
-
#
|
|
91
|
-
@contents.
|
|
91
|
+
# Rebuilding the whole heap is `O(n + m)`, where `n` is the existing heap size and `m` is the appended batch size. Incremental `push` is `O(m log(n))`, but is often closer to `O(m)` when appended elements are later than the existing entries and do not bubble far. Prefer `heapify` only when building from empty or when the batch dominates the existing heap.
|
|
92
|
+
if @contents.empty? || elements.size > @contents.size * HEAPIFY_INSERT_RATIO
|
|
93
|
+
@contents.concat(elements)
|
|
94
|
+
heapify!
|
|
95
|
+
else
|
|
96
|
+
elements.each{|element| push(element)}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
return self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Mutate the heap contents directly, then rebuild the heap property.
|
|
103
|
+
#
|
|
104
|
+
# This supports batched operations that can be completed with a single `O(n)` heapify instead of multiple `O(log n)` heap operations.
|
|
105
|
+
#
|
|
106
|
+
# @yields {|contents| ...} The heap contents array.
|
|
107
|
+
# @returns [self] Returns self for method chaining.
|
|
108
|
+
def heapify
|
|
109
|
+
yield @contents
|
|
92
110
|
|
|
93
|
-
#
|
|
111
|
+
# The block may arbitrarily append, delete or reorder contents, so repair the invariant with one `O(n)` bottom-up heapify pass.
|
|
94
112
|
heapify!
|
|
95
113
|
|
|
96
114
|
return self
|
|
@@ -52,15 +52,19 @@ module IO::Event
|
|
|
52
52
|
@waiting = nil
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# An optional reference to a fiber which can be cleared before it is resumed.
|
|
55
56
|
Optional = Struct.new(:fiber) do
|
|
57
|
+
# Transfer control to the fiber if it is still available.
|
|
56
58
|
def transfer(*arguments)
|
|
57
59
|
fiber&.transfer(*arguments)
|
|
58
60
|
end
|
|
59
61
|
|
|
62
|
+
# @returns [Boolean | Nil] Whether the referenced fiber is still alive.
|
|
60
63
|
def alive?
|
|
61
64
|
fiber&.alive?
|
|
62
65
|
end
|
|
63
66
|
|
|
67
|
+
# Clear the referenced fiber.
|
|
64
68
|
def nullify
|
|
65
69
|
self.fiber = nil
|
|
66
70
|
end
|
|
@@ -111,7 +115,9 @@ module IO::Event
|
|
|
111
115
|
!@ready.empty?
|
|
112
116
|
end
|
|
113
117
|
|
|
118
|
+
# A linked list node used to track fibers waiting for IO events.
|
|
114
119
|
Waiter = Struct.new(:fiber, :events, :tail) do
|
|
120
|
+
# @returns [Boolean | Nil] Whether the waiting fiber is still alive.
|
|
115
121
|
def alive?
|
|
116
122
|
self.fiber&.alive?
|
|
117
123
|
end
|
|
@@ -138,10 +144,12 @@ module IO::Event
|
|
|
138
144
|
tail&.dispatch(events, &reactivate)
|
|
139
145
|
end
|
|
140
146
|
|
|
147
|
+
# Clear the waiting fiber so it will not be resumed.
|
|
141
148
|
def invalidate
|
|
142
149
|
self.fiber = nil
|
|
143
150
|
end
|
|
144
151
|
|
|
152
|
+
# Iterate over each active waiting fiber and its requested events.
|
|
145
153
|
def each(&block)
|
|
146
154
|
if fiber = self.fiber
|
|
147
155
|
yield fiber, self.events
|
data/lib/io/event/selector.rb
CHANGED
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2021-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
|
+
require_relative "native"
|
|
6
7
|
require_relative "selector/select"
|
|
7
8
|
require_relative "debug/selector"
|
|
8
9
|
|
|
9
10
|
module IO::Event
|
|
10
11
|
# @namespace
|
|
11
12
|
module Selector
|
|
13
|
+
selectors = [:URing, :EPoll, :KQueue, :Select]
|
|
14
|
+
BEST = const_get(selectors.find{|name| const_defined?(name)})
|
|
15
|
+
private_constant :BEST
|
|
16
|
+
|
|
12
17
|
# The default selector implementation, which is chosen based on the environment and available implementations.
|
|
13
18
|
#
|
|
14
19
|
# @parameter env [Hash] The environment to read configuration from.
|
|
@@ -16,16 +21,8 @@ module IO::Event
|
|
|
16
21
|
def self.default(env = ENV)
|
|
17
22
|
if name = env["IO_EVENT_SELECTOR"]&.to_sym
|
|
18
23
|
return const_get(name)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
if self.const_defined?(:URing)
|
|
22
|
-
URing
|
|
23
|
-
elsif self.const_defined?(:EPoll)
|
|
24
|
-
EPoll
|
|
25
|
-
elsif self.const_defined?(:KQueue)
|
|
26
|
-
KQueue
|
|
27
24
|
else
|
|
28
|
-
|
|
25
|
+
BEST
|
|
29
26
|
end
|
|
30
27
|
end
|
|
31
28
|
|
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-
|
|
4
|
+
# Copyright, 2024-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "priority_heap"
|
|
7
7
|
|
|
@@ -9,6 +9,8 @@ class IO
|
|
|
9
9
|
module Event
|
|
10
10
|
# An efficient sorted set of timers.
|
|
11
11
|
class Timers
|
|
12
|
+
COMPACT_MINIMUM_COUNT = 128
|
|
13
|
+
|
|
12
14
|
# A handle to a scheduled timer.
|
|
13
15
|
class Handle
|
|
14
16
|
# Initialize the handle with the given time and block.
|
|
@@ -16,6 +18,7 @@ class IO
|
|
|
16
18
|
# @parameter time [Float] The time at which the block should be called.
|
|
17
19
|
# @parameter block [Proc] The block to call.
|
|
18
20
|
def initialize(time, block)
|
|
21
|
+
@timers = nil
|
|
19
22
|
@time = time
|
|
20
23
|
@block = block
|
|
21
24
|
end
|
|
@@ -26,6 +29,16 @@ class IO
|
|
|
26
29
|
# @attribute [Proc | Nil] The block to call when the timer fires.
|
|
27
30
|
attr :block
|
|
28
31
|
|
|
32
|
+
# Mark the timer as inserted into the heap.
|
|
33
|
+
def schedule!(timers)
|
|
34
|
+
@timers = timers
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Mark the timer as removed from the heap.
|
|
38
|
+
def removed!
|
|
39
|
+
@timers = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
29
42
|
# Compare the handle with another handle.
|
|
30
43
|
#
|
|
31
44
|
# @parameter other [Handle] The other handle to compare with.
|
|
@@ -49,7 +62,14 @@ class IO
|
|
|
49
62
|
|
|
50
63
|
# Cancel the timer.
|
|
51
64
|
def cancel!
|
|
65
|
+
return if @block.nil?
|
|
66
|
+
|
|
52
67
|
@block = nil
|
|
68
|
+
|
|
69
|
+
if timers = @timers
|
|
70
|
+
@timers = nil
|
|
71
|
+
timers.cancelled!(self)
|
|
72
|
+
end
|
|
53
73
|
end
|
|
54
74
|
|
|
55
75
|
# @returns [Boolean] Whether the timer has been cancelled.
|
|
@@ -62,6 +82,7 @@ class IO
|
|
|
62
82
|
def initialize
|
|
63
83
|
@heap = PriorityHeap.new
|
|
64
84
|
@scheduled = []
|
|
85
|
+
@cancelled = 0
|
|
65
86
|
end
|
|
66
87
|
|
|
67
88
|
# @returns [Integer] The number of timers in the heap.
|
|
@@ -101,6 +122,8 @@ class IO
|
|
|
101
122
|
while handle = @heap.peek
|
|
102
123
|
if handle.cancelled?
|
|
103
124
|
@heap.pop
|
|
125
|
+
handle.removed!
|
|
126
|
+
@cancelled -= 1 if @cancelled > 0
|
|
104
127
|
else
|
|
105
128
|
return handle.time - now
|
|
106
129
|
end
|
|
@@ -123,9 +146,12 @@ class IO
|
|
|
123
146
|
while handle = @heap.peek
|
|
124
147
|
if handle.cancelled?
|
|
125
148
|
@heap.pop
|
|
149
|
+
handle.removed!
|
|
150
|
+
@cancelled -= 1 if @cancelled > 0
|
|
126
151
|
elsif handle.time <= now
|
|
127
152
|
# Remove the earliest timer from the heap:
|
|
128
153
|
@heap.pop
|
|
154
|
+
handle.removed!
|
|
129
155
|
|
|
130
156
|
# Call the block:
|
|
131
157
|
handle.call(now)
|
|
@@ -137,11 +163,53 @@ class IO
|
|
|
137
163
|
|
|
138
164
|
# Flush all scheduled timers into the heap.
|
|
139
165
|
#
|
|
140
|
-
#
|
|
166
|
+
# Scheduling appends to `@scheduled` and cancellation is `O(1)`. We pay the cost of filtering and heap repair here, where we can batch work and choose between incremental insertion and one `heapify` pass.
|
|
141
167
|
protected def flush!
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
# Once cancelled handles are both numerous and a large fraction of the heap, rebuild the heap. This is `O(n + m)`, but it removes retained cancelled handles and appends live scheduled handles in the same `heapify` pass instead of paying for separate filtering and insertion.
|
|
169
|
+
if @cancelled >= COMPACT_MINIMUM_COUNT && @cancelled * 2 > @heap.size
|
|
170
|
+
@heap.heapify do |contents|
|
|
171
|
+
contents.delete_if do |handle|
|
|
172
|
+
if handle.cancelled?
|
|
173
|
+
handle.removed!
|
|
174
|
+
true
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
@scheduled.each do |handle|
|
|
179
|
+
unless handle.cancelled?
|
|
180
|
+
handle.schedule!(self)
|
|
181
|
+
contents << handle
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
@cancelled = 0
|
|
187
|
+
else
|
|
188
|
+
# If we are not compacting the heap, filter scheduled handles in place before insertion. This keeps cancelled scheduled handles out of the heap without adding cancellation-time heap deletion.
|
|
189
|
+
@scheduled.delete_if do |handle|
|
|
190
|
+
if handle.cancelled?
|
|
191
|
+
true
|
|
192
|
+
else
|
|
193
|
+
handle.schedule!(self)
|
|
194
|
+
false
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Small heaps can become entirely cancelled before reaching the compaction threshold. Clear those immediately so `size` does not retain cancelled handles indefinitely.
|
|
199
|
+
if @cancelled == @heap.size && @scheduled.empty?
|
|
200
|
+
@heap.clear!
|
|
201
|
+
@cancelled = 0
|
|
202
|
+
else
|
|
203
|
+
@heap.concat(@scheduled)
|
|
204
|
+
end
|
|
144
205
|
end
|
|
206
|
+
|
|
207
|
+
@scheduled.clear
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Track cancelled timers that are still retained in the heap.
|
|
211
|
+
def cancelled!(handle)
|
|
212
|
+
@cancelled += 1
|
|
145
213
|
end
|
|
146
214
|
end
|
|
147
215
|
end
|
data/lib/io/event/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -18,6 +18,14 @@ 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.16.3
|
|
22
|
+
|
|
23
|
+
- Handle `IOError` raised while shutting down the pure Ruby interrupt pipe, so `IO::Event::Interrupt#close` does not leak expected shutdown errors from the interrupt fiber.
|
|
24
|
+
|
|
25
|
+
### v1.16.2
|
|
26
|
+
|
|
27
|
+
- Improve timer heap performance by batching scheduled timer insertion, compacting cancelled timers during flush, and avoiding unnecessary heap rebuilds for small incremental inserts.
|
|
28
|
+
|
|
21
29
|
### v1.16.1
|
|
22
30
|
|
|
23
31
|
- Ensure the pure Ruby `Select` selector returns `false`, not `nil`, when `io_wait` resumes without any ready events.
|
|
@@ -56,14 +64,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
56
64
|
|
|
57
65
|
- Fix Windows build.
|
|
58
66
|
|
|
59
|
-
### v1.11.1
|
|
60
|
-
|
|
61
|
-
- Fix `read_nonblock` when using the `URing` selector, which was not handling zero-length reads correctly. This allows reading available data without blocking.
|
|
62
|
-
|
|
63
|
-
### v1.11.0
|
|
64
|
-
|
|
65
|
-
- [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.)
|
|
66
|
-
|
|
67
67
|
## Contributing
|
|
68
68
|
|
|
69
69
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v1.16.3
|
|
4
|
+
|
|
5
|
+
- Handle `IOError` raised while shutting down the pure Ruby interrupt pipe, so `IO::Event::Interrupt#close` does not leak expected shutdown errors from the interrupt fiber.
|
|
6
|
+
|
|
7
|
+
## v1.16.2
|
|
8
|
+
|
|
9
|
+
- Improve timer heap performance by batching scheduled timer insertion, compacting cancelled timers during flush, and avoiding unnecessary heap rebuilds for small incremental inserts.
|
|
10
|
+
|
|
3
11
|
## v1.16.1
|
|
4
12
|
|
|
5
13
|
- Ensure the pure Ruby `Select` selector returns `false`, not `nil`, when `io_wait` resumes without any ready events.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: io-event
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.16.
|
|
4
|
+
version: 1.16.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
8
8
|
- Math Ieu
|
|
9
9
|
- Wander Hillen
|
|
10
10
|
- Jean Boussier
|
|
11
|
+
- Tavian Barnes
|
|
11
12
|
- Benoit Daloze
|
|
12
13
|
- Bruno Sutic
|
|
13
14
|
- Shizuo Fujita
|
|
14
|
-
- Tavian Barnes
|
|
15
15
|
- Alex Matchneer
|
|
16
16
|
- Anthony Ross
|
|
17
17
|
- Delton Ding
|
metadata.gz.sig
CHANGED
|
Binary file
|