polyphony 0.38 → 0.43

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 (112) 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 +25 -2
  6. data/Gemfile.lock +15 -12
  7. data/README.md +2 -1
  8. data/Rakefile +3 -3
  9. data/TODO.md +27 -97
  10. data/docs/_config.yml +56 -7
  11. data/docs/_sass/custom/custom.scss +0 -30
  12. data/docs/_sass/overrides.scss +0 -46
  13. data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
  14. data/docs/_user-guide/index.md +9 -0
  15. data/docs/{user-guide → _user-guide}/web-server.md +0 -0
  16. data/docs/api-reference/fiber.md +2 -2
  17. data/docs/api-reference/index.md +9 -0
  18. data/docs/api-reference/polyphony-process.md +1 -1
  19. data/docs/api-reference/thread.md +1 -1
  20. data/docs/faq.md +21 -11
  21. data/docs/getting-started/index.md +10 -0
  22. data/docs/getting-started/installing.md +2 -6
  23. data/docs/getting-started/overview.md +486 -0
  24. data/docs/getting-started/tutorial.md +27 -19
  25. data/docs/index.md +1 -1
  26. data/docs/main-concepts/concurrency.md +0 -5
  27. data/docs/main-concepts/design-principles.md +69 -21
  28. data/docs/main-concepts/extending.md +1 -1
  29. data/docs/main-concepts/index.md +9 -0
  30. data/examples/core/01-spinning-up-fibers.rb +1 -0
  31. data/examples/core/03-interrupting.rb +4 -1
  32. data/examples/core/04-handling-signals.rb +19 -0
  33. data/examples/core/xx-agent.rb +102 -0
  34. data/examples/core/xx-sleeping.rb +14 -6
  35. data/examples/io/tunnel.rb +48 -0
  36. data/examples/io/xx-irb.rb +1 -1
  37. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  38. data/examples/performance/thread-vs-fiber/polyphony_server.rb +13 -36
  39. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  40. data/examples/performance/xx-array.rb +11 -0
  41. data/examples/performance/xx-fiber-switch.rb +9 -0
  42. data/examples/performance/xx-snooze.rb +15 -0
  43. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  44. data/ext/{gyro → polyphony}/fiber.c +17 -23
  45. data/ext/{gyro → polyphony}/libev.c +0 -0
  46. data/ext/{gyro → polyphony}/libev.h +0 -0
  47. data/ext/polyphony/libev_agent.c +718 -0
  48. data/ext/polyphony/libev_queue.c +216 -0
  49. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -40
  50. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +19 -39
  51. data/ext/polyphony/polyphony_ext.c +23 -0
  52. data/ext/{gyro → polyphony}/socket.c +21 -18
  53. data/ext/polyphony/thread.c +206 -0
  54. data/ext/{gyro → polyphony}/tracing.c +1 -1
  55. data/lib/polyphony.rb +19 -14
  56. data/lib/polyphony/adapters/irb.rb +1 -1
  57. data/lib/polyphony/adapters/postgres.rb +6 -5
  58. data/lib/polyphony/adapters/process.rb +5 -5
  59. data/lib/polyphony/adapters/trace.rb +28 -28
  60. data/lib/polyphony/core/channel.rb +3 -3
  61. data/lib/polyphony/core/exceptions.rb +1 -1
  62. data/lib/polyphony/core/global_api.rb +13 -11
  63. data/lib/polyphony/core/resource_pool.rb +3 -3
  64. data/lib/polyphony/core/sync.rb +2 -2
  65. data/lib/polyphony/core/thread_pool.rb +6 -6
  66. data/lib/polyphony/core/throttler.rb +13 -6
  67. data/lib/polyphony/event.rb +27 -0
  68. data/lib/polyphony/extensions/core.rb +22 -14
  69. data/lib/polyphony/extensions/fiber.rb +4 -4
  70. data/lib/polyphony/extensions/io.rb +59 -25
  71. data/lib/polyphony/extensions/openssl.rb +36 -16
  72. data/lib/polyphony/extensions/socket.rb +27 -9
  73. data/lib/polyphony/extensions/thread.rb +16 -9
  74. data/lib/polyphony/net.rb +9 -9
  75. data/lib/polyphony/version.rb +1 -1
  76. data/polyphony.gemspec +4 -4
  77. data/test/helper.rb +14 -1
  78. data/test/test_agent.rb +124 -0
  79. data/test/{test_async.rb → test_event.rb} +15 -7
  80. data/test/test_ext.rb +25 -4
  81. data/test/test_fiber.rb +19 -10
  82. data/test/test_global_api.rb +4 -4
  83. data/test/test_io.rb +46 -24
  84. data/test/test_queue.rb +74 -0
  85. data/test/test_signal.rb +3 -40
  86. data/test/test_socket.rb +34 -0
  87. data/test/test_thread.rb +37 -16
  88. data/test/test_trace.rb +6 -5
  89. metadata +40 -43
  90. data/docs/_includes/nav.html +0 -51
  91. data/docs/_includes/prevnext.html +0 -17
  92. data/docs/_layouts/default.html +0 -106
  93. data/docs/api-reference.md +0 -11
  94. data/docs/api-reference/gyro-async.md +0 -57
  95. data/docs/api-reference/gyro-child.md +0 -29
  96. data/docs/api-reference/gyro-queue.md +0 -44
  97. data/docs/api-reference/gyro-timer.md +0 -51
  98. data/docs/api-reference/gyro.md +0 -25
  99. data/docs/getting-started.md +0 -10
  100. data/docs/main-concepts.md +0 -10
  101. data/docs/user-guide.md +0 -10
  102. data/examples/core/forever_sleep.rb +0 -19
  103. data/ext/gyro/async.c +0 -162
  104. data/ext/gyro/child.c +0 -141
  105. data/ext/gyro/gyro_ext.c +0 -33
  106. data/ext/gyro/io.c +0 -489
  107. data/ext/gyro/queue.c +0 -142
  108. data/ext/gyro/selector.c +0 -228
  109. data/ext/gyro/signal.c +0 -133
  110. data/ext/gyro/thread.c +0 -308
  111. data/ext/gyro/timer.c +0 -149
  112. data/test/test_timer.rb +0 -56
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'openssl'
4
-
5
4
  require_relative './socket'
6
5
 
7
6
  # Open ssl socket helper methods (to make it compatible with Socket API)
@@ -18,14 +17,36 @@ class ::OpenSSL::SSL::SSLSocket
18
17
  io.reuse_addr
19
18
  end
20
19
 
21
- def sysread(maxlen, buf)
22
- read_watcher = nil
23
- write_watcher = nil
20
+ alias_method :orig_accept, :accept
21
+ def accept
22
+ loop do
23
+ result = accept_nonblock(exception: false)
24
+ case result
25
+ when :wait_readable then Thread.current.agent.wait_io(io, false)
26
+ when :wait_writable then Thread.current.agent.wait_io(io, true)
27
+ else
28
+ return result
29
+ end
30
+ end
31
+ end
32
+
33
+ alias_method :orig_sysread, :sysread
34
+ def sysread(maxlen, buf = +'')
24
35
  loop do
25
36
  case (result = read_nonblock(maxlen, buf, exception: false))
26
- when :wait_readable then (read_watcher ||= io.read_watcher).await
27
- when :wait_writable then (write_watcher ||= io.write_watcher).await
28
- else result
37
+ when :wait_readable then Thread.current.agent.wait_io(io, false)
38
+ else return result
39
+ end
40
+ end
41
+ end
42
+
43
+ alias_method :orig_syswrite, :syswrite
44
+ def syswrite(buf)
45
+ loop do
46
+ case (result = write_nonblock(buf, exception: false))
47
+ when :wait_writable then Thread.current.agent.wait_io(io, true)
48
+ else
49
+ return result
29
50
  end
30
51
  end
31
52
  end
@@ -39,15 +60,14 @@ class ::OpenSSL::SSL::SSLSocket
39
60
  # @sync = osync
40
61
  end
41
62
 
42
- def syswrite(buf)
43
- read_watcher = nil
44
- write_watcher = nil
45
- loop do
46
- case (result = write_nonblock(buf, exception: false))
47
- when :wait_readable then (read_watcher ||= io.read_watcher).await
48
- when :wait_writable then (write_watcher ||= io.write_watcher).await
49
- else result
50
- end
63
+ def readpartial(maxlen, buf = +'')
64
+ result = sysread(maxlen, buf)
65
+ result || (raise EOFError)
66
+ end
67
+
68
+ def read_loop
69
+ while (data = sysread(8192))
70
+ yield data
51
71
  end
52
72
  end
53
73
  end
@@ -7,14 +7,21 @@ require_relative '../core/thread_pool'
7
7
 
8
8
  # Socket overrides (eventually rewritten in C)
9
9
  class ::Socket
10
+ def accept
11
+ Thread.current.agent.accept(self)
12
+ end
13
+
10
14
  NO_EXCEPTION = { exception: false }.freeze
11
15
 
12
16
  def connect(remotesockaddr)
13
17
  loop do
14
18
  result = connect_nonblock(remotesockaddr, **NO_EXCEPTION)
15
- return if result == 0
16
-
17
- result == :wait_writable ? write_watcher.await : (raise IOError)
19
+ case result
20
+ when 0 then return
21
+ when :wait_writable then Thread.current.agent.wait_io(self, true)
22
+ else
23
+ raise IOError
24
+ end
18
25
  end
19
26
  end
20
27
 
@@ -22,9 +29,12 @@ class ::Socket
22
29
  outbuf ||= +''
23
30
  loop do
24
31
  result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
25
- raise IOError unless result
26
-
27
- result == :wait_readable ? read_watcher.await : (return result)
32
+ case result
33
+ when nil then raise IOError
34
+ when :wait_readable then Thread.current.agent.wait_io(self, false)
35
+ else
36
+ return result
37
+ end
28
38
  end
29
39
  end
30
40
 
@@ -32,9 +42,12 @@ class ::Socket
32
42
  @read_buffer ||= +''
33
43
  loop do
34
44
  result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
35
- raise IOError unless result
36
-
37
- result == :wait_readable ? read_watcher.await : (return result)
45
+ case result
46
+ when nil then raise IOError
47
+ when :wait_readable then Thread.current.agent.wait_io(self, false)
48
+ else
49
+ return result
50
+ end
38
51
  end
39
52
  end
40
53
 
@@ -117,4 +130,9 @@ class ::TCPServer
117
130
  def accept
118
131
  @io ? @io.accept : orig_accept
119
132
  end
133
+
134
+ alias_method :orig_close, :close
135
+ def close
136
+ @io ? @io.close : orig_close
137
+ end
120
138
  end
@@ -8,26 +8,30 @@ class ::Thread
8
8
 
9
9
  alias_method :orig_initialize, :initialize
10
10
  def initialize(*args, &block)
11
- @join_wait_queue = Gyro::Queue.new
11
+ @join_wait_queue = []
12
+ @finalization_mutex = Mutex.new
12
13
  @args = args
13
14
  @block = block
14
- @finalization_mutex = Mutex.new
15
15
  orig_initialize { execute }
16
16
  end
17
17
 
18
18
  def execute
19
+ # agent must be created in the context of the new thread, therefore it
20
+ # cannot be created in Thread#initialize
21
+ @agent = Polyphony::LibevAgent.new
19
22
  setup
20
23
  @ready = true
21
24
  result = @block.(*@args)
22
25
  rescue Polyphony::MoveOn, Polyphony::Terminate => e
23
26
  result = e.value
24
- rescue Exception => e
25
- result = e
27
+ rescue Exception => result
26
28
  ensure
27
29
  @ready = true
28
30
  finalize(result)
29
31
  end
30
32
 
33
+ attr_accessor :agent
34
+
31
35
  def setup
32
36
  @main_fiber = Fiber.current
33
37
  @main_fiber.setup_main_fiber
@@ -44,24 +48,25 @@ class ::Thread
44
48
  @result = result
45
49
  signal_waiters(result)
46
50
  end
47
- stop_event_selector
51
+ @agent.finalize
48
52
  end
49
53
 
50
54
  def signal_waiters(result)
51
- @join_wait_queue.shift_each { |w| w.signal(result) }
55
+ @join_wait_queue.each { |w| w.signal(result) }
52
56
  end
53
57
 
54
58
  alias_method :orig_join, :join
55
59
  def join(timeout = nil)
56
- async = Fiber.current.auto_async
60
+ watcher = Fiber.current.auto_watcher
61
+
57
62
  @finalization_mutex.synchronize do
58
63
  if @terminated
59
64
  @result.is_a?(Exception) ? (raise @result) : (return @result)
60
65
  else
61
- @join_wait_queue.push(async)
66
+ @join_wait_queue << watcher
62
67
  end
63
68
  end
64
- timeout ? move_on_after(timeout) { async.await } : async.await
69
+ timeout ? move_on_after(timeout) { watcher.await } : watcher.await
65
70
  end
66
71
  alias_method :await, :join
67
72
 
@@ -78,6 +83,8 @@ class ::Thread
78
83
 
79
84
  alias_method :orig_kill, :kill
80
85
  def kill
86
+ return if @terminated
87
+
81
88
  raise Polyphony::Terminate
82
89
  end
83
90
 
@@ -4,6 +4,7 @@ require_relative './extensions/socket'
4
4
  require_relative './extensions/openssl'
5
5
 
6
6
  module Polyphony
7
+ # A more elegant networking API
7
8
  module Net
8
9
  class << self
9
10
  def tcp_connect(host, port, opts = {})
@@ -17,11 +18,11 @@ module Polyphony
17
18
  socket
18
19
  end
19
20
  end
20
-
21
+
21
22
  def tcp_listen(host = nil, port = nil, opts = {})
22
23
  host ||= '0.0.0.0'
23
24
  raise 'Port number not specified' unless port
24
-
25
+
25
26
  socket = socket_from_options(host, port, opts)
26
27
  if opts[:secure_context] || opts[:secure]
27
28
  secure_server(socket, opts[:secure_context], opts)
@@ -29,7 +30,7 @@ module Polyphony
29
30
  socket
30
31
  end
31
32
  end
32
-
33
+
33
34
  def socket_from_options(host, port, opts)
34
35
  ::Socket.new(:INET, :STREAM).tap do |s|
35
36
  s.reuse_addr if opts[:reuse_addr]
@@ -39,19 +40,19 @@ module Polyphony
39
40
  s.listen(0)
40
41
  end
41
42
  end
42
-
43
+
43
44
  def secure_socket(socket, context, opts)
44
45
  context ||= OpenSSL::SSL::SSLContext.new
45
46
  setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
46
47
  socket = secure_socket_wrapper(socket, context)
47
-
48
+
48
49
  socket.tap do |s|
49
50
  s.hostname = opts[:host] if opts[:host]
50
51
  s.connect
51
52
  s.post_connection_check(opts[:host]) if opts[:host]
52
53
  end
53
54
  end
54
-
55
+
55
56
  def secure_socket_wrapper(socket, context)
56
57
  if context
57
58
  OpenSSL::SSL::SSLSocket.new(socket, context)
@@ -59,12 +60,12 @@ module Polyphony
59
60
  OpenSSL::SSL::SSLSocket.new(socket)
60
61
  end
61
62
  end
62
-
63
+
63
64
  def secure_server(socket, context, opts)
64
65
  setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
65
66
  OpenSSL::SSL::SSLServer.new(socket, context)
66
67
  end
67
-
68
+
68
69
  def setup_alpn(context, protocols)
69
70
  context.alpn_protocols = protocols
70
71
  context.alpn_select_cb = lambda do |peer_protocols|
@@ -74,4 +75,3 @@ module Polyphony
74
75
  end
75
76
  end
76
77
  end
77
-
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.38'
4
+ VERSION = '0.43'
5
5
  end
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  }
18
18
  s.rdoc_options = ["--title", "polyphony", "--main", "README.md"]
19
19
  s.extra_rdoc_files = ["README.md"]
20
- s.extensions = ["ext/gyro/extconf.rb"]
20
+ s.extensions = ["ext/polyphony/extconf.rb"]
21
21
  s.require_paths = ["lib"]
22
22
  s.required_ruby_version = '>= 2.6'
23
23
 
@@ -26,8 +26,8 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency 'minitest', '5.13.0'
27
27
  s.add_development_dependency 'minitest-reporters', '1.4.2'
28
28
  s.add_development_dependency 'simplecov', '0.17.1'
29
- s.add_development_dependency 'rubocop', '0.80.0'
30
- s.add_development_dependency 'pg', '1.1.3'
29
+ s.add_development_dependency 'rubocop', '0.85.1'
30
+ s.add_development_dependency 'pg', '1.1.4'
31
31
  s.add_development_dependency 'rake-compiler', '1.0.5'
32
32
  s.add_development_dependency 'redis', '4.1.0'
33
33
  s.add_development_dependency 'hiredis', '0.6.3'
@@ -36,5 +36,5 @@ Gem::Specification.new do |s|
36
36
  s.add_development_dependency 'jekyll', '~>3.8.6'
37
37
  s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
38
38
  s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
39
- s.add_development_dependency 'just-the-docs', '~>0.2.7'
39
+ s.add_development_dependency 'just-the-docs', '~>0.3.0'
40
40
  end
@@ -18,19 +18,32 @@ Minitest::Reporters.use! [
18
18
  Minitest::Reporters::SpecReporter.new
19
19
  ]
20
20
 
21
+ class ::Fiber
22
+ attr_writer :auto_watcher
23
+ end
24
+
21
25
  class MiniTest::Test
22
26
  def setup
27
+ # puts "* setup #{self.name}"
23
28
  if Fiber.current.children.size > 0
24
29
  puts "Children left: #{Fiber.current.children.inspect}"
25
30
  exit!
26
31
  end
27
32
  Fiber.current.setup_main_fiber
33
+ Fiber.current.instance_variable_set(:@auto_watcher, nil)
34
+ Thread.current.agent = Polyphony::LibevAgent.new
28
35
  sleep 0
29
36
  end
30
37
 
31
38
  def teardown
39
+ # puts "* teardown #{self.name.inspect} Fiber.current: #{Fiber.current.inspect}"
32
40
  Fiber.current.terminate_all_children
33
41
  Fiber.current.await_all_children
42
+ Fiber.current.auto_watcher = nil
43
+ rescue => e
44
+ puts e
45
+ puts e.backtrace.join("\n")
46
+ exit!
34
47
  end
35
48
  end
36
49
 
@@ -40,4 +53,4 @@ module Kernel
40
53
  rescue Exception => e
41
54
  e
42
55
  end
43
- end
56
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class AgentTest < MiniTest::Test
6
+ def setup
7
+ super
8
+ @prev_agent = Thread.current.agent
9
+ @agent = Polyphony::LibevAgent.new
10
+ Thread.current.agent = @agent
11
+ end
12
+
13
+ def teardown
14
+ @agent.finalize
15
+ Thread.current.agent = @prev_agent
16
+ end
17
+
18
+ def test_sleep
19
+ count = 0
20
+ t0 = Time.now
21
+ spin {
22
+ @agent.sleep 0.01
23
+ count += 1
24
+ }
25
+ suspend
26
+ assert Time.now - t0 >= 0.01
27
+ assert_equal 1, count
28
+ end
29
+
30
+ def test_write_read_partial
31
+ i, o = IO.pipe
32
+ buf = +''
33
+ f = spin { @agent.read(i, buf, 5, false) }
34
+ @agent.write(o, 'Hello world')
35
+ return_value = f.await
36
+
37
+ assert_equal 'Hello', buf
38
+ assert_equal return_value, buf
39
+ end
40
+
41
+ def test_write_read_to_eof_limited_buffer
42
+ i, o = IO.pipe
43
+ buf = +''
44
+ f = spin { @agent.read(i, buf, 5, true) }
45
+ @agent.write(o, 'Hello')
46
+ snooze
47
+ @agent.write(o, ' world')
48
+ snooze
49
+ o.close
50
+ return_value = f.await
51
+
52
+ assert_equal 'Hello', buf
53
+ assert_equal return_value, buf
54
+ end
55
+
56
+ def test_write_read_to_eof
57
+ i, o = IO.pipe
58
+ buf = +''
59
+ f = spin { @agent.read(i, buf, 10**6, true) }
60
+ @agent.write(o, 'Hello')
61
+ snooze
62
+ @agent.write(o, ' world')
63
+ snooze
64
+ o.close
65
+ return_value = f.await
66
+
67
+ assert_equal 'Hello world', buf
68
+ assert_equal return_value, buf
69
+ end
70
+
71
+ def test_waitpid
72
+ pid = fork do
73
+ @agent.post_fork
74
+ exit(42)
75
+ end
76
+
77
+ result = @agent.waitpid(pid)
78
+ assert_equal [pid, 42], result
79
+ end
80
+
81
+ def test_read_loop
82
+ i, o = IO.pipe
83
+
84
+ buf = []
85
+ spin do
86
+ buf << :ready
87
+ @agent.read_loop(i) { |d| buf << d }
88
+ buf << :done
89
+ end
90
+
91
+ o << 'foo'
92
+ o << 'bar'
93
+ o.close
94
+ snooze
95
+
96
+ assert_equal [:ready, 'foo', 'bar', :done], buf
97
+ end
98
+
99
+ def test_accept_loop
100
+ server = TCPServer.new('127.0.0.1', 1234)
101
+
102
+ clients = []
103
+ server_fiber = spin do
104
+ @agent.accept_loop(server) { |c| clients << c }
105
+ end
106
+
107
+ c1 = TCPSocket.new('127.0.0.1', 1234)
108
+ snooze
109
+
110
+ assert_equal 1, clients.size
111
+
112
+ c2 = TCPSocket.new('127.0.0.1', 1234)
113
+ snooze
114
+
115
+ assert_equal 2, clients.size
116
+
117
+ ensure
118
+ c1&.close
119
+ c2&.close
120
+ server_fiber.stop
121
+ snooze
122
+ server&.close
123
+ end
124
+ end