io-event 1.16.0 → 1.16.2
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/io/event/selector/uring.c +18 -13
- data/lib/io/event/priority_heap.rb +23 -5
- data/lib/io/event/selector/select.rb +1 -1
- data/lib/io/event/timers.rb +71 -3
- 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: 8cc6abcf010ce881e4623a5736241660d1ebf6a84883767d761616ddcb23bf4e
|
|
4
|
+
data.tar.gz: 4a438756e87e8feaefaafb40014f36c2b240fbbd837d4581c657eb310cb9b95a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b910e009ff697f179290d916e5914621d25a5478e0f09ac806563a0c3808b30fc28ca652042608e60625830468f37088943559c590112dfadb0d3a48d3b54688
|
|
7
|
+
data.tar.gz: c7501e4ef87e5c9744d666e43494b60b2b3c490d9e9cb55b75a87f7de199e1421b49ba0ce5e176cbaf230d6f3b8f838a5745cff81bac6f826328e7665de7a9e9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -32,7 +32,6 @@ struct IO_Event_Selector_URing
|
|
|
32
32
|
{
|
|
33
33
|
struct IO_Event_Selector backend;
|
|
34
34
|
struct io_uring ring;
|
|
35
|
-
size_t pending;
|
|
36
35
|
|
|
37
36
|
// Flag indicating whether the selector is currently blocked in a system call.
|
|
38
37
|
// Set to 1 when blocked in io_uring_wait_cqe_timeout() without GVL, 0 otherwise.
|
|
@@ -239,7 +238,6 @@ VALUE IO_Event_Selector_URing_allocate(VALUE self) {
|
|
|
239
238
|
IO_Event_Selector_initialize(&selector->backend, self, Qnil);
|
|
240
239
|
selector->ring.ring_fd = -1;
|
|
241
240
|
|
|
242
|
-
selector->pending = 0;
|
|
243
241
|
selector->blocked = 0;
|
|
244
242
|
selector->interrupt.descriptor = -1;
|
|
245
243
|
selector->wakeup_registered = 0;
|
|
@@ -421,15 +419,14 @@ void IO_Event_Selector_URing_dump_completion_queue(struct IO_Event_Selector_URin
|
|
|
421
419
|
// Flush the submission queue, optionally yielding if unsuccessful.
|
|
422
420
|
static
|
|
423
421
|
int io_uring_submit_all(struct IO_Event_Selector_URing *selector, bool yield) {
|
|
424
|
-
|
|
422
|
+
struct io_uring *ring = &selector->ring;
|
|
423
|
+
|
|
424
|
+
while (io_uring_sq_ready(ring) > 0) {
|
|
425
425
|
int result = io_uring_submit(&selector->ring);
|
|
426
426
|
|
|
427
|
-
if (result
|
|
428
|
-
// io_uring_submit() returns the number of submitted SQEs
|
|
429
|
-
selector->pending -= result;
|
|
430
|
-
} else if (result == -EBUSY || result == -EAGAIN) {
|
|
427
|
+
if (result == -EBUSY || result == -EAGAIN) {
|
|
431
428
|
if (yield) IO_Event_Selector_yield(&selector->backend);
|
|
432
|
-
} else {
|
|
429
|
+
} else if (result < 0) {
|
|
433
430
|
rb_syserr_fail(-result, "io_uring_submit_all:io_uring_submit");
|
|
434
431
|
return result;
|
|
435
432
|
}
|
|
@@ -442,7 +439,10 @@ int io_uring_submit_all(struct IO_Event_Selector_URing *selector, bool yield) {
|
|
|
442
439
|
// Flush the submission queue if pending operations are present.
|
|
443
440
|
static
|
|
444
441
|
int io_uring_submit_flush(struct IO_Event_Selector_URing *selector) {
|
|
445
|
-
if (DEBUG)
|
|
442
|
+
if (DEBUG) {
|
|
443
|
+
unsigned pending = io_uring_sq_ready(&selector->ring);
|
|
444
|
+
fprintf(stderr, "io_uring_submit_flush(pending=%u)\n", pending);
|
|
445
|
+
}
|
|
446
446
|
|
|
447
447
|
return io_uring_submit_all(selector, false);
|
|
448
448
|
}
|
|
@@ -450,15 +450,21 @@ int io_uring_submit_flush(struct IO_Event_Selector_URing *selector) {
|
|
|
450
450
|
// Immediately flush the submission queue, yielding to the event loop if it was not successful.
|
|
451
451
|
static
|
|
452
452
|
int io_uring_submit_now(struct IO_Event_Selector_URing *selector) {
|
|
453
|
-
if (DEBUG)
|
|
454
|
-
|
|
453
|
+
if (DEBUG) {
|
|
454
|
+
unsigned pending = io_uring_sq_ready(&selector->ring);
|
|
455
|
+
fprintf(stderr, "io_uring_submit_now(pending=%u)\n", pending);
|
|
456
|
+
}
|
|
457
|
+
|
|
455
458
|
return io_uring_submit_all(selector, true);
|
|
456
459
|
}
|
|
457
460
|
|
|
458
461
|
// Submit a pending operation. This does not submit the operation immediately, but instead defers it to the next call to `io_uring_submit_flush` or `io_uring_submit_now`. This is useful for operations that are not urgent, but should be used with care as it can lead to a deadlock if the submission queue is not flushed.
|
|
459
462
|
static
|
|
460
463
|
void io_uring_submit_pending(struct IO_Event_Selector_URing *selector) {
|
|
461
|
-
if (DEBUG)
|
|
464
|
+
if (DEBUG) {
|
|
465
|
+
unsigned pending = io_uring_sq_ready(&selector->ring);
|
|
466
|
+
fprintf(stderr, "io_uring_submit_pending(ring=%p, pending=%u)\n", &selector->ring, pending);
|
|
467
|
+
}
|
|
462
468
|
}
|
|
463
469
|
|
|
464
470
|
struct io_uring_sqe * io_get_sqe(struct IO_Event_Selector_URing *selector) {
|
|
@@ -471,7 +477,6 @@ struct io_uring_sqe * io_get_sqe(struct IO_Event_Selector_URing *selector) {
|
|
|
471
477
|
sqe = io_uring_get_sqe(&selector->ring);
|
|
472
478
|
}
|
|
473
479
|
|
|
474
|
-
selector->pending += 1;
|
|
475
480
|
return sqe;
|
|
476
481
|
}
|
|
477
482
|
|
|
@@ -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
|
data/lib/io/event/timers.rb
CHANGED
|
@@ -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.2
|
|
22
|
+
|
|
23
|
+
- Improve timer heap performance by batching scheduled timer insertion, compacting cancelled timers during flush, and avoiding unnecessary heap rebuilds for small incremental inserts.
|
|
24
|
+
|
|
25
|
+
### v1.16.1
|
|
26
|
+
|
|
27
|
+
- Ensure the pure Ruby `Select` selector returns `false`, not `nil`, when `io_wait` resumes without any ready events.
|
|
28
|
+
|
|
21
29
|
### v1.16.0
|
|
22
30
|
|
|
23
31
|
- Use `eventfd` for `URing` cross-thread wakeup, and enable `IORING_SETUP_SINGLE_ISSUER`, `IORING_SETUP_DEFER_TASKRUN`, and `IORING_SETUP_TASKRUN_FLAG`. The waking thread now signals via `eventfd` rather than submitting a `NOP` SQE, which unlocks the single-issuer optimisation, defers task work to the application thread, and lets `select()` skip the `io_uring_get_events()` syscall when no task work is pending.
|
|
@@ -56,14 +64,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
56
64
|
|
|
57
65
|
- Fix `read_nonblock` when using the `URing` selector, which was not handling zero-length reads correctly. This allows reading available data without blocking.
|
|
58
66
|
|
|
59
|
-
### v1.11.0
|
|
60
|
-
|
|
61
|
-
- [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.)
|
|
62
|
-
|
|
63
|
-
### v1.10.2
|
|
64
|
-
|
|
65
|
-
- Improved consistency of handling closed IO when invoking `#select`.
|
|
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.2
|
|
4
|
+
|
|
5
|
+
- Improve timer heap performance by batching scheduled timer insertion, compacting cancelled timers during flush, and avoiding unnecessary heap rebuilds for small incremental inserts.
|
|
6
|
+
|
|
7
|
+
## v1.16.1
|
|
8
|
+
|
|
9
|
+
- Ensure the pure Ruby `Select` selector returns `false`, not `nil`, when `io_wait` resumes without any ready events.
|
|
10
|
+
|
|
3
11
|
## v1.16.0
|
|
4
12
|
|
|
5
13
|
- Use `eventfd` for `URing` cross-thread wakeup, and enable `IORING_SETUP_SINGLE_ISSUER`, `IORING_SETUP_DEFER_TASKRUN`, and `IORING_SETUP_TASKRUN_FLAG`. The waking thread now signals via `eventfd` rather than submitting a `NOP` SQE, which unlocks the single-issuer optimisation, defers task work to the application thread, and lets `select()` skip the `io_uring_get_events()` syscall when no task work is pending.
|
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.16.
|
|
4
|
+
version: 1.16.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
124
|
version: '0'
|
|
125
125
|
requirements: []
|
|
126
|
-
rubygems_version: 4.0.
|
|
126
|
+
rubygems_version: 4.0.10
|
|
127
127
|
specification_version: 4
|
|
128
128
|
summary: An event loop.
|
|
129
129
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|