polyphony 0.43.2 → 0.43.8
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 +2 -2
- data/CHANGELOG.md +43 -0
- data/Gemfile.lock +2 -2
- data/README.md +2 -0
- data/TODO.md +2 -3
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_user-guide/web-server.md +11 -11
- data/docs/getting-started/overview.md +4 -4
- data/docs/index.md +4 -3
- data/docs/main-concepts/design-principles.md +23 -34
- data/docs/main-concepts/fiber-scheduling.md +1 -1
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/io/xx-happy-eyeballs.rb +21 -22
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/mem-usage.rb +34 -28
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +11 -9
- data/examples/xx-spin.rb +32 -0
- data/ext/polyphony/libev_agent.c +181 -24
- data/ext/polyphony/polyphony.c +0 -2
- data/ext/polyphony/polyphony.h +14 -7
- data/ext/polyphony/polyphony_ext.c +2 -2
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +16 -8
- data/lib/polyphony.rb +28 -12
- data/lib/polyphony/core/global_api.rb +5 -3
- data/lib/polyphony/core/resource_pool.rb +19 -9
- data/lib/polyphony/core/thread_pool.rb +1 -1
- data/lib/polyphony/event.rb +5 -15
- data/lib/polyphony/extensions/core.rb +40 -0
- data/lib/polyphony/extensions/fiber.rb +9 -14
- data/lib/polyphony/extensions/io.rb +17 -16
- data/lib/polyphony/extensions/openssl.rb +8 -0
- data/lib/polyphony/extensions/socket.rb +12 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/q.rb +24 -0
- data/test/test_agent.rb +3 -3
- data/test/test_event.rb +11 -0
- data/test/test_fiber.rb +3 -3
- data/test/test_global_api.rb +48 -15
- data/test/test_io.rb +24 -2
- data/test/test_queue.rb +39 -1
- data/test/test_resource_pool.rb +12 -0
- data/test/test_throttler.rb +6 -5
- data/test/test_trace.rb +18 -17
- metadata +15 -4
- data/ext/polyphony/libev_queue.c +0 -217
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
i, o = IO.pipe
|
8
|
+
|
9
|
+
w = Zlib::GzipWriter.new(o)
|
10
|
+
|
11
|
+
s = (1..1000).map { (65 + rand(26)).chr }.join
|
12
|
+
puts "full length: #{s.bytesize}"
|
13
|
+
w << s
|
14
|
+
w.close
|
15
|
+
o.close
|
16
|
+
|
17
|
+
|
18
|
+
z = i.read
|
19
|
+
puts "zipped length: #{z.bytesize}"
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
class Fiber
|
6
|
+
attr_accessor :next
|
7
|
+
end
|
8
|
+
|
9
|
+
# This program shows how the performance
|
10
|
+
|
11
|
+
def run(num_fibers)
|
12
|
+
count = 0
|
13
|
+
|
14
|
+
GC.disable
|
15
|
+
|
16
|
+
first = nil
|
17
|
+
last = nil
|
18
|
+
supervisor = Fiber.current
|
19
|
+
num_fibers.times do
|
20
|
+
fiber = Fiber.new do
|
21
|
+
loop do
|
22
|
+
count += 1
|
23
|
+
if count == 1_000_000
|
24
|
+
supervisor.transfer
|
25
|
+
else
|
26
|
+
Fiber.current.next.transfer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
first ||= fiber
|
31
|
+
last.next = fiber if last
|
32
|
+
last = fiber
|
33
|
+
end
|
34
|
+
|
35
|
+
last.next = first
|
36
|
+
|
37
|
+
t0 = Time.now
|
38
|
+
first.transfer
|
39
|
+
elapsed = Time.now - t0
|
40
|
+
|
41
|
+
puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
|
42
|
+
GC.start
|
43
|
+
end
|
44
|
+
|
45
|
+
run(100)
|
46
|
+
run(1000)
|
47
|
+
run(10000)
|
@@ -4,47 +4,53 @@ def mem_usage
|
|
4
4
|
`ps -o rss #{$$}`.split.last.to_i
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
7
|
+
def calculate_memory_cost(name, count, &block)
|
8
|
+
GC.enable
|
9
|
+
ObjectSpace.garbage_collect
|
10
|
+
sleep 0.5
|
8
11
|
GC.disable
|
9
12
|
rss0 = mem_usage
|
10
|
-
|
13
|
+
count0 = ObjectSpace.count_objects[:TOTAL] - ObjectSpace.count_objects[:FREE]
|
14
|
+
a = []
|
15
|
+
count.times { a << block.call }
|
11
16
|
rss1 = mem_usage
|
12
|
-
|
17
|
+
count1 = ObjectSpace.count_objects[:TOTAL] - ObjectSpace.count_objects[:FREE]
|
18
|
+
p [count0, count1]
|
19
|
+
# sleep 0.5
|
13
20
|
cost = (rss1 - rss0).to_f / count
|
21
|
+
count_delta = (count1 - count0) / count
|
14
22
|
|
15
|
-
puts "
|
23
|
+
puts "#{name} rss cost: #{cost}KB object count: #{count_delta}"
|
16
24
|
end
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
def calculate_thread_memory_cost(count)
|
21
|
-
GC.disable
|
22
|
-
rss0 = mem_usage
|
23
|
-
count.times { Thread.new { sleep 1 } }
|
24
|
-
sleep 0.5
|
25
|
-
rss1 = mem_usage
|
26
|
-
sleep 0.5
|
27
|
-
GC.start
|
28
|
-
cost = (rss1 - rss0).to_f / count
|
26
|
+
f = Fiber.new { |f| f.transfer }
|
27
|
+
f.transfer Fiber.current
|
29
28
|
|
30
|
-
|
29
|
+
calculate_memory_cost('fiber', 10000) do
|
30
|
+
f = Fiber.new { |f| f.transfer :foo }
|
31
|
+
f.transfer Fiber.current
|
32
|
+
f
|
31
33
|
end
|
32
34
|
|
33
|
-
|
35
|
+
t = Thread.new { sleep 1}
|
36
|
+
t.kill
|
37
|
+
t.join
|
38
|
+
|
39
|
+
calculate_memory_cost('thread', 500) do
|
40
|
+
t = Thread.new { sleep 1 }
|
41
|
+
sleep 0.001
|
42
|
+
t
|
43
|
+
end
|
44
|
+
(Thread.list - [Thread.current]).each(&:kill).each(&:join)
|
34
45
|
|
35
46
|
require 'bundler/setup'
|
36
47
|
require 'polyphony'
|
37
48
|
|
38
|
-
|
39
|
-
|
40
|
-
rss0 = mem_usage
|
41
|
-
count.times { spin { :foo } }
|
42
|
-
snooze
|
43
|
-
rss1 = mem_usage
|
44
|
-
GC.start
|
45
|
-
cost = (rss1 - rss0).to_f / count
|
49
|
+
f = spin { sleep 0.1 }
|
50
|
+
f.await
|
46
51
|
|
47
|
-
|
52
|
+
calculate_memory_cost('polyphony fiber', 10000) do
|
53
|
+
f = spin { :foo }
|
54
|
+
f.await
|
55
|
+
f
|
48
56
|
end
|
49
|
-
|
50
|
-
calculate_extended_fiber_memory_cost(10000)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
X = 1_000_000
|
7
|
+
|
8
|
+
GC.disable
|
9
|
+
|
10
|
+
count = 0
|
11
|
+
|
12
|
+
pong = spin_loop do
|
13
|
+
msg, ping = receive
|
14
|
+
count += 1
|
15
|
+
ping << 'pong'
|
16
|
+
end
|
17
|
+
|
18
|
+
ping = spin do
|
19
|
+
X.times do
|
20
|
+
pong << ['ping', Fiber.current]
|
21
|
+
msg = receive
|
22
|
+
count += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
t0 = Time.now
|
27
|
+
ping.await
|
28
|
+
dt = Time.now - t0
|
29
|
+
puts format('message rate: %d/s', (X / dt))
|
@@ -5,19 +5,20 @@ require 'polyphony'
|
|
5
5
|
|
6
6
|
def bm(fibers, iterations)
|
7
7
|
count = 0
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
count += 1
|
15
|
-
end
|
8
|
+
t_pre = Time.now
|
9
|
+
fibers.times do
|
10
|
+
spin do
|
11
|
+
iterations.times do
|
12
|
+
snooze
|
13
|
+
count += 1
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
17
|
+
t0 = Time.now
|
18
|
+
Fiber.current.await_all_children
|
19
19
|
dt = Time.now - t0
|
20
|
-
puts "#{[fibers, iterations].inspect} count: #{count} #{count / dt.to_f}/s"
|
20
|
+
puts "#{[fibers, iterations].inspect} setup: #{t0 - t_pre}s count: #{count} #{count / dt.to_f}/s"
|
21
|
+
Thread.current.run_queue_trace
|
21
22
|
end
|
22
23
|
|
23
24
|
GC.disable
|
@@ -27,5 +28,6 @@ bm(10, 100_000)
|
|
27
28
|
bm(100, 10_000)
|
28
29
|
bm(1_000, 1_000)
|
29
30
|
bm(10_000, 100)
|
31
|
+
|
30
32
|
# bm(100_000, 10)
|
31
33
|
# bm(1_000_000, 1)
|
data/examples/xx-spin.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
puts "pid: #{Process.pid}"
|
7
|
+
GC.disable
|
8
|
+
|
9
|
+
def mem_usage
|
10
|
+
# orig_backtick('ps -o rss #{$$}').split.last.to_i
|
11
|
+
`ps -o rss #{$$}`.split.last.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
f = File.open('spin.log', 'w+')
|
15
|
+
|
16
|
+
m0 = mem_usage
|
17
|
+
|
18
|
+
X = ARGV[0] ? ARGV[0].to_i : 10
|
19
|
+
STDOUT.orig_write "Starting #{X} fibers...\n"
|
20
|
+
t0 = Time.now
|
21
|
+
x = nil
|
22
|
+
X.times do |i|
|
23
|
+
spin { p i; suspend }
|
24
|
+
end
|
25
|
+
|
26
|
+
suspend
|
27
|
+
f.close
|
28
|
+
t1 = Time.now
|
29
|
+
m1 = mem_usage
|
30
|
+
rate = X / (t1 - t0)
|
31
|
+
mem_cost = (m1 - m0) / X.to_f
|
32
|
+
STDOUT.orig_write("#{ { time: t1 - t0, spin_rate: rate, fiber_mem_cost: mem_cost }.inspect }\n")
|
data/ext/polyphony/libev_agent.c
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
#include <netdb.h>
|
2
2
|
#include <sys/socket.h>
|
3
|
+
#include <sys/uio.h>
|
4
|
+
#include <unistd.h>
|
5
|
+
#include <fcntl.h>
|
3
6
|
|
4
7
|
#include "polyphony.h"
|
5
8
|
#include "../libev/ev.h"
|
@@ -130,7 +133,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
|
|
130
133
|
GetLibevAgent(self, agent);
|
131
134
|
|
132
135
|
if (is_nowait) {
|
133
|
-
long runnable_count =
|
136
|
+
long runnable_count = Queue_len(queue);
|
134
137
|
agent->run_no_wait_count++;
|
135
138
|
if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
|
136
139
|
return self;
|
@@ -277,11 +280,37 @@ VALUE libev_snooze() {
|
|
277
280
|
return Thread_switch_fiber(rb_thread_current());
|
278
281
|
}
|
279
282
|
|
283
|
+
ID ID_ivar_is_nonblocking;
|
284
|
+
|
285
|
+
// Since we need to ensure that fd's are non-blocking before every I/O
|
286
|
+
// operation, here we improve upon Ruby's rb_io_set_nonblock by caching the
|
287
|
+
// "nonblock" state in an instance variable. Calling rb_ivar_get on every read
|
288
|
+
// is still much cheaper than doing a fcntl syscall on every read! Preliminary
|
289
|
+
// benchmarks (with a "hello world" HTTP server) show throughput is improved
|
290
|
+
// by 10-13%.
|
291
|
+
inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
|
292
|
+
#ifdef _WIN32
|
293
|
+
return rb_w32_set_nonblock(fptr->fd);
|
294
|
+
#elif defined(F_GETFL)
|
295
|
+
VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
|
296
|
+
if (is_nonblocking == Qnil) {
|
297
|
+
rb_ivar_set(io, ID_ivar_is_nonblocking, Qtrue);
|
298
|
+
int oflags = fcntl(fptr->fd, F_GETFL);
|
299
|
+
if (oflags == -1) return;
|
300
|
+
if (oflags & O_NONBLOCK) return;
|
301
|
+
oflags |= O_NONBLOCK;
|
302
|
+
fcntl(fptr->fd, F_SETFL, oflags);
|
303
|
+
}
|
304
|
+
#endif
|
305
|
+
return;
|
306
|
+
}
|
307
|
+
|
280
308
|
VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
|
281
309
|
struct LibevAgent_t *agent;
|
282
310
|
struct libev_io watcher;
|
283
311
|
rb_io_t *fptr;
|
284
|
-
long
|
312
|
+
long dynamic_len = length == Qnil;
|
313
|
+
long len = dynamic_len ? 4096 : NUM2INT(length);
|
285
314
|
int shrinkable = io_setstrbuf(&str, len);
|
286
315
|
char *buf = RSTRING_PTR(str);
|
287
316
|
long total = 0;
|
@@ -293,13 +322,22 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
293
322
|
if (underlying_io != Qnil) io = underlying_io;
|
294
323
|
GetOpenFile(io, fptr);
|
295
324
|
rb_io_check_byte_readable(fptr);
|
296
|
-
|
325
|
+
io_set_nonblock(fptr, io);
|
297
326
|
watcher.fiber = Qnil;
|
298
327
|
|
299
328
|
OBJ_TAINT(str);
|
300
329
|
|
301
|
-
|
302
|
-
|
330
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
331
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
332
|
+
// find out if that's the case.
|
333
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
334
|
+
if (fptr->rbuf.len > 0) {
|
335
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
336
|
+
fptr->rbuf.len = 0;
|
337
|
+
}
|
338
|
+
|
339
|
+
while (1) {
|
340
|
+
ssize_t n = read(fptr->fd, buf, len - total);
|
303
341
|
if (n < 0) {
|
304
342
|
int e = errno;
|
305
343
|
if (e != EWOULDBLOCK && e != EAGAIN) rb_syserr_fail(e, strerror(e));
|
@@ -312,14 +350,21 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
312
350
|
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
313
351
|
|
314
352
|
if (n == 0) break; // EOF
|
315
|
-
|
316
353
|
total = total + n;
|
317
|
-
|
318
|
-
|
319
|
-
if (
|
354
|
+
if (!read_to_eof) break;
|
355
|
+
|
356
|
+
if (total == len) {
|
357
|
+
if (!dynamic_len) break;
|
358
|
+
|
359
|
+
rb_str_resize(str, total);
|
360
|
+
rb_str_modify_expand(str, len);
|
361
|
+
buf = RSTRING_PTR(str) + total;
|
362
|
+
shrinkable = 0;
|
363
|
+
len += len;
|
364
|
+
}
|
365
|
+
else buf += n;
|
320
366
|
}
|
321
367
|
}
|
322
|
-
|
323
368
|
if (total == 0) return Qnil;
|
324
369
|
|
325
370
|
io_set_read_length(str, total, shrinkable);
|
@@ -340,6 +385,7 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
|
340
385
|
shrinkable = io_setstrbuf(&str, len); \
|
341
386
|
buf = RSTRING_PTR(str); \
|
342
387
|
total = 0; \
|
388
|
+
OBJ_TAINT(str); \
|
343
389
|
}
|
344
390
|
|
345
391
|
#define YIELD_STR() { \
|
@@ -366,10 +412,17 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
|
366
412
|
if (underlying_io != Qnil) io = underlying_io;
|
367
413
|
GetOpenFile(io, fptr);
|
368
414
|
rb_io_check_byte_readable(fptr);
|
369
|
-
|
415
|
+
io_set_nonblock(fptr, io);
|
370
416
|
watcher.fiber = Qnil;
|
371
417
|
|
372
|
-
|
418
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
419
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
420
|
+
// find out if that's the case.
|
421
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
422
|
+
if (fptr->rbuf.len > 0) {
|
423
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
424
|
+
fptr->rbuf.len = 0;
|
425
|
+
}
|
373
426
|
|
374
427
|
while (1) {
|
375
428
|
ssize_t n = read(fptr->fd, buf, len);
|
@@ -410,12 +463,12 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
410
463
|
struct libev_io watcher;
|
411
464
|
rb_io_t *fptr;
|
412
465
|
VALUE switchpoint_result = Qnil;
|
413
|
-
|
466
|
+
VALUE underlying_io;
|
414
467
|
char *buf = StringValuePtr(str);
|
415
468
|
long len = RSTRING_LEN(str);
|
416
469
|
long left = len;
|
417
470
|
|
418
|
-
|
471
|
+
underlying_io = rb_iv_get(io, "@io");
|
419
472
|
if (underlying_io != Qnil) io = underlying_io;
|
420
473
|
GetLibevAgent(self, agent);
|
421
474
|
io = rb_io_get_write_io(io);
|
@@ -427,19 +480,20 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
427
480
|
if (n < 0) {
|
428
481
|
int e = errno;
|
429
482
|
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
430
|
-
|
431
483
|
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
432
484
|
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
433
485
|
}
|
434
486
|
else {
|
435
|
-
switchpoint_result = libev_snooze();
|
436
|
-
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
437
|
-
|
438
487
|
buf += n;
|
439
488
|
left -= n;
|
440
489
|
}
|
441
490
|
}
|
442
491
|
|
492
|
+
if (watcher.fiber == Qnil) {
|
493
|
+
switchpoint_result = libev_snooze();
|
494
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
495
|
+
}
|
496
|
+
|
443
497
|
RB_GC_GUARD(watcher.fiber);
|
444
498
|
RB_GC_GUARD(switchpoint_result);
|
445
499
|
|
@@ -448,6 +502,86 @@ error:
|
|
448
502
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
449
503
|
}
|
450
504
|
|
505
|
+
VALUE LibevAgent_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
|
506
|
+
struct LibevAgent_t *agent;
|
507
|
+
struct libev_io watcher;
|
508
|
+
rb_io_t *fptr;
|
509
|
+
VALUE switchpoint_result = Qnil;
|
510
|
+
VALUE underlying_io;
|
511
|
+
long total_length = 0;
|
512
|
+
long total_written = 0;
|
513
|
+
struct iovec *iov = 0;
|
514
|
+
struct iovec *iov_ptr = 0;
|
515
|
+
int iov_count = argc;
|
516
|
+
|
517
|
+
underlying_io = rb_iv_get(io, "@io");
|
518
|
+
if (underlying_io != Qnil) io = underlying_io;
|
519
|
+
GetLibevAgent(self, agent);
|
520
|
+
io = rb_io_get_write_io(io);
|
521
|
+
GetOpenFile(io, fptr);
|
522
|
+
watcher.fiber = Qnil;
|
523
|
+
|
524
|
+
iov = malloc(iov_count * sizeof(struct iovec));
|
525
|
+
for (int i = 0; i < argc; i++) {
|
526
|
+
VALUE str = argv[i];
|
527
|
+
iov[i].iov_base = StringValuePtr(str);
|
528
|
+
iov[i].iov_len = RSTRING_LEN(str);
|
529
|
+
total_length += iov[i].iov_len;
|
530
|
+
}
|
531
|
+
iov_ptr = iov;
|
532
|
+
|
533
|
+
while (1) {
|
534
|
+
ssize_t n = writev(fptr->fd, iov_ptr, iov_count);
|
535
|
+
if (n < 0) {
|
536
|
+
int e = errno;
|
537
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
538
|
+
|
539
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
540
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
541
|
+
}
|
542
|
+
else {
|
543
|
+
total_written += n;
|
544
|
+
if (total_written == total_length) break;
|
545
|
+
|
546
|
+
while (n > 0) {
|
547
|
+
if ((size_t) n < iov_ptr[0].iov_len) {
|
548
|
+
iov_ptr[0].iov_base = (char *) iov_ptr[0].iov_base + n;
|
549
|
+
iov_ptr[0].iov_len -= n;
|
550
|
+
n = 0;
|
551
|
+
}
|
552
|
+
else {
|
553
|
+
n -= iov_ptr[0].iov_len;
|
554
|
+
iov_ptr += 1;
|
555
|
+
iov_count -= 1;
|
556
|
+
}
|
557
|
+
}
|
558
|
+
}
|
559
|
+
}
|
560
|
+
if (watcher.fiber == Qnil) {
|
561
|
+
switchpoint_result = libev_snooze();
|
562
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
563
|
+
}
|
564
|
+
|
565
|
+
RB_GC_GUARD(watcher.fiber);
|
566
|
+
RB_GC_GUARD(switchpoint_result);
|
567
|
+
|
568
|
+
free(iov);
|
569
|
+
return INT2NUM(total_written);
|
570
|
+
error:
|
571
|
+
free(iov);
|
572
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
573
|
+
}
|
574
|
+
|
575
|
+
VALUE LibevAgent_write_m(int argc, VALUE *argv, VALUE self) {
|
576
|
+
if (argc < 2)
|
577
|
+
// TODO: raise ArgumentError
|
578
|
+
rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
|
579
|
+
|
580
|
+
return (argc == 2) ?
|
581
|
+
LibevAgent_write(self, argv[0], argv[1]) :
|
582
|
+
LibevAgent_writev(self, argv[0], argc - 1, argv + 1);
|
583
|
+
}
|
584
|
+
|
451
585
|
///////////////////////////////////////////////////////////////////////////
|
452
586
|
|
453
587
|
VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
@@ -463,7 +597,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
463
597
|
|
464
598
|
GetLibevAgent(self, agent);
|
465
599
|
GetOpenFile(sock, fptr);
|
466
|
-
|
600
|
+
io_set_nonblock(fptr, sock);
|
467
601
|
watcher.fiber = Qnil;
|
468
602
|
while (1) {
|
469
603
|
fd = accept(fptr->fd, &addr, &len);
|
@@ -489,7 +623,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
489
623
|
fp->fd = fd;
|
490
624
|
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
491
625
|
rb_io_ascii8bit_binmode(socket);
|
492
|
-
|
626
|
+
io_set_nonblock(fp, socket);
|
493
627
|
rb_io_synchronized(fp);
|
494
628
|
|
495
629
|
// if (rsock_do_not_reverse_lookup) {
|
@@ -518,7 +652,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
|
518
652
|
|
519
653
|
GetLibevAgent(self, agent);
|
520
654
|
GetOpenFile(sock, fptr);
|
521
|
-
|
655
|
+
io_set_nonblock(fptr, sock);
|
522
656
|
watcher.fiber = Qnil;
|
523
657
|
|
524
658
|
while (1) {
|
@@ -544,7 +678,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
|
544
678
|
fp->fd = fd;
|
545
679
|
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
546
680
|
rb_io_ascii8bit_binmode(socket);
|
547
|
-
|
681
|
+
io_set_nonblock(fp, socket);
|
548
682
|
rb_io_synchronized(fp);
|
549
683
|
|
550
684
|
rb_yield(socket);
|
@@ -572,7 +706,7 @@ error:
|
|
572
706
|
|
573
707
|
// GetLibevAgent(self, agent);
|
574
708
|
// GetOpenFile(sock, fptr);
|
575
|
-
//
|
709
|
+
// io_set_nonblock(fptr, sock);
|
576
710
|
// watcher.fiber = Qnil;
|
577
711
|
|
578
712
|
// addr.sin_family = AF_INET;
|
@@ -647,6 +781,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
|
|
647
781
|
ev_timer_start(agent->ev_loop, &watcher.timer);
|
648
782
|
|
649
783
|
switchpoint_result = libev_await(agent);
|
784
|
+
|
650
785
|
ev_timer_stop(agent->ev_loop, &watcher.timer);
|
651
786
|
|
652
787
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -695,6 +830,25 @@ struct ev_loop *LibevAgent_ev_loop(VALUE self) {
|
|
695
830
|
return agent->ev_loop;
|
696
831
|
}
|
697
832
|
|
833
|
+
void LibevAgent_async_callback(EV_P_ ev_async *w, int revents) { }
|
834
|
+
|
835
|
+
VALUE LibevAgent_wait_event(VALUE self, VALUE raise) {
|
836
|
+
struct LibevAgent_t *agent;
|
837
|
+
struct ev_async async;
|
838
|
+
VALUE switchpoint_result = Qnil;
|
839
|
+
GetLibevAgent(self, agent);
|
840
|
+
|
841
|
+
ev_async_init(&async, LibevAgent_async_callback);
|
842
|
+
ev_async_start(agent->ev_loop, &async);
|
843
|
+
|
844
|
+
switchpoint_result = libev_await(agent);
|
845
|
+
ev_async_stop(agent->ev_loop, &async);
|
846
|
+
|
847
|
+
if (RTEST(raise)) TEST_RESUME_EXCEPTION(switchpoint_result);
|
848
|
+
RB_GC_GUARD(switchpoint_result);
|
849
|
+
return switchpoint_result;
|
850
|
+
}
|
851
|
+
|
698
852
|
void Init_LibevAgent() {
|
699
853
|
rb_require("socket");
|
700
854
|
cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
@@ -715,11 +869,14 @@ void Init_LibevAgent() {
|
|
715
869
|
|
716
870
|
rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
|
717
871
|
rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
|
718
|
-
rb_define_method(cLibevAgent, "write",
|
872
|
+
rb_define_method(cLibevAgent, "write", LibevAgent_write_m, -1);
|
719
873
|
rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
|
720
874
|
rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
|
721
875
|
// rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
|
722
876
|
rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
|
723
877
|
rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
|
724
878
|
rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
|
879
|
+
rb_define_method(cLibevAgent, "wait_event", LibevAgent_wait_event, 1);
|
880
|
+
|
881
|
+
ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
|
725
882
|
}
|