itsi 0.1.0 → 0.1.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
- data/Cargo.lock +524 -44
- data/Rakefile +22 -33
- data/crates/itsi_error/Cargo.toml +2 -0
- data/crates/itsi_error/src/from.rs +70 -0
- data/crates/itsi_error/src/lib.rs +10 -37
- data/crates/itsi_instrument_entry/Cargo.toml +15 -0
- data/crates/itsi_instrument_entry/src/lib.rs +31 -0
- data/crates/itsi_rb_helpers/Cargo.toml +2 -0
- data/crates/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/crates/itsi_rb_helpers/src/lib.rs +90 -10
- data/crates/itsi_scheduler/Cargo.toml +9 -1
- data/crates/itsi_scheduler/extconf.rb +1 -1
- data/crates/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/crates/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/crates/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/crates/itsi_scheduler/src/lib.rs +31 -10
- data/crates/itsi_server/Cargo.toml +14 -2
- data/crates/itsi_server/extconf.rb +1 -1
- data/crates/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/crates/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/crates/itsi_server/src/body_proxy/mod.rs +2 -0
- data/crates/itsi_server/src/lib.rs +58 -7
- data/crates/itsi_server/src/request/itsi_request.rs +238 -104
- data/crates/itsi_server/src/response/itsi_response.rs +347 -0
- data/crates/itsi_server/src/response/mod.rs +1 -0
- data/crates/itsi_server/src/server/bind.rs +50 -20
- data/crates/itsi_server/src/server/bind_protocol.rs +37 -0
- data/crates/itsi_server/src/server/io_stream.rs +104 -0
- data/crates/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
- data/crates/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
- data/crates/itsi_server/src/server/itsi_server.rs +196 -134
- data/crates/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/crates/itsi_server/src/server/listener.rs +184 -127
- data/crates/itsi_server/src/server/mod.rs +7 -1
- data/crates/itsi_server/src/server/process_worker.rs +196 -0
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
- data/crates/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
- data/crates/itsi_server/src/server/signal.rs +70 -0
- data/crates/itsi_server/src/server/thread_worker.rs +368 -0
- data/crates/itsi_server/src/server/tls.rs +42 -28
- data/crates/itsi_tracing/Cargo.toml +4 -0
- data/crates/itsi_tracing/src/lib.rs +36 -6
- data/gems/scheduler/Cargo.lock +219 -23
- data/gems/scheduler/Rakefile +7 -1
- data/gems/scheduler/ext/itsi_error/Cargo.toml +2 -0
- data/gems/scheduler/ext/itsi_error/src/from.rs +70 -0
- data/gems/scheduler/ext/itsi_error/src/lib.rs +10 -37
- data/gems/scheduler/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/gems/scheduler/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/gems/scheduler/ext/itsi_rb_helpers/Cargo.toml +2 -0
- data/gems/scheduler/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/gems/scheduler/ext/itsi_rb_helpers/src/lib.rs +90 -10
- data/gems/scheduler/ext/itsi_scheduler/Cargo.toml +9 -1
- data/gems/scheduler/ext/itsi_scheduler/extconf.rb +1 -1
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/gems/scheduler/ext/itsi_scheduler/src/lib.rs +31 -10
- data/gems/scheduler/ext/itsi_server/Cargo.toml +41 -0
- data/gems/scheduler/ext/itsi_server/extconf.rb +6 -0
- data/gems/scheduler/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/gems/scheduler/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/gems/scheduler/ext/itsi_server/src/body_proxy/mod.rs +2 -0
- data/gems/scheduler/ext/itsi_server/src/lib.rs +103 -0
- data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +277 -0
- data/gems/scheduler/ext/itsi_server/src/request/mod.rs +1 -0
- data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +347 -0
- data/gems/scheduler/ext/itsi_server/src/response/mod.rs +1 -0
- data/gems/scheduler/ext/itsi_server/src/server/bind.rs +168 -0
- data/gems/scheduler/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/gems/scheduler/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +13 -0
- data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +5 -0
- data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +244 -0
- data/gems/scheduler/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/gems/scheduler/ext/itsi_server/src/server/listener.rs +275 -0
- data/gems/scheduler/ext/itsi_server/src/server/mod.rs +11 -0
- data/gems/scheduler/ext/itsi_server/src/server/process_worker.rs +196 -0
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
- data/gems/scheduler/ext/itsi_server/src/server/signal.rs +70 -0
- data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +368 -0
- data/gems/scheduler/ext/itsi_server/src/server/tls.rs +152 -0
- data/gems/scheduler/ext/itsi_tracing/Cargo.toml +4 -0
- data/gems/scheduler/ext/itsi_tracing/src/lib.rs +36 -6
- data/gems/scheduler/itsi-scheduler.gemspec +2 -3
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +137 -1
- data/gems/scheduler/test/helpers/test_helper.rb +24 -0
- data/gems/scheduler/test/test_active_record.rb +158 -0
- data/gems/scheduler/test/test_address_resolve.rb +23 -0
- data/gems/scheduler/test/test_block_unblock.rb +229 -0
- data/gems/scheduler/test/test_file_io.rb +193 -0
- data/gems/scheduler/test/test_itsi_scheduler.rb +24 -1
- data/gems/scheduler/test/test_kernel_sleep.rb +91 -0
- data/gems/scheduler/test/test_nested_fibers.rb +286 -0
- data/gems/scheduler/test/test_network_io.rb +274 -0
- data/gems/scheduler/test/test_process_wait.rb +26 -0
- data/gems/server/exe/itsi +88 -28
- data/gems/server/ext/itsi_error/Cargo.toml +2 -0
- data/gems/server/ext/itsi_error/src/from.rs +70 -0
- data/gems/server/ext/itsi_error/src/lib.rs +10 -37
- data/gems/server/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/gems/server/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/gems/server/ext/itsi_rb_helpers/Cargo.toml +2 -0
- data/gems/server/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/gems/server/ext/itsi_rb_helpers/src/lib.rs +90 -10
- data/gems/server/ext/itsi_scheduler/Cargo.toml +24 -0
- data/gems/server/ext/itsi_scheduler/extconf.rb +6 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/gems/server/ext/itsi_scheduler/src/lib.rs +38 -0
- data/gems/server/ext/itsi_server/Cargo.toml +14 -2
- data/gems/server/ext/itsi_server/extconf.rb +1 -1
- data/gems/server/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/gems/server/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/gems/server/ext/itsi_server/src/body_proxy/mod.rs +2 -0
- data/gems/server/ext/itsi_server/src/lib.rs +58 -7
- data/gems/server/ext/itsi_server/src/request/itsi_request.rs +238 -104
- data/gems/server/ext/itsi_server/src/response/itsi_response.rs +347 -0
- data/gems/server/ext/itsi_server/src/response/mod.rs +1 -0
- data/gems/server/ext/itsi_server/src/server/bind.rs +50 -20
- data/gems/server/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/gems/server/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
- data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
- data/gems/server/ext/itsi_server/src/server/itsi_server.rs +196 -134
- data/gems/server/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/gems/server/ext/itsi_server/src/server/listener.rs +184 -127
- data/gems/server/ext/itsi_server/src/server/mod.rs +7 -1
- data/gems/server/ext/itsi_server/src/server/process_worker.rs +196 -0
- data/gems/server/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
- data/gems/server/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
- data/gems/server/ext/itsi_server/src/server/signal.rs +70 -0
- data/gems/server/ext/itsi_server/src/server/thread_worker.rs +368 -0
- data/gems/server/ext/itsi_server/src/server/tls.rs +42 -28
- data/gems/server/ext/itsi_tracing/Cargo.toml +4 -0
- data/gems/server/ext/itsi_tracing/src/lib.rs +36 -6
- data/gems/server/itsi-server.gemspec +4 -5
- data/gems/server/lib/itsi/request.rb +30 -14
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +25 -0
- data/gems/server/lib/itsi/server/scheduler_mode.rb +6 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +82 -2
- data/gems/server/lib/itsi/signals.rb +23 -0
- data/gems/server/lib/itsi/stream_io.rb +38 -0
- data/gems/server/test/test_helper.rb +2 -0
- data/gems/server/test/test_itsi_server.rb +1 -1
- data/lib/itsi/version.rb +1 -1
- data/tasks.txt +18 -0
- metadata +102 -12
- data/crates/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/crates/itsi_server/src/stream_writer/mod.rs +0 -21
- data/gems/scheduler/test/test_helper.rb +0 -6
- data/gems/server/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/gems/server/ext/itsi_server/src/stream_writer/mod.rs +0 -21
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
class TestBlockUnblock < Minitest::Test
|
5
|
+
include Itsi::Scheduler::TestHelper
|
6
|
+
|
7
|
+
def test_block_with_timeout
|
8
|
+
result = nil
|
9
|
+
start_time = Time.now
|
10
|
+
|
11
|
+
with_scheduler do |_scheduler|
|
12
|
+
Fiber.schedule do
|
13
|
+
# Simulate a blocking operation with a 0.1-second timeout.
|
14
|
+
Fiber.scheduler.block(nil, 0.1)
|
15
|
+
result = :resumed
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
elapsed_time = Time.now - start_time
|
20
|
+
|
21
|
+
assert_equal :resumed, result
|
22
|
+
assert_in_delta 0.1, elapsed_time, 0.01, "Fiber did not resume after the expected timeout"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Test that a fiber blocked without a timeout can be manually unblocked.
|
26
|
+
def test_block_and_manual_unblock
|
27
|
+
result = nil
|
28
|
+
|
29
|
+
with_scheduler do |scheduler|
|
30
|
+
fiber = Fiber.schedule do
|
31
|
+
# This fiber blocks indefinitely until manually unblocked.
|
32
|
+
Fiber.scheduler.block(:self, nil)
|
33
|
+
result = :resumed
|
34
|
+
end
|
35
|
+
|
36
|
+
Fiber.schedule do
|
37
|
+
sleep 0.05
|
38
|
+
scheduler.unblock(:self, fiber)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_equal :resumed, result
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_double_unblock
|
46
|
+
result = nil
|
47
|
+
with_scheduler do |sched|
|
48
|
+
fiber = Fiber.schedule do
|
49
|
+
# This fiber blocks indefinitely until manually unblocked.
|
50
|
+
Fiber.scheduler.block(:self, nil)
|
51
|
+
result = :resumed
|
52
|
+
end
|
53
|
+
|
54
|
+
fiber_2 = Fiber.schedule do
|
55
|
+
Fiber.scheduler.unblock(:self, fiber)
|
56
|
+
Fiber.scheduler.unblock(:self, fiber)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
assert_equal :resumed, result
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_timed_double_unblock
|
64
|
+
result = nil
|
65
|
+
|
66
|
+
fiber = scheduler = nil
|
67
|
+
|
68
|
+
Thread.new do
|
69
|
+
sleep 0.01
|
70
|
+
scheduler.unblock(:self, fiber)
|
71
|
+
scheduler.unblock(:self, fiber)
|
72
|
+
sleep 1
|
73
|
+
end
|
74
|
+
|
75
|
+
with_scheduler do |sched|
|
76
|
+
scheduler = sched
|
77
|
+
fiber = Fiber.schedule do
|
78
|
+
# This fiber blocks indefinitely until manually unblocked.
|
79
|
+
Fiber.scheduler.block(:self, 0.1)
|
80
|
+
Fiber.scheduler.block(:self, 0.1)
|
81
|
+
result = :resumed
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
assert_equal :resumed, result
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_condition_variable_signaling
|
89
|
+
result = nil
|
90
|
+
mutex = Mutex.new
|
91
|
+
cv = ConditionVariable.new
|
92
|
+
|
93
|
+
# Set a scheduler so that non-blocking fibers are active.
|
94
|
+
# (Without a scheduler, blocking operations run in the usual way.)
|
95
|
+
with_scheduler do
|
96
|
+
# Create a non-blocking fiber that waits on the condition variable.
|
97
|
+
fiber = Fiber.schedule do
|
98
|
+
mutex.synchronize do
|
99
|
+
# The call to cv.wait(mutex) internally triggers the scheduler’s block hook.
|
100
|
+
cv.wait(mutex)
|
101
|
+
result = :resumed
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# In a separate thread, signal the condition variable after a delay.
|
106
|
+
Thread.new do
|
107
|
+
sleep 0.05
|
108
|
+
mutex.synchronize do
|
109
|
+
cv.signal # This should cause Ruby to internally call the scheduler’s unblock hook.
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Wait until the fiber finishes.
|
114
|
+
Timeout.timeout(1) do
|
115
|
+
sleep 0.01 while fiber.alive?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
assert_equal :resumed, result
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_queue_pop_unblocks_fiber
|
122
|
+
result = nil
|
123
|
+
queue = Queue.new
|
124
|
+
|
125
|
+
# Set a scheduler so that non-blocking fiber operations are enabled.
|
126
|
+
with_scheduler do
|
127
|
+
# Schedule a fiber that waits on the queue.
|
128
|
+
fiber = Fiber.schedule do
|
129
|
+
# queue.pop will block until an item is pushed.
|
130
|
+
result = queue.pop
|
131
|
+
end
|
132
|
+
|
133
|
+
# In a separate thread, push an element into the queue after a short delay.
|
134
|
+
Thread.new do
|
135
|
+
queue.push(:hello)
|
136
|
+
end.join
|
137
|
+
|
138
|
+
# Wait until the fiber finishes execution.
|
139
|
+
sleep 0.1 while fiber.alive?
|
140
|
+
end
|
141
|
+
assert_equal :hello, result
|
142
|
+
end
|
143
|
+
|
144
|
+
# Test that unblocking a fiber that isn’t blocked is a no-op.
|
145
|
+
def test_unblock_non_blocked_fiber
|
146
|
+
with_scheduler do |scheduler|
|
147
|
+
fiber = Fiber.new do
|
148
|
+
# Do nothing special.
|
149
|
+
:finished
|
150
|
+
end
|
151
|
+
|
152
|
+
# Try to unblock a fiber that isn’t currently blocked.
|
153
|
+
scheduler.unblock(nil, fiber)
|
154
|
+
# The fiber should finish normally.
|
155
|
+
assert_equal :finished, fiber.resume
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_multiple_fibers_blocking_and_unblocking
|
160
|
+
results = {}
|
161
|
+
|
162
|
+
with_scheduler do |scheduler|
|
163
|
+
Fiber.schedule do
|
164
|
+
Fiber.scheduler.block(:resource1, 0.1)
|
165
|
+
results[:fiber1] = :resumed
|
166
|
+
end
|
167
|
+
|
168
|
+
fiber2 = Fiber.schedule do
|
169
|
+
Fiber.scheduler.block(:resource2, nil)
|
170
|
+
results[:fiber2] = :resumed
|
171
|
+
end
|
172
|
+
|
173
|
+
Fiber.schedule do
|
174
|
+
sleep 0.05
|
175
|
+
scheduler.unblock(:resource, fiber2)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
assert_equal :resumed, results[:fiber1], "Fiber1 did not resume after timeout"
|
180
|
+
assert_equal :resumed, results[:fiber2], "Fiber2 did not resume after manual unblock"
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_block_with_immediate_unblock
|
184
|
+
result = nil
|
185
|
+
|
186
|
+
with_scheduler do |scheduler|
|
187
|
+
fiber = Fiber.schedule do
|
188
|
+
Fiber.scheduler.block(:resource, 0.1)
|
189
|
+
result = :resumed
|
190
|
+
end
|
191
|
+
|
192
|
+
Fiber.schedule do
|
193
|
+
scheduler.unblock(:resource, fiber)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
assert_equal :resumed, result
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_block_with_no_unblock
|
201
|
+
result = nil
|
202
|
+
|
203
|
+
with_scheduler(join: false) do |_scheduler|
|
204
|
+
Fiber.schedule do
|
205
|
+
Fiber.scheduler.block(:resource, 0.1)
|
206
|
+
result = :resumed
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
assert_nil result, :resumed
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_unblock_non_blocked_fiber_v2
|
214
|
+
result = :not_resumed
|
215
|
+
|
216
|
+
with_scheduler do |scheduler|
|
217
|
+
fiber = Fiber.schedule do
|
218
|
+
result = :resumed
|
219
|
+
end
|
220
|
+
|
221
|
+
Fiber.schedule do
|
222
|
+
sleep 0.05
|
223
|
+
scheduler.unblock(:resource, fiber)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
assert_equal :resumed, result, "Fiber should have been resumed normally"
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
class TestFileIO < Minitest::Test
|
5
|
+
include Itsi::Scheduler::TestHelper
|
6
|
+
|
7
|
+
# Test that a fiber waiting to read 5 bytes from a pipe is resumed
|
8
|
+
# when another fiber writes "hello" to the pipe.
|
9
|
+
def test_io_read_resume
|
10
|
+
reader, writer = IO.pipe
|
11
|
+
result = nil
|
12
|
+
|
13
|
+
with_scheduler do |_scheduler|
|
14
|
+
Fiber.schedule do
|
15
|
+
result = reader.read(5)
|
16
|
+
end
|
17
|
+
|
18
|
+
Fiber.schedule do
|
19
|
+
sleep 0.05
|
20
|
+
writer.write("hello")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
reader.close
|
25
|
+
writer.close
|
26
|
+
|
27
|
+
assert_equal "hello", result
|
28
|
+
end
|
29
|
+
|
30
|
+
# Test that IO.wait_readable times out correctly when no data arrives.
|
31
|
+
def test_io_wait_readable_timeout
|
32
|
+
reader, writer = IO.pipe
|
33
|
+
|
34
|
+
result = nil
|
35
|
+
with_scheduler do |_scheduler|
|
36
|
+
Fiber.schedule do
|
37
|
+
# When no data is available, wait_readable should return nil after the timeout.
|
38
|
+
result = reader.wait_readable(0.05)
|
39
|
+
puts "Awoken"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
reader.close
|
44
|
+
writer.close
|
45
|
+
|
46
|
+
assert_nil result, "Expected nil on timeout when no data is available"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Test that two different pipes can be read concurrently.
|
50
|
+
def test_multiple_io_reads
|
51
|
+
reader1, writer1 = IO.pipe
|
52
|
+
reader2, writer2 = IO.pipe
|
53
|
+
results = {}
|
54
|
+
|
55
|
+
with_scheduler do |_scheduler|
|
56
|
+
Fiber.schedule do
|
57
|
+
results[:first] = reader1.read(5)
|
58
|
+
end
|
59
|
+
|
60
|
+
Fiber.schedule do
|
61
|
+
results[:second] = reader2.read(6)
|
62
|
+
end
|
63
|
+
|
64
|
+
Fiber.schedule do
|
65
|
+
sleep 0.05
|
66
|
+
writer1.write("first")
|
67
|
+
writer2.write("second")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
reader1.close
|
72
|
+
writer1.close
|
73
|
+
reader2.close
|
74
|
+
writer2.close
|
75
|
+
|
76
|
+
assert_equal "first", results[:first]
|
77
|
+
assert_equal "second", results[:second]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Test an interleaved read/write operation.
|
81
|
+
# One fiber writes the data in chunks (with short sleeps in between),
|
82
|
+
# while another fiber reads in fixed-size chunks until EOF.
|
83
|
+
def test_io_interleaved_read_write
|
84
|
+
reader, writer = IO.pipe
|
85
|
+
data_read = "".dup
|
86
|
+
|
87
|
+
with_scheduler do |_scheduler|
|
88
|
+
Fiber.schedule do
|
89
|
+
loop do
|
90
|
+
chunk = reader.read(3)
|
91
|
+
break if chunk.nil? || chunk.empty?
|
92
|
+
|
93
|
+
data_read << chunk
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Fiber.schedule do
|
98
|
+
["Hel", "lo ", "Wor", "ld"].each do |part|
|
99
|
+
writer.write(part)
|
100
|
+
sleep 0.02
|
101
|
+
end
|
102
|
+
writer.close # signal EOF to the reader
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
reader.close
|
107
|
+
|
108
|
+
assert_equal "Hello World", data_read
|
109
|
+
end
|
110
|
+
|
111
|
+
# Test that a fiber using nonblocking I/O correctly waits for data.
|
112
|
+
def test_io_read_with_nonblocking_mode
|
113
|
+
reader, writer = IO.pipe
|
114
|
+
# Ensure the IOs are in synchronous mode.
|
115
|
+
reader.sync = true
|
116
|
+
writer.sync = true
|
117
|
+
result = nil
|
118
|
+
|
119
|
+
with_scheduler do |_scheduler|
|
120
|
+
Fiber.schedule do
|
121
|
+
# Use wait_readable to wait until data is available.
|
122
|
+
reader.wait_readable(0.2)
|
123
|
+
result = reader.read_nonblock(5)
|
124
|
+
end
|
125
|
+
|
126
|
+
Fiber.schedule do
|
127
|
+
sleep 0.05
|
128
|
+
writer.write("hello")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
reader.close
|
133
|
+
writer.close
|
134
|
+
|
135
|
+
assert_equal "hello", result
|
136
|
+
end
|
137
|
+
|
138
|
+
# Test that writing to an IO (here a pipe) succeeds immediately
|
139
|
+
# when the pipe is ready for writing.
|
140
|
+
def test_io_write_immediate
|
141
|
+
reader, writer = IO.pipe
|
142
|
+
bytes_written = nil
|
143
|
+
|
144
|
+
with_scheduler do |_scheduler|
|
145
|
+
Fiber.schedule do
|
146
|
+
# If the pipe is empty, write should not block.
|
147
|
+
bytes_written = writer.write("test")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
reader.close
|
152
|
+
writer.close
|
153
|
+
|
154
|
+
assert_equal 4, bytes_written
|
155
|
+
end
|
156
|
+
|
157
|
+
# Test what happens when two fibers are waiting on the same IO.
|
158
|
+
# With the current scheduler design, only one fiber will be resumed
|
159
|
+
# when data becomes available, and the other will eventually time out.
|
160
|
+
def test_multiple_fibers_waiting_on_same_fd
|
161
|
+
reader, writer = IO.pipe
|
162
|
+
results = []
|
163
|
+
data = "data"
|
164
|
+
|
165
|
+
with_scheduler do |_scheduler|
|
166
|
+
Fiber.schedule do
|
167
|
+
if res = reader.wait_readable(0.1)
|
168
|
+
reader.read(data.length)
|
169
|
+
end
|
170
|
+
results << (res ? "readable" : "timeout")
|
171
|
+
end
|
172
|
+
|
173
|
+
Fiber.schedule do
|
174
|
+
if res = reader.wait_readable(0.1)
|
175
|
+
reader.read(data.length)
|
176
|
+
end
|
177
|
+
results << (res ? "readable" : "timeout")
|
178
|
+
end
|
179
|
+
|
180
|
+
Fiber.schedule do
|
181
|
+
writer.write(data)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
writer.close
|
186
|
+
reader.close
|
187
|
+
|
188
|
+
# One fiber should be resumed with "readable" while the other times out.
|
189
|
+
assert_equal 2, results.size
|
190
|
+
assert_includes results, "readable"
|
191
|
+
assert_includes results, "timeout"
|
192
|
+
end
|
193
|
+
end
|
@@ -1,9 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "test_helper"
|
4
3
|
|
5
4
|
class TestItsiScheduler < Minitest::Test
|
5
|
+
include Itsi::Scheduler::TestHelper
|
6
|
+
|
6
7
|
def test_that_it_has_a_version_number
|
7
8
|
refute_nil ::Itsi::Scheduler::VERSION
|
8
9
|
end
|
10
|
+
|
11
|
+
def test_errors
|
12
|
+
results = []
|
13
|
+
start_at = Time.now
|
14
|
+
# Run the scheduler in a dedicated thread to avoid interference with the
|
15
|
+
# main thread’s scheduler state.
|
16
|
+
total = 0
|
17
|
+
out, err = capture_subprocess_io do
|
18
|
+
with_scheduler do |_scheduler|
|
19
|
+
9.times do |i|
|
20
|
+
Fiber.schedule do
|
21
|
+
sleep 0.05
|
22
|
+
raise i if i % 3 == 0
|
23
|
+
total += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_equal total, 6
|
30
|
+
assert_match /Failed to resume fiber /, out
|
31
|
+
end
|
9
32
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
class TestKernelSleep < Minitest::Test
|
5
|
+
include Itsi::Scheduler::TestHelper
|
6
|
+
|
7
|
+
def test_that_it_has_a_version_number
|
8
|
+
refute_nil ::Itsi::Scheduler::VERSION
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_it_can_sleep_concurrently
|
12
|
+
results = []
|
13
|
+
start_at = Time.now
|
14
|
+
# Run the scheduler in a dedicated thread to avoid interference with the
|
15
|
+
# main thread’s scheduler state.
|
16
|
+
with_scheduler do |_scheduler|
|
17
|
+
5.times do
|
18
|
+
Fiber.schedule do
|
19
|
+
sleep 0.05
|
20
|
+
results << "first"
|
21
|
+
sleep 0.05
|
22
|
+
results << "second"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ends_at = Time.now
|
28
|
+
# We expect 10 sleep completions overall (2 per fiber).
|
29
|
+
assert_equal 10, results.size
|
30
|
+
# Because all sleeps run concurrently, the total elapsed time should be about 1 second.
|
31
|
+
assert_in_delta 0.1, ends_at - start_at, 0.02, "Total elapsed time should be close to 0.1 second"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_sleep_zero_duration
|
35
|
+
with_scheduler do |_scheduler|
|
36
|
+
start = Time.now
|
37
|
+
result = sleep(0)
|
38
|
+
finish = Time.now
|
39
|
+
assert result, "sleep(0) should return a truthy value"
|
40
|
+
# Expect near-immediate return.
|
41
|
+
assert_operator finish - start, :<, 0.01, "sleep(0) should not delay execution"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_multiple_sleeps_in_single_fiber
|
46
|
+
order = []
|
47
|
+
with_scheduler do |_scheduler|
|
48
|
+
# Schedule one fiber that sleeps twice.
|
49
|
+
Fiber.schedule do
|
50
|
+
order << "start"
|
51
|
+
sleep 0.2
|
52
|
+
order << "after first sleep"
|
53
|
+
sleep 0.3
|
54
|
+
order << "after second sleep"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_equal ["start", "after first sleep", "after second sleep"], order
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_fibers_wake_in_correct_order
|
62
|
+
order = []
|
63
|
+
with_scheduler do |_scheduler|
|
64
|
+
# Schedule three fibers with different sleep durations.
|
65
|
+
Fiber.schedule do
|
66
|
+
sleep 0.3
|
67
|
+
order << "fiber1"
|
68
|
+
end
|
69
|
+
Fiber.schedule do
|
70
|
+
sleep 0.1
|
71
|
+
order << "fiber2"
|
72
|
+
end
|
73
|
+
Fiber.schedule do
|
74
|
+
sleep 0.2
|
75
|
+
order << "fiber3"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Since the fibers sleep for 0.1, 0.2, and 0.3 seconds respectively,
|
80
|
+
# we expect the wake-up order to be: fiber2, then fiber3, then fiber1.
|
81
|
+
assert_equal %w[fiber2 fiber3 fiber1], order
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_invalid_sleep_value
|
85
|
+
with_scheduler do |_scheduler|
|
86
|
+
assert_raises(TypeError) do
|
87
|
+
sleep("not a number")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|