raptor 0.22.3 → 0.24.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Dockerfile +10 -2
  4. data/README.md +183 -61
  5. data/docs/assets/profile_picture.png +0 -0
  6. data/docs/assets/startup_output.svg +37 -0
  7. data/docs/raptor-vs-puma.md +104 -424
  8. data/docs/talk.md +1388 -0
  9. data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
  10. data/ext/raptor_http/extconf.rb +2 -0
  11. data/ext/raptor_http/raptor_http.c +165 -234
  12. data/ext/raptor_http2/raptor_http2.c +272 -223
  13. data/ext/raptor_native/raptor_native.c +38 -20
  14. data/lib/rackup/handler/raptor.rb +14 -12
  15. data/lib/raptor/binder.rb +12 -12
  16. data/lib/raptor/cli.rb +19 -18
  17. data/lib/raptor/cluster.rb +120 -74
  18. data/lib/raptor/control_server.rb +5 -2
  19. data/lib/raptor/detached_body.rb +6 -2
  20. data/lib/raptor/http.rb +7 -6
  21. data/lib/raptor/http1.rb +281 -234
  22. data/lib/raptor/http2.rb +356 -276
  23. data/lib/raptor/plugin.rb +98 -0
  24. data/lib/raptor/reactor.rb +138 -148
  25. data/lib/raptor/reuseport_bpf.rb +62 -38
  26. data/lib/raptor/server.rb +45 -34
  27. data/lib/raptor/stats.rb +15 -3
  28. data/lib/raptor/thread_locals.rb +4 -3
  29. data/lib/raptor/version.rb +1 -1
  30. data/sig/generated/rackup/handler/raptor.rbs +2 -1
  31. data/sig/generated/raptor/binder.rbs +9 -9
  32. data/sig/generated/raptor/cli.rbs +6 -6
  33. data/sig/generated/raptor/cluster.rbs +18 -5
  34. data/sig/generated/raptor/control_server.rbs +2 -1
  35. data/sig/generated/raptor/detached_body.rbs +5 -1
  36. data/sig/generated/raptor/http.rbs +3 -5
  37. data/sig/generated/raptor/http1.rbs +119 -102
  38. data/sig/generated/raptor/http2.rbs +204 -190
  39. data/sig/generated/raptor/plugin.rbs +52 -0
  40. data/sig/generated/raptor/reactor.rbs +107 -118
  41. data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
  42. data/sig/generated/raptor/server.rbs +26 -26
  43. data/sig/generated/raptor/stats.rbs +3 -2
  44. data/sig/generated/raptor/thread_locals.rbs +4 -3
  45. metadata +6 -2
  46. data/docs/brisrails-talk.md +0 -2398
@@ -9,16 +9,19 @@ require "concurrent/utility/processor_counter"
9
9
  require "rack/builder"
10
10
  require "ractor-pool"
11
11
 
12
- require_relative "log"
13
12
  require_relative "binder"
14
13
  require_relative "control_server"
15
- require_relative "reactor"
16
- require_relative "server"
17
- require_relative "reuseport_bpf"
18
14
  require_relative "http1"
19
15
  require_relative "http2"
16
+ require_relative "log"
17
+ require_relative "plugin"
18
+ require_relative "raptor_native"
19
+ require_relative "reactor"
20
+ require_relative "reuseport_bpf"
21
+ require_relative "server"
20
22
  require_relative "stats"
21
23
  require_relative "systemd"
24
+ require_relative "version"
22
25
 
23
26
  module Raptor
24
27
  # Forks and supervises worker processes. Handles graceful shutdown on
@@ -100,7 +103,7 @@ module Raptor
100
103
  # @rbs @access_log_io: IO?
101
104
  # @rbs @launch_command: String?
102
105
  # @rbs @launch_argv: Array[String]?
103
- # @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
106
+ # @rbs @on_error: (^(Hash[String, untyped]?, Exception) -> void)?
104
107
  # @rbs @binder: Binder
105
108
  # @rbs @server_port: Integer
106
109
  # @rbs @app: untyped
@@ -115,7 +118,7 @@ module Raptor
115
118
  # @rbs @hot_restart_requested: bool
116
119
  # @rbs @refork_thresholds: Array[Integer]
117
120
  # @rbs @refork_requested: bool
118
- # @rbs @refork_threshold_idx: Integer
121
+ # @rbs @refork_threshold_index: Integer
119
122
  # @rbs @seed_pid: Integer?
120
123
  # @rbs @seed_ready: bool
121
124
  # @rbs @seed_vacated_index: Integer?
@@ -124,6 +127,12 @@ module Raptor
124
127
  # @rbs @resp_r: IO?
125
128
  # @rbs @resp_w: IO?
126
129
  # @rbs @bpf_active: bool
130
+ # @rbs @plugins: Array[Plugin]
131
+
132
+ # Returns the cluster configuration available to plugins.
133
+ #
134
+ # @return [Hash{Symbol => untyped}]
135
+ attr_reader :options #: Hash[Symbol, untyped]
127
136
 
128
137
  # Creates a new Cluster with the given options.
129
138
  #
@@ -153,6 +162,7 @@ module Raptor
153
162
  # @option options [Array<Proc>] :before_worker_boot procs called with the worker index before it begins serving
154
163
  # @option options [Array<Proc>] :before_worker_shutdown procs called with the worker index before its graceful shutdown
155
164
  # @option options [Array<Proc>] :before_refork procs called in a worker before it transitions to a seed
165
+ # @option options [Array<String, Symbol>] :plugins plugins to load and start in order
156
166
  # @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
157
167
  # @option options [String, nil] :control_url `unix://` URL serving cluster stats, or nil to disable
158
168
  # @option options [String, nil] :pid_file path to write the master PID to, or nil to disable
@@ -166,6 +176,8 @@ module Raptor
166
176
  #
167
177
  # @rbs (Hash[Symbol, untyped] options) -> void
168
178
  def initialize(options)
179
+ @options = options = options.dup
180
+ @plugins = []
169
181
  @drain_accept_queue = options[:drain_accept_queue]
170
182
  @worker_count = options[:workers]
171
183
  @http1_ractor_count = options[:http1][:ractors] || self.class.default_http1_ractor_count(@worker_count)
@@ -183,10 +195,10 @@ module Raptor
183
195
  @worker_timeout = options[:worker_timeout]
184
196
  @worker_drain_timeout = options[:worker_drain_timeout]
185
197
  @worker_shutdown_timeout = options[:worker_shutdown_timeout]
186
- @before_fork = Array(options[:before_fork])
187
- @before_worker_boot = Array(options[:before_worker_boot])
188
- @before_worker_shutdown = Array(options[:before_worker_shutdown])
189
- @before_refork = Array(options[:before_refork])
198
+ @before_fork = options[:before_fork] = Array(options[:before_fork]).dup
199
+ @before_worker_boot = options[:before_worker_boot] = Array(options[:before_worker_boot]).dup
200
+ @before_worker_shutdown = options[:before_worker_shutdown] = Array(options[:before_worker_shutdown]).dup
201
+ @before_refork = options[:before_refork] = Array(options[:before_refork]).dup
190
202
  @stats_file = options[:stats_file]
191
203
  @control_server = ControlServer.new(options[:control_url]) { stats_hash } if options[:control_url]
192
204
  @pid_file = options[:pid_file]
@@ -200,7 +212,7 @@ module Raptor
200
212
 
201
213
  Dir.chdir(options[:chdir]) if options[:chdir]
202
214
 
203
- inherited_fds = if raw = ENV.delete(INHERITED_FDS_ENV)
215
+ inherited_fds = if (raw = ENV.delete(INHERITED_FDS_ENV))
204
216
  JSON.parse(raw)
205
217
  elsif (systemd_fds = Systemd.listen_fds).any?
206
218
  Systemd.clear_listen_env
@@ -225,7 +237,7 @@ module Raptor
225
237
 
226
238
  @refork_thresholds = normalize_refork_thresholds(options[:refork_after])
227
239
  @refork_requested = false
228
- @refork_threshold_idx = 0
240
+ @refork_threshold_index = 0
229
241
  @seed_pid = nil
230
242
  @seed_ready = false
231
243
  @seed_vacated_index = nil
@@ -261,42 +273,63 @@ module Raptor
261
273
 
262
274
  File.open(@pid_file, File::CREAT | File::EXCL | File::WRONLY) { |file| file.write(Process.pid.to_s) } if @pid_file
263
275
 
264
- @bpf_active = ReuseportBPF.setup(@worker_count)
265
-
266
- @control_server&.bind
267
- @worker_count.times { |index| spawn_worker(index) }
268
- @control_server&.start
269
-
270
- stats_file_thread = if @stats_file
271
- Thread.new do
272
- Thread.current.name = "Stats File Writer"
276
+ @bpf_active = ReuseportBPF.setup(@worker_count, @binder.listeners.grep(TCPServer))
273
277
 
274
- write_stats_file_loop
278
+ begin
279
+ Array(@options[:plugins]).each do |name|
280
+ plugin = Plugin.load(name)
281
+ @plugins << plugin
282
+ plugin.start(self)
275
283
  end
276
- end
277
284
 
278
- Systemd.notify("READY=1\nMAINPID=#{Process.pid}")
285
+ @control_server&.bind
286
+ @worker_count.times { |index| spawn_worker(index) }
287
+ @control_server&.start
279
288
 
280
- until @shutdown
281
- break if reap_workers == :no_children
289
+ stats_file_thread = if @stats_file
290
+ Thread.new do
291
+ Thread.current.name = "Stats File Writer"
282
292
 
283
- perform_hot_restart if @hot_restart_requested
284
- poll_seed_ready if @seed_pid && !@seed_ready
285
- perform_phased_restart if @phased_restart_requested && !@phased_restarting
286
- check_refork_trigger if @refork_thresholds.any? && !@phased_restarting
287
- timeout_hung_workers
293
+ write_stats_file_loop
294
+ end
295
+ end
288
296
 
289
- sleep 0.1
290
- end
297
+ ready = false
298
+
299
+ until @shutdown
300
+ break if reap_workers == :no_children
301
+
302
+ perform_hot_restart if @hot_restart_requested
303
+ poll_seed_ready if @seed_pid && !@seed_ready
304
+ perform_phased_restart if @phased_restart_requested && !@phased_restarting
305
+ check_refork_trigger if @refork_thresholds.any? && !@phased_restarting
306
+ timeout_hung_workers
307
+
308
+ unless ready || @shutdown
309
+ stats = @stats.all
310
+ if @workers.size == @worker_count && @workers.all? { |index, pid|
311
+ stat = stats[index]
312
+ stat[:pid] == pid && stat[:phase] == @phase && stat[:booted] && !@timed_out.include?(pid)
313
+ }
314
+ Systemd.notify("READY=1\nMAINPID=#{Process.pid}")
315
+ ready = true
316
+ end
317
+ end
291
318
 
292
- Systemd.notify("STOPPING=1")
293
- stop_workers
294
- stats_file_thread&.join
295
- @control_server&.shutdown
296
- File.delete(@stats_file) rescue nil if @stats_file
297
- File.delete(@pid_file) rescue nil if @pid_file
298
- @stats.unmap
299
- @binder.close
319
+ sleep 0.1
320
+ end
321
+ ensure
322
+ @shutdown = true
323
+ Systemd.notify("STOPPING=1")
324
+ stop_workers
325
+ stats_file_thread&.join
326
+ stop_plugins
327
+ @control_server&.shutdown
328
+ File.delete(@stats_file) rescue nil if @stats_file
329
+ File.delete(@pid_file) rescue nil if @pid_file
330
+ @stats.unmap
331
+ @binder.close
332
+ end
300
333
  end
301
334
 
302
335
  # Returns stats for all worker processes.
@@ -310,7 +343,7 @@ module Raptor
310
343
  @stats.all
311
344
  end
312
345
 
313
- # Returns cluster statistics for the control server.
346
+ # Returns cluster statistics for plugins and the control server.
314
347
  # For an adaptive pool, `max_threads` is the number of threads currently
315
348
  # running so utilization reflects the capacity available at that moment.
316
349
  #
@@ -356,9 +389,23 @@ module Raptor
356
389
 
357
390
  private
358
391
 
359
- # @rbs (Float timestamp) -> String
360
- def timestamp(timestamp)
361
- Time.at(timestamp).utc.iso8601(6)
392
+ # Stops plugins in reverse startup order.
393
+ #
394
+ # @rbs () -> void
395
+ def stop_plugins
396
+ until @plugins.empty?
397
+ plugin = @plugins.pop
398
+ begin
399
+ plugin.shutdown
400
+ rescue StandardError => error
401
+ Log.rescued_error(error)
402
+ end
403
+ end
404
+ end
405
+
406
+ # @rbs (Numeric time) -> String
407
+ def timestamp(time)
408
+ Time.at(time).utc.iso8601(6)
362
409
  end
363
410
 
364
411
  # Returns the inherited-FDs hash for a systemd socket-activation handoff,
@@ -449,7 +496,7 @@ module Raptor
449
496
  def pid_alive?(pid)
450
497
  Process.kill(0, pid)
451
498
  true
452
- rescue Errno::ESRCH, Errno::ECHILD
499
+ rescue Errno::ESRCH
453
500
  false
454
501
  rescue Errno::EPERM
455
502
  true
@@ -513,14 +560,14 @@ module Raptor
513
560
  return unless candidate
514
561
 
515
562
  candidate_index, candidate_requests = candidate
516
- threshold = @refork_thresholds[@refork_threshold_idx]
563
+ threshold = @refork_thresholds[@refork_threshold_index]
517
564
 
518
565
  if @refork_requested
519
566
  @refork_requested = false
520
567
  elsif !threshold || candidate_requests < threshold
521
568
  return
522
569
  else
523
- @refork_threshold_idx += 1
570
+ @refork_threshold_index += 1
524
571
  end
525
572
 
526
573
  promote_worker_to_seed(candidate_index)
@@ -561,7 +608,6 @@ module Raptor
561
608
 
562
609
  retire_current_seed
563
610
  Log.info "Promoting worker #{index} to seed for phased refork"
564
- ReuseportBPF.mark_unavailable(index) if @bpf_active
565
611
  Process.kill("URG", pid) rescue nil
566
612
  @workers.delete(index)
567
613
  @seed_pid = pid
@@ -766,6 +812,7 @@ module Raptor
766
812
  Systemd.notify("RELOADING=1\nMONOTONIC_USEC=#{monotonic_usec}")
767
813
  @shutdown = true
768
814
  stop_workers
815
+ stop_plugins
769
816
  @binder.clear_close_on_exec
770
817
  ENV[INHERITED_FDS_ENV] = JSON.generate(@binder.inheritable_fds)
771
818
  @control_server&.shutdown
@@ -800,7 +847,7 @@ module Raptor
800
847
  trap("USR2", "IGNORE")
801
848
  trap("URG") { promote_to_seed = true } if @fork_r
802
849
 
803
- Raptor::CPU.pin(index) if @cpu_affinity && Raptor::CPU.count >= @worker_count
850
+ CPU.pin(index) if @cpu_affinity && CPU.count >= @worker_count
804
851
 
805
852
  started_at = Process.clock_gettime(Process::CLOCK_REALTIME)
806
853
  request_count = 0
@@ -813,11 +860,13 @@ module Raptor
813
860
  backlog: 0,
814
861
  busy_threads: 0,
815
862
  thread_capacity: @thread_count,
816
- started_at:,
863
+ started_at: started_at,
817
864
  last_checkin: started_at,
818
865
  booted: false
819
866
  )
820
867
 
868
+ @before_worker_boot.each { |hook| hook.parameters.empty? ? hook.call : hook.call(index) }
869
+
821
870
  reactor = nil
822
871
 
823
872
  counting_app = ->(env) {
@@ -872,6 +921,7 @@ module Raptor
872
921
  end
873
922
 
874
923
  reactor = Reactor.new(
924
+ http1,
875
925
  http1_ractor_pool,
876
926
  http2_ractor_pool,
877
927
  thread_pool,
@@ -881,30 +931,25 @@ module Raptor
881
931
  )
882
932
  reactor_thread = reactor.run
883
933
 
884
- worker_listeners = if @bpf_active
885
- bpf_listeners = ReuseportBPF.create_worker_listeners(@binder.bind_uris, index, @binder.socket_backlog)
934
+ bpf_listeners = if @bpf_active
886
935
  ReuseportBPF.enable_load_reporting(index)
887
- non_tcp_listeners = @binder.listeners.reject { |listener| listener.is_a?(TCPServer) }
888
- bpf_listeners + non_tcp_listeners
936
+ ReuseportBPF.create_worker_listeners(index, @binder.socket_backlog)
889
937
  else
890
- @binder.listeners
938
+ []
891
939
  end
892
940
 
893
941
  server = Server.new(
894
- @binder,
895
942
  reactor,
896
943
  thread_pool,
897
944
  http1,
898
945
  http2,
899
946
  connection_options: @connection_options,
900
947
  drain_accept_queue: @drain_accept_queue,
901
- listeners: worker_listeners,
948
+ listeners: bpf_listeners + @binder.listeners,
902
949
  worker_index: (index if @bpf_active)
903
950
  )
904
951
  server_thread = server.run
905
952
 
906
- @before_worker_boot.each { |hook| hook.parameters.empty? ? hook.call : hook.call(index) }
907
-
908
953
  Log.info "Worker #{index} booted"
909
954
 
910
955
  stats_thread = Thread.new do
@@ -919,7 +964,7 @@ module Raptor
919
964
  backlog: reactor.backlog,
920
965
  busy_threads: thread_pool.active_count,
921
966
  thread_capacity: thread_pool.size,
922
- started_at:,
967
+ started_at: started_at,
923
968
  last_checkin: Process.clock_gettime(Process::CLOCK_REALTIME),
924
969
  booted: true
925
970
  )
@@ -944,6 +989,8 @@ module Raptor
944
989
  end
945
990
 
946
991
  server_thread.join
992
+ # A seed never accepts again, and the workers it forks bind their own.
993
+ bpf_listeners.each(&:close) if promote_to_seed
947
994
  http1.shutdown
948
995
  http2.shutdown(reactor)
949
996
  reactor.drain_detached_bodies(@worker_drain_timeout)
@@ -972,11 +1019,6 @@ module Raptor
972
1019
  trap("TERM") { seed_shutdown = true }
973
1020
  trap("URG", "IGNORE")
974
1021
 
975
- child_pids = []
976
- trap("CHLD") do
977
- child_pids.reject! { Process.wait(_1, Process::WNOHANG) rescue true }
978
- end
979
-
980
1022
  @resp_w.write([0].pack("L"))
981
1023
 
982
1024
  until seed_shutdown
@@ -986,9 +1028,15 @@ module Raptor
986
1028
  break unless bytes.is_a?(String) && bytes.bytesize == 8
987
1029
 
988
1030
  slot_index, child_phase = bytes.unpack("LL")
989
- pid = fork { run_worker(slot_index, child_phase) }
990
- child_pids << pid
991
- @resp_w.write([pid].pack("L")) rescue nil
1031
+ # Forks through a short-lived intermediate process so the worker
1032
+ # reparents to the subreaper master, which supervises and reaps it
1033
+ # like a worker it forked itself.
1034
+ intermediate_pid = fork do
1035
+ pid = fork { run_worker(slot_index, child_phase) }
1036
+ @resp_w.write([pid].pack("L")) rescue nil
1037
+ exit!
1038
+ end
1039
+ Process.wait(intermediate_pid)
992
1040
  end
993
1041
  rescue Errno::EPIPE, IOError
994
1042
  end
@@ -1002,16 +1050,16 @@ module Raptor
1002
1050
  #
1003
1051
  # @rbs (AtomicThreadPool thread_pool) -> void
1004
1052
  def drain_thread_pool(thread_pool)
1005
- drain = Thread.new do
1053
+ drain_thread = Thread.new do
1006
1054
  Thread.current.name = "Thread Pool Drain"
1007
1055
 
1008
1056
  thread_pool.shutdown
1009
1057
  end
1010
- return if drain.join(@worker_drain_timeout)
1058
+ return if drain_thread.join(@worker_drain_timeout)
1011
1059
 
1012
1060
  Log.warn "Force-killing in-flight app threads after #{@worker_drain_timeout}s drain timeout"
1013
1061
  thread_pool.instance_variable_get(:@threads).each(&:kill)
1014
- drain.join
1062
+ drain_thread.join
1015
1063
  end
1016
1064
 
1017
1065
  # Returns a human-readable description of how a process exited.
@@ -1036,8 +1084,6 @@ module Raptor
1036
1084
  #
1037
1085
  # @rbs () -> void
1038
1086
  def shutdown
1039
- return if @shutdown
1040
-
1041
1087
  @shutdown = true
1042
1088
  end
1043
1089
 
@@ -19,9 +19,10 @@ module Raptor
19
19
  # @rbs @client: Atom
20
20
  # @rbs @thread: Thread?
21
21
 
22
- # Creates a control server for `url` without binding it.
22
+ # Creates a new ControlServer for `url` without binding it.
23
23
  #
24
24
  # @param url [String] `unix://` URL to listen on
25
+ # @yield for each stats request
25
26
  # @yieldreturn [Hash] cluster statistics
26
27
  # @return [void]
27
28
  # @raise [ArgumentError] if the URL is not a Unix socket
@@ -70,6 +71,8 @@ module Raptor
70
71
  #
71
72
  # @rbs () -> void
72
73
  def shutdown
74
+ return unless @server
75
+
73
76
  client = nil
74
77
  @client.swap do |current|
75
78
  client = current
@@ -131,7 +134,7 @@ module Raptor
131
134
  # @rbs (UNIXSocket client) -> void
132
135
  def handle(client)
133
136
  request_line = client.gets
134
- while line = client.gets
137
+ while (line = client.gets)
135
138
  break if line == "\r\n"
136
139
  end
137
140
 
@@ -66,6 +66,7 @@ module Raptor
66
66
  #
67
67
  # @param max_buffer_size [Integer] maximum body bytes waiting to be written
68
68
  # @return [void]
69
+ # @raise [ArgumentError] if `max_buffer_size` is not positive
69
70
  #
70
71
  # @rbs (?max_buffer_size: Integer) -> void
71
72
  def initialize(max_buffer_size: DEFAULT_MAX_BUFFER_SIZE)
@@ -84,6 +85,7 @@ module Raptor
84
85
  # Registers a callback that runs on the request thread once the server
85
86
  # accepts the response stream.
86
87
  #
88
+ # @yield once the response stream opens
87
89
  # @yieldparam stream [DetachedBody] the opened response stream
88
90
  # @return [DetachedBody]
89
91
  #
@@ -95,6 +97,7 @@ module Raptor
95
97
 
96
98
  # Registers a callback for when the response stream closes.
97
99
  #
100
+ # @yield once the response stream closes
98
101
  # @yieldparam reason [Symbol] why the stream closed
99
102
  # @return [DetachedBody]
100
103
  #
@@ -108,6 +111,7 @@ module Raptor
108
111
  #
109
112
  # @param chunk [String] response body bytes
110
113
  # @return [Symbol] `:accepted`, `:full`, or `:closed`
114
+ # @raise [TypeError] if the chunk is not a String
111
115
  #
112
116
  # @rbs (String chunk) -> Symbol
113
117
  def try_write(chunk)
@@ -150,7 +154,7 @@ module Raptor
150
154
  result
151
155
  end
152
156
 
153
- # Finishes the body after all accepted bytes have been written.
157
+ # Marks the body complete; the stream finishes once buffered bytes are written.
154
158
  #
155
159
  # @param trailers [Hash] trailing response headers
156
160
  # @return [void]
@@ -158,7 +162,7 @@ module Raptor
158
162
  # @rbs (?trailers: Hash[String, String | Array[String]]) -> void
159
163
  def close(trailers: {})
160
164
  trailers = trailers.to_h do |name, value|
161
- value = value.is_a?(Array) ? value.map { _1.dup.freeze }.freeze : value.dup.freeze
165
+ value = value.is_a?(Array) ? value.map { |item| item.dup.freeze }.freeze : value.dup.freeze
162
166
  [name.dup.freeze, value]
163
167
  end.freeze
164
168
  wake = false
data/lib/raptor/http.rb CHANGED
@@ -16,11 +16,10 @@ module Raptor
16
16
 
17
17
  CONTENT_LENGTH = "CONTENT_LENGTH"
18
18
  CONTENT_TYPE = "CONTENT_TYPE"
19
- HTTP_VERSION = "HTTP_VERSION"
20
19
  REMOTE_ADDR = "REMOTE_ADDR"
21
20
  REQUEST_URI = "REQUEST_URI"
22
21
  SERVER_SOFTWARE = "SERVER_SOFTWARE"
23
- SERVER_SOFTWARE_VALUE = "Raptor/#{Raptor::VERSION}".freeze
22
+ SERVER_SOFTWARE_VALUE = "Raptor/#{VERSION}".freeze
24
23
 
25
24
  class WriteError < StandardError
26
25
  # @rbs () -> String
@@ -56,6 +55,7 @@ module Raptor
56
55
  bytes += socket.write_nonblock(bytes.zero? ? string : string.byteslice(bytes..-1))
57
56
  rescue IO::WaitWritable
58
57
  raise WriteError unless socket.wait_writable(timeout)
58
+
59
59
  retry
60
60
  rescue IOError
61
61
  raise WriteError
@@ -79,9 +79,10 @@ module Raptor
79
79
  return if total.zero?
80
80
 
81
81
  begin
82
- written = Raptor::VectorIO.writev_nonblock(socket, strings)
82
+ written = VectorIO.writev_nonblock(socket, strings)
83
83
  rescue IO::WaitWritable
84
84
  raise WriteError unless socket.wait_writable(timeout)
85
+
85
86
  retry
86
87
  rescue IOError
87
88
  raise WriteError
@@ -113,7 +114,7 @@ module Raptor
113
114
  # @rbs (Hash[String, untyped] env_template, Integer? max_body_size) -> ^(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
114
115
  def self.parser_worker(env_template, max_body_size)
115
116
  proc do |data|
116
- Raptor::Http1.parse(data, env_template, max_body_size)
117
+ Http1.parse(data, env_template, max_body_size)
117
118
  end
118
119
  end
119
120
 
@@ -121,12 +122,12 @@ module Raptor
121
122
  # registration order, rescuing any that raise.
122
123
  #
123
124
  # @param env [Hash, nil] the Rack environment
124
- # @param status [Integer, nil] the response status code
125
+ # @param status [Object, nil] the status returned by the app, if any
125
126
  # @param headers [Hash, nil] the response headers
126
127
  # @param error [Exception, nil] any error raised during processing, or nil on success
127
128
  # @return [void]
128
129
  #
129
- # @rbs (Hash[String, untyped]? env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
130
+ # @rbs (Hash[String, untyped]? env, untyped status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
130
131
  def self.call_response_finished(env, status, headers, error)
131
132
  return unless env && env[Rack::RACK_RESPONSE_FINISHED].is_a?(Array)
132
133