raptor 0.23.0 → 0.25.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Dockerfile +7 -1
- data/README.md +184 -69
- data/docs/assets/profile_picture.png +0 -0
- data/docs/assets/startup_output.svg +37 -0
- data/docs/raptor-vs-puma.md +105 -429
- data/docs/talk.md +262 -169
- data/ext/raptor_http/raptor_http.c +82 -123
- data/lib/rackup/handler/raptor.rb +2 -1
- data/lib/raptor/cli.rb +1 -0
- data/lib/raptor/cluster.rb +85 -39
- data/lib/raptor/control_server.rb +2 -0
- data/lib/raptor/http.rb +0 -1
- data/lib/raptor/http1.rb +52 -30
- data/lib/raptor/http2.rb +46 -13
- data/lib/raptor/plugin.rb +98 -0
- data/lib/raptor/reactor.rb +102 -72
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor.rb +1 -1
- data/sig/generated/raptor/cluster.rbs +18 -5
- data/sig/generated/raptor/http.rbs +0 -2
- data/sig/generated/raptor/http1.rbs +21 -13
- data/sig/generated/raptor/http2.rbs +12 -9
- data/sig/generated/raptor/plugin.rbs +52 -0
- data/sig/generated/raptor/reactor.rbs +31 -16
- data/sig/generated/raptor.rbs +1 -1
- metadata +6 -2
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
#define MAX_FIELD_NAME 256
|
|
16
16
|
#define MAX_FIELD_VALUE (80 * 1024)
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
//
|
|
18
|
+
// Bounds each chunk-size line and the terminal chunk/trailer section, including
|
|
19
|
+
// CRLFs. Also limits cumulative nonterminal overhead beyond the payload size.
|
|
20
20
|
#define MAX_CHUNK_OVERHEAD (16 * 1024)
|
|
21
21
|
|
|
22
22
|
#define FLAG_CHUNKED 0x1
|
|
@@ -34,6 +34,7 @@ typedef struct {
|
|
|
34
34
|
int64_t content_len;
|
|
35
35
|
unsigned int flags;
|
|
36
36
|
VALUE request;
|
|
37
|
+
VALUE underscored_headers;
|
|
37
38
|
// Scratch space for the env key: "HTTP_" prefix + field name + NUL.
|
|
38
39
|
char buf[MAX_FIELD_NAME + 6];
|
|
39
40
|
} raptor_parser;
|
|
@@ -125,13 +126,6 @@ static VALUE intern_field(const char *buf, size_t len) {
|
|
|
125
126
|
return rb_enc_interned_str(buf, len, rb_utf8_encoding());
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
static inline void upcase_header_char(char *c) {
|
|
129
|
-
if (*c >= 'a' && *c <= 'z')
|
|
130
|
-
*c &= ~0x20;
|
|
131
|
-
else if (*c == '-')
|
|
132
|
-
*c = '_';
|
|
133
|
-
}
|
|
134
|
-
|
|
135
129
|
static int contains_chunked(const char *value, long len) {
|
|
136
130
|
static const char chunked[] = "chunked";
|
|
137
131
|
static const long chunked_len = sizeof(chunked) - 1;
|
|
@@ -159,6 +153,65 @@ static int64_t parse_content_length(const char *value, long len) {
|
|
|
159
153
|
return content_len;
|
|
160
154
|
}
|
|
161
155
|
|
|
156
|
+
static void parse_header(raptor_parser *parser, const char *field, const char *value, size_t value_len) {
|
|
157
|
+
size_t field_len = parser->field_len;
|
|
158
|
+
if (field_len == 0 || field_len > MAX_FIELD_NAME)
|
|
159
|
+
rb_raise(eHttpParserError, "invalid field name length");
|
|
160
|
+
if (value_len > MAX_FIELD_VALUE)
|
|
161
|
+
rb_raise(eHttpParserError, "field value too long");
|
|
162
|
+
|
|
163
|
+
int underscored = 0;
|
|
164
|
+
char *normalized = parser->buf + 5;
|
|
165
|
+
for (size_t i = 0; i < field_len; i++) {
|
|
166
|
+
char c = field[i];
|
|
167
|
+
if (c == '_') underscored = 1;
|
|
168
|
+
normalized[i] = c == '-' ? '_' : (c >= 'a' && c <= 'z' ? c & ~0x20 : c);
|
|
169
|
+
}
|
|
170
|
+
int content_length = field_len == 14 && memcmp(normalized, "CONTENT_LENGTH", 14) == 0;
|
|
171
|
+
int content_type = field_len == 12 && memcmp(normalized, "CONTENT_TYPE", 12) == 0;
|
|
172
|
+
int transfer_encoding = field_len == 17 && memcmp(normalized, "TRANSFER_ENCODING", 17) == 0;
|
|
173
|
+
if (underscored && (content_length || transfer_encoding))
|
|
174
|
+
rb_raise(eHttpParserError, "invalid framing header name");
|
|
175
|
+
|
|
176
|
+
size_t key_len = field_len;
|
|
177
|
+
if (content_length || content_type) {
|
|
178
|
+
memmove(parser->buf, normalized, field_len);
|
|
179
|
+
} else {
|
|
180
|
+
memcpy(parser->buf, "HTTP_", 5);
|
|
181
|
+
key_len += 5;
|
|
182
|
+
}
|
|
183
|
+
VALUE key = intern_field(parser->buf, key_len);
|
|
184
|
+
VALUE existing = rb_hash_aref(parser->request, key);
|
|
185
|
+
int existing_underscored = !NIL_P(parser->underscored_headers) &&
|
|
186
|
+
RTEST(rb_hash_aref(parser->underscored_headers, key));
|
|
187
|
+
// Canonical names win in either order; true duplicates still merge.
|
|
188
|
+
if (underscored) {
|
|
189
|
+
if (!NIL_P(existing) && !existing_underscored) return;
|
|
190
|
+
if (NIL_P(parser->underscored_headers)) parser->underscored_headers = rb_hash_new();
|
|
191
|
+
rb_hash_aset(parser->underscored_headers, key, Qtrue);
|
|
192
|
+
} else if (existing_underscored) {
|
|
193
|
+
existing = Qnil;
|
|
194
|
+
rb_hash_delete(parser->underscored_headers, key);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
while (value_len > 0 && (value[value_len - 1] == ' ' || value[value_len - 1] == '\t'))
|
|
198
|
+
value_len--;
|
|
199
|
+
if (content_length) {
|
|
200
|
+
parser->content_len = parse_content_length(value, (long)value_len);
|
|
201
|
+
if (parser->content_len > 0) parser->flags |= FLAG_HAS_BODY;
|
|
202
|
+
} else if (transfer_encoding && contains_chunked(value, (long)value_len)) {
|
|
203
|
+
parser->flags |= FLAG_CHUNKED | FLAG_HAS_BODY;
|
|
204
|
+
parser->content_len = 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!NIL_P(existing)) {
|
|
208
|
+
rb_str_cat(existing, ", ", 2);
|
|
209
|
+
rb_str_cat(existing, value, (long)value_len);
|
|
210
|
+
} else {
|
|
211
|
+
rb_hash_aset(parser->request, key, rb_str_new(value, (long)value_len));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
162
215
|
static const int raptor_parser_start = 1;
|
|
163
216
|
|
|
164
217
|
static size_t raptor_parser_execute(raptor_parser *parser, const char *buffer, size_t len) {
|
|
@@ -385,122 +438,12 @@ tr18:
|
|
|
385
438
|
tr26:
|
|
386
439
|
{ MARK(mark, p); }
|
|
387
440
|
{
|
|
388
|
-
|
|
389
|
-
rb_raise(eHttpParserError, "invalid field name length");
|
|
390
|
-
|
|
391
|
-
size_t value_len = LEN(mark, p);
|
|
392
|
-
if (value_len > MAX_FIELD_VALUE)
|
|
393
|
-
rb_raise(eHttpParserError, "field value too long");
|
|
394
|
-
|
|
395
|
-
const char *field_ptr = PTR_TO(field_start);
|
|
396
|
-
|
|
397
|
-
int needs_http_prefix = 1;
|
|
398
|
-
if (parser->field_len == 14 && memcmp(field_ptr, "CONTENT_LENGTH", 14) == 0)
|
|
399
|
-
needs_http_prefix = 0;
|
|
400
|
-
else if (parser->field_len == 12 && memcmp(field_ptr, "CONTENT_TYPE", 12) == 0)
|
|
401
|
-
needs_http_prefix = 0;
|
|
402
|
-
|
|
403
|
-
size_t key_len;
|
|
404
|
-
if (needs_http_prefix) {
|
|
405
|
-
memcpy(parser->buf, "HTTP_", 5);
|
|
406
|
-
memcpy(parser->buf + 5, field_ptr, parser->field_len);
|
|
407
|
-
key_len = 5 + parser->field_len;
|
|
408
|
-
parser->buf[key_len] = '\0';
|
|
409
|
-
} else {
|
|
410
|
-
memcpy(parser->buf, field_ptr, parser->field_len);
|
|
411
|
-
key_len = parser->field_len;
|
|
412
|
-
parser->buf[key_len] = '\0';
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
VALUE key = intern_field(parser->buf, key_len);
|
|
416
|
-
VALUE value = rb_str_new(PTR_TO(mark), value_len);
|
|
417
|
-
|
|
418
|
-
char *value_ptr = RSTRING_PTR(value);
|
|
419
|
-
long value_real_len = RSTRING_LEN(value);
|
|
420
|
-
while (value_real_len > 0 && (value_ptr[value_real_len - 1] == ' ' ||
|
|
421
|
-
value_ptr[value_real_len - 1] == '\t'))
|
|
422
|
-
value_real_len--;
|
|
423
|
-
rb_str_set_len(value, value_real_len);
|
|
424
|
-
|
|
425
|
-
if (!needs_http_prefix && parser->field_len == 14) {
|
|
426
|
-
parser->content_len = parse_content_length(value_ptr, value_real_len);
|
|
427
|
-
if (parser->content_len > 0)
|
|
428
|
-
parser->flags |= FLAG_HAS_BODY;
|
|
429
|
-
} else if (needs_http_prefix && parser->field_len == 17 &&
|
|
430
|
-
memcmp(field_ptr, "TRANSFER_ENCODING", 17) == 0) {
|
|
431
|
-
if (contains_chunked(value_ptr, value_real_len)) {
|
|
432
|
-
parser->flags |= FLAG_CHUNKED | FLAG_HAS_BODY;
|
|
433
|
-
parser->content_len = 0;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
VALUE existing = rb_hash_aref(parser->request, key);
|
|
438
|
-
if (!NIL_P(existing)) {
|
|
439
|
-
rb_str_cat(existing, ", ", 2);
|
|
440
|
-
rb_str_append(existing, value);
|
|
441
|
-
} else {
|
|
442
|
-
rb_hash_aset(parser->request, key, value);
|
|
443
|
-
}
|
|
441
|
+
parse_header(parser, PTR_TO(field_start), PTR_TO(mark), LEN(mark, p));
|
|
444
442
|
}
|
|
445
443
|
goto st14;
|
|
446
444
|
tr29:
|
|
447
445
|
{
|
|
448
|
-
|
|
449
|
-
rb_raise(eHttpParserError, "invalid field name length");
|
|
450
|
-
|
|
451
|
-
size_t value_len = LEN(mark, p);
|
|
452
|
-
if (value_len > MAX_FIELD_VALUE)
|
|
453
|
-
rb_raise(eHttpParserError, "field value too long");
|
|
454
|
-
|
|
455
|
-
const char *field_ptr = PTR_TO(field_start);
|
|
456
|
-
|
|
457
|
-
int needs_http_prefix = 1;
|
|
458
|
-
if (parser->field_len == 14 && memcmp(field_ptr, "CONTENT_LENGTH", 14) == 0)
|
|
459
|
-
needs_http_prefix = 0;
|
|
460
|
-
else if (parser->field_len == 12 && memcmp(field_ptr, "CONTENT_TYPE", 12) == 0)
|
|
461
|
-
needs_http_prefix = 0;
|
|
462
|
-
|
|
463
|
-
size_t key_len;
|
|
464
|
-
if (needs_http_prefix) {
|
|
465
|
-
memcpy(parser->buf, "HTTP_", 5);
|
|
466
|
-
memcpy(parser->buf + 5, field_ptr, parser->field_len);
|
|
467
|
-
key_len = 5 + parser->field_len;
|
|
468
|
-
parser->buf[key_len] = '\0';
|
|
469
|
-
} else {
|
|
470
|
-
memcpy(parser->buf, field_ptr, parser->field_len);
|
|
471
|
-
key_len = parser->field_len;
|
|
472
|
-
parser->buf[key_len] = '\0';
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
VALUE key = intern_field(parser->buf, key_len);
|
|
476
|
-
VALUE value = rb_str_new(PTR_TO(mark), value_len);
|
|
477
|
-
|
|
478
|
-
char *value_ptr = RSTRING_PTR(value);
|
|
479
|
-
long value_real_len = RSTRING_LEN(value);
|
|
480
|
-
while (value_real_len > 0 && (value_ptr[value_real_len - 1] == ' ' ||
|
|
481
|
-
value_ptr[value_real_len - 1] == '\t'))
|
|
482
|
-
value_real_len--;
|
|
483
|
-
rb_str_set_len(value, value_real_len);
|
|
484
|
-
|
|
485
|
-
if (!needs_http_prefix && parser->field_len == 14) {
|
|
486
|
-
parser->content_len = parse_content_length(value_ptr, value_real_len);
|
|
487
|
-
if (parser->content_len > 0)
|
|
488
|
-
parser->flags |= FLAG_HAS_BODY;
|
|
489
|
-
} else if (needs_http_prefix && parser->field_len == 17 &&
|
|
490
|
-
memcmp(field_ptr, "TRANSFER_ENCODING", 17) == 0) {
|
|
491
|
-
if (contains_chunked(value_ptr, value_real_len)) {
|
|
492
|
-
parser->flags |= FLAG_CHUNKED | FLAG_HAS_BODY;
|
|
493
|
-
parser->content_len = 0;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
VALUE existing = rb_hash_aref(parser->request, key);
|
|
498
|
-
if (!NIL_P(existing)) {
|
|
499
|
-
rb_str_cat(existing, ", ", 2);
|
|
500
|
-
rb_str_append(existing, value);
|
|
501
|
-
} else {
|
|
502
|
-
rb_hash_aset(parser->request, key, value);
|
|
503
|
-
}
|
|
446
|
+
parse_header(parser, PTR_TO(field_start), PTR_TO(mark), LEN(mark, p));
|
|
504
447
|
}
|
|
505
448
|
goto st14;
|
|
506
449
|
st14:
|
|
@@ -560,10 +503,8 @@ case 46:
|
|
|
560
503
|
goto st0;
|
|
561
504
|
tr21:
|
|
562
505
|
{ MARK(field_start, p); }
|
|
563
|
-
{ upcase_header_char((char *)p); }
|
|
564
506
|
goto st17;
|
|
565
507
|
tr23:
|
|
566
|
-
{ upcase_header_char((char *)p); }
|
|
567
508
|
goto st17;
|
|
568
509
|
st17:
|
|
569
510
|
if ( ++p == pe )
|
|
@@ -606,6 +547,7 @@ st18:
|
|
|
606
547
|
goto _test_eof18;
|
|
607
548
|
case 18:
|
|
608
549
|
switch( (*p) ) {
|
|
550
|
+
case 9: goto tr27;
|
|
609
551
|
case 13: goto tr26;
|
|
610
552
|
case 32: goto tr27;
|
|
611
553
|
case 127: goto st0;
|
|
@@ -1219,11 +1161,13 @@ static void raptor_parser_init(raptor_parser *parser) {
|
|
|
1219
1161
|
parser->content_len = 0;
|
|
1220
1162
|
parser->flags = 0;
|
|
1221
1163
|
parser->request = Qnil;
|
|
1164
|
+
parser->underscored_headers = Qnil;
|
|
1222
1165
|
}
|
|
1223
1166
|
|
|
1224
1167
|
static void parser_mark(void *ptr) {
|
|
1225
1168
|
raptor_parser *parser = ptr;
|
|
1226
1169
|
rb_gc_mark(parser->request);
|
|
1170
|
+
rb_gc_mark(parser->underscored_headers);
|
|
1227
1171
|
}
|
|
1228
1172
|
|
|
1229
1173
|
static size_t parser_memsize(const void *ptr) {
|
|
@@ -1325,6 +1269,9 @@ static VALUE parser_decode_chunked(int argc, VALUE *argv, VALUE self) {
|
|
|
1325
1269
|
|
|
1326
1270
|
while (offset < len) {
|
|
1327
1271
|
const char *crlf = find_crlf(buf + offset, len - offset);
|
|
1272
|
+
size_t line_bytes = crlf ? (size_t)(crlf - (buf + offset)) + 2 : len - offset;
|
|
1273
|
+
if (line_bytes > MAX_CHUNK_OVERHEAD)
|
|
1274
|
+
return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
|
|
1328
1275
|
if (!crlf) return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
|
|
1329
1276
|
|
|
1330
1277
|
size_t line_len = (size_t)(crlf - (buf + offset));
|
|
@@ -1354,6 +1301,9 @@ static VALUE parser_decode_chunked(int argc, VALUE *argv, VALUE self) {
|
|
|
1354
1301
|
while (1) {
|
|
1355
1302
|
if (trailer_offset >= len) return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
|
|
1356
1303
|
const char *tcrlf = find_crlf(buf + trailer_offset, len - trailer_offset);
|
|
1304
|
+
size_t terminal_bytes = tcrlf ? (size_t)(tcrlf - (buf + offset)) + 2 : len - offset;
|
|
1305
|
+
if (terminal_bytes > MAX_CHUNK_OVERHEAD)
|
|
1306
|
+
return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
|
|
1357
1307
|
if (!tcrlf) return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
|
|
1358
1308
|
if (tcrlf == buf + trailer_offset) return rb_ary_new_from_args(3, decoded, ID2SYM(id_complete), SIZET2NUM(trailer_offset + 2));
|
|
1359
1309
|
trailer_offset = (size_t)(tcrlf - buf) + 2;
|
|
@@ -1375,6 +1325,15 @@ static VALUE parser_decode_chunked(int argc, VALUE *argv, VALUE self) {
|
|
|
1375
1325
|
size_t bytes_to_copy = (size_t)chunk_size < available ? (size_t)chunk_size : available;
|
|
1376
1326
|
rb_str_cat(decoded, buf + data_offset, (long)bytes_to_copy);
|
|
1377
1327
|
|
|
1328
|
+
if (available <= (size_t)chunk_size)
|
|
1329
|
+
return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
|
|
1330
|
+
if (buf[data_offset + (size_t)chunk_size] != '\r')
|
|
1331
|
+
return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
|
|
1332
|
+
if (available == (size_t)chunk_size + 1)
|
|
1333
|
+
return rb_ary_new_from_args(2, decoded, ID2SYM(id_incomplete));
|
|
1334
|
+
if (buf[data_offset + (size_t)chunk_size + 1] != '\n')
|
|
1335
|
+
return rb_ary_new_from_args(2, decoded, ID2SYM(id_malformed));
|
|
1336
|
+
|
|
1378
1337
|
offset = data_offset + (size_t)chunk_size + 2;
|
|
1379
1338
|
}
|
|
1380
1339
|
|
|
@@ -106,6 +106,7 @@ module Rackup
|
|
|
106
106
|
result[:before_worker_boot] = config.fetch(:before_worker_boot, cli_defaults[:before_worker_boot])
|
|
107
107
|
result[:before_worker_shutdown] = config.fetch(:before_worker_shutdown, cli_defaults[:before_worker_shutdown])
|
|
108
108
|
result[:before_refork] = config.fetch(:before_refork, cli_defaults[:before_refork])
|
|
109
|
+
result[:plugins] = config.fetch(:plugins, cli_defaults[:plugins])
|
|
109
110
|
result[:stats_file] = config.fetch(:stats_file, cli_defaults[:stats_file])
|
|
110
111
|
result[:control_url] = config[:control_url] if config.key?(:control_url)
|
|
111
112
|
result[:pid_file] = config[:pid_file] if config.key?(:pid_file)
|
|
@@ -113,7 +114,7 @@ module Rackup
|
|
|
113
114
|
result[:stderr_file] = config[:stderr_file] if config.key?(:stderr_file)
|
|
114
115
|
result[:access_log_file] = config[:access_log_file] if config.key?(:access_log_file)
|
|
115
116
|
result[:on_error] = config[:on_error] if config.key?(:on_error)
|
|
116
|
-
result
|
|
117
|
+
config.merge(result)
|
|
117
118
|
end
|
|
118
119
|
private_class_method :build_cluster_options
|
|
119
120
|
end
|
data/lib/raptor/cli.rb
CHANGED
data/lib/raptor/cluster.rb
CHANGED
|
@@ -14,6 +14,7 @@ require_relative "control_server"
|
|
|
14
14
|
require_relative "http1"
|
|
15
15
|
require_relative "http2"
|
|
16
16
|
require_relative "log"
|
|
17
|
+
require_relative "plugin"
|
|
17
18
|
require_relative "raptor_native"
|
|
18
19
|
require_relative "reactor"
|
|
19
20
|
require_relative "reuseport_bpf"
|
|
@@ -126,6 +127,12 @@ module Raptor
|
|
|
126
127
|
# @rbs @resp_r: IO?
|
|
127
128
|
# @rbs @resp_w: IO?
|
|
128
129
|
# @rbs @bpf_active: bool
|
|
130
|
+
# @rbs @plugins: Array[Plugin]
|
|
131
|
+
|
|
132
|
+
# Returns the cluster configuration available to plugins.
|
|
133
|
+
#
|
|
134
|
+
# @return [Hash{Symbol => untyped}]
|
|
135
|
+
attr_reader :options #: Hash[Symbol, untyped]
|
|
129
136
|
|
|
130
137
|
# Creates a new Cluster with the given options.
|
|
131
138
|
#
|
|
@@ -155,6 +162,7 @@ module Raptor
|
|
|
155
162
|
# @option options [Array<Proc>] :before_worker_boot procs called with the worker index before it begins serving
|
|
156
163
|
# @option options [Array<Proc>] :before_worker_shutdown procs called with the worker index before its graceful shutdown
|
|
157
164
|
# @option options [Array<Proc>] :before_refork procs called in a worker before it transitions to a seed
|
|
165
|
+
# @option options [Array<String, Symbol>] :plugins plugins to load and start in order
|
|
158
166
|
# @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
|
|
159
167
|
# @option options [String, nil] :control_url `unix://` URL serving cluster stats, or nil to disable
|
|
160
168
|
# @option options [String, nil] :pid_file path to write the master PID to, or nil to disable
|
|
@@ -168,6 +176,8 @@ module Raptor
|
|
|
168
176
|
#
|
|
169
177
|
# @rbs (Hash[Symbol, untyped] options) -> void
|
|
170
178
|
def initialize(options)
|
|
179
|
+
@options = options = options.dup
|
|
180
|
+
@plugins = []
|
|
171
181
|
@drain_accept_queue = options[:drain_accept_queue]
|
|
172
182
|
@worker_count = options[:workers]
|
|
173
183
|
@http1_ractor_count = options[:http1][:ractors] || self.class.default_http1_ractor_count(@worker_count)
|
|
@@ -185,10 +195,10 @@ module Raptor
|
|
|
185
195
|
@worker_timeout = options[:worker_timeout]
|
|
186
196
|
@worker_drain_timeout = options[:worker_drain_timeout]
|
|
187
197
|
@worker_shutdown_timeout = options[:worker_shutdown_timeout]
|
|
188
|
-
@before_fork = Array(options[:before_fork])
|
|
189
|
-
@before_worker_boot = Array(options[:before_worker_boot])
|
|
190
|
-
@before_worker_shutdown = Array(options[:before_worker_shutdown])
|
|
191
|
-
@before_refork = Array(options[:before_refork])
|
|
198
|
+
@before_fork = options[:before_fork] = Array(options[:before_fork]).dup
|
|
199
|
+
@before_worker_boot = options[:before_worker_boot] = Array(options[:before_worker_boot]).dup
|
|
200
|
+
@before_worker_shutdown = options[:before_worker_shutdown] = Array(options[:before_worker_shutdown]).dup
|
|
201
|
+
@before_refork = options[:before_refork] = Array(options[:before_refork]).dup
|
|
192
202
|
@stats_file = options[:stats_file]
|
|
193
203
|
@control_server = ControlServer.new(options[:control_url]) { stats_hash } if options[:control_url]
|
|
194
204
|
@pid_file = options[:pid_file]
|
|
@@ -265,40 +275,61 @@ module Raptor
|
|
|
265
275
|
|
|
266
276
|
@bpf_active = ReuseportBPF.setup(@worker_count, @binder.listeners.grep(TCPServer))
|
|
267
277
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
Thread.new do
|
|
274
|
-
Thread.current.name = "Stats File Writer"
|
|
275
|
-
|
|
276
|
-
write_stats_file_loop
|
|
278
|
+
begin
|
|
279
|
+
Array(@options[:plugins]).each do |name|
|
|
280
|
+
plugin = Plugin.load(name)
|
|
281
|
+
@plugins << plugin
|
|
282
|
+
plugin.start(self)
|
|
277
283
|
end
|
|
278
|
-
end
|
|
279
284
|
|
|
280
|
-
|
|
285
|
+
@control_server&.bind
|
|
286
|
+
@worker_count.times { |index| spawn_worker(index) }
|
|
287
|
+
@control_server&.start
|
|
281
288
|
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
stats_file_thread = if @stats_file
|
|
290
|
+
Thread.new do
|
|
291
|
+
Thread.current.name = "Stats File Writer"
|
|
284
292
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
check_refork_trigger if @refork_thresholds.any? && !@phased_restarting
|
|
289
|
-
timeout_hung_workers
|
|
293
|
+
write_stats_file_loop
|
|
294
|
+
end
|
|
295
|
+
end
|
|
290
296
|
|
|
291
|
-
|
|
292
|
-
|
|
297
|
+
ready = false
|
|
298
|
+
|
|
299
|
+
until @shutdown
|
|
300
|
+
break if reap_workers == :no_children
|
|
301
|
+
|
|
302
|
+
perform_hot_restart if @hot_restart_requested
|
|
303
|
+
poll_seed_ready if @seed_pid && !@seed_ready
|
|
304
|
+
perform_phased_restart if @phased_restart_requested && !@phased_restarting
|
|
305
|
+
check_refork_trigger if @refork_thresholds.any? && !@phased_restarting
|
|
306
|
+
timeout_hung_workers
|
|
307
|
+
|
|
308
|
+
unless ready || @shutdown
|
|
309
|
+
stats = @stats.all
|
|
310
|
+
if @workers.size == @worker_count && @workers.all? { |index, pid|
|
|
311
|
+
stat = stats[index]
|
|
312
|
+
stat[:pid] == pid && stat[:phase] == @phase && stat[:booted] && !@timed_out.include?(pid)
|
|
313
|
+
}
|
|
314
|
+
Systemd.notify("READY=1\nMAINPID=#{Process.pid}")
|
|
315
|
+
ready = true
|
|
316
|
+
end
|
|
317
|
+
end
|
|
293
318
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
319
|
+
sleep 0.1
|
|
320
|
+
end
|
|
321
|
+
ensure
|
|
322
|
+
@shutdown = true
|
|
323
|
+
Systemd.notify("STOPPING=1")
|
|
324
|
+
stop_workers
|
|
325
|
+
stats_file_thread&.join
|
|
326
|
+
stop_plugins
|
|
327
|
+
@control_server&.shutdown
|
|
328
|
+
File.delete(@stats_file) rescue nil if @stats_file
|
|
329
|
+
File.delete(@pid_file) rescue nil if @pid_file
|
|
330
|
+
@stats.unmap
|
|
331
|
+
@binder.close
|
|
332
|
+
end
|
|
302
333
|
end
|
|
303
334
|
|
|
304
335
|
# Returns stats for all worker processes.
|
|
@@ -312,11 +343,9 @@ module Raptor
|
|
|
312
343
|
@stats.all
|
|
313
344
|
end
|
|
314
345
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
#
|
|
318
|
-
# pool, `max_threads` is the number of threads currently running so
|
|
319
|
-
# utilization reflects the capacity available at that moment.
|
|
346
|
+
# Returns cluster statistics for plugins and the control server.
|
|
347
|
+
# For an adaptive pool, `max_threads` is the number of threads currently
|
|
348
|
+
# running so utilization reflects the capacity available at that moment.
|
|
320
349
|
#
|
|
321
350
|
# @return [Hash] cluster statistics
|
|
322
351
|
#
|
|
@@ -358,6 +387,22 @@ module Raptor
|
|
|
358
387
|
}
|
|
359
388
|
end
|
|
360
389
|
|
|
390
|
+
private
|
|
391
|
+
|
|
392
|
+
# Stops plugins in reverse startup order.
|
|
393
|
+
#
|
|
394
|
+
# @rbs () -> void
|
|
395
|
+
def stop_plugins
|
|
396
|
+
until @plugins.empty?
|
|
397
|
+
plugin = @plugins.pop
|
|
398
|
+
begin
|
|
399
|
+
plugin.shutdown
|
|
400
|
+
rescue StandardError => error
|
|
401
|
+
Log.rescued_error(error)
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
361
406
|
# @rbs (Numeric time) -> String
|
|
362
407
|
def timestamp(time)
|
|
363
408
|
Time.at(time).utc.iso8601(6)
|
|
@@ -767,6 +812,7 @@ module Raptor
|
|
|
767
812
|
Systemd.notify("RELOADING=1\nMONOTONIC_USEC=#{monotonic_usec}")
|
|
768
813
|
@shutdown = true
|
|
769
814
|
stop_workers
|
|
815
|
+
stop_plugins
|
|
770
816
|
@binder.clear_close_on_exec
|
|
771
817
|
ENV[INHERITED_FDS_ENV] = JSON.generate(@binder.inheritable_fds)
|
|
772
818
|
@control_server&.shutdown
|
|
@@ -819,6 +865,8 @@ module Raptor
|
|
|
819
865
|
booted: false
|
|
820
866
|
)
|
|
821
867
|
|
|
868
|
+
@before_worker_boot.each { |hook| hook.parameters.empty? ? hook.call : hook.call(index) }
|
|
869
|
+
|
|
822
870
|
reactor = nil
|
|
823
871
|
|
|
824
872
|
counting_app = ->(env) {
|
|
@@ -902,8 +950,6 @@ module Raptor
|
|
|
902
950
|
)
|
|
903
951
|
server_thread = server.run
|
|
904
952
|
|
|
905
|
-
@before_worker_boot.each { |hook| hook.parameters.empty? ? hook.call : hook.call(index) }
|
|
906
|
-
|
|
907
953
|
Log.info "Worker #{index} booted"
|
|
908
954
|
|
|
909
955
|
stats_thread = Thread.new do
|