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.
@@ -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
@@ -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 Raindrops.const_defined?(:TCP_Info)
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
- # Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
117
- raise Errno::EPIPE, "client closed connection".freeze,
118
- EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
119
- else
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
- # Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
157
- # Not that efficient, but probably still better than doing unnecessary
158
- # work after a client gives up.
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 closed_state_str?(state)
176
- case state
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
 
@@ -65,7 +65,7 @@ module Pitchfork
65
65
  append_header(buf, key, value)
66
66
  end
67
67
  end
68
- socket.write(buf << "\r\n".freeze)
68
+ socket.write(buf << "\r\n")
69
69
  end
70
70
 
71
71
  if hijack
@@ -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: START_CTX[:argv].join(' ')
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.preallocate_drops(worker_processes)
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: START_CTX[:argv].join(' ')
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
- (off = @children.workers_count - worker_processes) == 0 and return
647
- off < 0 and return spawn_missing_workers
648
- @children.each_worker { |w| w.nr >= worker_processes and w.soft_kill(:TERM) }
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 ".freeze : "\r\n".freeze)
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 ".freeze :
715
- "HTTP/1.1 100 Continue\r\n\r\n".freeze)
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
- proc_name status: "requests: #{worker.requests_count}, processing: #{env["PATH_INFO"]}"
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
- client = false if client == :wait_readable
862
- if client
863
- case client
864
- when Message::PromoteWorker
865
- if Info.fork_safe?
866
- spawn_mold(worker)
867
- else
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
- request_env = process_client(client, worker, prepare_timeout(worker))
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
- worker.update_deadline(@timeout)
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(START_CTX[0])} #{@proctitle_role} - #{@proctitle_status}")
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?
@@ -122,12 +122,16 @@ module Pitchfork
122
122
 
123
123
  Message = Class.new(Struct)
124
124
  class Message
125
- SpawnWorker = Message.new(:nr)
126
- WorkerSpawned = Message.new(:nr, :pid, :generation, :pipe)
127
- PromoteWorker = Message.new(:generation)
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
- SoftKill = Message.new(:signum)
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