pitchfork 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f23c677bd64eb8ca7a040cb2d41dab4047e05ae849da16af8e537748bfb6dbe
4
- data.tar.gz: 890bac5b67c23d6f0aacf0d9130999cf69ac0bc5a2f1ea06bdd142d67c0606ac
3
+ metadata.gz: 3b4583ce30977e64c83d59160b7798f93073c6d0935d77c552e91397388bb434
4
+ data.tar.gz: 02c2c0abe2a400496c07bbe7f41c4d316eaed60b69a7681518a805b8e6f91a4a
5
5
  SHA512:
6
- metadata.gz: 39c33bbbe865ba22bebc8b6b1e823f71d0ba6b2787f06af26f0ffe3d4251c781850826fcbcdf1dc13b1734919770e35dc57815d2de057f18d97ba354f247a3e7
7
- data.tar.gz: 94e5ae39e129af1fd950c9a77e094bac5bda586f7358115fd8cf25bb3c6ee2079bb4654ebd4e6ed5e9ff498b461559da07d7b44acc0147fcac5c9a4695b7c62c
6
+ metadata.gz: 21360afc97d6172038ad4bfeae1c0fab4eeef787024c5a6743b0fb72fd61feb259666f0e9b0d519c9173072505f6f205d4bec2d764ec964173ea9116be20336f
7
+ data.tar.gz: b1faf79e0f186011c41523ff6bacc50a8ab1ad039fc0a9e934abc8fb17ffbbedcf9295fa50415f650b2a7ca30e019d3825079da1f83eaf10068c24c2b1734b08
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.11.0
4
+
5
+ - Drop invalid response headers.
6
+ - Enforce `spawn_timeout` for molds too.
7
+ - Gracefully shutdown the server if the mold appear to be corrupted (#79).
8
+ - Add more information in proctitle when forking a new sibbling.
9
+ - Add a `before_fork` callback called before forking new molds and new workers.
10
+
3
11
  # 0.10.0
4
12
 
5
13
  - Include requests count in workers proctitle.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pitchfork (0.10.0)
4
+ pitchfork (0.11.0)
5
5
  rack (>= 2.0)
6
6
  raindrops (~> 0.7)
7
7
 
data/Rakefile CHANGED
@@ -42,7 +42,6 @@ namespace :test do
42
42
  # preferable to edit the test suite as little as possible.
43
43
  task legacy_integration: :compile do
44
44
  File.write("test/integration/random_blob", File.read("/dev/random", 1_000_000))
45
- lib = File.expand_path("lib", __dir__)
46
45
  path = "#{File.expand_path("exe", __dir__)}:#{ENV["PATH"]}"
47
46
  old_path = ENV["PATH"]
48
47
  ENV["PATH"] = "#{path}:#{old_path}"
@@ -241,10 +241,10 @@ for more details on nginx upstream configuration.
241
241
  ### `spawn_timeout`
242
242
 
243
243
  ```ruby
244
- timeout 5
244
+ spawn_timeout 5
245
245
  ```
246
246
 
247
- Sets the timeout for a newly spawned worker to be ready after being spawned.
247
+ Sets the timeout for a newly spawned worker or mold to be ready after being spawned.
248
248
 
249
249
  This timeout is a safeguard against various low-level fork safety bugs that could cause
250
250
  a process to dead-lock.
@@ -284,6 +284,16 @@ after_monitor_ready do |server|
284
284
  end
285
285
  ```
286
286
 
287
+ ### `before_fork`
288
+
289
+ Called by the mold before forking a new workers, and by workers before they spawn a new mold.
290
+
291
+ ```ruby
292
+ before_fork do |server|
293
+ server.logger.info("About to fork, closing connections!")
294
+ end
295
+ ```
296
+
287
297
  ### `after_mold_fork`
288
298
 
289
299
  ```ruby
@@ -307,6 +317,11 @@ That is the case for instance of many SQL databases protocols.
307
317
  This is also the callback in which memory optimizations, such as
308
318
  heap compaction should be done.
309
319
 
320
+ This callback is also a good place to check for potential corruption
321
+ issues caused by forking. If you detect something wrong, you can
322
+ call `Process.exit`, and this mold won't be used, another one will be
323
+ spawned later. e.g. you can check `Socket.getaddrinfo` still works, etc.
324
+
310
325
  ### `after_worker_fork`
311
326
 
312
327
  ```ruby
data/docs/FORK_SAFETY.md CHANGED
@@ -22,13 +22,17 @@ reopen connections and restart threads:
22
22
  ```ruby
23
23
  # pitchfork.conf.rb
24
24
 
25
- after_mold_fork do
25
+ before_fork do
26
26
  Sequel::DATABASES.each(&:disconnect)
27
27
  end
28
28
 
29
- after_worker_fork do
29
+ after_mold_fork do
30
30
  SomeLibary.connection.close
31
31
  end
32
+
33
+ after_worker_fork do
34
+ SomeOtherLibary.connection.close
35
+ end
32
36
  ```
33
37
 
34
38
  The documentation of any database client or network library you use should be
@@ -66,6 +66,11 @@ module Pitchfork
66
66
  @workers.key?(nr)
67
67
  end
68
68
 
69
+ def abandon(worker)
70
+ @workers.delete(worker.nr)
71
+ @pending_workers.delete(worker.nr)
72
+ end
73
+
69
74
  def reap(pid)
70
75
  if child = @children.delete(pid)
71
76
  @pending_workers.delete(child.nr)
@@ -73,6 +78,9 @@ module Pitchfork
73
78
  @molds.delete(child.pid)
74
79
  @workers.delete(child.nr)
75
80
  if @mold == child
81
+ @pending_workers.reject! do |nr, worker|
82
+ worker.generation == @mold.generation
83
+ end
76
84
  @mold = nil
77
85
  end
78
86
  end
@@ -99,6 +107,10 @@ module Pitchfork
99
107
  @molds.values
100
108
  end
101
109
 
110
+ def empty?
111
+ @children.empty?
112
+ end
113
+
102
114
  def each(&block)
103
115
  @children.each_value(&block)
104
116
  end
@@ -126,14 +138,6 @@ module Pitchfork
126
138
  end
127
139
  end
128
140
 
129
- def hard_timeout(child)
130
- child.hard_timeout!
131
- rescue Errno::ESRCH
132
- reap(child.pid)
133
- child.close
134
- true
135
- end
136
-
137
141
  def workers
138
142
  @workers.values
139
143
  end
@@ -34,9 +34,11 @@ module Pitchfork
34
34
  :soft_timeout => 20,
35
35
  :cleanup_timeout => 2,
36
36
  :spawn_timeout => 10,
37
+ :timeout_signal => -> (_pid) { :KILL },
37
38
  :timeout => 22,
38
39
  :logger => default_logger,
39
40
  :worker_processes => 1,
41
+ :before_fork => nil,
40
42
  :after_worker_fork => lambda { |server, worker|
41
43
  server.logger.info("worker=#{worker.nr} gen=#{worker.generation} pid=#{$$} spawned")
42
44
  },
@@ -133,6 +135,10 @@ module Pitchfork
133
135
  set[:logger] = obj
134
136
  end
135
137
 
138
+ def before_fork(*args, &block)
139
+ set_hook(:before_fork, block_given? ? block : args[0], 1)
140
+ end
141
+
136
142
  def after_worker_fork(*args, &block)
137
143
  set_hook(:after_worker_fork, block_given? ? block : args[0])
138
144
  end
@@ -175,6 +181,19 @@ module Pitchfork
175
181
  set_int(:timeout, soft_timeout + cleanup_timeout, 5)
176
182
  end
177
183
 
184
+ def timeout_signal(*args, &block)
185
+ if block_given?
186
+ set_hook(:timeout_signal, block, 1)
187
+ elsif args.first.respond_to?(:call)
188
+ set_hook(:timeout_signal, args.first, 1)
189
+ elsif args.first.is_a?(Symbol)
190
+ signal = args.first
191
+ set_hook(:timeout_signal, ->(_pid) { signal }, 1)
192
+ else
193
+ raise ArgumentError, "timeout_signal must be a symbol or a proc"
194
+ end
195
+ end
196
+
178
197
  def spawn_timeout(seconds)
179
198
  set_int(:spawn_timeout, seconds, 1)
180
199
  end
@@ -14,6 +14,8 @@ module Pitchfork
14
14
  STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ?
15
15
  Rack::Utils::HTTP_STATUS_CODES : {}
16
16
 
17
+ ILLEGAL_HEADER_VALUE = /[\x00-\x08\x0A-\x1F]/
18
+
17
19
  # internal API, code will always be common-enough-for-even-old-Rack
18
20
  def err_response(code, response_start_sent)
19
21
  "#{response_start_sent ? '' : 'HTTP/1.1 '}" \
@@ -23,10 +25,16 @@ module Pitchfork
23
25
  def append_header(buf, key, value)
24
26
  case value
25
27
  when Array # Rack 3
26
- value.each { |v| buf << "#{key}: #{v}\r\n" }
28
+ value.each do |v|
29
+ next if ILLEGAL_HEADER_VALUE.match?(v)
30
+ buf << "#{key}: #{v}\r\n"
31
+ end
27
32
  when /\n/ # Rack 2
28
33
  # avoiding blank, key-only cookies with /\n+/
29
- value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
34
+ value.split(/\n+/).each do |v|
35
+ next if ILLEGAL_HEADER_VALUE.match?(v)
36
+ buf << "#{key}: #{v}\r\n"
37
+ end
30
38
  else
31
39
  buf << "#{key}: #{value}\r\n"
32
40
  end
@@ -74,8 +74,8 @@ module Pitchfork
74
74
  end
75
75
 
76
76
  # :stopdoc:
77
- attr_accessor :app, :timeout, :soft_timeout, :cleanup_timeout, :spawn_timeout, :worker_processes,
78
- :after_worker_fork, :after_mold_fork,
77
+ attr_accessor :app, :timeout, :timeout_signal, :soft_timeout, :cleanup_timeout, :spawn_timeout, :worker_processes,
78
+ :before_fork, :after_worker_fork, :after_mold_fork,
79
79
  :listener_opts, :children,
80
80
  :orig_app, :config, :ready_pipe,
81
81
  :default_middleware, :early_hints
@@ -201,7 +201,7 @@ module Pitchfork
201
201
  else
202
202
  build_app!
203
203
  bind_listeners!
204
- after_mold_fork.call(self, Worker.new(nil, pid: $$).promoted!)
204
+ after_mold_fork.call(self, Worker.new(nil, pid: $$).promoted!(@spawn_timeout))
205
205
  end
206
206
 
207
207
  if sync
@@ -315,7 +315,7 @@ module Pitchfork
315
315
  end
316
316
  end
317
317
  stop # gracefully shutdown all workers on our way out
318
- logger.info "master complete"
318
+ logger.info "master complete status=#{@exit_status}"
319
319
  @exit_status
320
320
  end
321
321
 
@@ -368,7 +368,7 @@ module Pitchfork
368
368
  when Message::WorkerSpawned
369
369
  worker = @children.update(message)
370
370
  # TODO: should we send a message to the worker to acknowledge?
371
- logger.info "worker=#{worker.nr} pid=#{worker.pid} registered"
371
+ logger.info "worker=#{worker.nr} pid=#{worker.pid} gen=#{worker.generation} registered"
372
372
  when Message::MoldSpawned
373
373
  new_mold = @children.update(message)
374
374
  logger.info("mold pid=#{new_mold.pid} gen=#{new_mold.generation} spawned")
@@ -394,7 +394,7 @@ module Pitchfork
394
394
  wait_for_pending_workers
395
395
  self.listeners = []
396
396
  limit = Pitchfork.time_now + timeout
397
- until @children.workers.empty? || Pitchfork.time_now > limit
397
+ until @children.empty? || Pitchfork.time_now > limit
398
398
  if graceful
399
399
  @children.soft_kill_all(:TERM)
400
400
  else
@@ -404,11 +404,17 @@ module Pitchfork
404
404
  return StopIteration
405
405
  end
406
406
  end
407
- @children.hard_kill_all(:KILL)
407
+
408
+ @children.each do |child|
409
+ if child.pid
410
+ @children.hard_kill(@timeout_signal.call(child.pid), child)
411
+ end
412
+ end
408
413
  @promotion_lock.unlink
409
414
  end
410
415
 
411
416
  def worker_exit(worker)
417
+ logger.info "worker=#{worker.nr} pid=#{worker.pid} gen=#{worker.generation} exiting"
412
418
  proc_name status: "exiting"
413
419
 
414
420
  if @before_worker_exit
@@ -494,8 +500,8 @@ module Pitchfork
494
500
  now = Pitchfork.time_now(true)
495
501
  next_sleep = @timeout - 1
496
502
 
497
- @children.workers.each do |worker|
498
- deadline = worker.deadline
503
+ @children.each do |child|
504
+ deadline = child.deadline
499
505
  if 0 == deadline # worker is idle
500
506
  next
501
507
  elsif deadline > now # worker still has time
@@ -506,28 +512,34 @@ module Pitchfork
506
512
  next
507
513
  else # worker is out of time
508
514
  next_sleep = 0
509
- hard_timeout(worker)
515
+ hard_timeout(child)
510
516
  end
511
517
  end
512
518
 
513
519
  next_sleep <= 0 ? 1 : next_sleep
514
520
  end
515
521
 
516
- def hard_timeout(worker)
517
- if @after_worker_hard_timeout
522
+ def hard_timeout(child)
523
+ if child.pid.nil? # Not yet registered, likely never spawned
524
+ logger.error "worker=#{child.nr} timed out during spawn, abandoning"
525
+ @children.abandon(worker)
526
+ return
527
+ end
528
+
529
+ if @after_worker_hard_timeout && !child.mold?
518
530
  begin
519
- @after_worker_hard_timeout.call(self, worker)
531
+ @after_worker_hard_timeout.call(self, child)
520
532
  rescue => error
521
533
  Pitchfork.log_error(@logger, "after_worker_hard_timeout callback", error)
522
534
  end
523
535
  end
524
536
 
525
- if worker.mold?
526
- logger.error "mold pid=#{worker.pid} timed out, killing"
537
+ if child.mold?
538
+ logger.error "mold pid=#{child.pid} gen=#{child.generation} timed out, killing"
527
539
  else
528
- logger.error "worker=#{worker.nr} pid=#{worker.pid} timed out, killing"
540
+ logger.error "worker=#{child.nr} pid=#{child.pid} gen=#{child.generation} timed out, killing"
529
541
  end
530
- @children.hard_timeout(worker) # take no prisoners for hard timeout violations
542
+ @children.hard_kill(@timeout_signal.call(child.pid), child) # take no prisoners for hard timeout violations
531
543
  end
532
544
 
533
545
  def trigger_refork
@@ -563,7 +575,8 @@ module Pitchfork
563
575
  # reason it gets stuck before reaching the worker loop,
564
576
  # the monitor process will kill it.
565
577
  worker.update_deadline(@spawn_timeout)
566
- Pitchfork.fork_sibling do
578
+ @before_fork&.call(self)
579
+ fork_sibling("spawn_worker") do
567
580
  worker.pid = Process.pid
568
581
 
569
582
  after_fork_internal
@@ -598,6 +611,8 @@ module Pitchfork
598
611
  worker = Pitchfork::Worker.new(worker_nr)
599
612
 
600
613
  if REFORKING_AVAILABLE
614
+ worker.generation = @children.mold&.generation || 0
615
+
601
616
  unless @children.mold&.spawn_worker(worker)
602
617
  @logger.error("Failed to send a spawn_worker command")
603
618
  end
@@ -784,15 +799,18 @@ module Pitchfork
784
799
  readers << worker
785
800
  trap(:QUIT) { nuke_listeners!(readers) }
786
801
  trap(:TERM) { nuke_listeners!(readers) }
802
+ trap(:INT) { nuke_listeners!(readers); exit!(0) }
787
803
  readers
788
804
  end
789
805
 
790
806
  def init_mold_process(mold)
791
- proc_name role: "(gen:#{mold.generation}) mold", status: "ready"
807
+ proc_name role: "(gen:#{mold.generation}) mold", status: "init"
792
808
  after_mold_fork.call(self, mold)
793
809
  readers = [mold]
794
810
  trap(:QUIT) { nuke_listeners!(readers) }
795
811
  trap(:TERM) { nuke_listeners!(readers) }
812
+ trap(:INT) { nuke_listeners!(readers); exit!(0) }
813
+ proc_name role: "(gen:#{mold.generation}) mold", status: "ready"
796
814
  readers
797
815
  end
798
816
 
@@ -831,7 +849,7 @@ module Pitchfork
831
849
  case client
832
850
  when Message::PromoteWorker
833
851
  if Info.fork_safe?
834
- spawn_mold(worker.generation)
852
+ spawn_mold(worker)
835
853
  else
836
854
  logger.error("worker=#{worker.nr} gen=#{worker.generation} is no longer fork safe, can't refork")
837
855
  end
@@ -852,8 +870,8 @@ module Pitchfork
852
870
  if @refork_condition && Info.fork_safe? && !worker.outdated?
853
871
  if @refork_condition.met?(worker, logger)
854
872
  proc_name status: "requests: #{worker.requests_count}, spawning mold"
855
- if spawn_mold(worker.generation)
856
- logger.info("Refork condition met, promoting ourselves")
873
+ if spawn_mold(worker)
874
+ logger.info("worker=#{worker.nr} gen=#{worker.generation} Refork condition met, promoting ourselves")
857
875
  end
858
876
  @refork_condition.backoff!
859
877
  end
@@ -867,13 +885,17 @@ module Pitchfork
867
885
  end
868
886
  end
869
887
 
870
- def spawn_mold(current_generation)
888
+ def spawn_mold(worker)
871
889
  return false unless @promotion_lock.try_lock
872
890
 
891
+ worker.update_deadline(@spawn_timeout)
892
+
893
+ @before_fork&.call(self)
894
+
873
895
  begin
874
- Pitchfork.fork_sibling do
875
- mold = Worker.new(nil, pid: Process.pid, generation: current_generation)
876
- mold.promote!
896
+ fork_sibling("spawn_mold") do
897
+ mold = Worker.new(nil, pid: Process.pid, generation: worker.generation)
898
+ mold.promote!(@spawn_timeout)
877
899
  mold.start_promotion(@control_socket[1])
878
900
  mold_loop(mold)
879
901
  end
@@ -907,8 +929,18 @@ module Pitchfork
907
929
  when false
908
930
  # no message, keep looping
909
931
  when Message::SpawnWorker
932
+ retries = 1
910
933
  begin
911
934
  spawn_worker(Worker.new(message.nr, generation: mold.generation), detach: true)
935
+ rescue ForkFailure
936
+ if retries > 0
937
+ @logger.fatal("mold pid=#{mold.pid} gen=#{mold.generation} Failed to spawn a worker. Retrying.")
938
+ retries -= 1
939
+ retry
940
+ else
941
+ @logger.fatal("mold pid=#{mold.pid} gen=#{mold.generation} Failed to spawn a worker twice in a row. Corrupted mold process?")
942
+ Process.exit(1)
943
+ end
912
944
  rescue => error
913
945
  raise BootFailure, error.message
914
946
  end
@@ -977,5 +1009,61 @@ module Pitchfork
977
1009
  handler.timeout_request = SoftTimeout.request(@soft_timeout, handler)
978
1010
  handler
979
1011
  end
1012
+
1013
+ FORK_TIMEOUT = 5
1014
+
1015
+ def fork_sibling(role, &block)
1016
+ if REFORKING_AVAILABLE
1017
+ r, w = Pitchfork::Info.keep_ios(IO.pipe)
1018
+ # We double fork so that the new worker is re-attached back
1019
+ # to the master.
1020
+ # This requires either PR_SET_CHILD_SUBREAPER which is exclusive to Linux 3.4
1021
+ # or the master to be PID 1.
1022
+ if middle_pid = FORK_LOCK.synchronize { Process.fork } # parent
1023
+ w.close
1024
+ # We need to wait(2) so that the middle process doesn't end up a zombie.
1025
+ # The process only call fork again an exit so it should be pretty fast.
1026
+ # However it might need to execute some `Process._fork` or `at_exit` callbacks,
1027
+ # as well as Ruby's cleanup procedure to run finalizers etc, and there is a risk
1028
+ # of deadlock.
1029
+ # So in case it takes more than 5 seconds to exit, we kill it.
1030
+ # TODO: rather than to busy loop here, we handle it in the worker/mold loop
1031
+ process_wait_with_timeout(middle_pid, FORK_TIMEOUT)
1032
+ pid_str = r.gets
1033
+ r.close
1034
+ if pid_str
1035
+ Integer(pid_str)
1036
+ else
1037
+ raise ForkFailure, "fork_sibling didn't succeed in #{FORK_TIMEOUT} seconds"
1038
+ end
1039
+ else # first child
1040
+ r.close
1041
+ Process.setproctitle("<pitchfork fork_sibling(#{role})>")
1042
+ pid = Pitchfork.clean_fork do
1043
+ # detach into a grand child
1044
+ w.close
1045
+ yield
1046
+ end
1047
+ w.puts(pid)
1048
+ w.close
1049
+ exit
1050
+ end
1051
+ else
1052
+ clean_fork(&block)
1053
+ end
1054
+ end
1055
+
1056
+ def process_wait_with_timeout(pid, timeout)
1057
+ (timeout * 50).times do
1058
+ _, status = Process.waitpid2(pid, Process::WNOHANG)
1059
+ return status if status
1060
+ sleep 0.02 # 50 * 20ms => 1s
1061
+ end
1062
+
1063
+ # The process didn't exit in the allotted time, so we kill it.
1064
+ Process.kill(@timeout_signal.call(pid), pid)
1065
+ _, status = Process.waitpid2(pid)
1066
+ status
1067
+ end
980
1068
  end
981
1069
  end
@@ -26,7 +26,7 @@ module Pitchfork
26
26
  @map.each_key(&block)
27
27
  end
28
28
  end
29
-
29
+
30
30
  @kept_ios = WeakSet.new
31
31
 
32
32
  class << self
@@ -2,6 +2,12 @@
2
2
 
3
3
  module Pitchfork
4
4
  module ReforkCondition
5
+ @backoff_delay = 10.0
6
+
7
+ class << self
8
+ attr_accessor :backoff_delay
9
+ end
10
+
5
11
  class RequestsCount
6
12
  def initialize(request_counts)
7
13
  @limits = request_counts
@@ -31,7 +37,7 @@ module Pitchfork
31
37
  end
32
38
  end
33
39
 
34
- def backoff!(delay = 10.0)
40
+ def backoff!(delay = ReforkCondition.backoff_delay)
35
41
  @backoff_until = Pitchfork.time_now + delay
36
42
  end
37
43
  end
@@ -10,7 +10,8 @@ module Pitchfork
10
10
  CURRENT_GENERATION_OFFSET = 0
11
11
  SHUTDOWN_OFFSET = 1
12
12
  MOLD_TICK_OFFSET = 2
13
- WORKER_TICK_OFFSET = 3
13
+ MOLD_PROMOTION_TICK_OFFSET = 3
14
+ WORKER_TICK_OFFSET = 4
14
15
 
15
16
  DROPS = [Raindrops.new(PER_DROP)]
16
17
 
@@ -49,6 +50,10 @@ module Pitchfork
49
50
  self[MOLD_TICK_OFFSET]
50
51
  end
51
52
 
53
+ def mold_promotion_deadline
54
+ self[MOLD_PROMOTION_TICK_OFFSET]
55
+ end
56
+
52
57
  def worker_deadline(worker_nr)
53
58
  self[WORKER_TICK_OFFSET + worker_nr]
54
59
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pitchfork
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.0"
5
5
  module Const
6
6
  UNICORN_VERSION = '6.1.0'
7
7
  end
@@ -27,7 +27,7 @@ module Pitchfork
27
27
  @deadline_drop = SharedMemory.worker_deadline(nr)
28
28
  self.deadline = 0
29
29
  else
30
- promoted!
30
+ promoted!(nil)
31
31
  end
32
32
  end
33
33
 
@@ -55,6 +55,13 @@ module Pitchfork
55
55
  message.class.members.each do |member|
56
56
  send("#{member}=", message.public_send(member))
57
57
  end
58
+
59
+ case message
60
+ when Message::MoldSpawned
61
+ @deadline_drop = SharedMemory.mold_promotion_deadline
62
+ when Message::MoldReady
63
+ @deadline_drop = SharedMemory.mold_deadline
64
+ end
58
65
  end
59
66
 
60
67
  def register_to_master(control_socket)
@@ -75,6 +82,7 @@ module Pitchfork
75
82
  message = Message::MoldReady.new(@nr, @pid, generation)
76
83
  control_socket.sendmsg(message)
77
84
  SharedMemory.current_generation = @generation
85
+ @deadline_drop = SharedMemory.mold_deadline
78
86
  end
79
87
 
80
88
  def promote(generation)
@@ -85,16 +93,16 @@ module Pitchfork
85
93
  send_message_nonblock(Message::SpawnWorker.new(new_worker.nr))
86
94
  end
87
95
 
88
- def promote!
96
+ def promote!(timeout)
89
97
  @generation += 1
90
- promoted!
98
+ promoted!(timeout)
91
99
  end
92
100
 
93
- def promoted!
101
+ def promoted!(timeout)
94
102
  @mold = true
95
103
  @nr = nil
96
- @deadline_drop = SharedMemory.mold_deadline
97
- self.deadline = 0
104
+ @deadline_drop = SharedMemory.mold_promotion_deadline
105
+ update_deadline(timeout) if timeout
98
106
  self
99
107
  end
100
108
 
@@ -143,10 +151,6 @@ module Pitchfork
143
151
  Process.kill(sig, pid)
144
152
  end
145
153
 
146
- def hard_timeout!
147
- hard_kill(:KILL)
148
- end
149
-
150
154
  # this only runs when the Rack app.call is not running
151
155
  # act like a listener
152
156
  def accept_nonblock(exception: nil) # :nodoc:
data/lib/pitchfork.rb CHANGED
@@ -33,6 +33,7 @@ module Pitchfork
33
33
  ClientShutdown = Class.new(EOFError)
34
34
 
35
35
  BootFailure = Class.new(StandardError)
36
+ ForkFailure = Class.new(StandardError)
36
37
 
37
38
  # :stopdoc:
38
39
 
@@ -196,43 +197,6 @@ module Pitchfork
196
197
  end
197
198
  end
198
199
 
199
- def fork_sibling(&block)
200
- if REFORKING_AVAILABLE
201
- # We double fork so that the new worker is re-attached back
202
- # to the master.
203
- # This requires either PR_SET_CHILD_SUBREAPER which is exclusive to Linux 3.4
204
- # or the master to be PID 1.
205
- if middle_pid = FORK_LOCK.synchronize { Process.fork } # parent
206
- # We need to wait(2) so that the middle process doesn't end up a zombie.
207
- # The process only call fork again an exit so it should be pretty fast.
208
- # However it might need to execute some `Process._fork` or `at_exit` callbacks,
209
- # so it case it takes more than 5 seconds to exit, we kill it with SIGBUS
210
- # to produce a crash report, as this is indicative of a nasty bug.
211
- process_wait_with_timeout(middle_pid, 5, :BUS)
212
- else # first child
213
- Process.setproctitle("<pitchfork fork_sibling>")
214
- clean_fork(&block) # detach into a grand child
215
- exit
216
- end
217
- else
218
- clean_fork(&block)
219
- end
220
-
221
- nil # it's tricky to return the PID
222
- end
223
-
224
- def process_wait_with_timeout(pid, timeout, timeout_signal = :KILL)
225
- (timeout * 200).times do
226
- status = Process.wait(pid, Process::WNOHANG)
227
- return status if status
228
- sleep 0.005 # 200 * 5ms => 1s
229
- end
230
-
231
- # The process didn't exit in the allotted time, so we kill it.
232
- Process.kill(timeout_signal, pid)
233
- Process.wait(pid)
234
- end
235
-
236
200
  def time_now(int = false)
237
201
  Process.clock_gettime(Process::CLOCK_MONOTONIC, int ? :second : :float_second)
238
202
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pitchfork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-02 00:00:00.000000000 Z
11
+ date: 2023-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raindrops