raptor 0.8.0 → 0.10.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/.buildkite/pipeline.yml +90 -32
- data/.dockerignore +5 -0
- data/CHANGELOG.md +22 -0
- data/Dockerfile +28 -0
- data/README.md +17 -9
- data/Rakefile +19 -1
- data/docs/raptor-vs-puma.md +557 -0
- data/ext/raptor_bpf/reuseport_select.bpf.c +63 -0
- data/ext/raptor_http/extconf.rb +1 -1
- data/ext/raptor_http/raptor_http.c +87 -6
- data/ext/raptor_native/extconf.rb +7 -0
- data/ext/raptor_native/raptor_native.c +99 -0
- data/lib/raptor/binder.rb +19 -4
- data/lib/raptor/cli.rb +1 -1
- data/lib/raptor/cluster.rb +27 -2
- data/lib/raptor/http.rb +41 -0
- data/lib/raptor/http1.rb +115 -56
- data/lib/raptor/reuseport_bpf.rb +109 -0
- data/lib/raptor/server.rb +40 -12
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/raptor/binder.rbs +10 -0
- data/sig/generated/raptor/cluster.rbs +4 -2
- data/sig/generated/raptor/http.rbs +13 -0
- data/sig/generated/raptor/http1.rbs +42 -13
- data/sig/generated/raptor/reuseport_bpf.rbs +56 -0
- data/sig/generated/raptor/server.rbs +20 -6
- metadata +24 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#include <linux/bpf.h>
|
|
2
|
+
#include <bpf/bpf_helpers.h>
|
|
3
|
+
|
|
4
|
+
#define MAX_WORKERS 64
|
|
5
|
+
|
|
6
|
+
// Per-worker listening sockets, keyed by worker index.
|
|
7
|
+
struct {
|
|
8
|
+
__uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY);
|
|
9
|
+
__type(key, __u32);
|
|
10
|
+
__type(value, __u64);
|
|
11
|
+
__uint(max_entries, MAX_WORKERS);
|
|
12
|
+
} socks SEC(".maps");
|
|
13
|
+
|
|
14
|
+
// Worker count at slot 0, per-worker backlog at slots 1..N.
|
|
15
|
+
struct {
|
|
16
|
+
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
17
|
+
__type(key, __u32);
|
|
18
|
+
__type(value, __u32);
|
|
19
|
+
__uint(max_entries, MAX_WORKERS + 1);
|
|
20
|
+
} loads SEC(".maps");
|
|
21
|
+
|
|
22
|
+
// Routes each incoming connection to the worker with the lowest backlog,
|
|
23
|
+
// with a random tie-break when loads are equal.
|
|
24
|
+
SEC("sk_reuseport")
|
|
25
|
+
int select_least_loaded(struct sk_reuseport_md *ctx) {
|
|
26
|
+
__u32 count_key = 0;
|
|
27
|
+
__u32 *count_ptr = bpf_map_lookup_elem(&loads, &count_key);
|
|
28
|
+
if (!count_ptr || *count_ptr == 0) {
|
|
29
|
+
return SK_DROP;
|
|
30
|
+
}
|
|
31
|
+
__u32 num_workers = *count_ptr;
|
|
32
|
+
if (num_workers > MAX_WORKERS) {
|
|
33
|
+
num_workers = MAX_WORKERS;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
__u32 min_load = ~0u;
|
|
37
|
+
__u32 max_load = 0;
|
|
38
|
+
__u32 min_idx = 0;
|
|
39
|
+
|
|
40
|
+
for (__u32 worker_idx = 0; worker_idx < MAX_WORKERS; worker_idx++) {
|
|
41
|
+
if (worker_idx >= num_workers) {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
__u32 worker_key = worker_idx + 1;
|
|
45
|
+
__u32 *load_ptr = bpf_map_lookup_elem(&loads, &worker_key);
|
|
46
|
+
if (!load_ptr) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (*load_ptr < min_load) {
|
|
50
|
+
min_load = *load_ptr;
|
|
51
|
+
min_idx = worker_idx;
|
|
52
|
+
}
|
|
53
|
+
if (*load_ptr > max_load) {
|
|
54
|
+
max_load = *load_ptr;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
__u32 chosen_idx = (min_load == max_load) ? (bpf_get_prandom_u32() % num_workers) : min_idx;
|
|
59
|
+
bpf_sk_select_reuseport(ctx, &socks, &chosen_idx, 0);
|
|
60
|
+
return SK_PASS;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
char _license[] SEC("license") = "GPL";
|
data/ext/raptor_http/extconf.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#include "ruby.h"
|
|
7
|
+
#include "ruby/encoding.h"
|
|
7
8
|
#include <assert.h>
|
|
8
9
|
#include <string.h>
|
|
9
10
|
#include <ctype.h>
|
|
@@ -44,6 +45,75 @@ static VALUE global_server_protocol;
|
|
|
44
45
|
static VALUE global_request_path;
|
|
45
46
|
static VALUE global_fragment;
|
|
46
47
|
|
|
48
|
+
struct common_field {
|
|
49
|
+
const char *name;
|
|
50
|
+
size_t len;
|
|
51
|
+
VALUE interned;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
#define FIELD(name) { name, sizeof(name) - 1, Qnil }
|
|
55
|
+
|
|
56
|
+
static struct common_field common_fields[] = {
|
|
57
|
+
FIELD("HTTP_HOST"),
|
|
58
|
+
FIELD("HTTP_USER_AGENT"),
|
|
59
|
+
FIELD("HTTP_CONNECTION"),
|
|
60
|
+
FIELD("HTTP_ACCEPT"),
|
|
61
|
+
FIELD("HTTP_ACCEPT_ENCODING"),
|
|
62
|
+
FIELD("HTTP_ACCEPT_LANGUAGE"),
|
|
63
|
+
FIELD("HTTP_ACCEPT_CHARSET"),
|
|
64
|
+
FIELD("HTTP_COOKIE"),
|
|
65
|
+
FIELD("HTTP_REFERER"),
|
|
66
|
+
FIELD("HTTP_CACHE_CONTROL"),
|
|
67
|
+
FIELD("HTTP_PRAGMA"),
|
|
68
|
+
|
|
69
|
+
FIELD("CONTENT_LENGTH"),
|
|
70
|
+
FIELD("CONTENT_TYPE"),
|
|
71
|
+
FIELD("HTTP_TRANSFER_ENCODING"),
|
|
72
|
+
|
|
73
|
+
FIELD("HTTP_AUTHORIZATION"),
|
|
74
|
+
FIELD("HTTP_ORIGIN"),
|
|
75
|
+
FIELD("HTTP_EXPECT"),
|
|
76
|
+
|
|
77
|
+
FIELD("HTTP_IF_MATCH"),
|
|
78
|
+
FIELD("HTTP_IF_NONE_MATCH"),
|
|
79
|
+
FIELD("HTTP_IF_MODIFIED_SINCE"),
|
|
80
|
+
FIELD("HTTP_IF_UNMODIFIED_SINCE"),
|
|
81
|
+
FIELD("HTTP_IF_RANGE"),
|
|
82
|
+
FIELD("HTTP_RANGE"),
|
|
83
|
+
|
|
84
|
+
FIELD("HTTP_UPGRADE"),
|
|
85
|
+
FIELD("HTTP_UPGRADE_INSECURE_REQUESTS"),
|
|
86
|
+
|
|
87
|
+
FIELD("HTTP_SEC_FETCH_DEST"),
|
|
88
|
+
FIELD("HTTP_SEC_FETCH_MODE"),
|
|
89
|
+
FIELD("HTTP_SEC_FETCH_SITE"),
|
|
90
|
+
FIELD("HTTP_SEC_FETCH_USER"),
|
|
91
|
+
FIELD("HTTP_SEC_CH_UA"),
|
|
92
|
+
FIELD("HTTP_SEC_CH_UA_MOBILE"),
|
|
93
|
+
FIELD("HTTP_SEC_CH_UA_PLATFORM"),
|
|
94
|
+
FIELD("HTTP_DNT"),
|
|
95
|
+
|
|
96
|
+
FIELD("HTTP_X_FORWARDED_FOR"),
|
|
97
|
+
FIELD("HTTP_X_FORWARDED_HOST"),
|
|
98
|
+
FIELD("HTTP_X_FORWARDED_PROTO"),
|
|
99
|
+
FIELD("HTTP_X_FORWARDED_SCHEME"),
|
|
100
|
+
FIELD("HTTP_X_FORWARDED_SSL"),
|
|
101
|
+
FIELD("HTTP_X_REAL_IP")
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
#undef FIELD
|
|
105
|
+
|
|
106
|
+
#define NUM_COMMON_FIELDS (sizeof(common_fields) / sizeof(common_fields[0]))
|
|
107
|
+
|
|
108
|
+
static VALUE raptor_http_intern_field(const char *buf, size_t len) {
|
|
109
|
+
for (size_t i = 0; i < NUM_COMMON_FIELDS; i++) {
|
|
110
|
+
if (common_fields[i].len == len && memcmp(common_fields[i].name, buf, len) == 0) {
|
|
111
|
+
return common_fields[i].interned;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return rb_enc_interned_str(buf, len, rb_utf8_encoding());
|
|
115
|
+
}
|
|
116
|
+
|
|
47
117
|
static inline void upcase_header_char(char *c) {
|
|
48
118
|
if (*c >= 'a' && *c <= 'z')
|
|
49
119
|
*c &= ~0x20;
|
|
@@ -294,16 +364,19 @@ tr26:
|
|
|
294
364
|
else if (parser->field_len == 12 && memcmp(field_ptr, "CONTENT_TYPE", 12) == 0)
|
|
295
365
|
needs_http_prefix = 0;
|
|
296
366
|
|
|
367
|
+
size_t key_len;
|
|
297
368
|
if (needs_http_prefix) {
|
|
298
369
|
memcpy(parser->buf, "HTTP_", 5);
|
|
299
370
|
memcpy(parser->buf + 5, field_ptr, parser->field_len);
|
|
300
|
-
|
|
371
|
+
key_len = 5 + parser->field_len;
|
|
372
|
+
parser->buf[key_len] = '\0';
|
|
301
373
|
} else {
|
|
302
374
|
memcpy(parser->buf, field_ptr, parser->field_len);
|
|
303
|
-
parser->
|
|
375
|
+
key_len = parser->field_len;
|
|
376
|
+
parser->buf[key_len] = '\0';
|
|
304
377
|
}
|
|
305
378
|
|
|
306
|
-
VALUE key =
|
|
379
|
+
VALUE key = raptor_http_intern_field(parser->buf, key_len);
|
|
307
380
|
VALUE value = rb_str_new(PTR_TO(mark), value_len);
|
|
308
381
|
|
|
309
382
|
char *value_ptr = RSTRING_PTR(value);
|
|
@@ -351,16 +424,19 @@ tr29:
|
|
|
351
424
|
else if (parser->field_len == 12 && memcmp(field_ptr, "CONTENT_TYPE", 12) == 0)
|
|
352
425
|
needs_http_prefix = 0;
|
|
353
426
|
|
|
427
|
+
size_t key_len;
|
|
354
428
|
if (needs_http_prefix) {
|
|
355
429
|
memcpy(parser->buf, "HTTP_", 5);
|
|
356
430
|
memcpy(parser->buf + 5, field_ptr, parser->field_len);
|
|
357
|
-
|
|
431
|
+
key_len = 5 + parser->field_len;
|
|
432
|
+
parser->buf[key_len] = '\0';
|
|
358
433
|
} else {
|
|
359
434
|
memcpy(parser->buf, field_ptr, parser->field_len);
|
|
360
|
-
parser->
|
|
435
|
+
key_len = parser->field_len;
|
|
436
|
+
parser->buf[key_len] = '\0';
|
|
361
437
|
}
|
|
362
438
|
|
|
363
|
-
VALUE key =
|
|
439
|
+
VALUE key = raptor_http_intern_field(parser->buf, key_len);
|
|
364
440
|
VALUE value = rb_str_new(PTR_TO(mark), value_len);
|
|
365
441
|
|
|
366
442
|
char *value_ptr = RSTRING_PTR(value);
|
|
@@ -1237,6 +1313,11 @@ RUBY_FUNC_EXPORTED void Init_raptor_http(void) {
|
|
|
1237
1313
|
global_request_path = rb_str_new2("PATH_INFO");
|
|
1238
1314
|
global_fragment = rb_str_new2("FRAGMENT");
|
|
1239
1315
|
|
|
1316
|
+
for (size_t i = 0; i < NUM_COMMON_FIELDS; i++) {
|
|
1317
|
+
common_fields[i].interned = rb_enc_interned_str(common_fields[i].name, common_fields[i].len, rb_utf8_encoding());
|
|
1318
|
+
rb_global_variable(&common_fields[i].interned);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1240
1321
|
rb_define_alloc_func(cHttpParser, parser_alloc);
|
|
1241
1322
|
rb_define_method(cHttpParser, "execute", parser_execute, 3);
|
|
1242
1323
|
rb_define_method(cHttpParser, "finished?", parser_finished_p, 0);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#ifdef __linux__
|
|
2
|
+
#define _GNU_SOURCE 1
|
|
3
|
+
#endif
|
|
4
|
+
|
|
5
|
+
#include "ruby.h"
|
|
6
|
+
#include "ruby/io.h"
|
|
7
|
+
#include <sys/uio.h>
|
|
8
|
+
#include <errno.h>
|
|
9
|
+
#include <limits.h>
|
|
10
|
+
|
|
11
|
+
#ifdef __linux__
|
|
12
|
+
#include <sched.h>
|
|
13
|
+
#include <unistd.h>
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
static VALUE eEAGAINWaitWritable;
|
|
17
|
+
|
|
18
|
+
static VALUE raptor_native_writev_nonblock(VALUE self, VALUE io, VALUE strings) {
|
|
19
|
+
(void)self;
|
|
20
|
+
Check_Type(strings, T_ARRAY);
|
|
21
|
+
long len = RARRAY_LEN(strings);
|
|
22
|
+
if (len == 0) return LONG2NUM(0);
|
|
23
|
+
if (len > IOV_MAX) len = IOV_MAX;
|
|
24
|
+
|
|
25
|
+
int fd = rb_io_descriptor(io);
|
|
26
|
+
struct iovec *iov = alloca(len * sizeof(struct iovec));
|
|
27
|
+
|
|
28
|
+
for (long i = 0; i < len; i++) {
|
|
29
|
+
VALUE str = RARRAY_AREF(strings, i);
|
|
30
|
+
Check_Type(str, T_STRING);
|
|
31
|
+
iov[i].iov_base = RSTRING_PTR(str);
|
|
32
|
+
iov[i].iov_len = RSTRING_LEN(str);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ssize_t written;
|
|
36
|
+
while ((written = writev(fd, iov, (int)len)) < 0) {
|
|
37
|
+
if (errno == EINTR) continue;
|
|
38
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) rb_raise(eEAGAINWaitWritable, "writev would block");
|
|
39
|
+
rb_sys_fail("writev");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return LONG2NUM((long)written);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static VALUE raptor_native_pin_to_cpu(VALUE self, VALUE cpu) {
|
|
46
|
+
(void)self;
|
|
47
|
+
#ifdef __linux__
|
|
48
|
+
int worker_index = NUM2INT(cpu);
|
|
49
|
+
cpu_set_t current;
|
|
50
|
+
CPU_ZERO(¤t);
|
|
51
|
+
if (sched_getaffinity(0, sizeof(current), ¤t) < 0) rb_sys_fail("sched_getaffinity");
|
|
52
|
+
|
|
53
|
+
int cpu_id = -1;
|
|
54
|
+
int seen = 0;
|
|
55
|
+
for (int candidate = 0; candidate < CPU_SETSIZE; candidate++) {
|
|
56
|
+
if (!CPU_ISSET(candidate, ¤t)) continue;
|
|
57
|
+
if (seen == worker_index) { cpu_id = candidate; break; }
|
|
58
|
+
seen++;
|
|
59
|
+
}
|
|
60
|
+
if (cpu_id < 0) return Qfalse;
|
|
61
|
+
|
|
62
|
+
cpu_set_t set;
|
|
63
|
+
CPU_ZERO(&set);
|
|
64
|
+
CPU_SET(cpu_id, &set);
|
|
65
|
+
if (sched_setaffinity(0, sizeof(set), &set) < 0) rb_sys_fail("sched_setaffinity");
|
|
66
|
+
return Qtrue;
|
|
67
|
+
#else
|
|
68
|
+
(void)cpu;
|
|
69
|
+
return Qfalse;
|
|
70
|
+
#endif
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static VALUE raptor_native_cpu_count(VALUE self) {
|
|
74
|
+
(void)self;
|
|
75
|
+
#ifdef __linux__
|
|
76
|
+
cpu_set_t set;
|
|
77
|
+
CPU_ZERO(&set);
|
|
78
|
+
if (sched_getaffinity(0, sizeof(set), &set) < 0) rb_sys_fail("sched_getaffinity");
|
|
79
|
+
return LONG2NUM((long)CPU_COUNT(&set));
|
|
80
|
+
#else
|
|
81
|
+
return LONG2NUM(0);
|
|
82
|
+
#endif
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
RUBY_FUNC_EXPORTED void Init_raptor_native(void) {
|
|
86
|
+
rb_ext_ractor_safe(true);
|
|
87
|
+
|
|
88
|
+
VALUE mRaptor = rb_define_module("Raptor");
|
|
89
|
+
VALUE mVectorIO = rb_define_module_under(mRaptor, "VectorIO");
|
|
90
|
+
VALUE mCPU = rb_define_module_under(mRaptor, "CPU");
|
|
91
|
+
|
|
92
|
+
rb_define_singleton_method(mVectorIO, "writev_nonblock", raptor_native_writev_nonblock, 2);
|
|
93
|
+
|
|
94
|
+
rb_define_singleton_method(mCPU, "pin", raptor_native_pin_to_cpu, 1);
|
|
95
|
+
rb_define_singleton_method(mCPU, "count", raptor_native_cpu_count, 0);
|
|
96
|
+
|
|
97
|
+
eEAGAINWaitWritable = rb_const_get(rb_cIO, rb_intern("EAGAINWaitWritable"));
|
|
98
|
+
rb_global_variable(&eEAGAINWaitWritable);
|
|
99
|
+
}
|
data/lib/raptor/binder.rb
CHANGED
|
@@ -61,6 +61,16 @@ module Raptor
|
|
|
61
61
|
# @rbs @listeners: Array[TCPServer | UNIXServer | SslListener]
|
|
62
62
|
# @rbs @uri_listeners: Hash[String, Array[TCPServer | UNIXServer | SslListener]]
|
|
63
63
|
|
|
64
|
+
# Array of bind URIs.
|
|
65
|
+
#
|
|
66
|
+
# @return [Array<String>] the bind URIs
|
|
67
|
+
attr_reader :bind_uris
|
|
68
|
+
|
|
69
|
+
# Kernel listen() queue depth for TCP/SSL listeners.
|
|
70
|
+
#
|
|
71
|
+
# @return [Integer] the socket backlog
|
|
72
|
+
attr_reader :socket_backlog
|
|
73
|
+
|
|
64
74
|
# Array of listening sockets.
|
|
65
75
|
#
|
|
66
76
|
# @return [Array<TCPServer, UNIXServer, SslListener>] the server sockets
|
|
@@ -244,11 +254,16 @@ module Raptor
|
|
|
244
254
|
|
|
245
255
|
host = host[1..-2] if host&.start_with?("[")
|
|
246
256
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
257
|
+
addrinfo = Addrinfo.tcp(host, port)
|
|
258
|
+
socket = Socket.new(addrinfo.afamily, Socket::SOCK_STREAM, 0)
|
|
259
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
|
260
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true) if Socket.const_defined?(:SO_REUSEPORT)
|
|
261
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
262
|
+
socket.bind(addrinfo)
|
|
263
|
+
socket.listen(@socket_backlog)
|
|
251
264
|
|
|
265
|
+
tcp_server = TCPServer.for_fd(socket.fileno)
|
|
266
|
+
socket.autoclose = false
|
|
252
267
|
[tcp_server]
|
|
253
268
|
end
|
|
254
269
|
|
data/lib/raptor/cli.rb
CHANGED
|
@@ -229,7 +229,7 @@ module Raptor
|
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
opts.on("-b", "--bind URI", String, "Bind address (default: tcp://0.0.0.0:9292)") do |bind|
|
|
232
|
-
if @options[:binds]
|
|
232
|
+
if @options[:binds].equal?(DEFAULT_OPTIONS[:binds])
|
|
233
233
|
@options[:binds] = [bind]
|
|
234
234
|
else
|
|
235
235
|
@options[:binds] << bind
|
data/lib/raptor/cluster.rb
CHANGED
|
@@ -9,8 +9,9 @@ require "ractor-pool"
|
|
|
9
9
|
|
|
10
10
|
require_relative "log"
|
|
11
11
|
require_relative "binder"
|
|
12
|
-
require_relative "server"
|
|
13
12
|
require_relative "reactor"
|
|
13
|
+
require_relative "server"
|
|
14
|
+
require_relative "reuseport_bpf"
|
|
14
15
|
require_relative "http1"
|
|
15
16
|
require_relative "http2"
|
|
16
17
|
require_relative "stats"
|
|
@@ -91,6 +92,7 @@ module Raptor
|
|
|
91
92
|
# @rbs @phased_restart_requested: bool
|
|
92
93
|
# @rbs @phased_restarting: bool
|
|
93
94
|
# @rbs @hot_restart_requested: bool
|
|
95
|
+
# @rbs @bpf_active: bool
|
|
94
96
|
|
|
95
97
|
# Creates a new Cluster with the specified configuration.
|
|
96
98
|
#
|
|
@@ -204,6 +206,8 @@ module Raptor
|
|
|
204
206
|
|
|
205
207
|
File.open(@pid_file, File::CREAT | File::EXCL | File::WRONLY) { |file| file.write(Process.pid.to_s) } if @pid_file
|
|
206
208
|
|
|
209
|
+
@bpf_active = ReuseportBPF.setup(@worker_count)
|
|
210
|
+
|
|
207
211
|
@worker_count.times { |index| spawn_worker(index) }
|
|
208
212
|
|
|
209
213
|
stats_file_thread = if @stats_file
|
|
@@ -232,6 +236,7 @@ module Raptor
|
|
|
232
236
|
File.delete(@stats_file) rescue nil if @stats_file
|
|
233
237
|
File.delete(@pid_file) rescue nil if @pid_file
|
|
234
238
|
@stats.unmap
|
|
239
|
+
@binder.close
|
|
235
240
|
end
|
|
236
241
|
|
|
237
242
|
# Returns stats for all worker processes.
|
|
@@ -439,6 +444,8 @@ module Raptor
|
|
|
439
444
|
trap("USR1", "IGNORE")
|
|
440
445
|
trap("USR2", "IGNORE")
|
|
441
446
|
|
|
447
|
+
Raptor::CPU.pin(index) if Raptor::CPU.count >= @worker_count
|
|
448
|
+
|
|
442
449
|
started_at = Process.clock_gettime(Process::CLOCK_REALTIME)
|
|
443
450
|
request_count = 0
|
|
444
451
|
|
|
@@ -501,7 +508,25 @@ module Raptor
|
|
|
501
508
|
)
|
|
502
509
|
reactor_thread = reactor.run
|
|
503
510
|
|
|
504
|
-
|
|
511
|
+
worker_listeners = if @bpf_active
|
|
512
|
+
bpf_listeners = ReuseportBPF.create_worker_listeners(@binder.bind_uris, index, @binder.socket_backlog)
|
|
513
|
+
non_tcp_listeners = @binder.listeners.reject { |listener| listener.is_a?(TCPServer) }
|
|
514
|
+
bpf_listeners + non_tcp_listeners
|
|
515
|
+
else
|
|
516
|
+
@binder.listeners
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
server = Server.new(
|
|
520
|
+
@binder,
|
|
521
|
+
reactor,
|
|
522
|
+
thread_pool,
|
|
523
|
+
http1,
|
|
524
|
+
http2,
|
|
525
|
+
connection_options: @connection_options,
|
|
526
|
+
drain_accept_queue: @drain_accept_queue,
|
|
527
|
+
listeners: worker_listeners,
|
|
528
|
+
worker_index: (index if @bpf_active)
|
|
529
|
+
)
|
|
505
530
|
server_thread = server.run
|
|
506
531
|
|
|
507
532
|
Log.info "Worker #{index} booted"
|
data/lib/raptor/http.rb
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require "rack"
|
|
5
5
|
|
|
6
6
|
require_relative "version"
|
|
7
|
+
require_relative "raptor_native"
|
|
7
8
|
|
|
8
9
|
module Raptor
|
|
9
10
|
# Shared HTTP utilities used by both the HTTP/1.x and HTTP/2 handlers:
|
|
@@ -51,6 +52,46 @@ module Raptor
|
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
# Writes `strings` in full via a single `writev(2)` syscall when possible,
|
|
56
|
+
# falling back to per-string writes on partial results. Bounded by
|
|
57
|
+
# `timeout` so a slow client can't pin the writing thread.
|
|
58
|
+
#
|
|
59
|
+
# @param socket [TCPSocket] the socket to write to
|
|
60
|
+
# @param strings [Array<String>] the buffers to write in order
|
|
61
|
+
# @param timeout [Integer] seconds to wait for the socket to become writable on each retry
|
|
62
|
+
# @return [void]
|
|
63
|
+
# @raise [WriteError] if the socket is not writable within the timeout or raises IOError
|
|
64
|
+
#
|
|
65
|
+
# @rbs (TCPSocket socket, Array[String] strings, ?timeout: Integer) -> void
|
|
66
|
+
def self.socket_writev(socket, strings, timeout: WRITE_TIMEOUT)
|
|
67
|
+
total = strings.sum(&:bytesize)
|
|
68
|
+
return if total.zero?
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
written = Raptor::VectorIO.writev_nonblock(socket, strings)
|
|
72
|
+
rescue IO::WaitWritable
|
|
73
|
+
raise WriteError unless socket.wait_writable(timeout)
|
|
74
|
+
retry
|
|
75
|
+
rescue IOError
|
|
76
|
+
raise WriteError
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
return if written == total
|
|
80
|
+
|
|
81
|
+
offset = 0
|
|
82
|
+
strings.each do |string|
|
|
83
|
+
size = string.bytesize
|
|
84
|
+
if written >= offset + size
|
|
85
|
+
offset += size
|
|
86
|
+
else
|
|
87
|
+
start = written - offset
|
|
88
|
+
socket_write(socket, start.zero? ? string : string.byteslice(start..-1), timeout: timeout)
|
|
89
|
+
offset += size
|
|
90
|
+
written = offset
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
54
95
|
# Writes a Common Log Format entry to `io`. Write failures are silently
|
|
55
96
|
# ignored.
|
|
56
97
|
#
|