pitchfork 0.7.0 → 0.9.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.
Potentially problematic release.
This version of pitchfork might be problematic. Click here for more details.
- 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
|