polyphony 0.36 → 0.42

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 (118) 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 +28 -2
  6. data/Gemfile +0 -11
  7. data/Gemfile.lock +15 -14
  8. data/README.md +2 -1
  9. data/Rakefile +7 -3
  10. data/TODO.md +28 -95
  11. data/docs/_config.yml +56 -7
  12. data/docs/_sass/custom/custom.scss +0 -30
  13. data/docs/_sass/overrides.scss +0 -46
  14. data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
  15. data/docs/_user-guide/index.md +9 -0
  16. data/docs/{user-guide → _user-guide}/web-server.md +0 -0
  17. data/docs/api-reference/fiber.md +2 -2
  18. data/docs/api-reference/index.md +9 -0
  19. data/docs/api-reference/polyphony-process.md +1 -1
  20. data/docs/api-reference/thread.md +1 -1
  21. data/docs/faq.md +21 -11
  22. data/docs/getting-started/index.md +10 -0
  23. data/docs/getting-started/installing.md +2 -6
  24. data/docs/getting-started/overview.md +507 -0
  25. data/docs/getting-started/tutorial.md +27 -19
  26. data/docs/index.md +3 -2
  27. data/docs/main-concepts/concurrency.md +0 -5
  28. data/docs/main-concepts/design-principles.md +69 -21
  29. data/docs/main-concepts/extending.md +1 -1
  30. data/docs/main-concepts/index.md +9 -0
  31. data/examples/core/01-spinning-up-fibers.rb +1 -0
  32. data/examples/core/03-interrupting.rb +4 -1
  33. data/examples/core/04-handling-signals.rb +19 -0
  34. data/examples/core/xx-agent.rb +102 -0
  35. data/examples/core/xx-fork-cleanup.rb +22 -0
  36. data/examples/core/xx-sleeping.rb +14 -6
  37. data/examples/io/tunnel.rb +48 -0
  38. data/examples/io/xx-irb.rb +1 -1
  39. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  40. data/examples/performance/thread-vs-fiber/polyphony_server.rb +13 -36
  41. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  42. data/examples/performance/xx-array.rb +11 -0
  43. data/examples/performance/xx-fiber-switch.rb +9 -0
  44. data/examples/performance/xx-snooze.rb +15 -0
  45. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  46. data/ext/{gyro → polyphony}/fiber.c +18 -22
  47. data/ext/{gyro → polyphony}/libev.c +0 -0
  48. data/ext/{gyro → polyphony}/libev.h +0 -0
  49. data/ext/polyphony/libev_agent.c +718 -0
  50. data/ext/polyphony/libev_queue.c +216 -0
  51. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -46
  52. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +25 -39
  53. data/ext/polyphony/polyphony_ext.c +23 -0
  54. data/ext/{gyro → polyphony}/socket.c +21 -18
  55. data/ext/polyphony/thread.c +206 -0
  56. data/ext/{gyro → polyphony}/tracing.c +1 -1
  57. data/lib/polyphony.rb +40 -44
  58. data/lib/polyphony/adapters/fs.rb +1 -4
  59. data/lib/polyphony/adapters/irb.rb +1 -1
  60. data/lib/polyphony/adapters/postgres.rb +6 -5
  61. data/lib/polyphony/adapters/process.rb +27 -23
  62. data/lib/polyphony/adapters/trace.rb +110 -105
  63. data/lib/polyphony/core/channel.rb +35 -35
  64. data/lib/polyphony/core/exceptions.rb +29 -29
  65. data/lib/polyphony/core/global_api.rb +94 -91
  66. data/lib/polyphony/core/resource_pool.rb +83 -83
  67. data/lib/polyphony/core/sync.rb +16 -16
  68. data/lib/polyphony/core/thread_pool.rb +49 -37
  69. data/lib/polyphony/core/throttler.rb +30 -23
  70. data/lib/polyphony/event.rb +27 -0
  71. data/lib/polyphony/extensions/core.rb +25 -17
  72. data/lib/polyphony/extensions/fiber.rb +269 -267
  73. data/lib/polyphony/extensions/io.rb +56 -26
  74. data/lib/polyphony/extensions/openssl.rb +5 -9
  75. data/lib/polyphony/extensions/socket.rb +29 -10
  76. data/lib/polyphony/extensions/thread.rb +19 -12
  77. data/lib/polyphony/net.rb +64 -60
  78. data/lib/polyphony/version.rb +1 -1
  79. data/polyphony.gemspec +4 -7
  80. data/test/helper.rb +14 -1
  81. data/test/stress.rb +17 -12
  82. data/test/test_agent.rb +124 -0
  83. data/test/{test_async.rb → test_event.rb} +15 -7
  84. data/test/test_ext.rb +25 -4
  85. data/test/test_fiber.rb +19 -10
  86. data/test/test_global_api.rb +4 -4
  87. data/test/test_io.rb +46 -24
  88. data/test/test_queue.rb +74 -0
  89. data/test/test_signal.rb +3 -40
  90. data/test/test_socket.rb +33 -0
  91. data/test/test_thread.rb +38 -16
  92. data/test/test_thread_pool.rb +2 -2
  93. data/test/test_throttler.rb +0 -1
  94. data/test/test_trace.rb +6 -5
  95. metadata +41 -57
  96. data/docs/_includes/nav.html +0 -51
  97. data/docs/_includes/prevnext.html +0 -17
  98. data/docs/_layouts/default.html +0 -106
  99. data/docs/api-reference.md +0 -11
  100. data/docs/api-reference/gyro-async.md +0 -57
  101. data/docs/api-reference/gyro-child.md +0 -29
  102. data/docs/api-reference/gyro-queue.md +0 -44
  103. data/docs/api-reference/gyro-timer.md +0 -51
  104. data/docs/api-reference/gyro.md +0 -25
  105. data/docs/getting-started.md +0 -10
  106. data/docs/main-concepts.md +0 -10
  107. data/docs/user-guide.md +0 -10
  108. data/examples/core/forever_sleep.rb +0 -19
  109. data/ext/gyro/async.c +0 -148
  110. data/ext/gyro/child.c +0 -127
  111. data/ext/gyro/gyro_ext.c +0 -33
  112. data/ext/gyro/io.c +0 -474
  113. data/ext/gyro/queue.c +0 -142
  114. data/ext/gyro/selector.c +0 -205
  115. data/ext/gyro/signal.c +0 -118
  116. data/ext/gyro/thread.c +0 -298
  117. data/ext/gyro/timer.c +0 -134
  118. data/test/test_timer.rb +0 -56
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'open3'
4
4
 
5
- # IO overrides
5
+ # IO class method patches
6
6
  class ::IO
7
7
  class << self
8
8
  alias_method :orig_binread, :binread
@@ -72,7 +72,10 @@ class ::IO
72
72
  Open3.popen2(cmd) { |_i, o, _t| yield o }
73
73
  end
74
74
  end
75
+ end
75
76
 
77
+ # IO instance method patches
78
+ class ::IO
76
79
  # def each(sep = $/, limit = nil, chomp: nil)
77
80
  # sep, limit = $/, sep if sep.is_a?(Integer)
78
81
  # end
@@ -93,6 +96,39 @@ class ::IO
93
96
  # def getc
94
97
  # end
95
98
 
99
+ alias_method :orig_read, :read
100
+ def read(len = 1 << 30)
101
+ @read_buffer ||= +''
102
+ result = Thread.current.agent.read(self, @read_buffer, len, true)
103
+ return nil unless result
104
+
105
+ already_read = @read_buffer
106
+ @read_buffer = +''
107
+ already_read
108
+ end
109
+
110
+ alias_method :orig_readpartial, :read
111
+ def readpartial(len)
112
+ @read_buffer ||= +''
113
+ result = Thread.current.agent.read(self, @read_buffer, len, false)
114
+ raise EOFError unless result
115
+
116
+ already_read = @read_buffer
117
+ @read_buffer = +''
118
+ already_read
119
+ end
120
+
121
+ alias_method :orig_write, :write
122
+ def write(str)
123
+ Thread.current.agent.write(self, str)
124
+ end
125
+
126
+ alias_method :orig_write_chevron, :<<
127
+ def <<(str)
128
+ Thread.current.agent.write(self, str)
129
+ self
130
+ end
131
+
96
132
  alias_method :orig_gets, :gets
97
133
  def gets(sep = $/, _limit = nil, _chomp: nil)
98
134
  if sep.is_a?(Integer)
@@ -101,23 +137,17 @@ class ::IO
101
137
  end
102
138
  sep_size = sep.bytesize
103
139
 
104
- @gets_buffer ||= +''
140
+ @read_buffer ||= +''
105
141
 
106
142
  loop do
107
- idx = @gets_buffer.index(sep)
108
- return @gets_buffer.slice!(0, idx + sep_size) if idx
109
-
110
- if (data = readpartial(8192))
111
- @gets_buffer << data
112
- else
113
- return nil if @gets_buffer.empty?
143
+ idx = @read_buffer.index(sep)
144
+ return @read_buffer.slice!(0, idx + sep_size) if idx
114
145
 
115
- line = @gets_buffer.freeze
116
- @gets_buffer = +''
117
- return line
118
- end
146
+ data = readpartial(8192)
147
+ @read_buffer << data
148
+ rescue EOFError
149
+ return nil
119
150
  end
120
- # orig_gets(sep, limit, chomp: chomp)
121
151
  end
122
152
 
123
153
  # def print(*args)
@@ -171,16 +201,16 @@ class ::IO
171
201
  buf ? readpartial(maxlen, buf) : readpartial(maxlen)
172
202
  end
173
203
 
174
- alias_method :orig_read, :read
175
- def read(length = nil, outbuf = nil)
176
- if length
177
- return outbuf ? readpartial(length) : readpartial(length, outbuf)
178
- end
179
-
180
- until eof?
181
- outbuf ||= +''
182
- outbuf << readpartial(8192)
183
- end
184
- outbuf
185
- end
204
+ # alias_method :orig_read, :read
205
+ # def read(length = nil, outbuf = nil)
206
+ # if length
207
+ # return outbuf ? readpartial(length) : readpartial(length, outbuf)
208
+ # end
209
+
210
+ # until eof?
211
+ # outbuf ||= +''
212
+ # outbuf << readpartial(8192)
213
+ # end
214
+ # outbuf
215
+ # end
186
216
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'openssl'
4
4
 
5
- import('./socket')
5
+ require_relative './socket'
6
6
 
7
7
  # Open ssl socket helper methods (to make it compatible with Socket API)
8
8
  class ::OpenSSL::SSL::SSLSocket
@@ -19,12 +19,10 @@ class ::OpenSSL::SSL::SSLSocket
19
19
  end
20
20
 
21
21
  def sysread(maxlen, buf)
22
- read_watcher = nil
23
- write_watcher = nil
24
22
  loop do
25
23
  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
24
+ when :wait_readable then Thread.current.agent.wait_io(io, false)
25
+ when :wait_writable then Thread.current.agent.wait_io(io, true)
28
26
  else result
29
27
  end
30
28
  end
@@ -40,12 +38,10 @@ class ::OpenSSL::SSL::SSLSocket
40
38
  end
41
39
 
42
40
  def syswrite(buf)
43
- read_watcher = nil
44
- write_watcher = nil
45
41
  loop do
46
42
  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
43
+ when :wait_readable then Thread.current.agent.wait_io(io, false)
44
+ when :wait_writable then Thread.current.agent.wait_io(io, true)
49
45
  else result
50
46
  end
51
47
  end
@@ -2,18 +2,26 @@
2
2
 
3
3
  require 'socket'
4
4
 
5
- import('./io')
5
+ require_relative './io'
6
+ require_relative '../core/thread_pool'
6
7
 
7
8
  # Socket overrides (eventually rewritten in C)
8
9
  class ::Socket
10
+ def accept
11
+ Thread.current.agent.accept(self)
12
+ end
13
+
9
14
  NO_EXCEPTION = { exception: false }.freeze
10
15
 
11
16
  def connect(remotesockaddr)
12
17
  loop do
13
18
  result = connect_nonblock(remotesockaddr, **NO_EXCEPTION)
14
- return if result == 0
15
-
16
- 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
17
25
  end
18
26
  end
19
27
 
@@ -21,9 +29,12 @@ class ::Socket
21
29
  outbuf ||= +''
22
30
  loop do
23
31
  result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
24
- raise IOError unless result
25
-
26
- 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
27
38
  end
28
39
  end
29
40
 
@@ -31,9 +42,12 @@ class ::Socket
31
42
  @read_buffer ||= +''
32
43
  loop do
33
44
  result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
34
- raise IOError unless result
35
-
36
- 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
37
51
  end
38
52
  end
39
53
 
@@ -116,4 +130,9 @@ class ::TCPServer
116
130
  def accept
117
131
  @io ? @io.accept : orig_accept
118
132
  end
133
+
134
+ alias_method :orig_close, :close
135
+ def close
136
+ @io ? @io.close : orig_close
137
+ end
119
138
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Exceptions = import '../core/exceptions'
3
+ require_relative '../core/exceptions'
4
4
 
5
5
  # Thread extensions
6
6
  class ::Thread
@@ -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
- rescue Exceptions::MoveOn, Exceptions::Terminate => e
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,7 +83,9 @@ class ::Thread
78
83
 
79
84
  alias_method :orig_kill, :kill
80
85
  def kill
81
- raise Exceptions::Terminate
86
+ return if @terminated
87
+
88
+ raise Polyphony::Terminate
82
89
  end
83
90
 
84
91
  alias_method :orig_inspect, :inspect
@@ -1,73 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :tcp_connect,
4
- :tcp_listen
3
+ require_relative './extensions/socket'
4
+ require_relative './extensions/openssl'
5
5
 
6
- import('./extensions/socket')
7
- import('./extensions/openssl')
6
+ module Polyphony
7
+ # A more elegant networking API
8
+ module Net
9
+ class << self
10
+ def tcp_connect(host, port, opts = {})
11
+ socket = ::Socket.new(:INET, :STREAM).tap do |s|
12
+ addr = ::Socket.sockaddr_in(port, host)
13
+ s.connect(addr)
14
+ end
15
+ if opts[:secure_context] || opts[:secure]
16
+ secure_socket(socket, opts[:secure_context], opts.merge(host: host))
17
+ else
18
+ socket
19
+ end
20
+ end
8
21
 
9
- def tcp_connect(host, port, opts = {})
10
- socket = ::Socket.new(:INET, :STREAM).tap do |s|
11
- addr = ::Socket.sockaddr_in(port, host)
12
- s.connect(addr)
13
- end
14
- if opts[:secure_context] || opts[:secure]
15
- secure_socket(socket, opts[:secure_context], opts.merge(host: host))
16
- else
17
- socket
18
- end
19
- end
22
+ def tcp_listen(host = nil, port = nil, opts = {})
23
+ host ||= '0.0.0.0'
24
+ raise 'Port number not specified' unless port
20
25
 
21
- def tcp_listen(host = nil, port = nil, opts = {})
22
- host ||= '0.0.0.0'
23
- raise 'Port number not specified' unless port
26
+ socket = socket_from_options(host, port, opts)
27
+ if opts[:secure_context] || opts[:secure]
28
+ secure_server(socket, opts[:secure_context], opts)
29
+ else
30
+ socket
31
+ end
32
+ end
24
33
 
25
- socket = socket_from_options(host, port, opts)
26
- if opts[:secure_context] || opts[:secure]
27
- secure_server(socket, opts[:secure_context], opts)
28
- else
29
- socket
30
- end
31
- end
34
+ def socket_from_options(host, port, opts)
35
+ ::Socket.new(:INET, :STREAM).tap do |s|
36
+ s.reuse_addr if opts[:reuse_addr]
37
+ s.dont_linger if opts[:dont_linger]
38
+ addr = ::Socket.sockaddr_in(port, host)
39
+ s.bind(addr)
40
+ s.listen(0)
41
+ end
42
+ end
32
43
 
33
- def socket_from_options(host, port, opts)
34
- ::Socket.new(:INET, :STREAM).tap do |s|
35
- s.reuse_addr if opts[:reuse_addr]
36
- s.dont_linger if opts[:dont_linger]
37
- addr = ::Socket.sockaddr_in(port, host)
38
- s.bind(addr)
39
- s.listen(0)
40
- end
41
- end
44
+ def secure_socket(socket, context, opts)
45
+ context ||= OpenSSL::SSL::SSLContext.new
46
+ setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
47
+ socket = secure_socket_wrapper(socket, context)
42
48
 
43
- def secure_socket(socket, context, opts)
44
- context ||= OpenSSL::SSL::SSLContext.new
45
- setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
46
- socket = secure_socket_wrapper(socket, context)
49
+ socket.tap do |s|
50
+ s.hostname = opts[:host] if opts[:host]
51
+ s.connect
52
+ s.post_connection_check(opts[:host]) if opts[:host]
53
+ end
54
+ end
47
55
 
48
- socket.tap do |s|
49
- s.hostname = opts[:host] if opts[:host]
50
- s.connect
51
- s.post_connection_check(opts[:host]) if opts[:host]
52
- end
53
- end
54
-
55
- def secure_socket_wrapper(socket, context)
56
- if context
57
- OpenSSL::SSL::SSLSocket.new(socket, context)
58
- else
59
- OpenSSL::SSL::SSLSocket.new(socket)
60
- end
61
- end
56
+ def secure_socket_wrapper(socket, context)
57
+ if context
58
+ OpenSSL::SSL::SSLSocket.new(socket, context)
59
+ else
60
+ OpenSSL::SSL::SSLSocket.new(socket)
61
+ end
62
+ end
62
63
 
63
- def secure_server(socket, context, opts)
64
- setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
65
- OpenSSL::SSL::SSLServer.new(socket, context)
66
- end
64
+ def secure_server(socket, context, opts)
65
+ setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
66
+ OpenSSL::SSL::SSLServer.new(socket, context)
67
+ end
67
68
 
68
- def setup_alpn(context, protocols)
69
- context.alpn_protocols = protocols
70
- context.alpn_select_cb = lambda do |peer_protocols|
71
- (protocols & peer_protocols).first
69
+ def setup_alpn(context, protocols)
70
+ context.alpn_protocols = protocols
71
+ context.alpn_select_cb = lambda do |peer_protocols|
72
+ (protocols & peer_protocols).first
73
+ end
74
+ end
75
+ end
72
76
  end
73
77
  end