polyphony 0.43.2 → 0.43.8

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