pitchfork 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c060e231677f29e29d84ea24a0d3bad9f8a34b8474245530bc229b4c64abd89
4
- data.tar.gz: 3c0f01f68d4abacc0f6fc1898937f4f614291ea59e716c3e9423f1919dd70607
3
+ metadata.gz: f9f15127612a30958ee43b9667990a0728b5fdfc46e13285bdfa1706085eb67d
4
+ data.tar.gz: '08ba863ccf209644093b83ee905305615cfef046c4cd5e11e74f16540c722ad7'
5
5
  SHA512:
6
- metadata.gz: fca437b006db57240a9cacb91c365894741c340a351bc3770b3e131e2789712cac6e00c029596f45f2ee6fc2b68bd3464bd3af2450a889a4d105a816c5fdc2a5
7
- data.tar.gz: 49ac19bfe8caa4b62d513a65ba1103a9610ec8034170636420a6bb93edd99c48e07f0436771a2fa73c9f67181e0f69a28bb6f7bea37519631445ebe00e6470f2
6
+ metadata.gz: 6c73e64f686acde16c5bb50df83a3a8596d9a3f578318cdc55bc3d8dbee1a0d7dd8534e627b5baa4994f3f30d4917ffe7d4e5d405850d068f7523cf52c21113b
7
+ data.tar.gz: 80c872414fe8289c7c98cb9949309f76aeec63b3f3de85f09349839f873721cdb41c12bfa4620c7863d62526533c7e4ee900b7f5461917d348fa79d4c5977e4a
@@ -11,7 +11,7 @@ jobs:
11
11
  matrix:
12
12
  os: ["ubuntu-latest"]
13
13
  redis: ["6.2"]
14
- ruby: ["3.1", "3.0", "2.7", "2.6"]
14
+ ruby: ["3.2", "3.1", "3.0", "2.7", "2.6"]
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Check out code
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
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
+
10
+ # 0.1.2
11
+
12
+ - Improve Ruby 3.2 and Rack 3 compatibility.
13
+
14
+ # 0.1.1
15
+
3
16
  - Fix `extconf.rb` to move the extension in the right place on gem install. (#18)
4
17
 
5
18
  # 0.1.0
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:3.1
1
+ FROM ruby:3.2
2
2
  RUN apt-get update -y && apt-get install -y ragel socat netcat smem apache2-utils
3
3
  WORKDIR /app
4
4
  CMD [ "bash" ]
data/Gemfile.lock CHANGED
@@ -1,26 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pitchfork (0.1.1)
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.16.3)
11
+ minitest (5.15.0)
12
12
  nio4r (2.5.8)
13
- puma (5.6.5)
13
+ puma (6.1.1)
14
14
  nio4r (~> 2.0)
15
- rack (3.0.0)
16
- raindrops (0.20.0)
15
+ rack (3.0.7)
16
+ raindrops (0.20.1)
17
17
  rake (13.0.6)
18
- rake-compiler (1.2.0)
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 integration: :compile do
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)
@@ -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
- ### `before_fork`
241
+ ### `after_promotion`
242
242
 
243
243
  ```ruby
244
- before_fork do |server, worker|
244
+ after_promotion do |server, mold|
245
245
  Database.disconnect!
246
246
  end
247
247
  ```
248
248
 
249
- Called in the context of the parent or mold.
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 another
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`.
@@ -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
- before_fork do |server, worker|
26
+ after_promotion do |server, worker|
27
27
  # the following is highly recommended for Rails
28
- # as there's no need for the master process to hold a connection
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 *events;
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
- return (void *)(long)epoll_wait(epw->fptr->fd, epw->events,
78
- epw->maxevents, epw->timeout_msec);
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 i, n;
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
- n = 0;
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 */