polyphony 0.34 → 0.41

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +11 -2
  3. data/.gitignore +2 -2
  4. data/.rubocop.yml +30 -0
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +0 -11
  7. data/Gemfile.lock +11 -10
  8. data/README.md +2 -1
  9. data/Rakefile +6 -2
  10. data/TODO.md +18 -95
  11. data/docs/_includes/head.html +40 -0
  12. data/docs/_includes/nav.html +5 -5
  13. data/docs/api-reference.md +1 -1
  14. data/docs/api-reference/fiber.md +18 -0
  15. data/docs/api-reference/gyro-async.md +57 -0
  16. data/docs/api-reference/gyro-child.md +29 -0
  17. data/docs/api-reference/gyro-queue.md +44 -0
  18. data/docs/api-reference/gyro-timer.md +51 -0
  19. data/docs/api-reference/gyro.md +25 -0
  20. data/docs/index.md +10 -7
  21. data/docs/main-concepts/design-principles.md +67 -9
  22. data/docs/main-concepts/extending.md +1 -1
  23. data/docs/main-concepts/fiber-scheduling.md +55 -72
  24. data/examples/core/xx-agent.rb +102 -0
  25. data/examples/core/xx-fork-cleanup.rb +22 -0
  26. data/examples/core/xx-sleeping.rb +14 -6
  27. data/examples/core/xx-timer-gc.rb +17 -0
  28. data/examples/io/tunnel.rb +48 -0
  29. data/examples/io/xx-irb.rb +1 -1
  30. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  31. data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
  32. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  33. data/ext/polyphony/fiber.c +112 -0
  34. data/ext/{gyro → polyphony}/libev.c +0 -0
  35. data/ext/{gyro → polyphony}/libev.h +0 -0
  36. data/ext/polyphony/libev_agent.c +503 -0
  37. data/ext/polyphony/libev_queue.c +214 -0
  38. data/ext/polyphony/polyphony.c +89 -0
  39. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +49 -59
  40. data/ext/polyphony/polyphony_ext.c +23 -0
  41. data/ext/{gyro → polyphony}/socket.c +21 -19
  42. data/ext/{gyro → polyphony}/thread.c +55 -119
  43. data/ext/{gyro → polyphony}/tracing.c +1 -1
  44. data/lib/polyphony.rb +37 -44
  45. data/lib/polyphony/adapters/fs.rb +1 -4
  46. data/lib/polyphony/adapters/irb.rb +2 -2
  47. data/lib/polyphony/adapters/postgres.rb +6 -5
  48. data/lib/polyphony/adapters/process.rb +27 -23
  49. data/lib/polyphony/adapters/trace.rb +110 -105
  50. data/lib/polyphony/core/channel.rb +35 -35
  51. data/lib/polyphony/core/exceptions.rb +29 -29
  52. data/lib/polyphony/core/global_api.rb +94 -91
  53. data/lib/polyphony/core/resource_pool.rb +83 -83
  54. data/lib/polyphony/core/sync.rb +16 -16
  55. data/lib/polyphony/core/thread_pool.rb +49 -37
  56. data/lib/polyphony/core/throttler.rb +30 -23
  57. data/lib/polyphony/event.rb +27 -0
  58. data/lib/polyphony/extensions/core.rb +23 -14
  59. data/lib/polyphony/extensions/fiber.rb +269 -267
  60. data/lib/polyphony/extensions/io.rb +56 -26
  61. data/lib/polyphony/extensions/openssl.rb +5 -9
  62. data/lib/polyphony/extensions/socket.rb +29 -10
  63. data/lib/polyphony/extensions/thread.rb +19 -12
  64. data/lib/polyphony/net.rb +64 -60
  65. data/lib/polyphony/version.rb +1 -1
  66. data/polyphony.gemspec +3 -6
  67. data/test/helper.rb +14 -1
  68. data/test/stress.rb +17 -12
  69. data/test/test_agent.rb +77 -0
  70. data/test/{test_async.rb → test_event.rb} +17 -9
  71. data/test/test_ext.rb +25 -4
  72. data/test/test_fiber.rb +23 -14
  73. data/test/test_global_api.rb +5 -5
  74. data/test/test_io.rb +46 -24
  75. data/test/test_queue.rb +74 -0
  76. data/test/test_signal.rb +3 -40
  77. data/test/test_socket.rb +33 -0
  78. data/test/test_thread.rb +38 -16
  79. data/test/test_thread_pool.rb +3 -3
  80. data/test/test_throttler.rb +0 -1
  81. data/test/test_trace.rb +6 -5
  82. metadata +34 -39
  83. data/ext/gyro/async.c +0 -158
  84. data/ext/gyro/child.c +0 -117
  85. data/ext/gyro/gyro.c +0 -203
  86. data/ext/gyro/gyro_ext.c +0 -31
  87. data/ext/gyro/io.c +0 -447
  88. data/ext/gyro/queue.c +0 -142
  89. data/ext/gyro/selector.c +0 -183
  90. data/ext/gyro/signal.c +0 -108
  91. data/ext/gyro/timer.c +0 -154
  92. data/test/test_timer.rb +0 -56
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ class Test
9
+ def test_sleep
10
+ puts "going to sleep"
11
+ sleep 1
12
+ puts "done sleeping"
13
+ end
14
+
15
+ def test_spin
16
+ spin {
17
+ 10.times {
18
+ STDOUT << '.'
19
+ sleep 0.1
20
+ }
21
+ }
22
+
23
+ puts "going to sleep\n"
24
+ sleep 1
25
+ puts 'woke up'
26
+ end
27
+
28
+ def test_file
29
+ f = File.open(__FILE__, 'r')
30
+ puts Thread.current.agent.read(f, +'', 10000, true)
31
+
32
+ Thread.current.agent.write(STDOUT, "Write something: ")
33
+ str = +''
34
+ Thread.current.agent.read(STDIN, str, 5, false)
35
+ puts str
36
+ end
37
+
38
+ def test_fork
39
+ pid = fork do
40
+ Thread.current.agent.post_fork
41
+ puts 'child going to sleep'
42
+ sleep 1
43
+ puts 'child done sleeping'
44
+ exit(42)
45
+ end
46
+
47
+ puts "Waiting for pid #{pid}"
48
+ result = Thread.current.agent.waitpid(pid)
49
+ puts "Done waiting"
50
+ p result
51
+ end
52
+
53
+ def test_async
54
+ async = Polyphony::Event.new
55
+
56
+ spin {
57
+ puts "signaller starting"
58
+ sleep 1
59
+ puts "signal"
60
+ async.signal(:foo)
61
+ }
62
+
63
+ puts "awaiting event"
64
+ p async.await
65
+ end
66
+
67
+ def test_queue
68
+ q = Gyro::Queue.new
69
+ spin {
70
+ 10.times {
71
+ q << Time.now.to_f
72
+ sleep 0.2
73
+ }
74
+ q << :STOP
75
+ }
76
+
77
+ loop do
78
+ value = q.shift
79
+ break if value == :STOP
80
+
81
+ p value
82
+ end
83
+ end
84
+
85
+ def test_thread
86
+ t = Thread.new do
87
+ puts "thread going to sleep"
88
+ sleep 0.2
89
+ puts "thread done sleeping"
90
+ end
91
+
92
+ t.await
93
+ end
94
+ end
95
+
96
+ t = Test.new
97
+
98
+ t.methods.select { |m| m =~ /^test_/ }.each do |m|
99
+ puts '*' * 40
100
+ puts m
101
+ t.send(m)
102
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ t = Thread.new do
9
+ async = Gyro::Async.new
10
+ spin { async.await }
11
+ sleep 100
12
+ end
13
+
14
+ sleep 0.5
15
+
16
+ Polyphony.fork do
17
+ puts "forked #{Process.pid}"
18
+ sleep 1
19
+ puts "done sleeping"
20
+ end
21
+
22
+ sleep 50
@@ -5,13 +5,21 @@ require 'polyphony'
5
5
 
6
6
  Exception.__disable_sanitized_backtrace__ = true
7
7
 
8
- spin {
9
- 10.times {
10
- STDOUT << '.'
11
- sleep 0.1
12
- }
13
- }
8
+ # spin {
9
+ # 10.times {
10
+ # STDOUT << '.'
11
+ # sleep 0.1
12
+ # }
13
+ # }
14
14
 
15
15
  puts 'going to sleep...'
16
16
  sleep 1
17
17
  puts 'woke up'
18
+
19
+ counter = 0
20
+ t = Polyphony::Throttler.new(5)
21
+ t.process do
22
+ p counter
23
+ counter += 1
24
+ t.stop if counter > 5
25
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ timers = 10.times.map do
9
+ spin do
10
+ t = Gyro::Timer.new(1, 1)
11
+ t.await
12
+ end
13
+ end
14
+
15
+ sleep 0.1
16
+ GC.start
17
+ sleep 0.1
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Ports = ARGV[0..1]
7
+ EndPoints = []
8
+
9
+ def log(msg)
10
+ puts "#{Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N')} #{msg}"
11
+ end
12
+
13
+ def endpoint_loop(idx, peer_idx)
14
+ port = Ports[idx]
15
+ server = Polyphony::Net.tcp_listen(
16
+ '0.0.0.0',
17
+ port,
18
+ reuse_addr: true
19
+ )
20
+ # server = TCPServer.open('0.0.0.0', port)
21
+ log "Listening on port #{port}"
22
+ loop do
23
+ conn = server.accept
24
+ conn.binmode
25
+ EndPoints[idx] = conn
26
+ log "Client connected on port #{port} (#{conn.remote_address.inspect})"
27
+ while data = conn.readpartial(8192)
28
+ peer = EndPoints[peer_idx]
29
+ if peer
30
+ peer << data
31
+ log "#{idx} => #{peer_idx} #{data.inspect}"
32
+ else
33
+ log "#{idx}: #{data.inspect}"
34
+ end
35
+ end
36
+ EndPoints[idx] = nil
37
+ log "Connection closed on port #{port}"
38
+ rescue => e
39
+ log "Error on port #{port}: #{e.inspect}"
40
+ end
41
+ end
42
+
43
+ spin { endpoint_loop(0, 1) }
44
+ spin { endpoint_loop(1, 0) }
45
+
46
+ log "Tunneling port #{Ports[0]} to port #{Ports[1]}..."
47
+ sleep
48
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'irb'
5
- require 'polyphony/irb'
5
+ require 'polyphony/adapters/irb'
6
6
 
7
7
  $counter = 0
8
8
  timer = spin do
@@ -20,22 +20,22 @@ end
20
20
 
21
21
  def handle_client(socket)
22
22
  parser = Http::Parser.new
23
- req = nil
23
+ reqs = []
24
24
  parser.on_message_complete = proc do |env|
25
- req = parser
25
+ reqs << Object.new # parser
26
26
  end
27
27
  while (data = socket.readpartial(8192)) do
28
28
  parser << data
29
- if req
29
+ while (req = reqs.shift)
30
30
  handle_request(socket, req)
31
31
  req = nil
32
- snooze
32
+ # snooze
33
33
  end
34
34
  end
35
35
  rescue IOError, SystemCallError => e
36
36
  # do nothing
37
37
  ensure
38
- socket.close rescue nil
38
+ socket&.close
39
39
  parser.reset!
40
40
  end
41
41
 
@@ -46,13 +46,14 @@ 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
- $incoming = Gyro::Queue.new
49
+ $incoming = Polyphony::Queue.new
50
50
 
51
51
  $threads = (1..4).map {
52
52
  Thread.new {
53
53
  Thread.current.setup_fiber_scheduling
54
54
  loop {
55
55
  conn = $incoming.pop
56
+ puts "#{Thread.current.inspect} pop #{conn.inspect}"
56
57
  spin { handle_client(conn) }
57
58
  }
58
59
  }
@@ -4,21 +4,10 @@ require 'bundler/setup'
4
4
  require 'polyphony'
5
5
  require 'http/parser'
6
6
 
7
- class Http::Parser
8
- def setup_async
9
- self.on_message_complete = proc { @request_complete = true }
10
- end
11
-
12
- def parse(data)
13
- self << data
14
- return nil unless @request_complete
15
-
16
- @request_complete = nil
17
- self
18
- end
19
- end
7
+ $connection_count = 0
20
8
 
21
9
  def handle_client(socket)
10
+ $connection_count += 1
22
11
  parser = Http::Parser.new
23
12
  reqs = []
24
13
  parser.on_message_complete = proc do |env|
@@ -29,20 +18,20 @@ def handle_client(socket)
29
18
  while (req = reqs.shift)
30
19
  handle_request(socket, req)
31
20
  req = nil
32
- # snooze
21
+ snooze
33
22
  end
34
23
  end
35
24
  rescue IOError, SystemCallError => e
36
25
  # do nothing
37
26
  ensure
38
- socket.close rescue nil
39
- parser.reset!
27
+ $connection_count -= 1
28
+ socket&.close
40
29
  end
41
30
 
42
31
  def handle_request(client, parser)
43
- status_code = 200
32
+ status_code = "200 OK"
44
33
  data = "Hello world!\n"
45
- headers = "Content-Length: #{data.bytesize}\r\n"
34
+ headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
46
35
  client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
47
36
  end
48
37
 
@@ -53,16 +42,16 @@ spin do
53
42
  loop do
54
43
  client = server.accept
55
44
  spin { handle_client(client) }
56
- # snooze
57
45
  end
46
+ ensure
47
+ server&.close
58
48
  end
59
49
 
60
- spin do
61
- loop do
62
- sleep 1
63
- puts "#{Time.now} #{Thread.current.fiber_scheduling_stats}"
64
- end
65
- end
50
+ # every(1) {
51
+ # stats = Thread.current.fiber_scheduling_stats
52
+ # stats[:connection_count] = $connection_count
53
+ # puts "#{Time.now} #{stats}"
54
+ # }
66
55
 
67
56
  puts "pid #{Process.pid}"
68
57
  suspend
@@ -16,5 +16,5 @@ $defs << "-DHAVE_SYS_RESOURCE_H" if have_header("sys/resource.h")
16
16
 
17
17
  CONFIG["optflags"] << " -fno-strict-aliasing" unless RUBY_PLATFORM =~ /mswin/
18
18
 
19
- dir_config "gyro_ext"
20
- create_makefile "gyro_ext"
19
+ dir_config "polyphony_ext"
20
+ create_makefile "polyphony_ext"
@@ -0,0 +1,112 @@
1
+ #include "polyphony.h"
2
+
3
+ ID ID_fiber_trace;
4
+ ID ID_ivar_auto_watcher;
5
+ ID ID_trace_ev_loop_enter;
6
+ ID ID_trace_ev_loop_leave;
7
+ ID ID_trace_run;
8
+ ID ID_trace_runnable;
9
+ ID ID_trace_terminate;
10
+ ID ID_trace_wait;
11
+
12
+ VALUE cEvent = Qnil;
13
+
14
+ VALUE SYM_dead;
15
+ VALUE SYM_running;
16
+ VALUE SYM_runnable;
17
+ VALUE SYM_waiting;
18
+
19
+ VALUE SYM_fiber_create;
20
+ VALUE SYM_fiber_ev_loop_enter;
21
+ VALUE SYM_fiber_ev_loop_leave;
22
+ VALUE SYM_fiber_run;
23
+ VALUE SYM_fiber_schedule;
24
+ VALUE SYM_fiber_switchpoint;
25
+ VALUE SYM_fiber_terminate;
26
+
27
+ static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
28
+ VALUE arg = (argc == 0) ? Qnil : argv[0];
29
+ VALUE ret = rb_funcall(self, ID_transfer, 1, arg);
30
+
31
+ TEST_RESUME_EXCEPTION(ret);
32
+ RB_GC_GUARD(ret);
33
+ return ret;
34
+ }
35
+
36
+ inline VALUE Fiber_auto_watcher(VALUE self) {
37
+ VALUE watcher;
38
+ if (cEvent == Qnil) {
39
+ cEvent = rb_const_get(mPolyphony, rb_intern("Event"));
40
+ }
41
+
42
+ watcher = rb_ivar_get(self, ID_ivar_auto_watcher);
43
+ if (watcher == Qnil) {
44
+ watcher = rb_funcall(cEvent, ID_new, 0);
45
+ rb_ivar_set(self, ID_ivar_auto_watcher, watcher);
46
+ }
47
+ return watcher;
48
+ }
49
+
50
+ static VALUE Fiber_schedule(int argc, VALUE *argv, VALUE self) {
51
+ VALUE value = (argc == 0) ? Qnil : argv[0];
52
+ Fiber_make_runnable(self, value);
53
+ return self;
54
+ }
55
+
56
+ static VALUE Fiber_state(VALUE self) {
57
+ if (!rb_fiber_alive_p(self) || (rb_ivar_get(self, ID_ivar_running) == Qfalse))
58
+ return SYM_dead;
59
+ if (rb_fiber_current() == self) return SYM_running;
60
+ if (rb_ivar_get(self, ID_runnable) != Qnil) return SYM_runnable;
61
+
62
+ return SYM_waiting;
63
+ }
64
+
65
+ void Fiber_make_runnable(VALUE fiber, VALUE value) {
66
+ VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
67
+ if (thread != Qnil) {
68
+ Thread_schedule_fiber(thread, fiber, value);
69
+ }
70
+ else {
71
+ VALUE caller;
72
+ 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
+ }
76
+ }
77
+
78
+ void Init_Fiber() {
79
+ VALUE cFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
80
+ rb_define_method(cFiber, "safe_transfer", Fiber_safe_transfer, -1);
81
+ rb_define_method(cFiber, "schedule", Fiber_schedule, -1);
82
+ rb_define_method(cFiber, "state", Fiber_state, 0);
83
+ rb_define_method(cFiber, "auto_watcher", Fiber_auto_watcher, 0);
84
+
85
+ SYM_dead = ID2SYM(rb_intern("dead"));
86
+ SYM_running = ID2SYM(rb_intern("running"));
87
+ SYM_runnable = ID2SYM(rb_intern("runnable"));
88
+ SYM_waiting = ID2SYM(rb_intern("waiting"));
89
+ rb_global_variable(&SYM_dead);
90
+ rb_global_variable(&SYM_running);
91
+ rb_global_variable(&SYM_runnable);
92
+ rb_global_variable(&SYM_waiting);
93
+
94
+ ID_fiber_trace = rb_intern("__fiber_trace__");
95
+ ID_ivar_auto_watcher = rb_intern("@auto_watcher");
96
+
97
+ SYM_fiber_create = ID2SYM(rb_intern("fiber_create"));
98
+ SYM_fiber_ev_loop_enter = ID2SYM(rb_intern("fiber_ev_loop_enter"));
99
+ SYM_fiber_ev_loop_leave = ID2SYM(rb_intern("fiber_ev_loop_leave"));
100
+ SYM_fiber_run = ID2SYM(rb_intern("fiber_run"));
101
+ SYM_fiber_schedule = ID2SYM(rb_intern("fiber_schedule"));
102
+ SYM_fiber_switchpoint = ID2SYM(rb_intern("fiber_switchpoint"));
103
+ SYM_fiber_terminate = ID2SYM(rb_intern("fiber_terminate"));
104
+
105
+ rb_global_variable(&SYM_fiber_create);
106
+ rb_global_variable(&SYM_fiber_ev_loop_enter);
107
+ rb_global_variable(&SYM_fiber_ev_loop_leave);
108
+ rb_global_variable(&SYM_fiber_run);
109
+ rb_global_variable(&SYM_fiber_schedule);
110
+ rb_global_variable(&SYM_fiber_switchpoint);
111
+ rb_global_variable(&SYM_fiber_terminate);
112
+ }