pitchfork 0.7.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +28 -0
- data/CHANGELOG.md +9 -0
- data/Dockerfile +1 -1
- data/Gemfile.lock +2 -2
- data/README.md +14 -7
- data/docs/CONFIGURATION.md +29 -5
- data/docs/WHY_MIGRATE.md +93 -0
- data/lib/pitchfork/configurator.rb +12 -0
- data/lib/pitchfork/http_server.rb +8 -2
- data/lib/pitchfork/info.rb +40 -11
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork.rb +158 -125
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb4e22969b9f2c38717f0cfa7a3c966995814156a17589bc06bdc609b7ad6e32
|
4
|
+
data.tar.gz: dbf7833c26ef94962abbd71d66c63e40dd0f7f405ee82951723ad3b4cbfa864f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f0c029a01bc999d90421fe0ed77f76c6f1e56a9f3ec8eaa4ab6722f40eb0ee7f9aa0f004044e4c97e93a593cdba0d7a6bab01ef6b075440c76012f05e5cccd4
|
7
|
+
data.tar.gz: 4ae4d319ffd2acbf99ad29156622510c951ca095c20074f8415376d6e217be1b959e68e3245590cce62de66f3f8af8f0aae469fec842865dd4dcfe1b060e3db9
|
@@ -0,0 +1,28 @@
|
|
1
|
+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
|
3
|
+
{
|
4
|
+
"name": "Ruby",
|
5
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
6
|
+
"build": {
|
7
|
+
// Path is relative to the devcontainer.json file.
|
8
|
+
"dockerfile": "../Dockerfile"
|
9
|
+
},
|
10
|
+
"features": {
|
11
|
+
"ghcr.io/devcontainers/features/github-cli:1": {}
|
12
|
+
},
|
13
|
+
|
14
|
+
// Features to add to the dev container. More info: https://containers.dev/features.
|
15
|
+
// "features": {},
|
16
|
+
|
17
|
+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
18
|
+
// "forwardPorts": [],
|
19
|
+
|
20
|
+
// Use 'postCreateCommand' to run commands after the container is created.
|
21
|
+
"postCreateCommand": "bundle install"
|
22
|
+
|
23
|
+
// Configure tool-specific properties.
|
24
|
+
// "customizations": {},
|
25
|
+
|
26
|
+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
27
|
+
// "remoteUser": "root"
|
28
|
+
}
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 0.9.0
|
4
|
+
|
5
|
+
- Implement `spawn_timeout` to protect against bugs causing workers to get stuck before they reach ready state.
|
6
|
+
|
7
|
+
# 0.8.0
|
8
|
+
|
9
|
+
- Add an `after_monitor_ready` callback, called in the monitor process at end of boot.
|
10
|
+
- Implement `Pitchfork.prevent_fork` for use in background threads that synchronize native locks with the GVL released.
|
11
|
+
|
3
12
|
# 0.7.0
|
4
13
|
|
5
14
|
- Set nicer `proctile` to better see the state of the process tree at a glance.
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pitchfork (0.
|
4
|
+
pitchfork (0.9.0)
|
5
5
|
rack (>= 2.0)
|
6
6
|
raindrops (~> 0.7)
|
7
7
|
|
@@ -10,7 +10,7 @@ GEM
|
|
10
10
|
specs:
|
11
11
|
minitest (5.15.0)
|
12
12
|
nio4r (2.5.9)
|
13
|
-
puma (6.3.
|
13
|
+
puma (6.3.1)
|
14
14
|
nio4r (~> 2.0)
|
15
15
|
rack (3.0.8)
|
16
16
|
raindrops (0.20.1)
|
data/README.md
CHANGED
@@ -9,13 +9,6 @@ advantage of features in Unix/Unix-like kernels. Slow clients should
|
|
9
9
|
only be served by placing a reverse proxy capable of fully buffering
|
10
10
|
both the request and response in between `pitchfork` and slow clients.
|
11
11
|
|
12
|
-
## Disclaimer
|
13
|
-
|
14
|
-
Until this notice is removed from the README, `pitchfork` should be
|
15
|
-
considered experimental. As such it is not encouraged to run it in
|
16
|
-
production just yet unless you feel capable of debugging yourself
|
17
|
-
any issue that may arise.
|
18
|
-
|
19
12
|
## Features
|
20
13
|
|
21
14
|
* Designed for Rack, Linux, fast clients, and ease-of-debugging. We
|
@@ -40,9 +33,23 @@ any issue that may arise.
|
|
40
33
|
or ports yourself. `pitchfork` can spawn and manage any number of
|
41
34
|
worker processes you choose to scale to your backend.
|
42
35
|
|
36
|
+
* Adaptative timeout: request timeout can be extended dynamically on a
|
37
|
+
per request basis, which allows to keep a strict overall timeout for
|
38
|
+
most endpoints, but allow a few endpoints to take longer.
|
39
|
+
|
43
40
|
* Load balancing is done entirely by the operating system kernel.
|
44
41
|
Requests never pile up behind a busy worker process.
|
45
42
|
|
43
|
+
## When to Use
|
44
|
+
|
45
|
+
Pitchfork isn't inherently better than other Ruby application servers, it mostly
|
46
|
+
focus on different tradeoffs.
|
47
|
+
|
48
|
+
If you are fine with your current server, it's best to stick with it.
|
49
|
+
|
50
|
+
If there is a problem you are trying to solve, please read the
|
51
|
+
[migration guide](docs/WHY_MIGRATE.md) first.
|
52
|
+
|
46
53
|
## Requirements
|
47
54
|
|
48
55
|
Ruby(MRI) Version 2.5 and above.
|
data/docs/CONFIGURATION.md
CHANGED
@@ -238,6 +238,19 @@ exit or be SIGKILL-ed due to timeouts.
|
|
238
238
|
See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
|
239
239
|
for more details on nginx upstream configuration.
|
240
240
|
|
241
|
+
### `spawn_timeout`
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
timeout 5
|
245
|
+
```
|
246
|
+
|
247
|
+
Sets the timeout for a newly spawned worker to be ready after being spawned.
|
248
|
+
|
249
|
+
This timeout is a safeguard against various low-level fork safety bugs that could cause
|
250
|
+
a process to dead-lock.
|
251
|
+
|
252
|
+
The default of `10` seconds is quite generous and likely doesn't need to be adjusted.
|
253
|
+
|
241
254
|
### `logger`
|
242
255
|
|
243
256
|
```ruby
|
@@ -253,12 +266,23 @@ The default Logger will log its output to STDERR.
|
|
253
266
|
Because pitchfork several callbacks around the lifecycle of workers.
|
254
267
|
It is often necessary to use these callbacks to close inherited connection after fork.
|
255
268
|
|
256
|
-
Note that when reforking is available, the `pitchfork`
|
257
|
-
at all. As such for hooks executed in the
|
269
|
+
Note that when reforking is available, the `pitchfork` monitor process won't load your application
|
270
|
+
at all. As such for hooks executed in the monitor, you may need to explicitly load the parts of your
|
258
271
|
application that are used in hooks.
|
259
272
|
|
260
273
|
`pitchfork` also don't attempt to rescue hook errors. Raising from a worker hook will crash the worker,
|
261
|
-
and raising from a
|
274
|
+
and raising from a monitor hook will bring the whole cluster down.
|
275
|
+
|
276
|
+
### `after_monitor_ready`
|
277
|
+
|
278
|
+
Called by the monitor process after it's done booting the application and
|
279
|
+
spawning the original workers.
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
after_monitor_ready do |server|
|
283
|
+
server.logger.info("Monitor pid=#{Process.pid} ready")
|
284
|
+
end
|
285
|
+
```
|
262
286
|
|
263
287
|
### `after_mold_fork`
|
264
288
|
|
@@ -338,7 +362,7 @@ By default the cleanup timeout is 2 seconds.
|
|
338
362
|
|
339
363
|
### `after_worker_hard_timeout`
|
340
364
|
|
341
|
-
Called in the
|
365
|
+
Called in the monitor process when a worker hard timeout is elapsed:
|
342
366
|
|
343
367
|
```ruby
|
344
368
|
after_worker_timeout do |server, worker|
|
@@ -353,7 +377,7 @@ soft timeout from working.
|
|
353
377
|
|
354
378
|
### `after_worker_exit`
|
355
379
|
|
356
|
-
Called in the
|
380
|
+
Called in the monitor process after a worker exits.
|
357
381
|
|
358
382
|
```ruby
|
359
383
|
after_worker_exit do |server, worker, status|
|
data/docs/WHY_MIGRATE.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Why migrate to Pitchfork?
|
2
|
+
|
3
|
+
First and foremost, if you don't have any specific problem with your current server, then don't.
|
4
|
+
|
5
|
+
Pitchfork isn't a silver bullet, it's a very opinionated software that focus on very specific tradeoffs,
|
6
|
+
that are different from other servers.
|
7
|
+
|
8
|
+
## Coming from Unicorn
|
9
|
+
|
10
|
+
### Why Migrate?
|
11
|
+
|
12
|
+
#### Adaptative timeout
|
13
|
+
|
14
|
+
Pitchfork allows to extend the request timeout on a per request basis,
|
15
|
+
this can be helpful when trying to reduce the global request timeout
|
16
|
+
to a saner value. You can enforce a stricter value, and extend it
|
17
|
+
in the minority of offending endpoints.
|
18
|
+
|
19
|
+
#### Memory Usage - Reforking
|
20
|
+
|
21
|
+
If you are unsatisfied with Unicorn memory usage, but threaded Puma isn't an option
|
22
|
+
for you, then Pitchfork may be an option if you are able to enable reforking.
|
23
|
+
|
24
|
+
However be warned that making an application fork safe can be non-trivial,
|
25
|
+
and mistakes can lead to critical bugs.
|
26
|
+
|
27
|
+
#### Rack 3
|
28
|
+
|
29
|
+
As of Unicorn `6.1.0`, Rack 3 isn't yet supported by Unicorn.
|
30
|
+
|
31
|
+
Pitchfork is compatible with Rack 3.
|
32
|
+
|
33
|
+
### Why Not Migrate?
|
34
|
+
|
35
|
+
#### Reduced Features
|
36
|
+
|
37
|
+
While Pitchfork started as a fork of Unicorn, many features such as daemonization,
|
38
|
+
pid file management, hot reload have been stripped.
|
39
|
+
|
40
|
+
Pitchfork only kept features that makes sense in a containerized world.
|
41
|
+
|
42
|
+
## Coming from Puma
|
43
|
+
|
44
|
+
Generally speaking, compared to (threaded) Puma, Pitchfork *may* offer better latency and isolation at the expense of throughput.
|
45
|
+
|
46
|
+
### Why Migrate?
|
47
|
+
|
48
|
+
#### Latency
|
49
|
+
|
50
|
+
If you suspect your application is subject to contention on the GVL or some other in-process shared resources,
|
51
|
+
then Pitchfork may offer improved latency.
|
52
|
+
|
53
|
+
It is however heavily recommended to first confirm this suspicion with profiling
|
54
|
+
tools such as [gvltools](https://github.com/Shopify/gvltools).
|
55
|
+
|
56
|
+
If you application isn't subject to in-process contention, Pitchfork is unlikely to improve latency.
|
57
|
+
|
58
|
+
#### Out of Band Garbage Collection
|
59
|
+
|
60
|
+
Another advantage of only processing a single request per process is that
|
61
|
+
[it allows to periodically trigger garbage collection when the worker isn't processing any request](https://shopify.engineering/adventures-in-garbage-collection).
|
62
|
+
|
63
|
+
This can significantly improve tail latency at the expense of throughput.
|
64
|
+
|
65
|
+
#### Resiliency and Isolation
|
66
|
+
|
67
|
+
Since Pitchfork workers have their own address space and only process one request at a time
|
68
|
+
it makes it much harder for one faulty request to impact another.
|
69
|
+
|
70
|
+
Even if a bug causes Ruby to crash, only the request that triggered the bug will be impacted.
|
71
|
+
|
72
|
+
If a bug causes Ruby to hang, the monitor process will SIGKILL the worker and the capacity will be
|
73
|
+
reclaimed.
|
74
|
+
|
75
|
+
This makes Pitchfork more resilient to some classes of bugs.
|
76
|
+
|
77
|
+
#### Thread Safety
|
78
|
+
|
79
|
+
Pitchfork doesn't require applications to be thread-safe. That is probably the worst reason
|
80
|
+
to migrate though.
|
81
|
+
|
82
|
+
### Why Not Migrate?
|
83
|
+
|
84
|
+
#### Memory Usage
|
85
|
+
|
86
|
+
Without reforking enabled Pitchfork will without a doubt use more memory than threaded Puma.
|
87
|
+
|
88
|
+
With reforking enabled, results will vary based on the application profile and the number of Puma threads,
|
89
|
+
but should be in the same ballpark, sometimes better, but likely worse, this depends on many variables and
|
90
|
+
can't really be predicted.
|
91
|
+
|
92
|
+
However be warned that [making an application fork safe](FORK_SAFETY.md) can be non-trivial,
|
93
|
+
and mistakes can lead to critical bugs.
|
@@ -33,6 +33,7 @@ module Pitchfork
|
|
33
33
|
DEFAULTS = {
|
34
34
|
:soft_timeout => 20,
|
35
35
|
:cleanup_timeout => 2,
|
36
|
+
:spawn_timeout => 10,
|
36
37
|
:timeout => 22,
|
37
38
|
:logger => default_logger,
|
38
39
|
:worker_processes => 1,
|
@@ -60,6 +61,9 @@ module Pitchfork
|
|
60
61
|
:after_worker_ready => lambda { |server, worker|
|
61
62
|
server.logger.info("worker=#{worker.nr} gen=#{worker.generation} ready")
|
62
63
|
},
|
64
|
+
:after_monitor_ready => lambda { |server|
|
65
|
+
server.logger.info("Monitor pid=#{Process.pid} ready")
|
66
|
+
},
|
63
67
|
:after_worker_timeout => nil,
|
64
68
|
:after_worker_hard_timeout => nil,
|
65
69
|
:after_request_complete => nil,
|
@@ -141,6 +145,10 @@ module Pitchfork
|
|
141
145
|
set_hook(:after_worker_ready, block_given? ? block : args[0])
|
142
146
|
end
|
143
147
|
|
148
|
+
def after_monitor_ready(*args, &block)
|
149
|
+
set_hook(:after_monitor_ready, block_given? ? block : args[0], 1)
|
150
|
+
end
|
151
|
+
|
144
152
|
def after_worker_timeout(*args, &block)
|
145
153
|
set_hook(:after_worker_timeout, block_given? ? block : args[0], 3)
|
146
154
|
end
|
@@ -167,6 +175,10 @@ module Pitchfork
|
|
167
175
|
set_int(:timeout, soft_timeout + cleanup_timeout, 5)
|
168
176
|
end
|
169
177
|
|
178
|
+
def spawn_timeout(seconds)
|
179
|
+
set_int(:spawn_timeout, seconds, 1)
|
180
|
+
end
|
181
|
+
|
170
182
|
def worker_processes(nr)
|
171
183
|
set_int(:worker_processes, nr, 1)
|
172
184
|
end
|
@@ -74,13 +74,13 @@ module Pitchfork
|
|
74
74
|
end
|
75
75
|
|
76
76
|
# :stopdoc:
|
77
|
-
attr_accessor :app, :timeout, :soft_timeout, :cleanup_timeout, :worker_processes,
|
77
|
+
attr_accessor :app, :timeout, :soft_timeout, :cleanup_timeout, :spawn_timeout, :worker_processes,
|
78
78
|
:after_worker_fork, :after_mold_fork,
|
79
79
|
:listener_opts, :children,
|
80
80
|
:orig_app, :config, :ready_pipe,
|
81
81
|
:default_middleware, :early_hints
|
82
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
|
83
|
+
:refork_condition, :after_worker_timeout, :after_worker_hard_timeout, :after_monitor_ready
|
84
84
|
|
85
85
|
attr_reader :logger
|
86
86
|
include Pitchfork::SocketHelper
|
@@ -212,6 +212,8 @@ module Pitchfork
|
|
212
212
|
wait_for_pending_workers
|
213
213
|
end
|
214
214
|
|
215
|
+
@after_monitor_ready&.call(self)
|
216
|
+
|
215
217
|
self
|
216
218
|
end
|
217
219
|
|
@@ -554,6 +556,10 @@ module Pitchfork
|
|
554
556
|
def spawn_worker(worker, detach:)
|
555
557
|
logger.info("worker=#{worker.nr} gen=#{worker.generation} spawning...")
|
556
558
|
|
559
|
+
# We set the deadline before spawning the child so that if for some
|
560
|
+
# reason it gets stuck before reaching the worker loop,
|
561
|
+
# the monitor process will kill it.
|
562
|
+
worker.update_deadline(@spawn_timeout)
|
557
563
|
Pitchfork.fork_sibling do
|
558
564
|
worker.pid = Process.pid
|
559
565
|
|
data/lib/pitchfork/info.rb
CHANGED
@@ -6,14 +6,35 @@ module Pitchfork
|
|
6
6
|
module Info
|
7
7
|
@workers_count = 0
|
8
8
|
@fork_safe = true
|
9
|
-
|
9
|
+
|
10
|
+
class WeakSet # :nodoc
|
11
|
+
def initialize
|
12
|
+
@map = ObjectSpace::WeakMap.new
|
13
|
+
end
|
14
|
+
|
15
|
+
if RUBY_VERSION < "2.7"
|
16
|
+
def <<(object)
|
17
|
+
@map[object] = object
|
18
|
+
end
|
19
|
+
else
|
20
|
+
def <<(object)
|
21
|
+
@map[object] = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
@map.each_key(&block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@kept_ios = WeakSet.new
|
10
31
|
|
11
32
|
class << self
|
12
33
|
attr_accessor :workers_count
|
13
34
|
|
14
35
|
def keep_io(io)
|
15
36
|
raise ArgumentError, "#{io.inspect} doesn't respond to :to_io" unless io.respond_to?(:to_io)
|
16
|
-
@kept_ios
|
37
|
+
@kept_ios << io
|
17
38
|
io
|
18
39
|
end
|
19
40
|
|
@@ -22,20 +43,14 @@ module Pitchfork
|
|
22
43
|
end
|
23
44
|
|
24
45
|
def close_all_ios!
|
25
|
-
ignored_ios = [$stdin, $stdout, $stderr]
|
46
|
+
ignored_ios = [$stdin, $stdout, $stderr, STDIN, STDOUT, STDERR].uniq.compact
|
26
47
|
|
27
|
-
@kept_ios.
|
48
|
+
@kept_ios.each do |io_like|
|
28
49
|
ignored_ios << (io_like.is_a?(IO) ? io_like : io_like.to_io)
|
29
50
|
end
|
30
51
|
|
31
52
|
ObjectSpace.each_object(IO) do |io|
|
32
|
-
|
33
|
-
io.closed?
|
34
|
-
rescue IOError
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
if !closed && io.autoclose? && !ignored_ios.include?(io)
|
53
|
+
if io_open?(io) && io_autoclosed?(io) && !ignored_ios.include?(io)
|
39
54
|
if io.is_a?(TCPSocket)
|
40
55
|
# If we inherited a TCP Socket, calling #close directly could send FIN or RST.
|
41
56
|
# So we first reopen /dev/null to avoid that.
|
@@ -73,6 +88,20 @@ module Pitchfork
|
|
73
88
|
def shutting_down?
|
74
89
|
SharedMemory.shutting_down?
|
75
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def io_open?(io)
|
95
|
+
!io.closed?
|
96
|
+
rescue IOError
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def io_autoclosed?(io)
|
101
|
+
io.autoclose?
|
102
|
+
rescue IOError
|
103
|
+
false
|
104
|
+
end
|
76
105
|
end
|
77
106
|
end
|
78
107
|
end
|
data/lib/pitchfork/version.rb
CHANGED
data/lib/pitchfork.rb
CHANGED
@@ -36,157 +36,190 @@ module Pitchfork
|
|
36
36
|
|
37
37
|
# :stopdoc:
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
FORK_LOCK = Monitor.new
|
40
|
+
@socket_type = :SOCK_SEQPACKET
|
41
|
+
|
42
|
+
class << self
|
43
|
+
# :startdoc:
|
44
|
+
|
45
|
+
# Prevent Pitchfork from forking new children for the duration of the block.
|
46
|
+
#
|
47
|
+
# If you have background threads calling code that synchronize native locks,
|
48
|
+
# while the GVL is released, forking while they are held could leak to
|
49
|
+
# corrupted children.
|
50
|
+
#
|
51
|
+
# One example of this is `getaddrinfo(3)`, so opening a connection from a
|
52
|
+
# background thread has a chance to produce stuck children.
|
53
|
+
#
|
54
|
+
# To avoid this you can wrap such code in `Pitchfork.prevent_fork`:
|
55
|
+
#
|
56
|
+
# def heartbeat_thread
|
57
|
+
# @heartbeat_thread ||= Thread.new do
|
58
|
+
# loop do
|
59
|
+
# Pitchfork.prevent_fork do
|
60
|
+
# heartbeat
|
61
|
+
# end
|
62
|
+
# sleep 10
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
def prevent_fork(&block)
|
68
|
+
FORK_LOCK.synchronize(&block)
|
47
69
|
end
|
48
70
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
71
|
+
# :stopdoc:
|
72
|
+
|
73
|
+
# This returns a lambda to pass in as the app, this does not "build" the
|
74
|
+
# app The returned lambda will be called when it is
|
75
|
+
# time to build the app.
|
76
|
+
def builder(ru, op)
|
77
|
+
# allow Configurator to parse cli switches embedded in the ru file
|
78
|
+
op = Pitchfork::Configurator::RACKUP.merge!(:file => ru, :optparse => op)
|
79
|
+
if ru =~ /\.ru$/ && !defined?(Rack::Builder)
|
80
|
+
abort "rack and Rack::Builder must be available for processing #{ru}"
|
59
81
|
end
|
60
82
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
83
|
+
# always called after config file parsing, may be called after forking
|
84
|
+
lambda do |_, server|
|
85
|
+
inner_app = case ru
|
86
|
+
when /\.ru$/
|
87
|
+
raw = File.read(ru)
|
88
|
+
raw.sub!(/^__END__\n.*/, '')
|
89
|
+
eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
|
90
|
+
else
|
91
|
+
require ru
|
92
|
+
Object.const_get(File.basename(ru, '.rb').capitalize)
|
93
|
+
end
|
94
|
+
|
95
|
+
Rack::Builder.new do
|
96
|
+
use(Rack::ContentLength)
|
97
|
+
use(Pitchfork::Chunked)
|
98
|
+
use(Rack::Lint) if ENV["RACK_ENV"] == "development"
|
99
|
+
use(Rack::TempfileReaper)
|
100
|
+
run inner_app
|
101
|
+
end.to_app
|
102
|
+
end
|
68
103
|
end
|
69
|
-
end
|
70
104
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
105
|
+
# returns an array of strings representing TCP listen socket addresses
|
106
|
+
# and Unix domain socket paths. This is useful for use with
|
107
|
+
# Raindrops::Middleware under Linux: https://yhbt.net/raindrops/
|
108
|
+
def listener_names
|
109
|
+
Pitchfork::HttpServer::LISTENERS.map do |io|
|
110
|
+
Pitchfork::SocketHelper.sock_name(io)
|
111
|
+
end
|
77
112
|
end
|
78
|
-
end
|
79
113
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
114
|
+
def log_error(logger, prefix, exc)
|
115
|
+
message = exc.message
|
116
|
+
message = message.dump if /[[:cntrl:]]/ =~ message
|
117
|
+
logger.error "#{prefix}: #{message} (#{exc.class})"
|
118
|
+
exc.backtrace.each { |line| logger.error(line) }
|
119
|
+
end
|
86
120
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
121
|
+
F_SETPIPE_SZ = 1031 if RUBY_PLATFORM =~ /linux/
|
122
|
+
|
123
|
+
def pipe # :nodoc:
|
124
|
+
IO.pipe.each do |io|
|
125
|
+
# shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
|
126
|
+
# limits.
|
127
|
+
if defined?(F_SETPIPE_SZ)
|
128
|
+
begin
|
129
|
+
io.fcntl(F_SETPIPE_SZ, Raindrops::PAGE_SIZE)
|
130
|
+
rescue Errno::EINVAL
|
131
|
+
# old kernel
|
132
|
+
rescue Errno::EPERM
|
133
|
+
# resizes fail if Linux is close to the pipe limit for the user
|
134
|
+
# or if the user does not have permissions to resize
|
135
|
+
end
|
101
136
|
end
|
102
137
|
end
|
103
138
|
end
|
104
|
-
end
|
105
139
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
140
|
+
def socketpair
|
141
|
+
pair = UNIXSocket.socketpair(@socket_type).map { |s| MessageSocket.new(s) }
|
142
|
+
pair[0].close_write
|
143
|
+
pair[1].close_read
|
144
|
+
pair
|
145
|
+
rescue Errno::EPROTONOSUPPORT
|
146
|
+
if @socket_type == :SOCK_SEQPACKET
|
147
|
+
# macOS and very old linuxes don't support SOCK_SEQPACKET (SCTP).
|
148
|
+
# In such case we can fallback to SOCK_STREAM (TCP)
|
149
|
+
warn("SEQPACKET (SCTP) isn't supported, falling back to STREAM")
|
150
|
+
@socket_type = :SOCK_STREAM
|
151
|
+
retry
|
152
|
+
else
|
153
|
+
raise
|
154
|
+
end
|
121
155
|
end
|
122
|
-
end
|
123
156
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
157
|
+
def clean_fork(setpgid: true, &block)
|
158
|
+
if pid = FORK_LOCK.synchronize { Process.fork }
|
159
|
+
if setpgid
|
160
|
+
Process.setpgid(pid, pid) # Make into a group leader
|
161
|
+
end
|
162
|
+
return pid
|
128
163
|
end
|
129
|
-
return pid
|
130
|
-
end
|
131
164
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
165
|
+
begin
|
166
|
+
# Pitchfork recursively refork the worker processes.
|
167
|
+
# Because of this we need to unwind the stack before resuming execution
|
168
|
+
# in the child, otherwise on each generation the available stack space would
|
169
|
+
# get smaller and smaller until it's basically 0.
|
170
|
+
#
|
171
|
+
# The very first version of this method used to call fork from a new
|
172
|
+
# thread, however this can cause issues with some native gems that rely on
|
173
|
+
# pthread_atfork(3) or pthread_mutex_lock(3), as the new main thread would
|
174
|
+
# now be different.
|
175
|
+
#
|
176
|
+
# A second version used to fork from a new fiber, but fibers have a much smaller
|
177
|
+
# stack space (https://bugs.ruby-lang.org/issues/3187), so it would break large applications.
|
178
|
+
#
|
179
|
+
# The latest version now use `throw` to unwind the stack after the fork, it however
|
180
|
+
# restrict it to be called only inside `handle_clean_fork`.
|
181
|
+
if Thread.current[:pitchfork_handle_clean_fork]
|
182
|
+
throw self, block
|
183
|
+
else
|
184
|
+
while block
|
185
|
+
block = catch(self) do
|
186
|
+
Thread.current[:pitchfork_handle_clean_fork] = true
|
187
|
+
block.call
|
188
|
+
nil
|
189
|
+
end
|
156
190
|
end
|
157
191
|
end
|
192
|
+
rescue
|
193
|
+
abort
|
194
|
+
else
|
195
|
+
exit
|
158
196
|
end
|
159
|
-
rescue
|
160
|
-
abort
|
161
|
-
else
|
162
|
-
exit
|
163
197
|
end
|
164
|
-
end
|
165
198
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
199
|
+
def fork_sibling(&block)
|
200
|
+
if REFORKING_AVAILABLE
|
201
|
+
# We double fork so that the new worker is re-attached back
|
202
|
+
# to the master.
|
203
|
+
# This requires either PR_SET_CHILD_SUBREAPER which is exclusive to Linux 3.4
|
204
|
+
# or the master to be PID 1.
|
205
|
+
if middle_pid = FORK_LOCK.synchronize { Process.fork } # parent
|
206
|
+
# We need to wait(2) so that the middle process doesn't end up a zombie.
|
207
|
+
Process.wait(middle_pid)
|
208
|
+
else # first child
|
209
|
+
clean_fork(&block) # detach into a grand child
|
210
|
+
exit
|
211
|
+
end
|
212
|
+
else
|
213
|
+
clean_fork(&block)
|
178
214
|
end
|
179
|
-
else
|
180
|
-
clean_fork(&block)
|
181
|
-
end
|
182
215
|
|
183
|
-
|
184
|
-
|
216
|
+
nil # it's tricky to return the PID
|
217
|
+
end
|
185
218
|
|
186
|
-
|
187
|
-
|
219
|
+
def time_now(int = false)
|
220
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, int ? :second : :float_second)
|
221
|
+
end
|
188
222
|
end
|
189
|
-
# :startdoc:
|
190
223
|
end
|
191
224
|
# :enddoc:
|
192
225
|
|
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.9.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-09-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: raindrops
|
@@ -49,6 +49,7 @@ extensions:
|
|
49
49
|
- ext/pitchfork_http/extconf.rb
|
50
50
|
extra_rdoc_files: []
|
51
51
|
files:
|
52
|
+
- ".devcontainer/devcontainer.json"
|
52
53
|
- ".git-blame-ignore-revs"
|
53
54
|
- ".gitattributes"
|
54
55
|
- ".github/workflows/ci.yml"
|
@@ -71,6 +72,7 @@ files:
|
|
71
72
|
- docs/REFORKING.md
|
72
73
|
- docs/SIGNALS.md
|
73
74
|
- docs/TUNING.md
|
75
|
+
- docs/WHY_MIGRATE.md
|
74
76
|
- examples/constant_caches.ru
|
75
77
|
- examples/echo.ru
|
76
78
|
- examples/hello.ru
|