polyphony 0.53.0 → 0.56.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 +47 -23
- data/Gemfile.lock +3 -1
- data/TODO.md +4 -7
- data/examples/core/idle_gc.rb +21 -0
- data/examples/core/queue.rb +19 -0
- data/examples/io/https_server.rb +30 -0
- data/examples/io/pipe.rb +11 -0
- data/examples/io/stdio.rb +8 -0
- data/ext/polyphony/backend_common.c +186 -0
- data/ext/polyphony/backend_common.h +25 -130
- data/ext/polyphony/backend_io_uring.c +219 -114
- data/ext/polyphony/backend_io_uring_context.c +14 -2
- data/ext/polyphony/backend_io_uring_context.h +11 -11
- data/ext/polyphony/backend_libev.c +246 -83
- data/ext/polyphony/polyphony.c +17 -15
- data/ext/polyphony/polyphony.h +3 -0
- data/ext/polyphony/runqueue.c +29 -1
- data/ext/polyphony/thread.c +27 -6
- data/lib/polyphony/core/sync.rb +8 -0
- data/lib/polyphony/extensions/openssl.rb +24 -17
- data/lib/polyphony/extensions/socket.rb +6 -20
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -0
- data/test/helper.rb +3 -3
- data/test/test_backend.rb +159 -5
- 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 +16 -10
- metadata +22 -2
data/ext/polyphony/polyphony.c
CHANGED
@@ -10,6 +10,7 @@ ID ID_each;
|
|
10
10
|
ID ID_inspect;
|
11
11
|
ID ID_invoke;
|
12
12
|
ID ID_new;
|
13
|
+
ID ID_ivar_blocking_mode;
|
13
14
|
ID ID_ivar_io;
|
14
15
|
ID ID_ivar_runnable;
|
15
16
|
ID ID_ivar_running;
|
@@ -158,19 +159,20 @@ void Init_Polyphony() {
|
|
158
159
|
|
159
160
|
cTimeoutException = rb_define_class_under(mPolyphony, "TimeoutException", rb_eException);
|
160
161
|
|
161
|
-
ID_call
|
162
|
-
ID_caller
|
163
|
-
ID_clear
|
164
|
-
ID_each
|
165
|
-
ID_inspect
|
166
|
-
ID_invoke
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
162
|
+
ID_call = rb_intern("call");
|
163
|
+
ID_caller = rb_intern("caller");
|
164
|
+
ID_clear = rb_intern("clear");
|
165
|
+
ID_each = rb_intern("each");
|
166
|
+
ID_inspect = rb_intern("inspect");
|
167
|
+
ID_invoke = rb_intern("invoke");
|
168
|
+
ID_ivar_blocking_mode = rb_intern("@blocking_mode");
|
169
|
+
ID_ivar_io = rb_intern("@io");
|
170
|
+
ID_ivar_runnable = rb_intern("@runnable");
|
171
|
+
ID_ivar_running = rb_intern("@running");
|
172
|
+
ID_ivar_thread = rb_intern("@thread");
|
173
|
+
ID_new = rb_intern("new");
|
174
|
+
ID_signal = rb_intern("signal");
|
175
|
+
ID_size = rb_intern("size");
|
176
|
+
ID_switch_fiber = rb_intern("switch_fiber");
|
177
|
+
ID_transfer = rb_intern("transfer");
|
176
178
|
}
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -47,6 +47,7 @@ extern ID ID_fiber_trace;
|
|
47
47
|
extern ID ID_inspect;
|
48
48
|
extern ID ID_invoke;
|
49
49
|
extern ID ID_ivar_backend;
|
50
|
+
extern ID ID_ivar_blocking_mode;
|
50
51
|
extern ID ID_ivar_io;
|
51
52
|
extern ID ID_ivar_runnable;
|
52
53
|
extern ID ID_ivar_running;
|
@@ -90,6 +91,7 @@ int Runqueue_index_of(VALUE self, VALUE fiber);
|
|
90
91
|
void Runqueue_clear(VALUE self);
|
91
92
|
long Runqueue_len(VALUE self);
|
92
93
|
int Runqueue_empty_p(VALUE self);
|
94
|
+
int Runqueue_should_poll_nonblocking(VALUE self);
|
93
95
|
|
94
96
|
#ifdef POLYPHONY_BACKEND_LIBEV
|
95
97
|
#define Backend_recv_loop Backend_read_loop
|
@@ -123,6 +125,7 @@ unsigned int Backend_pending_count(VALUE self);
|
|
123
125
|
VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
|
124
126
|
VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
|
125
127
|
VALUE Backend_wakeup(VALUE self);
|
128
|
+
VALUE Backend_run_idle_tasks(VALUE self);
|
126
129
|
|
127
130
|
VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
|
128
131
|
VALUE Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
|
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);
|
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,8 +86,9 @@ 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
|
+
unsigned int idle_tasks_run_count = 0;
|
91
92
|
|
92
93
|
if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
|
93
94
|
TRACE(2, SYM_fiber_switchpoint, current_fiber);
|
@@ -95,14 +96,28 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
95
96
|
while (1) {
|
96
97
|
next = Runqueue_shift(runqueue);
|
97
98
|
if (next.fiber != Qnil) {
|
98
|
-
|
99
|
+
// Polling for I/O op completion is normally done when the run queue is
|
100
|
+
// empty, but if the runqueue never empties, we'll never get to process
|
101
|
+
// any event completions. In order to prevent this, an anti-starve
|
102
|
+
// mechanism is employed, under the following conditions:
|
103
|
+
// - a blocking poll was not yet performed
|
104
|
+
// - there are pending blocking operations
|
105
|
+
// - the runqueue has signalled that a non-blocking poll should be
|
106
|
+
// performed
|
107
|
+
// - the run queue length high watermark has reached its threshold (currently 128)
|
108
|
+
// - the run queue switch counter has reached its threshold (currently 64)
|
109
|
+
if (!backend_was_polled && pending_ops_count && Runqueue_should_poll_nonblocking(runqueue)) {
|
99
110
|
// this prevents event starvation in case the run queue never empties
|
100
111
|
Backend_poll(backend, Qtrue, current_fiber, runqueue);
|
101
112
|
}
|
102
113
|
break;
|
103
114
|
}
|
104
|
-
|
105
|
-
|
115
|
+
|
116
|
+
if (!idle_tasks_run_count) {
|
117
|
+
idle_tasks_run_count++;
|
118
|
+
Backend_run_idle_tasks(backend);
|
119
|
+
}
|
120
|
+
if (pending_ops_count == 0) break;
|
106
121
|
Backend_poll(backend, Qnil, current_fiber, runqueue);
|
107
122
|
backend_was_polled = 1;
|
108
123
|
}
|
@@ -138,6 +153,10 @@ VALUE Thread_debug(VALUE self) {
|
|
138
153
|
return self;
|
139
154
|
}
|
140
155
|
|
156
|
+
VALUE Thread_class_backend(VALUE _self) {
|
157
|
+
return rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
158
|
+
}
|
159
|
+
|
141
160
|
void Init_Thread() {
|
142
161
|
rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
|
143
162
|
rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
|
@@ -150,6 +169,8 @@ void Init_Thread() {
|
|
150
169
|
rb_define_method(rb_cThread, "fiber_scheduling_index", Thread_fiber_scheduling_index, 1);
|
151
170
|
rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
|
152
171
|
|
172
|
+
rb_define_singleton_method(rb_cThread, "backend", Thread_class_backend, 0);
|
173
|
+
|
153
174
|
rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
|
154
175
|
|
155
176
|
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)
|
@@ -165,17 +165,10 @@ class ::TCPSocket
|
|
165
165
|
# Polyphony.backend_send(self, mesg, 0)
|
166
166
|
# end
|
167
167
|
|
168
|
-
def readpartial(maxlen, str =
|
169
|
-
|
170
|
-
result = Polyphony.backend_recv(self, @read_buffer, maxlen)
|
168
|
+
def readpartial(maxlen, str = +'')
|
169
|
+
result = Polyphony.backend_recv(self, str, maxlen)
|
171
170
|
raise EOFError unless result
|
172
171
|
|
173
|
-
if str
|
174
|
-
str << @read_buffer
|
175
|
-
else
|
176
|
-
str = @read_buffer
|
177
|
-
end
|
178
|
-
@read_buffer = +''
|
179
172
|
str
|
180
173
|
end
|
181
174
|
|
@@ -249,17 +242,10 @@ class ::UNIXSocket
|
|
249
242
|
Polyphony.backend_send(self, mesg, 0)
|
250
243
|
end
|
251
244
|
|
252
|
-
def readpartial(maxlen, str =
|
253
|
-
|
254
|
-
result = Polyphony.backend_recv(self, @read_buffer, maxlen)
|
245
|
+
def readpartial(maxlen, str = +'')
|
246
|
+
result = Polyphony.backend_recv(self, str, maxlen)
|
255
247
|
raise EOFError unless result
|
256
248
|
|
257
|
-
if str
|
258
|
-
str << @read_buffer
|
259
|
-
else
|
260
|
-
str = @read_buffer
|
261
|
-
end
|
262
|
-
@read_buffer = +''
|
263
249
|
str
|
264
250
|
end
|
265
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
|
|
@@ -241,9 +241,10 @@ class BackendTest < MiniTest::Test
|
|
241
241
|
def test_splice
|
242
242
|
i1, o1 = IO.pipe
|
243
243
|
i2, o2 = IO.pipe
|
244
|
+
len = nil
|
244
245
|
|
245
246
|
spin {
|
246
|
-
o2.splice(i1, 1000)
|
247
|
+
len = o2.splice(i1, 1000)
|
247
248
|
o2.close
|
248
249
|
}
|
249
250
|
|
@@ -251,14 +252,16 @@ class BackendTest < MiniTest::Test
|
|
251
252
|
result = i2.read
|
252
253
|
|
253
254
|
assert_equal 'foobar', result
|
255
|
+
assert_equal 6, len
|
254
256
|
end
|
255
257
|
|
256
258
|
def test_splice_to_eof
|
257
259
|
i1, o1 = IO.pipe
|
258
260
|
i2, o2 = IO.pipe
|
261
|
+
len = nil
|
259
262
|
|
260
263
|
f = spin {
|
261
|
-
o2.splice_to_eof(i1, 1000)
|
264
|
+
len = o2.splice_to_eof(i1, 1000)
|
262
265
|
o2.close
|
263
266
|
}
|
264
267
|
|
@@ -269,8 +272,159 @@ class BackendTest < MiniTest::Test
|
|
269
272
|
o1.write('bar')
|
270
273
|
result = i2.readpartial(1000)
|
271
274
|
assert_equal 'bar', result
|
272
|
-
|
273
|
-
f.interrupt
|
275
|
+
o1.close
|
274
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
|
+
|
285
|
+
def test_idle_gc
|
286
|
+
GC.disable
|
287
|
+
|
288
|
+
count = GC.count
|
289
|
+
snooze
|
290
|
+
assert_equal count, GC.count
|
291
|
+
sleep 0.01
|
292
|
+
assert_equal count, GC.count
|
293
|
+
|
294
|
+
@backend.idle_gc_period = 0.1
|
295
|
+
snooze
|
296
|
+
assert_equal count, GC.count
|
297
|
+
sleep 0.05
|
298
|
+
assert_equal count, GC.count
|
299
|
+
# The idle tasks are ran at most once per fiber switch, before the backend
|
300
|
+
# is polled. Therefore, the second sleep will not have triggered a GC, since
|
301
|
+
# only 0.05s have passed since the gc period was set.
|
302
|
+
sleep 0.07
|
303
|
+
assert_equal count, GC.count
|
304
|
+
# Upon the third sleep the GC should be triggered, at 0.12s post setting the
|
305
|
+
# GC period.
|
306
|
+
sleep 0.05
|
307
|
+
assert_equal count + 1, GC.count
|
308
|
+
ensure
|
309
|
+
GC.enable
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class BackendChainTest < MiniTest::Test
|
314
|
+
def setup
|
315
|
+
super
|
316
|
+
@prev_backend = Thread.current.backend
|
317
|
+
@backend = Polyphony::Backend.new
|
318
|
+
Thread.current.backend = @backend
|
319
|
+
end
|
320
|
+
|
321
|
+
def teardown
|
322
|
+
@backend.finalize
|
323
|
+
Thread.current.backend = @prev_backend
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_simple_write_chain
|
327
|
+
i, o = IO.pipe
|
328
|
+
|
329
|
+
result = Thread.backend.chain(
|
330
|
+
[:write, o, 'hello'],
|
331
|
+
[:write, o, ' world']
|
332
|
+
)
|
333
|
+
|
334
|
+
assert_equal 6, result
|
335
|
+
o.close
|
336
|
+
assert_equal 'hello world', i.read
|
337
|
+
end
|
338
|
+
|
339
|
+
def test_simple_send_chain
|
340
|
+
port = rand(1234..5678)
|
341
|
+
server = TCPServer.new('127.0.0.1', port)
|
342
|
+
|
343
|
+
server_fiber = spin do
|
344
|
+
while (socket = server.accept)
|
345
|
+
spin do
|
346
|
+
while (data = socket.gets(8192))
|
347
|
+
socket << data
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
snooze
|
354
|
+
client = TCPSocket.new('127.0.0.1', port)
|
355
|
+
|
356
|
+
result = Thread.backend.chain(
|
357
|
+
[:send, client, 'hello', 0],
|
358
|
+
[:send, client, " world\n", 0]
|
359
|
+
)
|
360
|
+
sleep 0.1
|
361
|
+
assert_equal "hello world\n", client.recv(8192)
|
362
|
+
client.close
|
363
|
+
ensure
|
364
|
+
server_fiber&.stop
|
365
|
+
server_fiber&.await
|
366
|
+
server&.close
|
367
|
+
end
|
368
|
+
|
369
|
+
def chunk_header(len)
|
370
|
+
"Content-Length: #{len}\r\n\r\n"
|
371
|
+
end
|
372
|
+
|
373
|
+
def serve_io(from, to)
|
374
|
+
i, o = IO.pipe
|
375
|
+
backend = Thread.current.backend
|
376
|
+
while true
|
377
|
+
len = o.splice(from, 8192)
|
378
|
+
break if len == 0
|
379
|
+
|
380
|
+
backend.chain(
|
381
|
+
[:write, to, chunk_header(len)],
|
382
|
+
[:splice, i, to, len]
|
383
|
+
)
|
384
|
+
end
|
385
|
+
to.close
|
386
|
+
end
|
387
|
+
|
388
|
+
def test_chain_with_splice
|
389
|
+
from_r, from_w = IO.pipe
|
390
|
+
to_r, to_w = IO.pipe
|
391
|
+
|
392
|
+
result = nil
|
393
|
+
f = spin { serve_io(from_r, to_w) }
|
394
|
+
|
395
|
+
from_w << 'Hello world!'
|
396
|
+
from_w.close
|
397
|
+
|
398
|
+
assert_equal "Content-Length: 12\r\n\r\nHello world!", to_r.read
|
399
|
+
end
|
400
|
+
|
401
|
+
def test_invalid_op
|
402
|
+
i, o = IO.pipe
|
403
|
+
|
404
|
+
assert_raises(RuntimeError) {
|
405
|
+
Thread.backend.chain(
|
406
|
+
[:read, i]
|
407
|
+
)
|
408
|
+
}
|
409
|
+
|
410
|
+
assert_raises(RuntimeError) {
|
411
|
+
Thread.backend.chain(
|
412
|
+
[:write, o, 'abc'],
|
413
|
+
[:write, o, 'abc'],
|
414
|
+
[:write, o, 'abc'],
|
415
|
+
[:read, i]
|
416
|
+
)
|
417
|
+
}
|
418
|
+
|
419
|
+
assert_raises(RuntimeError) {
|
420
|
+
Thread.backend.chain(
|
421
|
+
[:write, o]
|
422
|
+
)
|
423
|
+
}
|
424
|
+
|
425
|
+
# Eventually we should add some APIs to the io_uring backend to query the
|
426
|
+
# contxt store, then add some tests here to verify that the chain op ctx is
|
427
|
+
# released properly before raising the error (for the time being this has
|
428
|
+
# been verified manually).
|
275
429
|
end
|
276
430
|
end
|