polyphony 0.43.4 → 0.43.10

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.
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
  }