polyphony 0.43.5 → 0.43.11

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +21 -4
  5. data/TODO.md +0 -7
  6. data/bin/stress.rb +28 -0
  7. data/docs/_includes/head.html +40 -0
  8. data/docs/_includes/title.html +1 -0
  9. data/docs/_user-guide/web-server.md +11 -11
  10. data/docs/getting-started/overview.md +2 -2
  11. data/docs/index.md +3 -1
  12. data/docs/polyphony-logo.png +0 -0
  13. data/examples/core/xx-channels.rb +4 -2
  14. data/examples/core/xx-using-a-mutex.rb +2 -1
  15. data/examples/io/xx-happy-eyeballs.rb +21 -22
  16. data/examples/io/xx-zip.rb +19 -0
  17. data/examples/performance/fiber_transfer.rb +47 -0
  18. data/examples/xx-spin.rb +32 -0
  19. data/ext/polyphony/agent.h +41 -0
  20. data/ext/polyphony/event.c +86 -0
  21. data/ext/polyphony/fiber.c +0 -5
  22. data/ext/polyphony/libev_agent.c +277 -135
  23. data/ext/polyphony/polyphony.c +2 -2
  24. data/ext/polyphony/polyphony.h +14 -21
  25. data/ext/polyphony/polyphony_ext.c +4 -2
  26. data/ext/polyphony/queue.c +208 -0
  27. data/ext/polyphony/ring_buffer.c +0 -24
  28. data/ext/polyphony/thread.c +42 -31
  29. data/lib/polyphony.rb +6 -7
  30. data/lib/polyphony/core/channel.rb +3 -34
  31. data/lib/polyphony/core/resource_pool.rb +13 -75
  32. data/lib/polyphony/core/sync.rb +12 -9
  33. data/lib/polyphony/extensions/fiber.rb +8 -8
  34. data/lib/polyphony/extensions/openssl.rb +8 -0
  35. data/lib/polyphony/extensions/socket.rb +11 -9
  36. data/lib/polyphony/extensions/thread.rb +1 -1
  37. data/lib/polyphony/net.rb +2 -1
  38. data/lib/polyphony/version.rb +1 -1
  39. data/test/helper.rb +2 -2
  40. data/test/test_agent.rb +2 -2
  41. data/test/test_event.rb +12 -0
  42. data/test/test_fiber.rb +1 -1
  43. data/test/test_io.rb +14 -0
  44. data/test/test_queue.rb +33 -0
  45. data/test/test_resource_pool.rb +24 -58
  46. data/test/test_trace.rb +18 -17
  47. metadata +12 -5
  48. data/ext/polyphony/libev_queue.c +0 -288
  49. data/lib/polyphony/event.rb +0 -27
@@ -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,41 @@
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_sleep(VALUE self, VALUE duration);
17
+ // VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write);
18
+ // VALUE LibevAgent_wait_pid(VALUE self, VALUE pid);
19
+ // VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self);
20
+
21
+ typedef VALUE (* agent_pending_count_t)(VALUE self);
22
+ typedef VALUE (*agent_poll_t)(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue);
23
+ typedef VALUE (* agent_ref_t)(VALUE self);
24
+ typedef int (* agent_ref_count_t)(VALUE self);
25
+ typedef void (* agent_reset_ref_count_t)(VALUE self);
26
+ typedef VALUE (* agent_unref_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_t ref;
34
+ agent_ref_count_t ref_count;
35
+ agent_reset_ref_count_t reset_ref_count;
36
+ agent_unref_t unref;
37
+ agent_wait_event_t wait_event;
38
+ agent_wakeup_t wakeup;
39
+ } agent_interface_t;
40
+
41
+ #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,22 +1,26 @@
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
- struct LibevAgent_t {
14
+ typedef struct LibevAgent_t {
11
15
  struct ev_loop *ev_loop;
12
16
  struct ev_async break_async;
13
17
  int running;
14
18
  int ref_count;
15
19
  int run_no_wait_count;
16
- };
20
+ } LibevAgent_t;
17
21
 
18
22
  static size_t LibevAgent_size(const void *ptr) {
19
- return sizeof(struct LibevAgent_t);
23
+ return sizeof(LibevAgent_t);
20
24
  }
21
25
 
22
26
  static const rb_data_type_t LibevAgent_type = {
@@ -26,13 +30,13 @@ static const rb_data_type_t LibevAgent_type = {
26
30
  };
27
31
 
28
32
  static VALUE LibevAgent_allocate(VALUE klass) {
29
- struct LibevAgent_t *agent = ALLOC(struct LibevAgent_t);
33
+ LibevAgent_t *agent = ALLOC(LibevAgent_t);
30
34
 
31
35
  return TypedData_Wrap_Struct(klass, &LibevAgent_type, agent);
32
36
  }
33
37
 
34
38
  #define GetLibevAgent(obj, agent) \
35
- TypedData_Get_Struct((obj), struct LibevAgent_t, &LibevAgent_type, (agent))
39
+ TypedData_Get_Struct((obj), LibevAgent_t, &LibevAgent_type, (agent))
36
40
 
37
41
  void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
38
42
  // This callback does nothing, the break async is used solely for breaking out
@@ -40,7 +44,7 @@ void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, in
40
44
  }
41
45
 
42
46
  static VALUE LibevAgent_initialize(VALUE self) {
43
- struct LibevAgent_t *agent;
47
+ LibevAgent_t *agent;
44
48
  VALUE thread = rb_thread_current();
45
49
  int is_main_thread = (thread == rb_thread_main());
46
50
 
@@ -59,7 +63,7 @@ static VALUE LibevAgent_initialize(VALUE self) {
59
63
  }
60
64
 
61
65
  VALUE LibevAgent_finalize(VALUE self) {
62
- struct LibevAgent_t *agent;
66
+ LibevAgent_t *agent;
63
67
  GetLibevAgent(self, agent);
64
68
 
65
69
  ev_async_stop(agent->ev_loop, &agent->break_async);
@@ -70,7 +74,7 @@ VALUE LibevAgent_finalize(VALUE self) {
70
74
  }
71
75
 
72
76
  VALUE LibevAgent_post_fork(VALUE self) {
73
- struct LibevAgent_t *agent;
77
+ LibevAgent_t *agent;
74
78
  GetLibevAgent(self, agent);
75
79
 
76
80
  if (!ev_is_default_loop(agent->ev_loop)) {
@@ -87,7 +91,7 @@ VALUE LibevAgent_post_fork(VALUE self) {
87
91
  }
88
92
 
89
93
  VALUE LibevAgent_ref(VALUE self) {
90
- struct LibevAgent_t *agent;
94
+ LibevAgent_t *agent;
91
95
  GetLibevAgent(self, agent);
92
96
 
93
97
  agent->ref_count++;
@@ -95,7 +99,7 @@ VALUE LibevAgent_ref(VALUE self) {
95
99
  }
96
100
 
97
101
  VALUE LibevAgent_unref(VALUE self) {
98
- struct LibevAgent_t *agent;
102
+ LibevAgent_t *agent;
99
103
  GetLibevAgent(self, agent);
100
104
 
101
105
  agent->ref_count--;
@@ -103,14 +107,14 @@ VALUE LibevAgent_unref(VALUE self) {
103
107
  }
104
108
 
105
109
  int LibevAgent_ref_count(VALUE self) {
106
- struct LibevAgent_t *agent;
110
+ LibevAgent_t *agent;
107
111
  GetLibevAgent(self, agent);
108
112
 
109
113
  return agent->ref_count;
110
114
  }
111
115
 
112
116
  void LibevAgent_reset_ref_count(VALUE self) {
113
- struct LibevAgent_t *agent;
117
+ LibevAgent_t *agent;
114
118
  GetLibevAgent(self, agent);
115
119
 
116
120
  agent->ref_count = 0;
@@ -118,7 +122,7 @@ void LibevAgent_reset_ref_count(VALUE self) {
118
122
 
119
123
  VALUE LibevAgent_pending_count(VALUE self) {
120
124
  int count;
121
- struct LibevAgent_t *agent;
125
+ LibevAgent_t *agent;
122
126
  GetLibevAgent(self, agent);
123
127
  count = ev_pending_count(agent->ev_loop);
124
128
  return INT2NUM(count);
@@ -126,11 +130,11 @@ VALUE LibevAgent_pending_count(VALUE self) {
126
130
 
127
131
  VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue) {
128
132
  int is_nowait = nowait == Qtrue;
129
- struct LibevAgent_t *agent;
133
+ LibevAgent_t *agent;
130
134
  GetLibevAgent(self, agent);
131
135
 
132
136
  if (is_nowait) {
133
- long runnable_count = LibevQueue_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,8 +151,8 @@ 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) {
151
- struct LibevAgent_t *agent;
154
+ VALUE LibevAgent_wakeup(VALUE self) {
155
+ LibevAgent_t *agent;
152
156
  GetLibevAgent(self, agent);
153
157
 
154
158
  if (agent->running) {
@@ -242,7 +246,7 @@ void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
242
246
  Fiber_make_runnable(watcher->fiber, Qnil);
243
247
  }
244
248
 
245
- inline VALUE libev_await(struct LibevAgent_t *agent) {
249
+ inline VALUE libev_await(LibevAgent_t *agent) {
246
250
  VALUE ret;
247
251
  agent->ref_count++;
248
252
  ret = Thread_switch_fiber(rb_thread_current());
@@ -252,12 +256,12 @@ inline VALUE libev_await(struct LibevAgent_t *agent) {
252
256
  }
253
257
 
254
258
  VALUE libev_agent_await(VALUE self) {
255
- struct LibevAgent_t *agent;
259
+ LibevAgent_t *agent;
256
260
  GetLibevAgent(self, agent);
257
261
  return libev_await(agent);
258
262
  }
259
263
 
260
- VALUE libev_io_wait(struct LibevAgent_t *agent, struct libev_io *watcher, rb_io_t *fptr, int flags) {
264
+ VALUE libev_io_wait(LibevAgent_t *agent, struct libev_io *watcher, rb_io_t *fptr, int flags) {
261
265
  VALUE switchpoint_result;
262
266
 
263
267
  if (watcher->fiber == Qnil) {
@@ -277,8 +281,33 @@ 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
- struct LibevAgent_t *agent;
310
+ LibevAgent_t *agent;
282
311
  struct libev_io watcher;
283
312
  rb_io_t *fptr;
284
313
  long dynamic_len = length == Qnil;
@@ -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() { \
@@ -357,7 +396,7 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
357
396
  PREPARE_STR(); \
358
397
  }
359
398
 
360
- struct LibevAgent_t *agent;
399
+ LibevAgent_t *agent;
361
400
  struct libev_io watcher;
362
401
  rb_io_t *fptr;
363
402
  VALUE 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);
@@ -413,20 +459,62 @@ error:
413
459
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
414
460
  }
415
461
 
416
- VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self) {
417
- struct LibevAgent_t *agent;
462
+ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
463
+ LibevAgent_t *agent;
464
+ struct libev_io watcher;
465
+ rb_io_t *fptr;
466
+ VALUE switchpoint_result = Qnil;
467
+ VALUE underlying_io;
468
+ char *buf = StringValuePtr(str);
469
+ long len = RSTRING_LEN(str);
470
+ long left = len;
471
+
472
+ underlying_io = rb_iv_get(io, "@io");
473
+ if (underlying_io != Qnil) io = underlying_io;
474
+ GetLibevAgent(self, agent);
475
+ io = rb_io_get_write_io(io);
476
+ GetOpenFile(io, fptr);
477
+ watcher.fiber = Qnil;
478
+
479
+ while (left > 0) {
480
+ ssize_t n = write(fptr->fd, buf, left);
481
+ if (n < 0) {
482
+ int e = errno;
483
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
484
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
485
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
486
+ }
487
+ else {
488
+ buf += n;
489
+ left -= n;
490
+ }
491
+ }
492
+
493
+ if (watcher.fiber == Qnil) {
494
+ switchpoint_result = libev_snooze();
495
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
496
+ }
497
+
498
+ RB_GC_GUARD(watcher.fiber);
499
+ RB_GC_GUARD(switchpoint_result);
500
+
501
+ return INT2NUM(len);
502
+ error:
503
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
504
+ }
505
+
506
+ VALUE LibevAgent_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
507
+ LibevAgent_t *agent;
418
508
  struct libev_io watcher;
419
509
  rb_io_t *fptr;
420
510
  VALUE switchpoint_result = Qnil;
421
- VALUE io;
422
511
  VALUE underlying_io;
512
+ long total_length = 0;
423
513
  long total_written = 0;
424
- int arg_idx = 1;
514
+ struct iovec *iov = 0;
515
+ struct iovec *iov_ptr = 0;
516
+ int iov_count = argc;
425
517
 
426
- if (argc < 2)
427
- rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
428
-
429
- io = argv[0];
430
518
  underlying_io = rb_iv_get(io, "@io");
431
519
  if (underlying_io != Qnil) io = underlying_io;
432
520
  GetLibevAgent(self, agent);
@@ -434,30 +522,42 @@ VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self) {
434
522
  GetOpenFile(io, fptr);
435
523
  watcher.fiber = Qnil;
436
524
 
437
- while (arg_idx < argc) {
438
- VALUE str = argv[arg_idx];
439
- char *buf = StringValuePtr(str);
440
- long len = RSTRING_LEN(str);
441
- long left = len;
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;
442
533
 
443
- while (left > 0) {
444
- ssize_t n = write(fptr->fd, buf, left);
445
- if (n < 0) {
446
- int e = errno;
447
- if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
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));
448
539
 
449
- switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
450
- if (TEST_EXCEPTION(switchpoint_result)) goto error;
451
- }
452
- else {
453
- buf += n;
454
- left -= n;
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
+ }
455
558
  }
456
559
  }
457
- total_written += len;
458
- arg_idx++;
459
560
  }
460
-
461
561
  if (watcher.fiber == Qnil) {
462
562
  switchpoint_result = libev_snooze();
463
563
  if (TEST_EXCEPTION(switchpoint_result)) goto error;
@@ -466,15 +566,27 @@ VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self) {
466
566
  RB_GC_GUARD(watcher.fiber);
467
567
  RB_GC_GUARD(switchpoint_result);
468
568
 
569
+ free(iov);
469
570
  return INT2NUM(total_written);
470
571
  error:
572
+ free(iov);
471
573
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
472
574
  }
473
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
+
474
586
  ///////////////////////////////////////////////////////////////////////////
475
587
 
476
588
  VALUE LibevAgent_accept(VALUE self, VALUE sock) {
477
- struct LibevAgent_t *agent;
589
+ LibevAgent_t *agent;
478
590
  struct libev_io watcher;
479
591
  rb_io_t *fptr;
480
592
  int fd;
@@ -486,7 +598,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
486
598
 
487
599
  GetLibevAgent(self, agent);
488
600
  GetOpenFile(sock, fptr);
489
- rb_io_set_nonblock(fptr);
601
+ io_set_nonblock(fptr, sock);
490
602
  watcher.fiber = Qnil;
491
603
  while (1) {
492
604
  fd = accept(fptr->fd, &addr, &len);
@@ -512,7 +624,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
512
624
  fp->fd = fd;
513
625
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
514
626
  rb_io_ascii8bit_binmode(socket);
515
- rb_io_set_nonblock(fp);
627
+ io_set_nonblock(fp, socket);
516
628
  rb_io_synchronized(fp);
517
629
 
518
630
  // if (rsock_do_not_reverse_lookup) {
@@ -528,7 +640,7 @@ error:
528
640
  }
529
641
 
530
642
  VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
531
- struct LibevAgent_t *agent;
643
+ LibevAgent_t *agent;
532
644
  struct libev_io watcher;
533
645
  rb_io_t *fptr;
534
646
  int fd;
@@ -541,7 +653,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
541
653
 
542
654
  GetLibevAgent(self, agent);
543
655
  GetOpenFile(sock, fptr);
544
- rb_io_set_nonblock(fptr);
656
+ io_set_nonblock(fptr, sock);
545
657
  watcher.fiber = Qnil;
546
658
 
547
659
  while (1) {
@@ -567,7 +679,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
567
679
  fp->fd = fd;
568
680
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
569
681
  rb_io_ascii8bit_binmode(socket);
570
- rb_io_set_nonblock(fp);
682
+ io_set_nonblock(fp, socket);
571
683
  rb_io_synchronized(fp);
572
684
 
573
685
  rb_yield(socket);
@@ -583,71 +695,69 @@ error:
583
695
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
584
696
  }
585
697
 
586
- // VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
587
- // struct LibevAgent_t *agent;
588
- // struct libev_io watcher;
589
- // rb_io_t *fptr;
590
- // struct sockaddr_in addr;
591
- // char *host_buf = StringValueCStr(host);
592
- // VALUE switchpoint_result = Qnil;
593
- // VALUE underlying_sock = rb_iv_get(sock, "@io");
594
- // if (underlying_sock != Qnil) sock = underlying_sock;
595
-
596
- // GetLibevAgent(self, agent);
597
- // GetOpenFile(sock, fptr);
598
- // rb_io_set_nonblock(fptr);
599
- // watcher.fiber = Qnil;
600
-
601
- // addr.sin_family = AF_INET;
602
- // addr.sin_addr.s_addr = inet_addr(host_buf);
603
- // addr.sin_port = htons(NUM2INT(port));
604
-
605
- // while (1) {
606
- // int result = connect(fptr->fd, &addr, sizeof(addr));
607
- // if (result < 0) {
608
- // int e = errno;
609
- // if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
610
-
611
- // switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
612
- // if (TEST_EXCEPTION(switchpoint_result)) goto error;
613
- // }
614
- // else {
615
- // switchpoint_result = libev_snooze();
616
- // if (TEST_EXCEPTION(switchpoint_result)) goto error;
617
-
618
- // return sock;
619
- // }
620
- // }
621
- // RB_GC_GUARD(switchpoint_result);
622
- // return Qnil;
623
- // error:
624
- // return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
625
- // }
626
-
627
- VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
628
- struct LibevAgent_t *agent;
698
+ VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
699
+ LibevAgent_t *agent;
629
700
  struct libev_io watcher;
630
701
  rb_io_t *fptr;
702
+ struct sockaddr_in addr;
703
+ char *host_buf = StringValueCStr(host);
631
704
  VALUE switchpoint_result = Qnil;
632
- int events = RTEST(write) ? EV_WRITE : EV_READ;
705
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
706
+ if (underlying_sock != Qnil) sock = underlying_sock;
633
707
 
634
- VALUE underlying_io = rb_iv_get(io, "@io");
635
708
  GetLibevAgent(self, agent);
636
- if (underlying_io != Qnil) io = underlying_io;
637
- GetOpenFile(io, fptr);
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
+ }
733
+
734
+ VALUE libev_wait_fd(LibevAgent_t *agent, int fd, int events, int raise_exception) {
735
+ struct libev_io watcher;
736
+ VALUE switchpoint_result = Qnil;
638
737
 
639
738
  watcher.fiber = rb_fiber_current();
640
- ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
739
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fd, events);
641
740
  ev_io_start(agent->ev_loop, &watcher.io);
642
741
  switchpoint_result = libev_await(agent);
643
742
  ev_io_stop(agent->ev_loop, &watcher.io);
644
743
 
645
- TEST_RESUME_EXCEPTION(switchpoint_result);
646
- RB_GC_GUARD(watcher.fiber);
744
+ if (raise_exception) TEST_RESUME_EXCEPTION(switchpoint_result);
647
745
  RB_GC_GUARD(switchpoint_result);
648
746
  return switchpoint_result;
649
747
  }
650
748
 
749
+ VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
750
+ LibevAgent_t *agent;
751
+ rb_io_t *fptr;
752
+ int events = RTEST(write) ? EV_WRITE : EV_READ;
753
+ VALUE underlying_io = rb_iv_get(io, "@io");
754
+ if (underlying_io != Qnil) io = underlying_io;
755
+ GetLibevAgent(self, agent);
756
+ GetOpenFile(io, fptr);
757
+
758
+ return libev_wait_fd(agent, fptr->fd, events, 1);
759
+ }
760
+
651
761
  struct libev_timer {
652
762
  struct ev_timer timer;
653
763
  VALUE fiber;
@@ -660,7 +770,7 @@ void LibevAgent_timer_callback(EV_P_ ev_timer *w, int revents)
660
770
  }
661
771
 
662
772
  VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
663
- struct LibevAgent_t *agent;
773
+ LibevAgent_t *agent;
664
774
  struct libev_timer watcher;
665
775
  VALUE switchpoint_result = Qnil;
666
776
 
@@ -670,6 +780,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
670
780
  ev_timer_start(agent->ev_loop, &watcher.timer);
671
781
 
672
782
  switchpoint_result = libev_await(agent);
783
+
673
784
  ev_timer_stop(agent->ev_loop, &watcher.timer);
674
785
 
675
786
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -694,7 +805,7 @@ void LibevAgent_child_callback(EV_P_ ev_child *w, int revents)
694
805
  }
695
806
 
696
807
  VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
697
- struct LibevAgent_t *agent;
808
+ LibevAgent_t *agent;
698
809
  struct libev_child watcher;
699
810
  VALUE switchpoint_result = Qnil;
700
811
  GetLibevAgent(self, agent);
@@ -713,36 +824,67 @@ VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
713
824
  }
714
825
 
715
826
  struct ev_loop *LibevAgent_ev_loop(VALUE self) {
716
- struct LibevAgent_t *agent;
827
+ LibevAgent_t *agent;
717
828
  GetLibevAgent(self, agent);
718
829
  return agent->ev_loop;
719
830
  }
720
831
 
832
+ void LibevAgent_async_callback(EV_P_ ev_async *w, int revents) { }
833
+
834
+ VALUE LibevAgent_wait_event(VALUE self, VALUE raise) {
835
+ LibevAgent_t *agent;
836
+ VALUE switchpoint_result = Qnil;
837
+ GetLibevAgent(self, agent);
838
+
839
+ struct ev_async async;
840
+
841
+ ev_async_init(&async, LibevAgent_async_callback);
842
+ ev_async_start(agent->ev_loop, &async);
843
+ switchpoint_result = libev_await(agent);
844
+ ev_async_stop(agent->ev_loop, &async);
845
+
846
+ if (RTEST(raise)) TEST_RESUME_EXCEPTION(switchpoint_result);
847
+ RB_GC_GUARD(switchpoint_result);
848
+ return switchpoint_result;
849
+ }
850
+
721
851
  void Init_LibevAgent() {
722
852
  rb_require("socket");
723
853
  cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
724
854
 
725
- cLibevAgent = rb_define_class_under(mPolyphony, "LibevAgent", rb_cData);
726
- rb_define_alloc_func(cLibevAgent, LibevAgent_allocate);
727
-
728
- rb_define_method(cLibevAgent, "initialize", LibevAgent_initialize, 0);
729
- rb_define_method(cLibevAgent, "finalize", LibevAgent_finalize, 0);
730
- rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
731
- rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
732
-
733
- rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
734
- rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
735
-
736
- rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
737
- rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
738
-
739
- rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
740
- rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
741
- rb_define_method(cLibevAgent, "write", LibevAgent_write, -1);
742
- rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
743
- rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
744
- // rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
745
- rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
746
- rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
747
- rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
855
+ VALUE cAgent = rb_define_class_under(mPolyphony, "Agent", rb_cData);
856
+ rb_define_alloc_func(cAgent, LibevAgent_allocate);
857
+
858
+ rb_define_method(cAgent, "initialize", LibevAgent_initialize, 0);
859
+ rb_define_method(cAgent, "finalize", LibevAgent_finalize, 0);
860
+ rb_define_method(cAgent, "post_fork", LibevAgent_post_fork, 0);
861
+ rb_define_method(cAgent, "pending_count", LibevAgent_pending_count, 0);
862
+
863
+ rb_define_method(cAgent, "ref", LibevAgent_ref, 0);
864
+ rb_define_method(cAgent, "unref", LibevAgent_unref, 0);
865
+
866
+ rb_define_method(cAgent, "poll", LibevAgent_poll, 3);
867
+ rb_define_method(cAgent, "break", LibevAgent_wakeup, 0);
868
+
869
+ rb_define_method(cAgent, "read", LibevAgent_read, 4);
870
+ rb_define_method(cAgent, "read_loop", LibevAgent_read_loop, 1);
871
+ rb_define_method(cAgent, "write", LibevAgent_write_m, -1);
872
+ rb_define_method(cAgent, "accept", LibevAgent_accept, 1);
873
+ rb_define_method(cAgent, "accept_loop", LibevAgent_accept_loop, 1);
874
+ rb_define_method(cAgent, "connect", LibevAgent_connect, 3);
875
+ rb_define_method(cAgent, "wait_io", LibevAgent_wait_io, 2);
876
+ rb_define_method(cAgent, "sleep", LibevAgent_sleep, 1);
877
+ rb_define_method(cAgent, "waitpid", LibevAgent_waitpid, 1);
878
+ rb_define_method(cAgent, "wait_event", LibevAgent_wait_event, 1);
879
+
880
+ ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
881
+
882
+ __AGENT__.pending_count = LibevAgent_pending_count;
883
+ __AGENT__.poll = LibevAgent_poll;
884
+ __AGENT__.ref = LibevAgent_ref;
885
+ __AGENT__.ref_count = LibevAgent_ref_count;
886
+ __AGENT__.reset_ref_count = LibevAgent_reset_ref_count;
887
+ __AGENT__.unref = LibevAgent_unref;
888
+ __AGENT__.wait_event = LibevAgent_wait_event;
889
+ __AGENT__.wakeup = LibevAgent_wakeup;
748
890
  }