pitchfork 0.16.0 → 0.18.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 +8 -2
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -1
- data/README.md +12 -12
- data/Rakefile +14 -25
- data/docs/CONFIGURATION.md +67 -13
- data/docs/DESIGN.md +7 -7
- data/docs/FORK_SAFETY.md +3 -0
- data/docs/PHILOSOPHY.md +2 -2
- data/docs/REFORKING.md +12 -12
- data/docs/SIGNALS.md +7 -8
- data/docs/TUNING.md +3 -3
- data/examples/nginx.conf +1 -1
- data/examples/pitchfork.conf.rb +1 -1
- data/ext/pitchfork_http/c_util.h +2 -2
- data/ext/pitchfork_http/epollexclusive.h +2 -2
- data/lib/pitchfork/children.rb +1 -1
- data/lib/pitchfork/configurator.rb +21 -12
- data/lib/pitchfork/http_server.rb +92 -67
- data/lib/pitchfork/info.rb +3 -2
- data/lib/pitchfork/message.rb +2 -0
- data/lib/pitchfork/refork_condition.rb +1 -1
- data/lib/pitchfork/shared_memory.rb +36 -9
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +75 -35
- data/lib/pitchfork.rb +6 -1
- metadata +3 -3
@@ -44,20 +44,47 @@ module Pitchfork
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
class WorkerState
|
48
|
+
def initialize(field)
|
49
|
+
@field = field
|
50
|
+
end
|
51
|
+
|
52
|
+
def ready?
|
53
|
+
(@field.value & 1) == 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def ready=(bool)
|
57
|
+
if bool
|
58
|
+
@field.value |= 1
|
59
|
+
else
|
60
|
+
@field.value &= ~1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def deadline=(value)
|
65
|
+
# Shift the value up and preserve the current ready bit.
|
66
|
+
@field.value = (value << 1) | (@field.value & 1)
|
67
|
+
end
|
68
|
+
|
69
|
+
def deadline
|
70
|
+
@field.value >> 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def mold_state
|
75
|
+
WorkerState.new(self[MOLD_TICK_OFFSET])
|
49
76
|
end
|
50
77
|
|
51
|
-
def
|
52
|
-
self[MOLD_PROMOTION_TICK_OFFSET]
|
78
|
+
def mold_promotion_state
|
79
|
+
WorkerState.new(self[MOLD_PROMOTION_TICK_OFFSET])
|
53
80
|
end
|
54
81
|
|
55
|
-
def
|
56
|
-
self[SERVICE_TICK_OFFSET]
|
82
|
+
def service_state
|
83
|
+
WorkerState.new(self[SERVICE_TICK_OFFSET])
|
57
84
|
end
|
58
85
|
|
59
|
-
def
|
60
|
-
self[WORKER_TICK_OFFSET + worker_nr]
|
86
|
+
def worker_state(worker_nr)
|
87
|
+
WorkerState.new(self[WORKER_TICK_OFFSET + worker_nr])
|
61
88
|
end
|
62
89
|
|
63
90
|
def [](offset)
|
@@ -75,4 +102,4 @@ module Pitchfork
|
|
75
102
|
end
|
76
103
|
end
|
77
104
|
end
|
78
|
-
end
|
105
|
+
end
|
data/lib/pitchfork/version.rb
CHANGED
data/lib/pitchfork/worker.rb
CHANGED
@@ -14,17 +14,17 @@ module Pitchfork
|
|
14
14
|
# :stopdoc:
|
15
15
|
EXIT_SIGNALS = [:QUIT, :TERM]
|
16
16
|
attr_accessor :nr, :pid, :generation
|
17
|
-
attr_reader :
|
17
|
+
attr_reader :monitor, :requests_count
|
18
18
|
|
19
19
|
def initialize(nr, pid: nil, generation: 0)
|
20
20
|
@nr = nr
|
21
21
|
@pid = pid
|
22
22
|
@generation = generation
|
23
23
|
@mold = false
|
24
|
-
@to_io = @
|
24
|
+
@to_io = @monitor = nil
|
25
25
|
@exiting = false
|
26
26
|
@requests_count = 0
|
27
|
-
|
27
|
+
init_state
|
28
28
|
end
|
29
29
|
|
30
30
|
def exiting?
|
@@ -32,7 +32,7 @@ module Pitchfork
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def pending?
|
35
|
-
@
|
35
|
+
@monitor.nil?
|
36
36
|
end
|
37
37
|
|
38
38
|
def outdated?
|
@@ -46,31 +46,44 @@ module Pitchfork
|
|
46
46
|
|
47
47
|
case message
|
48
48
|
when Message::MoldSpawned
|
49
|
-
@
|
49
|
+
@state_drop = SharedMemory.mold_promotion_state
|
50
50
|
when Message::MoldReady
|
51
|
-
@
|
51
|
+
@state_drop = SharedMemory.mold_state
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def
|
55
|
+
def register_to_monitor(control_socket)
|
56
56
|
create_socketpair!
|
57
|
-
message = Message::WorkerSpawned.new(@nr, @pid, generation, @
|
57
|
+
message = Message::WorkerSpawned.new(@nr, @pid, generation, @monitor)
|
58
58
|
control_socket.sendmsg(message)
|
59
|
-
@
|
59
|
+
@monitor.close
|
60
60
|
end
|
61
61
|
|
62
62
|
def start_promotion(control_socket)
|
63
63
|
create_socketpair!
|
64
|
-
message = Message::MoldSpawned.new(@nr, @pid, generation, @
|
64
|
+
message = Message::MoldSpawned.new(@nr, @pid, generation, @monitor)
|
65
65
|
control_socket.sendmsg(message)
|
66
|
-
@
|
66
|
+
@monitor.close
|
67
67
|
end
|
68
68
|
|
69
69
|
def finish_promotion(control_socket)
|
70
|
+
SharedMemory.current_generation = @generation
|
70
71
|
message = Message::MoldReady.new(@nr, @pid, generation)
|
71
72
|
control_socket.sendmsg(message)
|
72
|
-
|
73
|
-
|
73
|
+
@state_drop = SharedMemory.mold_state
|
74
|
+
end
|
75
|
+
|
76
|
+
def notify_ready(control_socket)
|
77
|
+
self.ready = true
|
78
|
+
message = if worker?
|
79
|
+
Message::WorkerReady.new(@nr, @pid, @generation)
|
80
|
+
elsif service?
|
81
|
+
Message::ServiceReady.new(@pid, @generation)
|
82
|
+
else
|
83
|
+
raise "Unexpected child type"
|
84
|
+
end
|
85
|
+
|
86
|
+
control_socket.sendmsg(message)
|
74
87
|
end
|
75
88
|
|
76
89
|
def promote(generation)
|
@@ -93,7 +106,7 @@ module Pitchfork
|
|
93
106
|
def promoted!(timeout)
|
94
107
|
@mold = true
|
95
108
|
@nr = nil
|
96
|
-
@
|
109
|
+
@state_drop = SharedMemory.mold_promotion_state
|
97
110
|
update_deadline(timeout) if timeout
|
98
111
|
self
|
99
112
|
end
|
@@ -106,12 +119,16 @@ module Pitchfork
|
|
106
119
|
false
|
107
120
|
end
|
108
121
|
|
122
|
+
def worker?
|
123
|
+
!mold? && !service?
|
124
|
+
end
|
125
|
+
|
109
126
|
def to_io # IO.select-compatible
|
110
127
|
@to_io.to_io
|
111
128
|
end
|
112
129
|
|
113
|
-
def
|
114
|
-
@
|
130
|
+
def monitor=(socket)
|
131
|
+
@monitor = MessageSocket.new(socket)
|
115
132
|
end
|
116
133
|
|
117
134
|
# call a signal handler immediately without triggering EINTR
|
@@ -126,7 +143,7 @@ module Pitchfork
|
|
126
143
|
trap(sig, old_cb)
|
127
144
|
end
|
128
145
|
|
129
|
-
#
|
146
|
+
# monitor sends fake signals to children
|
130
147
|
def soft_kill(sig) # :nodoc:
|
131
148
|
signum = Signal.list[sig.to_s] or raise ArgumentError, "BUG: bad signal: #{sig.inspect}"
|
132
149
|
|
@@ -149,7 +166,7 @@ module Pitchfork
|
|
149
166
|
case buf = @to_io.recvmsg_nonblock(exception: false)
|
150
167
|
when :wait_readable # keep waiting
|
151
168
|
return false
|
152
|
-
when nil # EOF
|
169
|
+
when nil # EOF monitor died, but we are at a safe place to exit
|
153
170
|
fake_sig(:QUIT)
|
154
171
|
return false
|
155
172
|
when Message::SoftKill
|
@@ -171,18 +188,29 @@ module Pitchfork
|
|
171
188
|
super || (!@nr.nil? && @nr == other)
|
172
189
|
end
|
173
190
|
|
191
|
+
def ready?
|
192
|
+
@state_drop.ready?
|
193
|
+
end
|
194
|
+
|
195
|
+
def ready=(bool)
|
196
|
+
@state_drop.ready = bool
|
197
|
+
end
|
198
|
+
|
174
199
|
def update_deadline(timeout)
|
175
200
|
self.deadline = Pitchfork.time_now(true) + timeout
|
176
201
|
end
|
177
202
|
|
178
203
|
# called in the worker process
|
179
204
|
def deadline=(value) # :nodoc:
|
180
|
-
|
205
|
+
# If we are (re)setting to zero mark worker as not ready.
|
206
|
+
self.ready = false if value == 0
|
207
|
+
|
208
|
+
@state_drop.deadline = value
|
181
209
|
end
|
182
210
|
|
183
|
-
# called in the
|
211
|
+
# called in the monitor process
|
184
212
|
def deadline # :nodoc:
|
185
|
-
@
|
213
|
+
@state_drop.deadline
|
186
214
|
end
|
187
215
|
|
188
216
|
def reset
|
@@ -193,26 +221,34 @@ module Pitchfork
|
|
193
221
|
@requests_count += by
|
194
222
|
end
|
195
223
|
|
196
|
-
# called in both the
|
224
|
+
# called in both the monitor (reaping worker) and worker (SIGQUIT handler)
|
197
225
|
def close # :nodoc:
|
198
226
|
self.deadline = 0
|
199
|
-
@
|
227
|
+
@monitor.close if @monitor
|
200
228
|
@to_io.close if @to_io
|
201
229
|
end
|
202
230
|
|
203
231
|
def create_socketpair!
|
204
|
-
@to_io, @
|
232
|
+
@to_io, @monitor = Info.keep_ios(Pitchfork.socketpair)
|
205
233
|
end
|
206
234
|
|
207
235
|
def after_fork_in_child
|
208
|
-
@
|
236
|
+
@monitor&.close
|
237
|
+
end
|
238
|
+
|
239
|
+
def to_log
|
240
|
+
if mold?
|
241
|
+
pid ? "mold gen=#{generation} pid=#{pid}" : "mold gen=#{generation}"
|
242
|
+
else
|
243
|
+
pid ? "worker=#{nr} gen=#{generation} pid=#{pid}" : "worker=#{nr} gen=#{generation}"
|
244
|
+
end
|
209
245
|
end
|
210
246
|
|
211
247
|
private
|
212
248
|
|
213
|
-
def
|
249
|
+
def init_state
|
214
250
|
if nr
|
215
|
-
@
|
251
|
+
@state_drop = SharedMemory.worker_state(@nr)
|
216
252
|
self.deadline = 0
|
217
253
|
else
|
218
254
|
promoted!(nil)
|
@@ -222,14 +258,14 @@ module Pitchfork
|
|
222
258
|
def pipe=(socket)
|
223
259
|
raise ArgumentError, "pipe can't be nil" unless socket
|
224
260
|
Info.keep_io(socket)
|
225
|
-
@
|
261
|
+
@monitor = MessageSocket.new(socket)
|
226
262
|
end
|
227
263
|
|
228
264
|
def send_message_nonblock(message)
|
229
265
|
success = false
|
230
|
-
return false unless @
|
266
|
+
return false unless @monitor
|
231
267
|
begin
|
232
|
-
case @
|
268
|
+
case @monitor.sendmsg_nonblock(message, exception: false)
|
233
269
|
when :wait_writable
|
234
270
|
else
|
235
271
|
success = true
|
@@ -250,17 +286,21 @@ module Pitchfork
|
|
250
286
|
true
|
251
287
|
end
|
252
288
|
|
253
|
-
def
|
289
|
+
def register_to_monitor(control_socket)
|
254
290
|
create_socketpair!
|
255
|
-
message = Message::ServiceSpawned.new(@pid, generation, @
|
291
|
+
message = Message::ServiceSpawned.new(@pid, generation, @monitor)
|
256
292
|
control_socket.sendmsg(message)
|
257
|
-
@
|
293
|
+
@monitor.close
|
294
|
+
end
|
295
|
+
|
296
|
+
def to_log
|
297
|
+
pid ? "service gen=#{generation} pid=#{pid}" : "service gen=#{generation}"
|
258
298
|
end
|
259
299
|
|
260
300
|
private
|
261
301
|
|
262
|
-
def
|
263
|
-
@
|
302
|
+
def init_state
|
303
|
+
@state_drop = SharedMemory.service_state
|
264
304
|
self.deadline = 0
|
265
305
|
end
|
266
306
|
end
|
data/lib/pitchfork.rb
CHANGED
@@ -87,7 +87,12 @@ module Pitchfork
|
|
87
87
|
when /\.ru$/
|
88
88
|
raw = File.read(ru)
|
89
89
|
raw.sub!(/^__END__\n.*/, '')
|
90
|
-
|
90
|
+
lines = raw.lines
|
91
|
+
trailing_comments_index = lines.index { |line| !line.start_with?('#') }
|
92
|
+
prelude = lines[0...trailing_comments_index].join
|
93
|
+
raw = lines[trailing_comments_index..-1].join
|
94
|
+
|
95
|
+
eval("#{prelude}\nRack::Builder.new do\n#{raw}\nend.to_app\n", TOPLEVEL_BINDING, ru)
|
91
96
|
else
|
92
97
|
require ru
|
93
98
|
Object.const_get(File.basename(ru, '.rb').capitalize)
|
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.18.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:
|
11
|
+
date: 2025-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -141,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
141
|
- !ruby/object:Gem::Version
|
142
142
|
version: '0'
|
143
143
|
requirements: []
|
144
|
-
rubygems_version: 3.5.
|
144
|
+
rubygems_version: 3.5.9
|
145
145
|
signing_key:
|
146
146
|
specification_version: 4
|
147
147
|
summary: Rack HTTP server for fast clients and Unix
|