itsi 0.1.0 → 0.1.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
- 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 +181 -133
- data/crates/itsi_server/src/server/lifecycle_event.rs +8 -0
- data/crates/itsi_server/src/server/listener.rs +169 -128
- 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 +253 -0
- data/crates/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +238 -0
- data/crates/itsi_server/src/server/signal.rs +57 -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 +230 -0
- data/gems/scheduler/ext/itsi_server/src/server/lifecycle_event.rs +8 -0
- data/gems/scheduler/ext/itsi_server/src/server/listener.rs +259 -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 +253 -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 +238 -0
- data/gems/scheduler/ext/itsi_server/src/server/signal.rs +57 -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 -2
- 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 +181 -133
- data/gems/server/ext/itsi_server/src/server/lifecycle_event.rs +8 -0
- data/gems/server/ext/itsi_server/src/server/listener.rs +169 -128
- 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 +253 -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 +238 -0
- data/gems/server/ext/itsi_server/src/server/signal.rs +57 -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 -4
- 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 +68 -2
- data/gems/server/lib/itsi/signals.rb +18 -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 +17 -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,274 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "timeout"
|
5
|
+
require "debug"
|
6
|
+
|
7
|
+
class TestNetworkIO < Minitest::Test
|
8
|
+
include Itsi::Scheduler::TestHelper
|
9
|
+
|
10
|
+
def test_tcp_echo
|
11
|
+
message = "Hello, Itsi!"
|
12
|
+
response = nil
|
13
|
+
|
14
|
+
with_scheduler do |_scheduler|
|
15
|
+
server = TCPServer.new("127.0.0.1", 0)
|
16
|
+
port = server.addr[1]
|
17
|
+
|
18
|
+
# Server fiber: accept one connection and echo data.
|
19
|
+
Fiber.schedule do
|
20
|
+
client = server.accept
|
21
|
+
data = client.read(message.size)
|
22
|
+
client.write(data)
|
23
|
+
client.close
|
24
|
+
server.close
|
25
|
+
end
|
26
|
+
|
27
|
+
# Client fiber: connect, send message, and read echo.
|
28
|
+
Fiber.schedule do
|
29
|
+
client = TCPSocket.new("127.0.0.1", port)
|
30
|
+
client.write(message)
|
31
|
+
response = client.read(message.size)
|
32
|
+
client.close
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_equal message, response
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_concurrent_tcp_clients
|
40
|
+
server = TCPServer.new("127.0.0.1", 0)
|
41
|
+
port = server.addr[1]
|
42
|
+
messages = %w[first second third fourth]
|
43
|
+
responses = {}
|
44
|
+
|
45
|
+
with_scheduler do |_scheduler|
|
46
|
+
# Server fiber: accept several connections.
|
47
|
+
Fiber.schedule do
|
48
|
+
messages.size.times do
|
49
|
+
client = server.accept
|
50
|
+
# Spawn a fiber for each connection to echo the data.
|
51
|
+
Fiber.schedule do
|
52
|
+
data = client.readpartial(1024)
|
53
|
+
client.write(data)
|
54
|
+
client.close
|
55
|
+
end
|
56
|
+
end
|
57
|
+
server.close
|
58
|
+
end
|
59
|
+
|
60
|
+
# Client fibers: connect concurrently and send messages.
|
61
|
+
messages.each do |msg|
|
62
|
+
Fiber.schedule do
|
63
|
+
sleep rand(0.01..0.05) # random delay for interleaving
|
64
|
+
client = TCPSocket.new("127.0.0.1", port)
|
65
|
+
client.write(msg)
|
66
|
+
responses[msg] = client.read(msg.size)
|
67
|
+
client.close
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
messages.each do |msg|
|
73
|
+
assert_equal msg, responses[msg]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_interleaved_network_and_sleep
|
78
|
+
server = TCPServer.new("127.0.0.1", 0)
|
79
|
+
port = server.addr[1]
|
80
|
+
responses = {}
|
81
|
+
|
82
|
+
with_scheduler do |_scheduler|
|
83
|
+
# Echo server that delays between chunks.
|
84
|
+
Fiber.schedule do
|
85
|
+
client = server.accept
|
86
|
+
data = "".dup
|
87
|
+
# Read in chunks until we have received 12 bytes.
|
88
|
+
while data.size < 12
|
89
|
+
begin
|
90
|
+
chunk = client.readpartial(3)
|
91
|
+
rescue EOFError
|
92
|
+
break
|
93
|
+
end
|
94
|
+
data << chunk
|
95
|
+
sleep 0.02 # delay between chunks
|
96
|
+
end
|
97
|
+
# Echo back the reversed data.
|
98
|
+
client.write(data.reverse)
|
99
|
+
client.close
|
100
|
+
server.close
|
101
|
+
end
|
102
|
+
|
103
|
+
# Client fiber: send data in small chunks.
|
104
|
+
Fiber.schedule do
|
105
|
+
sleep 0.05 # allow server to start
|
106
|
+
client = TCPSocket.new("127.0.0.1", port)
|
107
|
+
"HelloWorld!!".chars.each_slice(3) do |slice|
|
108
|
+
client.write(slice.join)
|
109
|
+
sleep 0.01
|
110
|
+
end
|
111
|
+
responses[:result] = client.read(12)
|
112
|
+
client.close
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
expected = "HelloWorld!!".reverse[0, 12]
|
117
|
+
assert_equal expected, responses[:result]
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_tcp_timeout
|
121
|
+
server = TCPServer.new("127.0.0.1", 0)
|
122
|
+
port = server.addr[1]
|
123
|
+
result = nil
|
124
|
+
|
125
|
+
with_scheduler do |_scheduler|
|
126
|
+
# Server fiber: accept the connection but delay sending.
|
127
|
+
Fiber.schedule do
|
128
|
+
client = server.accept
|
129
|
+
sleep 0.2 # delay long enough to force a timeout on the client
|
130
|
+
client.write("late data")
|
131
|
+
client.close
|
132
|
+
server.close
|
133
|
+
end
|
134
|
+
|
135
|
+
# Client fiber: connect and wait for data with a short timeout.
|
136
|
+
Fiber.schedule do
|
137
|
+
sleep 0.05
|
138
|
+
client = TCPSocket.new("127.0.0.1", port)
|
139
|
+
result = if client.wait_readable(0.1)
|
140
|
+
client.readpartial(1024)
|
141
|
+
else
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
client.close
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# The client should time out (i.e. result remains nil) because the server waits too long.
|
149
|
+
assert_nil result
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_multiple_fibers_on_same_socket
|
153
|
+
server = TCPServer.new("127.0.0.1", 0)
|
154
|
+
port = server.addr[1]
|
155
|
+
results = []
|
156
|
+
|
157
|
+
with_scheduler do |_scheduler|
|
158
|
+
# Server fiber: accept a connection and send a short message.
|
159
|
+
Fiber.schedule do
|
160
|
+
client1 = server.accept
|
161
|
+
client2 = server.accept
|
162
|
+
client1.write("network")
|
163
|
+
client2.write("network")
|
164
|
+
client1.close
|
165
|
+
client2.close
|
166
|
+
server.close
|
167
|
+
end
|
168
|
+
|
169
|
+
# Two separate client fibers using separate connections.
|
170
|
+
# (In many schedulers, if two fibers wait on the same IO object,
|
171
|
+
# only one may be resumed when data becomes available.)
|
172
|
+
2.times do |i|
|
173
|
+
Fiber.schedule do
|
174
|
+
sleep 0.001
|
175
|
+
client = TCPSocket.new("127.0.0.1", port)
|
176
|
+
res = client.wait_readable(0.05) ? "readable" : "timeout"
|
177
|
+
results << res
|
178
|
+
client.close
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
assert_equal 2, results.size
|
184
|
+
assert_includes results, "readable"
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_two_fibers_only_one_connects
|
188
|
+
server = TCPServer.new("127.0.0.1", 0)
|
189
|
+
port = server.addr[1]
|
190
|
+
results = []
|
191
|
+
|
192
|
+
with_scheduler do |_scheduler|
|
193
|
+
# Server fiber: accept a connection and send a short message.
|
194
|
+
Fiber.schedule do
|
195
|
+
client1 = server.accept
|
196
|
+
client1.write("network")
|
197
|
+
client1.close
|
198
|
+
sleep 0.1
|
199
|
+
server.close
|
200
|
+
end
|
201
|
+
|
202
|
+
# Two separate client fibers using separate connections.
|
203
|
+
# (In many schedulers, if two fibers wait on the same IO object,
|
204
|
+
# only one may be resumed when data becomes available.)
|
205
|
+
2.times do |i|
|
206
|
+
Fiber.schedule do
|
207
|
+
sleep 0.001
|
208
|
+
client = TCPSocket.new("127.0.0.1", port)
|
209
|
+
res = client.wait_readable(0.05) ? "readable" : "timeout"
|
210
|
+
results << res
|
211
|
+
client.close
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
assert_equal 2, results.size
|
217
|
+
assert_includes results, "readable"
|
218
|
+
assert_includes results, "timeout"
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_udp_and_tcp_interleaving
|
222
|
+
# Set up a UDP server.
|
223
|
+
udp_server = UDPSocket.new
|
224
|
+
udp_server.bind("127.0.0.1", 0)
|
225
|
+
udp_port = udp_server.addr[1]
|
226
|
+
|
227
|
+
# Set up a TCP server.
|
228
|
+
tcp_server = TCPServer.new("127.0.0.1", 0)
|
229
|
+
tcp_port = tcp_server.addr[1]
|
230
|
+
|
231
|
+
udp_result = nil
|
232
|
+
tcp_result = nil
|
233
|
+
|
234
|
+
with_scheduler do |_scheduler|
|
235
|
+
# UDP server fiber: wait for a datagram.
|
236
|
+
Fiber.schedule do
|
237
|
+
udp_result = udp_server.recvfrom(1024)[0]
|
238
|
+
udp_server.close
|
239
|
+
end
|
240
|
+
|
241
|
+
# TCP server fiber: accept a connection and read data.
|
242
|
+
Fiber.schedule do
|
243
|
+
client = tcp_server.accept
|
244
|
+
tcp_result = client.readpartial(1024)
|
245
|
+
client.close
|
246
|
+
tcp_server.close
|
247
|
+
end
|
248
|
+
|
249
|
+
# UDP client fiber: send a datagram.
|
250
|
+
Fiber.schedule do
|
251
|
+
sleep 0.02
|
252
|
+
udp_client = UDPSocket.new
|
253
|
+
udp_client.send("udp data", 0, "127.0.0.1", udp_port)
|
254
|
+
udp_client.close
|
255
|
+
end
|
256
|
+
|
257
|
+
# TCP client fiber: connect and send data.
|
258
|
+
Fiber.schedule do
|
259
|
+
sleep 0.02
|
260
|
+
client = TCPSocket.new("127.0.0.1", tcp_port)
|
261
|
+
client.write("tcp data")
|
262
|
+
client.close
|
263
|
+
end
|
264
|
+
|
265
|
+
# Additional fiber to interleave a sleep.
|
266
|
+
Fiber.schedule do
|
267
|
+
sleep 0.05
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
assert_equal "udp data", udp_result
|
272
|
+
assert_equal "tcp data", tcp_result
|
273
|
+
end
|
274
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'debug'
|
3
|
+
|
4
|
+
|
5
|
+
class TestProcessWait < Minitest::Test
|
6
|
+
include Itsi::Scheduler::TestHelper
|
7
|
+
|
8
|
+
def test_process_wait
|
9
|
+
start_time = Time.now
|
10
|
+
pids = []
|
11
|
+
|
12
|
+
with_scheduler do |_scheduler|
|
13
|
+
3.times do
|
14
|
+
pids << Process.spawn("sleep 0.25")
|
15
|
+
end
|
16
|
+
3.times do |i|
|
17
|
+
Fiber.schedule do
|
18
|
+
Process.wait(pids[i])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end_time = Time.now
|
23
|
+
assert_in_delta(0.25, end_time - start_time, 0.1)
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/gems/server/exe/itsi
CHANGED
@@ -1,27 +1,32 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
|
-
|
4
3
|
require "optparse"
|
5
|
-
require "rack"
|
6
|
-
require "etc"
|
7
4
|
|
8
5
|
# Default options used when starting Osprey from the CLI using `osprey`
|
9
6
|
DEFAULT_OPTIONS = {
|
10
7
|
# Number of workers
|
11
|
-
workers:
|
8
|
+
workers: 1,
|
12
9
|
# Number of threads per worker
|
13
10
|
threads: 1,
|
14
11
|
# Graceful shutdown timeout
|
15
|
-
shutdown_timeout:
|
12
|
+
shutdown_timeout: 5,
|
16
13
|
# Binds
|
17
|
-
binds: [
|
14
|
+
binds: ["http://0.0.0.0:3000"],
|
15
|
+
# Preload
|
16
|
+
preload: true,
|
17
|
+
# Rackup file
|
18
|
+
rackup_file: "config.ru",
|
19
|
+
# Scheduler class
|
20
|
+
scheduler_class: nil,
|
21
|
+
# Whether to stream the body or not
|
22
|
+
stream_body: false
|
18
23
|
}
|
19
24
|
|
20
25
|
options = DEFAULT_OPTIONS.to_a.select(&:last).to_h
|
21
26
|
|
22
27
|
# Define the option parser
|
23
28
|
OptionParser.new do |opts|
|
24
|
-
opts.banner = "Usage:
|
29
|
+
opts.banner = "Usage: itsi [options]"
|
25
30
|
|
26
31
|
opts.on("-w", "--workers WORKERS", Integer, "Number of workers (default: #{options[:workers]})") do |w|
|
27
32
|
options[:workers] = w
|
@@ -31,29 +36,64 @@ OptionParser.new do |opts|
|
|
31
36
|
options[:threads] = t
|
32
37
|
end
|
33
38
|
|
34
|
-
opts.on("-
|
35
|
-
options[:
|
39
|
+
opts.on("-r", "--rackup_file FILE", String, "Rackup file to use (default: #{options[:rackup_file]})") do |rf|
|
40
|
+
options[:rackup_file] = rf
|
36
41
|
end
|
37
42
|
|
38
|
-
opts.on("-
|
39
|
-
|
43
|
+
opts.on("--worker-memory-limit MEMORY_LIMIT", Integer,
|
44
|
+
"Memory limit for each worker (default: #{options[:worker_memory_limit] || 'None'}). If this limit is breached the worker is gracefully restarted") do |ml|
|
45
|
+
options[:worker_memory_limit] = ml
|
40
46
|
end
|
41
47
|
|
42
|
-
opts.on("-f", "--
|
43
|
-
|
44
|
-
|
48
|
+
opts.on("-f", "--fiber_scheduler [CLASS_NAME]", [String], "Scheduler class to use (default: nil). Provide blank or true to use Itsi::Scheduler, or a classname to use an alternative scheduler") do |scheduler_class|
|
49
|
+
if scheduler_class.nil? || scheduler_class == "true"
|
50
|
+
options[:scheduler_class] = "Itsi::Scheduler"
|
51
|
+
elsif scheduler_class == "false"
|
52
|
+
options.delete(:scheduler_class)
|
53
|
+
else
|
54
|
+
options[:scheduler_class] = scheduler_class
|
55
|
+
end
|
45
56
|
end
|
46
57
|
|
47
|
-
opts.on("--
|
48
|
-
|
58
|
+
opts.on("--preload [true, false, :bundle_group_name]", String, " Toggle preloading the application") do |preload|
|
59
|
+
if preload == "true"
|
60
|
+
options[:preload] = true
|
61
|
+
elsif preload == "false"
|
62
|
+
options[:preload] = false
|
63
|
+
else
|
64
|
+
# Not supported yet
|
65
|
+
end
|
49
66
|
end
|
50
67
|
|
51
|
-
|
52
|
-
|
68
|
+
|
69
|
+
opts.on("-b", "--bind BIND", String, "Bind address (default: #{options[:binds].join(", ")}). You can specify this flag multiple times to bind to multiple addresses.") do |bind|
|
70
|
+
options[:binds].pop if options[:binds].first.frozen?
|
71
|
+
options[:binds] << bind
|
53
72
|
end
|
54
73
|
|
55
|
-
opts.on("-
|
56
|
-
|
74
|
+
opts.on("-c", "--cert_path CERT_PATH", String,
|
75
|
+
"Path to the SSL certificate file (must follow a --bind option). You can specify this flag multiple times.") do |cp|
|
76
|
+
raise OptionParser::InvalidOption, "--cert_path must follow a --bind" if options[:binds].empty?
|
77
|
+
require "uri"
|
78
|
+
|
79
|
+
# Modify the last bind entry to add/update the cert query parameter
|
80
|
+
uri = URI.parse("http://#{options[:binds].last}") # Ensure valid URI parsing
|
81
|
+
params = URI.decode_www_form(uri.query.to_s).to_h
|
82
|
+
params["cert"] = cp
|
83
|
+
query_string = params.map { |k, v| "#{k}=#{v}" }.join("&")
|
84
|
+
options[:binds][-1] = "#{uri.host}?#{query_string}"
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on("-k", "--key_path KEY_PATH", String, "Path to the SSL key file (must follow a --bind option). You can specify this flag multiple times.") do |kp|
|
88
|
+
raise OptionParser::InvalidOption, "--key_path must follow a --bind" if options[:binds].empty?
|
89
|
+
require "uri"
|
90
|
+
|
91
|
+
# Modify the last bind entry to add/update the key query parameter
|
92
|
+
uri = URI.parse("http://#{options[:binds].last}") # Ensure valid URI parsing
|
93
|
+
params = URI.decode_www_form(uri.query.to_s).to_h
|
94
|
+
params["key"] = kp
|
95
|
+
query_string = params.map { |k, v| "#{k}=#{v}" }.join("&")
|
96
|
+
options[:binds][-1] = "#{uri.host}?#{query_string}"
|
57
97
|
end
|
58
98
|
|
59
99
|
opts.on("--shutdown_timeout SHUTDOWN_TIMEOUT", String,
|
@@ -65,20 +105,40 @@ OptionParser.new do |opts|
|
|
65
105
|
options[:script_name] = script_name
|
66
106
|
end
|
67
107
|
|
68
|
-
opts.on("--
|
108
|
+
opts.on("--stream-body", TrueClass, "Stream body frames (default: false for best compatibility)") do |stream_body|
|
109
|
+
options[:stream_body] = stream_body
|
110
|
+
end
|
111
|
+
|
112
|
+
opts.on("-h", "--help", "Show this help message") do
|
69
113
|
puts opts
|
70
114
|
exit
|
71
115
|
end
|
72
116
|
end.parse!
|
73
117
|
|
74
|
-
#
|
75
|
-
|
118
|
+
# Rack app loader, invoked per worker.
|
119
|
+
# This is a no-op if preloading is enabled (we just return the preloaded app).
|
120
|
+
preloader = \
|
121
|
+
if options[:preload]
|
122
|
+
require "rack"
|
123
|
+
|
124
|
+
app = Array(Rack::Builder.parse_file(options[:rackup_file])).first
|
125
|
+
require_relative "../lib/itsi/server/scheduler_mode" if options[:scheduler_class]
|
126
|
+
app.method(:itself).to_proc
|
127
|
+
else
|
128
|
+
lambda do
|
129
|
+
require "rack"
|
130
|
+
|
131
|
+
app = Array(Rack::Builder.parse_file(options[:rackup_file])).first
|
132
|
+
require_relative "../lib/itsi/server/scheduler_mode" if options[:scheduler_class]
|
133
|
+
app
|
134
|
+
end
|
135
|
+
end
|
76
136
|
|
77
|
-
|
78
|
-
#
|
79
|
-
# Start the Osprey server
|
137
|
+
# Make sure Itsi is loaded, if not already loaded by the rack_app above.
|
138
|
+
# Start the Itsi server
|
80
139
|
require "itsi/server"
|
140
|
+
|
81
141
|
Itsi::Server.new(
|
82
|
-
app:
|
83
|
-
**options
|
142
|
+
app: preloader,
|
143
|
+
**options.except(:preload, :rackup_file)
|
84
144
|
).start
|
@@ -0,0 +1,70 @@
|
|
1
|
+
use crate::ItsiError;
|
2
|
+
use std::ffi::NulError;
|
3
|
+
|
4
|
+
pub static CLIENT_CONNECTION_CLOSED: &str = "Client disconnected";
|
5
|
+
|
6
|
+
impl From<httparse::Error> for ItsiError {
|
7
|
+
fn from(err: httparse::Error) -> Self {
|
8
|
+
ItsiError::ArgumentError(err.to_string())
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
impl From<nix::errno::Errno> for ItsiError {
|
13
|
+
fn from(err: nix::errno::Errno) -> Self {
|
14
|
+
ItsiError::ArgumentError(err.to_string())
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
impl From<std::io::Error> for ItsiError {
|
19
|
+
fn from(err: std::io::Error) -> Self {
|
20
|
+
ItsiError::ArgumentError(err.to_string())
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
impl From<rcgen::Error> for ItsiError {
|
25
|
+
fn from(err: rcgen::Error) -> Self {
|
26
|
+
ItsiError::ArgumentError(err.to_string())
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
impl From<NulError> for ItsiError {
|
31
|
+
fn from(err: NulError) -> Self {
|
32
|
+
ItsiError::ArgumentError(err.to_string())
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
impl From<magnus::Error> for ItsiError {
|
37
|
+
fn from(err: magnus::Error) -> Self {
|
38
|
+
match err.error_type() {
|
39
|
+
magnus::error::ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
|
40
|
+
magnus::error::ErrorType::Error(_exception_class, cow) => {
|
41
|
+
ItsiError::ArgumentError(cow.to_string())
|
42
|
+
}
|
43
|
+
magnus::error::ErrorType::Exception(exception) => {
|
44
|
+
ItsiError::ArgumentError(exception.to_string())
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
impl From<ItsiError> for magnus::Error {
|
51
|
+
fn from(err: ItsiError) -> Self {
|
52
|
+
match err {
|
53
|
+
ItsiError::InvalidInput(msg) => magnus::Error::new(magnus::exception::arg_error(), msg),
|
54
|
+
ItsiError::InternalServerError(msg) => {
|
55
|
+
magnus::Error::new(magnus::exception::exception(), msg)
|
56
|
+
}
|
57
|
+
ItsiError::UnsupportedProtocol(msg) => {
|
58
|
+
magnus::Error::new(magnus::exception::arg_error(), msg)
|
59
|
+
}
|
60
|
+
ItsiError::ArgumentError(msg) => {
|
61
|
+
magnus::Error::new(magnus::exception::arg_error(), msg)
|
62
|
+
}
|
63
|
+
ItsiError::Jump(msg) => magnus::Error::new(magnus::exception::local_jump_error(), msg),
|
64
|
+
ItsiError::Break() => magnus::Error::new(magnus::exception::interrupt(), "Break"),
|
65
|
+
ItsiError::ClientConnectionClosed => {
|
66
|
+
magnus::Error::new(magnus::exception::eof_error(), CLIENT_CONNECTION_CLOSED)
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
@@ -1,49 +1,22 @@
|
|
1
|
+
pub mod from;
|
1
2
|
use thiserror::Error;
|
2
3
|
|
3
4
|
pub type Result<T> = std::result::Result<T, ItsiError>;
|
4
5
|
|
5
6
|
#[derive(Error, Debug)]
|
6
7
|
pub enum ItsiError {
|
7
|
-
#[error("Invalid input")]
|
8
|
+
#[error("Invalid input {0}")]
|
8
9
|
InvalidInput(String),
|
9
|
-
#[error("Internal server error")]
|
10
|
-
InternalServerError,
|
11
|
-
#[error("Unsupported protocol")]
|
10
|
+
#[error("Internal server error {0}")]
|
11
|
+
InternalServerError(String),
|
12
|
+
#[error("Unsupported protocol {0}")]
|
12
13
|
UnsupportedProtocol(String),
|
13
|
-
#[error("Argument error")]
|
14
|
+
#[error("Argument error: {0}")]
|
14
15
|
ArgumentError(String),
|
16
|
+
#[error("Client Connection Closed")]
|
17
|
+
ClientConnectionClosed,
|
15
18
|
#[error("Jump")]
|
16
19
|
Jump(String),
|
17
|
-
|
18
|
-
|
19
|
-
impl From<ItsiError> for magnus::Error {
|
20
|
-
fn from(err: ItsiError) -> Self {
|
21
|
-
magnus::Error::new(magnus::exception::runtime_error(), err.to_string())
|
22
|
-
}
|
23
|
-
}
|
24
|
-
|
25
|
-
impl From<std::io::Error> for ItsiError {
|
26
|
-
fn from(err: std::io::Error) -> Self {
|
27
|
-
ItsiError::ArgumentError(err.to_string())
|
28
|
-
}
|
29
|
-
}
|
30
|
-
|
31
|
-
impl From<rcgen::Error> for ItsiError {
|
32
|
-
fn from(err: rcgen::Error) -> Self {
|
33
|
-
ItsiError::ArgumentError(err.to_string())
|
34
|
-
}
|
35
|
-
}
|
36
|
-
|
37
|
-
impl From<magnus::Error> for ItsiError {
|
38
|
-
fn from(err: magnus::Error) -> Self {
|
39
|
-
match err.error_type() {
|
40
|
-
magnus::error::ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
|
41
|
-
magnus::error::ErrorType::Error(_exception_class, cow) => {
|
42
|
-
ItsiError::ArgumentError(cow.to_string())
|
43
|
-
}
|
44
|
-
magnus::error::ErrorType::Exception(exception) => {
|
45
|
-
ItsiError::ArgumentError(exception.to_string())
|
46
|
-
}
|
47
|
-
}
|
48
|
-
}
|
20
|
+
#[error("Break")]
|
21
|
+
Break(),
|
49
22
|
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
[package]
|
2
|
+
name = "itsi_instrument_entry"
|
3
|
+
version = "0.1.0"
|
4
|
+
edition = "2021"
|
5
|
+
authors = ["Wouter Coppieters <wc@pico.net.nz>"]
|
6
|
+
license = "MIT"
|
7
|
+
publish = false
|
8
|
+
|
9
|
+
|
10
|
+
[lib]
|
11
|
+
proc-macro = true
|
12
|
+
[dependencies]
|
13
|
+
proc-macro2 = "1.0"
|
14
|
+
quote = "1.0"
|
15
|
+
syn = { version = "1.0", features = ["full"] }
|
@@ -0,0 +1,31 @@
|
|
1
|
+
use proc_macro::TokenStream;
|
2
|
+
use proc_macro2::TokenStream as TokenStream2;
|
3
|
+
use quote::quote;
|
4
|
+
use syn::{parse_macro_input, ItemFn};
|
5
|
+
|
6
|
+
#[proc_macro_attribute]
|
7
|
+
pub fn instrument_with_entry(attr: TokenStream, item: TokenStream) -> TokenStream {
|
8
|
+
let attr_tokens = TokenStream2::from(attr);
|
9
|
+
let input_fn = parse_macro_input!(item as ItemFn);
|
10
|
+
let attrs = input_fn.attrs;
|
11
|
+
let vis = input_fn.vis;
|
12
|
+
let sig = input_fn.sig;
|
13
|
+
let block = input_fn.block;
|
14
|
+
let output = quote! {
|
15
|
+
#[cfg(debug_assertions)]
|
16
|
+
#[tracing::instrument(#attr_tokens)]
|
17
|
+
#(#attrs)*
|
18
|
+
#vis #sig {
|
19
|
+
tracing::trace!("");
|
20
|
+
#block
|
21
|
+
}
|
22
|
+
|
23
|
+
#[cfg(not(debug_assertions))]
|
24
|
+
#(#attrs)*
|
25
|
+
#vis #sig {
|
26
|
+
#block
|
27
|
+
}
|
28
|
+
};
|
29
|
+
|
30
|
+
output.into()
|
31
|
+
}
|