io-event 1.12.1 → 1.14.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/context/getting-started.md +61 -0
- data/context/index.yaml +11 -0
- data/ext/extconf.rb +3 -3
- data/lib/io/event/debug/selector.rb +1 -1
- data/lib/io/event/priority_heap.rb +100 -2
- data/lib/io/event/selector/select.rb +1 -1
- data/lib/io/event/support.rb +1 -1
- data/lib/io/event/version.rb +1 -1
- data/readme.md +4 -0
- data/releases.md +32 -0
- data.tar.gz.sig +0 -0
- metadata +4 -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: 53a9942daf957580fe332adea530a3bba659c0ec5405560078233ead0b893d11
|
4
|
+
data.tar.gz: d6eea90381836e4f1bf62430ae95ea0f7f2b1693683d2a114b5193f0c7a1d3f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bfd04d670380038ec6a57a212f0f927b3fd748f93ac95c26e04adb1ed829a999fc02ebca56a27812c84be402b9fef576ba5d53be0df7b8f5e06bb516c70f2b6
|
7
|
+
data.tar.gz: ca71eac94412be0bbb953d32eb60ef1654fe24535012bc3f6f9a1a5375377175a71641c28e5ab92a5f3d44418ab50c3ef177a552cc0abe57f633e718cb2b4bc4
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
This guide explains how to use `io-event` for non-blocking IO.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the gem to your project:
|
8
|
+
|
9
|
+
~~~ bash
|
10
|
+
$ bundle add io-event
|
11
|
+
~~~
|
12
|
+
|
13
|
+
## Core Concepts
|
14
|
+
|
15
|
+
`io-event` has several core concepts:
|
16
|
+
|
17
|
+
- A {ruby IO::Event::Selector} implementation which provides the primitive operations for implementation an event loop.
|
18
|
+
- A {ruby IO::Event::Debug::Selector} which adds extra validations and checks at the expense of performance. You should generally use this during tests.
|
19
|
+
|
20
|
+
## Basic Event Loop
|
21
|
+
|
22
|
+
This example shows how to perform a blocking operation
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'fiber'
|
26
|
+
require 'io/event'
|
27
|
+
|
28
|
+
selector = IO::Event::Selector.new(Fiber.current)
|
29
|
+
input, output = IO.pipe
|
30
|
+
|
31
|
+
writer = Fiber.new do
|
32
|
+
output.write("Hello World")
|
33
|
+
output.close
|
34
|
+
end
|
35
|
+
|
36
|
+
reader = Fiber.new do
|
37
|
+
selector.io_wait(Fiber.current, input, IO::READABLE)
|
38
|
+
pp read: input.read
|
39
|
+
end
|
40
|
+
|
41
|
+
# The reader will be blocked until the IO has data available:
|
42
|
+
reader.transfer
|
43
|
+
|
44
|
+
# Write some data to the pipe and close the writing end:
|
45
|
+
writer.transfer
|
46
|
+
|
47
|
+
selector.select(1)
|
48
|
+
|
49
|
+
# Results in:
|
50
|
+
# {:read=>"Hello World"}
|
51
|
+
```
|
52
|
+
|
53
|
+
## Debugging
|
54
|
+
|
55
|
+
The {ruby IO::Event::Debug::Selector} class adds extra validations and checks at the expense of performance. It can also log all operations. You can use this by setting the following environment variables:
|
56
|
+
|
57
|
+
```shell
|
58
|
+
$ IO_EVENT_SELECTOR_DEBUG=y IO_EVENT_SELECTOR_DEBUG_LOG=/dev/stderr bundle exec ./my_script.rb
|
59
|
+
```
|
60
|
+
|
61
|
+
The format of the log is subject to change, but it may be useful for debugging.
|
data/context/index.yaml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
3
|
+
---
|
4
|
+
description: An event loop.
|
5
|
+
metadata:
|
6
|
+
documentation_uri: https://socketry.github.io/io-event/
|
7
|
+
source_code_uri: https://github.com/socketry/io-event.git
|
8
|
+
files:
|
9
|
+
- path: getting-started.md
|
10
|
+
title: Getting Started
|
11
|
+
description: This guide explains how to use `io-event` for non-blocking IO.
|
data/ext/extconf.rb
CHANGED
@@ -19,7 +19,7 @@ append_cflags(["-Wall", "-Wno-unknown-pragmas", "-std=c99"])
|
|
19
19
|
|
20
20
|
if ENV.key?("RUBY_DEBUG")
|
21
21
|
$stderr.puts "Enabling debug mode..."
|
22
|
-
|
22
|
+
|
23
23
|
append_cflags(["-DRUBY_DEBUG", "-O0"])
|
24
24
|
end
|
25
25
|
|
@@ -33,7 +33,7 @@ have_func("&rb_fiber_transfer")
|
|
33
33
|
if have_library("uring") and have_header("liburing.h")
|
34
34
|
# We might want to consider using this in the future:
|
35
35
|
# have_func("io_uring_submit_and_wait_timeout", "liburing.h")
|
36
|
-
|
36
|
+
|
37
37
|
$srcs << "io/event/selector/uring.c"
|
38
38
|
end
|
39
39
|
|
@@ -70,7 +70,7 @@ end
|
|
70
70
|
|
71
71
|
if ENV.key?("RUBY_SANITIZE")
|
72
72
|
$stderr.puts "Enabling sanitizers..."
|
73
|
-
|
73
|
+
|
74
74
|
# Add address and undefined behaviour sanitizers:
|
75
75
|
append_cflags(["-fsanitize=address", "-fsanitize=undefined", "-fno-omit-frame-pointer"])
|
76
76
|
$LDFLAGS << " -fsanitize=address -fsanitize=undefined"
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2021, by Wander Hillen.
|
5
|
-
# Copyright, 2021-
|
5
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
6
6
|
|
7
7
|
class IO
|
8
8
|
module Event
|
@@ -28,6 +28,11 @@ class IO
|
|
28
28
|
@contents.size
|
29
29
|
end
|
30
30
|
|
31
|
+
# @returns [Boolean] true if the heap is empty, false otherwise.
|
32
|
+
def empty?
|
33
|
+
@contents.empty?
|
34
|
+
end
|
35
|
+
|
31
36
|
# Removes and returns the smallest element in the heap, or nil if the heap is empty.
|
32
37
|
#
|
33
38
|
# @returns [Object | Nil] The smallest element in the heap, or nil if the heap is empty.
|
@@ -74,19 +79,112 @@ class IO
|
|
74
79
|
return self
|
75
80
|
end
|
76
81
|
|
82
|
+
# Add multiple elements to the heap efficiently in O(n) time.
|
83
|
+
# This is more efficient than calling push multiple times (O(n log n)).
|
84
|
+
#
|
85
|
+
# @parameter elements [Array] The elements to add to the heap.
|
86
|
+
# @returns [self] Returns self for method chaining.
|
87
|
+
def concat(elements)
|
88
|
+
return self if elements.empty?
|
89
|
+
|
90
|
+
# Add all elements to the array without maintaining heap property - O(n)
|
91
|
+
@contents.concat(elements)
|
92
|
+
|
93
|
+
# Rebuild the heap property for the entire array - O(n)
|
94
|
+
heapify!
|
95
|
+
|
96
|
+
return self
|
97
|
+
end
|
98
|
+
|
77
99
|
# Empties out the heap, discarding all elements
|
78
100
|
def clear!
|
79
101
|
@contents = []
|
80
102
|
end
|
81
103
|
|
104
|
+
# Remove a specific element from the heap.
|
105
|
+
#
|
106
|
+
# O(n) where n is the number of elements in the heap.
|
107
|
+
#
|
108
|
+
# @parameter element [Object] The element to remove.
|
109
|
+
# @returns [Object | Nil] The removed element, or nil if not found.
|
110
|
+
def delete(element)
|
111
|
+
# Find the index of the element - O(n) linear search
|
112
|
+
index = @contents.index(element)
|
113
|
+
return nil unless index
|
114
|
+
|
115
|
+
# If it's the last element, just remove it
|
116
|
+
if index == @contents.size - 1
|
117
|
+
return @contents.pop
|
118
|
+
end
|
119
|
+
|
120
|
+
# Store the value we're removing
|
121
|
+
removed_value = @contents[index]
|
122
|
+
|
123
|
+
# Replace with the last element
|
124
|
+
last = @contents.pop
|
125
|
+
@contents[index] = last
|
126
|
+
|
127
|
+
# Restore heap property - might need to bubble up or down
|
128
|
+
if index > 0 && @contents[index] < @contents[(index - 1) / 2]
|
129
|
+
# New element is smaller than parent, bubble up
|
130
|
+
bubble_up(index)
|
131
|
+
else
|
132
|
+
# New element might be larger than children, bubble down
|
133
|
+
bubble_down(index)
|
134
|
+
end
|
135
|
+
|
136
|
+
# validate!
|
137
|
+
|
138
|
+
return removed_value
|
139
|
+
end
|
140
|
+
|
141
|
+
# Remove elements matching the given block condition by rebuilding the heap.
|
142
|
+
#
|
143
|
+
# This is more efficient than multiple delete operations when removing many elements.
|
144
|
+
#
|
145
|
+
# O(n) where n is the number of elements in the heap.
|
146
|
+
#
|
147
|
+
# @yields [Object] Each element in the heap for testing
|
148
|
+
# @returns [Integer] The number of elements removed
|
149
|
+
def delete_if
|
150
|
+
return enum_for(:delete_if) unless block_given?
|
151
|
+
|
152
|
+
original_size = @contents.size
|
153
|
+
|
154
|
+
# Filter out elements that match the condition - O(n)
|
155
|
+
@contents.reject! {|element| yield(element)}
|
156
|
+
|
157
|
+
# If we removed elements, rebuild the heap - O(n)
|
158
|
+
if @contents.size < original_size
|
159
|
+
heapify!
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return number of elements removed
|
163
|
+
original_size - @contents.size
|
164
|
+
end
|
165
|
+
|
82
166
|
# Validate the heap invariant. Every element except the root must not be smaller than its parent element. Note that it MAY be equal.
|
83
167
|
def valid?
|
84
|
-
# Notice we skip index 0 on purpose, because it has no parent
|
168
|
+
# Notice we skip index 0 on purpose, because it has no parent:
|
85
169
|
(1..(@contents.size - 1)).all? {|index| @contents[index] >= @contents[(index - 1) / 2]}
|
86
170
|
end
|
87
171
|
|
88
172
|
private
|
89
173
|
|
174
|
+
# Rebuild the heap property from an arbitrary array in O(n) time.
|
175
|
+
# Uses bottom-up heapify algorithm starting from the last non-leaf node.
|
176
|
+
def heapify!
|
177
|
+
return if @contents.size <= 1
|
178
|
+
|
179
|
+
# Start from the last non-leaf node and work backwards to root:
|
180
|
+
last_non_leaf_index = (@contents.size / 2) - 1
|
181
|
+
last_non_leaf_index.downto(0) do |index|
|
182
|
+
bubble_down(index)
|
183
|
+
end
|
184
|
+
|
185
|
+
# validate!
|
186
|
+
end
|
187
|
+
|
90
188
|
# Left here for reference, but unused.
|
91
189
|
# def swap(i, j)
|
92
190
|
# @contents[i], @contents[j] = @contents[j], @contents[i]
|
data/lib/io/event/support.rb
CHANGED
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.14.0
|
22
|
+
|
23
|
+
- [Enhanced `IO::Event::PriorityHeap` with deletion and bulk insertion methods](https://socketry.github.io/io-event/releases/index#enhanced-io::event::priorityheap-with-deletion-and-bulk-insertion-methods)
|
24
|
+
|
21
25
|
### v1.11.2
|
22
26
|
|
23
27
|
- Fix Windows build.
|
data/releases.md
CHANGED
@@ -1,5 +1,37 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v1.14.0
|
4
|
+
|
5
|
+
### Enhanced `IO::Event::PriorityHeap` with deletion and bulk insertion methods
|
6
|
+
|
7
|
+
The {ruby IO::Event::PriorityHeap} now supports efficient element removal and bulk insertion:
|
8
|
+
|
9
|
+
- **`delete(element)`**: Remove a specific element from the heap in O(n) time
|
10
|
+
- **`delete_if(&block)`**: Remove elements matching a condition with O(n) amortized bulk deletion
|
11
|
+
- **`concat(elements)`**: Add multiple elements efficiently in O(n) time
|
12
|
+
|
13
|
+
<!-- end list -->
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
heap = IO::Event::PriorityHeap.new
|
17
|
+
|
18
|
+
# Efficient bulk insertion - O(n) instead of O(n log n)
|
19
|
+
heap.concat([5, 2, 8, 1, 9, 3])
|
20
|
+
|
21
|
+
# Remove specific element
|
22
|
+
removed = heap.delete(5) # Returns 5, heap maintains order
|
23
|
+
|
24
|
+
# Bulk removal with condition
|
25
|
+
count = heap.delete_if { |x| x.even? } # Removes 2, 8 efficiently
|
26
|
+
```
|
27
|
+
|
28
|
+
The `delete_if` and `concat` methods are particularly efficient for bulk operations, using bottom-up heapification to maintain the heap property in O(n) time. This provides significant performance improvements:
|
29
|
+
|
30
|
+
- **Bulk insertion**: O(n log n) → O(n) for adding multiple elements
|
31
|
+
- **Bulk deletion**: O(k×n) → O(n) for removing k elements
|
32
|
+
|
33
|
+
Both methods maintain the heap invariant and include comprehensive test coverage with edge case validation.
|
34
|
+
|
3
35
|
## v1.11.2
|
4
36
|
|
5
37
|
- Fix Windows build.
|
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.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -55,6 +55,8 @@ extensions:
|
|
55
55
|
extra_rdoc_files: []
|
56
56
|
files:
|
57
57
|
- agent.md
|
58
|
+
- context/getting-started.md
|
59
|
+
- context/index.yaml
|
58
60
|
- design.md
|
59
61
|
- ext/extconf.rb
|
60
62
|
- ext/io/event/array.h
|
@@ -114,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
116
|
- !ruby/object:Gem::Version
|
115
117
|
version: '0'
|
116
118
|
requirements: []
|
117
|
-
rubygems_version: 3.
|
119
|
+
rubygems_version: 3.7.0.dev
|
118
120
|
specification_version: 4
|
119
121
|
summary: An event loop.
|
120
122
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|