io-event 1.16.1 → 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/lib/io/event/priority_heap.rb +23 -5
- data/lib/io/event/timers.rb +71 -3
- data/lib/io/event/version.rb +1 -1
- data/readme.md +4 -4
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- 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
|
|
@@ -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,10 @@ 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
|
+
|
|
21
25
|
### v1.16.1
|
|
22
26
|
|
|
23
27
|
- Ensure the pure Ruby `Select` selector returns `false`, not `nil`, when `io_wait` resumes without any ready events.
|
|
@@ -60,10 +64,6 @@ Please see the [project releases](https://socketry.github.io/io-event/releases/i
|
|
|
60
64
|
|
|
61
65
|
- Fix `read_nonblock` when using the `URing` selector, which was not handling zero-length reads correctly. This allows reading available data without blocking.
|
|
62
66
|
|
|
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,9 @@
|
|
|
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
|
+
|
|
3
7
|
## v1.16.1
|
|
4
8
|
|
|
5
9
|
- 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
metadata.gz.sig
CHANGED
|
Binary file
|