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 +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/benchmark/cow_benchmark.rb +1 -1
- data/docs/CONFIGURATION.md +15 -0
- data/docs/SIGNALS.md +4 -4
- data/lib/pitchfork/configurator.rb +15 -1
- data/lib/pitchfork/http_server.rb +53 -22
- data/lib/pitchfork/info.rb +30 -0
- data/lib/pitchfork/shared_memory.rb +71 -0
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +11 -42
- data/lib/pitchfork.rb +8 -4
- metadata +4 -3
- data/lib/pitchfork/preread_input.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3a7c87f91b13b474f5c204a251c380cd091190f3ecfca8ac3107d697e883f12
|
4
|
+
data.tar.gz: '00116558f20d20e4aad0cda6a1866fc7104e3712df27b1668470c52346812258'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/benchmark/cow_benchmark.rb
CHANGED
data/docs/CONFIGURATION.md
CHANGED
@@ -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
|
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
|
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 =>
|
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
|
-
|
48
|
-
|
49
|
-
@callback
|
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
|
-
|
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, :
|
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
|
-
|
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 << :
|
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
|
-
|
340
|
+
when :QUIT, :TERM # graceful shutdown
|
341
|
+
SharedMemory.shutting_down!
|
342
|
+
logger.info "#{message} received, starting graceful shutdown"
|
337
343
|
return StopIteration
|
338
|
-
when :
|
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(:
|
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(:
|
386
|
+
soft_kill_each_child(:TERM)
|
379
387
|
else
|
380
|
-
kill_each_child(:
|
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}
|
495
|
+
logger.error "mold pid=#{worker.pid} timed out, killing"
|
477
496
|
else
|
478
|
-
logger.error "worker=#{worker.nr} pid=#{worker.pid}
|
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(:
|
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(:
|
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
|
data/lib/pitchfork/version.rb
CHANGED
data/lib/pitchfork/worker.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
require
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
96
|
-
|
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
|
-
|
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
|
-
|
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
|
30
|
-
#
|
31
|
-
#
|
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.
|
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-
|
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
|