polyphony 0.41 → 0.42

Sign up to get free protection for your applications and to get access to all the features.
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);