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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/Dockerfile +10 -2
- data/README.md +183 -61
- data/docs/assets/profile_picture.png +0 -0
- data/docs/assets/startup_output.svg +37 -0
- data/docs/raptor-vs-puma.md +104 -424
- data/docs/talk.md +1388 -0
- data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
- data/ext/raptor_http/extconf.rb +2 -0
- data/ext/raptor_http/raptor_http.c +165 -234
- data/ext/raptor_http2/raptor_http2.c +272 -223
- data/ext/raptor_native/raptor_native.c +38 -20
- data/lib/rackup/handler/raptor.rb +14 -12
- data/lib/raptor/binder.rb +12 -12
- data/lib/raptor/cli.rb +19 -18
- data/lib/raptor/cluster.rb +120 -74
- data/lib/raptor/control_server.rb +5 -2
- data/lib/raptor/detached_body.rb +6 -2
- data/lib/raptor/http.rb +7 -6
- data/lib/raptor/http1.rb +281 -234
- data/lib/raptor/http2.rb +356 -276
- data/lib/raptor/plugin.rb +98 -0
- data/lib/raptor/reactor.rb +138 -148
- data/lib/raptor/reuseport_bpf.rb +62 -38
- data/lib/raptor/server.rb +45 -34
- data/lib/raptor/stats.rb +15 -3
- data/lib/raptor/thread_locals.rb +4 -3
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/rackup/handler/raptor.rbs +2 -1
- data/sig/generated/raptor/binder.rbs +9 -9
- data/sig/generated/raptor/cli.rbs +6 -6
- data/sig/generated/raptor/cluster.rbs +18 -5
- data/sig/generated/raptor/control_server.rbs +2 -1
- data/sig/generated/raptor/detached_body.rbs +5 -1
- data/sig/generated/raptor/http.rbs +3 -5
- data/sig/generated/raptor/http1.rbs +119 -102
- data/sig/generated/raptor/http2.rbs +204 -190
- data/sig/generated/raptor/plugin.rbs +52 -0
- data/sig/generated/raptor/reactor.rbs +107 -118
- data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
- data/sig/generated/raptor/server.rbs +26 -26
- data/sig/generated/raptor/stats.rbs +3 -2
- data/sig/generated/raptor/thread_locals.rbs +4 -3
- metadata +6 -2
- data/docs/brisrails-talk.md +0 -2398
data/lib/raptor/cluster.rb
CHANGED
|
@@ -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
|
|
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 @
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
285
|
+
@control_server&.bind
|
|
286
|
+
@worker_count.times { |index| spawn_worker(index) }
|
|
287
|
+
@control_server&.start
|
|
279
288
|
|
|
280
|
-
|
|
281
|
-
|
|
289
|
+
stats_file_thread = if @stats_file
|
|
290
|
+
Thread.new do
|
|
291
|
+
Thread.current.name = "Stats File Writer"
|
|
282
292
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
#
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
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[@
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
888
|
-
bpf_listeners + non_tcp_listeners
|
|
936
|
+
ReuseportBPF.create_worker_listeners(index, @binder.socket_backlog)
|
|
889
937
|
else
|
|
890
|
-
|
|
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:
|
|
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
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
data/lib/raptor/detached_body.rb
CHANGED
|
@@ -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
|
-
#
|
|
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 {
|
|
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/#{
|
|
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 =
|
|
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
|
-
|
|
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 [
|
|
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,
|
|
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
|
|