polyphony 0.43.4 → 0.43.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +45 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +21 -4
  6. data/TODO.md +1 -6
  7. data/bin/stress.rb +28 -0
  8. data/docs/_includes/head.html +40 -0
  9. data/docs/_includes/title.html +1 -0
  10. data/docs/_user-guide/web-server.md +11 -11
  11. data/docs/getting-started/overview.md +2 -2
  12. data/docs/index.md +4 -3
  13. data/docs/main-concepts/design-principles.md +23 -34
  14. data/docs/main-concepts/fiber-scheduling.md +1 -1
  15. data/docs/polyphony-logo.png +0 -0
  16. data/examples/core/xx-channels.rb +4 -2
  17. data/examples/core/xx-using-a-mutex.rb +2 -1
  18. data/examples/io/xx-happy-eyeballs.rb +21 -22
  19. data/examples/io/xx-zip.rb +19 -0
  20. data/examples/performance/fiber_transfer.rb +47 -0
  21. data/examples/performance/messaging.rb +29 -0
  22. data/examples/performance/multi_snooze.rb +11 -9
  23. data/examples/xx-spin.rb +32 -0
  24. data/ext/polyphony/agent.h +39 -0
  25. data/ext/polyphony/event.c +86 -0
  26. data/ext/polyphony/fiber.c +0 -5
  27. data/ext/polyphony/libev_agent.c +231 -79
  28. data/ext/polyphony/polyphony.c +2 -2
  29. data/ext/polyphony/polyphony.h +19 -16
  30. data/ext/polyphony/polyphony_ext.c +4 -2
  31. data/ext/polyphony/queue.c +194 -0
  32. data/ext/polyphony/ring_buffer.c +96 -0
  33. data/ext/polyphony/ring_buffer.h +28 -0
  34. data/ext/polyphony/thread.c +48 -31
  35. data/lib/polyphony.rb +5 -6
  36. data/lib/polyphony/core/channel.rb +3 -34
  37. data/lib/polyphony/core/resource_pool.rb +13 -75
  38. data/lib/polyphony/core/sync.rb +12 -9
  39. data/lib/polyphony/core/thread_pool.rb +1 -1
  40. data/lib/polyphony/extensions/core.rb +9 -0
  41. data/lib/polyphony/extensions/fiber.rb +9 -2
  42. data/lib/polyphony/extensions/io.rb +16 -15
  43. data/lib/polyphony/extensions/openssl.rb +8 -0
  44. data/lib/polyphony/extensions/socket.rb +13 -9
  45. data/lib/polyphony/extensions/thread.rb +1 -1
  46. data/lib/polyphony/version.rb +1 -1
  47. data/test/helper.rb +2 -2
  48. data/test/q.rb +24 -0
  49. data/test/test_agent.rb +2 -2
  50. data/test/test_event.rb +12 -0
  51. data/test/test_global_api.rb +2 -2
  52. data/test/test_io.rb +24 -2
  53. data/test/test_queue.rb +59 -1
  54. data/test/test_resource_pool.rb +0 -43
  55. data/test/test_trace.rb +18 -17
  56. metadata +16 -5
  57. data/ext/polyphony/libev_queue.c +0 -217
  58. data/lib/polyphony/event.rb +0 -27
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
+ require 'polyphony/core/sync'
5
6
 
6
7
  def loop_it(number, lock)
7
8
  loop do
@@ -13,7 +14,7 @@ def loop_it(number, lock)
13
14
  end
14
15
  end
15
16
 
16
- lock = Polyphony::Sync::Mutex.new
17
+ lock = Polyphony::Mutex.new
17
18
  spin { loop_it(1, lock) }
18
19
  spin { loop_it(2, lock) }
19
20
  spin { loop_it(3, lock) }
@@ -1,37 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # idea taken from the example given in trio:
4
- # https://www.youtube.com/watch?v=oLkfnc_UMcE
5
-
6
- require 'bundler/setup'
7
3
  require 'polyphony'
8
4
 
9
- def try_connect(target, supervisor)
10
- puts "trying #{target[2]}"
11
- socket = Polyphony::Net.tcp_connect(target[2], 80)
12
- # connection successful
13
- supervisor.stop([target[2], socket])
5
+ def try_connect(ip_address, port, supervisor)
6
+ puts "trying #{ip_address}"
7
+ sleep rand * 0.2
8
+ socket = TCPSocket.new(ip_address, port)
9
+ puts "connected to #{ip_address}"
10
+ supervisor.schedule [ip_address, socket]
14
11
  rescue IOError, SystemCallError
15
12
  # ignore error
16
13
  end
17
14
 
18
- def happy_eyeballs(hostname, port, max_wait_time: 0.025)
15
+ def happy_eyeballs(hostname, port, max_wait_time: 0.010)
19
16
  targets = Socket.getaddrinfo(hostname, port, :INET, :STREAM)
20
17
  t0 = Time.now
21
- cancel_after(5) do
22
- success = supervise do |supervisor|
23
- targets.each_with_index do |t, idx|
24
- sleep(max_wait_time) if idx > 0
25
- supervisor.spin { try_connect(t, supervisor) }
26
- end
27
- end
28
- if success
29
- puts format('success: %s (%.3fs)', success[0], Time.now - t0)
30
- else
31
- puts "timed out (#{Time.now - t0}s)"
18
+ fibers = []
19
+ supervisor = Fiber.current
20
+ spin do
21
+ targets.each do |t|
22
+ spin { try_connect(t[2], t[1], supervisor) }
23
+ sleep(max_wait_time)
32
24
  end
25
+ suspend
26
+ end
27
+ target, socket = move_on_after(5) { suspend }
28
+ supervisor.shutdown_all_children
29
+ if target
30
+ puts format('success: %s (%.3fs)', target, Time.now - t0)
31
+ else
32
+ puts 'timed out'
33
33
  end
34
34
  end
35
35
 
36
- # Let's try it out:
37
36
  happy_eyeballs('debian.org', 'https')
@@ -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)
@@ -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
- t0 = Time.now
9
- supervise do |s|
10
- fibers.times do
11
- s.spin do
12
- iterations.times do
13
- snooze
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)
@@ -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")
@@ -0,0 +1,39 @@
1
+ #ifndef AGENT_H
2
+ #define AGENT_H
3
+
4
+ #include "ruby.h"
5
+
6
+ // agent interface function signatures
7
+
8
+ // VALUE LibevAgent_accept(VALUE self, VALUE sock);
9
+ // VALUE LibevAgent_accept_loop(VALUE self, VALUE sock);
10
+ // VALUE libev_agent_await(VALUE self);
11
+ // VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port);
12
+ // VALUE LibevAgent_finalize(VALUE self);
13
+ // VALUE LibevAgent_post_fork(VALUE self);
14
+ // VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof);
15
+ // VALUE LibevAgent_read_loop(VALUE self, VALUE io);
16
+ // VALUE LibevAgent_ref(VALUE self);
17
+ // VALUE LibevAgent_sleep(VALUE self, VALUE duration);
18
+ // VALUE LibevAgent_unref(VALUE self);
19
+ // VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write);
20
+ // VALUE LibevAgent_wait_pid(VALUE self, VALUE pid);
21
+ // VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self);
22
+
23
+ typedef VALUE (* agent_pending_count_t)(VALUE self);
24
+ typedef VALUE (*agent_poll_t)(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue);
25
+ typedef int (* agent_ref_count_t)(VALUE self);
26
+ typedef void (* agent_reset_ref_count_t)(VALUE self);
27
+ typedef VALUE (* agent_wait_event_t)(VALUE self, VALUE raise_on_exception);
28
+ typedef VALUE (* agent_wakeup_t)(VALUE self);
29
+
30
+ typedef struct agent_interface {
31
+ agent_pending_count_t pending_count;
32
+ agent_poll_t poll;
33
+ agent_ref_count_t ref_count;
34
+ agent_reset_ref_count_t reset_ref_count;
35
+ agent_wait_event_t wait_event;
36
+ agent_wakeup_t wakeup;
37
+ } agent_interface_t;
38
+
39
+ #endif /* AGENT_H */
@@ -0,0 +1,86 @@
1
+ #include "polyphony.h"
2
+ #include "ring_buffer.h"
3
+
4
+ typedef struct event {
5
+ VALUE waiting_fiber;
6
+ } Event_t;
7
+
8
+ VALUE cEvent = Qnil;
9
+
10
+ static void Event_mark(void *ptr) {
11
+ Event_t *event = ptr;
12
+ rb_gc_mark(event->waiting_fiber);
13
+ }
14
+
15
+ static void Event_free(void *ptr) {
16
+ xfree(ptr);
17
+ }
18
+
19
+ static size_t Event_size(const void *ptr) {
20
+ return sizeof(Event_t);
21
+ }
22
+
23
+ static const rb_data_type_t Event_type = {
24
+ "Event",
25
+ {Event_mark, Event_free, Event_size,},
26
+ 0, 0, 0
27
+ };
28
+
29
+ static VALUE Event_allocate(VALUE klass) {
30
+ Event_t *event;
31
+
32
+ event = ALLOC(Event_t);
33
+ return TypedData_Wrap_Struct(klass, &Event_type, event);
34
+ }
35
+
36
+ #define GetEvent(obj, event) \
37
+ TypedData_Get_Struct((obj), Event_t, &Event_type, (event))
38
+
39
+ static VALUE Event_initialize(VALUE self) {
40
+ Event_t *event;
41
+ GetEvent(self, event);
42
+
43
+ event->waiting_fiber = Qnil;
44
+
45
+ return self;
46
+ }
47
+
48
+ VALUE Event_signal(int argc, VALUE *argv, VALUE self) {
49
+ VALUE value = argc > 0 ? argv[0] : Qnil;
50
+ Event_t *event;
51
+ GetEvent(self, event);
52
+
53
+ if (event->waiting_fiber != Qnil) {
54
+ Fiber_make_runnable(event->waiting_fiber, value);
55
+ event->waiting_fiber = Qnil;
56
+ }
57
+ return self;
58
+ }
59
+
60
+ VALUE Event_await(VALUE self) {
61
+ Event_t *event;
62
+ GetEvent(self, event);
63
+
64
+ if (event->waiting_fiber != Qnil)
65
+ rb_raise(rb_eRuntimeError, "Event is already awaited by another fiber");
66
+
67
+ VALUE agent = rb_ivar_get(rb_thread_current(), ID_ivar_agent);
68
+ event->waiting_fiber = rb_fiber_current();
69
+ VALUE switchpoint_result = __AGENT__.wait_event(agent, Qnil);
70
+ event->waiting_fiber = Qnil;
71
+
72
+ TEST_RESUME_EXCEPTION(switchpoint_result);
73
+ RB_GC_GUARD(agent);
74
+ RB_GC_GUARD(switchpoint_result);
75
+
76
+ return switchpoint_result;
77
+ }
78
+
79
+ void Init_Event() {
80
+ cEvent = rb_define_class_under(mPolyphony, "Event", rb_cData);
81
+ rb_define_alloc_func(cEvent, Event_allocate);
82
+
83
+ rb_define_method(cEvent, "initialize", Event_initialize, 0);
84
+ rb_define_method(cEvent, "await", Event_await, 0);
85
+ rb_define_method(cEvent, "signal", Event_signal, -1);
86
+ }
@@ -9,8 +9,6 @@ ID ID_trace_runnable;
9
9
  ID ID_trace_terminate;
10
10
  ID ID_trace_wait;
11
11
 
12
- VALUE cEvent = Qnil;
13
-
14
12
  VALUE SYM_dead;
15
13
  VALUE SYM_running;
16
14
  VALUE SYM_runnable;
@@ -35,9 +33,6 @@ static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
35
33
 
36
34
  inline VALUE Fiber_auto_watcher(VALUE self) {
37
35
  VALUE watcher;
38
- if (cEvent == Qnil) {
39
- cEvent = rb_const_get(mPolyphony, rb_intern("Event"));
40
- }
41
36
 
42
37
  watcher = rb_ivar_get(self, ID_ivar_auto_watcher);
43
38
  if (watcher == Qnil) {
@@ -1,10 +1,14 @@
1
1
  #include <netdb.h>
2
2
  #include <sys/socket.h>
3
+ #include <sys/uio.h>
4
+ #include <unistd.h>
5
+ #include <fcntl.h>
6
+ #include <netinet/in.h>
7
+ #include <arpa/inet.h>
3
8
 
4
9
  #include "polyphony.h"
5
10
  #include "../libev/ev.h"
6
11
 
7
- VALUE cLibevAgent = Qnil;
8
12
  VALUE cTCPSocket;
9
13
 
10
14
  struct LibevAgent_t {
@@ -130,7 +134,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
130
134
  GetLibevAgent(self, agent);
131
135
 
132
136
  if (is_nowait) {
133
- long runnable_count = RARRAY_LEN(queue);
137
+ long runnable_count = Queue_len(queue);
134
138
  agent->run_no_wait_count++;
135
139
  if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
136
140
  return self;
@@ -147,7 +151,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
147
151
  return self;
148
152
  }
149
153
 
150
- VALUE LibevAgent_break(VALUE self) {
154
+ VALUE LibevAgent_wakeup(VALUE self) {
151
155
  struct LibevAgent_t *agent;
152
156
  GetLibevAgent(self, agent);
153
157
 
@@ -277,6 +281,31 @@ VALUE libev_snooze() {
277
281
  return Thread_switch_fiber(rb_thread_current());
278
282
  }
279
283
 
284
+ ID ID_ivar_is_nonblocking;
285
+
286
+ // Since we need to ensure that fd's are non-blocking before every I/O
287
+ // operation, here we improve upon Ruby's rb_io_set_nonblock by caching the
288
+ // "nonblock" state in an instance variable. Calling rb_ivar_get on every read
289
+ // is still much cheaper than doing a fcntl syscall on every read! Preliminary
290
+ // benchmarks (with a "hello world" HTTP server) show throughput is improved
291
+ // by 10-13%.
292
+ inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
293
+ #ifdef _WIN32
294
+ return rb_w32_set_nonblock(fptr->fd);
295
+ #elif defined(F_GETFL)
296
+ VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
297
+ if (is_nonblocking == Qnil) {
298
+ rb_ivar_set(io, ID_ivar_is_nonblocking, Qtrue);
299
+ int oflags = fcntl(fptr->fd, F_GETFL);
300
+ if (oflags == -1) return;
301
+ if (oflags & O_NONBLOCK) return;
302
+ oflags |= O_NONBLOCK;
303
+ fcntl(fptr->fd, F_SETFL, oflags);
304
+ }
305
+ #endif
306
+ return;
307
+ }
308
+
280
309
  VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
281
310
  struct LibevAgent_t *agent;
282
311
  struct libev_io watcher;
@@ -294,11 +323,20 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
294
323
  if (underlying_io != Qnil) io = underlying_io;
295
324
  GetOpenFile(io, fptr);
296
325
  rb_io_check_byte_readable(fptr);
297
- rb_io_set_nonblock(fptr);
326
+ io_set_nonblock(fptr, io);
298
327
  watcher.fiber = Qnil;
299
328
 
300
329
  OBJ_TAINT(str);
301
330
 
331
+ // Apparently after reopening a closed file, the file position is not reset,
332
+ // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
333
+ // find out if that's the case.
334
+ // See: https://github.com/digital-fabric/polyphony/issues/30
335
+ if (fptr->rbuf.len > 0) {
336
+ lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
337
+ fptr->rbuf.len = 0;
338
+ }
339
+
302
340
  while (1) {
303
341
  ssize_t n = read(fptr->fd, buf, len - total);
304
342
  if (n < 0) {
@@ -348,6 +386,7 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
348
386
  shrinkable = io_setstrbuf(&str, len); \
349
387
  buf = RSTRING_PTR(str); \
350
388
  total = 0; \
389
+ OBJ_TAINT(str); \
351
390
  }
352
391
 
353
392
  #define YIELD_STR() { \
@@ -374,10 +413,17 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
374
413
  if (underlying_io != Qnil) io = underlying_io;
375
414
  GetOpenFile(io, fptr);
376
415
  rb_io_check_byte_readable(fptr);
377
- rb_io_set_nonblock(fptr);
416
+ io_set_nonblock(fptr, io);
378
417
  watcher.fiber = Qnil;
379
418
 
380
- OBJ_TAINT(str);
419
+ // Apparently after reopening a closed file, the file position is not reset,
420
+ // which causes the read to fail. Fortunately we can use fptr->rbuf.len to
421
+ // find out if that's the case.
422
+ // See: https://github.com/digital-fabric/polyphony/issues/30
423
+ if (fptr->rbuf.len > 0) {
424
+ lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
425
+ fptr->rbuf.len = 0;
426
+ }
381
427
 
382
428
  while (1) {
383
429
  ssize_t n = read(fptr->fd, buf, len);
@@ -418,12 +464,12 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
418
464
  struct libev_io watcher;
419
465
  rb_io_t *fptr;
420
466
  VALUE switchpoint_result = Qnil;
421
-
467
+ VALUE underlying_io;
422
468
  char *buf = StringValuePtr(str);
423
469
  long len = RSTRING_LEN(str);
424
470
  long left = len;
425
471
 
426
- VALUE underlying_io = rb_iv_get(io, "@io");
472
+ underlying_io = rb_iv_get(io, "@io");
427
473
  if (underlying_io != Qnil) io = underlying_io;
428
474
  GetLibevAgent(self, agent);
429
475
  io = rb_io_get_write_io(io);
@@ -435,19 +481,20 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
435
481
  if (n < 0) {
436
482
  int e = errno;
437
483
  if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
438
-
439
484
  switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
440
485
  if (TEST_EXCEPTION(switchpoint_result)) goto error;
441
486
  }
442
487
  else {
443
- switchpoint_result = libev_snooze();
444
- if (TEST_EXCEPTION(switchpoint_result)) goto error;
445
-
446
488
  buf += n;
447
489
  left -= n;
448
490
  }
449
491
  }
450
492
 
493
+ if (watcher.fiber == Qnil) {
494
+ switchpoint_result = libev_snooze();
495
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
496
+ }
497
+
451
498
  RB_GC_GUARD(watcher.fiber);
452
499
  RB_GC_GUARD(switchpoint_result);
453
500
 
@@ -456,6 +503,86 @@ error:
456
503
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
457
504
  }
458
505
 
506
+ VALUE LibevAgent_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
507
+ struct LibevAgent_t *agent;
508
+ struct libev_io watcher;
509
+ rb_io_t *fptr;
510
+ VALUE switchpoint_result = Qnil;
511
+ VALUE underlying_io;
512
+ long total_length = 0;
513
+ long total_written = 0;
514
+ struct iovec *iov = 0;
515
+ struct iovec *iov_ptr = 0;
516
+ int iov_count = argc;
517
+
518
+ underlying_io = rb_iv_get(io, "@io");
519
+ if (underlying_io != Qnil) io = underlying_io;
520
+ GetLibevAgent(self, agent);
521
+ io = rb_io_get_write_io(io);
522
+ GetOpenFile(io, fptr);
523
+ watcher.fiber = Qnil;
524
+
525
+ iov = malloc(iov_count * sizeof(struct iovec));
526
+ for (int i = 0; i < argc; i++) {
527
+ VALUE str = argv[i];
528
+ iov[i].iov_base = StringValuePtr(str);
529
+ iov[i].iov_len = RSTRING_LEN(str);
530
+ total_length += iov[i].iov_len;
531
+ }
532
+ iov_ptr = iov;
533
+
534
+ while (1) {
535
+ ssize_t n = writev(fptr->fd, iov_ptr, iov_count);
536
+ if (n < 0) {
537
+ int e = errno;
538
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
539
+
540
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
541
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
542
+ }
543
+ else {
544
+ total_written += n;
545
+ if (total_written == total_length) break;
546
+
547
+ while (n > 0) {
548
+ if ((size_t) n < iov_ptr[0].iov_len) {
549
+ iov_ptr[0].iov_base = (char *) iov_ptr[0].iov_base + n;
550
+ iov_ptr[0].iov_len -= n;
551
+ n = 0;
552
+ }
553
+ else {
554
+ n -= iov_ptr[0].iov_len;
555
+ iov_ptr += 1;
556
+ iov_count -= 1;
557
+ }
558
+ }
559
+ }
560
+ }
561
+ if (watcher.fiber == Qnil) {
562
+ switchpoint_result = libev_snooze();
563
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
564
+ }
565
+
566
+ RB_GC_GUARD(watcher.fiber);
567
+ RB_GC_GUARD(switchpoint_result);
568
+
569
+ free(iov);
570
+ return INT2NUM(total_written);
571
+ error:
572
+ free(iov);
573
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
574
+ }
575
+
576
+ VALUE LibevAgent_write_m(int argc, VALUE *argv, VALUE self) {
577
+ if (argc < 2)
578
+ // TODO: raise ArgumentError
579
+ rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
580
+
581
+ return (argc == 2) ?
582
+ LibevAgent_write(self, argv[0], argv[1]) :
583
+ LibevAgent_writev(self, argv[0], argc - 1, argv + 1);
584
+ }
585
+
459
586
  ///////////////////////////////////////////////////////////////////////////
460
587
 
461
588
  VALUE LibevAgent_accept(VALUE self, VALUE sock) {
@@ -471,7 +598,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
471
598
 
472
599
  GetLibevAgent(self, agent);
473
600
  GetOpenFile(sock, fptr);
474
- rb_io_set_nonblock(fptr);
601
+ io_set_nonblock(fptr, sock);
475
602
  watcher.fiber = Qnil;
476
603
  while (1) {
477
604
  fd = accept(fptr->fd, &addr, &len);
@@ -497,7 +624,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
497
624
  fp->fd = fd;
498
625
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
499
626
  rb_io_ascii8bit_binmode(socket);
500
- rb_io_set_nonblock(fp);
627
+ io_set_nonblock(fp, socket);
501
628
  rb_io_synchronized(fp);
502
629
 
503
630
  // if (rsock_do_not_reverse_lookup) {
@@ -526,7 +653,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
526
653
 
527
654
  GetLibevAgent(self, agent);
528
655
  GetOpenFile(sock, fptr);
529
- rb_io_set_nonblock(fptr);
656
+ io_set_nonblock(fptr, sock);
530
657
  watcher.fiber = Qnil;
531
658
 
532
659
  while (1) {
@@ -552,7 +679,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
552
679
  fp->fd = fd;
553
680
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
554
681
  rb_io_ascii8bit_binmode(socket);
555
- rb_io_set_nonblock(fp);
682
+ io_set_nonblock(fp, socket);
556
683
  rb_io_synchronized(fp);
557
684
 
558
685
  rb_yield(socket);
@@ -568,46 +695,41 @@ error:
568
695
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
569
696
  }
570
697
 
571
- // VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
572
- // struct LibevAgent_t *agent;
573
- // struct libev_io watcher;
574
- // rb_io_t *fptr;
575
- // struct sockaddr_in addr;
576
- // char *host_buf = StringValueCStr(host);
577
- // VALUE switchpoint_result = Qnil;
578
- // VALUE underlying_sock = rb_iv_get(sock, "@io");
579
- // if (underlying_sock != Qnil) sock = underlying_sock;
580
-
581
- // GetLibevAgent(self, agent);
582
- // GetOpenFile(sock, fptr);
583
- // rb_io_set_nonblock(fptr);
584
- // watcher.fiber = Qnil;
585
-
586
- // addr.sin_family = AF_INET;
587
- // addr.sin_addr.s_addr = inet_addr(host_buf);
588
- // addr.sin_port = htons(NUM2INT(port));
589
-
590
- // while (1) {
591
- // int result = connect(fptr->fd, &addr, sizeof(addr));
592
- // if (result < 0) {
593
- // int e = errno;
594
- // if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
595
-
596
- // switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
597
- // if (TEST_EXCEPTION(switchpoint_result)) goto error;
598
- // }
599
- // else {
600
- // switchpoint_result = libev_snooze();
601
- // if (TEST_EXCEPTION(switchpoint_result)) goto error;
602
-
603
- // return sock;
604
- // }
605
- // }
606
- // RB_GC_GUARD(switchpoint_result);
607
- // return Qnil;
608
- // error:
609
- // return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
610
- // }
698
+ VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
699
+ struct LibevAgent_t *agent;
700
+ struct libev_io watcher;
701
+ rb_io_t *fptr;
702
+ struct sockaddr_in addr;
703
+ char *host_buf = StringValueCStr(host);
704
+ VALUE switchpoint_result = Qnil;
705
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
706
+ if (underlying_sock != Qnil) sock = underlying_sock;
707
+
708
+ GetLibevAgent(self, agent);
709
+ GetOpenFile(sock, fptr);
710
+ io_set_nonblock(fptr, sock);
711
+ watcher.fiber = Qnil;
712
+
713
+ addr.sin_family = AF_INET;
714
+ addr.sin_addr.s_addr = inet_addr(host_buf);
715
+ addr.sin_port = htons(NUM2INT(port));
716
+
717
+ int result = connect(fptr->fd, (struct sockaddr *)&addr, sizeof(addr));
718
+ if (result < 0) {
719
+ int e = errno;
720
+ if (e != EINPROGRESS) rb_syserr_fail(e, strerror(e));
721
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
722
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
723
+ }
724
+ else {
725
+ switchpoint_result = libev_snooze();
726
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
727
+ }
728
+ RB_GC_GUARD(switchpoint_result);
729
+ return sock;
730
+ error:
731
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
732
+ }
611
733
 
612
734
  VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
613
735
  struct LibevAgent_t *agent;
@@ -655,6 +777,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
655
777
  ev_timer_start(agent->ev_loop, &watcher.timer);
656
778
 
657
779
  switchpoint_result = libev_await(agent);
780
+
658
781
  ev_timer_stop(agent->ev_loop, &watcher.timer);
659
782
 
660
783
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -703,31 +826,60 @@ struct ev_loop *LibevAgent_ev_loop(VALUE self) {
703
826
  return agent->ev_loop;
704
827
  }
705
828
 
829
+ void LibevAgent_async_callback(EV_P_ ev_async *w, int revents) { }
830
+
831
+ VALUE LibevAgent_wait_event(VALUE self, VALUE raise) {
832
+ struct LibevAgent_t *agent;
833
+ struct ev_async async;
834
+ VALUE switchpoint_result = Qnil;
835
+ GetLibevAgent(self, agent);
836
+
837
+ ev_async_init(&async, LibevAgent_async_callback);
838
+ ev_async_start(agent->ev_loop, &async);
839
+
840
+ switchpoint_result = libev_await(agent);
841
+ ev_async_stop(agent->ev_loop, &async);
842
+
843
+ if (RTEST(raise)) TEST_RESUME_EXCEPTION(switchpoint_result);
844
+ RB_GC_GUARD(switchpoint_result);
845
+ return switchpoint_result;
846
+ }
847
+
706
848
  void Init_LibevAgent() {
707
849
  rb_require("socket");
708
850
  cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
709
851
 
710
- cLibevAgent = rb_define_class_under(mPolyphony, "LibevAgent", rb_cData);
711
- rb_define_alloc_func(cLibevAgent, LibevAgent_allocate);
712
-
713
- rb_define_method(cLibevAgent, "initialize", LibevAgent_initialize, 0);
714
- rb_define_method(cLibevAgent, "finalize", LibevAgent_finalize, 0);
715
- rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
716
- rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
717
-
718
- rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
719
- rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
720
-
721
- rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
722
- rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
723
-
724
- rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
725
- rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
726
- rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
727
- rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
728
- rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
729
- // rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
730
- rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
731
- rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
732
- rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
852
+ VALUE cAgent = rb_define_class_under(mPolyphony, "Agent", rb_cData);
853
+ rb_define_alloc_func(cAgent, LibevAgent_allocate);
854
+
855
+ rb_define_method(cAgent, "initialize", LibevAgent_initialize, 0);
856
+ rb_define_method(cAgent, "finalize", LibevAgent_finalize, 0);
857
+ rb_define_method(cAgent, "post_fork", LibevAgent_post_fork, 0);
858
+ rb_define_method(cAgent, "pending_count", LibevAgent_pending_count, 0);
859
+
860
+ rb_define_method(cAgent, "ref", LibevAgent_ref, 0);
861
+ rb_define_method(cAgent, "unref", LibevAgent_unref, 0);
862
+
863
+ rb_define_method(cAgent, "poll", LibevAgent_poll, 3);
864
+ rb_define_method(cAgent, "break", LibevAgent_wakeup, 0);
865
+
866
+ rb_define_method(cAgent, "read", LibevAgent_read, 4);
867
+ rb_define_method(cAgent, "read_loop", LibevAgent_read_loop, 1);
868
+ rb_define_method(cAgent, "write", LibevAgent_write_m, -1);
869
+ rb_define_method(cAgent, "accept", LibevAgent_accept, 1);
870
+ rb_define_method(cAgent, "accept_loop", LibevAgent_accept_loop, 1);
871
+ rb_define_method(cAgent, "connect", LibevAgent_connect, 3);
872
+ rb_define_method(cAgent, "wait_io", LibevAgent_wait_io, 2);
873
+ rb_define_method(cAgent, "sleep", LibevAgent_sleep, 1);
874
+ rb_define_method(cAgent, "waitpid", LibevAgent_waitpid, 1);
875
+ rb_define_method(cAgent, "wait_event", LibevAgent_wait_event, 1);
876
+
877
+ ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
878
+
879
+ __AGENT__.wakeup = LibevAgent_wakeup;
880
+ __AGENT__.pending_count = LibevAgent_pending_count;
881
+ __AGENT__.poll = LibevAgent_poll;
882
+ __AGENT__.ref_count = LibevAgent_ref_count;
883
+ __AGENT__.reset_ref_count = LibevAgent_reset_ref_count;
884
+ __AGENT__.wait_event = LibevAgent_wait_event;
733
885
  }