polyphony 0.41 → 0.42

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile.lock +5 -5
  4. data/Rakefile +1 -1
  5. data/TODO.md +19 -9
  6. data/docs/_config.yml +56 -7
  7. data/docs/_sass/custom/custom.scss +0 -30
  8. data/docs/_sass/overrides.scss +0 -46
  9. data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
  10. data/docs/_user-guide/index.md +9 -0
  11. data/docs/{user-guide → _user-guide}/web-server.md +0 -0
  12. data/docs/api-reference/index.md +9 -0
  13. data/docs/api-reference/polyphony-process.md +1 -1
  14. data/docs/api-reference/thread.md +1 -1
  15. data/docs/faq.md +21 -11
  16. data/docs/getting-started/index.md +10 -0
  17. data/docs/getting-started/installing.md +2 -6
  18. data/docs/getting-started/overview.md +507 -0
  19. data/docs/getting-started/tutorial.md +27 -19
  20. data/docs/index.md +1 -1
  21. data/docs/main-concepts/concurrency.md +0 -5
  22. data/docs/main-concepts/design-principles.md +2 -12
  23. data/docs/main-concepts/index.md +9 -0
  24. data/examples/core/01-spinning-up-fibers.rb +1 -0
  25. data/examples/core/03-interrupting.rb +4 -1
  26. data/examples/core/04-handling-signals.rb +19 -0
  27. data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -18
  28. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  29. data/examples/performance/xx-array.rb +11 -0
  30. data/examples/performance/xx-fiber-switch.rb +9 -0
  31. data/examples/performance/xx-snooze.rb +15 -0
  32. data/ext/polyphony/fiber.c +0 -3
  33. data/ext/polyphony/libev_agent.c +234 -19
  34. data/ext/polyphony/libev_queue.c +3 -1
  35. data/ext/polyphony/polyphony.c +0 -10
  36. data/ext/polyphony/polyphony.h +6 -6
  37. data/ext/polyphony/thread.c +8 -36
  38. data/lib/polyphony.rb +5 -2
  39. data/lib/polyphony/core/channel.rb +2 -2
  40. data/lib/polyphony/core/global_api.rb +2 -2
  41. data/lib/polyphony/core/resource_pool.rb +2 -2
  42. data/lib/polyphony/extensions/core.rb +2 -3
  43. data/lib/polyphony/version.rb +1 -1
  44. data/polyphony.gemspec +1 -1
  45. data/test/test_agent.rb +49 -2
  46. metadata +16 -20
  47. data/docs/_includes/head.html +0 -40
  48. data/docs/_includes/nav.html +0 -51
  49. data/docs/_includes/prevnext.html +0 -17
  50. data/docs/_layouts/default.html +0 -106
  51. data/docs/api-reference.md +0 -11
  52. data/docs/api-reference/gyro-async.md +0 -57
  53. data/docs/api-reference/gyro-child.md +0 -29
  54. data/docs/api-reference/gyro-queue.md +0 -44
  55. data/docs/api-reference/gyro-timer.md +0 -51
  56. data/docs/api-reference/gyro.md +0 -25
  57. data/docs/getting-started.md +0 -10
  58. data/docs/main-concepts.md +0 -10
  59. data/docs/user-guide.md +0 -10
  60. data/examples/core/forever_sleep.rb +0 -19
@@ -1,13 +1,20 @@
1
1
  ---
2
2
  layout: page
3
3
  title: Tutorial
4
- nav_order: 2
5
4
  parent: Getting Started
6
- permalink: /getting-started/tutorial/
7
- prev_title: Installing Polyphony
8
- next_title: Concurrency the Easy Way
5
+ nav_order: 3
6
+ ---
7
+
8
+ # Tutorial
9
+ {: .no_toc }
10
+
11
+ ## Table of contents
12
+ {: .no_toc .text-delta }
13
+
14
+ - TOC
15
+ {:toc}
16
+
9
17
  ---
10
- # Polyphony: a Tutorial
11
18
 
12
19
  Polyphony is a new Ruby library aimed at making writing concurrent Ruby apps
13
20
  easy and fun. In this article, we'll introduce Polyphony's fiber-based
@@ -116,7 +123,7 @@ suspend # The main fiber suspends, waiting for all other work to finish
116
123
  sleep 1 # The sleeper fiber goes to sleep
117
124
  Gyro::Timer.new(1, 0).await # A timer event watcher is setup and yields
118
125
  Thread.current.switch_fiber # Polyphony looks for other runnable fibers
119
- Thread.current.selector.run # With no work left, the event loop is ran
126
+ Thread.current.agent.poll # With no work left, the event loop is ran
120
127
  fiber.schedule # The timer event fires, scheduling the sleeper fiber
121
128
  # <= The sleep method returns
122
129
  puts "Woke up"
@@ -218,7 +225,7 @@ this by setting up a timeout fiber that cancels the fiber dealing with the conne
218
225
  def handle_client(client)
219
226
  timeout = cancel_after(10)
220
227
  while (data = client.gets)
221
- timeout.reset
228
+ timeout.restart
222
229
  client << data
223
230
  end
224
231
  rescue Polyphony::Cancel
@@ -230,18 +237,19 @@ ensure
230
237
  end
231
238
  ```
232
239
 
233
- The cancel scope is initialized with a timeout of 10 seconds. Any blocking
234
- operation ocurring within the cancel scope will be interrupted once 10 seconds
235
- have elapsed. In order to keep the connection alive while the client is active,
236
- we call `scope.reset_timeout` each time data is received from the client, and
237
- thus reset the cancel scope timer.
238
-
239
- In addition, we use an `ensure` block to make sure the client connection is
240
- closed, whether or not it was interrupted by the cancel scope timer. The habit
241
- of always cleaning up using `ensure` in the face of potential interruptions is a
242
- fundamental element of using Polyphony correctly. This makes your code robust,
243
- even in a highly chaotic concurrent execution environment where tasks can be
244
- interrupted at any time.
240
+ The call to `#cancel_after` spins up a new fiber that will sleep for 10 seconds,
241
+ then cancel its parent. The call to `client.gets` blocks until new data is
242
+ available. If no new data is available, the `timeout` fiber will finish
243
+ sleeping, and then cancel the client handling fiber by raising a
244
+ `Polyphony::Cancel` exception. However, if new data is received, the `timeout`
245
+ fiber is restarted, causing to begin sleeping again for 10 seconds. If the
246
+ client has closed the connection, or some other exception occurs, the `timeout`
247
+ fiber is automatically stopped as it is a child of the client handling fiber.
248
+
249
+ The habit of always cleaning up using `ensure` in the face of potential
250
+ interruptions is a fundamental element of using Polyphony correctly. This makes
251
+ your code robust, even in a highly chaotic concurrent execution environment
252
+ where tasks can be started, restarted and interrupted at any time.
245
253
 
246
254
  ## Implementing graceful shutdown
247
255
 
@@ -53,7 +53,7 @@ adapters are being developed.
53
53
  * Natural, sequential programming style that makes it easy to reason about
54
54
  concurrent code.
55
55
  * Abstractions and constructs for controlling the execution of concurrent code:
56
- supervisors, cancel scopes, throttling, resource pools etc.
56
+ supervisors, throttling, resource pools etc.
57
57
  * Code can use native networking classes and libraries, growing support for
58
58
  third-party gems such as `pg` and `redis`.
59
59
  * Use stdlib classes such as `TCPServer` and `TCPSocket` and `Net::HTTP`.
@@ -130,16 +130,11 @@ Polyphony also provides several methods and constructs for controlling multiple
130
130
  fibers. Methods like `cancel_after` and `move_on_after` allow interrupting a
131
131
  fiber that's blocking on any arbitrary operation.
132
132
 
133
- Cancel scopes \(borrowed from the brilliant Python library
134
- [Trio](https://trio.readthedocs.io/en/stable/)\) allows cancelling ongoing
135
- operations for any reason with more control over cancelling behaviour.
136
-
137
133
  Some other constructs offered by Polyphony:
138
134
 
139
135
  * `Mutex` - a mutex used to synchronize access to a single shared resource.
140
136
  * `ResourcePool` - used for synchronizing access to a limited amount of shared
141
137
  resources, for example a pool of database connections.
142
-
143
138
  * `Throttler` - used for throttling repeating operations, for example throttling
144
139
  access to a shared resource, or throttling incoming requests.
145
140
 
@@ -143,18 +143,8 @@ library. Polyphony's design is based on the following principles:
143
143
  end
144
144
  ```
145
145
 
146
- - Concurrency primitives should allow creating higher-order concurrent
147
- constructs through composition. This is done primarily through supervisors and
148
- cancel scopes:
149
-
150
- ```ruby
151
- # wait for multiple fibers
152
- supervise { |s|
153
- clients.each { |client|
154
- s.spin { client.puts 'Elvis has left the chatroom' }
155
- }
156
- }
157
- ```
146
+ - Concurrency primitives should allow creating higher-order concurrent
147
+ constructs through composition.
158
148
 
159
149
  - The entire design should embrace fibers. There should be no callback-based
160
150
  asynchronous APIs.
@@ -0,0 +1,9 @@
1
+ ---
2
+ layout: page
3
+ title: Main Concepts
4
+ has_children: true
5
+ nav_order: 3
6
+ ---
7
+
8
+ # Main Concepts
9
+ {: .no_toc }
@@ -14,4 +14,5 @@ end
14
14
  spin { nap(:a, 1) }
15
15
  spin { nap(:b, 2) }
16
16
 
17
+ # Calling suspend will block until all child fibers have terminated
17
18
  suspend
@@ -18,7 +18,8 @@ ensure
18
18
  end
19
19
 
20
20
  # The Kernel#cancel_after interrupts a blocking operation by raising a
21
- # Polyphony::Cancel exception after the given timeout
21
+ # Polyphony::Cancel exception after the given timeout. If not rescued, the
22
+ # exception is propagated up the fiber hierarchy
22
23
  spin do
23
24
  # cancel after 1 second
24
25
  cancel_after(1) { nap(:cancel, 2) }
@@ -26,6 +27,8 @@ rescue Polyphony::Cancel => e
26
27
  puts "got exception: #{e}"
27
28
  end
28
29
 
30
+ # The Kernel#move_on_after interrupts a blocking operation by raising a
31
+ # Polyphony::MoveOn exception, which is silently swallowed by the fiber
29
32
  spin do
30
33
  # move on after 1 second
31
34
  move_on_after(1) do
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ # trap('TERM') do
7
+ # Polyphony.emit_signal_exception(::SystemExit)
8
+ # end
9
+
10
+ # trap('INT') do
11
+ # Polyphony.emit_signal_exception(::Interrupt)
12
+ # end
13
+
14
+ puts "go to sleep"
15
+ begin
16
+ sleep
17
+ ensure
18
+ puts "done sleeping"
19
+ end
@@ -35,23 +35,11 @@ def handle_request(client, parser)
35
35
  client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
36
36
  end
37
37
 
38
- spin do
39
- server = TCPServer.open('0.0.0.0', 1234)
40
- puts "listening on port 1234"
38
+ server = TCPServer.open('0.0.0.0', 1234)
39
+ puts "pid #{Process.pid}"
40
+ puts "listening on port 1234"
41
41
 
42
- loop do
43
- client = server.accept
44
- spin { handle_client(client) }
45
- end
46
- ensure
47
- server&.close
42
+ loop do
43
+ client = server.accept
44
+ spin { handle_client(client) }
48
45
  end
49
-
50
- # every(1) {
51
- # stats = Thread.current.fiber_scheduling_stats
52
- # stats[:connection_count] = $connection_count
53
- # puts "#{Time.now} #{stats}"
54
- # }
55
-
56
- puts "pid #{Process.pid}"
57
- suspend
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'http/parser'
6
+
7
+ $connection_count = 0
8
+
9
+ def handle_client(socket)
10
+ $connection_count += 1
11
+ parser = Http::Parser.new
12
+ reqs = []
13
+ parser.on_message_complete = proc do |env|
14
+ reqs << Object.new # parser
15
+ end
16
+ Thread.current.agent.read_loop(socket) do |data|
17
+ parser << data
18
+ while (req = reqs.shift)
19
+ handle_request(socket, req)
20
+ end
21
+ end
22
+ rescue IOError, SystemCallError => e
23
+ # do nothing
24
+ ensure
25
+ $connection_count -= 1
26
+ socket&.close
27
+ end
28
+
29
+ def handle_request(client, parser)
30
+ status_code = "200 OK"
31
+ data = "Hello world!\n"
32
+ headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
33
+ client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
34
+ end
35
+
36
+ spin do
37
+ server = TCPServer.open('0.0.0.0', 1234)
38
+ puts "listening on port 1234"
39
+
40
+ Thread.current.agent.accept_loop(server) do |client|
41
+ spin { handle_client(client) }
42
+ end
43
+ # loop do
44
+ # client = server.accept
45
+ # spin { handle_client(client) }
46
+ # end
47
+ ensure
48
+ server&.close
49
+ end
50
+
51
+ # every(1) {
52
+ # stats = Thread.current.fiber_scheduling_stats
53
+ # stats[:connection_count] = $connection_count
54
+ # puts "#{Time.now} #{stats}"
55
+ # }
56
+
57
+ puts "pid #{Process.pid}"
58
+ suspend
@@ -0,0 +1,11 @@
1
+ X = ARGV[0] ? ARGV[0].to_i : 10
2
+ a = (1..X).to_a
3
+
4
+ Y = 1_000_000
5
+ t0 = Time.now
6
+ Y.times do
7
+ i = a.shift
8
+ a.push i
9
+ end
10
+
11
+ puts "rate: #{Y / (Time.now - t0)}"
@@ -0,0 +1,9 @@
1
+ X = 1_000_000
2
+ f = Fiber.new do
3
+ loop { Fiber.yield }
4
+ end
5
+
6
+ t0 = Time.now
7
+ X.times { f.resume }
8
+ dt = Time.now - t0
9
+ puts "#{X / dt.to_f}/s"
@@ -0,0 +1,15 @@
1
+ require 'bundler/setup'
2
+ require 'polyphony'
3
+
4
+ Y = ARGV[0] ? ARGV[0].to_i : 1
5
+
6
+ count = 0
7
+ Y.times do
8
+ spin { loop { count += 1; snooze } }
9
+ end
10
+
11
+ t0 = Time.now
12
+ sleep 10
13
+ elapsed = Time.now - t0
14
+ rate = count / elapsed
15
+ puts "concurrency: #{Y} rate: #{rate} switchpoints per second"
@@ -68,10 +68,7 @@ void Fiber_make_runnable(VALUE fiber, VALUE value) {
68
68
  Thread_schedule_fiber(thread, fiber, value);
69
69
  }
70
70
  else {
71
- VALUE caller;
72
71
  rb_warn("No thread set for fiber (fiber, value, caller):");
73
- caller = rb_funcall(rb_cObject, rb_intern("caller"), 0);
74
- INSPECT(3, fiber, value, caller);
75
72
  }
76
73
  }
77
74
 
@@ -9,6 +9,7 @@ struct LibevAgent_t {
9
9
  struct ev_loop *ev_loop;
10
10
  struct ev_async break_async;
11
11
  int running;
12
+ int ref_count;
12
13
  int run_no_wait_count;
13
14
  };
14
15
 
@@ -49,6 +50,7 @@ static VALUE LibevAgent_initialize(VALUE self) {
49
50
  ev_unref(agent->ev_loop); // don't count the break_async watcher
50
51
 
51
52
  agent->running = 0;
53
+ agent->ref_count = 0;
52
54
  agent->run_no_wait_count = 0;
53
55
 
54
56
  return Qnil;
@@ -82,6 +84,36 @@ VALUE LibevAgent_post_fork(VALUE self) {
82
84
  return self;
83
85
  }
84
86
 
87
+ VALUE LibevAgent_ref(VALUE self) {
88
+ struct LibevAgent_t *agent;
89
+ GetLibevAgent(self, agent);
90
+
91
+ agent->ref_count++;
92
+ return self;
93
+ }
94
+
95
+ VALUE LibevAgent_unref(VALUE self) {
96
+ struct LibevAgent_t *agent;
97
+ GetLibevAgent(self, agent);
98
+
99
+ agent->ref_count--;
100
+ return self;
101
+ }
102
+
103
+ int LibevAgent_ref_count(VALUE self) {
104
+ struct LibevAgent_t *agent;
105
+ GetLibevAgent(self, agent);
106
+
107
+ return agent->ref_count;
108
+ }
109
+
110
+ void LibevAgent_reset_ref_count(VALUE self) {
111
+ struct LibevAgent_t *agent;
112
+ GetLibevAgent(self, agent);
113
+
114
+ agent->ref_count = 0;
115
+ }
116
+
85
117
  VALUE LibevAgent_pending_count(VALUE self) {
86
118
  int count;
87
119
  struct LibevAgent_t *agent;
@@ -143,6 +175,8 @@ struct io_internal_read_struct {
143
175
  size_t capa;
144
176
  };
145
177
 
178
+ #define StringValue(v) rb_string_value(&(v))
179
+
146
180
  int io_setstrbuf(VALUE *str, long len) {
147
181
  #ifdef _WIN32
148
182
  len = (len + 1) & ~1L; /* round up for wide char */
@@ -206,6 +240,21 @@ static void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
206
240
  Fiber_make_runnable(watcher->fiber, Qnil);
207
241
  }
208
242
 
243
+ inline VALUE libev_await(struct LibevAgent_t *agent) {
244
+ VALUE ret;
245
+ agent->ref_count++;
246
+ ret = Thread_switch_fiber(rb_thread_current());
247
+ agent->ref_count--;
248
+ RB_GC_GUARD(ret);
249
+ return ret;
250
+ }
251
+
252
+ VALUE libev_agent_await(VALUE self) {
253
+ struct LibevAgent_t *agent;
254
+ GetLibevAgent(self, agent);
255
+ return libev_await(agent);
256
+ }
257
+
209
258
  VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
210
259
  struct LibevAgent_t *agent;
211
260
  struct libev_io watcher;
@@ -245,10 +294,11 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
245
294
  ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
246
295
  }
247
296
  ev_io_start(agent->ev_loop, &watcher.io);
248
- switchpoint_result = Polyphony_switchpoint();
297
+ switchpoint_result = libev_await(agent);
249
298
  ev_io_stop(agent->ev_loop, &watcher.io);
250
- if (TEST_EXCEPTION(switchpoint_result))
299
+ if (TEST_EXCEPTION(switchpoint_result)) {
251
300
  goto error;
301
+ }
252
302
  }
253
303
  else
254
304
  rb_syserr_fail(e, strerror(e));
@@ -256,6 +306,14 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
256
306
  }
257
307
  }
258
308
 
309
+ if (watcher.fiber == Qnil) {
310
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
311
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
312
+ if (TEST_EXCEPTION(switchpoint_result)) {
313
+ goto error;
314
+ }
315
+ }
316
+
259
317
  if (total == 0) return Qnil;
260
318
 
261
319
  io_set_read_length(str, total, shrinkable);
@@ -269,6 +327,86 @@ error:
269
327
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
270
328
  }
271
329
 
330
+ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
331
+
332
+ #define PREPARE_STR() { \
333
+ str = Qnil; \
334
+ shrinkable = io_setstrbuf(&str, len); \
335
+ buf = RSTRING_PTR(str); \
336
+ total = 0; \
337
+ }
338
+
339
+ #define YIELD_STR() { \
340
+ io_set_read_length(str, total, shrinkable); \
341
+ io_enc_str(str, fptr); \
342
+ rb_yield(str); \
343
+ PREPARE_STR(); \
344
+ }
345
+
346
+ struct LibevAgent_t *agent;
347
+ struct libev_io watcher;
348
+ rb_io_t *fptr;
349
+ VALUE str;
350
+ int total;
351
+ int len = 8192;
352
+ int shrinkable;
353
+ char *buf;
354
+ VALUE switchpoint_result = Qnil;
355
+ VALUE underlying_io = rb_iv_get(io, "@io");
356
+
357
+ PREPARE_STR();
358
+
359
+ GetLibevAgent(self, agent);
360
+ if (underlying_io != Qnil) io = underlying_io;
361
+ GetOpenFile(io, fptr);
362
+ rb_io_check_byte_readable(fptr);
363
+ rb_io_set_nonblock(fptr);
364
+ watcher.fiber = Qnil;
365
+
366
+ OBJ_TAINT(str);
367
+
368
+ while (1) {
369
+ int n = read(fptr->fd, buf, len);
370
+ if (n == 0)
371
+ break;
372
+ if (n > 0) {
373
+ total = n;
374
+ YIELD_STR();
375
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
376
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
377
+ if (TEST_EXCEPTION(switchpoint_result)) {
378
+ goto error;
379
+ }
380
+ }
381
+ else {
382
+ int e = errno;
383
+ if ((e == EWOULDBLOCK || e == EAGAIN)) {
384
+ if (watcher.fiber == Qnil) {
385
+ watcher.fiber = rb_fiber_current();
386
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
387
+ }
388
+ ev_io_start(agent->ev_loop, &watcher.io);
389
+ switchpoint_result = libev_await(agent);
390
+ ev_io_stop(agent->ev_loop, &watcher.io);
391
+ if (TEST_EXCEPTION(switchpoint_result)) {
392
+ goto error;
393
+ }
394
+ }
395
+ else
396
+ rb_syserr_fail(e, strerror(e));
397
+ // rb_syserr_fail_path(e, fptr->pathv);
398
+ }
399
+ }
400
+
401
+ RB_GC_GUARD(str);
402
+ RB_GC_GUARD(watcher.fiber);
403
+ RB_GC_GUARD(switchpoint_result);
404
+
405
+ return io;
406
+ error:
407
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
408
+ }
409
+
272
410
  VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
273
411
  struct LibevAgent_t *agent;
274
412
  struct libev_io watcher;
@@ -289,19 +427,21 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
289
427
  while (left > 0) {
290
428
  int result = write(fptr->fd, buf, left);
291
429
  if (result < 0) {
292
- if (errno == EAGAIN) {
430
+ int e = errno;
431
+ if (e == EAGAIN) {
293
432
  if (watcher.fiber == Qnil) {
294
433
  watcher.fiber = rb_fiber_current();
295
434
  ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_WRITE);
296
435
  }
297
436
  ev_io_start(agent->ev_loop, &watcher.io);
298
- switchpoint_result = Polyphony_switchpoint();
437
+ switchpoint_result = libev_await(agent);
299
438
  ev_io_stop(agent->ev_loop, &watcher.io);
300
439
  if (TEST_EXCEPTION(switchpoint_result))
301
440
  goto error;
302
441
  }
303
442
  else {
304
- // report error
443
+ rb_syserr_fail(e, strerror(e));
444
+ // rb_syserr_fail_path(e, fptr->pathv);
305
445
 
306
446
  }
307
447
  }
@@ -311,6 +451,14 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
311
451
  }
312
452
  }
313
453
 
454
+ if (watcher.fiber == Qnil) {
455
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
456
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
457
+ if (TEST_EXCEPTION(switchpoint_result)) {
458
+ goto error;
459
+ }
460
+ }
461
+
314
462
  RB_GC_GUARD(watcher.fiber);
315
463
  RB_GC_GUARD(switchpoint_result);
316
464
 
@@ -321,16 +469,6 @@ error:
321
469
 
322
470
  ///////////////////////////////////////////////////////////////////////////
323
471
 
324
- struct rsock_send_arg {
325
- int fd, flags;
326
- VALUE mesg;
327
- struct sockaddr *to;
328
- socklen_t tolen;
329
- };
330
-
331
- #define StringValue(v) rb_string_value(&(v))
332
- #define IS_ADDRINFO(obj) rb_typeddata_is_kind_of((obj), &addrinfo_type)
333
-
334
472
  VALUE LibevAgent_accept(VALUE self, VALUE sock) {
335
473
  struct LibevAgent_t *agent;
336
474
  struct libev_io watcher;
@@ -339,6 +477,8 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
339
477
  struct sockaddr addr;
340
478
  socklen_t len = (socklen_t)sizeof addr;
341
479
  VALUE switchpoint_result = Qnil;
480
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
481
+ if (underlying_sock != Qnil) sock = underlying_sock;
342
482
 
343
483
  GetLibevAgent(self, agent);
344
484
  GetOpenFile(sock, fptr);
@@ -354,7 +494,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
354
494
  ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
355
495
  }
356
496
  ev_io_start(agent->ev_loop, &watcher.io);
357
- switchpoint_result = Polyphony_switchpoint();
497
+ switchpoint_result = libev_await(agent);
358
498
  ev_io_stop(agent->ev_loop, &watcher.io);
359
499
 
360
500
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -375,16 +515,86 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
375
515
  rb_io_ascii8bit_binmode(connection);
376
516
  rb_io_set_nonblock(fp);
377
517
  rb_io_synchronized(fp);
518
+
378
519
  // if (rsock_do_not_reverse_lookup) {
379
520
  // fp->mode |= FMODE_NOREVLOOKUP;
380
521
  // }
381
522
 
523
+ if (watcher.fiber == Qnil) {
524
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
525
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
526
+ if (TEST_EXCEPTION(switchpoint_result)) {
527
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
528
+ }
529
+ }
530
+
382
531
  return connection;
383
532
  }
384
533
  }
385
534
  return Qnil;
386
535
  }
387
536
 
537
+ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
538
+ struct LibevAgent_t *agent;
539
+ struct libev_io watcher;
540
+ rb_io_t *fptr;
541
+ int fd;
542
+ struct sockaddr addr;
543
+ socklen_t len = (socklen_t)sizeof addr;
544
+ VALUE switchpoint_result = Qnil;
545
+ VALUE connection = Qnil;
546
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
547
+ if (underlying_sock != Qnil) sock = underlying_sock;
548
+
549
+ GetLibevAgent(self, agent);
550
+ GetOpenFile(sock, fptr);
551
+ rb_io_set_nonblock(fptr);
552
+ watcher.fiber = Qnil;
553
+
554
+ while (1) {
555
+ fd = accept(fptr->fd, &addr, &len);
556
+ if (fd < 0) {
557
+ int e = errno;
558
+ if (e == EWOULDBLOCK || e == EAGAIN) {
559
+ if (watcher.fiber == Qnil) {
560
+ watcher.fiber = rb_fiber_current();
561
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
562
+ }
563
+ ev_io_start(agent->ev_loop, &watcher.io);
564
+ switchpoint_result = libev_await(agent);
565
+ ev_io_stop(agent->ev_loop, &watcher.io);
566
+
567
+ TEST_RESUME_EXCEPTION(switchpoint_result);
568
+ }
569
+ else
570
+ rb_syserr_fail(e, strerror(e));
571
+ // rb_syserr_fail_path(e, fptr->pathv);
572
+ }
573
+ else {
574
+ rb_io_t *fp;
575
+ connection = rb_obj_alloc(cTCPSocket);
576
+ MakeOpenFile(connection, fp);
577
+ rb_update_max_fd(fd);
578
+ fp->fd = fd;
579
+ fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
580
+ rb_io_ascii8bit_binmode(connection);
581
+ rb_io_set_nonblock(fp);
582
+ rb_io_synchronized(fp);
583
+
584
+ rb_yield(connection);
585
+ connection = Qnil;
586
+
587
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
588
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
589
+ TEST_RESUME_EXCEPTION(switchpoint_result);
590
+ }
591
+ }
592
+
593
+ RB_GC_GUARD(connection);
594
+ RB_GC_GUARD(watcher.fiber);
595
+ RB_GC_GUARD(switchpoint_result);
596
+ }
597
+
388
598
  VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
389
599
  struct LibevAgent_t *agent;
390
600
  struct libev_io watcher;
@@ -400,7 +610,7 @@ VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
400
610
  watcher.fiber = rb_fiber_current();
401
611
  ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
402
612
  ev_io_start(agent->ev_loop, &watcher.io);
403
- switchpoint_result = Polyphony_switchpoint();
613
+ switchpoint_result = libev_await(agent);
404
614
  ev_io_stop(agent->ev_loop, &watcher.io);
405
615
 
406
616
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -430,7 +640,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
430
640
  ev_timer_init(&watcher.timer, LibevAgent_timer_callback, NUM2DBL(duration), 0.);
431
641
  ev_timer_start(agent->ev_loop, &watcher.timer);
432
642
 
433
- switchpoint_result = Polyphony_switchpoint();
643
+ switchpoint_result = libev_await(agent);
434
644
  ev_timer_stop(agent->ev_loop, &watcher.timer);
435
645
 
436
646
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -464,7 +674,7 @@ VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
464
674
  ev_child_init(&watcher.child, LibevAgent_child_callback, NUM2INT(pid), 0);
465
675
  ev_child_start(agent->ev_loop, &watcher.child);
466
676
 
467
- switchpoint_result = Polyphony_switchpoint();
677
+ switchpoint_result = libev_await(agent);
468
678
  ev_child_stop(agent->ev_loop, &watcher.child);
469
679
 
470
680
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -491,12 +701,17 @@ void Init_LibevAgent() {
491
701
  rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
492
702
  rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
493
703
 
704
+ rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
705
+ rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
706
+
494
707
  rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
495
708
  rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
496
709
 
497
710
  rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
711
+ rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
498
712
  rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
499
713
  rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
714
+ rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
500
715
  rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
501
716
  rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
502
717
  rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);