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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +13 -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_response.rb +13 -6
- data/lib/pitchfork/http_server.rb +91 -85
- 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
@@ -224,7 +224,7 @@ static int is_chunked(VALUE v)
|
|
224
224
|
return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse;
|
225
225
|
}
|
226
226
|
|
227
|
-
static void write_value(struct http_parser *hp,
|
227
|
+
static void write_value(VALUE self, struct http_parser *hp,
|
228
228
|
const char *buffer, const char *p)
|
229
229
|
{
|
230
230
|
VALUE f = find_common_field(PTR_TO(start.field), hp->s.field_len);
|
@@ -244,7 +244,7 @@ static void write_value(struct http_parser *hp,
|
|
244
244
|
* rack env variable.
|
245
245
|
*/
|
246
246
|
if (CONST_MEM_EQ("VERSION", field, flen)) {
|
247
|
-
hp->cont
|
247
|
+
RB_OBJ_WRITE(self, &hp->cont, Qnil);
|
248
248
|
return;
|
249
249
|
}
|
250
250
|
f = uncommon_field(field, flen);
|
@@ -296,16 +296,16 @@ static void write_value(struct http_parser *hp,
|
|
296
296
|
|
297
297
|
e = rb_hash_aref(hp->env, f);
|
298
298
|
if (NIL_P(e)) {
|
299
|
-
hp->cont
|
299
|
+
RB_OBJ_WRITE(self, &hp->cont, rb_hash_aset(hp->env, f, v));
|
300
300
|
} else if (f == g_http_host) {
|
301
301
|
/*
|
302
302
|
* ignored, absolute URLs in REQUEST_URI take precedence over
|
303
303
|
* the Host: header (ref: rfc 2616, section 5.2.1)
|
304
304
|
*/
|
305
|
-
|
305
|
+
RB_OBJ_WRITE(self, &hp->cont, Qnil);
|
306
306
|
} else {
|
307
307
|
rb_str_buf_cat(e, ",", 1);
|
308
|
-
hp->cont
|
308
|
+
RB_OBJ_WRITE(self, &hp->cont, rb_str_buf_append(e, v));
|
309
309
|
}
|
310
310
|
}
|
311
311
|
|
@@ -321,7 +321,7 @@ static void write_value(struct http_parser *hp,
|
|
321
321
|
action downcase_char { downcase_char(deconst(fpc)); }
|
322
322
|
action write_field { hp->s.field_len = LEN(start.field, fpc); }
|
323
323
|
action start_value { MARK(mark, fpc); }
|
324
|
-
action write_value { write_value(hp, buffer, fpc); }
|
324
|
+
action write_value { write_value(self, hp, buffer, fpc); }
|
325
325
|
action write_cont_value { write_cont_value(hp, buffer, fpc); }
|
326
326
|
action request_method { request_method(hp, PTR_TO(mark), LEN(mark, fpc)); }
|
327
327
|
action scheme {
|
@@ -439,7 +439,7 @@ static void http_parser_init(struct http_parser *hp)
|
|
439
439
|
|
440
440
|
/** exec **/
|
441
441
|
static void
|
442
|
-
http_parser_execute(struct http_parser *hp, char *buffer, size_t len)
|
442
|
+
http_parser_execute(VALUE self, struct http_parser *hp, char *buffer, size_t len)
|
443
443
|
{
|
444
444
|
const char *p, *pe;
|
445
445
|
int cs = hp->cs;
|
@@ -485,9 +485,13 @@ static size_t hp_memsize(const void *ptr)
|
|
485
485
|
}
|
486
486
|
|
487
487
|
static const rb_data_type_t hp_type = {
|
488
|
-
|
489
|
-
|
490
|
-
|
488
|
+
.wrap_struct_name = "pitchfork_http_parser",
|
489
|
+
.function = {
|
490
|
+
.dmark = hp_mark,
|
491
|
+
.dfree = RUBY_TYPED_DEFAULT_FREE,
|
492
|
+
.dsize = hp_memsize,
|
493
|
+
},
|
494
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
491
495
|
};
|
492
496
|
|
493
497
|
static struct http_parser *data_get(VALUE self)
|
@@ -618,8 +622,8 @@ static VALUE HttpParser_init(VALUE self)
|
|
618
622
|
struct http_parser *hp = data_get(self);
|
619
623
|
|
620
624
|
http_parser_init(hp);
|
621
|
-
hp->buf
|
622
|
-
hp->env
|
625
|
+
RB_OBJ_WRITE(self, &hp->buf, rb_str_new(NULL, 0));
|
626
|
+
RB_OBJ_WRITE(self, &hp->env, rb_hash_new());
|
623
627
|
|
624
628
|
return self;
|
625
629
|
}
|
@@ -700,7 +704,7 @@ static VALUE HttpParser_parse(VALUE self)
|
|
700
704
|
if (HP_FL_TEST(hp, TO_CLEAR))
|
701
705
|
HttpParser_clear(self);
|
702
706
|
|
703
|
-
http_parser_execute(hp, RSTRING_PTR(data), RSTRING_LEN(data));
|
707
|
+
http_parser_execute(self, hp, RSTRING_PTR(data), RSTRING_LEN(data));
|
704
708
|
if (hp->offset > MAX_HEADER_LEN)
|
705
709
|
parser_raise(e413, "HTTP header is too large");
|
706
710
|
|
@@ -756,8 +760,8 @@ static VALUE HttpParser_headers(VALUE self, VALUE env, VALUE buf)
|
|
756
760
|
{
|
757
761
|
struct http_parser *hp = data_get(self);
|
758
762
|
|
759
|
-
hp->
|
760
|
-
hp->
|
763
|
+
RB_OBJ_WRITE(self, &hp->buf, buf);
|
764
|
+
RB_OBJ_WRITE(self, &hp->env, env);
|
761
765
|
|
762
766
|
return HttpParser_parse(self);
|
763
767
|
}
|
@@ -885,9 +889,9 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE dst, VALUE src)
|
|
885
889
|
rb_str_resize(dst, srclen); /* we can never copy more than srclen bytes */
|
886
890
|
|
887
891
|
hp->s.dest_offset = 0;
|
888
|
-
hp->cont
|
889
|
-
hp->buf
|
890
|
-
http_parser_execute(hp, srcptr, srclen);
|
892
|
+
RB_OBJ_WRITE(self, &hp->cont, dst);
|
893
|
+
RB_OBJ_WRITE(self, &hp->buf, src);
|
894
|
+
http_parser_execute(self, hp, srcptr, srclen);
|
891
895
|
if (hp->cs == http_parser_error)
|
892
896
|
parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
|
893
897
|
|
@@ -917,7 +921,7 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE dst, VALUE src)
|
|
917
921
|
* This causes copy-on-write behavior to be triggered anyways
|
918
922
|
* when the +src+ buffer is modified (when reading off the socket).
|
919
923
|
*/
|
920
|
-
hp->buf
|
924
|
+
RB_OBJ_WRITE(self, &hp->buf, src);
|
921
925
|
memcpy(RSTRING_PTR(dst), srcptr, nr);
|
922
926
|
hp->len.content -= nr;
|
923
927
|
if (hp->len.content == 0) {
|
@@ -31,30 +31,29 @@ module Pitchfork
|
|
31
31
|
:logger => Logger.new($stderr),
|
32
32
|
:worker_processes => 1,
|
33
33
|
:after_fork => lambda { |server, worker|
|
34
|
-
|
35
|
-
|
36
|
-
:
|
37
|
-
|
38
|
-
|
34
|
+
server.logger.info("worker=#{worker.nr} gen=#{worker.generation} pid=#{$$} spawned")
|
35
|
+
},
|
36
|
+
:after_promotion => lambda { |server, worker|
|
37
|
+
server.logger.info("gen=#{worker.generation} pid=#{$$} promoted")
|
38
|
+
},
|
39
39
|
:after_worker_exit => lambda { |server, worker, status|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
40
|
+
m = if worker.nil?
|
41
|
+
"repead unknown process (#{status.inspect})"
|
42
|
+
elsif worker.mold?
|
43
|
+
"mold pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
44
|
+
else
|
45
|
+
"worker=#{worker.nr rescue 'unknown'} pid=#{worker.pid rescue 'unknown'} gen=#{worker.generation rescue 'unknown'} reaped (#{status.inspect})"
|
46
|
+
end
|
47
|
+
if status.success?
|
48
|
+
server.logger.info(m)
|
49
|
+
else
|
50
|
+
server.logger.error(m)
|
51
|
+
end
|
52
|
+
},
|
53
53
|
:after_worker_ready => lambda { |server, worker|
|
54
|
-
|
55
|
-
|
54
|
+
server.logger.info("worker=#{worker.nr} gen=#{worker.generation} ready")
|
55
|
+
},
|
56
56
|
:early_hints => false,
|
57
|
-
:mold_selector => MoldSelector::LeastSharedMemory.new,
|
58
57
|
:refork_condition => nil,
|
59
58
|
:check_client_connection => false,
|
60
59
|
:rewindable_input => true,
|
@@ -85,9 +84,6 @@ module Pitchfork
|
|
85
84
|
|
86
85
|
RACKUP[:set_listener] and
|
87
86
|
set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
|
88
|
-
|
89
|
-
RACKUP[:no_default_middleware] and
|
90
|
-
set[:default_middleware] = false
|
91
87
|
end
|
92
88
|
|
93
89
|
def commit!(server, options = {}) #:nodoc:
|
@@ -123,14 +119,14 @@ module Pitchfork
|
|
123
119
|
set[:logger] = obj
|
124
120
|
end
|
125
121
|
|
126
|
-
def before_fork(*args, &block)
|
127
|
-
set_hook(:before_fork, block_given? ? block : args[0])
|
128
|
-
end
|
129
|
-
|
130
122
|
def after_fork(*args, &block)
|
131
123
|
set_hook(:after_fork, block_given? ? block : args[0])
|
132
124
|
end
|
133
125
|
|
126
|
+
def after_promotion(*args, &block)
|
127
|
+
set_hook(:after_promotion, block_given? ? block : args[0])
|
128
|
+
end
|
129
|
+
|
134
130
|
def after_worker_ready(*args, &block)
|
135
131
|
set_hook(:after_worker_ready, block_given? ? block : args[0])
|
136
132
|
end
|
@@ -139,10 +135,6 @@ module Pitchfork
|
|
139
135
|
set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
|
140
136
|
end
|
141
137
|
|
142
|
-
def mold_selector(*args, &block)
|
143
|
-
set_hook(:mold_selector, block_given? ? block : args[0], 3)
|
144
|
-
end
|
145
|
-
|
146
138
|
def timeout(seconds)
|
147
139
|
set_int(:timeout, seconds, 3)
|
148
140
|
# POSIX says 31 days is the smallest allowed maximum timeout for select()
|
@@ -154,10 +146,6 @@ module Pitchfork
|
|
154
146
|
set_int(:worker_processes, nr, 1)
|
155
147
|
end
|
156
148
|
|
157
|
-
def default_middleware(bool)
|
158
|
-
set_bool(:default_middleware, bool)
|
159
|
-
end
|
160
|
-
|
161
149
|
def early_hints(bool)
|
162
150
|
set_bool(:early_hints, bool)
|
163
151
|
end
|
@@ -208,8 +196,12 @@ module Pitchfork
|
|
208
196
|
# Defines the number of requests per-worker after which a new generation
|
209
197
|
# should be spawned.
|
210
198
|
#
|
199
|
+
# +false+ can be used to mark a final generation, otherwise the last request
|
200
|
+
# count is re-used indefinitely.
|
201
|
+
#
|
211
202
|
# example:
|
212
203
|
#. refork_after [50, 100, 1000]
|
204
|
+
#. refork_after [50, 100, 1000, false]
|
213
205
|
#
|
214
206
|
# Note that reforking is only available on Linux. Other Unix-like systems
|
215
207
|
# don't have this capability.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Pitchfork
|
4
|
+
class Flock
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@file = Tempfile.create([name, '.lock'])
|
10
|
+
@file.write("#{Process.pid}\n")
|
11
|
+
@file.flush
|
12
|
+
@owned = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def at_fork
|
16
|
+
@owned = false
|
17
|
+
@file.close
|
18
|
+
@file = File.open(@file.path, "w")
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def unlink
|
23
|
+
File.unlink(@file.path)
|
24
|
+
rescue Errno::ENOENT
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def try_lock
|
29
|
+
raise Error, "Pitchfork::Flock(#{@name}) trying to lock an already owned lock" if @owned
|
30
|
+
|
31
|
+
if @file.flock(File::LOCK_EX | File::LOCK_NB)
|
32
|
+
@owned = true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def unlock
|
39
|
+
raise Error, "Pitchfork::Flock(#{@name}) trying to unlock a non-owned lock" unless @owned
|
40
|
+
|
41
|
+
begin
|
42
|
+
if @file.flock(File::LOCK_UN)
|
43
|
+
@owned = false
|
44
|
+
true
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -20,6 +20,18 @@ module Pitchfork
|
|
20
20
|
"#{code} #{STATUS_CODES[code]}\r\n\r\n"
|
21
21
|
end
|
22
22
|
|
23
|
+
def append_header(buf, key, value)
|
24
|
+
case value
|
25
|
+
when Array # Rack 3
|
26
|
+
value.each { |v| buf << "#{key}: #{v}\r\n" }
|
27
|
+
when /\n/ # Rack 2
|
28
|
+
# avoiding blank, key-only cookies with /\n+/
|
29
|
+
value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
|
30
|
+
else
|
31
|
+
buf << "#{key}: #{value}\r\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
23
35
|
# writes the rack_response to socket as an HTTP response
|
24
36
|
def http_response_write(socket, status, headers, body,
|
25
37
|
req = Pitchfork::HttpParser.new)
|
@@ -41,12 +53,7 @@ module Pitchfork
|
|
41
53
|
# key in Rack < 1.5
|
42
54
|
hijack = value
|
43
55
|
else
|
44
|
-
|
45
|
-
# avoiding blank, key-only cookies with /\n+/
|
46
|
-
value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
|
47
|
-
else
|
48
|
-
buf << "#{key}: #{value}\r\n"
|
49
|
-
end
|
56
|
+
append_header(buf, key, value)
|
50
57
|
end
|
51
58
|
end
|
52
59
|
socket.write(buf << "\r\n".freeze)
|