pitchfork 0.11.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +6 -7
- data/docs/DESIGN.md +1 -1
- data/docs/FORK_SAFETY.md +8 -6
- data/docs/PHILOSOPHY.md +2 -2
- data/docs/REFORKING.md +6 -6
- data/docs/SIGNALS.md +3 -3
- data/ext/pitchfork_http/epollexclusive.h +15 -7
- data/ext/pitchfork_http/extconf.rb +2 -0
- data/lib/pitchfork/http_parser.rb +6 -1
- data/lib/pitchfork/http_server.rb +2 -1
- data/lib/pitchfork/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5dc129b2e6e6e940be0f9e388400a376614a65a125a64b68c73b919744c433b3
|
4
|
+
data.tar.gz: b12e1d5f360edc7159567060c095b40510e8e89c54dae058882742c29c830f78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a43b0204dc0e77a4d28007dbfd89e9645724a4198ea6ec7ffa0895aefaf075bd42d4303c37d0f7e61afb550b665b78ab6b22ca9f934ef2d3721883acf0a68cd
|
7
|
+
data.tar.gz: 7eb41807d6971ac948ee264b1f70fd2e16afcf8f289655073c876b752a53a405b9705e97509f5be82c519ee12694ecf1910b18a20cce8aad3ce9a4ffd3af9ac6
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pitchfork (0.11.
|
4
|
+
pitchfork (0.11.1)
|
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.
|
12
|
-
nio4r (2.
|
13
|
-
puma (6.
|
11
|
+
minitest (5.22.2)
|
12
|
+
nio4r (2.7.0)
|
13
|
+
puma (6.4.2)
|
14
14
|
nio4r (~> 2.0)
|
15
|
-
rack (3.0.
|
15
|
+
rack (3.0.9.1)
|
16
16
|
raindrops (0.20.1)
|
17
17
|
rake (13.0.6)
|
18
18
|
rake-compiler (1.2.1)
|
@@ -20,8 +20,7 @@ GEM
|
|
20
20
|
|
21
21
|
PLATFORMS
|
22
22
|
aarch64-linux
|
23
|
-
arm64-darwin
|
24
|
-
arm64-darwin-22
|
23
|
+
arm64-darwin
|
25
24
|
x86_64-linux
|
26
25
|
|
27
26
|
DEPENDENCIES
|
data/docs/DESIGN.md
CHANGED
@@ -49,7 +49,7 @@
|
|
49
49
|
nothing to accept().
|
50
50
|
|
51
51
|
* Since non-blocking accept() is used, there can be a thundering
|
52
|
-
herd when an occasional client connects when application
|
52
|
+
herd when an occasional client connects when the application
|
53
53
|
*is not busy*. The thundering herd problem should not affect
|
54
54
|
applications that are running all the time since worker processes
|
55
55
|
will only select()/accept() outside of the application dispatch.
|
data/docs/FORK_SAFETY.md
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
Because `pitchfork` is a preforking server, your application code and libraries
|
4
4
|
must be fork safe.
|
5
5
|
|
6
|
-
Generally code might be fork-unsafe for one of two reasons
|
6
|
+
Generally, code might be fork-unsafe for one of two reasons.
|
7
7
|
|
8
8
|
## Inherited Connection
|
9
9
|
|
10
|
-
When a process is forked, any open file
|
10
|
+
When a process is forked, any open file descriptors (sockets, files, pipes, etc)
|
11
11
|
end up shared between the parent and child process. This is never what you
|
12
12
|
want, so any code keeping persistent connections should close them either
|
13
13
|
before or after the fork happens.
|
@@ -39,7 +39,7 @@ The documentation of any database client or network library you use should be
|
|
39
39
|
read with care to figure out how to disconnect it, and whether it is best to
|
40
40
|
do it before or after fork.
|
41
41
|
|
42
|
-
Since the most common Ruby application servers `Puma`, `Unicorn` and `Passenger`
|
42
|
+
Since the most common Ruby application servers like `Puma`, `Unicorn` and `Passenger`
|
43
43
|
have forking at least as an option, the requirements are generally well documented.
|
44
44
|
|
45
45
|
However what is novel with `Pitchfork`, is that processes can be forked more than once.
|
@@ -61,7 +61,7 @@ So any libraries that spawn a background thread for periodical work may need to
|
|
61
61
|
that a fork happened and that it should restart its thread.
|
62
62
|
|
63
63
|
Just like with connections, some libraries take on them to automatically restart their background
|
64
|
-
thread when they detect a fork happened.
|
64
|
+
thread when they detect that a fork happened.
|
65
65
|
|
66
66
|
# Refork Safety
|
67
67
|
|
@@ -71,7 +71,7 @@ but not work in Pitchfork when reforking is enabled.
|
|
71
71
|
This is because it is not uncommon for network connections or background threads to only be
|
72
72
|
initialized upon the first request. As such they're not inherited on the first fork.
|
73
73
|
|
74
|
-
However when reforking is enabled, new processes
|
74
|
+
However when reforking is enabled, new processes are forked out of a warmed up process, as such
|
75
75
|
any lazily created connection is much more likely to have been created.
|
76
76
|
|
77
77
|
As such, if you enable reforking for the first time, it is heavily recommended to first do it
|
@@ -88,4 +88,6 @@ impact of discovering such bug.
|
|
88
88
|
- The `ruby-vips` gem binds the `libvips` image processing library that isn't fork safe.
|
89
89
|
(https://github.com/libvips/libvips/discussions/3577)
|
90
90
|
|
91
|
-
|
91
|
+
- Any gem binding with `libgobject`, such as the `gda` gem, likely aren't fork safe.
|
92
|
+
|
93
|
+
No other gem is known to be incompatible for now, but if you find one, please open an issue to add it to the list.
|
data/docs/PHILOSOPHY.md
CHANGED
@@ -80,8 +80,8 @@ Suitable options include `nginx`, `caddy` and likely several others.
|
|
80
80
|
One of the main advantages of threaded servers over preforking servers is their
|
81
81
|
lower memory usage.
|
82
82
|
|
83
|
-
However `pitchfork` solves this with its reforking feature. If enabled and properly configured
|
84
|
-
it very significantly increase Copy-on-Write performance, closing the gap with threaded servers.
|
83
|
+
However `pitchfork` solves this with its reforking feature. If enabled and properly configured,
|
84
|
+
it can very significantly increase Copy-on-Write performance, closing the gap with threaded servers.
|
85
85
|
|
86
86
|
## Assume Modern Deployment Methods
|
87
87
|
|
data/docs/REFORKING.md
CHANGED
@@ -21,7 +21,7 @@ forked processes are essentially free.
|
|
21
21
|
So in theory, preforking servers shouldn't use more memory than threaded servers.
|
22
22
|
|
23
23
|
However, in a Ruby process, there is generally a lot of memory regions that are lazily initialized.
|
24
|
-
This
|
24
|
+
This includes the Ruby Virtual Machine inline caches, JITed code if you use YJIT, and also
|
25
25
|
some common patterns in applications, such as memoization:
|
26
26
|
|
27
27
|
```ruby
|
@@ -35,13 +35,13 @@ end
|
|
35
35
|
However, since workers are forked right after boot, most codepaths have never been executed,
|
36
36
|
so most of these caches are not yet initialized.
|
37
37
|
|
38
|
-
As more code
|
39
|
-
of shared memory of a
|
38
|
+
As more code gets executed, more and more memory pages get invalidated. If you were to graph the ratio
|
39
|
+
of shared memory of a Ruby process over time, you'd likely see a logarithmic curve, with a quick degradation
|
40
40
|
during the first few processed request as the most common code paths get warmed up, and then a stabilization.
|
41
41
|
|
42
42
|
### Reforking
|
43
43
|
|
44
|
-
That is where reforking helps. Since
|
44
|
+
That is where reforking helps. Since most of these invalidations only happen when a code path is executed for the
|
45
45
|
first time, if you take a warmed up worker out of rotation, and use it to fork new workers, warmed up pages will
|
46
46
|
be shared again, and most of them won't be invalidated anymore.
|
47
47
|
|
@@ -123,9 +123,9 @@ PID COMMAND
|
|
123
123
|
```
|
124
124
|
|
125
125
|
However the `pitchfork` master process registers itself as a "child subreaper" via [`PR_SET_CHILD_SUBREAPER`](https://man7.org/linux/man-pages/man2/prctl.2.html).
|
126
|
-
This means any descendant process that is orphaned will be re-parented as a child of the master rather than a child of the init process (pid 1).
|
126
|
+
This means that any descendant process that is orphaned will be re-parented as a child of the master process rather than a child of the init process (pid 1).
|
127
127
|
|
128
|
-
With this in mind, the mold forks twice to create an orphaned process that will get re-attached to the master,
|
128
|
+
With this in mind, the mold forks twice to create an orphaned process that will get re-attached to the master process,
|
129
129
|
effectively forking a sibling rather than a child. Similarly, workers do the same when forking new molds.
|
130
130
|
This technique eases killing previous generations of molds and workers.
|
131
131
|
|
data/docs/SIGNALS.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
## Signal handling
|
2
2
|
|
3
|
-
In general, signals need only be sent to the master process. However,
|
3
|
+
In general, signals need to only be sent to the master process. However,
|
4
4
|
the signals Pitchfork uses internally to communicate with the worker
|
5
5
|
processes are documented here as well.
|
6
6
|
|
@@ -22,8 +22,8 @@ processes are documented here as well.
|
|
22
22
|
### Worker Processes
|
23
23
|
|
24
24
|
Note: the master uses a pipe to signal workers
|
25
|
-
instead of `kill(2)` for most cases. Using signals still
|
26
|
-
remains supported for external tools/libraries
|
25
|
+
instead of `kill(2)` for most cases. Using signals still works and
|
26
|
+
remains supported for external tools/libraries, however.
|
27
27
|
|
28
28
|
Sending signals directly to the worker processes should not normally be
|
29
29
|
needed. If the master process is running, any exited worker will be
|
@@ -21,6 +21,16 @@
|
|
21
21
|
# define USE_EPOLL (0)
|
22
22
|
#endif
|
23
23
|
|
24
|
+
#ifndef HAVE_RB_IO_DESCRIPTOR /* Ruby < 3.1 */
|
25
|
+
static int rb_io_descriptor(VALUE io)
|
26
|
+
{
|
27
|
+
rb_io_t *fptr;
|
28
|
+
GetOpenFile(io, fptr);
|
29
|
+
rb_io_check_closed(fptr);
|
30
|
+
return fptr->fd;
|
31
|
+
}
|
32
|
+
#endif
|
33
|
+
|
24
34
|
#if USE_EPOLL
|
25
35
|
/*
|
26
36
|
* :nodoc:
|
@@ -54,8 +64,7 @@ static VALUE prep_readers(VALUE cls, VALUE readers)
|
|
54
64
|
*/
|
55
65
|
e.events = EPOLLEXCLUSIVE | EPOLLIN;
|
56
66
|
io = rb_io_get_io(io);
|
57
|
-
|
58
|
-
rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fptr->fd, &e);
|
67
|
+
rc = epoll_ctl(epfd, EPOLL_CTL_ADD, rb_io_descriptor(io), &e);
|
59
68
|
if (rc < 0) rb_sys_fail("epoll_ctl");
|
60
69
|
}
|
61
70
|
return epio;
|
@@ -65,7 +74,7 @@ static VALUE prep_readers(VALUE cls, VALUE readers)
|
|
65
74
|
#if USE_EPOLL
|
66
75
|
struct ep_wait {
|
67
76
|
struct epoll_event event;
|
68
|
-
|
77
|
+
VALUE io;
|
69
78
|
int timeout_msec;
|
70
79
|
};
|
71
80
|
|
@@ -79,7 +88,7 @@ static void *do_wait(void *ptr) /* runs w/o GVL */
|
|
79
88
|
* at-a-time (c.f. fs/eventpoll.c in linux.git, it's quite
|
80
89
|
* easy-to-understand for anybody familiar with Ruby C).
|
81
90
|
*/
|
82
|
-
return (void *)(long)epoll_wait(epw->
|
91
|
+
return (void *)(long)epoll_wait(rb_io_descriptor(epw->io), &epw->event, 1,
|
83
92
|
epw->timeout_msec);
|
84
93
|
}
|
85
94
|
|
@@ -93,11 +102,10 @@ get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec)
|
|
93
102
|
|
94
103
|
Check_Type(ready, T_ARRAY);
|
95
104
|
Check_Type(readers, T_ARRAY);
|
96
|
-
|
97
|
-
GetOpenFile(epio, epw.fptr);
|
98
|
-
|
105
|
+
epw.io = rb_io_get_io(epio);
|
99
106
|
epw.timeout_msec = NUM2INT(timeout_msec);
|
100
107
|
n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL);
|
108
|
+
RB_GC_GUARD(epw.io);
|
101
109
|
if (n < 0) {
|
102
110
|
if (errno != EINTR) rb_sys_fail("epoll_wait");
|
103
111
|
} else if (n > 0) { /* maxevents is hardcoded to 1 */
|
@@ -3,6 +3,8 @@ require 'mkmf'
|
|
3
3
|
|
4
4
|
have_const("PR_SET_CHILD_SUBREAPER", "sys/prctl.h")
|
5
5
|
have_func("rb_enc_interned_str", "ruby.h") # Ruby 3.0+
|
6
|
+
have_func("rb_io_descriptor", "ruby.h") # Ruby 3.1+
|
7
|
+
|
6
8
|
if RUBY_VERSION.start_with?('3.0.')
|
7
9
|
# https://bugs.ruby-lang.org/issues/18772
|
8
10
|
$CFLAGS << ' -DRB_ENC_INTERNED_STR_NULL_CHECK=1 '
|
@@ -193,7 +193,12 @@ module Pitchfork
|
|
193
193
|
|
194
194
|
# called by ext/pitchfork_http/pitchfork_http.rl via rb_funcall
|
195
195
|
def self.is_chunked?(v) # :nodoc:
|
196
|
-
vals = v.split(
|
196
|
+
vals = v.split(',')
|
197
|
+
vals.each do |val|
|
198
|
+
val.strip!
|
199
|
+
val.downcase!
|
200
|
+
end
|
201
|
+
|
197
202
|
if vals.pop == 'chunked'.freeze
|
198
203
|
return true unless vals.include?('chunked'.freeze)
|
199
204
|
raise Pitchfork::HttpParserError, 'double chunked', []
|
@@ -722,6 +722,7 @@ module Pitchfork
|
|
722
722
|
|
723
723
|
proc_name status: "requests: #{worker.requests_count}, processing: #{env["PATH_INFO"]}"
|
724
724
|
|
725
|
+
env["pitchfork.worker"] = worker
|
725
726
|
timeout_handler.rack_env = env
|
726
727
|
env["pitchfork.timeout"] = timeout_handler
|
727
728
|
|
@@ -1049,7 +1050,7 @@ module Pitchfork
|
|
1049
1050
|
exit
|
1050
1051
|
end
|
1051
1052
|
else
|
1052
|
-
clean_fork(&block)
|
1053
|
+
Pitchfork.clean_fork(&block)
|
1053
1054
|
end
|
1054
1055
|
end
|
1055
1056
|
|
data/lib/pitchfork/version.rb
CHANGED
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.11.
|
4
|
+
version: 0.11.1
|
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: 2024-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: raindrops
|
@@ -138,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: '0'
|
140
140
|
requirements: []
|
141
|
-
rubygems_version: 3.
|
141
|
+
rubygems_version: 3.5.5
|
142
142
|
signing_key:
|
143
143
|
specification_version: 4
|
144
144
|
summary: Rack HTTP server for fast clients and Unix
|