pitchfork 0.1.2 → 0.2.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/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +7 -0
- data/Dockerfile +1 -1
- data/Gemfile.lock +7 -6
- data/Rakefile +10 -2
- data/docs/CONFIGURATION.md +30 -32
- data/examples/pitchfork.conf.rb +2 -2
- data/exe/pitchfork +1 -8
- data/ext/pitchfork_http/epollexclusive.h +13 -17
- data/ext/pitchfork_http/pitchfork_http.c +196 -192
- data/ext/pitchfork_http/pitchfork_http.rl +23 -19
- data/lib/pitchfork/configurator.rb +28 -36
- data/lib/pitchfork/flock.rb +51 -0
- data/lib/pitchfork/http_server.rb +87 -70
- data/lib/pitchfork/refork_condition.rb +3 -3
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +21 -16
- data/lib/pitchfork.rb +25 -31
- metadata +4 -4
- data/lib/pitchfork/mold_selector.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9f15127612a30958ee43b9667990a0728b5fdfc46e13285bdfa1706085eb67d
|
4
|
+
data.tar.gz: '08ba863ccf209644093b83ee905305615cfef046c4cd5e11e74f16540c722ad7'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c73e64f686acde16c5bb50df83a3a8596d9a3f578318cdc55bc3d8dbee1a0d7dd8534e627b5baa4994f3f30d4917ffe7d4e5d405850d068f7523cf52c21113b
|
7
|
+
data.tar.gz: 80c872414fe8289c7c98cb9949309f76aeec63b3f3de85f09349839f873721cdb41c12bfa4620c7863d62526533c7e4ee900b7f5461917d348fa79d4c5977e4a
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
- Remove default middlewares.
|
4
|
+
- Refork indefinitely when `refork_after` is set, unless the last element is `false`.
|
5
|
+
- Remove `mold_selector`. The promotion logic has been moved inside workers (#38).
|
6
|
+
- Add the `after_promotion` callback.
|
7
|
+
- Removed the `before_fork` callback.
|
8
|
+
- Fork workers and molds with a clean stack to allow more generations. (#30)
|
9
|
+
|
3
10
|
# 0.1.2
|
4
11
|
|
5
12
|
- Improve Ruby 3.2 and Rack 3 compatibility.
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pitchfork (0.
|
4
|
+
pitchfork (0.2.0)
|
5
5
|
rack (>= 2.0)
|
6
6
|
raindrops (~> 0.7)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
minitest (5.
|
11
|
+
minitest (5.15.0)
|
12
12
|
nio4r (2.5.8)
|
13
|
-
puma (
|
13
|
+
puma (6.1.1)
|
14
14
|
nio4r (~> 2.0)
|
15
|
-
rack (3.0.
|
16
|
-
raindrops (0.20.
|
15
|
+
rack (3.0.7)
|
16
|
+
raindrops (0.20.1)
|
17
17
|
rake (13.0.6)
|
18
|
-
rake-compiler (1.2.
|
18
|
+
rake-compiler (1.2.1)
|
19
19
|
rake
|
20
20
|
|
21
21
|
PLATFORMS
|
22
22
|
aarch64-linux
|
23
23
|
arm64-darwin-21
|
24
|
+
arm64-darwin-22
|
24
25
|
x86_64-linux
|
25
26
|
|
26
27
|
DEPENDENCIES
|
data/Rakefile
CHANGED
@@ -11,6 +11,14 @@ Rake::TestTask.new("test:unit") do |t|
|
|
11
11
|
t.warning = true
|
12
12
|
end
|
13
13
|
|
14
|
+
Rake::TestTask.new("test:integration") do |t|
|
15
|
+
t.libs << "test"
|
16
|
+
t.libs << "lib"
|
17
|
+
t.test_files = FileList["test/integration/**/test_*.rb"]
|
18
|
+
t.options = '-v' if ENV['CI'] || ENV['VERBOSE']
|
19
|
+
t.warning = true
|
20
|
+
end
|
21
|
+
|
14
22
|
namespace :test do
|
15
23
|
# It's not so much that these tests are slow, but they tend to fork
|
16
24
|
# and/or register signal handlers, so they if something goes wrong
|
@@ -32,7 +40,7 @@ namespace :test do
|
|
32
40
|
# It's quite hard to work with and it would be good to convert all this
|
33
41
|
# to Ruby integration tests, but while pitchfork is a moving target it's
|
34
42
|
# preferable to edit the test suite as little as possible.
|
35
|
-
task
|
43
|
+
task legacy_integration: :compile do
|
36
44
|
File.write("test/integration/random_blob", File.read("/dev/random", 1_000_000))
|
37
45
|
lib = File.expand_path("lib", __dir__)
|
38
46
|
path = "#{File.expand_path("exe", __dir__)}:#{ENV["PATH"]}"
|
@@ -67,6 +75,6 @@ task :ragel do
|
|
67
75
|
end
|
68
76
|
end
|
69
77
|
|
70
|
-
task test: %i(test:unit test:slow test:integration)
|
78
|
+
task test: %i(test:unit test:slow test:integration test:legacy_integration)
|
71
79
|
|
72
80
|
task default: %i(ragel compile test)
|
data/docs/CONFIGURATION.md
CHANGED
@@ -238,20 +238,27 @@ application that are used in hooks.
|
|
238
238
|
`pitchfork` also don't attempt to rescue hook errors. Raising from a worker hook will crash the worker,
|
239
239
|
and raising from a master hook will bring the whole cluster down.
|
240
240
|
|
241
|
-
### `
|
241
|
+
### `after_promotion`
|
242
242
|
|
243
243
|
```ruby
|
244
|
-
|
244
|
+
after_promotion do |server, mold|
|
245
245
|
Database.disconnect!
|
246
246
|
end
|
247
247
|
```
|
248
248
|
|
249
|
-
Called in the context of the
|
249
|
+
Called in the context of the mold when initially spawned or promotted.
|
250
|
+
|
251
|
+
It's usage is similar to a `before_fork` callback found on other servers
|
252
|
+
but it is called once on promotion rather than before forking each worker.
|
253
|
+
|
250
254
|
For most protocols connections can be closed after fork, but some
|
251
255
|
stateful protocols require to close connections before fork.
|
252
256
|
|
253
257
|
That is the case for instance of many SQL databases protocols.
|
254
258
|
|
259
|
+
This is also the callback in which memory optimizations, such as
|
260
|
+
heap compaction should be done.
|
261
|
+
|
255
262
|
### `after_fork`
|
256
263
|
|
257
264
|
```ruby
|
@@ -263,6 +270,19 @@ end
|
|
263
270
|
Called in the worker after forking. Generally used to close inherited connections
|
264
271
|
or to restart backgrounds threads for libraries that don't do it automatically.
|
265
272
|
|
273
|
+
### `after_promotion`
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
after_promotion do |server, mold|
|
277
|
+
NetworkClient.disconnect!
|
278
|
+
4.times { GC.start } # promote surviving objects to oldgen
|
279
|
+
GC.compact
|
280
|
+
end
|
281
|
+
```
|
282
|
+
|
283
|
+
Called in the worker after it was promoted into a mold. Generally used to shutdown
|
284
|
+
open connections and file descriptors, as well as to perform memory optimiations
|
285
|
+
such as compacting the heap, trimming memory etc.
|
266
286
|
|
267
287
|
### `after_worker_ready`
|
268
288
|
|
@@ -302,9 +322,15 @@ once at least one worker processed `50` requests.
|
|
302
322
|
|
303
323
|
Each element is a limit for the next generation. On the example above a new generation
|
304
324
|
is triggered when a worker has processed 50 requests, then the second generation when
|
305
|
-
a worker from the new generation processed an additional 100 requests and finally after
|
325
|
+
a worker from the new generation processed an additional 100 requests and finally after *every*
|
306
326
|
1000 requests.
|
307
327
|
|
328
|
+
If you don't want unlimited reforking, you can set `false` as the last element of the array:
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
refork_after [50, 100, 1000, false]
|
332
|
+
```
|
333
|
+
|
308
334
|
Generally speaking Copy-on-Write efficiency tend to degrade fast during the early requests,
|
309
335
|
and then less and less frequently.
|
310
336
|
|
@@ -314,36 +340,8 @@ By default automatic reforking isn't enabled.
|
|
314
340
|
|
315
341
|
Make sure to read the [fork safety guide](FORK_SAFETY.md) before enabling reforking.
|
316
342
|
|
317
|
-
### `mold_selector`
|
318
|
-
|
319
|
-
Sets the mold selector implementation.
|
320
|
-
|
321
|
-
```ruby
|
322
|
-
mold_selector do |server|
|
323
|
-
candidate = server.children.workers.sample # return an random worker
|
324
|
-
server.logger.info("worker=#{worker.nr} pid=#{worker.pid} selected as new mold")
|
325
|
-
candidate
|
326
|
-
end
|
327
|
-
```
|
328
|
-
|
329
|
-
The has access to `server.children` a `Pitchfork::Children` instance.
|
330
|
-
This object can be used to introspect the state of the cluster and select the most
|
331
|
-
appropriate worker to be used as the new mold from which workers will be reforked.
|
332
|
-
|
333
|
-
The default implementation selects the worker with the least
|
334
|
-
amount of shared memory. This heuristic aim to select the most
|
335
|
-
warmed up worker.
|
336
|
-
|
337
|
-
This should be considered a very advanced API and it is discouraged
|
338
|
-
to use it unless you are confident you have a clear understanding
|
339
|
-
of pitchfork's architecture.
|
340
|
-
|
341
343
|
## Rack Features
|
342
344
|
|
343
|
-
### `default_middleware`
|
344
|
-
|
345
|
-
Sets whether to add Pitchfork's default middlewares. Defaults to `true`.
|
346
|
-
|
347
345
|
### `early_hints`
|
348
346
|
|
349
347
|
Sets whether to enable the proposed early hints Rack API. Defaults to `false`.
|
data/examples/pitchfork.conf.rb
CHANGED
@@ -23,9 +23,9 @@ check_client_connection false
|
|
23
23
|
# local variable to guard against running a hook multiple times
|
24
24
|
run_once = true
|
25
25
|
|
26
|
-
|
26
|
+
after_promotion do |server, worker|
|
27
27
|
# the following is highly recommended for Rails
|
28
|
-
# as there's no need for the
|
28
|
+
# as there's no need for the mold process to hold a connection
|
29
29
|
defined?(ActiveRecord::Base) and
|
30
30
|
ActiveRecord::Base.connection.disconnect!
|
31
31
|
|
data/exe/pitchfork
CHANGED
@@ -6,7 +6,6 @@ require 'optparse'
|
|
6
6
|
ENV["RACK_ENV"] ||= "development"
|
7
7
|
rackup_opts = Pitchfork::Configurator::RACKUP
|
8
8
|
options = rackup_opts[:options]
|
9
|
-
set_no_default_middleware = true
|
10
9
|
|
11
10
|
op = OptionParser.new("", 24, ' ') do |opts|
|
12
11
|
cmd = File.basename($0)
|
@@ -59,11 +58,6 @@ op = OptionParser.new("", 24, ' ') do |opts|
|
|
59
58
|
ENV["RACK_ENV"] = e
|
60
59
|
end
|
61
60
|
|
62
|
-
opts.on("-N", "--no-default-middleware",
|
63
|
-
"do not load middleware implied by RACK_ENV") do |e|
|
64
|
-
rackup_opts[:no_default_middleware] = true if set_no_default_middleware
|
65
|
-
end
|
66
|
-
|
67
61
|
opts.on("-s", "--server SERVER",
|
68
62
|
"this flag only exists for compatibility") do |s|
|
69
63
|
warn "-s/--server only exists for compatibility with rackup"
|
@@ -101,7 +95,6 @@ op = OptionParser.new("", 24, ' ') do |opts|
|
|
101
95
|
opts.parse! ARGV
|
102
96
|
end
|
103
97
|
|
104
|
-
set_no_default_middleware = false
|
105
98
|
app = Pitchfork.builder(ARGV[0] || 'config.ru', op)
|
106
99
|
op = nil
|
107
100
|
|
@@ -113,4 +106,4 @@ if $DEBUG
|
|
113
106
|
})
|
114
107
|
end
|
115
108
|
|
116
|
-
Pitchfork::HttpServer.new(app, options).start.join
|
109
|
+
exit(Pitchfork::HttpServer.new(app, options).start(false).join)
|
@@ -64,9 +64,8 @@ static VALUE prep_readers(VALUE cls, VALUE readers)
|
|
64
64
|
|
65
65
|
#if USE_EPOLL
|
66
66
|
struct ep_wait {
|
67
|
-
struct epoll_event
|
67
|
+
struct epoll_event event;
|
68
68
|
rb_io_t *fptr;
|
69
|
-
int maxevents;
|
70
69
|
int timeout_msec;
|
71
70
|
};
|
72
71
|
|
@@ -74,8 +73,14 @@ static void *do_wait(void *ptr) /* runs w/o GVL */
|
|
74
73
|
{
|
75
74
|
struct ep_wait *epw = ptr;
|
76
75
|
|
77
|
-
|
78
|
-
|
76
|
+
/*
|
77
|
+
* Linux delivers epoll events in the order received, and using
|
78
|
+
* maxevents=1 ensures we pluck one item off ep->rdllist
|
79
|
+
* at-a-time (c.f. fs/eventpoll.c in linux.git, it's quite
|
80
|
+
* easy-to-understand for anybody familiar with Ruby C).
|
81
|
+
*/
|
82
|
+
return (void *)(long)epoll_wait(epw->fptr->fd, &epw->event, 1,
|
83
|
+
epw->timeout_msec);
|
79
84
|
}
|
80
85
|
|
81
86
|
/* :nodoc: */
|
@@ -84,14 +89,10 @@ static VALUE
|
|
84
89
|
get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec)
|
85
90
|
{
|
86
91
|
struct ep_wait epw;
|
87
|
-
long
|
88
|
-
VALUE buf;
|
92
|
+
long n;
|
89
93
|
|
90
94
|
Check_Type(ready, T_ARRAY);
|
91
95
|
Check_Type(readers, T_ARRAY);
|
92
|
-
epw.maxevents = RARRAY_LENINT(readers);
|
93
|
-
buf = rb_str_buf_new(sizeof(struct epoll_event) * epw.maxevents);
|
94
|
-
epw.events = (struct epoll_event *)RSTRING_PTR(buf);
|
95
96
|
epio = rb_io_get_io(epio);
|
96
97
|
GetOpenFile(epio, epw.fptr);
|
97
98
|
|
@@ -99,17 +100,12 @@ get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec)
|
|
99
100
|
n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL);
|
100
101
|
if (n < 0) {
|
101
102
|
if (errno != EINTR) rb_sys_fail("epoll_wait");
|
102
|
-
|
103
|
-
|
104
|
-
/* Linux delivers events in order received */
|
105
|
-
for (i = 0; i < n; i++) {
|
106
|
-
struct epoll_event *ev = &epw.events[i];
|
107
|
-
VALUE obj = rb_ary_entry(readers, ev->data.u64);
|
103
|
+
} else if (n > 0) { /* maxevents is hardcoded to 1 */
|
104
|
+
VALUE obj = rb_ary_entry(readers, epw.event.data.u64);
|
108
105
|
|
109
106
|
if (RTEST(obj))
|
110
107
|
rb_ary_push(ready, obj);
|
111
|
-
}
|
112
|
-
rb_str_resize(buf, 0);
|
108
|
+
} /* n == 0 : timeout */
|
113
109
|
return Qfalse;
|
114
110
|
}
|
115
111
|
#endif /* USE_EPOLL */
|