raptor 0.23.0 → 0.25.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
- data/CHANGELOG.md +20 -0
- data/Dockerfile +7 -1
- data/README.md +184 -69
- data/docs/assets/profile_picture.png +0 -0
- data/docs/assets/startup_output.svg +37 -0
- data/docs/raptor-vs-puma.md +105 -429
- data/docs/talk.md +262 -169
- data/ext/raptor_http/raptor_http.c +82 -123
- data/lib/rackup/handler/raptor.rb +2 -1
- data/lib/raptor/cli.rb +1 -0
- data/lib/raptor/cluster.rb +85 -39
- data/lib/raptor/control_server.rb +2 -0
- data/lib/raptor/http.rb +0 -1
- data/lib/raptor/http1.rb +52 -30
- data/lib/raptor/http2.rb +46 -13
- data/lib/raptor/plugin.rb +98 -0
- data/lib/raptor/reactor.rb +102 -72
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor.rb +1 -1
- data/sig/generated/raptor/cluster.rbs +18 -5
- data/sig/generated/raptor/http.rbs +0 -2
- data/sig/generated/raptor/http1.rbs +21 -13
- data/sig/generated/raptor/http2.rbs +12 -9
- data/sig/generated/raptor/plugin.rbs +52 -0
- data/sig/generated/raptor/reactor.rbs +31 -16
- data/sig/generated/raptor.rbs +1 -1
- metadata +6 -2
data/lib/raptor/reactor.rb
CHANGED
|
@@ -9,7 +9,7 @@ require "atomic-ruby/atomic_boolean"
|
|
|
9
9
|
require "atomic-ruby/atomic_condition_variable"
|
|
10
10
|
require "atomic-ruby/atomic_queue"
|
|
11
11
|
require "atomic-ruby/atomic_thread_pool"
|
|
12
|
-
require "
|
|
12
|
+
require "io/event"
|
|
13
13
|
require "red-black-tree"
|
|
14
14
|
|
|
15
15
|
require_relative "detached_body"
|
|
@@ -76,7 +76,6 @@ module Raptor
|
|
|
76
76
|
attr_accessor :wait #: Symbol?
|
|
77
77
|
attr_accessor :reading #: bool
|
|
78
78
|
attr_accessor :closing #: bool
|
|
79
|
-
attr_accessor :monitor #: NIO::Monitor?
|
|
80
79
|
attr_accessor :timeout #: TimeoutClient?
|
|
81
80
|
attr_reader :output #: Array[String]
|
|
82
81
|
|
|
@@ -91,7 +90,6 @@ module Raptor
|
|
|
91
90
|
@wait = nil
|
|
92
91
|
@reading = false
|
|
93
92
|
@closing = false
|
|
94
|
-
@monitor = nil
|
|
95
93
|
@timeout = nil
|
|
96
94
|
end
|
|
97
95
|
|
|
@@ -182,7 +180,10 @@ module Raptor
|
|
|
182
180
|
# @rbs @persistent_data_timeout: Integer
|
|
183
181
|
# @rbs @http2_keepalive_interval: Integer
|
|
184
182
|
# @rbs @http2_keepalive_timeout: Integer
|
|
185
|
-
# @rbs @selector:
|
|
183
|
+
# @rbs @selector: untyped
|
|
184
|
+
# @rbs @wakeup: Fiber?
|
|
185
|
+
# @rbs @waiters: Hash[TCPSocket, [Fiber, Integer]]
|
|
186
|
+
# @rbs @ready: Array[[TCPSocket, Integer]]
|
|
186
187
|
# @rbs @queue: Queue[TCPSocket]
|
|
187
188
|
# @rbs @io_queue: AtomicQueue
|
|
188
189
|
# @rbs @io_applied: AtomicConditionVariable
|
|
@@ -231,7 +232,10 @@ module Raptor
|
|
|
231
232
|
@http2_keepalive_interval = http2_options[:keepalive_interval]
|
|
232
233
|
@http2_keepalive_timeout = http2_options[:keepalive_timeout]
|
|
233
234
|
|
|
234
|
-
@selector =
|
|
235
|
+
@selector = nil
|
|
236
|
+
@wakeup = nil
|
|
237
|
+
@waiters = {}
|
|
238
|
+
@ready = []
|
|
235
239
|
@queue = Queue.new
|
|
236
240
|
@io_queue = AtomicQueue.new
|
|
237
241
|
@io_applied = AtomicConditionVariable.new
|
|
@@ -262,13 +266,19 @@ module Raptor
|
|
|
262
266
|
Thread.new do
|
|
263
267
|
Thread.current.name = "Reactor"
|
|
264
268
|
@thread = Thread.current
|
|
269
|
+
selector = IO::Event::Selector.new(Fiber.current)
|
|
270
|
+
@wakeup = Fiber.new { loop { selector.transfer } }
|
|
271
|
+
@selector = selector
|
|
265
272
|
|
|
266
273
|
until stopped?
|
|
267
274
|
begin
|
|
268
275
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
269
276
|
timeout = @timeouts.min&.timeout(now)
|
|
270
|
-
@
|
|
271
|
-
|
|
277
|
+
timeout = 0 unless @ready.empty? && @queue.empty? && @io_queue.empty?
|
|
278
|
+
@selector.select(timeout)
|
|
279
|
+
until @ready.empty?
|
|
280
|
+
socket, events = @ready.shift
|
|
281
|
+
handle_ready(socket, events)
|
|
272
282
|
end
|
|
273
283
|
|
|
274
284
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -283,6 +293,8 @@ module Raptor
|
|
|
283
293
|
end
|
|
284
294
|
end
|
|
285
295
|
|
|
296
|
+
@waiters.keys.each { |socket| deregister(socket) }
|
|
297
|
+
@ready.clear
|
|
286
298
|
@id_to_flow_control.each_value(&:close)
|
|
287
299
|
@id_to_socket.each_value { |socket| socket.close rescue nil }
|
|
288
300
|
@id_to_socket.clear
|
|
@@ -330,7 +342,7 @@ module Raptor
|
|
|
330
342
|
|
|
331
343
|
@socket_to_state[socket] = state
|
|
332
344
|
@queue << socket
|
|
333
|
-
|
|
345
|
+
wakeup
|
|
334
346
|
rescue ClosedQueueError
|
|
335
347
|
socket.close
|
|
336
348
|
end
|
|
@@ -372,7 +384,7 @@ module Raptor
|
|
|
372
384
|
@id_to_socket[id] = socket
|
|
373
385
|
@socket_to_state[socket] = state
|
|
374
386
|
@queue << socket
|
|
375
|
-
|
|
387
|
+
wakeup
|
|
376
388
|
rescue ClosedQueueError
|
|
377
389
|
socket.close
|
|
378
390
|
end
|
|
@@ -422,7 +434,7 @@ module Raptor
|
|
|
422
434
|
def attach_http1_body(socket, id, body, state, finished)
|
|
423
435
|
attached = AtomicBoolean.new(false)
|
|
424
436
|
@io_queue << [:attach_http1, id, socket, body, attached, finished, state]
|
|
425
|
-
|
|
437
|
+
wakeup rescue nil
|
|
426
438
|
@io_applied.wait { attached.true? }
|
|
427
439
|
return false if body.closed?
|
|
428
440
|
|
|
@@ -430,7 +442,7 @@ module Raptor
|
|
|
430
442
|
true
|
|
431
443
|
rescue
|
|
432
444
|
@io_queue << [:cancel_http1, id]
|
|
433
|
-
|
|
445
|
+
wakeup rescue nil
|
|
434
446
|
raise
|
|
435
447
|
end
|
|
436
448
|
|
|
@@ -490,7 +502,7 @@ module Raptor
|
|
|
490
502
|
flush_output(id, io) if io && !io.wait
|
|
491
503
|
else
|
|
492
504
|
@io_queue << [:write, id, data]
|
|
493
|
-
|
|
505
|
+
wakeup rescue nil
|
|
494
506
|
end
|
|
495
507
|
end
|
|
496
508
|
|
|
@@ -506,7 +518,7 @@ module Raptor
|
|
|
506
518
|
def attach_http2_body(id, stream_id, body, finished)
|
|
507
519
|
attached = AtomicBoolean.new(false)
|
|
508
520
|
@io_queue << [:attach_http2, id, stream_id, body, attached, finished]
|
|
509
|
-
|
|
521
|
+
wakeup rescue nil
|
|
510
522
|
@io_applied.wait { attached.true? }
|
|
511
523
|
return false if body.closed?
|
|
512
524
|
|
|
@@ -527,7 +539,7 @@ module Raptor
|
|
|
527
539
|
return if @detached_count.value.zero?
|
|
528
540
|
|
|
529
541
|
@io_queue << [:resume_http2, id]
|
|
530
|
-
|
|
542
|
+
wakeup rescue nil
|
|
531
543
|
end
|
|
532
544
|
|
|
533
545
|
# Cancels a detached response stream.
|
|
@@ -541,7 +553,7 @@ module Raptor
|
|
|
541
553
|
return if @detached_count.value.zero?
|
|
542
554
|
|
|
543
555
|
@io_queue << [:cancel_http2, id, stream_id]
|
|
544
|
-
|
|
556
|
+
wakeup rescue nil
|
|
545
557
|
end
|
|
546
558
|
|
|
547
559
|
# Waits for detached responses to finish, then cancels any that outlive
|
|
@@ -554,7 +566,7 @@ module Raptor
|
|
|
554
566
|
def drain_detached_bodies(timeout)
|
|
555
567
|
barrier = AtomicBoolean.new(false)
|
|
556
568
|
@io_queue << [:barrier, nil, barrier]
|
|
557
|
-
|
|
569
|
+
wakeup rescue nil
|
|
558
570
|
@io_applied.wait { barrier.true? }
|
|
559
571
|
|
|
560
572
|
Timeout.timeout(timeout) do
|
|
@@ -562,7 +574,7 @@ module Raptor
|
|
|
562
574
|
end
|
|
563
575
|
rescue Timeout::Error
|
|
564
576
|
@io_queue << [:cancel_all]
|
|
565
|
-
|
|
577
|
+
wakeup rescue nil
|
|
566
578
|
@detached_drained.wait { @detached_count.value.zero? }
|
|
567
579
|
end
|
|
568
580
|
|
|
@@ -617,7 +629,7 @@ module Raptor
|
|
|
617
629
|
return unless socket
|
|
618
630
|
|
|
619
631
|
@queue << socket
|
|
620
|
-
|
|
632
|
+
wakeup
|
|
621
633
|
rescue ClosedQueueError
|
|
622
634
|
socket.close
|
|
623
635
|
end
|
|
@@ -630,7 +642,7 @@ module Raptor
|
|
|
630
642
|
# @rbs (Integer id) -> void
|
|
631
643
|
def close_connection(id)
|
|
632
644
|
@io_queue << [:close, id]
|
|
633
|
-
|
|
645
|
+
wakeup rescue nil
|
|
634
646
|
end
|
|
635
647
|
|
|
636
648
|
# Closes the registration queue and wakes the selector so the
|
|
@@ -641,7 +653,7 @@ module Raptor
|
|
|
641
653
|
# @rbs () -> void
|
|
642
654
|
def shutdown
|
|
643
655
|
@queue.close
|
|
644
|
-
|
|
656
|
+
wakeup
|
|
645
657
|
end
|
|
646
658
|
|
|
647
659
|
# Returns the number of complete requests either being processed
|
|
@@ -656,6 +668,17 @@ module Raptor
|
|
|
656
668
|
|
|
657
669
|
private
|
|
658
670
|
|
|
671
|
+
# Wakes the event loop, including work queued before it starts waiting.
|
|
672
|
+
#
|
|
673
|
+
# @rbs () -> void
|
|
674
|
+
def wakeup
|
|
675
|
+
selector = @selector
|
|
676
|
+
return unless selector
|
|
677
|
+
|
|
678
|
+
selector.push(@wakeup)
|
|
679
|
+
selector.wakeup
|
|
680
|
+
end
|
|
681
|
+
|
|
659
682
|
# Returns whether shutdown has no registrations or writes left to drain.
|
|
660
683
|
#
|
|
661
684
|
# @return [Boolean]
|
|
@@ -760,7 +783,7 @@ module Raptor
|
|
|
760
783
|
dispatch = proc { |callback| @thread_pool << callback }
|
|
761
784
|
wake = proc do
|
|
762
785
|
@io_queue << [:ready_http1, id]
|
|
763
|
-
|
|
786
|
+
wakeup rescue nil
|
|
764
787
|
end
|
|
765
788
|
unless body.attach([io.budget, @detached_budget], wake, dispatch, finished)
|
|
766
789
|
socket.close rescue nil
|
|
@@ -771,7 +794,7 @@ module Raptor
|
|
|
771
794
|
@socket_to_state[socket] = { id: id, protocol: :http1, detached: true }
|
|
772
795
|
@id_to_io[id] = io
|
|
773
796
|
@detached_count.swap { |count| count + 1 }
|
|
774
|
-
|
|
797
|
+
update_interests(id, io)
|
|
775
798
|
end
|
|
776
799
|
|
|
777
800
|
# Writes buffered HTTP/1.x body chunks without blocking.
|
|
@@ -803,7 +826,7 @@ module Raptor
|
|
|
803
826
|
|
|
804
827
|
if !io.finishing && !io.wait && io.body&.next_size
|
|
805
828
|
@io_queue << [:ready_http1, id]
|
|
806
|
-
|
|
829
|
+
wakeup rescue nil
|
|
807
830
|
end
|
|
808
831
|
end
|
|
809
832
|
|
|
@@ -823,7 +846,7 @@ module Raptor
|
|
|
823
846
|
body.finish(:closed)
|
|
824
847
|
detached_body_removed
|
|
825
848
|
@id_to_io.delete(id)
|
|
826
|
-
deregister(socket
|
|
849
|
+
deregister(socket)
|
|
827
850
|
clear_write_timeout(io)
|
|
828
851
|
|
|
829
852
|
if io.state[:keep_alive]
|
|
@@ -887,7 +910,7 @@ module Raptor
|
|
|
887
910
|
|
|
888
911
|
wake = proc do
|
|
889
912
|
@io_queue << [:ready_http2, id, stream_id]
|
|
890
|
-
|
|
913
|
+
wakeup rescue nil
|
|
891
914
|
end
|
|
892
915
|
return unless body.attach([io.budget, @detached_budget], wake, dispatch, finished)
|
|
893
916
|
|
|
@@ -967,7 +990,7 @@ module Raptor
|
|
|
967
990
|
|
|
968
991
|
if !io.wait && io.output.empty? && !io.ready.empty?
|
|
969
992
|
@io_queue << [:flush_http2, id]
|
|
970
|
-
|
|
993
|
+
wakeup rescue nil
|
|
971
994
|
end
|
|
972
995
|
end
|
|
973
996
|
|
|
@@ -1133,7 +1156,7 @@ module Raptor
|
|
|
1133
1156
|
elsif io.closing
|
|
1134
1157
|
remove_connection(id)
|
|
1135
1158
|
else
|
|
1136
|
-
|
|
1159
|
+
update_interests(id, io)
|
|
1137
1160
|
end
|
|
1138
1161
|
end
|
|
1139
1162
|
|
|
@@ -1148,7 +1171,7 @@ module Raptor
|
|
|
1148
1171
|
def wait_for_write(id, io, readiness)
|
|
1149
1172
|
io.wait = readiness
|
|
1150
1173
|
track_write_timeout(id, io)
|
|
1151
|
-
|
|
1174
|
+
update_interests(id, io)
|
|
1152
1175
|
end
|
|
1153
1176
|
|
|
1154
1177
|
# Updates selector interest for one reactor-owned connection.
|
|
@@ -1158,26 +1181,34 @@ module Raptor
|
|
|
1158
1181
|
# @return [void]
|
|
1159
1182
|
#
|
|
1160
1183
|
# @rbs (Integer id, ConnectionIO io) -> void
|
|
1161
|
-
def
|
|
1184
|
+
def update_interests(id, io)
|
|
1162
1185
|
socket = @id_to_socket[id]
|
|
1163
1186
|
return unless socket && io
|
|
1164
1187
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
:r
|
|
1171
|
-
elsif write
|
|
1172
|
-
:w
|
|
1173
|
-
end
|
|
1188
|
+
events = 0
|
|
1189
|
+
events |= IO::READABLE if io.reading || io.wait == :read
|
|
1190
|
+
events |= IO::WRITABLE if io.wait == :write
|
|
1191
|
+
wait_for_io(socket, events)
|
|
1192
|
+
end
|
|
1174
1193
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1194
|
+
# Waits for a connection's next readable or writable event.
|
|
1195
|
+
#
|
|
1196
|
+
# @rbs (TCPSocket socket, Integer events) -> void
|
|
1197
|
+
def wait_for_io(socket, events)
|
|
1198
|
+
waiter = @waiters[socket]
|
|
1199
|
+
return if waiter && waiter[1] == events
|
|
1200
|
+
|
|
1201
|
+
deregister(socket)
|
|
1202
|
+
return if events.zero?
|
|
1203
|
+
|
|
1204
|
+
fiber = Fiber.new do
|
|
1205
|
+
readiness = @selector.io_wait(Fiber.current, socket, events)
|
|
1206
|
+
@ready << [socket, readiness] if readiness
|
|
1207
|
+
ensure
|
|
1208
|
+
@waiters.delete(socket)
|
|
1180
1209
|
end
|
|
1210
|
+
@waiters[socket] = [fiber, events]
|
|
1211
|
+
fiber.transfer
|
|
1181
1212
|
end
|
|
1182
1213
|
|
|
1183
1214
|
# Starts or refreshes one connection's non-blocking write deadline.
|
|
@@ -1210,19 +1241,15 @@ module Raptor
|
|
|
1210
1241
|
# Removes a socket from selector tracking.
|
|
1211
1242
|
#
|
|
1212
1243
|
# @param socket [TCPSocket] socket to deregister
|
|
1213
|
-
# @param io [ConnectionIO, nil] connection I/O state
|
|
1214
1244
|
# @return [void]
|
|
1215
1245
|
#
|
|
1216
|
-
# @rbs (TCPSocket socket
|
|
1217
|
-
def deregister(socket
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
@selector.deregister(socket)
|
|
1221
|
-
io.monitor = nil if io
|
|
1222
|
-
rescue IOError
|
|
1246
|
+
# @rbs (TCPSocket socket) -> void
|
|
1247
|
+
def deregister(socket)
|
|
1248
|
+
waiter = @waiters.delete(socket)
|
|
1249
|
+
waiter[0].transfer if waiter
|
|
1223
1250
|
end
|
|
1224
1251
|
|
|
1225
|
-
# Registers a socket
|
|
1252
|
+
# Registers a socket for readiness and sets up timeout tracking.
|
|
1226
1253
|
#
|
|
1227
1254
|
# @param socket [TCPSocket] the socket to register
|
|
1228
1255
|
# @return [void]
|
|
@@ -1235,9 +1262,9 @@ module Raptor
|
|
|
1235
1262
|
if state[:protocol] == :http2
|
|
1236
1263
|
io = @id_to_io[state[:id]]
|
|
1237
1264
|
io.reading = true
|
|
1238
|
-
|
|
1265
|
+
update_interests(state[:id], io)
|
|
1239
1266
|
else
|
|
1240
|
-
|
|
1267
|
+
wait_for_io(socket, IO::READABLE)
|
|
1241
1268
|
end
|
|
1242
1269
|
|
|
1243
1270
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -1301,17 +1328,17 @@ module Raptor
|
|
|
1301
1328
|
# Dispatches readable and writable events without allowing application
|
|
1302
1329
|
# threads to take ownership of HTTP/2 sockets.
|
|
1303
1330
|
#
|
|
1304
|
-
# @param
|
|
1331
|
+
# @param socket [TCPSocket] ready client socket
|
|
1332
|
+
# @param events [Integer] readiness events
|
|
1305
1333
|
# @return [void]
|
|
1306
1334
|
#
|
|
1307
|
-
# @rbs (
|
|
1308
|
-
def
|
|
1309
|
-
socket = monitor.value
|
|
1335
|
+
# @rbs (TCPSocket socket, Integer events) -> void
|
|
1336
|
+
def handle_ready(socket, events)
|
|
1310
1337
|
state = @socket_to_state[socket]
|
|
1311
1338
|
return unless state
|
|
1312
1339
|
|
|
1313
1340
|
if state[:detached]
|
|
1314
|
-
|
|
1341
|
+
handle_http1_detached_ready(socket, events, state[:id])
|
|
1315
1342
|
return
|
|
1316
1343
|
end
|
|
1317
1344
|
|
|
@@ -1325,7 +1352,7 @@ module Raptor
|
|
|
1325
1352
|
return unless io
|
|
1326
1353
|
|
|
1327
1354
|
wait = io.wait
|
|
1328
|
-
if (wait == :read &&
|
|
1355
|
+
if (wait == :read && events.anybits?(IO::READABLE)) || (wait == :write && events.anybits?(IO::WRITABLE))
|
|
1329
1356
|
flush_output(id, io)
|
|
1330
1357
|
return unless @id_to_socket.key?(id)
|
|
1331
1358
|
return if io.wait == :read
|
|
@@ -1333,40 +1360,43 @@ module Raptor
|
|
|
1333
1360
|
flush_http2_detached_bodies(id) unless io.detached.empty?
|
|
1334
1361
|
end
|
|
1335
1362
|
|
|
1336
|
-
return unless io.reading &&
|
|
1363
|
+
return unless io.reading && events.anybits?(IO::READABLE)
|
|
1337
1364
|
|
|
1338
1365
|
io.reading = false
|
|
1339
|
-
|
|
1366
|
+
update_interests(id, io)
|
|
1340
1367
|
client = @id_to_timeout.delete(id)
|
|
1341
1368
|
@timeouts.delete!(client) if client
|
|
1342
1369
|
read_and_queue_for_parse(socket, state)
|
|
1370
|
+
ensure
|
|
1371
|
+
io = @id_to_io[state[:id]] if state
|
|
1372
|
+
update_interests(state[:id], io) if io
|
|
1343
1373
|
end
|
|
1344
1374
|
|
|
1345
1375
|
# Handles readiness for an HTTP/1.x detached response.
|
|
1346
1376
|
#
|
|
1347
|
-
# @param
|
|
1377
|
+
# @param socket [TCPSocket] ready client socket
|
|
1378
|
+
# @param events [Integer] readiness events
|
|
1348
1379
|
# @param id [Integer] unique connection identifier
|
|
1349
1380
|
# @return [void]
|
|
1350
1381
|
#
|
|
1351
|
-
# @rbs (
|
|
1352
|
-
def
|
|
1353
|
-
socket = monitor.value
|
|
1382
|
+
# @rbs (TCPSocket socket, Integer events, Integer id) -> void
|
|
1383
|
+
def handle_http1_detached_ready(socket, events, id)
|
|
1354
1384
|
io = @id_to_io[id]
|
|
1355
1385
|
return unless io.is_a?(Http1IO)
|
|
1356
1386
|
|
|
1357
1387
|
wait = io.wait
|
|
1358
|
-
if (wait == :read &&
|
|
1388
|
+
if (wait == :read && events.anybits?(IO::READABLE)) || (wait == :write && events.anybits?(IO::WRITABLE))
|
|
1359
1389
|
flush_output(id, io)
|
|
1360
1390
|
return unless @id_to_io.key?(id)
|
|
1361
1391
|
|
|
1362
1392
|
flush_http1_detached_body(id) unless io.wait
|
|
1363
1393
|
end
|
|
1364
1394
|
|
|
1365
|
-
return unless io.reading &&
|
|
1395
|
+
return unless io.reading && events.anybits?(IO::READABLE)
|
|
1366
1396
|
|
|
1367
1397
|
io.input << socket.read_nonblock(CHUNK_SIZE)
|
|
1368
1398
|
io.reading = false
|
|
1369
|
-
|
|
1399
|
+
update_interests(id, io)
|
|
1370
1400
|
rescue IO::WaitReadable
|
|
1371
1401
|
rescue IO::WaitWritable
|
|
1372
1402
|
wait_for_write(id, io, :write)
|
|
@@ -1382,7 +1412,7 @@ module Raptor
|
|
|
1382
1412
|
#
|
|
1383
1413
|
# @rbs (TCPSocket socket) -> void
|
|
1384
1414
|
def wakeup!(socket)
|
|
1385
|
-
|
|
1415
|
+
deregister(socket)
|
|
1386
1416
|
state = @socket_to_state[socket]
|
|
1387
1417
|
client = @id_to_timeout.delete(state[:id])
|
|
1388
1418
|
@timeouts.delete!(client) if client
|
|
@@ -1433,7 +1463,7 @@ module Raptor
|
|
|
1433
1463
|
socket.read_nonblock(CHUNK_SIZE)
|
|
1434
1464
|
rescue IO::WaitReadable
|
|
1435
1465
|
@queue << socket
|
|
1436
|
-
|
|
1466
|
+
wakeup
|
|
1437
1467
|
return
|
|
1438
1468
|
rescue EOFError, IOError, SystemCallError, OpenSSL::SSL::SSLError
|
|
1439
1469
|
cleanup(socket)
|
|
@@ -1461,7 +1491,7 @@ module Raptor
|
|
|
1461
1491
|
def cleanup(socket)
|
|
1462
1492
|
state = @socket_to_state.delete(socket)
|
|
1463
1493
|
io = @id_to_io.delete(state[:id])
|
|
1464
|
-
deregister(socket
|
|
1494
|
+
deregister(socket)
|
|
1465
1495
|
@id_to_socket.delete(state[:id])
|
|
1466
1496
|
@id_to_writer.delete(state[:id])
|
|
1467
1497
|
@id_to_flow_control.delete(state[:id])&.close
|
data/lib/raptor/version.rb
CHANGED
data/lib/raptor.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative "raptor/detached_body"
|
|
|
8
8
|
#
|
|
9
9
|
# Raptor is a high-performance, preloading, multi-process, multi-threaded Ruby 4+ web server
|
|
10
10
|
# implementing Rack 3+, leveraging Ractors for parallel HTTP/1.1 and HTTP/2 request processing,
|
|
11
|
-
# native C extensions for HTTP parsing and HPACK compression, and
|
|
11
|
+
# native C extensions for HTTP parsing and HPACK compression, and io-event for non-blocking I/O.
|
|
12
12
|
#
|
|
13
13
|
module Raptor
|
|
14
14
|
end
|
|
@@ -66,6 +66,8 @@ module Raptor
|
|
|
66
66
|
|
|
67
67
|
@binder: Binder
|
|
68
68
|
|
|
69
|
+
@plugins: Array[Plugin]
|
|
70
|
+
|
|
69
71
|
@before_refork: Array[Proc]
|
|
70
72
|
|
|
71
73
|
@before_worker_shutdown: Array[Proc]
|
|
@@ -152,6 +154,11 @@ module Raptor
|
|
|
152
154
|
|
|
153
155
|
@drain_accept_queue: bool
|
|
154
156
|
|
|
157
|
+
# Returns the cluster configuration available to plugins.
|
|
158
|
+
#
|
|
159
|
+
# @return [Hash{Symbol => untyped}]
|
|
160
|
+
attr_reader options: Hash[Symbol, untyped]
|
|
161
|
+
|
|
155
162
|
# Creates a new Cluster with the given options.
|
|
156
163
|
#
|
|
157
164
|
# @param options [Hash] cluster configuration options
|
|
@@ -180,6 +187,7 @@ module Raptor
|
|
|
180
187
|
# @option options [Array<Proc>] :before_worker_boot procs called with the worker index before it begins serving
|
|
181
188
|
# @option options [Array<Proc>] :before_worker_shutdown procs called with the worker index before its graceful shutdown
|
|
182
189
|
# @option options [Array<Proc>] :before_refork procs called in a worker before it transitions to a seed
|
|
190
|
+
# @option options [Array<String, Symbol>] :plugins plugins to load and start in order
|
|
183
191
|
# @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
|
|
184
192
|
# @option options [String, nil] :control_url `unix://` URL serving cluster stats, or nil to disable
|
|
185
193
|
# @option options [String, nil] :pid_file path to write the master PID to, or nil to disable
|
|
@@ -210,17 +218,22 @@ module Raptor
|
|
|
210
218
|
# @rbs () -> Array[Hash[Symbol, untyped]]
|
|
211
219
|
def stats: () -> Array[Hash[Symbol, untyped]]
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
#
|
|
216
|
-
# pool, `max_threads` is the number of threads currently running so
|
|
217
|
-
# utilization reflects the capacity available at that moment.
|
|
221
|
+
# Returns cluster statistics for plugins and the control server.
|
|
222
|
+
# For an adaptive pool, `max_threads` is the number of threads currently
|
|
223
|
+
# running so utilization reflects the capacity available at that moment.
|
|
218
224
|
#
|
|
219
225
|
# @return [Hash] cluster statistics
|
|
220
226
|
#
|
|
221
227
|
# @rbs () -> Hash[Symbol, untyped]
|
|
222
228
|
def stats_hash: () -> Hash[Symbol, untyped]
|
|
223
229
|
|
|
230
|
+
private
|
|
231
|
+
|
|
232
|
+
# Stops plugins in reverse startup order.
|
|
233
|
+
#
|
|
234
|
+
# @rbs () -> void
|
|
235
|
+
def stop_plugins: () -> void
|
|
236
|
+
|
|
224
237
|
# @rbs (Numeric time) -> String
|
|
225
238
|
def timestamp: (Numeric time) -> String
|
|
226
239
|
|
|
@@ -34,6 +34,8 @@ module Raptor
|
|
|
34
34
|
|
|
35
35
|
INTERNAL_SERVER_ERROR_RESPONSE: ::String
|
|
36
36
|
|
|
37
|
+
NOT_IMPLEMENTED_RESPONSE: ::String
|
|
38
|
+
|
|
37
39
|
CHUNKED_TERMINATOR: ::String
|
|
38
40
|
|
|
39
41
|
CONNECTION_CLOSE: ::String
|
|
@@ -72,17 +74,14 @@ module Raptor
|
|
|
72
74
|
# @rbs (Hash[String, untyped] env) -> bool
|
|
73
75
|
def self.invalid_host?: (Hash[String, untyped] env) -> bool
|
|
74
76
|
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# missing, not the final encoding, or duplicated; a `Transfer-Encoding`
|
|
78
|
-
# paired with a `Content-Length`; or a `Content-Length` containing any
|
|
79
|
-
# non-digit character.
|
|
77
|
+
# Rejects ambiguous message framing per RFC 9112 section 6.3 and
|
|
78
|
+
# transfer codings other than a single `chunked`.
|
|
80
79
|
#
|
|
81
80
|
# @param env [Hash] the Rack environment after header parsing
|
|
82
|
-
# @return [
|
|
81
|
+
# @return [Symbol, nil] `:malformed`, `:unsupported_encoding`, or nil
|
|
83
82
|
#
|
|
84
|
-
# @rbs (Hash[String, untyped] env) ->
|
|
85
|
-
def self.
|
|
83
|
+
# @rbs (Hash[String, untyped] env) -> Symbol?
|
|
84
|
+
def self.framing_error: (Hash[String, untyped] env) -> Symbol?
|
|
86
85
|
|
|
87
86
|
# Returns any pipelined bytes following the request, or nil if none remain.
|
|
88
87
|
#
|
|
@@ -112,9 +111,9 @@ module Raptor
|
|
|
112
111
|
# Advances an HTTP/1.x request parse from the state hash's buffered
|
|
113
112
|
# bytes. A complete well-formed request returns with `:complete` set
|
|
114
113
|
# plus populated `:env` and `:body`, and records any bytes after it in
|
|
115
|
-
# the parse data's `:pipelined
|
|
116
|
-
# `:
|
|
117
|
-
# so the reactor can wait for more.
|
|
114
|
+
# the parse data's `:pipelined`. Rejected input sets `:malformed`,
|
|
115
|
+
# `:too_large`, or `:unsupported_encoding`; incomplete input returns
|
|
116
|
+
# the state so the reactor can wait for more.
|
|
118
117
|
#
|
|
119
118
|
# @param data [Hash] the current parse state
|
|
120
119
|
# @param env_template [Hash] the Rack env template to seed the request with
|
|
@@ -256,7 +255,7 @@ module Raptor
|
|
|
256
255
|
# @param parser [HttpParser] the parser holding the finished parse state
|
|
257
256
|
# @param nread [Integer] the byte offset where the body begins in `buffer`
|
|
258
257
|
# @return [String, nil, Symbol] body, nil when absent, or `:incomplete`,
|
|
259
|
-
# `:too_large`, or `:
|
|
258
|
+
# `:too_large`, `:malformed`, or `:unsupported_encoding`
|
|
260
259
|
#
|
|
261
260
|
# @rbs (String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, HttpParser parser, Integer nread) -> (String | Symbol)?
|
|
262
261
|
def extract_body: (String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, HttpParser parser, Integer nread) -> (String | Symbol)?
|
|
@@ -384,6 +383,14 @@ module Raptor
|
|
|
384
383
|
# @rbs (TCPSocket socket) -> void
|
|
385
384
|
def reject_malformed: (TCPSocket socket) -> void
|
|
386
385
|
|
|
386
|
+
# Writes a 501 response and closes the socket.
|
|
387
|
+
#
|
|
388
|
+
# @param socket [TCPSocket] the client socket
|
|
389
|
+
# @return [void]
|
|
390
|
+
#
|
|
391
|
+
# @rbs (TCPSocket socket) -> void
|
|
392
|
+
def reject_unsupported_encoding: (TCPSocket socket) -> void
|
|
393
|
+
|
|
387
394
|
# Builds a Rack environment hash from parsed HTTP request data.
|
|
388
395
|
#
|
|
389
396
|
# @param env [Hash] partial env hash from the HTTP parser
|
|
@@ -399,7 +406,8 @@ module Raptor
|
|
|
399
406
|
|
|
400
407
|
# Builds the `rack.input` IO object for the request body. Returns an
|
|
401
408
|
# in-memory StringIO for bodies up to the spool threshold, or a Tempfile
|
|
402
|
-
# for larger bodies
|
|
409
|
+
# for larger bodies. The complete body is already buffered at this point,
|
|
410
|
+
# so spooling does not bound peak receive memory.
|
|
403
411
|
#
|
|
404
412
|
# @param body [String, nil] decoded request body
|
|
405
413
|
# @return [IO] an IO-like object positioned at the start of the body
|
|
@@ -307,7 +307,7 @@ module Raptor
|
|
|
307
307
|
# updated connection state along with any outgoing protocol frames and
|
|
308
308
|
# completed stream requests.
|
|
309
309
|
#
|
|
310
|
-
# @param data [Hash] the connection state including buffer and
|
|
310
|
+
# @param data [Hash] the connection state including buffer, HPACK table and body-size limit
|
|
311
311
|
# @return [Hash] updated state with outgoing_frames and completed_requests
|
|
312
312
|
#
|
|
313
313
|
# @rbs (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
|
|
@@ -381,23 +381,25 @@ module Raptor
|
|
|
381
381
|
# @rbs (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[Integer] cancelled_streams, Array[[Integer, Integer]] window_updates, Array[String] ping_acknowledgements, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
|
|
382
382
|
def self.build_result: (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[Integer] cancelled_streams, Array[[ Integer, Integer ]] window_updates, Array[String] ping_acknowledgements, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
|
|
383
383
|
|
|
384
|
-
@
|
|
384
|
+
@app: ^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ]
|
|
385
385
|
|
|
386
|
-
@
|
|
386
|
+
@server_port_string: String
|
|
387
387
|
|
|
388
|
-
@
|
|
388
|
+
@write_timeout: Integer
|
|
389
389
|
|
|
390
|
-
@
|
|
390
|
+
@max_body_size: Integer?
|
|
391
|
+
|
|
392
|
+
@access_log_io: IO?
|
|
391
393
|
|
|
392
394
|
@clean_thread_locals: bool
|
|
393
395
|
|
|
394
|
-
@
|
|
396
|
+
@clean_fiber_locals: bool
|
|
395
397
|
|
|
396
|
-
@
|
|
398
|
+
@on_error: (^(Hash[String, untyped]?, Exception) -> void)?
|
|
397
399
|
|
|
398
|
-
@
|
|
400
|
+
@running: AtomicBoolean
|
|
399
401
|
|
|
400
|
-
@
|
|
402
|
+
@initial_settings_frame: String
|
|
401
403
|
|
|
402
404
|
# Creates a new Http2 handler.
|
|
403
405
|
#
|
|
@@ -405,6 +407,7 @@ module Raptor
|
|
|
405
407
|
# @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
|
|
406
408
|
# @param connection_options [Hash] per-connection settings shared across protocols
|
|
407
409
|
# @option connection_options [Integer] :write_timeout per-write socket timeout in seconds
|
|
410
|
+
# @option connection_options [Integer, nil] :max_body_size maximum request body size in bytes
|
|
408
411
|
# @param http2_options [Hash] HTTP/2-specific settings
|
|
409
412
|
# @option http2_options [Integer] :max_concurrent_streams maximum HTTP/2 concurrent streams per connection
|
|
410
413
|
# @param access_log_io [IO, nil] IO to write Common Log Format access entries to, or nil to disable
|