pitchfork 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +7 -3
- data/.ruby-version +1 -0
- data/CHANGELOG.md +12 -0
- data/Dockerfile +1 -1
- data/Gemfile.lock +3 -5
- data/benchmark/README.md +1 -1
- data/benchmark/cow_benchmark.rb +1 -0
- data/docs/CONFIGURATION.md +39 -1
- data/docs/MIGRATING_FROM_UNICORN.md +34 -0
- data/docs/WHY_MIGRATE.md +5 -0
- data/examples/constant_caches.ru +1 -0
- data/examples/echo.ru +1 -0
- data/examples/hello.ru +1 -0
- data/examples/pitchfork.conf.minimal.rb +1 -0
- data/examples/pitchfork.conf.rb +1 -0
- data/examples/pitchfork.conf.service.rb +27 -0
- data/exe/pitchfork +5 -4
- data/ext/pitchfork_http/epollexclusive.h +2 -2
- data/ext/pitchfork_http/extconf.rb +3 -0
- data/ext/pitchfork_http/memory_page.c +223 -0
- data/ext/pitchfork_http/pitchfork_http.c +213 -211
- data/ext/pitchfork_http/pitchfork_http.rl +3 -1
- data/lib/pitchfork/children.rb +21 -15
- data/lib/pitchfork/configurator.rb +13 -0
- data/lib/pitchfork/const.rb +1 -0
- data/lib/pitchfork/flock.rb +1 -0
- data/lib/pitchfork/http_parser.rb +18 -72
- data/lib/pitchfork/http_response.rb +4 -3
- data/lib/pitchfork/http_server.rb +181 -62
- data/lib/pitchfork/launcher.rb +1 -0
- data/lib/pitchfork/message.rb +11 -6
- data/lib/pitchfork/select_waiter.rb +1 -0
- data/lib/pitchfork/shared_memory.rb +16 -14
- data/lib/pitchfork/socket_helper.rb +2 -1
- data/lib/pitchfork/stream_input.rb +6 -5
- data/lib/pitchfork/tee_input.rb +3 -2
- data/lib/pitchfork/tmpio.rb +1 -0
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +44 -15
- data/lib/pitchfork.rb +1 -20
- data/pitchfork.gemspec +0 -1
- metadata +7 -18
- data/lib/pitchfork/app/old_rails/static.rb +0 -59
@@ -1,4 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require 'pitchfork/pitchfork_http'
|
3
5
|
require 'pitchfork/flock'
|
4
6
|
require 'pitchfork/soft_timeout'
|
@@ -75,7 +77,7 @@ module Pitchfork
|
|
75
77
|
|
76
78
|
# :stopdoc:
|
77
79
|
attr_accessor :app, :timeout, :timeout_signal, :soft_timeout, :cleanup_timeout, :spawn_timeout, :worker_processes,
|
78
|
-
:before_fork, :after_worker_fork, :after_mold_fork,
|
80
|
+
:before_fork, :after_worker_fork, :after_mold_fork, :before_service_worker_ready, :before_service_worker_exit,
|
79
81
|
:listener_opts, :children,
|
80
82
|
:orig_app, :config, :ready_pipe,
|
81
83
|
:default_middleware, :early_hints
|
@@ -91,33 +93,7 @@ module Pitchfork
|
|
91
93
|
# in new projects
|
92
94
|
LISTENERS = []
|
93
95
|
|
94
|
-
NOOP = '.'
|
95
|
-
|
96
|
-
# :startdoc:
|
97
|
-
# This Hash is considered a stable interface and changing its contents
|
98
|
-
# will allow you to switch between different installations of Pitchfork
|
99
|
-
# or even different installations of the same applications without
|
100
|
-
# downtime. Keys of this constant Hash are described as follows:
|
101
|
-
#
|
102
|
-
# * 0 - the path to the pitchfork executable
|
103
|
-
# * :argv - a deep copy of the ARGV array the executable originally saw
|
104
|
-
# * :cwd - the working directory of the application, this is where
|
105
|
-
# you originally started Pitchfork.
|
106
|
-
# TODO: Can we get rid of this?
|
107
|
-
START_CTX = {
|
108
|
-
:argv => ARGV.map(&:dup),
|
109
|
-
0 => $0.dup,
|
110
|
-
}
|
111
|
-
# We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
|
112
|
-
# and like systems
|
113
|
-
START_CTX[:cwd] = begin
|
114
|
-
a = File.stat(pwd = ENV['PWD'])
|
115
|
-
b = File.stat(Dir.pwd)
|
116
|
-
a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
|
117
|
-
rescue
|
118
|
-
Dir.pwd
|
119
|
-
end
|
120
|
-
# :stopdoc:
|
96
|
+
NOOP = '.'
|
121
97
|
|
122
98
|
# Creates a working server on host:port (strange things happen if
|
123
99
|
# port isn't a Number). Use HttpServer::run to start the server and
|
@@ -138,7 +114,7 @@ module Pitchfork
|
|
138
114
|
self.config = Pitchfork::Configurator.new(options)
|
139
115
|
self.listener_opts = {}
|
140
116
|
|
141
|
-
proc_name role: 'monitor', status:
|
117
|
+
proc_name role: 'monitor', status: ARGV.join(' ')
|
142
118
|
|
143
119
|
# We use @control_socket differently in the master and worker processes:
|
144
120
|
#
|
@@ -171,7 +147,7 @@ module Pitchfork
|
|
171
147
|
:QUIT, :INT, :TERM, :USR2, :TTIN, :TTOU ]
|
172
148
|
|
173
149
|
Info.workers_count = worker_processes
|
174
|
-
SharedMemory.
|
150
|
+
SharedMemory.preallocate_pages(worker_processes)
|
175
151
|
end
|
176
152
|
|
177
153
|
# Runs the thing. Returns self so you can run join on it
|
@@ -294,7 +270,7 @@ module Pitchfork
|
|
294
270
|
def join
|
295
271
|
@respawn = true
|
296
272
|
|
297
|
-
proc_name role: 'monitor', status:
|
273
|
+
proc_name role: 'monitor', status: ARGV.join(' ')
|
298
274
|
|
299
275
|
logger.info "master process ready" # test_exec.rb relies on this message
|
300
276
|
if @ready_pipe
|
@@ -372,6 +348,9 @@ module Pitchfork
|
|
372
348
|
when Message::MoldSpawned
|
373
349
|
new_mold = @children.update(message)
|
374
350
|
logger.info("mold pid=#{new_mold.pid} gen=#{new_mold.generation} spawned")
|
351
|
+
when Message::ServiceSpawned
|
352
|
+
new_service = @children.update(message)
|
353
|
+
logger.info("service pid=#{new_service.pid} gen=#{new_service.generation} spawned")
|
375
354
|
when Message::MoldReady
|
376
355
|
old_molds = @children.molds
|
377
356
|
new_mold = @children.update(message)
|
@@ -427,6 +406,20 @@ module Pitchfork
|
|
427
406
|
Process.exit
|
428
407
|
end
|
429
408
|
|
409
|
+
def service_exit(service)
|
410
|
+
logger.info "service pid=#{service.pid} gen=#{service.generation} exiting"
|
411
|
+
proc_name status: "exiting"
|
412
|
+
|
413
|
+
if @before_service_worker_exit
|
414
|
+
begin
|
415
|
+
@before_service_worker_exit.call(self, service)
|
416
|
+
rescue => error
|
417
|
+
Pitchfork.log_error(logger, "before_service_worker_exit error", error)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
Process.exit
|
421
|
+
end
|
422
|
+
|
430
423
|
def rewindable_input
|
431
424
|
Pitchfork::HttpParser.input_class.method_defined?(:rewind)
|
432
425
|
end
|
@@ -512,6 +505,7 @@ module Pitchfork
|
|
512
505
|
next
|
513
506
|
else # worker is out of time
|
514
507
|
next_sleep = 0
|
508
|
+
child.deadline = now + 1
|
515
509
|
hard_timeout(child)
|
516
510
|
end
|
517
511
|
end
|
@@ -587,6 +581,69 @@ module Pitchfork
|
|
587
581
|
worker
|
588
582
|
end
|
589
583
|
|
584
|
+
def service_loop(service)
|
585
|
+
readers = init_service_process(service)
|
586
|
+
waiter = prep_readers(readers)
|
587
|
+
|
588
|
+
ready = readers.dup
|
589
|
+
|
590
|
+
if @before_service_worker_ready
|
591
|
+
begin
|
592
|
+
@before_service_worker_ready.call(self, service)
|
593
|
+
rescue => error
|
594
|
+
Pitchfork.log_error(logger, "before_service_worker_ready", error)
|
595
|
+
Process.exit(1)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
proc_name status: "ready"
|
600
|
+
|
601
|
+
while readers[0]
|
602
|
+
begin
|
603
|
+
service.update_deadline(@timeout)
|
604
|
+
while sock = ready.shift
|
605
|
+
# Pitchfork::Worker#accept_nonblock is not like accept(2) at all,
|
606
|
+
# but that will return false
|
607
|
+
client = sock.accept_nonblock(exception: false)
|
608
|
+
|
609
|
+
case client
|
610
|
+
when false, :wait_readable
|
611
|
+
# no message, keep looping
|
612
|
+
end
|
613
|
+
|
614
|
+
service.update_deadline(@timeout)
|
615
|
+
end
|
616
|
+
|
617
|
+
# timeout so we can update .deadline and keep parent from SIGKILL-ing us
|
618
|
+
service.update_deadline(@timeout)
|
619
|
+
|
620
|
+
|
621
|
+
waiter.get_readers(ready, readers, @timeout * 500) # to milliseconds, but halved
|
622
|
+
rescue => e
|
623
|
+
Pitchfork.log_error(@logger, "listen loop error", e) if readers[0]
|
624
|
+
end
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
def spawn_service(service, detach:)
|
629
|
+
logger.info("service gen=#{service.generation} spawning...")
|
630
|
+
|
631
|
+
# We set the deadline before spawning the child so that if for some
|
632
|
+
# reason it gets stuck before reaching the worker loop,
|
633
|
+
# the monitor process will kill it.
|
634
|
+
service.update_deadline(@spawn_timeout)
|
635
|
+
@before_fork&.call(self)
|
636
|
+
fork_sibling("spawn_service") do
|
637
|
+
service.pid = Process.pid
|
638
|
+
|
639
|
+
after_fork_internal
|
640
|
+
service_loop(service)
|
641
|
+
service_exit(service)
|
642
|
+
end
|
643
|
+
|
644
|
+
service
|
645
|
+
end
|
646
|
+
|
590
647
|
def spawn_initial_mold
|
591
648
|
mold = Worker.new(nil)
|
592
649
|
mold.create_socketpair!
|
@@ -603,6 +660,21 @@ module Pitchfork
|
|
603
660
|
end
|
604
661
|
|
605
662
|
def spawn_missing_workers
|
663
|
+
if @before_service_worker_ready && !@children.service
|
664
|
+
service = Pitchfork::Service.new
|
665
|
+
if REFORKING_AVAILABLE
|
666
|
+
service.generation = @children.mold&.generation || 0
|
667
|
+
|
668
|
+
unless @children.mold&.spawn_service(service)
|
669
|
+
@logger.error("Failed to send a spawn_service command")
|
670
|
+
end
|
671
|
+
else
|
672
|
+
spawn_service(service, detach: false)
|
673
|
+
end
|
674
|
+
|
675
|
+
@children.register_service(service)
|
676
|
+
end
|
677
|
+
|
606
678
|
worker_nr = -1
|
607
679
|
until (worker_nr += 1) == @worker_processes
|
608
680
|
if @children.nr_alive?(worker_nr)
|
@@ -640,9 +712,18 @@ module Pitchfork
|
|
640
712
|
end
|
641
713
|
|
642
714
|
def maintain_worker_count
|
643
|
-
|
644
|
-
off
|
645
|
-
|
715
|
+
off = @children.workers_count - worker_processes
|
716
|
+
off -= 1 if @before_service_worker_ready && !@children.service
|
717
|
+
|
718
|
+
if off < 0
|
719
|
+
spawn_missing_workers
|
720
|
+
elsif off > 0
|
721
|
+
@children.each_worker do |worker|
|
722
|
+
if worker.nr >= worker_processes
|
723
|
+
worker.soft_kill(:TERM)
|
724
|
+
end
|
725
|
+
end
|
726
|
+
end
|
646
727
|
end
|
647
728
|
|
648
729
|
def restart_outdated_workers
|
@@ -655,6 +736,16 @@ module Pitchfork
|
|
655
736
|
max_pending_workers = (worker_processes * 0.1).ceil
|
656
737
|
workers_to_restart = max_pending_workers - @children.restarting_workers_count
|
657
738
|
|
739
|
+
if service = @children.service
|
740
|
+
if service.outdated?
|
741
|
+
if service.soft_kill(:TERM)
|
742
|
+
logger.info("Sent SIGTERM to service pid=#{service.pid} gen=#{service.generation}")
|
743
|
+
else
|
744
|
+
logger.info("Failed to send SIGTERM to service pid=#{service.pid} gen=#{service.generation}")
|
745
|
+
end
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
658
749
|
if workers_to_restart > 0
|
659
750
|
outdated_workers = @children.workers.select { |w| !w.exiting? && w.generation < @children.mold.generation }
|
660
751
|
outdated_workers.each do |worker|
|
@@ -696,21 +787,17 @@ module Pitchfork
|
|
696
787
|
|
697
788
|
def e103_response_write(client, headers)
|
698
789
|
rss = @request.response_start_sent
|
699
|
-
buf = rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n"
|
790
|
+
buf = (rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n").b
|
700
791
|
headers.each { |key, value| append_header(buf, key, value) }
|
701
|
-
buf << (rss ? "\r\nHTTP/1.1 "
|
792
|
+
buf << (rss ? "\r\nHTTP/1.1 " : "\r\n")
|
702
793
|
client.write(buf)
|
703
794
|
end
|
704
795
|
|
705
796
|
def e100_response_write(client, env)
|
706
|
-
# We use String#freeze to avoid allocations under Ruby 2.1+
|
707
|
-
# Not many users hit this code path, so it's better to reduce the
|
708
|
-
# constant table sizes even for Ruby 2.0 users who'll hit extra
|
709
|
-
# allocations here.
|
710
797
|
client.write(@request.response_start_sent ?
|
711
|
-
"100 Continue\r\n\r\nHTTP/1.1 "
|
712
|
-
"HTTP/1.1 100 Continue\r\n\r\n"
|
713
|
-
env.delete('HTTP_EXPECT'
|
798
|
+
"100 Continue\r\n\r\nHTTP/1.1 " :
|
799
|
+
"HTTP/1.1 100 Continue\r\n\r\n")
|
800
|
+
env.delete('HTTP_EXPECT')
|
714
801
|
end
|
715
802
|
|
716
803
|
# once a client is accepted, it is processed in its entirety here
|
@@ -720,7 +807,11 @@ module Pitchfork
|
|
720
807
|
@request = Pitchfork::HttpParser.new
|
721
808
|
env = @request.read(client)
|
722
809
|
|
723
|
-
|
810
|
+
status = "requests: #{worker.requests_count}, processing: #{env["PATH_INFO"]}"
|
811
|
+
if request_id = env["HTTP_X_REQUEST_ID"]
|
812
|
+
status += ", request_id: #{request_id}"
|
813
|
+
end
|
814
|
+
proc_name status: status
|
724
815
|
|
725
816
|
env["pitchfork.worker"] = worker
|
726
817
|
timeout_handler.rack_env = env
|
@@ -814,6 +905,18 @@ module Pitchfork
|
|
814
905
|
readers
|
815
906
|
end
|
816
907
|
|
908
|
+
def init_service_process(service)
|
909
|
+
proc_name role: "(gen:#{service.generation}) mold", status: "init"
|
910
|
+
LISTENERS.each(&:close) # Don't appear as listening to incoming requests
|
911
|
+
service.register_to_master(@control_socket[1])
|
912
|
+
readers = [service]
|
913
|
+
trap(:QUIT) { nuke_listeners!(readers) }
|
914
|
+
trap(:TERM) { nuke_listeners!(readers) }
|
915
|
+
trap(:INT) { nuke_listeners!(readers); exit!(0) }
|
916
|
+
proc_name role: "(gen:#{service.generation}) service", status: "ready"
|
917
|
+
readers
|
918
|
+
end
|
919
|
+
|
817
920
|
def init_mold_process(mold)
|
818
921
|
proc_name role: "(gen:#{mold.generation}) mold", status: "init"
|
819
922
|
after_mold_fork.call(self, mold)
|
@@ -827,7 +930,7 @@ module Pitchfork
|
|
827
930
|
|
828
931
|
if Pitchfork.const_defined?(:Waiter)
|
829
932
|
def prep_readers(readers)
|
830
|
-
Pitchfork::Waiter.prep_readers(readers)
|
933
|
+
Pitchfork::Info.keep_io(Pitchfork::Waiter.prep_readers(readers))
|
831
934
|
end
|
832
935
|
else
|
833
936
|
require_relative 'select_waiter'
|
@@ -855,24 +958,25 @@ module Pitchfork
|
|
855
958
|
# Pitchfork::Worker#accept_nonblock is not like accept(2) at all,
|
856
959
|
# but that will return false
|
857
960
|
client = sock.accept_nonblock(exception: false)
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
logger.error("worker=#{worker.nr} gen=#{worker.generation} is no longer fork safe, can't refork")
|
866
|
-
end
|
867
|
-
when Message
|
868
|
-
worker.update(client)
|
961
|
+
|
962
|
+
case client
|
963
|
+
when false, :wait_readable
|
964
|
+
# no message, keep looping
|
965
|
+
when Message::PromoteWorker
|
966
|
+
if Info.fork_safe?
|
967
|
+
spawn_mold(worker)
|
869
968
|
else
|
870
|
-
|
871
|
-
@after_request_complete&.call(self, worker, request_env)
|
872
|
-
worker.increment_requests_count
|
969
|
+
logger.error("worker=#{worker.nr} gen=#{worker.generation} is no longer fork safe, can't refork")
|
873
970
|
end
|
874
|
-
|
971
|
+
when Message
|
972
|
+
worker.update(client)
|
973
|
+
else
|
974
|
+
request_env = process_client(client, worker, prepare_timeout(worker))
|
975
|
+
worker.increment_requests_count
|
976
|
+
@after_request_complete&.call(self, worker, request_env)
|
875
977
|
end
|
978
|
+
|
979
|
+
worker.update_deadline(@timeout)
|
876
980
|
end
|
877
981
|
|
878
982
|
# timeout so we can update .deadline and keep parent from SIGKILL-ing us
|
@@ -955,6 +1059,22 @@ module Pitchfork
|
|
955
1059
|
rescue => error
|
956
1060
|
raise BootFailure, error.message
|
957
1061
|
end
|
1062
|
+
when Message::SpawnService
|
1063
|
+
retries = 1
|
1064
|
+
begin
|
1065
|
+
spawn_service(Service.new(generation: mold.generation), detach: true)
|
1066
|
+
rescue ForkFailure
|
1067
|
+
if retries > 0
|
1068
|
+
@logger.fatal("mold pid=#{mold.pid} gen=#{mold.generation} Failed to spawn a service. Retrying.")
|
1069
|
+
retries -= 1
|
1070
|
+
retry
|
1071
|
+
else
|
1072
|
+
@logger.fatal("mold pid=#{mold.pid} gen=#{mold.generation} Failed to spawn a service twice in a row. Corrupted mold process?")
|
1073
|
+
Process.exit(1)
|
1074
|
+
end
|
1075
|
+
rescue => error
|
1076
|
+
raise BootFailure, error.message
|
1077
|
+
end
|
958
1078
|
else
|
959
1079
|
logger.error("Unexpected mold message #{message.inspect}")
|
960
1080
|
end
|
@@ -1001,7 +1121,7 @@ module Pitchfork
|
|
1001
1121
|
@proctitle_role = role if role
|
1002
1122
|
@proctitle_status = status if status
|
1003
1123
|
|
1004
|
-
Process.setproctitle("#{File.basename(
|
1124
|
+
Process.setproctitle("#{File.basename($PROGRAM_NAME)} #{@proctitle_role} - #{@proctitle_status}")
|
1005
1125
|
end
|
1006
1126
|
|
1007
1127
|
def bind_listeners!
|
@@ -1009,7 +1129,6 @@ module Pitchfork
|
|
1009
1129
|
if listeners.empty?
|
1010
1130
|
listeners << Pitchfork::Const::DEFAULT_LISTEN
|
1011
1131
|
@init_listeners << Pitchfork::Const::DEFAULT_LISTEN
|
1012
|
-
START_CTX[:argv] << "-l#{Pitchfork::Const::DEFAULT_LISTEN}"
|
1013
1132
|
end
|
1014
1133
|
listeners.each { |addr| listen(addr) }
|
1015
1134
|
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
data/lib/pitchfork/launcher.rb
CHANGED
data/lib/pitchfork/message.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# :stopdoc:
|
4
5
|
module Pitchfork
|
@@ -121,12 +122,16 @@ module Pitchfork
|
|
121
122
|
|
122
123
|
Message = Class.new(Struct)
|
123
124
|
class Message
|
124
|
-
SpawnWorker =
|
125
|
-
WorkerSpawned =
|
126
|
-
PromoteWorker =
|
127
|
-
MoldSpawned = Message.new(:nr, :pid, :generation, :pipe)
|
128
|
-
MoldReady = Message.new(:nr, :pid, :generation)
|
125
|
+
SpawnWorker = new(:nr)
|
126
|
+
WorkerSpawned = new(:nr, :pid, :generation, :pipe)
|
127
|
+
PromoteWorker = new(:generation)
|
129
128
|
|
130
|
-
|
129
|
+
MoldSpawned = new(:nr, :pid, :generation, :pipe)
|
130
|
+
MoldReady = new(:nr, :pid, :generation)
|
131
|
+
|
132
|
+
SpawnService = new(:_) # Struct.new requires at least 1 member on Ruby < 3.3
|
133
|
+
ServiceSpawned = new(:pid, :generation, :pipe)
|
134
|
+
|
135
|
+
SoftKill = new(:signum)
|
131
136
|
end
|
132
137
|
end
|
@@ -1,40 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'raindrops'
|
4
|
-
|
5
3
|
module Pitchfork
|
6
4
|
module SharedMemory
|
7
5
|
extend self
|
8
6
|
|
9
|
-
PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
|
10
7
|
CURRENT_GENERATION_OFFSET = 0
|
11
8
|
SHUTDOWN_OFFSET = 1
|
12
9
|
MOLD_TICK_OFFSET = 2
|
13
10
|
MOLD_PROMOTION_TICK_OFFSET = 3
|
14
|
-
|
11
|
+
SERVICE_TICK_OFFSET = 4
|
12
|
+
WORKER_TICK_OFFSET = 5
|
15
13
|
|
16
|
-
|
14
|
+
PAGES = [MemoryPage.new(MemoryPage::SLOTS)]
|
17
15
|
|
18
16
|
def current_generation
|
19
|
-
|
17
|
+
PAGES[0][CURRENT_GENERATION_OFFSET]
|
20
18
|
end
|
21
19
|
|
22
20
|
def current_generation=(value)
|
23
|
-
|
21
|
+
PAGES[0][CURRENT_GENERATION_OFFSET] = value
|
24
22
|
end
|
25
23
|
|
26
24
|
def shutting_down!
|
27
|
-
|
25
|
+
PAGES[0][SHUTDOWN_OFFSET] = 1
|
28
26
|
end
|
29
27
|
|
30
28
|
def shutting_down?
|
31
|
-
|
29
|
+
PAGES[0][SHUTDOWN_OFFSET] > 0
|
32
30
|
end
|
33
31
|
|
34
32
|
class Field
|
35
33
|
def initialize(offset)
|
36
|
-
@drop =
|
37
|
-
@offset = offset %
|
34
|
+
@drop = PAGES.fetch(offset / MemoryPage::SLOTS)
|
35
|
+
@offset = offset % MemoryPage::SLOTS
|
38
36
|
end
|
39
37
|
|
40
38
|
def value
|
@@ -54,6 +52,10 @@ module Pitchfork
|
|
54
52
|
self[MOLD_PROMOTION_TICK_OFFSET]
|
55
53
|
end
|
56
54
|
|
55
|
+
def service_deadline
|
56
|
+
self[SERVICE_TICK_OFFSET]
|
57
|
+
end
|
58
|
+
|
57
59
|
def worker_deadline(worker_nr)
|
58
60
|
self[WORKER_TICK_OFFSET + worker_nr]
|
59
61
|
end
|
@@ -67,9 +69,9 @@ module Pitchfork
|
|
67
69
|
#
|
68
70
|
# However this doesn't account for TTIN signals that increase the
|
69
71
|
# number of workers, but we should probably remove that feature too.
|
70
|
-
def
|
71
|
-
0.upto(((WORKER_TICK_OFFSET + workers_count) /
|
72
|
-
|
72
|
+
def preallocate_pages(workers_count)
|
73
|
+
0.upto(((WORKER_TICK_OFFSET + workers_count) / MemoryPage::SLOTS.to_f).ceil) do |i|
|
74
|
+
PAGES[i] ||= MemoryPage.new(MemoryPage::SLOTS)
|
73
75
|
end
|
74
76
|
end
|
75
77
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
# :enddoc:
|
3
4
|
require 'socket'
|
4
5
|
|
@@ -71,7 +72,7 @@ module Pitchfork
|
|
71
72
|
rescue => e
|
72
73
|
logger.error("#{sock_name(sock)} " \
|
73
74
|
"failed to set accept_filter=#{name} (#{e.inspect})")
|
74
|
-
logger.error("perhaps accf_http(9) needs to be loaded"
|
75
|
+
logger.error("perhaps accf_http(9) needs to be loaded")
|
75
76
|
end if arg != got
|
76
77
|
end
|
77
78
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Pitchfork
|
4
5
|
# When processing uploads, pitchfork may expose a StreamInput object under
|
@@ -17,7 +18,7 @@ module Pitchfork
|
|
17
18
|
@socket = socket
|
18
19
|
@parser = request
|
19
20
|
@buf = request.buf
|
20
|
-
@rbuf = ''
|
21
|
+
@rbuf = +''
|
21
22
|
@bytes_read = 0
|
22
23
|
filter_body(@rbuf, @buf) unless @buf.empty?
|
23
24
|
end
|
@@ -41,7 +42,7 @@ module Pitchfork
|
|
41
42
|
# ios.read(length [, buffer]) will return immediately if there is
|
42
43
|
# any data and only block when nothing is available (providing
|
43
44
|
# IO#readpartial semantics).
|
44
|
-
def read(length = nil, rv = '')
|
45
|
+
def read(length = nil, rv = ''.b)
|
45
46
|
if length
|
46
47
|
if length <= @rbuf.size
|
47
48
|
length < 0 and raise ArgumentError, "negative length #{length} given"
|
@@ -79,16 +80,16 @@ module Pitchfork
|
|
79
80
|
# unlike IO#gets.
|
80
81
|
def gets(sep = $/)
|
81
82
|
if sep.nil?
|
82
|
-
read_all(rv = '')
|
83
|
+
read_all(rv = ''.b)
|
83
84
|
return rv.empty? ? nil : rv
|
84
85
|
end
|
85
86
|
re = /\A(.*?#{Regexp.escape(sep)})/
|
86
87
|
|
87
88
|
begin
|
88
|
-
@rbuf.sub!(re, '') and return $1
|
89
|
+
@rbuf.sub!(re, ''.b) and return $1
|
89
90
|
return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
|
90
91
|
@socket.readpartial(@@io_chunk_size, @buf) or eof!
|
91
|
-
filter_body(once = '', @buf)
|
92
|
+
filter_body(once = ''.b, @buf)
|
92
93
|
@rbuf << once
|
93
94
|
end while true
|
94
95
|
end
|
data/lib/pitchfork/tee_input.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Pitchfork
|
4
5
|
# Acts like tee(1) on an input input to provide a input-like stream
|
@@ -42,7 +43,7 @@ module Pitchfork
|
|
42
43
|
@len = request.content_length
|
43
44
|
super
|
44
45
|
@tmp = @len && @len <= @@client_body_buffer_size ?
|
45
|
-
StringIO.new
|
46
|
+
StringIO.new.binmode : new_tmpio
|
46
47
|
end
|
47
48
|
|
48
49
|
# :call-seq:
|
@@ -121,7 +122,7 @@ module Pitchfork
|
|
121
122
|
|
122
123
|
# consumes the stream of the socket
|
123
124
|
def consume!
|
124
|
-
junk = ""
|
125
|
+
junk = "".b
|
125
126
|
nil while read(@@io_chunk_size, junk)
|
126
127
|
end
|
127
128
|
|
data/lib/pitchfork/tmpio.rb
CHANGED