pitchfork 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|