raptor 0.22.2 → 0.23.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/Dockerfile +3 -1
  4. data/README.md +38 -31
  5. data/docs/raptor-vs-puma.md +44 -38
  6. data/docs/talk.md +1295 -0
  7. data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
  8. data/ext/raptor_http/extconf.rb +2 -0
  9. data/ext/raptor_http/raptor_http.c +91 -119
  10. data/ext/raptor_http2/raptor_http2.c +272 -223
  11. data/ext/raptor_native/raptor_native.c +38 -20
  12. data/lib/rackup/handler/raptor.rb +12 -11
  13. data/lib/raptor/binder.rb +12 -12
  14. data/lib/raptor/cli.rb +18 -18
  15. data/lib/raptor/cluster.rb +44 -44
  16. data/lib/raptor/control_server.rb +3 -2
  17. data/lib/raptor/detached_body.rb +6 -2
  18. data/lib/raptor/http.rb +7 -5
  19. data/lib/raptor/http1.rb +233 -208
  20. data/lib/raptor/http2.rb +314 -267
  21. data/lib/raptor/reactor.rb +139 -149
  22. data/lib/raptor/reuseport_bpf.rb +62 -38
  23. data/lib/raptor/server.rb +53 -38
  24. data/lib/raptor/stats.rb +15 -3
  25. data/lib/raptor/thread_locals.rb +4 -3
  26. data/lib/raptor/version.rb +1 -1
  27. data/sig/generated/rackup/handler/raptor.rbs +2 -1
  28. data/sig/generated/raptor/binder.rbs +9 -9
  29. data/sig/generated/raptor/cli.rbs +6 -6
  30. data/sig/generated/raptor/cluster.rbs +9 -9
  31. data/sig/generated/raptor/control_server.rbs +2 -1
  32. data/sig/generated/raptor/detached_body.rbs +5 -1
  33. data/sig/generated/raptor/http.rbs +3 -3
  34. data/sig/generated/raptor/http1.rbs +100 -91
  35. data/sig/generated/raptor/http2.rbs +200 -189
  36. data/sig/generated/raptor/reactor.rbs +107 -118
  37. data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
  38. data/sig/generated/raptor/server.rbs +26 -26
  39. data/sig/generated/raptor/stats.rbs +3 -2
  40. data/sig/generated/raptor/thread_locals.rbs +4 -3
  41. metadata +2 -2
  42. data/docs/brisrails-talk.md +0 -2398
@@ -1,20 +1,22 @@
1
1
  # rbs_inline: enabled
2
2
  # frozen_string_literal: true
3
3
 
4
- require "nio"
5
4
  require "openssl"
6
- require "red-black-tree"
7
5
  require "timeout"
8
6
 
9
7
  require "atomic-ruby/atom"
10
8
  require "atomic-ruby/atomic_boolean"
11
9
  require "atomic-ruby/atomic_condition_variable"
12
10
  require "atomic-ruby/atomic_queue"
11
+ require "atomic-ruby/atomic_thread_pool"
12
+ require "nio"
13
+ require "red-black-tree"
13
14
 
14
15
  require_relative "detached_body"
15
16
  require_relative "http1"
16
17
  require_relative "http2"
17
18
  require_relative "log"
19
+ require_relative "raptor_http2"
18
20
 
19
21
  module Raptor
20
22
  # Multiplexes client connections, manages connection deadlines, feeds
@@ -22,12 +24,18 @@ module Raptor
22
24
  # to the caller-provided handlers.
23
25
  #
24
26
  class Reactor
27
+ CHUNK_SIZE = 64 * 1024
28
+ DETACHED_CONNECTION_BUFFER_SIZE = 1024 * 1024
29
+ DETACHED_WRITE_BATCH = 8
30
+ DETACHED_WORKER_BUFFER_SIZE = 16 * 1024 * 1024
31
+
32
+ TIMEOUT_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
33
+
25
34
  # A client connection node ordered by absolute expiry time so the
26
35
  # soonest-to-expire is always at the tree's minimum.
27
36
  #
28
37
  class TimeoutClient < RedBlackTree::Node
29
- # @rbs attr_accessor timeout_at: Float
30
- attr_accessor :timeout_at
38
+ attr_accessor :timeout_at #: Float
31
39
 
32
40
  # Returns the client connection state.
33
41
  #
@@ -46,7 +54,7 @@ module Raptor
46
54
  #
47
55
  # @rbs (Float now) -> Float
48
56
  def timeout(now)
49
- [timeout_at - now, 0].max
57
+ [timeout_at - now, 0.0].max
50
58
  end
51
59
 
52
60
  # Orders nodes by `timeout_at` so the tree minimum is the next
@@ -64,26 +72,13 @@ module Raptor
64
72
  # Tracks reactor-owned output state for a connection.
65
73
  #
66
74
  class ConnectionIO
67
- # @rbs attr_accessor offset: Integer
68
- attr_accessor :offset
69
-
70
- # @rbs attr_accessor wait: Symbol?
71
- attr_accessor :wait
72
-
73
- # @rbs attr_accessor reading: bool
74
- attr_accessor :reading
75
-
76
- # @rbs attr_accessor closing: bool
77
- attr_accessor :closing
78
-
79
- # @rbs attr_accessor monitor: NIO::Monitor?
80
- attr_accessor :monitor
81
-
82
- # @rbs attr_accessor timeout: TimeoutClient?
83
- attr_accessor :timeout
84
-
85
- # @rbs attr_reader output: Array[String]
86
- attr_reader :output
75
+ attr_accessor :offset #: Integer
76
+ attr_accessor :wait #: Symbol?
77
+ attr_accessor :reading #: bool
78
+ attr_accessor :closing #: bool
79
+ attr_accessor :monitor #: NIO::Monitor?
80
+ attr_accessor :timeout #: TimeoutClient?
81
+ attr_reader :output #: Array[String]
87
82
 
88
83
  # Creates empty connection I/O state for reactor-owned writes.
89
84
  #
@@ -113,25 +108,16 @@ module Raptor
113
108
  # Tracks reactor-owned I/O state for an HTTP/1.x detached response.
114
109
  #
115
110
  class Http1IO < ConnectionIO
116
- # @rbs attr_accessor body: DetachedBody?
117
- attr_accessor :body
118
-
119
- # @rbs attr_accessor finishing: bool
120
- attr_accessor :finishing
121
-
122
- # @rbs attr_reader budget: DetachedBody::Budget
123
- attr_reader :budget
124
-
125
- # @rbs attr_reader input: String
126
- attr_reader :input
127
-
128
- # @rbs attr_reader state: Hash[Symbol, untyped]
129
- attr_reader :state
111
+ attr_accessor :body #: DetachedBody?
112
+ attr_accessor :finishing #: bool
113
+ attr_reader :budget #: DetachedBody::Budget
114
+ attr_reader :input #: String
115
+ attr_reader :state #: Hash[Symbol, untyped]
130
116
 
131
117
  # Creates I/O state for an HTTP/1.x detached response.
132
118
  #
133
119
  # @param body [DetachedBody] the response body
134
- # @param state [Hash] connection state restored after the response finishes
120
+ # @param state [Hash] connection state restored after the response finishes, including bytes the client pipelined after the request
135
121
  # @return [void]
136
122
  #
137
123
  # @rbs (DetachedBody body, Hash[Symbol, untyped] state) -> void
@@ -140,7 +126,7 @@ module Raptor
140
126
  @body = body
141
127
  @state = state
142
128
  @budget = DetachedBody::Budget.new(DETACHED_CONNECTION_BUFFER_SIZE)
143
- @input = String.new(encoding: Encoding::ASCII_8BIT)
129
+ @input = String.new(state[:pipelined] || "", encoding: Encoding::ASCII_8BIT)
144
130
  @reading = true
145
131
  @finishing = false
146
132
  end
@@ -158,17 +144,10 @@ module Raptor
158
144
  # Tracks reactor-owned I/O state for an HTTP/2 connection.
159
145
  #
160
146
  class Http2IO < ConnectionIO
161
- # @rbs attr_reader detached: Hash[Integer, DetachedBody]
162
- attr_reader :detached
163
-
164
- # @rbs attr_reader ready: Array[Integer]
165
- attr_reader :ready
166
-
167
- # @rbs attr_reader ready_streams: Hash[Integer, bool]
168
- attr_reader :ready_streams
169
-
170
- # @rbs attr_reader budget: DetachedBody::Budget
171
- attr_reader :budget
147
+ attr_reader :detached #: Hash[Integer, DetachedBody]
148
+ attr_reader :ready #: Array[Integer]
149
+ attr_reader :ready_streams #: Hash[Integer, bool]
150
+ attr_reader :budget #: DetachedBody::Budget
172
151
 
173
152
  # Creates empty I/O state for an HTTP/2 connection and its detached streams.
174
153
  #
@@ -193,19 +172,14 @@ module Raptor
193
172
  end
194
173
  end
195
174
 
196
- CHUNK_SIZE = 64 * 1024
197
- DETACHED_CONNECTION_BUFFER_SIZE = 1024 * 1024
198
- DETACHED_WRITE_BATCH = 8
199
- DETACHED_WORKER_BUFFER_SIZE = 16 * 1024 * 1024
200
- TIMEOUT_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
201
-
202
- # @rbs @thread_pool: untyped
203
- # @rbs @http1_ractor_pool: untyped
204
- # @rbs @http2_ractor_pool: untyped
175
+ # @rbs @http1: Http1
176
+ # @rbs @http1_ractor_pool: RactorPool
177
+ # @rbs @http2_ractor_pool: RactorPool?
178
+ # @rbs @thread_pool: AtomicThreadPool
205
179
  # @rbs @first_data_timeout: Integer
206
180
  # @rbs @chunk_data_timeout: Integer
207
- # @rbs @persistent_data_timeout: Integer
208
181
  # @rbs @write_timeout: Integer
182
+ # @rbs @persistent_data_timeout: Integer
209
183
  # @rbs @http2_keepalive_interval: Integer
210
184
  # @rbs @http2_keepalive_timeout: Integer
211
185
  # @rbs @selector: NIO::Selector
@@ -216,8 +190,8 @@ module Raptor
216
190
  # @rbs @id_to_socket: Hash[Integer, TCPSocket]
217
191
  # @rbs @socket_to_state: Hash[TCPSocket, Hash[Symbol, untyped]]
218
192
  # @rbs @id_to_timeout: Hash[Integer, TimeoutClient]
219
- # @rbs @id_to_writer: Hash[Integer, untyped]
220
- # @rbs @id_to_flow_control: Hash[Integer, untyped]
193
+ # @rbs @id_to_writer: Hash[Integer, Http2::Writer]
194
+ # @rbs @id_to_flow_control: Hash[Integer, Http2::FlowControl]
221
195
  # @rbs @id_to_http2_last_stream: Hash[Integer, Integer]
222
196
  # @rbs @id_to_http2_drain_stream: Hash[Integer, Integer]
223
197
  # @rbs @id_to_http2_keepalive: Hash[Integer, Hash[Symbol, untyped]]
@@ -227,8 +201,9 @@ module Raptor
227
201
  # @rbs @detached_drained: AtomicConditionVariable
228
202
  # @rbs @thread: Thread?
229
203
 
230
- # Creates a new Reactor instance.
204
+ # Creates a new Reactor.
231
205
  #
206
+ # @param http1 [Http1] the HTTP/1.1 handler that resumes idle keep-alive connections
232
207
  # @param http1_ractor_pool [RactorPool] ractor pool for HTTP/1.x parsing
233
208
  # @param http2_ractor_pool [RactorPool, nil] ractor pool for HTTP/2 parsing, or nil when no listener supports HTTP/2
234
209
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
@@ -243,8 +218,9 @@ module Raptor
243
218
  # @option http2_options [Integer] :keepalive_timeout time to await a PING acknowledgement
244
219
  # @return [void]
245
220
  #
246
- # @rbs (untyped http1_ractor_pool, untyped http2_ractor_pool, untyped thread_pool, connection_options: Hash[Symbol, untyped], http1_options: Hash[Symbol, untyped], http2_options: Hash[Symbol, untyped]) -> void
247
- def initialize(http1_ractor_pool, http2_ractor_pool, thread_pool, connection_options:, http1_options:, http2_options:)
221
+ # @rbs (Http1 http1, RactorPool http1_ractor_pool, RactorPool? http2_ractor_pool, AtomicThreadPool thread_pool, connection_options: Hash[Symbol, untyped], http1_options: Hash[Symbol, untyped], http2_options: Hash[Symbol, untyped]) -> void
222
+ def initialize(http1, http1_ractor_pool, http2_ractor_pool, thread_pool, connection_options:, http1_options:, http2_options:)
223
+ @http1 = http1
248
224
  @http1_ractor_pool = http1_ractor_pool
249
225
  @http2_ractor_pool = http2_ractor_pool
250
226
  @thread_pool = thread_pool
@@ -328,19 +304,14 @@ module Raptor
328
304
  #
329
305
  # @param state [Hash] client connection state including socket and ID
330
306
  # @option state [TCPSocket] :socket the client socket
331
- # @option state [Integer] :id unique identifier for the client
307
+ # @option state [Integer] :id unique connection identifier
332
308
  # @return [void]
333
309
  #
334
310
  # @rbs (Hash[Symbol, untyped] state) -> void
335
311
  def add(state)
336
- socket = state[:socket]
337
- state.delete(:socket)
338
- writer = state.delete(:writer)
339
- flow_control = state.delete(:flow_control)
312
+ socket = state.delete(:socket)
340
313
  @id_to_socket[state[:id]] = socket
341
314
  @socket_to_state[socket] = state
342
- @id_to_writer[state[:id]] = writer if writer
343
- @id_to_flow_control[state[:id]] = flow_control if flow_control
344
315
 
345
316
  read_and_queue_for_parse(socket, state)
346
317
  end
@@ -349,7 +320,7 @@ module Raptor
349
320
  # it for further I/O.
350
321
  #
351
322
  # @param state [Hash] updated client connection state
352
- # @option state [Integer] :id client identifier
323
+ # @option state [Integer] :id unique connection identifier
353
324
  # @return [void]
354
325
  #
355
326
  # @rbs (Hash[Symbol, untyped] state) -> void
@@ -368,7 +339,7 @@ module Raptor
368
339
  # has been handed off to the thread pool. The socket itself is kept
369
340
  # open so the worker can write the response.
370
341
  #
371
- # @param id [Integer] unique client identifier
342
+ # @param id [Integer] unique connection identifier
372
343
  # @return [TCPSocket, nil] the socket associated with `id`, if any
373
344
  #
374
345
  # @rbs (Integer id) -> TCPSocket?
@@ -382,9 +353,9 @@ module Raptor
382
353
  # under the persistent-data timeout.
383
354
  #
384
355
  # @param socket [TCPSocket] the kept-alive client socket
385
- # @param id [Integer] the unique client identifier
356
+ # @param id [Integer] unique connection identifier
386
357
  # @param request_count [Integer] number of requests handled on this connection
387
- # @param remote_addr [String] the client's remote IP address
358
+ # @param remote_addr [String] the client IP address
388
359
  # @param url_scheme [String] "http" or "https"
389
360
  # @return [void]
390
361
  #
@@ -406,9 +377,9 @@ module Raptor
406
377
  socket.close
407
378
  end
408
379
 
409
- # Returns the socket for a given client identifier without removing it.
380
+ # Returns the socket for a given connection identifier without removing it.
410
381
  #
411
- # @param id [Integer] unique client identifier
382
+ # @param id [Integer] unique connection identifier
412
383
  # @return [TCPSocket, nil] the socket, if found
413
384
  #
414
385
  # @rbs (Integer id) -> TCPSocket?
@@ -416,24 +387,24 @@ module Raptor
416
387
  @id_to_socket[id]
417
388
  end
418
389
 
419
- # Returns the writer object associated with a given connection, if one
420
- # was supplied when the connection was added.
390
+ # Returns the HTTP/2 frame writer registered by {attach_http2} for a
391
+ # connection.
421
392
  #
422
- # @param id [Integer] unique client identifier
423
- # @return [Object, nil] the writer, if found
393
+ # @param id [Integer] unique connection identifier
394
+ # @return [Http2::Writer, nil] the writer, if found
424
395
  #
425
- # @rbs (Integer id) -> untyped?
396
+ # @rbs (Integer id) -> Http2::Writer?
426
397
  def writer_for(id)
427
398
  @id_to_writer[id]
428
399
  end
429
400
 
430
- # Returns the flow controller associated with a given connection, if
431
- # one was supplied when the connection was added.
401
+ # Returns the HTTP/2 flow controller registered by {attach_http2} for a
402
+ # connection.
432
403
  #
433
- # @param id [Integer] unique client identifier
434
- # @return [Object, nil] the flow controller, if found
404
+ # @param id [Integer] unique connection identifier
405
+ # @return [Http2::FlowControl, nil] the flow controller, if found
435
406
  #
436
- # @rbs (Integer id) -> untyped?
407
+ # @rbs (Integer id) -> Http2::FlowControl?
437
408
  def flow_control_for(id)
438
409
  @id_to_flow_control[id]
439
410
  end
@@ -441,7 +412,7 @@ module Raptor
441
412
  # Attaches a detached response body to an HTTP/1.x connection.
442
413
  #
443
414
  # @param socket [TCPSocket] the client socket
444
- # @param id [Integer] unique client identifier
415
+ # @param id [Integer] unique connection identifier
445
416
  # @param body [DetachedBody] the response body
446
417
  # @param state [Hash] connection state restored once the response finishes
447
418
  # @param finished [Proc] called with the close reason
@@ -458,7 +429,8 @@ module Raptor
458
429
  body.open
459
430
  true
460
431
  rescue
461
- remove_connection(id)
432
+ @io_queue << [:cancel_http1, id]
433
+ @selector.wakeup rescue nil
462
434
  raise
463
435
  end
464
436
 
@@ -466,7 +438,7 @@ module Raptor
466
438
  # that arrived after graceful draining began.
467
439
  #
468
440
  # @param id [Integer] unique connection identifier
469
- # @param stream_id [Integer] HTTP/2 stream identifier
441
+ # @param stream_id [Integer] the HTTP/2 stream identifier
470
442
  # @return [Boolean] whether the stream may be dispatched
471
443
  #
472
444
  # @rbs (Integer id, Integer stream_id) -> bool
@@ -483,13 +455,15 @@ module Raptor
483
455
  # Sends GOAWAY on every HTTP/2 connection and fixes the last stream each
484
456
  # connection may dispatch while existing application work drains.
485
457
  #
486
- # @yieldparam stream_id [Integer] last dispatched stream identifier
458
+ # @yield for each HTTP/2 connection
459
+ # @yieldparam stream_id [Integer] the last dispatched HTTP/2 stream identifier
487
460
  # @yieldreturn [String] serialized GOAWAY frame
488
461
  # @return [void]
489
462
  #
490
463
  # @rbs () { (Integer stream_id) -> String } -> void
491
464
  def drain_http2
492
- @id_to_flow_control.each_key do |id|
465
+ # Connections may attach on other threads while this iterates.
466
+ @id_to_flow_control.keys.each do |id|
493
467
  last_stream = @id_to_http2_last_stream[id] || 0
494
468
  @id_to_http2_drain_stream[id] = last_stream
495
469
  writer = @id_to_writer[id]
@@ -523,7 +497,7 @@ module Raptor
523
497
  # Attaches a detached response body to an HTTP/2 stream.
524
498
  #
525
499
  # @param id [Integer] unique connection identifier
526
- # @param stream_id [Integer] HTTP/2 stream identifier
500
+ # @param stream_id [Integer] the HTTP/2 stream identifier
527
501
  # @param body [DetachedBody] the response body
528
502
  # @param finished [Proc] called with the close reason
529
503
  # @return [Boolean] whether the body was attached
@@ -559,7 +533,7 @@ module Raptor
559
533
  # Cancels a detached response stream.
560
534
  #
561
535
  # @param id [Integer] unique connection identifier
562
- # @param stream_id [Integer] HTTP/2 stream identifier
536
+ # @param stream_id [Integer] the HTTP/2 stream identifier
563
537
  # @return [void]
564
538
  #
565
539
  # @rbs (Integer id, Integer stream_id) -> void
@@ -595,16 +569,16 @@ module Raptor
595
569
  # Stores an HTTP/2 connection's socket, state, writer, and flow
596
570
  # controller in the reactor's per-connection maps.
597
571
  #
598
- # @param id [Integer] unique client identifier
572
+ # @param id [Integer] unique connection identifier
599
573
  # @param socket [TCPSocket] the connection socket
600
574
  # @param state [Hash] initial connection state
601
- # @param writer [Http2::Writer] per-connection frame writer
575
+ # @param writer [Http2::Writer] frame writer for the connection
602
576
  # @param flow_control [Http2::FlowControl] per-connection outbound flow controller
603
577
  # @param ping_frame [String] serialized PING frame for keepalive probes
604
578
  # @param ping_payload [String] payload expected in the corresponding acknowledgement
605
579
  # @return [void]
606
580
  #
607
- # @rbs (id: Integer, socket: TCPSocket, state: Hash[Symbol, untyped], writer: untyped, flow_control: untyped, ping_frame: String, ping_payload: String) -> void
581
+ # @rbs (id: Integer, socket: TCPSocket, state: Hash[Symbol, untyped], writer: Http2::Writer, flow_control: Http2::FlowControl, ping_frame: String, ping_payload: String) -> void
608
582
  def attach_http2(id:, socket:, state:, writer:, flow_control:, ping_frame:, ping_payload:)
609
583
  @id_to_socket[id] = socket
610
584
  @socket_to_state[socket] = state
@@ -634,7 +608,7 @@ module Raptor
634
608
 
635
609
  # Registers an attached socket for future reactor-driven reads.
636
610
  #
637
- # @param id [Integer] unique client identifier
611
+ # @param id [Integer] unique connection identifier
638
612
  # @return [void]
639
613
  #
640
614
  # @rbs (Integer id) -> void
@@ -648,27 +622,9 @@ module Raptor
648
622
  socket.close
649
623
  end
650
624
 
651
- # Updates connection state for an HTTP/2 connection and re-registers
652
- # the socket for further reads.
653
- #
654
- # @param state [Hash] updated connection state from the ractor pool
655
- # @return [void]
656
- #
657
- # @rbs (Hash[Symbol, untyped] state) -> void
658
- def update_http2_state(state)
659
- socket = @id_to_socket[state[:id]]
660
- return unless socket
661
-
662
- @socket_to_state[socket] = state
663
- @queue << socket
664
- @selector.wakeup
665
- rescue ClosedQueueError
666
- socket.close
667
- end
668
-
669
625
  # Closes a connection after writing any frames already queued for it.
670
626
  #
671
- # @param id [Integer] unique client identifier
627
+ # @param id [Integer] unique connection identifier
672
628
  # @return [void]
673
629
  #
674
630
  # @rbs (Integer id) -> void
@@ -741,8 +697,7 @@ module Raptor
741
697
  type, id, value, body, attached, finished, state = operation
742
698
  case type
743
699
  when :write
744
- data = value
745
- queue_output(@id_to_io[id], data)
700
+ queue_output(@id_to_io[id], value)
746
701
  connections[id] = true
747
702
  when :close
748
703
  close_after_writes(id)
@@ -763,6 +718,8 @@ module Raptor
763
718
  end
764
719
  when :ready_http1
765
720
  flush_http1_detached_body(id)
721
+ when :cancel_http1
722
+ remove_http1_detached_body(id, :cancelled)
766
723
  when :ready_http2
767
724
  mark_http2_detached_body_ready(id, value)
768
725
  connections[id] = true
@@ -791,7 +748,7 @@ module Raptor
791
748
  # Adds a detached body to an HTTP/1.x connection.
792
749
  #
793
750
  # @param socket [TCPSocket] the client socket
794
- # @param id [Integer] unique client identifier
751
+ # @param id [Integer] unique connection identifier
795
752
  # @param body [DetachedBody] the response body
796
753
  # @param state [Hash] connection state restored once the response finishes
797
754
  # @param finished [Proc] called with the close reason
@@ -819,7 +776,7 @@ module Raptor
819
776
 
820
777
  # Writes buffered HTTP/1.x body chunks without blocking.
821
778
  #
822
- # @param id [Integer] unique client identifier
779
+ # @param id [Integer] unique connection identifier
823
780
  # @return [void]
824
781
  #
825
782
  # @rbs (Integer id) -> void
@@ -852,7 +809,7 @@ module Raptor
852
809
 
853
810
  # Finishes an HTTP/1.x detached response and either reuses or closes its connection.
854
811
  #
855
- # @param id [Integer] unique client identifier
812
+ # @param id [Integer] unique connection identifier
856
813
  # @param io [Http1IO] connection I/O state
857
814
  # @return [void]
858
815
  #
@@ -894,7 +851,7 @@ module Raptor
894
851
 
895
852
  # Cancels an HTTP/1.x detached response and closes its connection.
896
853
  #
897
- # @param id [Integer] unique client identifier
854
+ # @param id [Integer] unique connection identifier
898
855
  # @param reason [Symbol] why the response closed
899
856
  # @return [void]
900
857
  #
@@ -913,7 +870,7 @@ module Raptor
913
870
  # Adds a detached body to an HTTP/2 stream.
914
871
  #
915
872
  # @param id [Integer] unique connection identifier
916
- # @param stream_id [Integer] HTTP/2 stream identifier
873
+ # @param stream_id [Integer] the HTTP/2 stream identifier
917
874
  # @param body [DetachedBody] the response body
918
875
  # @param finished [Proc] called with the close reason
919
876
  # @return [void]
@@ -941,7 +898,7 @@ module Raptor
941
898
  # Marks an HTTP/2 detached stream eligible for fair scheduling.
942
899
  #
943
900
  # @param id [Integer] unique connection identifier
944
- # @param stream_id [Integer] HTTP/2 stream identifier
901
+ # @param stream_id [Integer] the HTTP/2 stream identifier
945
902
  # @return [void]
946
903
  #
947
904
  # @rbs (Integer id, Integer stream_id) -> void
@@ -1017,7 +974,7 @@ module Raptor
1017
974
  # Finishes an HTTP/2 detached stream with DATA or trailing HEADERS.
1018
975
  #
1019
976
  # @param id [Integer] unique connection identifier
1020
- # @param stream_id [Integer] HTTP/2 stream identifier
977
+ # @param stream_id [Integer] the HTTP/2 stream identifier
1021
978
  # @param body [DetachedBody] the response body
1022
979
  # @return [void]
1023
980
  #
@@ -1039,7 +996,7 @@ module Raptor
1039
996
  # Removes an HTTP/2 detached stream and schedules its close callback.
1040
997
  #
1041
998
  # @param id [Integer] unique connection identifier
1042
- # @param stream_id [Integer] HTTP/2 stream identifier
999
+ # @param stream_id [Integer] the HTTP/2 stream identifier
1043
1000
  # @param reason [Symbol] why the stream closed
1044
1001
  # @return [void]
1045
1002
  #
@@ -1087,7 +1044,7 @@ module Raptor
1087
1044
 
1088
1045
  # Closes a connection once its queued frames are written.
1089
1046
  #
1090
- # @param id [Integer] unique client identifier
1047
+ # @param id [Integer] unique connection identifier
1091
1048
  # @return [void]
1092
1049
  #
1093
1050
  # @rbs (Integer id) -> void
@@ -1104,7 +1061,7 @@ module Raptor
1104
1061
 
1105
1062
  # Immediately closes a connection and drops its reactor state.
1106
1063
  #
1107
- # @param id [Integer] unique client identifier
1064
+ # @param id [Integer] unique connection identifier
1108
1065
  # @return [void]
1109
1066
  #
1110
1067
  # @rbs (Integer id) -> void
@@ -1113,9 +1070,9 @@ module Raptor
1113
1070
  cleanup(socket) if socket
1114
1071
  end
1115
1072
 
1116
- # Adds bytes to one connection and writes as much as the socket accepts.
1073
+ # Appends bytes to one connection's pending output.
1117
1074
  #
1118
- # @param io [ConnectionIO] connection I/O state
1075
+ # @param io [ConnectionIO, nil] connection I/O state
1119
1076
  # @param data [String] serialized response bytes
1120
1077
  # @return [void]
1121
1078
  #
@@ -1133,7 +1090,7 @@ module Raptor
1133
1090
  # Drains one connection's queued response bytes without blocking.
1134
1091
  #
1135
1092
  # @param id [Integer] unique connection identifier
1136
- # @param io [ConnectionIO] connection I/O state
1093
+ # @param io [ConnectionIO, nil] connection I/O state
1137
1094
  # @return [void]
1138
1095
  #
1139
1096
  # @rbs (Integer id, ConnectionIO? io) -> void
@@ -1372,6 +1329,7 @@ module Raptor
1372
1329
  flush_output(id, io)
1373
1330
  return unless @id_to_socket.key?(id)
1374
1331
  return if io.wait == :read
1332
+
1375
1333
  flush_http2_detached_bodies(id) unless io.detached.empty?
1376
1334
  end
1377
1335
 
@@ -1379,15 +1337,15 @@ module Raptor
1379
1337
 
1380
1338
  io.reading = false
1381
1339
  update_monitor(id, io)
1382
- to_client = @id_to_timeout.delete(id)
1383
- @timeouts.delete!(to_client) if to_client
1340
+ client = @id_to_timeout.delete(id)
1341
+ @timeouts.delete!(client) if client
1384
1342
  read_and_queue_for_parse(socket, state)
1385
1343
  end
1386
1344
 
1387
1345
  # Handles readiness for an HTTP/1.x detached response.
1388
1346
  #
1389
1347
  # @param monitor [NIO::Monitor] ready selector monitor
1390
- # @param id [Integer] unique client identifier
1348
+ # @param id [Integer] unique connection identifier
1391
1349
  # @return [void]
1392
1350
  #
1393
1351
  # @rbs (NIO::Monitor monitor, Integer id) -> void
@@ -1416,7 +1374,8 @@ module Raptor
1416
1374
  remove_connection(id)
1417
1375
  end
1418
1376
 
1419
- # Handles socket wakeup by deregistering and queuing for processing.
1377
+ # Deregisters a readable socket and resumes its idle keep-alive
1378
+ # connection or queues its bytes for parsing.
1420
1379
  #
1421
1380
  # @param socket [TCPSocket] the socket that became ready
1422
1381
  # @return [void]
@@ -1425,9 +1384,40 @@ module Raptor
1425
1384
  def wakeup!(socket)
1426
1385
  @selector.deregister(socket)
1427
1386
  state = @socket_to_state[socket]
1428
- to_client = @id_to_timeout.delete(state[:id])
1429
- @timeouts.delete!(to_client) if to_client
1430
- read_and_queue_for_parse(socket, state)
1387
+ client = @id_to_timeout.delete(state[:id])
1388
+ @timeouts.delete!(client) if client
1389
+
1390
+ if state[:persisted] && !state.key?(:buffer)
1391
+ resume_keepalive(socket, state)
1392
+ else
1393
+ read_and_queue_for_parse(socket, state)
1394
+ end
1395
+ end
1396
+
1397
+ # Resumes a readable keep-alive connection on an application thread,
1398
+ # avoiding a Ractor round trip for complete requests.
1399
+ #
1400
+ # @param socket [TCPSocket] the readable client socket
1401
+ # @param state [Hash] current connection state
1402
+ # @return [void]
1403
+ #
1404
+ # @rbs (TCPSocket socket, Hash[Symbol, untyped] state) -> void
1405
+ def resume_keepalive(socket, state)
1406
+ remove(state[:id])
1407
+ @thread_pool << proc do
1408
+ @http1.eager_keepalive(
1409
+ socket,
1410
+ state[:id],
1411
+ self,
1412
+ @thread_pool,
1413
+ state[:request_count],
1414
+ state[:remote_addr],
1415
+ state[:url_scheme],
1416
+ resumed: true
1417
+ )
1418
+ end
1419
+ rescue AtomicThreadPool::EnqueuedWorkAfterShutdownError
1420
+ socket.close rescue nil
1431
1421
  end
1432
1422
 
1433
1423
  # Reads data from a socket and either queues it for parsing or
@@ -1435,9 +1425,9 @@ module Raptor
1435
1425
  #
1436
1426
  # @param socket [TCPSocket] the socket to read from and queue
1437
1427
  # @param state [Hash] current connection state
1438
- # @return [Hash, nil] updated state, if successful
1428
+ # @return [void]
1439
1429
  #
1440
- # @rbs (TCPSocket socket, Hash[Symbol, untyped] state) -> Hash[Symbol, untyped]?
1430
+ # @rbs (TCPSocket socket, Hash[Symbol, untyped] state) -> void
1441
1431
  def read_and_queue_for_parse(socket, state)
1442
1432
  data = begin
1443
1433
  socket.read_nonblock(CHUNK_SIZE)
@@ -1445,7 +1435,7 @@ module Raptor
1445
1435
  @queue << socket
1446
1436
  @selector.wakeup
1447
1437
  return
1448
- rescue EOFError
1438
+ rescue EOFError, IOError, SystemCallError, OpenSSL::SSL::SSLError
1449
1439
  cleanup(socket)
1450
1440
  return
1451
1441
  end
@@ -1496,20 +1486,20 @@ module Raptor
1496
1486
  socket.close rescue nil
1497
1487
  end
1498
1488
 
1499
- # Returns true when the request has been fully parsed.
1489
+ # Returns whether the request has been fully parsed.
1500
1490
  #
1501
1491
  # @param state [Hash] connection state
1502
- # @return [Boolean] true if the request is complete
1492
+ # @return [Boolean]
1503
1493
  #
1504
1494
  # @rbs (Hash[Symbol, untyped] state) -> bool
1505
1495
  def complete?(state)
1506
1496
  state[:complete]
1507
1497
  end
1508
1498
 
1509
- # Checks if any data has been received for this connection.
1499
+ # Returns whether any data has been received for this connection.
1510
1500
  #
1511
1501
  # @param state [Hash] connection state
1512
- # @return [Boolean] true if first data has been received
1502
+ # @return [Boolean]
1513
1503
  #
1514
1504
  # @rbs (Hash[Symbol, untyped] state) -> bool
1515
1505
  def first_data_received?(state)