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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +11 -2
- data/.gitignore +2 -2
- data/.rubocop.yml +30 -0
- data/CHANGELOG.md +28 -2
- data/Gemfile +0 -11
- data/Gemfile.lock +15 -14
- data/README.md +2 -1
- data/Rakefile +7 -3
- data/TODO.md +28 -95
- data/docs/_config.yml +56 -7
- data/docs/_sass/custom/custom.scss +0 -30
- data/docs/_sass/overrides.scss +0 -46
- data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/{user-guide → _user-guide}/web-server.md +0 -0
- data/docs/api-reference/fiber.md +2 -2
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/polyphony-process.md +1 -1
- data/docs/api-reference/thread.md +1 -1
- data/docs/faq.md +21 -11
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +2 -6
- data/docs/getting-started/overview.md +507 -0
- data/docs/getting-started/tutorial.md +27 -19
- data/docs/index.md +3 -2
- data/docs/main-concepts/concurrency.md +0 -5
- data/docs/main-concepts/design-principles.md +69 -21
- data/docs/main-concepts/extending.md +1 -1
- data/docs/main-concepts/index.md +9 -0
- data/examples/core/01-spinning-up-fibers.rb +1 -0
- data/examples/core/03-interrupting.rb +4 -1
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-sleeping.rb +14 -6
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-irb.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +13 -36
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/ext/{gyro → polyphony}/extconf.rb +2 -2
- data/ext/{gyro → polyphony}/fiber.c +18 -22
- data/ext/{gyro → polyphony}/libev.c +0 -0
- data/ext/{gyro → polyphony}/libev.h +0 -0
- data/ext/polyphony/libev_agent.c +718 -0
- data/ext/polyphony/libev_queue.c +216 -0
- data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -46
- data/ext/{gyro/gyro.h → polyphony/polyphony.h} +25 -39
- data/ext/polyphony/polyphony_ext.c +23 -0
- data/ext/{gyro → polyphony}/socket.c +21 -18
- data/ext/polyphony/thread.c +206 -0
- data/ext/{gyro → polyphony}/tracing.c +1 -1
- data/lib/polyphony.rb +40 -44
- data/lib/polyphony/adapters/fs.rb +1 -4
- data/lib/polyphony/adapters/irb.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +6 -5
- data/lib/polyphony/adapters/process.rb +27 -23
- data/lib/polyphony/adapters/trace.rb +110 -105
- data/lib/polyphony/core/channel.rb +35 -35
- data/lib/polyphony/core/exceptions.rb +29 -29
- data/lib/polyphony/core/global_api.rb +94 -91
- data/lib/polyphony/core/resource_pool.rb +83 -83
- data/lib/polyphony/core/sync.rb +16 -16
- data/lib/polyphony/core/thread_pool.rb +49 -37
- data/lib/polyphony/core/throttler.rb +30 -23
- data/lib/polyphony/event.rb +27 -0
- data/lib/polyphony/extensions/core.rb +25 -17
- data/lib/polyphony/extensions/fiber.rb +269 -267
- data/lib/polyphony/extensions/io.rb +56 -26
- data/lib/polyphony/extensions/openssl.rb +5 -9
- data/lib/polyphony/extensions/socket.rb +29 -10
- data/lib/polyphony/extensions/thread.rb +19 -12
- data/lib/polyphony/net.rb +64 -60
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +4 -7
- data/test/helper.rb +14 -1
- data/test/stress.rb +17 -12
- data/test/test_agent.rb +124 -0
- data/test/{test_async.rb → test_event.rb} +15 -7
- data/test/test_ext.rb +25 -4
- data/test/test_fiber.rb +19 -10
- data/test/test_global_api.rb +4 -4
- data/test/test_io.rb +46 -24
- data/test/test_queue.rb +74 -0
- data/test/test_signal.rb +3 -40
- data/test/test_socket.rb +33 -0
- data/test/test_thread.rb +38 -16
- data/test/test_thread_pool.rb +2 -2
- data/test/test_throttler.rb +0 -1
- data/test/test_trace.rb +6 -5
- metadata +41 -57
- data/docs/_includes/nav.html +0 -51
- data/docs/_includes/prevnext.html +0 -17
- data/docs/_layouts/default.html +0 -106
- data/docs/api-reference.md +0 -11
- data/docs/api-reference/gyro-async.md +0 -57
- data/docs/api-reference/gyro-child.md +0 -29
- data/docs/api-reference/gyro-queue.md +0 -44
- data/docs/api-reference/gyro-timer.md +0 -51
- data/docs/api-reference/gyro.md +0 -25
- data/docs/getting-started.md +0 -10
- data/docs/main-concepts.md +0 -10
- data/docs/user-guide.md +0 -10
- data/examples/core/forever_sleep.rb +0 -19
- data/ext/gyro/async.c +0 -148
- data/ext/gyro/child.c +0 -127
- data/ext/gyro/gyro_ext.c +0 -33
- data/ext/gyro/io.c +0 -474
- data/ext/gyro/queue.c +0 -142
- data/ext/gyro/selector.c +0 -205
- data/ext/gyro/signal.c +0 -118
- data/ext/gyro/thread.c +0 -298
- data/ext/gyro/timer.c +0 -134
- data/test/test_timer.rb +0 -56
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'open3'
|
4
4
|
|
5
|
-
# IO
|
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
|
-
@
|
140
|
+
@read_buffer ||= +''
|
105
141
|
|
106
142
|
loop do
|
107
|
-
idx = @
|
108
|
-
return @
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
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 (
|
27
|
-
when :wait_writable then (
|
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 (
|
48
|
-
when :wait_writable then (
|
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
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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 =
|
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
|
25
|
+
rescue Polyphony::MoveOn, Polyphony::Terminate => e
|
23
26
|
result = e.value
|
24
|
-
rescue Exception =>
|
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
|
-
|
51
|
+
@agent.finalize
|
48
52
|
end
|
49
53
|
|
50
54
|
def signal_waiters(result)
|
51
|
-
@join_wait_queue.
|
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
|
-
|
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
|
66
|
+
@join_wait_queue << watcher
|
62
67
|
end
|
63
68
|
end
|
64
|
-
timeout ? move_on_after(timeout) {
|
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
|
-
|
86
|
+
return if @terminated
|
87
|
+
|
88
|
+
raise Polyphony::Terminate
|
82
89
|
end
|
83
90
|
|
84
91
|
alias_method :orig_inspect, :inspect
|
data/lib/polyphony/net.rb
CHANGED
@@ -1,73 +1,77 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require_relative './extensions/socket'
|
4
|
+
require_relative './extensions/openssl'
|
5
5
|
|
6
|
-
|
7
|
-
|
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
|
10
|
-
|
11
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
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
|