polyphony 0.16 → 0.17

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +11 -11
  5. data/TODO.md +14 -5
  6. data/examples/core/channel_echo.rb +3 -3
  7. data/examples/core/enumerator.rb +1 -1
  8. data/examples/core/fork.rb +1 -1
  9. data/examples/core/genserver.rb +1 -1
  10. data/examples/core/lock.rb +3 -3
  11. data/examples/core/multiple_spawn.rb +2 -2
  12. data/examples/core/nested_async.rb +1 -1
  13. data/examples/core/nested_multiple_spawn.rb +3 -3
  14. data/examples/core/resource.rb +1 -1
  15. data/examples/core/resource_cancel.rb +1 -1
  16. data/examples/core/resource_delegate.rb +1 -1
  17. data/examples/core/sleep_spawn.rb +2 -2
  18. data/examples/core/spawn.rb +1 -1
  19. data/examples/core/spawn_cancel.rb +1 -1
  20. data/examples/core/spawn_error.rb +5 -5
  21. data/examples/core/supervisor.rb +4 -4
  22. data/examples/core/supervisor_with_cancel_scope.rb +3 -3
  23. data/examples/core/supervisor_with_error.rb +4 -4
  24. data/examples/core/supervisor_with_manual_move_on.rb +4 -4
  25. data/examples/core/thread.rb +2 -2
  26. data/examples/core/thread_cancel.rb +2 -2
  27. data/examples/core/thread_pool.rb +2 -2
  28. data/examples/core/throttle.rb +3 -3
  29. data/examples/fs/read.rb +1 -1
  30. data/examples/http/happy_eyeballs.rb +1 -1
  31. data/examples/http/http_client.rb +1 -1
  32. data/examples/http/http_server.rb +1 -1
  33. data/examples/http/http_server_throttled.rb +1 -1
  34. data/examples/http/http_ws_server.rb +2 -2
  35. data/examples/http/https_wss_server.rb +1 -1
  36. data/examples/interfaces/pg_client.rb +1 -1
  37. data/examples/interfaces/pg_pool.rb +1 -1
  38. data/examples/interfaces/redis_channels.rb +5 -5
  39. data/examples/interfaces/redis_pubsub.rb +2 -2
  40. data/examples/interfaces/redis_pubsub_perf.rb +3 -3
  41. data/examples/io/cat.rb +13 -0
  42. data/examples/io/echo_client.rb +2 -2
  43. data/examples/io/echo_server.rb +1 -1
  44. data/examples/io/echo_server_with_timeout.rb +1 -1
  45. data/examples/io/echo_stdin.rb +1 -1
  46. data/examples/io/io_read.rb +9 -0
  47. data/examples/io/system.rb +11 -0
  48. data/examples/performance/perf_multi_snooze.rb +2 -2
  49. data/examples/performance/perf_snooze.rb +2 -2
  50. data/examples/performance/thread-vs-fiber/polyphony_server.rb +2 -2
  51. data/ext/ev/io.c +53 -4
  52. data/lib/polyphony/core/coprocess.rb +1 -0
  53. data/lib/polyphony/core/supervisor.rb +1 -1
  54. data/lib/polyphony/extensions/io.rb +97 -17
  55. data/lib/polyphony/extensions/kernel.rb +47 -27
  56. data/lib/polyphony/http/server.rb +1 -1
  57. data/lib/polyphony/postgres.rb +0 -4
  58. data/lib/polyphony/version.rb +1 -1
  59. data/test/test_coprocess.rb +13 -13
  60. data/test/test_core.rb +12 -12
  61. data/test/test_io.rb +95 -3
  62. data/test/test_kernel.rb +26 -0
  63. metadata +6 -2
@@ -37,7 +37,7 @@ def compare_performance
37
37
  t0 = Time.now
38
38
  supervise do |s|
39
39
  X.times do
40
- s.spawn Polyphony::ThreadPool.process { lengthy_op }
40
+ s.coproc Polyphony::ThreadPool.process { lengthy_op }
41
41
  end
42
42
  end
43
43
  thread_pool_perf = X / (Time.now - t0)
@@ -54,4 +54,4 @@ rescue Exception => e
54
54
  end
55
55
  end
56
56
 
57
- spawn { compare_performance }
57
+ coproc { compare_performance }
@@ -3,14 +3,14 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
 
6
- spawn {
6
+ coproc {
7
7
  throttled_loop(3) { STDOUT << '.' }
8
8
  }
9
9
 
10
- spawn {
10
+ coproc {
11
11
  throttled_loop(rate: 2) { STDOUT << '?' }
12
12
  }
13
13
 
14
- spawn {
14
+ coproc {
15
15
  throttled_loop(interval: 1) { STDOUT << '*' }
16
16
  }
data/examples/fs/read.rb CHANGED
@@ -26,7 +26,7 @@ def thread_pool_read_file(x, y)
26
26
  t0 = Time.now
27
27
  supervise do |s|
28
28
  y.times {
29
- s.spawn { x.times { IO.read(PATH) } }
29
+ s.coproc { x.times { IO.read(PATH) } }
30
30
  }
31
31
  end
32
32
  puts "thread_pool_read_file: #{Time.now - t0}"
@@ -20,7 +20,7 @@ def happy_eyeballs(hostname, port, max_wait_time: 0.025)
20
20
  success = supervise do |supervisor|
21
21
  targets.each_with_index do |t, idx|
22
22
  sleep(max_wait_time) if idx > 0
23
- supervisor.spawn try_connect(t, supervisor)
23
+ supervisor.coproc try_connect(t, supervisor)
24
24
  end
25
25
  end
26
26
  if success
@@ -11,7 +11,7 @@ X = 10
11
11
  puts "Making #{X} requests..."
12
12
  t0 = Time.now
13
13
  supervise do |s|
14
- X.times { s.spawn { get_server_time } }
14
+ X.times { s.coproc { get_server_time } }
15
15
  end
16
16
  elapsed = Time.now - t0
17
17
  puts "count: #{X} elapsed: #{elapsed} rate: #{X / elapsed} reqs/s"
@@ -4,7 +4,7 @@ require 'bundler/setup'
4
4
  require 'polyphony/http'
5
5
 
6
6
  opts = { reuse_addr: true, dont_linger: true }
7
- spawn {
7
+ coproc {
8
8
  Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
9
9
  req.respond("Hello world!\n")
10
10
  end
@@ -5,7 +5,7 @@ require 'polyphony/http'
5
5
 
6
6
  $throttler = throttle(1000)
7
7
  opts = { reuse_addr: true, dont_linger: true }
8
- spawn {
8
+ coproc {
9
9
  Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
10
10
  $throttler.call { req.respond("Hello world!\n") }
11
11
  end
@@ -7,7 +7,7 @@ require 'polyphony/http'
7
7
  require 'polyphony/websocket'
8
8
 
9
9
  def ws_handler(conn)
10
- timer = spawn {
10
+ timer = coproc {
11
11
  throttled_loop(1) {
12
12
  conn << Time.now.to_s
13
13
  }
@@ -29,7 +29,7 @@ opts = {
29
29
 
30
30
  HTML = IO.read(File.join(__dir__, 'ws_page.html'))
31
31
 
32
- spawn {
32
+ coproc {
33
33
  server = Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
34
34
  req.respond(HTML, 'Content-Type' => 'text/html')
35
35
  end
@@ -7,7 +7,7 @@ require 'localhost/authority'
7
7
  STDOUT.sync = true
8
8
 
9
9
  def ws_handler(conn)
10
- timer = spawn {
10
+ timer = coproc {
11
11
  throttled_loop(1) {
12
12
  conn << Time.now.to_s rescue nil
13
13
  }
@@ -11,7 +11,7 @@ rescue => e
11
11
  puts e.backtrace.join("\n")
12
12
  end
13
13
 
14
- time_printer = spawn do
14
+ time_printer = coproc do
15
15
  last = Time.now
16
16
  throttled_loop(10) do
17
17
  now = Time.now
@@ -28,7 +28,7 @@ DBPOOL.preheat!
28
28
  t0 = Time.now
29
29
  count = 0
30
30
  coprocs = CONCURRENCY.times.map {
31
- spawn { loop { DBPOOL.acquire { |db| get_records(db); count += 1 } } }
31
+ coproc { loop { DBPOOL.acquire { |db| get_records(db); count += 1 } } }
32
32
  }
33
33
  sleep 5
34
34
  puts "count: #{count} query rate: #{count / (Time.now - t0)} queries/s"
@@ -16,7 +16,7 @@ class RedisChannel < Polyphony::Channel
16
16
 
17
17
  def self.start_monitor
18
18
  @channels = {}
19
- @monitor = spawn do
19
+ @monitor = coproc do
20
20
  subscribe_connection.subscribe(CHANNEL_MASTER_TOPIC) do |on|
21
21
  on.message do |topic, message|
22
22
  message = Marshal.load(message)
@@ -47,7 +47,7 @@ class RedisChannel < Polyphony::Channel
47
47
 
48
48
  def self.watch(channel)
49
49
  @channels[channel.topic] = channel
50
- spawn do
50
+ coproc do
51
51
  publish_connection.publish(CHANNEL_MASTER_TOPIC, Marshal.dump({
52
52
  kind: :subscribe,
53
53
  topic: channel.topic
@@ -57,7 +57,7 @@ class RedisChannel < Polyphony::Channel
57
57
 
58
58
  def self.unwatch(channel)
59
59
  @channels.delete(channel.topic)
60
- spawn do
60
+ coproc do
61
61
  publish_connection.publish(CHANNEL_MASTER_TOPIC, Marshal.dump({
62
62
  kind: :unsubscribe,
63
63
  topic: channel.topic
@@ -99,14 +99,14 @@ end
99
99
  RedisChannel.start_monitor
100
100
  channel = RedisChannel.new('channel1')
101
101
 
102
- spawn do
102
+ coproc do
103
103
  loop do
104
104
  message = channel.receive
105
105
  puts "got message: #{message}"
106
106
  end
107
107
  end
108
108
 
109
- spawn do
109
+ coproc do
110
110
  move_on_after(3) do
111
111
  throttled_loop(1) do
112
112
  channel << Time.now
@@ -3,7 +3,7 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony/redis'
5
5
 
6
- spawn do
6
+ coproc do
7
7
  redis = Redis.new
8
8
  redis.subscribe('redis-channel') do |on|
9
9
  on.message do |channel, message|
@@ -13,7 +13,7 @@ spawn do
13
13
  end
14
14
  end
15
15
 
16
- spawn do
16
+ coproc do
17
17
  redis = Redis.new
18
18
  move_on_after(3) do
19
19
  throttled_loop(1) do
@@ -17,7 +17,7 @@ X_SESSIONS.times do
17
17
  }
18
18
  end
19
19
 
20
- spawn do
20
+ coproc do
21
21
  redis = Redis.new
22
22
  redis.subscribe('events') do |on|
23
23
  on.message do |_, message|
@@ -40,14 +40,14 @@ def distribute_event(event)
40
40
  # puts "elapsed: #{elapsed} (#{rate}/s)" if $update_count % 100 == 0
41
41
  end
42
42
 
43
- spawn do
43
+ coproc do
44
44
  redis = Redis.new
45
45
  throttled_loop(1000) do
46
46
  redis.publish('events', {path: "node#{rand(X_NODES)}"}.to_json)
47
47
  end
48
48
  end
49
49
 
50
- spawn do
50
+ coproc do
51
51
  last_count = 0
52
52
  last_stamp = Time.now
53
53
  throttled_loop(1) do
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ f = File.open(__FILE__, 'r') do |f|
7
+ line_number = 1
8
+ while (l = f.gets)
9
+ puts "encoding: #{l.encoding.inspect}"
10
+ STDOUT.puts '%03d %s' % [line_number, l]
11
+ line_number += 1
12
+ end
13
+ end
@@ -5,11 +5,11 @@ require 'polyphony'
5
5
 
6
6
  socket = Polyphony::Net.tcp_connect('127.0.0.1', 1234)
7
7
 
8
- writer = spawn do
8
+ writer = coproc do
9
9
  throttled_loop(1) { socket << "#{Time.now}\n" rescue nil }
10
10
  end
11
11
 
12
- reader = spawn do
12
+ reader = coproc do
13
13
  puts "received from echo server:"
14
14
  while data = socket.readpartial(8192)
15
15
  STDOUT << data
@@ -6,7 +6,7 @@ require 'polyphony'
6
6
  server = TCPServer.open(1234)
7
7
  puts "Echoing on port 1234..."
8
8
  while client = server.accept
9
- spawn do
9
+ coproc do
10
10
  while data = client.readpartial(8192) rescue nil
11
11
  client.write("you said: ", data.chomp, "!\n")
12
12
  end
@@ -9,7 +9,7 @@ begin
9
9
 
10
10
  loop do
11
11
  client = server.accept
12
- spawn do
12
+ coproc do
13
13
  cancel_scope = nil
14
14
  move_on_after(5) do |s|
15
15
  cancel_scope = s
@@ -6,7 +6,7 @@ require 'polyphony'
6
6
  puts "Write something..."
7
7
  move_on_after(5) do |scope|
8
8
  loop do
9
- data = STDIN.readpartial(8192)
9
+ data = STDIN.gets
10
10
  scope.reset_timeout
11
11
  puts "you wrote: #{data}"
12
12
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ s = IO.read(__FILE__)
7
+ puts "encoding: #{s.encoding.inspect}"
8
+ puts s
9
+ puts
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ timer = coproc {
7
+ throttled_loop(5) { STDOUT << '.' }
8
+ }
9
+
10
+ puts system('ruby -e "sleep 1; puts :done; STDOUT.close"')
11
+ timer.stop
@@ -6,12 +6,12 @@ require 'polyphony'
6
6
  ITERATIONS = 1_000
7
7
  FIBERS = 1_000
8
8
 
9
- spawn do
9
+ coproc do
10
10
  count = 0
11
11
  t0 = Time.now
12
12
  supervise do |s|
13
13
  FIBERS.times do
14
- s.spawn do
14
+ s.coproc do
15
15
  ITERATIONS.times { snooze; count += 1 }
16
16
  end
17
17
  end
@@ -14,7 +14,7 @@ X = 1_000_000
14
14
  # puts "#{X / dt.to_f}/s"
15
15
 
16
16
  # sleep
17
- # spawn do
17
+ # coproc do
18
18
  # t0 = Time.now
19
19
  # X.times { sleep(0) }
20
20
  # dt = Time.now - t0
@@ -22,7 +22,7 @@ X = 1_000_000
22
22
  # end
23
23
 
24
24
  # snooze
25
- spawn do
25
+ coproc do
26
26
  t0 = Time.now
27
27
  X.times { snooze }
28
28
  dt = Time.now - t0
@@ -46,13 +46,13 @@ def handle_request(client, parser)
46
46
  client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
47
47
  end
48
48
 
49
- spawn do
49
+ coproc do
50
50
  server = TCPServer.open(1234)
51
51
  puts "listening on port 1234"
52
52
 
53
53
  loop do
54
54
  client = server.accept
55
- spawn handle_client(client)
55
+ coproc handle_client(client)
56
56
  end
57
57
  rescue Exception => e
58
58
  puts "uncaught exception: #{e.inspect}"
data/ext/ev/io.c CHANGED
@@ -32,11 +32,15 @@ void EV_IO_callback(ev_loop *ev_loop, struct ev_io *io, int revents);
32
32
 
33
33
  static int EV_IO_symbol2event_mask(VALUE sym);
34
34
 
35
+ // static VALUE IO_gets(int argc, VALUE *argv, VALUE io);
35
36
  static VALUE IO_read(int argc, VALUE *argv, VALUE io);
36
37
  static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io);
37
38
  static VALUE IO_write(int argc, VALUE *argv, VALUE io);
38
39
  static VALUE IO_write_chevron(VALUE io, VALUE str);
39
40
 
41
+ static VALUE IO_read_watcher(VALUE self);
42
+ static VALUE IO_write_watcher(VALUE self);
43
+
40
44
  void Init_EV_IO() {
41
45
  mEV = rb_define_module("EV");
42
46
  cEV_IO = rb_define_class_under(mEV, "IO", rb_cData);
@@ -48,11 +52,14 @@ void Init_EV_IO() {
48
52
  rb_define_method(cEV_IO, "await", EV_IO_await, 0);
49
53
 
50
54
  VALUE cIO = rb_const_get(rb_cObject, rb_intern("IO"));
55
+ // rb_define_method(cIO, "gets", IO_gets, -1);
51
56
  rb_define_method(cIO, "read", IO_read, -1);
52
57
  rb_define_method(cIO, "readpartial", IO_readpartial, -1);
53
58
  rb_define_method(cIO, "write", IO_write, -1);
54
59
  rb_define_method(cIO, "write_nonblock", IO_write, -1);
55
60
  rb_define_method(cIO, "<<", IO_write_chevron, 1);
61
+ rb_define_method(cIO, "read_watcher", IO_read_watcher, 0);
62
+ rb_define_method(cIO, "write_watcher", IO_write_watcher, 0);
56
63
  }
57
64
 
58
65
  static const rb_data_type_t EV_IO_type = {
@@ -249,6 +256,23 @@ static void io_set_read_length(VALUE str, long n, int shrinkable) {
249
256
  if (shrinkable) io_shrink_read_string(str, n);
250
257
  }
251
258
  }
259
+
260
+ static rb_encoding*
261
+ io_read_encoding(rb_io_t *fptr)
262
+ {
263
+ if (fptr->encs.enc) {
264
+ return fptr->encs.enc;
265
+ }
266
+ return rb_default_external_encoding();
267
+ }
268
+
269
+ static VALUE io_enc_str(VALUE str, rb_io_t *fptr)
270
+ {
271
+ OBJ_TAINT(str);
272
+ rb_enc_associate(str, io_read_encoding(fptr));
273
+ return str;
274
+ }
275
+
252
276
  //////////////////////////////////////////////////////////////////////
253
277
  //////////////////////////////////////////////////////////////////////
254
278
 
@@ -267,9 +291,10 @@ static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
267
291
  VALUE str = argc >= 2 ? argv[1] : Qnil;
268
292
 
269
293
  shrinkable = io_setstrbuf(&str, len);
270
- OBJ_TAINT(str);
294
+ // OBJ_TAINT(str);
271
295
  GetOpenFile(io, fptr);
272
296
  rb_io_check_byte_readable(fptr);
297
+ rb_io_set_nonblock(fptr);
273
298
 
274
299
  if (len == 0)
275
300
  return str;
@@ -283,6 +308,7 @@ static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
283
308
  int e = errno;
284
309
  if ((e == EWOULDBLOCK || e == EAGAIN)) {
285
310
  if (read_watcher == Qnil)
311
+ // read_watcher = IO_read_watcher(io);
286
312
  read_watcher = rb_funcall(io, ID_read_watcher, 0);
287
313
  EV_IO_await(read_watcher);
288
314
  }
@@ -301,11 +327,12 @@ static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
301
327
  }
302
328
  }
303
329
 
304
- io_set_read_length(str, total, shrinkable);
305
-
306
330
  if (total == 0)
307
331
  return Qnil;
308
332
 
333
+ io_set_read_length(str, total, shrinkable);
334
+ io_enc_str(str, fptr);
335
+
309
336
  return str;
310
337
  }
311
338
 
@@ -326,6 +353,7 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
326
353
  shrinkable = io_setstrbuf(&str, len);
327
354
  OBJ_TAINT(str);
328
355
  GetOpenFile(io, fptr);
356
+ rb_io_set_nonblock(fptr);
329
357
  rb_io_check_byte_readable(fptr);
330
358
 
331
359
  if (len == 0)
@@ -337,6 +365,7 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
337
365
  int e = errno;
338
366
  if ((e == EWOULDBLOCK || e == EAGAIN)) {
339
367
  if (read_watcher == Qnil)
368
+ // read_watcher = IO_read_watcher(io);
340
369
  read_watcher = rb_funcall(io, ID_read_watcher, 0);
341
370
  EV_IO_await(read_watcher);
342
371
  }
@@ -349,6 +378,7 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
349
378
  }
350
379
 
351
380
  io_set_read_length(str, n, shrinkable);
381
+ io_enc_str(str, fptr);
352
382
 
353
383
  if (n == 0)
354
384
  return Qnil;
@@ -383,6 +413,7 @@ static VALUE IO_write(int argc, VALUE *argv, VALUE io) {
383
413
  int e = errno;
384
414
  if (e == EWOULDBLOCK || e == EAGAIN) {
385
415
  if (write_watcher == Qnil)
416
+ // write_watcher = IO_write_watcher(io);
386
417
  write_watcher = rb_funcall(io, ID_write_watcher, 0);
387
418
  EV_IO_await(write_watcher);
388
419
  }
@@ -408,4 +439,22 @@ static VALUE IO_write(int argc, VALUE *argv, VALUE io) {
408
439
  static VALUE IO_write_chevron(VALUE io, VALUE str) {
409
440
  IO_write(1, &str, io);
410
441
  return io;
411
- }
442
+ }
443
+
444
+ static VALUE IO_read_watcher(VALUE self) {
445
+ VALUE watcher = rb_iv_get(self, "@read_watcher");
446
+ if (watcher == Qnil) {
447
+ watcher = rb_funcall(cEV_IO, rb_intern("new"), 2, self, ID2SYM(rb_intern("r")));
448
+ rb_iv_set(self, "@read_watcher", watcher);
449
+ }
450
+ return watcher;
451
+ }
452
+
453
+ static VALUE IO_write_watcher(VALUE self) {
454
+ VALUE watcher = rb_iv_get(self, "@write_watcher");
455
+ if (watcher == Qnil) {
456
+ watcher = rb_funcall(cEV_IO, rb_intern("new"), 2, self, ID2SYM(rb_intern("w")));
457
+ rb_iv_set(self, "@write_watcher", watcher);
458
+ }
459
+ return watcher;
460
+ }