pitchfork 0.12.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -16,6 +16,7 @@
|
|
16
16
|
#include "child_subreaper.h"
|
17
17
|
|
18
18
|
void init_pitchfork_httpdate(void);
|
19
|
+
void init_pitchfork_memory_page(VALUE);
|
19
20
|
|
20
21
|
#define UH_FL_CHUNKED 0x1
|
21
22
|
#define UH_FL_HASBODY 0x2
|
@@ -960,7 +961,7 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
960
961
|
assert(!NIL_P(var) && "missed global field"); \
|
961
962
|
} while (0)
|
962
963
|
|
963
|
-
void Init_pitchfork_http(void)
|
964
|
+
RUBY_FUNC_EXPORTED void Init_pitchfork_http(void)
|
964
965
|
{
|
965
966
|
VALUE mPitchfork;
|
966
967
|
|
@@ -1024,5 +1025,6 @@ void Init_pitchfork_http(void)
|
|
1024
1025
|
|
1025
1026
|
init_epollexclusive(mPitchfork);
|
1026
1027
|
init_child_subreaper(mPitchfork);
|
1028
|
+
init_pitchfork_memory_page(mPitchfork);
|
1027
1029
|
}
|
1028
1030
|
#undef SET_GLOBAL
|
data/lib/pitchfork/children.rb
CHANGED
@@ -1,31 +1,35 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Pitchfork
|
4
5
|
# This class keep tracks of the state of all the master children.
|
5
6
|
class Children
|
6
|
-
attr_reader :mold
|
7
|
+
attr_reader :mold, :service
|
7
8
|
attr_accessor :last_generation
|
8
9
|
|
9
10
|
def initialize
|
10
11
|
@last_generation = 0
|
11
|
-
@children = {} # All children, including molds, indexed by PID.
|
12
|
+
@children = {} # All children, including molds and services, indexed by PID.
|
12
13
|
@workers = {} # Workers indexed by their `nr`.
|
13
14
|
@molds = {} # Molds, index by PID.
|
14
15
|
@mold = nil # The latest mold, if any.
|
16
|
+
@service = nil
|
15
17
|
@pending_workers = {} # Pending workers indexed by their `nr`.
|
16
18
|
@pending_molds = {} # Worker promoted to mold, not yet acknowledged
|
17
19
|
end
|
18
20
|
|
19
|
-
def refresh
|
20
|
-
@workers.each_value(&:refresh)
|
21
|
-
@molds.each_value(&:refresh)
|
22
|
-
end
|
23
|
-
|
24
21
|
def register(child)
|
25
22
|
# Children always start as workers, never molds, so we know they have a `#nr`.
|
23
|
+
unless child.nr
|
24
|
+
raise "[BUG] Trying to register a child without an `nr`: #{child.inspect}"
|
25
|
+
end
|
26
26
|
@pending_workers[child.nr] = @workers[child.nr] = child
|
27
27
|
end
|
28
28
|
|
29
|
+
def register_service(service)
|
30
|
+
@service = service
|
31
|
+
end
|
32
|
+
|
29
33
|
def register_mold(mold)
|
30
34
|
@pending_molds[mold.pid] = mold
|
31
35
|
@children[mold.pid] = mold
|
@@ -43,6 +47,11 @@ module Pitchfork
|
|
43
47
|
@pending_molds[mold.pid] = mold
|
44
48
|
@children[mold.pid] = mold
|
45
49
|
return mold
|
50
|
+
when Message::ServiceSpawned
|
51
|
+
service = @service
|
52
|
+
service.update(message)
|
53
|
+
@children[service.pid] = service
|
54
|
+
return service
|
46
55
|
end
|
47
56
|
|
48
57
|
child = @children[message.pid] || (message.nr && @workers[message.nr])
|
@@ -77,12 +86,17 @@ module Pitchfork
|
|
77
86
|
@pending_molds.delete(child.pid)
|
78
87
|
@molds.delete(child.pid)
|
79
88
|
@workers.delete(child.nr)
|
89
|
+
|
80
90
|
if @mold == child
|
81
91
|
@pending_workers.reject! do |nr, worker|
|
82
92
|
worker.generation == @mold.generation
|
83
93
|
end
|
84
94
|
@mold = nil
|
85
95
|
end
|
96
|
+
|
97
|
+
if @service == child
|
98
|
+
@service = nil
|
99
|
+
end
|
86
100
|
end
|
87
101
|
child
|
88
102
|
end
|
@@ -153,13 +167,5 @@ module Pitchfork
|
|
153
167
|
def workers_count
|
154
168
|
@workers.size
|
155
169
|
end
|
156
|
-
|
157
|
-
def total_pss
|
158
|
-
total_pss = MemInfo.new(Process.pid).pss
|
159
|
-
@children.each do |_, worker|
|
160
|
-
total_pss += worker.meminfo.pss if worker.meminfo
|
161
|
-
end
|
162
|
-
total_pss
|
163
|
-
end
|
164
170
|
end
|
165
171
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
require 'logger'
|
3
4
|
|
4
5
|
module Pitchfork
|
@@ -51,6 +52,8 @@ module Pitchfork
|
|
51
52
|
"repead unknown process (#{status.inspect})"
|
52
53
|
elsif worker.mold?
|
53
54
|
"mold pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
55
|
+
elsif worker.service?
|
56
|
+
"service pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
54
57
|
else
|
55
58
|
"worker=#{worker.nr rescue 'unknown'} pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
56
59
|
end
|
@@ -74,6 +77,8 @@ module Pitchfork
|
|
74
77
|
:check_client_connection => false,
|
75
78
|
:rewindable_input => true,
|
76
79
|
:client_body_buffer_size => Pitchfork::Const::MAX_BODY,
|
80
|
+
:before_service_worker_ready => nil,
|
81
|
+
:before_service_worker_exit => nil,
|
77
82
|
}
|
78
83
|
#:startdoc:
|
79
84
|
|
@@ -175,6 +180,14 @@ module Pitchfork
|
|
175
180
|
set_hook(:after_request_complete, block_given? ? block : args[0], 3)
|
176
181
|
end
|
177
182
|
|
183
|
+
def before_service_worker_ready(&block)
|
184
|
+
set_hook(:before_service_worker_ready, block, 2)
|
185
|
+
end
|
186
|
+
|
187
|
+
def before_service_worker_exit(&block)
|
188
|
+
set_hook(:before_service_worker_exit, block, 2)
|
189
|
+
end
|
190
|
+
|
178
191
|
def timeout(seconds, cleanup: 2)
|
179
192
|
soft_timeout = set_int(:soft_timeout, seconds, 3)
|
180
193
|
cleanup_timeout = set_int(:cleanup_timeout, cleanup, 2)
|
data/lib/pitchfork/const.rb
CHANGED
data/lib/pitchfork/flock.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
# :enddoc:
|
3
4
|
# no stable API here
|
4
5
|
|
@@ -19,14 +20,13 @@ module Pitchfork
|
|
19
20
|
"SERVER_SOFTWARE" => "Pitchfork #{Pitchfork::Const::UNICORN_VERSION}"
|
20
21
|
}
|
21
22
|
|
22
|
-
NULL_IO = StringIO.new
|
23
|
+
NULL_IO = StringIO.new.binmode
|
23
24
|
|
24
25
|
# :stopdoc:
|
25
|
-
HTTP_RESPONSE_START = [ 'HTTP'
|
26
|
+
HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
|
26
27
|
EMPTY_ARRAY = [].freeze
|
27
28
|
@@input_class = Pitchfork::TeeInput
|
28
29
|
@@check_client_connection = false
|
29
|
-
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
|
30
30
|
|
31
31
|
def self.input_class
|
32
32
|
@@input_class
|
@@ -104,83 +104,29 @@ module Pitchfork
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def hijacked?
|
107
|
-
env.include?('rack.hijack_io'
|
107
|
+
env.include?('rack.hijack_io')
|
108
108
|
end
|
109
109
|
|
110
|
-
if
|
111
|
-
TCPI = Raindrops::TCP_Info.allocate
|
112
|
-
|
110
|
+
if Socket.const_defined?(:TCP_INFO) # Linux
|
113
111
|
def check_client_connection(socket) # :nodoc:
|
114
112
|
if TCPSocket === socket
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
write_http_header(socket)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
if Raindrops.const_defined?(:TCP)
|
124
|
-
# raindrops 0.18.0+ supports FreeBSD + Linux using the same names
|
125
|
-
# Evaluate these hash lookups at load time so we can
|
126
|
-
# generate an opt_case_dispatch instruction
|
127
|
-
eval <<-EOS
|
128
|
-
def closed_state?(state) # :nodoc:
|
129
|
-
case state
|
130
|
-
when #{Raindrops::TCP[:ESTABLISHED]}
|
131
|
-
false
|
132
|
-
when #{Raindrops::TCP.values_at(
|
133
|
-
:CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
|
134
|
-
true
|
135
|
-
else
|
136
|
-
false
|
137
|
-
end
|
138
|
-
end
|
139
|
-
EOS
|
140
|
-
else
|
141
|
-
# raindrops before 0.18 only supported TCP_INFO under Linux
|
142
|
-
def closed_state?(state) # :nodoc:
|
143
|
-
case state
|
144
|
-
when 1 # ESTABLISHED
|
145
|
-
false
|
146
|
-
when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
|
147
|
-
true
|
148
|
-
else
|
149
|
-
false
|
113
|
+
begin
|
114
|
+
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
115
|
+
rescue IOError, SystemCallError
|
116
|
+
return write_http_header(socket)
|
150
117
|
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
else
|
154
118
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
def check_client_connection(socket) # :nodoc:
|
159
|
-
if TCPSocket === socket && @@tcpi_inspect_ok
|
160
|
-
opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
|
161
|
-
if opt =~ /\bstate=(\S+)/
|
162
|
-
raise Errno::EPIPE, "client closed connection".freeze,
|
163
|
-
EMPTY_ARRAY if closed_state_str?($1)
|
164
|
-
else
|
165
|
-
@@tcpi_inspect_ok = false
|
166
|
-
write_http_header(socket)
|
119
|
+
case tcp_info.data.unpack1("C")
|
120
|
+
when 6, 7, 8, 9, 11 # TIME_WAIT, CLOSE, CLOSE_WAIT, LAST_ACK, CLOSING
|
121
|
+
raise Errno::EPIPE, "client closed connection", EMPTY_ARRAY
|
167
122
|
end
|
168
|
-
opt.clear
|
169
123
|
else
|
170
124
|
write_http_header(socket)
|
171
125
|
end
|
172
126
|
end
|
173
|
-
|
174
|
-
def
|
175
|
-
|
176
|
-
when 'ESTABLISHED'
|
177
|
-
false
|
178
|
-
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
|
179
|
-
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
|
180
|
-
true
|
181
|
-
else
|
182
|
-
false
|
183
|
-
end
|
127
|
+
else
|
128
|
+
def check_client_connection(socket) # :nodoc:
|
129
|
+
write_http_header(socket)
|
184
130
|
end
|
185
131
|
end
|
186
132
|
|
@@ -199,11 +145,11 @@ module Pitchfork
|
|
199
145
|
val.downcase!
|
200
146
|
end
|
201
147
|
|
202
|
-
if vals.pop == 'chunked'
|
203
|
-
return true unless vals.include?('chunked'
|
148
|
+
if vals.pop == 'chunked'
|
149
|
+
return true unless vals.include?('chunked')
|
204
150
|
raise Pitchfork::HttpParserError, 'double chunked', []
|
205
151
|
end
|
206
|
-
return false unless vals.include?('chunked'
|
152
|
+
return false unless vals.include?('chunked')
|
207
153
|
raise Pitchfork::HttpParserError, 'chunked not last', []
|
208
154
|
end
|
209
155
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
# :enddoc:
|
3
4
|
|
4
5
|
module Pitchfork
|
@@ -48,10 +49,10 @@ module Pitchfork
|
|
48
49
|
if headers
|
49
50
|
code = status.to_i
|
50
51
|
msg = STATUS_CODES[code]
|
51
|
-
start = req.response_start_sent ? ''
|
52
|
+
start = req.response_start_sent ? '' : 'HTTP/1.1 '
|
52
53
|
buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
|
53
54
|
"Date: #{httpdate}\r\n" \
|
54
|
-
"Connection: close\r\n"
|
55
|
+
"Connection: close\r\n".b
|
55
56
|
headers.each do |key, value|
|
56
57
|
case key
|
57
58
|
when %r{\A(?:Date|Connection)\z}i
|
@@ -64,7 +65,7 @@ module Pitchfork
|
|
64
65
|
append_header(buf, key, value)
|
65
66
|
end
|
66
67
|
end
|
67
|
-
socket.write(buf << "\r\n"
|
68
|
+
socket.write(buf << "\r\n")
|
68
69
|
end
|
69
70
|
|
70
71
|
if hijack
|