polyphony 0.52.0 → 0.55.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/.github/workflows/test.yml +1 -1
- data/.gitignore +3 -1
- data/CHANGELOG.md +45 -22
- data/Gemfile.lock +3 -1
- data/TODO.md +36 -11
- data/examples/core/queue.rb +19 -0
- data/examples/io/echo_server.rb +1 -0
- data/examples/io/https_server.rb +30 -0
- data/examples/io/stdio.rb +8 -0
- data/examples/io/tcp_proxy.rb +2 -2
- data/ext/polyphony/backend_common.h +10 -10
- data/ext/polyphony/backend_io_uring.c +251 -85
- data/ext/polyphony/backend_io_uring_context.c +15 -2
- data/ext/polyphony/backend_io_uring_context.h +12 -11
- data/ext/polyphony/backend_libev.c +165 -24
- data/ext/polyphony/extconf.rb +3 -1
- data/ext/polyphony/polyphony.c +10 -2
- data/ext/polyphony/polyphony.h +5 -0
- data/ext/polyphony/polyphony_ext.c +3 -0
- data/ext/polyphony/runqueue.c +29 -1
- data/ext/polyphony/socket_extensions.c +33 -0
- data/ext/polyphony/thread.c +22 -6
- data/lib/polyphony/core/sync.rb +8 -0
- data/lib/polyphony/extensions/io.rb +8 -0
- data/lib/polyphony/extensions/openssl.rb +24 -17
- data/lib/polyphony/extensions/socket.rb +30 -35
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -0
- data/test/helper.rb +3 -3
- data/test/test_backend.rb +164 -1
- data/test/test_fiber.rb +0 -1
- data/test/test_io.rb +6 -3
- data/test/test_signal.rb +1 -1
- data/test/test_sync.rb +43 -0
- data/test/test_thread.rb +4 -0
- data/test/test_thread_pool.rb +1 -1
- data/test/test_timer.rb +13 -7
- metadata +20 -2
@@ -6,6 +6,7 @@ void Init_Backend();
|
|
6
6
|
void Init_Queue();
|
7
7
|
void Init_Event();
|
8
8
|
void Init_Runqueue();
|
9
|
+
void Init_SocketExtensions();
|
9
10
|
void Init_Thread();
|
10
11
|
void Init_Tracing();
|
11
12
|
|
@@ -24,6 +25,8 @@ void Init_polyphony_ext() {
|
|
24
25
|
Init_Thread();
|
25
26
|
Init_Tracing();
|
26
27
|
|
28
|
+
Init_SocketExtensions();
|
29
|
+
|
27
30
|
#ifdef POLYPHONY_PLAYGROUND
|
28
31
|
playground();
|
29
32
|
#endif
|
data/ext/polyphony/runqueue.c
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
typedef struct queue {
|
5
5
|
runqueue_ring_buffer entries;
|
6
|
+
unsigned int high_watermark;
|
7
|
+
unsigned int switch_count;
|
6
8
|
} Runqueue_t;
|
7
9
|
|
8
10
|
VALUE cRunqueue = Qnil;
|
@@ -43,6 +45,8 @@ static VALUE Runqueue_initialize(VALUE self) {
|
|
43
45
|
GetRunqueue(self, runqueue);
|
44
46
|
|
45
47
|
runqueue_ring_buffer_init(&runqueue->entries);
|
48
|
+
runqueue->high_watermark = 0;
|
49
|
+
runqueue->switch_count = 0;
|
46
50
|
|
47
51
|
return self;
|
48
52
|
}
|
@@ -53,6 +57,8 @@ void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
|
53
57
|
|
54
58
|
if (reschedule) runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
55
59
|
runqueue_ring_buffer_push(&runqueue->entries, fiber, value);
|
60
|
+
if (runqueue->entries.count > runqueue->high_watermark)
|
61
|
+
runqueue->high_watermark = runqueue->entries.count;
|
56
62
|
}
|
57
63
|
|
58
64
|
void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
@@ -60,12 +66,19 @@ void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
|
60
66
|
GetRunqueue(self, runqueue);
|
61
67
|
if (reschedule) runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
62
68
|
runqueue_ring_buffer_unshift(&runqueue->entries, fiber, value);
|
69
|
+
if (runqueue->entries.count > runqueue->high_watermark)
|
70
|
+
runqueue->high_watermark = runqueue->entries.count;
|
63
71
|
}
|
64
72
|
|
65
73
|
runqueue_entry Runqueue_shift(VALUE self) {
|
66
74
|
Runqueue_t *runqueue;
|
67
75
|
GetRunqueue(self, runqueue);
|
68
|
-
|
76
|
+
runqueue_entry entry = runqueue_ring_buffer_shift(&runqueue->entries);
|
77
|
+
if (entry.fiber == Qnil)
|
78
|
+
runqueue->high_watermark = 0;
|
79
|
+
else
|
80
|
+
runqueue->switch_count += 1;
|
81
|
+
return entry;
|
69
82
|
}
|
70
83
|
|
71
84
|
void Runqueue_delete(VALUE self, VALUE fiber) {
|
@@ -100,6 +113,21 @@ int Runqueue_empty_p(VALUE self) {
|
|
100
113
|
return (runqueue->entries.count == 0);
|
101
114
|
}
|
102
115
|
|
116
|
+
static const unsigned int ANTI_STARVE_HIGH_WATERMARK_THRESHOLD = 128;
|
117
|
+
static const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
|
118
|
+
|
119
|
+
int Runqueue_should_poll_nonblocking(VALUE self) {
|
120
|
+
Runqueue_t *runqueue;
|
121
|
+
GetRunqueue(self, runqueue);
|
122
|
+
|
123
|
+
if (runqueue->high_watermark < ANTI_STARVE_HIGH_WATERMARK_THRESHOLD) return 0;
|
124
|
+
if (runqueue->switch_count < ANTI_STARVE_SWITCH_COUNT_THRESHOLD) return 0;
|
125
|
+
|
126
|
+
// the
|
127
|
+
runqueue->switch_count = 0;
|
128
|
+
return 1;
|
129
|
+
}
|
130
|
+
|
103
131
|
void Init_Runqueue() {
|
104
132
|
cRunqueue = rb_define_class_under(mPolyphony, "Runqueue", rb_cObject);
|
105
133
|
rb_define_alloc_func(cRunqueue, Runqueue_allocate);
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
|
3
|
+
VALUE Socket_send(VALUE self, VALUE msg, VALUE flags) {
|
4
|
+
return Backend_send(BACKEND(), self, msg, flags);
|
5
|
+
}
|
6
|
+
|
7
|
+
VALUE Socket_write(int argc, VALUE *argv, VALUE self) {
|
8
|
+
VALUE ary = rb_ary_new_from_values(argc, argv);
|
9
|
+
VALUE result = Backend_sendv(BACKEND(), self, ary, INT2NUM(0));
|
10
|
+
RB_GC_GUARD(ary);
|
11
|
+
return result;
|
12
|
+
}
|
13
|
+
|
14
|
+
VALUE Socket_double_chevron(VALUE self, VALUE msg) {
|
15
|
+
Backend_send(BACKEND(), self, msg, INT2NUM(0));
|
16
|
+
return self;
|
17
|
+
}
|
18
|
+
|
19
|
+
void Init_SocketExtensions() {
|
20
|
+
rb_require("socket");
|
21
|
+
|
22
|
+
VALUE cSocket = rb_const_get(rb_cObject, rb_intern("Socket"));
|
23
|
+
VALUE cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
24
|
+
|
25
|
+
rb_define_method(cSocket, "send", Socket_send, 2);
|
26
|
+
rb_define_method(cTCPSocket, "send", Socket_send, 2);
|
27
|
+
|
28
|
+
rb_define_method(cSocket, "write", Socket_write, -1);
|
29
|
+
rb_define_method(cTCPSocket, "write", Socket_write, -1);
|
30
|
+
|
31
|
+
rb_define_method(cSocket, "<<", Socket_double_chevron, 1);
|
32
|
+
rb_define_method(cTCPSocket, "<<", Socket_double_chevron, 1);
|
33
|
+
}
|
data/ext/polyphony/thread.c
CHANGED
@@ -21,7 +21,7 @@ static VALUE SYM_scheduled_fibers;
|
|
21
21
|
static VALUE SYM_pending_watchers;
|
22
22
|
|
23
23
|
static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
24
|
-
VALUE backend = rb_ivar_get(self,ID_ivar_backend);
|
24
|
+
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
25
25
|
VALUE stats = rb_hash_new();
|
26
26
|
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
27
27
|
long pending_count;
|
@@ -53,7 +53,7 @@ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
|
|
53
53
|
// event selector. Otherwise it's gonna be stuck waiting for an event to
|
54
54
|
// happen, not knowing that it there's already a fiber ready to run in its
|
55
55
|
// run queue.
|
56
|
-
VALUE backend = rb_ivar_get(self,ID_ivar_backend);
|
56
|
+
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
57
57
|
Backend_wakeup(backend);
|
58
58
|
}
|
59
59
|
}
|
@@ -86,7 +86,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
86
86
|
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
87
87
|
runqueue_entry next;
|
88
88
|
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
89
|
-
unsigned int
|
89
|
+
unsigned int pending_ops_count = Backend_pending_count(backend);
|
90
90
|
unsigned int backend_was_polled = 0;
|
91
91
|
|
92
92
|
if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
|
@@ -95,14 +95,24 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
95
95
|
while (1) {
|
96
96
|
next = Runqueue_shift(runqueue);
|
97
97
|
if (next.fiber != Qnil) {
|
98
|
-
|
98
|
+
// Polling for I/O op completion is normally done when the run queue is
|
99
|
+
// empty, but if the runqueue never empties, we'll never get to process
|
100
|
+
// any event completions. In order to prevent this, an anti-starve
|
101
|
+
// mechanism is employed, under the following conditions:
|
102
|
+
// - a blocking poll was not yet performed
|
103
|
+
// - there are pending blocking operations
|
104
|
+
// - the runqueue has signalled that a non-blocking poll should be
|
105
|
+
// performed
|
106
|
+
// - the run queue length high watermark has reached its threshold (currently 128)
|
107
|
+
// - the run queue switch counter has reached its threshold (currently 64)
|
108
|
+
if (!backend_was_polled && pending_ops_count && Runqueue_should_poll_nonblocking(runqueue)) {
|
99
109
|
// this prevents event starvation in case the run queue never empties
|
100
110
|
Backend_poll(backend, Qtrue, current_fiber, runqueue);
|
101
111
|
}
|
102
112
|
break;
|
103
113
|
}
|
104
|
-
|
105
|
-
|
114
|
+
|
115
|
+
if (pending_ops_count == 0) break;
|
106
116
|
Backend_poll(backend, Qnil, current_fiber, runqueue);
|
107
117
|
backend_was_polled = 1;
|
108
118
|
}
|
@@ -138,6 +148,10 @@ VALUE Thread_debug(VALUE self) {
|
|
138
148
|
return self;
|
139
149
|
}
|
140
150
|
|
151
|
+
VALUE Thread_class_backend(VALUE _self) {
|
152
|
+
return rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
153
|
+
}
|
154
|
+
|
141
155
|
void Init_Thread() {
|
142
156
|
rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
|
143
157
|
rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
|
@@ -150,6 +164,8 @@ void Init_Thread() {
|
|
150
164
|
rb_define_method(rb_cThread, "fiber_scheduling_index", Thread_fiber_scheduling_index, 1);
|
151
165
|
rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
|
152
166
|
|
167
|
+
rb_define_singleton_method(rb_cThread, "backend", Thread_class_backend, 0);
|
168
|
+
|
153
169
|
rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
|
154
170
|
|
155
171
|
ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
|
data/lib/polyphony/core/sync.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'openssl'
|
4
4
|
require_relative './socket'
|
5
5
|
|
6
|
-
#
|
6
|
+
# OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
|
7
7
|
class ::OpenSSL::SSL::SSLSocket
|
8
8
|
alias_method :orig_initialize, :initialize
|
9
9
|
def initialize(socket, context = nil)
|
@@ -23,22 +23,12 @@ class ::OpenSSL::SSL::SSLSocket
|
|
23
23
|
io.reuse_addr
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
when :wait_writable then Polyphony.backend_wait_io(io, true)
|
33
|
-
else
|
34
|
-
return result
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def accept_loop
|
40
|
-
while true
|
41
|
-
yield accept
|
26
|
+
def fill_rbuff
|
27
|
+
data = self.sysread(BLOCK_SIZE)
|
28
|
+
if data
|
29
|
+
@rbuffer << data
|
30
|
+
else
|
31
|
+
@eof = true
|
42
32
|
end
|
43
33
|
end
|
44
34
|
|
@@ -84,4 +74,21 @@ class ::OpenSSL::SSL::SSLSocket
|
|
84
74
|
yield data
|
85
75
|
end
|
86
76
|
end
|
77
|
+
alias_method :recv_loop, :read_loop
|
78
|
+
|
79
|
+
alias_method :orig_peeraddr, :peeraddr
|
80
|
+
def peeraddr(_ = nil)
|
81
|
+
orig_peeraddr
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
|
86
|
+
class ::OpenSSL::SSL::SSLServer
|
87
|
+
def accept_loop(ignore_errors = true)
|
88
|
+
loop do
|
89
|
+
yield accept
|
90
|
+
rescue SystemCallError, StandardError => e
|
91
|
+
raise e unless ignore_errors
|
92
|
+
end
|
93
|
+
end
|
87
94
|
end
|
@@ -36,9 +36,9 @@ class ::Socket
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def recvfrom(maxlen, flags = 0)
|
39
|
-
|
39
|
+
buf = +''
|
40
40
|
while true
|
41
|
-
result = recvfrom_nonblock(maxlen, flags,
|
41
|
+
result = recvfrom_nonblock(maxlen, flags, buf, **NO_EXCEPTION)
|
42
42
|
case result
|
43
43
|
when nil then raise IOError
|
44
44
|
when :wait_readable then Polyphony.backend_wait_io(self, false)
|
@@ -48,14 +48,17 @@ class ::Socket
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def send(mesg, flags)
|
52
|
-
|
53
|
-
end
|
51
|
+
# def send(mesg, flags)
|
52
|
+
# Polyphony.backend_send(self, mesg, flags)
|
53
|
+
# end
|
54
54
|
|
55
|
-
def write(*args)
|
56
|
-
|
57
|
-
end
|
58
|
-
|
55
|
+
# def write(*args)
|
56
|
+
# Polyphony.backend_sendv(self, args, 0)
|
57
|
+
# end
|
58
|
+
|
59
|
+
# def <<(mesg)
|
60
|
+
# Polyphony.backend_send(self, mesg, 0)
|
61
|
+
# end
|
59
62
|
|
60
63
|
def readpartial(maxlen, str = +'')
|
61
64
|
Polyphony.backend_recv(self, str, maxlen)
|
@@ -150,26 +153,22 @@ class ::TCPSocket
|
|
150
153
|
Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
|
151
154
|
end
|
152
155
|
|
153
|
-
def send(mesg, flags)
|
154
|
-
|
155
|
-
end
|
156
|
+
# def send(mesg, flags)
|
157
|
+
# Polyphony.backend_send(self, mesg, flags)
|
158
|
+
# end
|
156
159
|
|
157
|
-
def write(*args)
|
158
|
-
|
159
|
-
end
|
160
|
-
alias_method :<<, :write
|
160
|
+
# def write(*args)
|
161
|
+
# Polyphony.backend_sendv(self, args, 0)
|
162
|
+
# end
|
161
163
|
|
162
|
-
def
|
163
|
-
|
164
|
-
|
164
|
+
# def <<(mesg)
|
165
|
+
# Polyphony.backend_send(self, mesg, 0)
|
166
|
+
# end
|
167
|
+
|
168
|
+
def readpartial(maxlen, str = +'')
|
169
|
+
result = Polyphony.backend_recv(self, str, maxlen)
|
165
170
|
raise EOFError unless result
|
166
171
|
|
167
|
-
if str
|
168
|
-
str << @read_buffer
|
169
|
-
else
|
170
|
-
str = @read_buffer
|
171
|
-
end
|
172
|
-
@read_buffer = +''
|
173
172
|
str
|
174
173
|
end
|
175
174
|
|
@@ -238,19 +237,15 @@ class ::UNIXSocket
|
|
238
237
|
def write(*args)
|
239
238
|
Polyphony.backend_sendv(self, args, 0)
|
240
239
|
end
|
241
|
-
alias_method :<<, :write
|
242
240
|
|
243
|
-
def
|
244
|
-
|
245
|
-
|
241
|
+
def <<(mesg)
|
242
|
+
Polyphony.backend_send(self, mesg, 0)
|
243
|
+
end
|
244
|
+
|
245
|
+
def readpartial(maxlen, str = +'')
|
246
|
+
result = Polyphony.backend_recv(self, str, maxlen)
|
246
247
|
raise EOFError unless result
|
247
248
|
|
248
|
-
if str
|
249
|
-
str << @read_buffer
|
250
|
-
else
|
251
|
-
str = @read_buffer
|
252
|
-
end
|
253
|
-
@read_buffer = +''
|
254
249
|
str
|
255
250
|
end
|
256
251
|
|
data/lib/polyphony/version.rb
CHANGED
data/polyphony.gemspec
CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
|
|
37
37
|
s.add_development_dependency 'mysql2', '0.5.3'
|
38
38
|
s.add_development_dependency 'sequel', '5.34.0'
|
39
39
|
s.add_development_dependency 'httparty', '0.17.1'
|
40
|
+
s.add_development_dependency 'localhost', '~>1.1.4'
|
40
41
|
|
41
42
|
# s.add_development_dependency 'jekyll', '~>3.8.6'
|
42
43
|
# s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
|
data/test/helper.rb
CHANGED
@@ -15,9 +15,9 @@ require 'minitest/reporters'
|
|
15
15
|
|
16
16
|
::Exception.__disable_sanitized_backtrace__ = true
|
17
17
|
|
18
|
-
Minitest::Reporters.use! [
|
19
|
-
|
20
|
-
]
|
18
|
+
# Minitest::Reporters.use! [
|
19
|
+
# Minitest::Reporters::SpecReporter.new
|
20
|
+
# ]
|
21
21
|
|
22
22
|
class ::Fiber
|
23
23
|
attr_writer :auto_watcher
|
data/test/test_backend.rb
CHANGED
@@ -26,7 +26,7 @@ class BackendTest < MiniTest::Test
|
|
26
26
|
@backend.sleep 0.01
|
27
27
|
count += 1
|
28
28
|
}.await
|
29
|
-
assert_in_delta 0.03, Time.now - t0, 0.
|
29
|
+
assert_in_delta 0.03, Time.now - t0, 0.01
|
30
30
|
assert_equal 3, count
|
31
31
|
end
|
32
32
|
|
@@ -237,4 +237,167 @@ class BackendTest < MiniTest::Test
|
|
237
237
|
end
|
238
238
|
assert_equal [1], buffer
|
239
239
|
end
|
240
|
+
|
241
|
+
def test_splice
|
242
|
+
i1, o1 = IO.pipe
|
243
|
+
i2, o2 = IO.pipe
|
244
|
+
len = nil
|
245
|
+
|
246
|
+
spin {
|
247
|
+
len = o2.splice(i1, 1000)
|
248
|
+
o2.close
|
249
|
+
}
|
250
|
+
|
251
|
+
o1.write('foobar')
|
252
|
+
result = i2.read
|
253
|
+
|
254
|
+
assert_equal 'foobar', result
|
255
|
+
assert_equal 6, len
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_splice_to_eof
|
259
|
+
i1, o1 = IO.pipe
|
260
|
+
i2, o2 = IO.pipe
|
261
|
+
len = nil
|
262
|
+
|
263
|
+
f = spin {
|
264
|
+
len = o2.splice_to_eof(i1, 1000)
|
265
|
+
o2.close
|
266
|
+
}
|
267
|
+
|
268
|
+
o1.write('foo')
|
269
|
+
result = i2.readpartial(1000)
|
270
|
+
assert_equal 'foo', result
|
271
|
+
|
272
|
+
o1.write('bar')
|
273
|
+
result = i2.readpartial(1000)
|
274
|
+
assert_equal 'bar', result
|
275
|
+
o1.close
|
276
|
+
f.await
|
277
|
+
assert_equal 6, len
|
278
|
+
ensure
|
279
|
+
if f.alive?
|
280
|
+
f.interrupt
|
281
|
+
f.await
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
class BackendChainTest < MiniTest::Test
|
287
|
+
def setup
|
288
|
+
super
|
289
|
+
@prev_backend = Thread.current.backend
|
290
|
+
@backend = Polyphony::Backend.new
|
291
|
+
Thread.current.backend = @backend
|
292
|
+
end
|
293
|
+
|
294
|
+
def teardown
|
295
|
+
@backend.finalize
|
296
|
+
Thread.current.backend = @prev_backend
|
297
|
+
end
|
298
|
+
|
299
|
+
def test_simple_write_chain
|
300
|
+
i, o = IO.pipe
|
301
|
+
|
302
|
+
result = Thread.backend.chain(
|
303
|
+
[:write, o, 'hello'],
|
304
|
+
[:write, o, ' world']
|
305
|
+
)
|
306
|
+
|
307
|
+
assert_equal 6, result
|
308
|
+
o.close
|
309
|
+
assert_equal 'hello world', i.read
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_simple_send_chain
|
313
|
+
port = rand(1234..5678)
|
314
|
+
server = TCPServer.new('127.0.0.1', port)
|
315
|
+
|
316
|
+
server_fiber = spin do
|
317
|
+
while (socket = server.accept)
|
318
|
+
spin do
|
319
|
+
while (data = socket.gets(8192))
|
320
|
+
socket << data
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
snooze
|
327
|
+
client = TCPSocket.new('127.0.0.1', port)
|
328
|
+
|
329
|
+
result = Thread.backend.chain(
|
330
|
+
[:send, client, 'hello', 0],
|
331
|
+
[:send, client, " world\n", 0]
|
332
|
+
)
|
333
|
+
sleep 0.1
|
334
|
+
assert_equal "hello world\n", client.recv(8192)
|
335
|
+
client.close
|
336
|
+
ensure
|
337
|
+
server_fiber&.stop
|
338
|
+
server_fiber&.await
|
339
|
+
server&.close
|
340
|
+
end
|
341
|
+
|
342
|
+
def chunk_header(len)
|
343
|
+
"Content-Length: #{len}\r\n\r\n"
|
344
|
+
end
|
345
|
+
|
346
|
+
def serve_io(from, to)
|
347
|
+
i, o = IO.pipe
|
348
|
+
backend = Thread.current.backend
|
349
|
+
while true
|
350
|
+
len = o.splice(from, 8192)
|
351
|
+
break if len == 0
|
352
|
+
|
353
|
+
backend.chain(
|
354
|
+
[:write, to, chunk_header(len)],
|
355
|
+
[:splice, i, to, len]
|
356
|
+
)
|
357
|
+
end
|
358
|
+
to.close
|
359
|
+
end
|
360
|
+
|
361
|
+
def test_chain_with_splice
|
362
|
+
from_r, from_w = IO.pipe
|
363
|
+
to_r, to_w = IO.pipe
|
364
|
+
|
365
|
+
result = nil
|
366
|
+
f = spin { serve_io(from_r, to_w) }
|
367
|
+
|
368
|
+
from_w << 'Hello world!'
|
369
|
+
from_w.close
|
370
|
+
|
371
|
+
assert_equal "Content-Length: 12\r\n\r\nHello world!", to_r.read
|
372
|
+
end
|
373
|
+
|
374
|
+
def test_invalid_op
|
375
|
+
i, o = IO.pipe
|
376
|
+
|
377
|
+
assert_raises(RuntimeError) {
|
378
|
+
Thread.backend.chain(
|
379
|
+
[:read, i]
|
380
|
+
)
|
381
|
+
}
|
382
|
+
|
383
|
+
assert_raises(RuntimeError) {
|
384
|
+
Thread.backend.chain(
|
385
|
+
[:write, o, 'abc'],
|
386
|
+
[:write, o, 'abc'],
|
387
|
+
[:write, o, 'abc'],
|
388
|
+
[:read, i]
|
389
|
+
)
|
390
|
+
}
|
391
|
+
|
392
|
+
assert_raises(RuntimeError) {
|
393
|
+
Thread.backend.chain(
|
394
|
+
[:write, o]
|
395
|
+
)
|
396
|
+
}
|
397
|
+
|
398
|
+
# Eventually we should add some APIs to the io_uring backend to query the
|
399
|
+
# contxt store, then add some tests here to verify that the chain op ctx is
|
400
|
+
# released properly before raising the error (for the time being this has
|
401
|
+
# been verified manually).
|
402
|
+
end
|
240
403
|
end
|