raptor 0.24.0 → 0.26.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.
@@ -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 "nio"
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: NIO::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 = NIO::Selector.new
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
@@ -259,16 +263,31 @@ module Raptor
259
263
  #
260
264
  # @rbs () -> Thread
261
265
  def run
262
- Thread.new do
266
+ ready = AtomicConditionVariable.new
267
+ startup_error = nil
268
+ thread = Thread.new do
263
269
  Thread.current.name = "Reactor"
264
270
  @thread = Thread.current
271
+ begin
272
+ selector = IO::Event::Selector.new(Fiber.current)
273
+ @wakeup = Fiber.new { loop { selector.transfer } }
274
+ @selector = selector
275
+ rescue => error
276
+ startup_error = error
277
+ ready.signal
278
+ next
279
+ end
280
+ ready.signal
265
281
 
266
282
  until stopped?
267
283
  begin
268
284
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
269
285
  timeout = @timeouts.min&.timeout(now)
270
- @selector.select(timeout) do |monitor|
271
- handle_monitor(monitor)
286
+ timeout = 0 unless @ready.empty? && @queue.empty? && @io_queue.empty?
287
+ @selector.select(timeout)
288
+ until @ready.empty?
289
+ socket, events = @ready.shift
290
+ handle_ready(socket, events)
272
291
  end
273
292
 
274
293
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -283,6 +302,8 @@ module Raptor
283
302
  end
284
303
  end
285
304
 
305
+ @waiters.keys.each { |socket| deregister(socket) }
306
+ @ready.clear
286
307
  @id_to_flow_control.each_value(&:close)
287
308
  @id_to_socket.each_value { |socket| socket.close rescue nil }
288
309
  @id_to_socket.clear
@@ -298,6 +319,14 @@ module Raptor
298
319
  @thread = nil
299
320
  @selector.close
300
321
  end
322
+
323
+ ready.wait { @selector || startup_error }
324
+ if startup_error
325
+ thread.join
326
+ raise startup_error
327
+ end
328
+
329
+ thread
301
330
  end
302
331
 
303
332
  # Adds a new client connection to the reactor.
@@ -330,7 +359,7 @@ module Raptor
330
359
 
331
360
  @socket_to_state[socket] = state
332
361
  @queue << socket
333
- @selector.wakeup
362
+ wakeup
334
363
  rescue ClosedQueueError
335
364
  socket.close
336
365
  end
@@ -372,7 +401,7 @@ module Raptor
372
401
  @id_to_socket[id] = socket
373
402
  @socket_to_state[socket] = state
374
403
  @queue << socket
375
- @selector.wakeup
404
+ wakeup
376
405
  rescue ClosedQueueError
377
406
  socket.close
378
407
  end
@@ -422,7 +451,7 @@ module Raptor
422
451
  def attach_http1_body(socket, id, body, state, finished)
423
452
  attached = AtomicBoolean.new(false)
424
453
  @io_queue << [:attach_http1, id, socket, body, attached, finished, state]
425
- @selector.wakeup rescue nil
454
+ wakeup rescue nil
426
455
  @io_applied.wait { attached.true? }
427
456
  return false if body.closed?
428
457
 
@@ -430,7 +459,7 @@ module Raptor
430
459
  true
431
460
  rescue
432
461
  @io_queue << [:cancel_http1, id]
433
- @selector.wakeup rescue nil
462
+ wakeup rescue nil
434
463
  raise
435
464
  end
436
465
 
@@ -490,7 +519,7 @@ module Raptor
490
519
  flush_output(id, io) if io && !io.wait
491
520
  else
492
521
  @io_queue << [:write, id, data]
493
- @selector.wakeup rescue nil
522
+ wakeup rescue nil
494
523
  end
495
524
  end
496
525
 
@@ -506,7 +535,7 @@ module Raptor
506
535
  def attach_http2_body(id, stream_id, body, finished)
507
536
  attached = AtomicBoolean.new(false)
508
537
  @io_queue << [:attach_http2, id, stream_id, body, attached, finished]
509
- @selector.wakeup rescue nil
538
+ wakeup rescue nil
510
539
  @io_applied.wait { attached.true? }
511
540
  return false if body.closed?
512
541
 
@@ -527,7 +556,7 @@ module Raptor
527
556
  return if @detached_count.value.zero?
528
557
 
529
558
  @io_queue << [:resume_http2, id]
530
- @selector.wakeup rescue nil
559
+ wakeup rescue nil
531
560
  end
532
561
 
533
562
  # Cancels a detached response stream.
@@ -541,7 +570,7 @@ module Raptor
541
570
  return if @detached_count.value.zero?
542
571
 
543
572
  @io_queue << [:cancel_http2, id, stream_id]
544
- @selector.wakeup rescue nil
573
+ wakeup rescue nil
545
574
  end
546
575
 
547
576
  # Waits for detached responses to finish, then cancels any that outlive
@@ -554,7 +583,7 @@ module Raptor
554
583
  def drain_detached_bodies(timeout)
555
584
  barrier = AtomicBoolean.new(false)
556
585
  @io_queue << [:barrier, nil, barrier]
557
- @selector.wakeup rescue nil
586
+ wakeup rescue nil
558
587
  @io_applied.wait { barrier.true? }
559
588
 
560
589
  Timeout.timeout(timeout) do
@@ -562,7 +591,7 @@ module Raptor
562
591
  end
563
592
  rescue Timeout::Error
564
593
  @io_queue << [:cancel_all]
565
- @selector.wakeup rescue nil
594
+ wakeup rescue nil
566
595
  @detached_drained.wait { @detached_count.value.zero? }
567
596
  end
568
597
 
@@ -617,7 +646,7 @@ module Raptor
617
646
  return unless socket
618
647
 
619
648
  @queue << socket
620
- @selector.wakeup
649
+ wakeup
621
650
  rescue ClosedQueueError
622
651
  socket.close
623
652
  end
@@ -630,7 +659,7 @@ module Raptor
630
659
  # @rbs (Integer id) -> void
631
660
  def close_connection(id)
632
661
  @io_queue << [:close, id]
633
- @selector.wakeup rescue nil
662
+ wakeup rescue nil
634
663
  end
635
664
 
636
665
  # Closes the registration queue and wakes the selector so the
@@ -641,7 +670,7 @@ module Raptor
641
670
  # @rbs () -> void
642
671
  def shutdown
643
672
  @queue.close
644
- @selector.wakeup
673
+ wakeup
645
674
  end
646
675
 
647
676
  # Returns the number of complete requests either being processed
@@ -656,6 +685,17 @@ module Raptor
656
685
 
657
686
  private
658
687
 
688
+ # Wakes the event loop, including work queued before it starts waiting.
689
+ #
690
+ # @rbs () -> void
691
+ def wakeup
692
+ selector = @selector
693
+ return unless selector
694
+
695
+ selector.push(@wakeup)
696
+ selector.wakeup
697
+ end
698
+
659
699
  # Returns whether shutdown has no registrations or writes left to drain.
660
700
  #
661
701
  # @return [Boolean]
@@ -760,7 +800,7 @@ module Raptor
760
800
  dispatch = proc { |callback| @thread_pool << callback }
761
801
  wake = proc do
762
802
  @io_queue << [:ready_http1, id]
763
- @selector.wakeup rescue nil
803
+ wakeup rescue nil
764
804
  end
765
805
  unless body.attach([io.budget, @detached_budget], wake, dispatch, finished)
766
806
  socket.close rescue nil
@@ -771,7 +811,7 @@ module Raptor
771
811
  @socket_to_state[socket] = { id: id, protocol: :http1, detached: true }
772
812
  @id_to_io[id] = io
773
813
  @detached_count.swap { |count| count + 1 }
774
- update_monitor(id, io)
814
+ update_interests(id, io)
775
815
  end
776
816
 
777
817
  # Writes buffered HTTP/1.x body chunks without blocking.
@@ -803,7 +843,7 @@ module Raptor
803
843
 
804
844
  if !io.finishing && !io.wait && io.body&.next_size
805
845
  @io_queue << [:ready_http1, id]
806
- @selector.wakeup rescue nil
846
+ wakeup rescue nil
807
847
  end
808
848
  end
809
849
 
@@ -823,7 +863,7 @@ module Raptor
823
863
  body.finish(:closed)
824
864
  detached_body_removed
825
865
  @id_to_io.delete(id)
826
- deregister(socket, io)
866
+ deregister(socket)
827
867
  clear_write_timeout(io)
828
868
 
829
869
  if io.state[:keep_alive]
@@ -887,7 +927,7 @@ module Raptor
887
927
 
888
928
  wake = proc do
889
929
  @io_queue << [:ready_http2, id, stream_id]
890
- @selector.wakeup rescue nil
930
+ wakeup rescue nil
891
931
  end
892
932
  return unless body.attach([io.budget, @detached_budget], wake, dispatch, finished)
893
933
 
@@ -967,7 +1007,7 @@ module Raptor
967
1007
 
968
1008
  if !io.wait && io.output.empty? && !io.ready.empty?
969
1009
  @io_queue << [:flush_http2, id]
970
- @selector.wakeup rescue nil
1010
+ wakeup rescue nil
971
1011
  end
972
1012
  end
973
1013
 
@@ -1133,7 +1173,7 @@ module Raptor
1133
1173
  elsif io.closing
1134
1174
  remove_connection(id)
1135
1175
  else
1136
- update_monitor(id, io)
1176
+ update_interests(id, io)
1137
1177
  end
1138
1178
  end
1139
1179
 
@@ -1148,7 +1188,7 @@ module Raptor
1148
1188
  def wait_for_write(id, io, readiness)
1149
1189
  io.wait = readiness
1150
1190
  track_write_timeout(id, io)
1151
- update_monitor(id, io)
1191
+ update_interests(id, io)
1152
1192
  end
1153
1193
 
1154
1194
  # Updates selector interest for one reactor-owned connection.
@@ -1158,26 +1198,34 @@ module Raptor
1158
1198
  # @return [void]
1159
1199
  #
1160
1200
  # @rbs (Integer id, ConnectionIO io) -> void
1161
- def update_monitor(id, io)
1201
+ def update_interests(id, io)
1162
1202
  socket = @id_to_socket[id]
1163
1203
  return unless socket && io
1164
1204
 
1165
- read = io.reading || io.wait == :read
1166
- write = io.wait == :write
1167
- interests = if read && write
1168
- :rw
1169
- elsif read
1170
- :r
1171
- elsif write
1172
- :w
1173
- end
1205
+ events = 0
1206
+ events |= IO::READABLE if io.reading || io.wait == :read
1207
+ events |= IO::WRITABLE if io.wait == :write
1208
+ wait_for_io(socket, events)
1209
+ end
1210
+
1211
+ # Waits for a connection's next readable or writable event.
1212
+ #
1213
+ # @rbs (TCPSocket socket, Integer events) -> void
1214
+ def wait_for_io(socket, events)
1215
+ waiter = @waiters[socket]
1216
+ return if waiter && waiter[1] == events
1174
1217
 
1175
- if io.monitor
1176
- io.monitor.interests = interests
1177
- elsif interests
1178
- io.monitor = @selector.register(socket, interests)
1179
- io.monitor.value = socket
1218
+ deregister(socket)
1219
+ return if events.zero?
1220
+
1221
+ fiber = Fiber.new do
1222
+ readiness = @selector.io_wait(Fiber.current, socket, events)
1223
+ @ready << [socket, readiness] if readiness
1224
+ ensure
1225
+ @waiters.delete(socket)
1180
1226
  end
1227
+ @waiters[socket] = [fiber, events]
1228
+ fiber.transfer
1181
1229
  end
1182
1230
 
1183
1231
  # Starts or refreshes one connection's non-blocking write deadline.
@@ -1210,19 +1258,15 @@ module Raptor
1210
1258
  # Removes a socket from selector tracking.
1211
1259
  #
1212
1260
  # @param socket [TCPSocket] socket to deregister
1213
- # @param io [ConnectionIO, nil] connection I/O state
1214
1261
  # @return [void]
1215
1262
  #
1216
- # @rbs (TCPSocket socket, ConnectionIO? io) -> void
1217
- def deregister(socket, io)
1218
- return unless io&.monitor || @selector.registered?(socket)
1219
-
1220
- @selector.deregister(socket)
1221
- io.monitor = nil if io
1222
- rescue IOError
1263
+ # @rbs (TCPSocket socket) -> void
1264
+ def deregister(socket)
1265
+ waiter = @waiters.delete(socket)
1266
+ waiter[0].transfer if waiter
1223
1267
  end
1224
1268
 
1225
- # Registers a socket with the NIO selector and sets up timeout tracking.
1269
+ # Registers a socket for readiness and sets up timeout tracking.
1226
1270
  #
1227
1271
  # @param socket [TCPSocket] the socket to register
1228
1272
  # @return [void]
@@ -1235,9 +1279,9 @@ module Raptor
1235
1279
  if state[:protocol] == :http2
1236
1280
  io = @id_to_io[state[:id]]
1237
1281
  io.reading = true
1238
- update_monitor(state[:id], io)
1282
+ update_interests(state[:id], io)
1239
1283
  else
1240
- @selector.register(socket, :r).value = socket
1284
+ wait_for_io(socket, IO::READABLE)
1241
1285
  end
1242
1286
 
1243
1287
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -1301,17 +1345,17 @@ module Raptor
1301
1345
  # Dispatches readable and writable events without allowing application
1302
1346
  # threads to take ownership of HTTP/2 sockets.
1303
1347
  #
1304
- # @param monitor [NIO::Monitor] ready selector monitor
1348
+ # @param socket [TCPSocket] ready client socket
1349
+ # @param events [Integer] readiness events
1305
1350
  # @return [void]
1306
1351
  #
1307
- # @rbs (NIO::Monitor monitor) -> void
1308
- def handle_monitor(monitor)
1309
- socket = monitor.value
1352
+ # @rbs (TCPSocket socket, Integer events) -> void
1353
+ def handle_ready(socket, events)
1310
1354
  state = @socket_to_state[socket]
1311
1355
  return unless state
1312
1356
 
1313
1357
  if state[:detached]
1314
- handle_http1_detached_monitor(monitor, state[:id])
1358
+ handle_http1_detached_ready(socket, events, state[:id])
1315
1359
  return
1316
1360
  end
1317
1361
 
@@ -1325,7 +1369,7 @@ module Raptor
1325
1369
  return unless io
1326
1370
 
1327
1371
  wait = io.wait
1328
- if (wait == :read && monitor.readable?) || (wait == :write && monitor.writable?)
1372
+ if (wait == :read && events.anybits?(IO::READABLE)) || (wait == :write && events.anybits?(IO::WRITABLE))
1329
1373
  flush_output(id, io)
1330
1374
  return unless @id_to_socket.key?(id)
1331
1375
  return if io.wait == :read
@@ -1333,40 +1377,43 @@ module Raptor
1333
1377
  flush_http2_detached_bodies(id) unless io.detached.empty?
1334
1378
  end
1335
1379
 
1336
- return unless io.reading && monitor.readable?
1380
+ return unless io.reading && events.anybits?(IO::READABLE)
1337
1381
 
1338
1382
  io.reading = false
1339
- update_monitor(id, io)
1383
+ update_interests(id, io)
1340
1384
  client = @id_to_timeout.delete(id)
1341
1385
  @timeouts.delete!(client) if client
1342
1386
  read_and_queue_for_parse(socket, state)
1387
+ ensure
1388
+ io = @id_to_io[state[:id]] if state
1389
+ update_interests(state[:id], io) if io
1343
1390
  end
1344
1391
 
1345
1392
  # Handles readiness for an HTTP/1.x detached response.
1346
1393
  #
1347
- # @param monitor [NIO::Monitor] ready selector monitor
1394
+ # @param socket [TCPSocket] ready client socket
1395
+ # @param events [Integer] readiness events
1348
1396
  # @param id [Integer] unique connection identifier
1349
1397
  # @return [void]
1350
1398
  #
1351
- # @rbs (NIO::Monitor monitor, Integer id) -> void
1352
- def handle_http1_detached_monitor(monitor, id)
1353
- socket = monitor.value
1399
+ # @rbs (TCPSocket socket, Integer events, Integer id) -> void
1400
+ def handle_http1_detached_ready(socket, events, id)
1354
1401
  io = @id_to_io[id]
1355
1402
  return unless io.is_a?(Http1IO)
1356
1403
 
1357
1404
  wait = io.wait
1358
- if (wait == :read && monitor.readable?) || (wait == :write && monitor.writable?)
1405
+ if (wait == :read && events.anybits?(IO::READABLE)) || (wait == :write && events.anybits?(IO::WRITABLE))
1359
1406
  flush_output(id, io)
1360
1407
  return unless @id_to_io.key?(id)
1361
1408
 
1362
1409
  flush_http1_detached_body(id) unless io.wait
1363
1410
  end
1364
1411
 
1365
- return unless io.reading && monitor.readable?
1412
+ return unless io.reading && events.anybits?(IO::READABLE)
1366
1413
 
1367
1414
  io.input << socket.read_nonblock(CHUNK_SIZE)
1368
1415
  io.reading = false
1369
- update_monitor(id, io)
1416
+ update_interests(id, io)
1370
1417
  rescue IO::WaitReadable
1371
1418
  rescue IO::WaitWritable
1372
1419
  wait_for_write(id, io, :write)
@@ -1382,7 +1429,7 @@ module Raptor
1382
1429
  #
1383
1430
  # @rbs (TCPSocket socket) -> void
1384
1431
  def wakeup!(socket)
1385
- @selector.deregister(socket)
1432
+ deregister(socket)
1386
1433
  state = @socket_to_state[socket]
1387
1434
  client = @id_to_timeout.delete(state[:id])
1388
1435
  @timeouts.delete!(client) if client
@@ -1433,7 +1480,7 @@ module Raptor
1433
1480
  socket.read_nonblock(CHUNK_SIZE)
1434
1481
  rescue IO::WaitReadable
1435
1482
  @queue << socket
1436
- @selector.wakeup
1483
+ wakeup
1437
1484
  return
1438
1485
  rescue EOFError, IOError, SystemCallError, OpenSSL::SSL::SSLError
1439
1486
  cleanup(socket)
@@ -1461,7 +1508,7 @@ module Raptor
1461
1508
  def cleanup(socket)
1462
1509
  state = @socket_to_state.delete(socket)
1463
1510
  io = @id_to_io.delete(state[:id])
1464
- deregister(socket, io)
1511
+ deregister(socket)
1465
1512
  @id_to_socket.delete(state[:id])
1466
1513
  @id_to_writer.delete(state[:id])
1467
1514
  @id_to_flow_control.delete(state[:id])&.close
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Raptor
5
- VERSION = "0.24.0"
5
+ VERSION = "0.26.0"
6
6
  end