pitchfork 0.5.0 → 0.6.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: ccd444fbdc89d5a253819e7be03e5d097c5aaf8b061980e15d4ec5326d2c54b5
4
- data.tar.gz: f300bd3135e8d8d9e15f14dfe2780c75521ececeb72b8245fef0380e2ba338f5
3
+ metadata.gz: b3a7c87f91b13b474f5c204a251c380cd091190f3ecfca8ac3107d697e883f12
4
+ data.tar.gz: '00116558f20d20e4aad0cda6a1866fc7104e3712df27b1668470c52346812258'
5
5
  SHA512:
6
- metadata.gz: b054a33d46f4b60c14e44fbac330bc4318a1b52259e89f2e8a1c16a58d8467f02eef981dbe018906a0c7a027c98db75a8efd483619d7ea5e2ece234b30b3846f
7
- data.tar.gz: b12a622275fe638d49be5d9457d11309421119f9628044dec9898231f83425cab6aa6434b9f42372c5f8954b261f7e979e300a41fa75c39172fb8e1832cb0805
6
+ metadata.gz: 2b51ac98fdc2fc2d83a72c6a55c5e26e42996b43bb6e33815e8958c6f5c729f11b17676b9b8bdb098d75b53ebac5bc98114f2bfa864550b3111162824ae6597f
7
+ data.tar.gz: 5bbf64cf105b20930fd6627001abfb610c9f87ad8510b35a7ab1fcb5c5ccd5437eec011eb6253aefdf87f7d7bc353ac2abbaeb30ac6afad91ddd48fbe81f475c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.6.0
4
+
5
+ - Expose `Pitchfork::Info.workers_count` and `.live_workers_count` to be consumed by application health checks.
6
+ - Implement `before_worker_exit` callback.
7
+ - Make each mold and worker a process group leader.
8
+ - Get rid of `Pitchfork::PrereadInput`.
9
+ - Add `Pitchfork.shutting_down?` to allow health check endpoints to fail sooner on graceful shutdowns.
10
+ - Treat `TERM` as graceful shutdown rather than quick shutdown.
11
+ - Implement `after_worker_hard_timeout` callback.
12
+
13
+ # 0.5.0
14
+
3
15
  - Added a soft timeout in addition to the historical Unicorn hard timeout.
4
16
  On soft timeout, the `after_worker_timeout` callback is invoked.
5
17
  - Implement `after_request_complete` callback.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pitchfork (0.5.0)
4
+ pitchfork (0.6.0)
5
5
  rack (>= 2.0)
6
6
  raindrops (~> 0.7)
7
7
 
@@ -13,5 +13,5 @@ sleep 3
13
13
  puts "Memory Usage:"
14
14
  puts Net::HTTP.get(URI(app_url))
15
15
 
16
- Process.kill("TERM", pid)
16
+ Process.kill("INT", pid)
17
17
  Process.wait
@@ -336,6 +336,21 @@ really have to, make sure to configure the `cleanup` timeout so that the
336
336
  callback has time to complete before the "hard" timeout triggers.
337
337
  By default the cleanup timeout is 2 seconds.
338
338
 
339
+ ### `after_worker_hard_timeout`
340
+
341
+ Called in the master process when a worker hard timeout is elapsed:
342
+
343
+ ```ruby
344
+ after_worker_timeout do |server, worker|
345
+ $stderr.puts "Worker hard timeout, pid=#{worker.pid}"
346
+ end
347
+ ```
348
+
349
+ Once the callback complete, the worker will be signaled with `SIGKILL`.
350
+
351
+ This callback being called in an indication that something is preventing the
352
+ soft timeout from working.
353
+
339
354
  ### `after_worker_exit`
340
355
 
341
356
  Called in the master process after a worker exits.
data/docs/SIGNALS.md CHANGED
@@ -6,9 +6,9 @@ processes are documented here as well.
6
6
 
7
7
  ### Master Process
8
8
 
9
- * `INT/TERM` - quick shutdown, kills all workers immediately
9
+ * `INT` - quick shutdown, kills all workers immediately
10
10
 
11
- * `QUIT` - graceful shutdown, waits for workers to finish their
11
+ * `QUIT/TERM` - graceful shutdown, waits for workers to finish their
12
12
  current request before finishing.
13
13
 
14
14
  * `USR2` - trigger a manual refork. A worker is promoted as
@@ -29,10 +29,10 @@ Sending signals directly to the worker processes should not normally be
29
29
  needed. If the master process is running, any exited worker will be
30
30
  automatically respawned.
31
31
 
32
- * `INT/TERM` - Quick shutdown, immediately exit.
32
+ * `INT` - Quick shutdown, immediately exit.
33
33
  The master process will respawn a worker to replace this one.
34
34
  Immediate shutdown is still triggered using kill(2) and not the
35
35
  internal pipe as of unicorn 4.8
36
36
 
37
- * `QUIT` - Gracefully exit after finishing the current request.
37
+ * `QUIT/TERM` - Gracefully exit after finishing the current request.
38
38
  The master process will respawn a worker to replace this one.
@@ -26,11 +26,15 @@ module Pitchfork
26
26
  }
27
27
 
28
28
  # Default settings for Pitchfork
29
+ default_logger = Logger.new($stderr)
30
+ default_logger.formatter = Logger::Formatter.new
31
+ default_logger.progname = "[Pitchfork]"
32
+
29
33
  DEFAULTS = {
30
34
  :soft_timeout => 20,
31
35
  :cleanup_timeout => 2,
32
36
  :timeout => 22,
33
- :logger => Logger.new($stderr),
37
+ :logger => default_logger,
34
38
  :worker_processes => 1,
35
39
  :after_worker_fork => lambda { |server, worker|
36
40
  server.logger.info("worker=#{worker.nr} gen=#{worker.generation} pid=#{$$} spawned")
@@ -38,6 +42,7 @@ module Pitchfork
38
42
  :after_mold_fork => lambda { |server, worker|
39
43
  server.logger.info("mold gen=#{worker.generation} pid=#{$$} spawned")
40
44
  },
45
+ :before_worker_exit => nil,
41
46
  :after_worker_exit => lambda { |server, worker, status|
42
47
  m = if worker.nil?
43
48
  "repead unknown process (#{status.inspect})"
@@ -56,6 +61,7 @@ module Pitchfork
56
61
  server.logger.info("worker=#{worker.nr} gen=#{worker.generation} ready")
57
62
  },
58
63
  :after_worker_timeout => nil,
64
+ :after_worker_hard_timeout => nil,
59
65
  :after_request_complete => nil,
60
66
  :early_hints => false,
61
67
  :refork_condition => nil,
@@ -139,6 +145,14 @@ module Pitchfork
139
145
  set_hook(:after_worker_timeout, block_given? ? block : args[0], 3)
140
146
  end
141
147
 
148
+ def after_worker_hard_timeout(*args, &block)
149
+ set_hook(:after_worker_hard_timeout, block_given? ? block : args[0], 2)
150
+ end
151
+
152
+ def before_worker_exit(*args, &block)
153
+ set_hook(:before_worker_exit, block_given? ? block : args[0], 2)
154
+ end
155
+
142
156
  def after_worker_exit(*args, &block)
143
157
  set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
144
158
  end
@@ -2,6 +2,8 @@
2
2
  require 'pitchfork/pitchfork_http'
3
3
  require 'pitchfork/flock'
4
4
  require 'pitchfork/soft_timeout'
5
+ require 'pitchfork/shared_memory'
6
+ require 'pitchfork/info'
5
7
 
6
8
  module Pitchfork
7
9
  # This is the process manager of Pitchfork. This manages worker
@@ -44,14 +46,15 @@ module Pitchfork
44
46
  end
45
47
 
46
48
  def call(original_thread) # :nodoc:
47
- @server.logger.error("worker=#{@worker.nr} pid=#{@worker.pid} timed out, exiting")
48
- if @callback
49
- @callback.call(@server, @worker, Info.new(original_thread, @rack_env))
49
+ begin
50
+ @server.logger.error("worker=#{@worker.nr} pid=#{@worker.pid} timed out, exiting")
51
+ if @callback
52
+ @callback.call(@server, @worker, Info.new(original_thread, @rack_env))
53
+ end
54
+ rescue => error
55
+ Pitchfork.log_error(@server.logger, "after_worker_timeout error", error)
50
56
  end
51
- rescue => error
52
- Pitchfork.log_error(@server.logger, "after_worker_timeout error", error)
53
- ensure
54
- exit
57
+ @server.worker_exit(@worker)
55
58
  end
56
59
 
57
60
  def finished # :nodoc:
@@ -76,8 +79,8 @@ module Pitchfork
76
79
  :listener_opts, :children,
77
80
  :orig_app, :config, :ready_pipe,
78
81
  :default_middleware, :early_hints
79
- attr_writer :after_worker_exit, :after_worker_ready, :after_request_complete, :refork_condition,
80
- :after_worker_timeout
82
+ attr_writer :after_worker_exit, :before_worker_exit, :after_worker_ready, :after_request_complete,
83
+ :refork_condition, :after_worker_timeout, :after_worker_hard_timeout
81
84
 
82
85
  attr_reader :logger
83
86
  include Pitchfork::SocketHelper
@@ -163,7 +166,9 @@ module Pitchfork
163
166
  # list of signals we care about and trap in master.
164
167
  @queue_sigs = [
165
168
  :QUIT, :INT, :TERM, :USR2, :TTIN, :TTOU ]
166
- Worker.preallocate_drops(worker_processes)
169
+
170
+ Info.workers_count = worker_processes
171
+ SharedMemory.preallocate_drops(worker_processes)
167
172
  end
168
173
 
169
174
  # Runs the thing. Returns self so you can run join on it
@@ -312,7 +317,7 @@ module Pitchfork
312
317
  if REFORKING_AVAILABLE && @respawn && @children.molds.empty?
313
318
  logger.info("No mold alive, shutting down")
314
319
  @exit_status = 1
315
- @sig_queue << :QUIT
320
+ @sig_queue << :TERM
316
321
  @respawn = false
317
322
  end
318
323
 
@@ -332,10 +337,12 @@ module Pitchfork
332
337
  end
333
338
 
334
339
  master_sleep(sleep_time) if sleep
335
- when :QUIT # graceful shutdown
336
- logger.info "QUIT received, starting graceful shutdown"
340
+ when :QUIT, :TERM # graceful shutdown
341
+ SharedMemory.shutting_down!
342
+ logger.info "#{message} received, starting graceful shutdown"
337
343
  return StopIteration
338
- when :TERM, :INT # immediate shutdown
344
+ when :INT # immediate shutdown
345
+ SharedMemory.shutting_down!
339
346
  logger.info "#{message} received, starting immediate shutdown"
340
347
  stop(false)
341
348
  return StopIteration
@@ -359,7 +366,7 @@ module Pitchfork
359
366
  logger.info("mold pid=#{new_mold.pid} gen=#{new_mold.generation} ready")
360
367
  old_molds.each do |old_mold|
361
368
  logger.info("Terminating old mold pid=#{old_mold.pid} gen=#{old_mold.generation}")
362
- old_mold.soft_kill(:QUIT)
369
+ old_mold.soft_kill(:TERM)
363
370
  end
364
371
  else
365
372
  logger.error("Unexpected message in sig_queue #{message.inspect}")
@@ -370,14 +377,15 @@ module Pitchfork
370
377
  # Terminates all workers, but does not exit master process
371
378
  def stop(graceful = true)
372
379
  @respawn = false
380
+ SharedMemory.shutting_down!
373
381
  wait_for_pending_workers
374
382
  self.listeners = []
375
383
  limit = Pitchfork.time_now + timeout
376
384
  until @children.workers.empty? || Pitchfork.time_now > limit
377
385
  if graceful
378
- soft_kill_each_child(:QUIT)
386
+ soft_kill_each_child(:TERM)
379
387
  else
380
- kill_each_child(:TERM)
388
+ kill_each_child(:INT)
381
389
  end
382
390
  if monitor_loop(false) == StopIteration
383
391
  return StopIteration
@@ -387,6 +395,17 @@ module Pitchfork
387
395
  @promotion_lock.unlink
388
396
  end
389
397
 
398
+ def worker_exit(worker)
399
+ if @before_worker_exit
400
+ begin
401
+ @before_worker_exit.call(self, worker)
402
+ rescue => error
403
+ Pitchfork.log_error(logger, "before_worker_exit error", error)
404
+ end
405
+ end
406
+ Process.exit
407
+ end
408
+
390
409
  def rewindable_input
391
410
  Pitchfork::HttpParser.input_class.method_defined?(:rewind)
392
411
  end
@@ -473,10 +492,19 @@ module Pitchfork
473
492
  else # worker is out of time
474
493
  next_sleep = 0
475
494
  if worker.mold?
476
- logger.error "mold pid=#{worker.pid} deadline=#{deadline} timed out, killing"
495
+ logger.error "mold pid=#{worker.pid} timed out, killing"
477
496
  else
478
- logger.error "worker=#{worker.nr} pid=#{worker.pid} deadline=#{deadline} timed out, killing"
497
+ logger.error "worker=#{worker.nr} pid=#{worker.pid} timed out, killing"
498
+ end
499
+
500
+ if @after_worker_hard_timeout
501
+ begin
502
+ @after_worker_hard_timeout.call(self, worker)
503
+ rescue => error
504
+ Pitchfork.log_error(@logger, "after_worker_hard_timeout callback", error)
505
+ end
479
506
  end
507
+
480
508
  kill_worker(:KILL, worker.pid) # take no prisoners for hard timeout violations
481
509
  end
482
510
  end
@@ -519,6 +547,7 @@ module Pitchfork
519
547
 
520
548
  after_fork_internal
521
549
  worker_loop(worker)
550
+ worker_exit(worker)
522
551
  end
523
552
 
524
553
  worker
@@ -577,7 +606,7 @@ module Pitchfork
577
606
  def maintain_worker_count
578
607
  (off = @children.workers_count - worker_processes) == 0 and return
579
608
  off < 0 and return spawn_missing_workers
580
- @children.each_worker { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
609
+ @children.each_worker { |w| w.nr >= worker_processes and w.soft_kill(:TERM) }
581
610
  end
582
611
 
583
612
  def restart_outdated_workers
@@ -594,7 +623,7 @@ module Pitchfork
594
623
  outdated_workers = @children.workers.select { |w| !w.exiting? && w.generation < @children.mold.generation }
595
624
  outdated_workers.each do |worker|
596
625
  logger.info("worker=#{worker.nr} pid=#{worker.pid} gen=#{worker.generation} restarting")
597
- worker.soft_kill(:QUIT)
626
+ worker.soft_kill(:TERM)
598
627
  end
599
628
  end
600
629
  end
@@ -704,7 +733,7 @@ module Pitchfork
704
733
  def init_worker_process(worker)
705
734
  worker.reset
706
735
  worker.register_to_master(@control_socket[1])
707
- # we'll re-trap :QUIT later for graceful shutdown iff we accept clients
736
+ # we'll re-trap :QUIT and :TERM later for graceful shutdown iff we accept clients
708
737
  exit_sigs = [ :QUIT, :TERM, :INT ]
709
738
  exit_sigs.each { |sig| trap(sig) { exit!(0) } }
710
739
  exit!(0) if (@sig_queue & exit_sigs)[0]
@@ -722,6 +751,7 @@ module Pitchfork
722
751
  readers = LISTENERS.dup
723
752
  readers << worker
724
753
  trap(:QUIT) { nuke_listeners!(readers) }
754
+ trap(:TERM) { nuke_listeners!(readers) }
725
755
  readers
726
756
  end
727
757
 
@@ -730,6 +760,7 @@ module Pitchfork
730
760
  after_mold_fork.call(self, mold)
731
761
  readers = [mold]
732
762
  trap(:QUIT) { nuke_listeners!(readers) }
763
+ trap(:TERM) { nuke_listeners!(readers) }
733
764
  readers
734
765
  end
735
766
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pitchfork/shared_memory'
4
+
5
+ module Pitchfork
6
+ module Info
7
+ @workers_count = 0
8
+
9
+ class << self
10
+ attr_accessor :workers_count
11
+
12
+ def live_workers_count
13
+ now = Pitchfork.time_now(true)
14
+ (0...workers_count).count do |nr|
15
+ SharedMemory.worker_deadline(nr).value > now
16
+ end
17
+ end
18
+
19
+ # Returns true if the server is shutting down.
20
+ # This can be useful to implement health check endpoints, so they
21
+ # can fail immediately after TERM/QUIT/INT was received by the master
22
+ # process.
23
+ # Otherwise they may succeed while Pitchfork is draining requests causing
24
+ # more requests to be sent.
25
+ def shutting_down?
26
+ SharedMemory.shutting_down?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'raindrops'
4
+
5
+ module Pitchfork
6
+ module SharedMemory
7
+ extend self
8
+
9
+ PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
10
+ CURRENT_GENERATION_OFFSET = 0
11
+ SHUTDOWN_OFFSET = 1
12
+ MOLD_TICK_OFFSET = 2
13
+ WORKER_TICK_OFFSET = 3
14
+
15
+ DROPS = [Raindrops.new(PER_DROP)]
16
+
17
+ def current_generation
18
+ DROPS[0][CURRENT_GENERATION_OFFSET]
19
+ end
20
+
21
+ def current_generation=(value)
22
+ DROPS[0][CURRENT_GENERATION_OFFSET] = value
23
+ end
24
+
25
+ def shutting_down!
26
+ DROPS[0][SHUTDOWN_OFFSET] = 1
27
+ end
28
+
29
+ def shutting_down?
30
+ DROPS[0][SHUTDOWN_OFFSET] > 0
31
+ end
32
+
33
+ class Field
34
+ def initialize(offset)
35
+ @drop = DROPS.fetch(offset / PER_DROP)
36
+ @offset = offset % PER_DROP
37
+ end
38
+
39
+ def value
40
+ @drop[@offset]
41
+ end
42
+
43
+ def value=(value)
44
+ @drop[@offset] = value
45
+ end
46
+ end
47
+
48
+ def mold_deadline
49
+ self[MOLD_TICK_OFFSET]
50
+ end
51
+
52
+ def worker_deadline(worker_nr)
53
+ self[WORKER_TICK_OFFSET + worker_nr]
54
+ end
55
+
56
+ def [](offset)
57
+ Field.new(offset)
58
+ end
59
+
60
+ # Since workers are created from another process, we have to
61
+ # pre-allocate the drops so they are shared between everyone.
62
+ #
63
+ # However this doesn't account for TTIN signals that increase the
64
+ # number of workers, but we should probably remove that feature too.
65
+ def preallocate_drops(workers_count)
66
+ 0.upto(((WORKER_TICK_OFFSET + workers_count) / PER_DROP.to_f).ceil) do |i|
67
+ DROPS[i] ||= Raindrops.new(PER_DROP)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pitchfork
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  module Const
6
6
  UNICORN_VERSION = '6.1.0'
7
7
  end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
- require "raindrops"
2
+ require 'pitchfork/shared_memory'
3
3
 
4
4
  module Pitchfork
5
5
  # This class and its members can be considered a stable interface
@@ -24,7 +24,8 @@ module Pitchfork
24
24
  @exiting = false
25
25
  @requests_count = 0
26
26
  if nr
27
- build_raindrops(nr)
27
+ @deadline_drop = SharedMemory.worker_deadline(nr)
28
+ self.deadline = 0
28
29
  else
29
30
  promoted!
30
31
  end
@@ -47,7 +48,7 @@ module Pitchfork
47
48
  end
48
49
 
49
50
  def outdated?
50
- CURRENT_GENERATION_DROP[0] > @generation
51
+ SharedMemory.current_generation > @generation
51
52
  end
52
53
 
53
54
  def update(message)
@@ -73,7 +74,7 @@ module Pitchfork
73
74
  def finish_promotion(control_socket)
74
75
  message = Message::MoldReady.new(@nr, @pid, generation)
75
76
  control_socket.sendmsg(message)
76
- CURRENT_GENERATION_DROP[0] = @generation
77
+ SharedMemory.current_generation = @generation
77
78
  end
78
79
 
79
80
  def promote(generation)
@@ -92,8 +93,8 @@ module Pitchfork
92
93
  def promoted!
93
94
  @mold = true
94
95
  @nr = nil
95
- @drop_offset = 0
96
- @deadline_drop = MOLD_DROP
96
+ @deadline_drop = SharedMemory.mold_deadline
97
+ self.deadline = 0
97
98
  self
98
99
  end
99
100
 
@@ -173,20 +174,12 @@ module Pitchfork
173
174
 
174
175
  # called in the worker process
175
176
  def deadline=(value) # :nodoc:
176
- if mold?
177
- MOLD_DROP[0] = value
178
- else
179
- @deadline_drop[@drop_offset] = value
180
- end
177
+ @deadline_drop.value = value
181
178
  end
182
179
 
183
180
  # called in the master process
184
181
  def deadline # :nodoc:
185
- if mold?
186
- MOLD_DROP[0]
187
- else
188
- @deadline_drop[@drop_offset]
189
- end
182
+ @deadline_drop.value
190
183
  end
191
184
 
192
185
  def reset
@@ -199,6 +192,7 @@ module Pitchfork
199
192
 
200
193
  # called in both the master (reaping worker) and worker (SIGQUIT handler)
201
194
  def close # :nodoc:
195
+ self.deadline = 0
202
196
  @master.close if @master
203
197
  @to_io.close if @to_io
204
198
  end
@@ -225,35 +219,10 @@ module Pitchfork
225
219
  else
226
220
  success = true
227
221
  end
228
- rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED
222
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ENOTCONN
229
223
  # worker will be reaped soon
230
224
  end
231
225
  success
232
226
  end
233
-
234
- MOLD_DROP = Raindrops.new(1)
235
- CURRENT_GENERATION_DROP = Raindrops.new(1)
236
- PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
237
- TICK_DROPS = []
238
-
239
- class << self
240
- # Since workers are created from another process, we have to
241
- # pre-allocate the drops so they are shared between everyone.
242
- #
243
- # However this doesn't account for TTIN signals that increase the
244
- # number of workers, but we should probably remove that feature too.
245
- def preallocate_drops(workers_count)
246
- 0.upto(workers_count / PER_DROP) do |i|
247
- TICK_DROPS[i] = Raindrops.new(PER_DROP)
248
- end
249
- end
250
- end
251
-
252
- def build_raindrops(drop_nr)
253
- drop_index = drop_nr / PER_DROP
254
- @drop_offset = drop_nr % PER_DROP
255
- @deadline_drop = TICK_DROPS[drop_index] ||= Raindrops.new(PER_DROP)
256
- @deadline_drop[@drop_offset] = 0
257
- end
258
227
  end
259
228
  end
data/lib/pitchfork.rb CHANGED
@@ -26,9 +26,10 @@ module Pitchfork
26
26
  # application dispatch. This is always raised with an empty backtrace
27
27
  # since there is nothing in the application stack that is responsible
28
28
  # for client shutdowns/disconnects. This exception is visible to Rack
29
- # applications unless PrereadInput middleware is loaded. This
30
- # is a subclass of the standard EOFError class and applications should
31
- # not rescue it explicitly, but rescue EOFError instead.
29
+ # applications. This is a subclass of the standard EOFError class and
30
+ # applications should not rescue it explicitly, but rescue EOFError instead.
31
+ # Such an error is likely an indication that the reverse proxy in front
32
+ # of Pitchfork isn't properly buffering requests.
32
33
  ClientShutdown = Class.new(EOFError)
33
34
 
34
35
  BootFailure = Class.new(StandardError)
@@ -120,8 +121,11 @@ module Pitchfork
120
121
  end
121
122
  end
122
123
 
123
- def self.clean_fork(&block)
124
+ def self.clean_fork(setpgid: true, &block)
124
125
  if pid = Process.fork
126
+ if setpgid
127
+ Process.setpgid(pid, pid) # Make into a group leader
128
+ end
125
129
  return pid
126
130
  end
127
131
 
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.5.0
4
+ version: 0.6.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-06-21 00:00:00.000000000 Z
11
+ date: 2023-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raindrops
@@ -101,12 +101,13 @@ files:
101
101
  - lib/pitchfork/http_parser.rb
102
102
  - lib/pitchfork/http_response.rb
103
103
  - lib/pitchfork/http_server.rb
104
+ - lib/pitchfork/info.rb
104
105
  - lib/pitchfork/launcher.rb
105
106
  - lib/pitchfork/mem_info.rb
106
107
  - lib/pitchfork/message.rb
107
- - lib/pitchfork/preread_input.rb
108
108
  - lib/pitchfork/refork_condition.rb
109
109
  - lib/pitchfork/select_waiter.rb
110
+ - lib/pitchfork/shared_memory.rb
110
111
  - lib/pitchfork/socket_helper.rb
111
112
  - lib/pitchfork/soft_timeout.rb
112
113
  - lib/pitchfork/stream_input.rb
@@ -1,33 +0,0 @@
1
- # -*- encoding: binary -*-
2
-
3
- module Pitchfork
4
- # This middleware is used to ensure input is buffered to memory
5
- # or disk (depending on size) before the application is dispatched
6
- # by entirely consuming it (from TeeInput) beforehand.
7
- #
8
- # Usage (in config.ru):
9
- #
10
- # require 'pitchfork/preread_input'
11
- # if defined?(Pitchfork)
12
- # use Pitchfork::PrereadInput
13
- # end
14
- # run YourApp.new
15
- class PrereadInput
16
-
17
- # :stopdoc:
18
- def initialize(app)
19
- @app = app
20
- end
21
-
22
- def call(env)
23
- buf = ""
24
- input = env["rack.input"]
25
- if input.respond_to?(:rewind)
26
- true while input.read(16384, buf)
27
- input.rewind
28
- end
29
- @app.call(env)
30
- end
31
- # :startdoc:
32
- end
33
- end