polyphony 0.51.0 → 0.54.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -2
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile.lock +7 -68
  5. data/TODO.md +37 -6
  6. data/examples/core/forking.rb +2 -2
  7. data/examples/core/queue.rb +19 -0
  8. data/examples/io/echo_server.rb +1 -0
  9. data/examples/io/https_server.rb +30 -0
  10. data/examples/io/tcp_proxy.rb +2 -2
  11. data/ext/polyphony/backend_common.h +29 -6
  12. data/ext/polyphony/backend_io_uring.c +125 -23
  13. data/ext/polyphony/backend_io_uring_context.c +1 -0
  14. data/ext/polyphony/backend_io_uring_context.h +1 -0
  15. data/ext/polyphony/backend_libev.c +309 -21
  16. data/ext/polyphony/event.c +1 -1
  17. data/ext/polyphony/extconf.rb +9 -2
  18. data/ext/polyphony/polyphony.c +102 -0
  19. data/ext/polyphony/polyphony.h +32 -2
  20. data/ext/polyphony/polyphony_ext.c +3 -0
  21. data/ext/polyphony/queue.c +1 -1
  22. data/ext/polyphony/runqueue.c +1 -1
  23. data/ext/polyphony/socket_extensions.c +33 -0
  24. data/ext/polyphony/thread.c +8 -2
  25. data/lib/polyphony/adapters/irb.rb +1 -1
  26. data/lib/polyphony/adapters/mysql2.rb +1 -1
  27. data/lib/polyphony/adapters/postgres.rb +5 -5
  28. data/lib/polyphony/adapters/process.rb +2 -2
  29. data/lib/polyphony/core/global_api.rb +5 -5
  30. data/lib/polyphony/core/sync.rb +9 -1
  31. data/lib/polyphony/core/throttler.rb +1 -1
  32. data/lib/polyphony/core/timer.rb +2 -2
  33. data/lib/polyphony/extensions/core.rb +1 -1
  34. data/lib/polyphony/extensions/io.rb +20 -25
  35. data/lib/polyphony/extensions/openssl.rb +28 -21
  36. data/lib/polyphony/extensions/socket.rb +51 -54
  37. data/lib/polyphony/version.rb +1 -1
  38. data/polyphony.gemspec +6 -5
  39. data/test/helper.rb +1 -1
  40. data/test/stress.rb +2 -0
  41. data/test/test_backend.rb +152 -5
  42. data/test/test_global_api.rb +2 -2
  43. data/test/test_io.rb +33 -2
  44. data/test/test_kernel.rb +1 -1
  45. data/test/test_signal.rb +1 -1
  46. data/test/test_socket.rb +27 -0
  47. data/test/test_sync.rb +43 -0
  48. data/test/test_thread.rb +4 -0
  49. data/test/test_timer.rb +1 -1
  50. metadata +10 -49
@@ -55,7 +55,7 @@ module ::Process
55
55
  class << self
56
56
  alias_method :orig_detach, :detach
57
57
  def detach(pid)
58
- fiber = spin { Thread.current.backend.waitpid(pid) }
58
+ fiber = spin { Polyphony.backend_waitpid(pid) }
59
59
  fiber.define_singleton_method(:pid) { pid }
60
60
  fiber
61
61
  end
@@ -101,7 +101,7 @@ class ::IO
101
101
  return @read_buffer.slice!(0) if @read_buffer && !@read_buffer.empty?
102
102
 
103
103
  @read_buffer ||= +''
104
- Thread.current.backend.read(self, @read_buffer, 8192, false)
104
+ Polyphony.backend_read(self, @read_buffer, 8192, false)
105
105
  return @read_buffer.slice!(0) if !@read_buffer.empty?
106
106
 
107
107
  nil
@@ -110,7 +110,7 @@ class ::IO
110
110
  alias_method :orig_read, :read
111
111
  def read(len = nil)
112
112
  @read_buffer ||= +''
113
- result = Thread.current.backend.read(self, @read_buffer, len, true)
113
+ result = Polyphony.backend_read(self, @read_buffer, len, true)
114
114
  return nil unless result
115
115
 
116
116
  already_read = @read_buffer
@@ -120,7 +120,7 @@ class ::IO
120
120
 
121
121
  alias_method :orig_readpartial, :read
122
122
  def readpartial(len, str = +'')
123
- result = Thread.current.backend.read(self, str, len, false)
123
+ result = Polyphony.backend_read(self, str, len, false)
124
124
  raise EOFError unless result
125
125
 
126
126
  result
@@ -128,12 +128,12 @@ class ::IO
128
128
 
129
129
  alias_method :orig_write, :write
130
130
  def write(str, *args)
131
- Thread.current.backend.write(self, str, *args)
131
+ Polyphony.backend_write(self, str, *args)
132
132
  end
133
133
 
134
134
  alias_method :orig_write_chevron, :<<
135
135
  def <<(str)
136
- Thread.current.backend.write(self, str)
136
+ Polyphony.backend_write(self, str)
137
137
  self
138
138
  end
139
139
 
@@ -217,34 +217,21 @@ class ::IO
217
217
  end
218
218
 
219
219
  def read_loop(&block)
220
- Thread.current.backend.read_loop(self, &block)
220
+ Polyphony.backend_read_loop(self, &block)
221
221
  end
222
222
 
223
- def feed_loop(receiver, method, &block)
224
- Thread.current.backend.feed_loop(self, receiver, method, &block)
223
+ def feed_loop(receiver, method = :call, &block)
224
+ Polyphony.backend_feed_loop(self, receiver, method, &block)
225
225
  end
226
226
 
227
- # alias_method :orig_read, :read
228
- # def read(length = nil, outbuf = nil)
229
- # if length
230
- # return outbuf ? readpartial(length) : readpartial(length, outbuf)
231
- # end
232
-
233
- # until eof?
234
- # outbuf ||= +''
235
- # outbuf << readpartial(8192)
236
- # end
237
- # outbuf
238
- # end
239
-
240
227
  def wait_readable(timeout = nil)
241
228
  if timeout
242
229
  move_on_after(timeout) do
243
- Thread.current.backend.wait_io(self, false)
230
+ Polyphony.backend_wait_io(self, false)
244
231
  self
245
232
  end
246
233
  else
247
- Thread.current.backend.wait_io(self, false)
234
+ Polyphony.backend_wait_io(self, false)
248
235
  self
249
236
  end
250
237
  end
@@ -252,12 +239,20 @@ class ::IO
252
239
  def wait_writable(timeout = nil)
253
240
  if timeout
254
241
  move_on_after(timeout) do
255
- Thread.current.backend.wait_io(self, true)
242
+ Polyphony.backend_wait_io(self, true)
256
243
  self
257
244
  end
258
245
  else
259
- Thread.current.backend.wait_io(self, true)
246
+ Polyphony.backend_wait_io(self, true)
260
247
  self
261
248
  end
262
249
  end
250
+
251
+ def splice(src, maxlen)
252
+ Polyphony.backend_splice(src, self, maxlen)
253
+ end
254
+
255
+ def splice_to_eof(src, chunksize = 8192)
256
+ Polyphony.backend_splice_to_eof(src, self, chunksize)
257
+ end
263
258
  end
@@ -3,7 +3,7 @@
3
3
  require 'openssl'
4
4
  require_relative './socket'
5
5
 
6
- # Open ssl socket helper methods (to make it compatible with Socket API)
6
+ # OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
7
7
  class ::OpenSSL::SSL::SSLSocket
8
8
  alias_method :orig_initialize, :initialize
9
9
  def initialize(socket, context = nil)
@@ -23,22 +23,12 @@ class ::OpenSSL::SSL::SSLSocket
23
23
  io.reuse_addr
24
24
  end
25
25
 
26
- alias_method :orig_accept, :accept
27
- def accept
28
- while true
29
- result = accept_nonblock(exception: false)
30
- case result
31
- when :wait_readable then Thread.current.backend.wait_io(io, false)
32
- when :wait_writable then Thread.current.backend.wait_io(io, true)
33
- else
34
- return result
35
- end
36
- end
37
- end
38
-
39
- def accept_loop
40
- while true
41
- yield accept
26
+ def fill_rbuff
27
+ data = self.sysread(BLOCK_SIZE)
28
+ if data
29
+ @rbuffer << data
30
+ else
31
+ @eof = true
42
32
  end
43
33
  end
44
34
 
@@ -46,8 +36,8 @@ class ::OpenSSL::SSL::SSLSocket
46
36
  def sysread(maxlen, buf = +'')
47
37
  while true
48
38
  case (result = read_nonblock(maxlen, buf, exception: false))
49
- when :wait_readable then Thread.current.backend.wait_io(io, false)
50
- when :wait_writable then Thread.current.backend.wait_io(io, true)
39
+ when :wait_readable then Polyphony.backend_wait_io(io, false)
40
+ when :wait_writable then Polyphony.backend_wait_io(io, true)
51
41
  else return result
52
42
  end
53
43
  end
@@ -57,8 +47,8 @@ class ::OpenSSL::SSL::SSLSocket
57
47
  def syswrite(buf)
58
48
  while true
59
49
  case (result = write_nonblock(buf, exception: false))
60
- when :wait_readable then Thread.current.backend.wait_io(io, false)
61
- when :wait_writable then Thread.current.backend.wait_io(io, true)
50
+ when :wait_readable then Polyphony.backend_wait_io(io, false)
51
+ when :wait_writable then Polyphony.backend_wait_io(io, true)
62
52
  else
63
53
  return result
64
54
  end
@@ -84,4 +74,21 @@ class ::OpenSSL::SSL::SSLSocket
84
74
  yield data
85
75
  end
86
76
  end
77
+ alias_method :recv_loop, :read_loop
78
+
79
+ alias_method :orig_peeraddr, :peeraddr
80
+ def peeraddr(_ = nil)
81
+ orig_peeraddr
82
+ end
83
+ end
84
+
85
+ # OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
86
+ class ::OpenSSL::SSL::SSLServer
87
+ def accept_loop(ignore_errors = true)
88
+ loop do
89
+ yield accept
90
+ rescue SystemCallError, StandardError => e
91
+ raise e unless ignore_errors
92
+ end
93
+ end
87
94
  end
@@ -8,31 +8,31 @@ require_relative '../core/thread_pool'
8
8
  # Socket overrides (eventually rewritten in C)
9
9
  class ::Socket
10
10
  def accept
11
- Thread.current.backend.accept(self, TCPSocket)
11
+ Polyphony.backend_accept(self, TCPSocket)
12
12
  end
13
13
 
14
14
  def accept_loop(&block)
15
- Thread.current.backend.accept_loop(self, TCPSocket, &block)
15
+ Polyphony.backend_accept_loop(self, TCPSocket, &block)
16
16
  end
17
17
 
18
18
  NO_EXCEPTION = { exception: false }.freeze
19
19
 
20
20
  def connect(addr)
21
21
  addr = Addrinfo.new(addr) if addr.is_a?(String)
22
- Thread.current.backend.connect(self, addr.ip_address, addr.ip_port)
22
+ Polyphony.backend_connect(self, addr.ip_address, addr.ip_port)
23
23
  end
24
24
 
25
25
  def recv(maxlen, flags = 0, outbuf = nil)
26
- Thread.current.backend.recv(self, outbuf || +'', maxlen)
26
+ Polyphony.backend_recv(self, outbuf || +'', maxlen)
27
27
  end
28
28
 
29
29
  def recv_loop(&block)
30
- Thread.current.backend.recv_loop(self, &block)
30
+ Polyphony.backend_recv_loop(self, &block)
31
31
  end
32
32
  alias_method :read_loop, :recv_loop
33
33
 
34
- def feed_loop(receiver, method, &block)
35
- Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
34
+ def feed_loop(receiver, method = :call, &block)
35
+ Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
36
36
  end
37
37
 
38
38
  def recvfrom(maxlen, flags = 0)
@@ -41,28 +41,27 @@ class ::Socket
41
41
  result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
42
42
  case result
43
43
  when nil then raise IOError
44
- when :wait_readable then Thread.current.backend.wait_io(self, false)
44
+ when :wait_readable then Polyphony.backend_wait_io(self, false)
45
45
  else
46
46
  return result
47
47
  end
48
48
  end
49
49
  end
50
50
 
51
- def send(mesg, flags = 0)
52
- Thread.current.backend.send(self, mesg)
53
- end
51
+ # def send(mesg, flags)
52
+ # Polyphony.backend_send(self, mesg, flags)
53
+ # end
54
54
 
55
- def write(str, *args)
56
- if args.empty?
57
- Thread.current.backend.send(self, str)
58
- else
59
- Thread.current.backend.send(self, str + args.join)
60
- end
61
- end
62
- alias_method :<<, :write
55
+ # def write(*args)
56
+ # Polyphony.backend_sendv(self, args, 0)
57
+ # end
58
+
59
+ # def <<(mesg)
60
+ # Polyphony.backend_send(self, mesg, 0)
61
+ # end
63
62
 
64
63
  def readpartial(maxlen, str = +'')
65
- Thread.current.backend.recv(self, str, maxlen)
64
+ Polyphony.backend_recv(self, str, maxlen)
66
65
  end
67
66
 
68
67
  ZERO_LINGER = [0, 0].pack('ii').freeze
@@ -142,34 +141,33 @@ class ::TCPSocket
142
141
  end
143
142
 
144
143
  def recv(maxlen, flags = 0, outbuf = nil)
145
- Thread.current.backend.recv(self, outbuf || +'', maxlen)
144
+ Polyphony.backend_recv(self, outbuf || +'', maxlen)
146
145
  end
147
146
 
148
147
  def recv_loop(&block)
149
- Thread.current.backend.recv_loop(self, &block)
148
+ Polyphony.backend_recv_loop(self, &block)
150
149
  end
151
150
  alias_method :read_loop, :recv_loop
152
151
 
153
- def feed_loop(receiver, method, &block)
154
- Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
152
+ def feed_loop(receiver, method = :call, &block)
153
+ Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
155
154
  end
156
155
 
157
- def send(mesg, flags = 0)
158
- Thread.current.backend.send(self, mesg)
159
- end
156
+ # def send(mesg, flags)
157
+ # Polyphony.backend_send(self, mesg, flags)
158
+ # end
160
159
 
161
- def write(str, *args)
162
- if args.empty?
163
- Thread.current.backend.send(self, str)
164
- else
165
- Thread.current.backend.send(self, str + args.join)
166
- end
167
- end
168
- alias_method :<<, :write
160
+ # def write(*args)
161
+ # Polyphony.backend_sendv(self, args, 0)
162
+ # end
163
+
164
+ # def <<(mesg)
165
+ # Polyphony.backend_send(self, mesg, 0)
166
+ # end
169
167
 
170
168
  def readpartial(maxlen, str = nil)
171
169
  @read_buffer ||= +''
172
- result = Thread.current.backend.recv(self, @read_buffer, maxlen)
170
+ result = Polyphony.backend_recv(self, @read_buffer, maxlen)
173
171
  raise EOFError unless result
174
172
 
175
173
  if str
@@ -200,12 +198,12 @@ class ::TCPServer
200
198
 
201
199
  alias_method :orig_accept, :accept
202
200
  def accept
203
- Thread.current.backend.accept(@io, TCPSocket)
201
+ Polyphony.backend_accept(@io, TCPSocket)
204
202
  # @io.accept
205
203
  end
206
204
 
207
205
  def accept_loop(&block)
208
- Thread.current.backend.accept_loop(@io, TCPSocket, &block)
206
+ Polyphony.backend_accept_loop(@io, TCPSocket, &block)
209
207
  end
210
208
 
211
209
  alias_method :orig_close, :close
@@ -217,44 +215,43 @@ end
217
215
  class ::UNIXServer
218
216
  alias_method :orig_accept, :accept
219
217
  def accept
220
- Thread.current.backend.accept(self, UNIXSocket)
218
+ Polyphony.backend_accept(self, UNIXSocket)
221
219
  end
222
220
 
223
221
  def accept_loop(&block)
224
- Thread.current.backend.accept_loop(self, UNIXSocket, &block)
222
+ Polyphony.backend_accept_loop(self, UNIXSocket, &block)
225
223
  end
226
224
  end
227
225
 
228
226
  class ::UNIXSocket
229
227
  def recv(maxlen, flags = 0, outbuf = nil)
230
- Thread.current.backend.recv(self, outbuf || +'', maxlen)
228
+ Polyphony.backend_recv(self, outbuf || +'', maxlen)
231
229
  end
232
230
 
233
231
  def recv_loop(&block)
234
- Thread.current.backend.recv_loop(self, &block)
232
+ Polyphony.backend_recv_loop(self, &block)
235
233
  end
236
234
  alias_method :read_loop, :recv_loop
237
235
 
238
- def feed_loop(receiver, method, &block)
239
- Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
236
+ def feed_loop(receiver, method = :call, &block)
237
+ Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
240
238
  end
241
239
 
242
- def send(mesg, flags = 0)
243
- Thread.current.backend.send(self, mesg)
240
+ def send(mesg, flags)
241
+ Polyphony.backend_send(self, mesg, flags)
244
242
  end
245
243
 
246
- def write(str, *args)
247
- if args.empty?
248
- Thread.current.backend.send(self, str)
249
- else
250
- Thread.current.backend.send(self, str + args.join)
251
- end
244
+ def write(*args)
245
+ Polyphony.backend_sendv(self, args, 0)
246
+ end
247
+
248
+ def <<(mesg)
249
+ Polyphony.backend_send(self, mesg, 0)
252
250
  end
253
- alias_method :<<, :write
254
251
 
255
252
  def readpartial(maxlen, str = nil)
256
253
  @read_buffer ||= +''
257
- result = Thread.current.backend.recv(self, @read_buffer, maxlen)
254
+ result = Polyphony.backend_recv(self, @read_buffer, maxlen)
258
255
  raise EOFError unless result
259
256
 
260
257
  if str
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.51.0'
4
+ VERSION = '0.54.0'
5
5
  end
data/polyphony.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
  s.required_ruby_version = '>= 2.6'
23
23
 
24
24
  s.add_development_dependency 'rake-compiler', '1.1.1'
25
- s.add_development_dependency 'minitest', '5.13.0'
25
+ s.add_development_dependency 'minitest', '5.14.4'
26
26
  s.add_development_dependency 'minitest-reporters', '1.4.2'
27
27
  s.add_development_dependency 'simplecov', '0.17.1'
28
28
  s.add_development_dependency 'rubocop', '0.85.1'
@@ -37,9 +37,10 @@ Gem::Specification.new do |s|
37
37
  s.add_development_dependency 'mysql2', '0.5.3'
38
38
  s.add_development_dependency 'sequel', '5.34.0'
39
39
  s.add_development_dependency 'httparty', '0.17.1'
40
+ s.add_development_dependency 'localhost', '~>1.1.4'
40
41
 
41
- s.add_development_dependency 'jekyll', '~>3.8.6'
42
- s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
43
- s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
44
- s.add_development_dependency 'just-the-docs', '~>0.3.0'
42
+ # s.add_development_dependency 'jekyll', '~>3.8.6'
43
+ # s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
44
+ # s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
45
+ # s.add_development_dependency 'just-the-docs', '~>0.3.0'
45
46
  end
data/test/helper.rb CHANGED
@@ -52,7 +52,7 @@ class MiniTest::Test
52
52
  Fiber.current.instance_variable_set(:@auto_watcher, nil)
53
53
  Thread.current.backend.finalize
54
54
  Thread.current.backend = Polyphony::Backend.new
55
- sleep 0
55
+ sleep 0.001
56
56
  end
57
57
 
58
58
  def teardown
data/test/stress.rb CHANGED
@@ -8,6 +8,8 @@ def run_test(count)
8
8
  puts "#{count}: running tests..."
9
9
  # sleep 1
10
10
  system(TEST_CMD)
11
+ puts
12
+
11
13
  return if $?.exitstatus == 0
12
14
 
13
15
  puts "Failure after #{count} tests"