polyphony 0.41 → 0.43.3

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/Gemfile.lock +6 -6
  4. data/README.md +0 -1
  5. data/Rakefile +1 -1
  6. data/TODO.md +18 -9
  7. data/docs/_config.yml +56 -7
  8. data/docs/_sass/custom/custom.scss +6 -26
  9. data/docs/_sass/overrides.scss +0 -46
  10. data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
  11. data/docs/_user-guide/index.md +9 -0
  12. data/docs/{user-guide → _user-guide}/web-server.md +0 -0
  13. data/docs/api-reference/index.md +9 -0
  14. data/docs/api-reference/polyphony-process.md +1 -1
  15. data/docs/api-reference/thread.md +1 -1
  16. data/docs/faq.md +21 -11
  17. data/docs/favicon.ico +0 -0
  18. data/docs/getting-started/index.md +10 -0
  19. data/docs/getting-started/installing.md +2 -6
  20. data/docs/getting-started/overview.md +486 -0
  21. data/docs/getting-started/tutorial.md +27 -19
  22. data/docs/index.md +6 -2
  23. data/docs/main-concepts/concurrency.md +0 -5
  24. data/docs/main-concepts/design-principles.md +2 -12
  25. data/docs/main-concepts/index.md +9 -0
  26. data/docs/polyphony-logo.png +0 -0
  27. data/examples/adapters/concurrent-ruby.rb +9 -0
  28. data/examples/adapters/redis_blpop.rb +12 -0
  29. data/examples/core/01-spinning-up-fibers.rb +1 -0
  30. data/examples/core/03-interrupting.rb +4 -1
  31. data/examples/core/04-handling-signals.rb +19 -0
  32. data/examples/core/xx-daemon.rb +14 -0
  33. data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -18
  34. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  35. data/examples/performance/xx-array.rb +11 -0
  36. data/examples/performance/xx-fiber-switch.rb +9 -0
  37. data/examples/performance/xx-snooze.rb +15 -0
  38. data/ext/polyphony/fiber.c +0 -3
  39. data/ext/polyphony/libev_agent.c +303 -81
  40. data/ext/polyphony/libev_queue.c +8 -5
  41. data/ext/polyphony/polyphony.c +0 -16
  42. data/ext/polyphony/polyphony.h +6 -6
  43. data/ext/polyphony/polyphony_ext.c +0 -2
  44. data/ext/polyphony/thread.c +8 -42
  45. data/lib/polyphony.rb +29 -2
  46. data/lib/polyphony/adapters/redis.rb +3 -2
  47. data/lib/polyphony/core/channel.rb +2 -2
  48. data/lib/polyphony/core/global_api.rb +6 -4
  49. data/lib/polyphony/core/resource_pool.rb +19 -9
  50. data/lib/polyphony/extensions/core.rb +8 -3
  51. data/lib/polyphony/extensions/fiber.rb +0 -12
  52. data/lib/polyphony/extensions/io.rb +4 -0
  53. data/lib/polyphony/extensions/openssl.rb +34 -10
  54. data/lib/polyphony/extensions/socket.rb +2 -2
  55. data/lib/polyphony/version.rb +1 -1
  56. data/polyphony.gemspec +1 -1
  57. data/test/test_agent.rb +59 -6
  58. data/test/test_fiber.rb +3 -3
  59. data/test/test_global_api.rb +48 -15
  60. data/test/test_resource_pool.rb +12 -0
  61. data/test/test_socket.rb +5 -4
  62. data/test/test_throttler.rb +6 -5
  63. metadata +21 -21
  64. data/docs/_includes/head.html +0 -40
  65. data/docs/_includes/nav.html +0 -51
  66. data/docs/_includes/prevnext.html +0 -17
  67. data/docs/_layouts/default.html +0 -106
  68. data/docs/api-reference.md +0 -11
  69. data/docs/api-reference/gyro-async.md +0 -57
  70. data/docs/api-reference/gyro-child.md +0 -29
  71. data/docs/api-reference/gyro-queue.md +0 -44
  72. data/docs/api-reference/gyro-timer.md +0 -51
  73. data/docs/api-reference/gyro.md +0 -25
  74. data/docs/getting-started.md +0 -10
  75. data/docs/main-concepts.md +0 -10
  76. data/docs/user-guide.md +0 -10
  77. data/examples/core/forever_sleep.rb +0 -19
  78. data/ext/polyphony/socket.c +0 -213
@@ -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
 
@@ -6,7 +6,11 @@ permalink: /
6
6
  next_title: Installing Polyphony
7
7
  ---
8
8
 
9
- # Polyphony - fine-grained concurrency for Ruby
9
+ # Polyphony
10
+ {:.text-center .logo-title}
11
+
12
+ ## Fine-grained concurrency for Ruby
13
+ {:.text-center .logo-title}
10
14
 
11
15
  Polyphony is a library for building concurrent applications in Ruby. Polyphony
12
16
  implements a comprehensive
@@ -53,7 +57,7 @@ adapters are being developed.
53
57
  * Natural, sequential programming style that makes it easy to reason about
54
58
  concurrent code.
55
59
  * Abstractions and constructs for controlling the execution of concurrent code:
56
- supervisors, cancel scopes, throttling, resource pools etc.
60
+ supervisors, throttling, resource pools etc.
57
61
  * Code can use native networking classes and libraries, growing support for
58
62
  third-party gems such as `pg` and `redis`.
59
63
  * 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 }
Binary file
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'concurrent'
6
+
7
+ puts "Hello, concurrent-ruby"
8
+
9
+ # this program should not hang with concurrent-ruby 1.1.6 (see issue #22)
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/adapters/redis'
5
+ # require 'redis'
6
+
7
+ redis = Redis.new(host: ENV['REDISHOST'] || 'localhost')
8
+
9
+ redis.lpush("queue_key", "omgvalue")
10
+ puts "len: #{redis.llen("queue_key")}"
11
+ result = redis.blpop("queue_key")
12
+ puts result.inspect
@@ -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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ puts "pid: #{Process.pid}"
9
+
10
+ Process.daemon(true, true)
11
+
12
+ Polyphony::ThreadPool.process do
13
+ puts "Hello world from pid #{Process.pid}"
14
+ 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
+ socket.read_loop 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
 
@@ -1,6 +1,8 @@
1
+ #include <netdb.h>
2
+ #include <sys/socket.h>
3
+
1
4
  #include "polyphony.h"
2
5
  #include "../libev/ev.h"
3
- #include <sys/socket.h>
4
6
 
5
7
  VALUE cLibevAgent = Qnil;
6
8
  VALUE cTCPSocket;
@@ -9,6 +11,7 @@ struct LibevAgent_t {
9
11
  struct ev_loop *ev_loop;
10
12
  struct ev_async break_async;
11
13
  int running;
14
+ int ref_count;
12
15
  int run_no_wait_count;
13
16
  };
14
17
 
@@ -49,6 +52,7 @@ static VALUE LibevAgent_initialize(VALUE self) {
49
52
  ev_unref(agent->ev_loop); // don't count the break_async watcher
50
53
 
51
54
  agent->running = 0;
55
+ agent->ref_count = 0;
52
56
  agent->run_no_wait_count = 0;
53
57
 
54
58
  return Qnil;
@@ -82,6 +86,36 @@ VALUE LibevAgent_post_fork(VALUE self) {
82
86
  return self;
83
87
  }
84
88
 
89
+ VALUE LibevAgent_ref(VALUE self) {
90
+ struct LibevAgent_t *agent;
91
+ GetLibevAgent(self, agent);
92
+
93
+ agent->ref_count++;
94
+ return self;
95
+ }
96
+
97
+ VALUE LibevAgent_unref(VALUE self) {
98
+ struct LibevAgent_t *agent;
99
+ GetLibevAgent(self, agent);
100
+
101
+ agent->ref_count--;
102
+ return self;
103
+ }
104
+
105
+ int LibevAgent_ref_count(VALUE self) {
106
+ struct LibevAgent_t *agent;
107
+ GetLibevAgent(self, agent);
108
+
109
+ return agent->ref_count;
110
+ }
111
+
112
+ void LibevAgent_reset_ref_count(VALUE self) {
113
+ struct LibevAgent_t *agent;
114
+ GetLibevAgent(self, agent);
115
+
116
+ agent->ref_count = 0;
117
+ }
118
+
85
119
  VALUE LibevAgent_pending_count(VALUE self) {
86
120
  int count;
87
121
  struct LibevAgent_t *agent;
@@ -96,7 +130,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
96
130
  GetLibevAgent(self, agent);
97
131
 
98
132
  if (is_nowait) {
99
- int runnable_count = RARRAY_LEN(queue);
133
+ long runnable_count = RARRAY_LEN(queue);
100
134
  agent->run_no_wait_count++;
101
135
  if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
102
136
  return self;
@@ -143,6 +177,8 @@ struct io_internal_read_struct {
143
177
  size_t capa;
144
178
  };
145
179
 
180
+ #define StringValue(v) rb_string_value(&(v))
181
+
146
182
  int io_setstrbuf(VALUE *str, long len) {
147
183
  #ifdef _WIN32
148
184
  len = (len + 1) & ~1L; /* round up for wide char */
@@ -200,17 +236,52 @@ struct libev_io {
200
236
  VALUE fiber;
201
237
  };
202
238
 
203
- static void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
239
+ void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
204
240
  {
205
241
  struct libev_io *watcher = (struct libev_io *)w;
206
242
  Fiber_make_runnable(watcher->fiber, Qnil);
207
243
  }
208
244
 
245
+ inline VALUE libev_await(struct LibevAgent_t *agent) {
246
+ VALUE ret;
247
+ agent->ref_count++;
248
+ ret = Thread_switch_fiber(rb_thread_current());
249
+ agent->ref_count--;
250
+ RB_GC_GUARD(ret);
251
+ return ret;
252
+ }
253
+
254
+ VALUE libev_agent_await(VALUE self) {
255
+ struct LibevAgent_t *agent;
256
+ GetLibevAgent(self, agent);
257
+ return libev_await(agent);
258
+ }
259
+
260
+ VALUE libev_io_wait(struct LibevAgent_t *agent, struct libev_io *watcher, rb_io_t *fptr, int flags) {
261
+ VALUE switchpoint_result;
262
+
263
+ if (watcher->fiber == Qnil) {
264
+ watcher->fiber = rb_fiber_current();
265
+ ev_io_init(&watcher->io, LibevAgent_io_callback, fptr->fd, flags);
266
+ }
267
+ ev_io_start(agent->ev_loop, &watcher->io);
268
+ switchpoint_result = libev_await(agent);
269
+ ev_io_stop(agent->ev_loop, &watcher->io);
270
+
271
+ RB_GC_GUARD(switchpoint_result);
272
+ return switchpoint_result;
273
+ }
274
+
275
+ VALUE libev_snooze() {
276
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
277
+ return Thread_switch_fiber(rb_thread_current());
278
+ }
279
+
209
280
  VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
210
281
  struct LibevAgent_t *agent;
211
282
  struct libev_io watcher;
212
283
  rb_io_t *fptr;
213
- int len = NUM2INT(length);
284
+ long len = NUM2INT(length);
214
285
  int shrinkable = io_setstrbuf(&str, len);
215
286
  char *buf = RSTRING_PTR(str);
216
287
  long total = 0;
@@ -228,32 +299,25 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
228
299
  OBJ_TAINT(str);
229
300
 
230
301
  while (len > 0) {
231
- int n = read(fptr->fd, buf, len);
232
- if (n == 0)
233
- break;
234
- if (n > 0) {
302
+ ssize_t n = read(fptr->fd, buf, len);
303
+ if (n < 0) {
304
+ int e = errno;
305
+ if (e != EWOULDBLOCK && e != EAGAIN) rb_syserr_fail(e, strerror(e));
306
+
307
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
308
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
309
+ }
310
+ else {
311
+ switchpoint_result = libev_snooze();
312
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
313
+
314
+ if (n == 0) break; // EOF
315
+
235
316
  total = total + n;
236
317
  buf += n;
237
318
  len -= n;
238
319
  if (!read_to_eof || (len == 0)) break;
239
320
  }
240
- else {
241
- int e = errno;
242
- if ((e == EWOULDBLOCK || e == EAGAIN)) {
243
- if (watcher.fiber == Qnil) {
244
- watcher.fiber = rb_fiber_current();
245
- ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
246
- }
247
- ev_io_start(agent->ev_loop, &watcher.io);
248
- switchpoint_result = Polyphony_switchpoint();
249
- ev_io_stop(agent->ev_loop, &watcher.io);
250
- if (TEST_EXCEPTION(switchpoint_result))
251
- goto error;
252
- }
253
- else
254
- rb_syserr_fail(e, strerror(e));
255
- // rb_syserr_fail_path(e, fptr->pathv);
256
- }
257
321
  }
258
322
 
259
323
  if (total == 0) return Qnil;
@@ -269,6 +333,78 @@ error:
269
333
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
270
334
  }
271
335
 
336
+ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
337
+
338
+ #define PREPARE_STR() { \
339
+ str = Qnil; \
340
+ shrinkable = io_setstrbuf(&str, len); \
341
+ buf = RSTRING_PTR(str); \
342
+ total = 0; \
343
+ }
344
+
345
+ #define YIELD_STR() { \
346
+ io_set_read_length(str, total, shrinkable); \
347
+ io_enc_str(str, fptr); \
348
+ rb_yield(str); \
349
+ PREPARE_STR(); \
350
+ }
351
+
352
+ struct LibevAgent_t *agent;
353
+ struct libev_io watcher;
354
+ rb_io_t *fptr;
355
+ VALUE str;
356
+ long total;
357
+ long len = 8192;
358
+ int shrinkable;
359
+ char *buf;
360
+ VALUE switchpoint_result = Qnil;
361
+ VALUE underlying_io = rb_iv_get(io, "@io");
362
+
363
+ PREPARE_STR();
364
+
365
+ GetLibevAgent(self, agent);
366
+ if (underlying_io != Qnil) io = underlying_io;
367
+ GetOpenFile(io, fptr);
368
+ rb_io_check_byte_readable(fptr);
369
+ rb_io_set_nonblock(fptr);
370
+ watcher.fiber = Qnil;
371
+
372
+ OBJ_TAINT(str);
373
+
374
+ while (1) {
375
+ ssize_t n = read(fptr->fd, buf, len);
376
+ if (n < 0) {
377
+ int e = errno;
378
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
379
+
380
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
381
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
382
+ }
383
+ else {
384
+ switchpoint_result = libev_snooze();
385
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
386
+
387
+ if (n == 0) break; // EOF
388
+
389
+ total = n;
390
+ YIELD_STR();
391
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
392
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
393
+ if (TEST_EXCEPTION(switchpoint_result)) {
394
+ goto error;
395
+ }
396
+ }
397
+ }
398
+
399
+ RB_GC_GUARD(str);
400
+ RB_GC_GUARD(watcher.fiber);
401
+ RB_GC_GUARD(switchpoint_result);
402
+
403
+ return io;
404
+ error:
405
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
406
+ }
407
+
272
408
  VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
273
409
  struct LibevAgent_t *agent;
274
410
  struct libev_io watcher;
@@ -276,8 +412,8 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
276
412
  VALUE switchpoint_result = Qnil;
277
413
 
278
414
  char *buf = StringValuePtr(str);
279
- int len = RSTRING_LEN(str);
280
- int left = len;
415
+ long len = RSTRING_LEN(str);
416
+ long left = len;
281
417
 
282
418
  VALUE underlying_io = rb_iv_get(io, "@io");
283
419
  if (underlying_io != Qnil) io = underlying_io;
@@ -287,27 +423,20 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
287
423
  watcher.fiber = Qnil;
288
424
 
289
425
  while (left > 0) {
290
- int result = write(fptr->fd, buf, left);
291
- if (result < 0) {
292
- if (errno == EAGAIN) {
293
- if (watcher.fiber == Qnil) {
294
- watcher.fiber = rb_fiber_current();
295
- ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_WRITE);
296
- }
297
- ev_io_start(agent->ev_loop, &watcher.io);
298
- switchpoint_result = Polyphony_switchpoint();
299
- ev_io_stop(agent->ev_loop, &watcher.io);
300
- if (TEST_EXCEPTION(switchpoint_result))
301
- goto error;
302
- }
303
- else {
304
- // report error
305
-
306
- }
426
+ ssize_t n = write(fptr->fd, buf, left);
427
+ if (n < 0) {
428
+ int e = errno;
429
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
430
+
431
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
432
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
307
433
  }
308
434
  else {
309
- buf += result;
310
- left -= result;
435
+ switchpoint_result = libev_snooze();
436
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
437
+
438
+ buf += n;
439
+ left -= n;
311
440
  }
312
441
  }
313
442
 
@@ -321,16 +450,6 @@ error:
321
450
 
322
451
  ///////////////////////////////////////////////////////////////////////////
323
452
 
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
453
  VALUE LibevAgent_accept(VALUE self, VALUE sock) {
335
454
  struct LibevAgent_t *agent;
336
455
  struct libev_io watcher;
@@ -339,6 +458,8 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
339
458
  struct sockaddr addr;
340
459
  socklen_t len = (socklen_t)sizeof addr;
341
460
  VALUE switchpoint_result = Qnil;
461
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
462
+ if (underlying_sock != Qnil) sock = underlying_sock;
342
463
 
343
464
  GetLibevAgent(self, agent);
344
465
  GetOpenFile(sock, fptr);
@@ -348,43 +469,138 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
348
469
  fd = accept(fptr->fd, &addr, &len);
349
470
  if (fd < 0) {
350
471
  int e = errno;
351
- if (e == EWOULDBLOCK || e == EAGAIN) {
352
- if (watcher.fiber == Qnil) {
353
- watcher.fiber = rb_fiber_current();
354
- ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
355
- }
356
- ev_io_start(agent->ev_loop, &watcher.io);
357
- switchpoint_result = Polyphony_switchpoint();
358
- ev_io_stop(agent->ev_loop, &watcher.io);
359
-
360
- TEST_RESUME_EXCEPTION(switchpoint_result);
361
- RB_GC_GUARD(watcher.fiber);
362
- RB_GC_GUARD(switchpoint_result);
363
- }
364
- else
365
- rb_syserr_fail(e, strerror(e));
366
- // rb_syserr_fail_path(e, fptr->pathv);
472
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
473
+
474
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
475
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
367
476
  }
368
477
  else {
369
- VALUE connection = rb_obj_alloc(cTCPSocket);
478
+ VALUE socket;
370
479
  rb_io_t *fp;
371
- MakeOpenFile(connection, fp);
480
+ switchpoint_result = libev_snooze();
481
+ if (TEST_EXCEPTION(switchpoint_result)) {
482
+ close(fd); // close fd since we're raising an exception
483
+ goto error;
484
+ }
485
+
486
+ socket = rb_obj_alloc(cTCPSocket);
487
+ MakeOpenFile(socket, fp);
372
488
  rb_update_max_fd(fd);
373
489
  fp->fd = fd;
374
490
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
375
- rb_io_ascii8bit_binmode(connection);
491
+ rb_io_ascii8bit_binmode(socket);
376
492
  rb_io_set_nonblock(fp);
377
493
  rb_io_synchronized(fp);
494
+
378
495
  // if (rsock_do_not_reverse_lookup) {
379
496
  // fp->mode |= FMODE_NOREVLOOKUP;
380
497
  // }
498
+ return socket;
499
+ }
500
+ }
501
+ RB_GC_GUARD(switchpoint_result);
502
+ return Qnil;
503
+ error:
504
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
505
+ }
381
506
 
382
- return connection;
507
+ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
508
+ struct LibevAgent_t *agent;
509
+ struct libev_io watcher;
510
+ rb_io_t *fptr;
511
+ int fd;
512
+ struct sockaddr addr;
513
+ socklen_t len = (socklen_t)sizeof addr;
514
+ VALUE switchpoint_result = Qnil;
515
+ VALUE socket = Qnil;
516
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
517
+ if (underlying_sock != Qnil) sock = underlying_sock;
518
+
519
+ GetLibevAgent(self, agent);
520
+ GetOpenFile(sock, fptr);
521
+ rb_io_set_nonblock(fptr);
522
+ watcher.fiber = Qnil;
523
+
524
+ while (1) {
525
+ fd = accept(fptr->fd, &addr, &len);
526
+ if (fd < 0) {
527
+ int e = errno;
528
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
529
+
530
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
531
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
532
+ }
533
+ else {
534
+ rb_io_t *fp;
535
+ switchpoint_result = libev_snooze();
536
+ if (TEST_EXCEPTION(switchpoint_result)) {
537
+ close(fd); // close fd since we're raising an exception
538
+ goto error;
539
+ }
540
+
541
+ socket = rb_obj_alloc(cTCPSocket);
542
+ MakeOpenFile(socket, fp);
543
+ rb_update_max_fd(fd);
544
+ fp->fd = fd;
545
+ fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
546
+ rb_io_ascii8bit_binmode(socket);
547
+ rb_io_set_nonblock(fp);
548
+ rb_io_synchronized(fp);
549
+
550
+ rb_yield(socket);
551
+ socket = Qnil;
383
552
  }
384
553
  }
554
+
555
+ RB_GC_GUARD(socket);
556
+ RB_GC_GUARD(watcher.fiber);
557
+ RB_GC_GUARD(switchpoint_result);
385
558
  return Qnil;
559
+ error:
560
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
386
561
  }
387
562
 
563
+ // VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
564
+ // struct LibevAgent_t *agent;
565
+ // struct libev_io watcher;
566
+ // rb_io_t *fptr;
567
+ // struct sockaddr_in addr;
568
+ // char *host_buf = StringValueCStr(host);
569
+ // VALUE switchpoint_result = Qnil;
570
+ // VALUE underlying_sock = rb_iv_get(sock, "@io");
571
+ // if (underlying_sock != Qnil) sock = underlying_sock;
572
+
573
+ // GetLibevAgent(self, agent);
574
+ // GetOpenFile(sock, fptr);
575
+ // rb_io_set_nonblock(fptr);
576
+ // watcher.fiber = Qnil;
577
+
578
+ // addr.sin_family = AF_INET;
579
+ // addr.sin_addr.s_addr = inet_addr(host_buf);
580
+ // addr.sin_port = htons(NUM2INT(port));
581
+
582
+ // while (1) {
583
+ // int result = connect(fptr->fd, &addr, sizeof(addr));
584
+ // if (result < 0) {
585
+ // int e = errno;
586
+ // if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
587
+
588
+ // switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
589
+ // if (TEST_EXCEPTION(switchpoint_result)) goto error;
590
+ // }
591
+ // else {
592
+ // switchpoint_result = libev_snooze();
593
+ // if (TEST_EXCEPTION(switchpoint_result)) goto error;
594
+
595
+ // return sock;
596
+ // }
597
+ // }
598
+ // RB_GC_GUARD(switchpoint_result);
599
+ // return Qnil;
600
+ // error:
601
+ // return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
602
+ // }
603
+
388
604
  VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
389
605
  struct LibevAgent_t *agent;
390
606
  struct libev_io watcher;
@@ -400,7 +616,7 @@ VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
400
616
  watcher.fiber = rb_fiber_current();
401
617
  ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
402
618
  ev_io_start(agent->ev_loop, &watcher.io);
403
- switchpoint_result = Polyphony_switchpoint();
619
+ switchpoint_result = libev_await(agent);
404
620
  ev_io_stop(agent->ev_loop, &watcher.io);
405
621
 
406
622
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -414,7 +630,7 @@ struct libev_timer {
414
630
  VALUE fiber;
415
631
  };
416
632
 
417
- static void LibevAgent_timer_callback(EV_P_ ev_timer *w, int revents)
633
+ void LibevAgent_timer_callback(EV_P_ ev_timer *w, int revents)
418
634
  {
419
635
  struct libev_timer *watcher = (struct libev_timer *)w;
420
636
  Fiber_make_runnable(watcher->fiber, Qnil);
@@ -430,7 +646,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
430
646
  ev_timer_init(&watcher.timer, LibevAgent_timer_callback, NUM2DBL(duration), 0.);
431
647
  ev_timer_start(agent->ev_loop, &watcher.timer);
432
648
 
433
- switchpoint_result = Polyphony_switchpoint();
649
+ switchpoint_result = libev_await(agent);
434
650
  ev_timer_stop(agent->ev_loop, &watcher.timer);
435
651
 
436
652
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -444,7 +660,7 @@ struct libev_child {
444
660
  VALUE fiber;
445
661
  };
446
662
 
447
- static void LibevAgent_child_callback(EV_P_ ev_child *w, int revents)
663
+ void LibevAgent_child_callback(EV_P_ ev_child *w, int revents)
448
664
  {
449
665
  struct libev_child *watcher = (struct libev_child *)w;
450
666
  int exit_status = w->rstatus >> 8; // weird, why should we do this?
@@ -464,7 +680,7 @@ VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
464
680
  ev_child_init(&watcher.child, LibevAgent_child_callback, NUM2INT(pid), 0);
465
681
  ev_child_start(agent->ev_loop, &watcher.child);
466
682
 
467
- switchpoint_result = Polyphony_switchpoint();
683
+ switchpoint_result = libev_await(agent);
468
684
  ev_child_stop(agent->ev_loop, &watcher.child);
469
685
 
470
686
  TEST_RESUME_EXCEPTION(switchpoint_result);
@@ -491,12 +707,18 @@ void Init_LibevAgent() {
491
707
  rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
492
708
  rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
493
709
 
710
+ rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
711
+ rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
712
+
494
713
  rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
495
714
  rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
496
715
 
497
716
  rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
717
+ rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
498
718
  rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
499
719
  rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
720
+ rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
721
+ // rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
500
722
  rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
501
723
  rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
502
724
  rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);