pitchfork 0.13.0 → 0.14.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/.github/workflows/ci.yml +6 -2
- data/.ruby-version +1 -0
- data/CHANGELOG.md +6 -0
- data/Dockerfile +1 -1
- data/Gemfile.lock +2 -4
- data/docs/CONFIGURATION.md +38 -0
- data/docs/MIGRATING_FROM_UNICORN.md +34 -0
- data/docs/WHY_MIGRATE.md +5 -0
- data/examples/pitchfork.conf.service.rb +27 -0
- data/exe/pitchfork +4 -4
- data/ext/pitchfork_http/extconf.rb +2 -0
- data/ext/pitchfork_http/memory_page.c +223 -0
- data/ext/pitchfork_http/pitchfork_http.c +213 -211
- data/ext/pitchfork_http/pitchfork_http.rl +3 -1
- data/lib/pitchfork/children.rb +20 -15
- data/lib/pitchfork/configurator.rb +12 -0
- data/lib/pitchfork/http_parser.rb +11 -66
- data/lib/pitchfork/http_response.rb +1 -1
- data/lib/pitchfork/http_server.rb +175 -59
- data/lib/pitchfork/message.rb +10 -6
- data/lib/pitchfork/shared_memory.rb +16 -14
- data/lib/pitchfork/socket_helper.rb +1 -1
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +43 -15
- data/lib/pitchfork.rb +0 -20
- data/pitchfork.gemspec +0 -1
- metadata +7 -18
- data/lib/pitchfork/app/old_rails/static.rb +0 -60
@@ -16,6 +16,7 @@
|
|
16
16
|
#include "child_subreaper.h"
|
17
17
|
|
18
18
|
void init_pitchfork_httpdate(void);
|
19
|
+
void init_pitchfork_memory_page(VALUE);
|
19
20
|
|
20
21
|
#define UH_FL_CHUNKED 0x1
|
21
22
|
#define UH_FL_HASBODY 0x2
|
@@ -960,7 +961,7 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
960
961
|
assert(!NIL_P(var) && "missed global field"); \
|
961
962
|
} while (0)
|
962
963
|
|
963
|
-
void Init_pitchfork_http(void)
|
964
|
+
RUBY_FUNC_EXPORTED void Init_pitchfork_http(void)
|
964
965
|
{
|
965
966
|
VALUE mPitchfork;
|
966
967
|
|
@@ -1024,5 +1025,6 @@ void Init_pitchfork_http(void)
|
|
1024
1025
|
|
1025
1026
|
init_epollexclusive(mPitchfork);
|
1026
1027
|
init_child_subreaper(mPitchfork);
|
1028
|
+
init_pitchfork_memory_page(mPitchfork);
|
1027
1029
|
}
|
1028
1030
|
#undef SET_GLOBAL
|
data/lib/pitchfork/children.rb
CHANGED
@@ -4,29 +4,32 @@
|
|
4
4
|
module Pitchfork
|
5
5
|
# This class keep tracks of the state of all the master children.
|
6
6
|
class Children
|
7
|
-
attr_reader :mold
|
7
|
+
attr_reader :mold, :service
|
8
8
|
attr_accessor :last_generation
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@last_generation = 0
|
12
|
-
@children = {} # All children, including molds, indexed by PID.
|
12
|
+
@children = {} # All children, including molds and services, indexed by PID.
|
13
13
|
@workers = {} # Workers indexed by their `nr`.
|
14
14
|
@molds = {} # Molds, index by PID.
|
15
15
|
@mold = nil # The latest mold, if any.
|
16
|
+
@service = nil
|
16
17
|
@pending_workers = {} # Pending workers indexed by their `nr`.
|
17
18
|
@pending_molds = {} # Worker promoted to mold, not yet acknowledged
|
18
19
|
end
|
19
20
|
|
20
|
-
def refresh
|
21
|
-
@workers.each_value(&:refresh)
|
22
|
-
@molds.each_value(&:refresh)
|
23
|
-
end
|
24
|
-
|
25
21
|
def register(child)
|
26
22
|
# Children always start as workers, never molds, so we know they have a `#nr`.
|
23
|
+
unless child.nr
|
24
|
+
raise "[BUG] Trying to register a child without an `nr`: #{child.inspect}"
|
25
|
+
end
|
27
26
|
@pending_workers[child.nr] = @workers[child.nr] = child
|
28
27
|
end
|
29
28
|
|
29
|
+
def register_service(service)
|
30
|
+
@service = service
|
31
|
+
end
|
32
|
+
|
30
33
|
def register_mold(mold)
|
31
34
|
@pending_molds[mold.pid] = mold
|
32
35
|
@children[mold.pid] = mold
|
@@ -44,6 +47,11 @@ module Pitchfork
|
|
44
47
|
@pending_molds[mold.pid] = mold
|
45
48
|
@children[mold.pid] = mold
|
46
49
|
return mold
|
50
|
+
when Message::ServiceSpawned
|
51
|
+
service = @service
|
52
|
+
service.update(message)
|
53
|
+
@children[service.pid] = service
|
54
|
+
return service
|
47
55
|
end
|
48
56
|
|
49
57
|
child = @children[message.pid] || (message.nr && @workers[message.nr])
|
@@ -78,12 +86,17 @@ module Pitchfork
|
|
78
86
|
@pending_molds.delete(child.pid)
|
79
87
|
@molds.delete(child.pid)
|
80
88
|
@workers.delete(child.nr)
|
89
|
+
|
81
90
|
if @mold == child
|
82
91
|
@pending_workers.reject! do |nr, worker|
|
83
92
|
worker.generation == @mold.generation
|
84
93
|
end
|
85
94
|
@mold = nil
|
86
95
|
end
|
96
|
+
|
97
|
+
if @service == child
|
98
|
+
@service = nil
|
99
|
+
end
|
87
100
|
end
|
88
101
|
child
|
89
102
|
end
|
@@ -154,13 +167,5 @@ module Pitchfork
|
|
154
167
|
def workers_count
|
155
168
|
@workers.size
|
156
169
|
end
|
157
|
-
|
158
|
-
def total_pss
|
159
|
-
total_pss = MemInfo.new(Process.pid).pss
|
160
|
-
@children.each do |_, worker|
|
161
|
-
total_pss += worker.meminfo.pss if worker.meminfo
|
162
|
-
end
|
163
|
-
total_pss
|
164
|
-
end
|
165
170
|
end
|
166
171
|
end
|
@@ -52,6 +52,8 @@ module Pitchfork
|
|
52
52
|
"repead unknown process (#{status.inspect})"
|
53
53
|
elsif worker.mold?
|
54
54
|
"mold pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
55
|
+
elsif worker.service?
|
56
|
+
"service pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
55
57
|
else
|
56
58
|
"worker=#{worker.nr rescue 'unknown'} pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
57
59
|
end
|
@@ -75,6 +77,8 @@ module Pitchfork
|
|
75
77
|
:check_client_connection => false,
|
76
78
|
:rewindable_input => true,
|
77
79
|
:client_body_buffer_size => Pitchfork::Const::MAX_BODY,
|
80
|
+
:before_service_worker_ready => nil,
|
81
|
+
:before_service_worker_exit => nil,
|
78
82
|
}
|
79
83
|
#:startdoc:
|
80
84
|
|
@@ -176,6 +180,14 @@ module Pitchfork
|
|
176
180
|
set_hook(:after_request_complete, block_given? ? block : args[0], 3)
|
177
181
|
end
|
178
182
|
|
183
|
+
def before_service_worker_ready(&block)
|
184
|
+
set_hook(:before_service_worker_ready, block, 2)
|
185
|
+
end
|
186
|
+
|
187
|
+
def before_service_worker_exit(&block)
|
188
|
+
set_hook(:before_service_worker_exit, block, 2)
|
189
|
+
end
|
190
|
+
|
179
191
|
def timeout(seconds, cleanup: 2)
|
180
192
|
soft_timeout = set_int(:soft_timeout, seconds, 3)
|
181
193
|
cleanup_timeout = set_int(:cleanup_timeout, cleanup, 2)
|
@@ -27,7 +27,6 @@ module Pitchfork
|
|
27
27
|
EMPTY_ARRAY = [].freeze
|
28
28
|
@@input_class = Pitchfork::TeeInput
|
29
29
|
@@check_client_connection = false
|
30
|
-
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
|
31
30
|
|
32
31
|
def self.input_class
|
33
32
|
@@input_class
|
@@ -108,80 +107,26 @@ module Pitchfork
|
|
108
107
|
env.include?('rack.hijack_io')
|
109
108
|
end
|
110
109
|
|
111
|
-
if
|
112
|
-
TCPI = Raindrops::TCP_Info.allocate
|
113
|
-
|
110
|
+
if Socket.const_defined?(:TCP_INFO) # Linux
|
114
111
|
def check_client_connection(socket) # :nodoc:
|
115
112
|
if TCPSocket === socket
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
write_http_header(socket)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
if Raindrops.const_defined?(:TCP)
|
125
|
-
# raindrops 0.18.0+ supports FreeBSD + Linux using the same names
|
126
|
-
# Evaluate these hash lookups at load time so we can
|
127
|
-
# generate an opt_case_dispatch instruction
|
128
|
-
eval <<-EOS
|
129
|
-
def closed_state?(state) # :nodoc:
|
130
|
-
case state
|
131
|
-
when #{Raindrops::TCP[:ESTABLISHED]}
|
132
|
-
false
|
133
|
-
when #{Raindrops::TCP.values_at(
|
134
|
-
:CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
|
135
|
-
true
|
136
|
-
else
|
137
|
-
false
|
138
|
-
end
|
139
|
-
end
|
140
|
-
EOS
|
141
|
-
else
|
142
|
-
# raindrops before 0.18 only supported TCP_INFO under Linux
|
143
|
-
def closed_state?(state) # :nodoc:
|
144
|
-
case state
|
145
|
-
when 1 # ESTABLISHED
|
146
|
-
false
|
147
|
-
when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
|
148
|
-
true
|
149
|
-
else
|
150
|
-
false
|
113
|
+
begin
|
114
|
+
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
115
|
+
rescue IOError, SystemCallError
|
116
|
+
return write_http_header(socket)
|
151
117
|
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
else
|
155
118
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
def check_client_connection(socket) # :nodoc:
|
160
|
-
if TCPSocket === socket && @@tcpi_inspect_ok
|
161
|
-
opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
|
162
|
-
if opt =~ /\bstate=(\S+)/
|
163
|
-
raise Errno::EPIPE, "client closed connection".freeze,
|
164
|
-
EMPTY_ARRAY if closed_state_str?($1)
|
165
|
-
else
|
166
|
-
@@tcpi_inspect_ok = false
|
167
|
-
write_http_header(socket)
|
119
|
+
case tcp_info.data.unpack1("C")
|
120
|
+
when 6, 7, 8, 9, 11 # TIME_WAIT, CLOSE, CLOSE_WAIT, LAST_ACK, CLOSING
|
121
|
+
raise Errno::EPIPE, "client closed connection", EMPTY_ARRAY
|
168
122
|
end
|
169
|
-
opt.clear
|
170
123
|
else
|
171
124
|
write_http_header(socket)
|
172
125
|
end
|
173
126
|
end
|
174
|
-
|
175
|
-
def
|
176
|
-
|
177
|
-
when 'ESTABLISHED'
|
178
|
-
false
|
179
|
-
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
|
180
|
-
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
|
181
|
-
true
|
182
|
-
else
|
183
|
-
false
|
184
|
-
end
|
127
|
+
else
|
128
|
+
def check_client_connection(socket) # :nodoc:
|
129
|
+
write_http_header(socket)
|
185
130
|
end
|
186
131
|
end
|
187
132
|
|
@@ -77,7 +77,7 @@ module Pitchfork
|
|
77
77
|
|
78
78
|
# :stopdoc:
|
79
79
|
attr_accessor :app, :timeout, :timeout_signal, :soft_timeout, :cleanup_timeout, :spawn_timeout, :worker_processes,
|
80
|
-
:before_fork, :after_worker_fork, :after_mold_fork,
|
80
|
+
:before_fork, :after_worker_fork, :after_mold_fork, :before_service_worker_ready, :before_service_worker_exit,
|
81
81
|
:listener_opts, :children,
|
82
82
|
:orig_app, :config, :ready_pipe,
|
83
83
|
:default_middleware, :early_hints
|
@@ -95,32 +95,6 @@ module Pitchfork
|
|
95
95
|
|
96
96
|
NOOP = '.'
|
97
97
|
|
98
|
-
# :startdoc:
|
99
|
-
# This Hash is considered a stable interface and changing its contents
|
100
|
-
# will allow you to switch between different installations of Pitchfork
|
101
|
-
# or even different installations of the same applications without
|
102
|
-
# downtime. Keys of this constant Hash are described as follows:
|
103
|
-
#
|
104
|
-
# * 0 - the path to the pitchfork executable
|
105
|
-
# * :argv - a deep copy of the ARGV array the executable originally saw
|
106
|
-
# * :cwd - the working directory of the application, this is where
|
107
|
-
# you originally started Pitchfork.
|
108
|
-
# TODO: Can we get rid of this?
|
109
|
-
START_CTX = {
|
110
|
-
:argv => ARGV.map(&:dup),
|
111
|
-
0 => $0.dup,
|
112
|
-
}
|
113
|
-
# We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
|
114
|
-
# and like systems
|
115
|
-
START_CTX[:cwd] = begin
|
116
|
-
a = File.stat(pwd = ENV['PWD'])
|
117
|
-
b = File.stat(Dir.pwd)
|
118
|
-
a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
|
119
|
-
rescue
|
120
|
-
Dir.pwd
|
121
|
-
end
|
122
|
-
# :stopdoc:
|
123
|
-
|
124
98
|
# Creates a working server on host:port (strange things happen if
|
125
99
|
# port isn't a Number). Use HttpServer::run to start the server and
|
126
100
|
# HttpServer.run.join to join the thread that's processing
|
@@ -140,7 +114,7 @@ module Pitchfork
|
|
140
114
|
self.config = Pitchfork::Configurator.new(options)
|
141
115
|
self.listener_opts = {}
|
142
116
|
|
143
|
-
proc_name role: 'monitor', status:
|
117
|
+
proc_name role: 'monitor', status: ARGV.join(' ')
|
144
118
|
|
145
119
|
# We use @control_socket differently in the master and worker processes:
|
146
120
|
#
|
@@ -173,7 +147,7 @@ module Pitchfork
|
|
173
147
|
:QUIT, :INT, :TERM, :USR2, :TTIN, :TTOU ]
|
174
148
|
|
175
149
|
Info.workers_count = worker_processes
|
176
|
-
SharedMemory.
|
150
|
+
SharedMemory.preallocate_pages(worker_processes)
|
177
151
|
end
|
178
152
|
|
179
153
|
# Runs the thing. Returns self so you can run join on it
|
@@ -296,7 +270,7 @@ module Pitchfork
|
|
296
270
|
def join
|
297
271
|
@respawn = true
|
298
272
|
|
299
|
-
proc_name role: 'monitor', status:
|
273
|
+
proc_name role: 'monitor', status: ARGV.join(' ')
|
300
274
|
|
301
275
|
logger.info "master process ready" # test_exec.rb relies on this message
|
302
276
|
if @ready_pipe
|
@@ -374,6 +348,9 @@ module Pitchfork
|
|
374
348
|
when Message::MoldSpawned
|
375
349
|
new_mold = @children.update(message)
|
376
350
|
logger.info("mold pid=#{new_mold.pid} gen=#{new_mold.generation} spawned")
|
351
|
+
when Message::ServiceSpawned
|
352
|
+
new_service = @children.update(message)
|
353
|
+
logger.info("service pid=#{new_service.pid} gen=#{new_service.generation} spawned")
|
377
354
|
when Message::MoldReady
|
378
355
|
old_molds = @children.molds
|
379
356
|
new_mold = @children.update(message)
|
@@ -429,6 +406,20 @@ module Pitchfork
|
|
429
406
|
Process.exit
|
430
407
|
end
|
431
408
|
|
409
|
+
def service_exit(service)
|
410
|
+
logger.info "service pid=#{service.pid} gen=#{service.generation} exiting"
|
411
|
+
proc_name status: "exiting"
|
412
|
+
|
413
|
+
if @before_service_worker_exit
|
414
|
+
begin
|
415
|
+
@before_service_worker_exit.call(self, service)
|
416
|
+
rescue => error
|
417
|
+
Pitchfork.log_error(logger, "before_service_worker_exit error", error)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
Process.exit
|
421
|
+
end
|
422
|
+
|
432
423
|
def rewindable_input
|
433
424
|
Pitchfork::HttpParser.input_class.method_defined?(:rewind)
|
434
425
|
end
|
@@ -590,6 +581,69 @@ module Pitchfork
|
|
590
581
|
worker
|
591
582
|
end
|
592
583
|
|
584
|
+
def service_loop(service)
|
585
|
+
readers = init_service_process(service)
|
586
|
+
waiter = prep_readers(readers)
|
587
|
+
|
588
|
+
ready = readers.dup
|
589
|
+
|
590
|
+
if @before_service_worker_ready
|
591
|
+
begin
|
592
|
+
@before_service_worker_ready.call(self, service)
|
593
|
+
rescue => error
|
594
|
+
Pitchfork.log_error(logger, "before_service_worker_ready", error)
|
595
|
+
Process.exit(1)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
proc_name status: "ready"
|
600
|
+
|
601
|
+
while readers[0]
|
602
|
+
begin
|
603
|
+
service.update_deadline(@timeout)
|
604
|
+
while sock = ready.shift
|
605
|
+
# Pitchfork::Worker#accept_nonblock is not like accept(2) at all,
|
606
|
+
# but that will return false
|
607
|
+
client = sock.accept_nonblock(exception: false)
|
608
|
+
|
609
|
+
case client
|
610
|
+
when false, :wait_readable
|
611
|
+
# no message, keep looping
|
612
|
+
end
|
613
|
+
|
614
|
+
service.update_deadline(@timeout)
|
615
|
+
end
|
616
|
+
|
617
|
+
# timeout so we can update .deadline and keep parent from SIGKILL-ing us
|
618
|
+
service.update_deadline(@timeout)
|
619
|
+
|
620
|
+
|
621
|
+
waiter.get_readers(ready, readers, @timeout * 500) # to milliseconds, but halved
|
622
|
+
rescue => e
|
623
|
+
Pitchfork.log_error(@logger, "listen loop error", e) if readers[0]
|
624
|
+
end
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
def spawn_service(service, detach:)
|
629
|
+
logger.info("service gen=#{service.generation} spawning...")
|
630
|
+
|
631
|
+
# We set the deadline before spawning the child so that if for some
|
632
|
+
# reason it gets stuck before reaching the worker loop,
|
633
|
+
# the monitor process will kill it.
|
634
|
+
service.update_deadline(@spawn_timeout)
|
635
|
+
@before_fork&.call(self)
|
636
|
+
fork_sibling("spawn_service") do
|
637
|
+
service.pid = Process.pid
|
638
|
+
|
639
|
+
after_fork_internal
|
640
|
+
service_loop(service)
|
641
|
+
service_exit(service)
|
642
|
+
end
|
643
|
+
|
644
|
+
service
|
645
|
+
end
|
646
|
+
|
593
647
|
def spawn_initial_mold
|
594
648
|
mold = Worker.new(nil)
|
595
649
|
mold.create_socketpair!
|
@@ -606,6 +660,21 @@ module Pitchfork
|
|
606
660
|
end
|
607
661
|
|
608
662
|
def spawn_missing_workers
|
663
|
+
if @before_service_worker_ready && !@children.service
|
664
|
+
service = Pitchfork::Service.new
|
665
|
+
if REFORKING_AVAILABLE
|
666
|
+
service.generation = @children.mold&.generation || 0
|
667
|
+
|
668
|
+
unless @children.mold&.spawn_service(service)
|
669
|
+
@logger.error("Failed to send a spawn_service command")
|
670
|
+
end
|
671
|
+
else
|
672
|
+
spawn_service(service, detach: false)
|
673
|
+
end
|
674
|
+
|
675
|
+
@children.register_service(service)
|
676
|
+
end
|
677
|
+
|
609
678
|
worker_nr = -1
|
610
679
|
until (worker_nr += 1) == @worker_processes
|
611
680
|
if @children.nr_alive?(worker_nr)
|
@@ -643,9 +712,18 @@ module Pitchfork
|
|
643
712
|
end
|
644
713
|
|
645
714
|
def maintain_worker_count
|
646
|
-
|
647
|
-
off
|
648
|
-
|
715
|
+
off = @children.workers_count - worker_processes
|
716
|
+
off -= 1 if @before_service_worker_ready && !@children.service
|
717
|
+
|
718
|
+
if off < 0
|
719
|
+
spawn_missing_workers
|
720
|
+
elsif off > 0
|
721
|
+
@children.each_worker do |worker|
|
722
|
+
if worker.nr >= worker_processes
|
723
|
+
worker.soft_kill(:TERM)
|
724
|
+
end
|
725
|
+
end
|
726
|
+
end
|
649
727
|
end
|
650
728
|
|
651
729
|
def restart_outdated_workers
|
@@ -658,6 +736,16 @@ module Pitchfork
|
|
658
736
|
max_pending_workers = (worker_processes * 0.1).ceil
|
659
737
|
workers_to_restart = max_pending_workers - @children.restarting_workers_count
|
660
738
|
|
739
|
+
if service = @children.service
|
740
|
+
if service.outdated?
|
741
|
+
if service.soft_kill(:TERM)
|
742
|
+
logger.info("Sent SIGTERM to service pid=#{service.pid} gen=#{service.generation}")
|
743
|
+
else
|
744
|
+
logger.info("Failed to send SIGTERM to service pid=#{service.pid} gen=#{service.generation}")
|
745
|
+
end
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
661
749
|
if workers_to_restart > 0
|
662
750
|
outdated_workers = @children.workers.select { |w| !w.exiting? && w.generation < @children.mold.generation }
|
663
751
|
outdated_workers.each do |worker|
|
@@ -701,18 +789,14 @@ module Pitchfork
|
|
701
789
|
rss = @request.response_start_sent
|
702
790
|
buf = (rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n").b
|
703
791
|
headers.each { |key, value| append_header(buf, key, value) }
|
704
|
-
buf << (rss ? "\r\nHTTP/1.1 "
|
792
|
+
buf << (rss ? "\r\nHTTP/1.1 " : "\r\n")
|
705
793
|
client.write(buf)
|
706
794
|
end
|
707
795
|
|
708
796
|
def e100_response_write(client, env)
|
709
|
-
# We use String#freeze to avoid allocations under Ruby 2.1+
|
710
|
-
# Not many users hit this code path, so it's better to reduce the
|
711
|
-
# constant table sizes even for Ruby 2.0 users who'll hit extra
|
712
|
-
# allocations here.
|
713
797
|
client.write(@request.response_start_sent ?
|
714
|
-
"100 Continue\r\n\r\nHTTP/1.1 "
|
715
|
-
"HTTP/1.1 100 Continue\r\n\r\n"
|
798
|
+
"100 Continue\r\n\r\nHTTP/1.1 " :
|
799
|
+
"HTTP/1.1 100 Continue\r\n\r\n")
|
716
800
|
env.delete('HTTP_EXPECT')
|
717
801
|
end
|
718
802
|
|
@@ -723,7 +807,11 @@ module Pitchfork
|
|
723
807
|
@request = Pitchfork::HttpParser.new
|
724
808
|
env = @request.read(client)
|
725
809
|
|
726
|
-
|
810
|
+
status = "requests: #{worker.requests_count}, processing: #{env["PATH_INFO"]}"
|
811
|
+
if request_id = env["HTTP_X_REQUEST_ID"]
|
812
|
+
status += ", request_id: #{request_id}"
|
813
|
+
end
|
814
|
+
proc_name status: status
|
727
815
|
|
728
816
|
env["pitchfork.worker"] = worker
|
729
817
|
timeout_handler.rack_env = env
|
@@ -817,6 +905,18 @@ module Pitchfork
|
|
817
905
|
readers
|
818
906
|
end
|
819
907
|
|
908
|
+
def init_service_process(service)
|
909
|
+
proc_name role: "(gen:#{service.generation}) mold", status: "init"
|
910
|
+
LISTENERS.each(&:close) # Don't appear as listening to incoming requests
|
911
|
+
service.register_to_master(@control_socket[1])
|
912
|
+
readers = [service]
|
913
|
+
trap(:QUIT) { nuke_listeners!(readers) }
|
914
|
+
trap(:TERM) { nuke_listeners!(readers) }
|
915
|
+
trap(:INT) { nuke_listeners!(readers); exit!(0) }
|
916
|
+
proc_name role: "(gen:#{service.generation}) service", status: "ready"
|
917
|
+
readers
|
918
|
+
end
|
919
|
+
|
820
920
|
def init_mold_process(mold)
|
821
921
|
proc_name role: "(gen:#{mold.generation}) mold", status: "init"
|
822
922
|
after_mold_fork.call(self, mold)
|
@@ -830,7 +930,7 @@ module Pitchfork
|
|
830
930
|
|
831
931
|
if Pitchfork.const_defined?(:Waiter)
|
832
932
|
def prep_readers(readers)
|
833
|
-
Pitchfork::Waiter.prep_readers(readers)
|
933
|
+
Pitchfork::Info.keep_io(Pitchfork::Waiter.prep_readers(readers))
|
834
934
|
end
|
835
935
|
else
|
836
936
|
require_relative 'select_waiter'
|
@@ -858,24 +958,25 @@ module Pitchfork
|
|
858
958
|
# Pitchfork::Worker#accept_nonblock is not like accept(2) at all,
|
859
959
|
# but that will return false
|
860
960
|
client = sock.accept_nonblock(exception: false)
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
logger.error("worker=#{worker.nr} gen=#{worker.generation} is no longer fork safe, can't refork")
|
869
|
-
end
|
870
|
-
when Message
|
871
|
-
worker.update(client)
|
961
|
+
|
962
|
+
case client
|
963
|
+
when false, :wait_readable
|
964
|
+
# no message, keep looping
|
965
|
+
when Message::PromoteWorker
|
966
|
+
if Info.fork_safe?
|
967
|
+
spawn_mold(worker)
|
872
968
|
else
|
873
|
-
|
874
|
-
@after_request_complete&.call(self, worker, request_env)
|
875
|
-
worker.increment_requests_count
|
969
|
+
logger.error("worker=#{worker.nr} gen=#{worker.generation} is no longer fork safe, can't refork")
|
876
970
|
end
|
877
|
-
|
971
|
+
when Message
|
972
|
+
worker.update(client)
|
973
|
+
else
|
974
|
+
request_env = process_client(client, worker, prepare_timeout(worker))
|
975
|
+
worker.increment_requests_count
|
976
|
+
@after_request_complete&.call(self, worker, request_env)
|
878
977
|
end
|
978
|
+
|
979
|
+
worker.update_deadline(@timeout)
|
879
980
|
end
|
880
981
|
|
881
982
|
# timeout so we can update .deadline and keep parent from SIGKILL-ing us
|
@@ -958,6 +1059,22 @@ module Pitchfork
|
|
958
1059
|
rescue => error
|
959
1060
|
raise BootFailure, error.message
|
960
1061
|
end
|
1062
|
+
when Message::SpawnService
|
1063
|
+
retries = 1
|
1064
|
+
begin
|
1065
|
+
spawn_service(Service.new(generation: mold.generation), detach: true)
|
1066
|
+
rescue ForkFailure
|
1067
|
+
if retries > 0
|
1068
|
+
@logger.fatal("mold pid=#{mold.pid} gen=#{mold.generation} Failed to spawn a service. Retrying.")
|
1069
|
+
retries -= 1
|
1070
|
+
retry
|
1071
|
+
else
|
1072
|
+
@logger.fatal("mold pid=#{mold.pid} gen=#{mold.generation} Failed to spawn a service twice in a row. Corrupted mold process?")
|
1073
|
+
Process.exit(1)
|
1074
|
+
end
|
1075
|
+
rescue => error
|
1076
|
+
raise BootFailure, error.message
|
1077
|
+
end
|
961
1078
|
else
|
962
1079
|
logger.error("Unexpected mold message #{message.inspect}")
|
963
1080
|
end
|
@@ -1004,7 +1121,7 @@ module Pitchfork
|
|
1004
1121
|
@proctitle_role = role if role
|
1005
1122
|
@proctitle_status = status if status
|
1006
1123
|
|
1007
|
-
Process.setproctitle("#{File.basename(
|
1124
|
+
Process.setproctitle("#{File.basename($PROGRAM_NAME)} #{@proctitle_role} - #{@proctitle_status}")
|
1008
1125
|
end
|
1009
1126
|
|
1010
1127
|
def bind_listeners!
|
@@ -1012,7 +1129,6 @@ module Pitchfork
|
|
1012
1129
|
if listeners.empty?
|
1013
1130
|
listeners << Pitchfork::Const::DEFAULT_LISTEN
|
1014
1131
|
@init_listeners << Pitchfork::Const::DEFAULT_LISTEN
|
1015
|
-
START_CTX[:argv] << "-l#{Pitchfork::Const::DEFAULT_LISTEN}"
|
1016
1132
|
end
|
1017
1133
|
listeners.each { |addr| listen(addr) }
|
1018
1134
|
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
data/lib/pitchfork/message.rb
CHANGED
@@ -122,12 +122,16 @@ module Pitchfork
|
|
122
122
|
|
123
123
|
Message = Class.new(Struct)
|
124
124
|
class Message
|
125
|
-
SpawnWorker =
|
126
|
-
WorkerSpawned =
|
127
|
-
PromoteWorker =
|
128
|
-
MoldSpawned = Message.new(:nr, :pid, :generation, :pipe)
|
129
|
-
MoldReady = Message.new(:nr, :pid, :generation)
|
125
|
+
SpawnWorker = new(:nr)
|
126
|
+
WorkerSpawned = new(:nr, :pid, :generation, :pipe)
|
127
|
+
PromoteWorker = new(:generation)
|
130
128
|
|
131
|
-
|
129
|
+
MoldSpawned = new(:nr, :pid, :generation, :pipe)
|
130
|
+
MoldReady = new(:nr, :pid, :generation)
|
131
|
+
|
132
|
+
SpawnService = new(:_) # Struct.new requires at least 1 member on Ruby < 3.3
|
133
|
+
ServiceSpawned = new(:pid, :generation, :pipe)
|
134
|
+
|
135
|
+
SoftKill = new(:signum)
|
132
136
|
end
|
133
137
|
end
|