polyphony 0.19 → 0.20
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/.gitignore +1 -1
- data/.rubocop.yml +87 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +17 -6
- data/README.md +200 -139
- data/Rakefile +4 -4
- data/TODO.md +35 -7
- data/bin/poly +11 -0
- data/docs/getting-started/getting-started.md +1 -1
- data/docs/summary.md +3 -0
- data/docs/technical-overview/exception-handling.md +94 -0
- data/docs/technical-overview/fiber-scheduling.md +99 -0
- data/examples/core/cancel.rb +8 -4
- data/examples/core/channel_echo.rb +18 -17
- data/examples/core/defer.rb +12 -0
- data/examples/core/enumerator.rb +4 -4
- data/examples/core/fiber_error.rb +9 -0
- data/examples/core/fiber_error_with_backtrace.rb +73 -0
- data/examples/core/fork.rb +6 -6
- data/examples/core/genserver.rb +16 -8
- data/examples/core/lock.rb +3 -3
- data/examples/core/move_on.rb +4 -3
- data/examples/core/move_on_twice.rb +5 -5
- data/examples/core/move_on_with_ensure.rb +8 -11
- data/examples/core/move_on_with_value.rb +14 -0
- data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
- data/examples/core/nested_cancel.rb +5 -5
- data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
- data/examples/core/nested_spin.rb +17 -0
- data/examples/core/pingpong.rb +21 -0
- data/examples/core/pulse.rb +4 -5
- data/examples/core/resource.rb +6 -4
- data/examples/core/resource_cancel.rb +6 -9
- data/examples/core/resource_delegate.rb +3 -3
- data/examples/core/sleep.rb +3 -3
- data/examples/core/sleep_spin.rb +19 -0
- data/examples/core/snooze.rb +32 -0
- data/examples/core/spin.rb +14 -0
- data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
- data/examples/core/spin_error.rb +17 -0
- data/examples/core/spin_error_backtrace.rb +30 -0
- data/examples/core/spin_uncaught_error.rb +15 -0
- data/examples/core/supervisor.rb +8 -8
- data/examples/core/supervisor_with_cancel_scope.rb +7 -7
- data/examples/core/supervisor_with_error.rb +8 -8
- data/examples/core/supervisor_with_manual_move_on.rb +6 -7
- data/examples/core/suspend.rb +13 -0
- data/examples/core/thread.rb +1 -1
- data/examples/core/thread_cancel.rb +9 -11
- data/examples/core/thread_pool.rb +18 -14
- data/examples/core/throttle.rb +7 -7
- data/examples/core/timeout.rb +3 -3
- data/examples/fs/read.rb +7 -9
- data/examples/http/config.ru +7 -3
- data/examples/http/cuba.ru +22 -0
- data/examples/http/happy_eyeballs.rb +6 -4
- data/examples/http/http_client.rb +1 -1
- data/examples/http/http_get.rb +1 -1
- data/examples/http/http_parse_experiment.rb +21 -16
- data/examples/http/http_proxy.rb +28 -26
- data/examples/http/http_server.rb +10 -10
- data/examples/http/http_server_forked.rb +6 -5
- data/examples/http/http_server_throttled.rb +3 -3
- data/examples/http/http_ws_server.rb +11 -11
- data/examples/http/https_raw_client.rb +1 -1
- data/examples/http/https_server.rb +8 -8
- data/examples/http/https_wss_server.rb +13 -11
- data/examples/http/rack_server.rb +2 -2
- data/examples/http/rack_server_https.rb +4 -4
- data/examples/http/rack_server_https_forked.rb +5 -5
- data/examples/http/websocket_secure_server.rb +6 -6
- data/examples/http/websocket_server.rb +5 -5
- data/examples/interfaces/pg_client.rb +4 -4
- data/examples/interfaces/pg_pool.rb +13 -6
- data/examples/interfaces/pg_transaction.rb +5 -4
- data/examples/interfaces/redis_channels.rb +15 -11
- data/examples/interfaces/redis_client.rb +2 -2
- data/examples/interfaces/redis_pubsub.rb +2 -1
- data/examples/interfaces/redis_pubsub_perf.rb +13 -9
- data/examples/io/backticks.rb +11 -0
- data/examples/io/cat.rb +4 -5
- data/examples/io/echo_client.rb +9 -4
- data/examples/io/echo_client_from_stdin.rb +20 -0
- data/examples/io/echo_pipe.rb +7 -8
- data/examples/io/echo_server.rb +8 -6
- data/examples/io/echo_server_with_timeout.rb +13 -10
- data/examples/io/echo_stdin.rb +3 -3
- data/examples/io/httparty.rb +2 -2
- data/examples/io/httparty_multi.rb +8 -4
- data/examples/io/httparty_threaded.rb +6 -2
- data/examples/io/io_read.rb +2 -2
- data/examples/io/irb.rb +16 -4
- data/examples/io/net-http.rb +3 -3
- data/examples/io/open.rb +17 -0
- data/examples/io/system.rb +3 -3
- data/examples/io/tcpserver.rb +15 -0
- data/examples/io/tcpsocket.rb +6 -5
- data/examples/performance/multi_snooze.rb +29 -0
- data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
- data/examples/performance/snooze_raw.rb +39 -0
- data/ext/gyro/async.c +165 -0
- data/ext/gyro/child.c +167 -0
- data/ext/{ev → gyro}/extconf.rb +4 -3
- data/ext/gyro/gyro.c +316 -0
- data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
- data/ext/gyro/gyro_ext.c +23 -0
- data/ext/{ev → gyro}/io.c +65 -57
- data/ext/{ev → gyro}/libev.h +0 -0
- data/ext/gyro/signal.c +117 -0
- data/ext/{ev → gyro}/socket.c +61 -6
- data/ext/gyro/timer.c +199 -0
- data/ext/libev/Changes +35 -0
- data/ext/libev/README +2 -1
- data/ext/libev/ev.c +213 -151
- data/ext/libev/ev.h +95 -88
- data/ext/libev/ev_epoll.c +26 -15
- data/ext/libev/ev_kqueue.c +11 -5
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +13 -8
- data/ext/libev/ev_port.c +5 -2
- data/ext/libev/ev_vars.h +14 -3
- data/ext/libev/ev_wrap.h +16 -0
- data/lib/ev_ext.bundle +0 -0
- data/lib/polyphony.rb +46 -50
- data/lib/polyphony/auto_run.rb +12 -0
- data/lib/polyphony/core/cancel_scope.rb +11 -7
- data/lib/polyphony/core/channel.rb +16 -9
- data/lib/polyphony/core/coprocess.rb +101 -51
- data/lib/polyphony/core/exceptions.rb +14 -12
- data/lib/polyphony/core/resource_pool.rb +21 -8
- data/lib/polyphony/core/supervisor.rb +10 -5
- data/lib/polyphony/core/sync.rb +7 -6
- data/lib/polyphony/core/thread.rb +4 -4
- data/lib/polyphony/core/thread_pool.rb +4 -4
- data/lib/polyphony/core/throttler.rb +6 -4
- data/lib/polyphony/extensions/core.rb +253 -0
- data/lib/polyphony/extensions/io.rb +28 -16
- data/lib/polyphony/extensions/openssl.rb +2 -1
- data/lib/polyphony/extensions/socket.rb +47 -52
- data/lib/polyphony/http.rb +4 -3
- data/lib/polyphony/http/agent.rb +68 -57
- data/lib/polyphony/http/server.rb +5 -5
- data/lib/polyphony/http/server/http1.rb +268 -0
- data/lib/polyphony/http/server/http2.rb +62 -0
- data/lib/polyphony/http/server/http2_stream.rb +104 -0
- data/lib/polyphony/http/server/rack.rb +64 -0
- data/lib/polyphony/http/server/request.rb +119 -0
- data/lib/polyphony/net.rb +26 -15
- data/lib/polyphony/postgres.rb +17 -13
- data/lib/polyphony/redis.rb +16 -15
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony/websocket.rb +11 -4
- data/polyphony.gemspec +13 -9
- data/test/eg.rb +27 -0
- data/test/helper.rb +25 -0
- data/test/run.rb +5 -0
- data/test/test_async.rb +33 -0
- data/test/test_coprocess.rb +239 -77
- data/test/test_core.rb +95 -61
- data/test/test_gyro.rb +148 -0
- data/test/test_http_server.rb +313 -0
- data/test/test_io.rb +79 -27
- data/test/test_kernel.rb +22 -12
- data/test/test_signal.rb +36 -0
- data/test/test_timer.rb +24 -0
- metadata +89 -33
- data/examples/core/nested_async.rb +0 -17
- data/examples/core/next_tick.rb +0 -12
- data/examples/core/sleep_spawn.rb +0 -19
- data/examples/core/spawn.rb +0 -14
- data/examples/core/spawn_error.rb +0 -28
- data/examples/performance/perf_multi_snooze.rb +0 -21
- data/ext/ev/async.c +0 -168
- data/ext/ev/child.c +0 -169
- data/ext/ev/ev_ext.c +0 -23
- data/ext/ev/ev_module.c +0 -242
- data/ext/ev/signal.c +0 -119
- data/ext/ev/timer.c +0 -197
- data/lib/polyphony/core/fiber_pool.rb +0 -98
- data/lib/polyphony/extensions/kernel.rb +0 -169
- data/lib/polyphony/http/http1_adapter.rb +0 -254
- data/lib/polyphony/http/http2_adapter.rb +0 -157
- data/lib/polyphony/http/rack.rb +0 -25
- data/lib/polyphony/http/request.rb +0 -66
- data/test/test_ev.rb +0 -110
@@ -1,157 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export_default :Protocol
|
4
|
-
|
5
|
-
require 'http/2'
|
6
|
-
|
7
|
-
Request = import('./request')
|
8
|
-
|
9
|
-
class StreamWrapper
|
10
|
-
def initialize(stream, parse_fiber)
|
11
|
-
@stream = stream
|
12
|
-
@parse_fiber = parse_fiber
|
13
|
-
@parsing = true
|
14
|
-
|
15
|
-
stream.on(:data) { |data| on_body(data) }
|
16
|
-
stream.on(:half_close) { on_message_complete }
|
17
|
-
end
|
18
|
-
|
19
|
-
# Reads body chunk from connection
|
20
|
-
def get_body_chunk
|
21
|
-
@calling_fiber = Fiber.current
|
22
|
-
@read_body = true
|
23
|
-
@parse_fiber.safe_transfer
|
24
|
-
end
|
25
|
-
|
26
|
-
# Wait for request to finish
|
27
|
-
def consume_request
|
28
|
-
return unless @parsing
|
29
|
-
|
30
|
-
@calling_fiber = Fiber.current
|
31
|
-
@read_body = false
|
32
|
-
@parse_fiber.safe_transfer while @parsing
|
33
|
-
end
|
34
|
-
|
35
|
-
def on_body(data)
|
36
|
-
@calling_fiber.transfer(data) if @read_body
|
37
|
-
end
|
38
|
-
|
39
|
-
def on_message_complete
|
40
|
-
@parsing = false
|
41
|
-
@calling_fiber.transfer nil
|
42
|
-
end
|
43
|
-
|
44
|
-
# response API
|
45
|
-
def respond(chunk, headers)
|
46
|
-
consume_request if @parsing
|
47
|
-
|
48
|
-
headers[':status'] ||= '200'
|
49
|
-
@stream.headers(headers, end_stream: false)
|
50
|
-
@stream.data(chunk, end_stream: true)
|
51
|
-
@headers_sent = true
|
52
|
-
end
|
53
|
-
|
54
|
-
def send_headers(headers, empty_response = false)
|
55
|
-
return if @headers_sent
|
56
|
-
|
57
|
-
consume_request if @parsing
|
58
|
-
|
59
|
-
headers[':status'] ||= (empty_response ? 204 : 200).to_s
|
60
|
-
@stream.headers(headers, end_stream: false)
|
61
|
-
@headers_sent = true
|
62
|
-
end
|
63
|
-
|
64
|
-
def send_body_chunk(chunk, done: false)
|
65
|
-
send_headers({}, false) unless @headers_sent
|
66
|
-
@stream.data(chunk, end_stream: done)
|
67
|
-
end
|
68
|
-
|
69
|
-
def finish
|
70
|
-
consume_request if @parsing
|
71
|
-
|
72
|
-
unless @headers_sent
|
73
|
-
headers[':status'] ||= '204'
|
74
|
-
@stream.headers(headers, end_stream: true)
|
75
|
-
else
|
76
|
-
@stream.close
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
class Protocol
|
82
|
-
def self.upgrade_each(socket, opts, headers, &block)
|
83
|
-
adapter = new(socket, opts, headers)
|
84
|
-
adapter.each(&block)
|
85
|
-
end
|
86
|
-
|
87
|
-
def initialize(conn, opts, upgrade_headers = nil)
|
88
|
-
@conn = conn
|
89
|
-
@opts = opts
|
90
|
-
|
91
|
-
@interface = ::HTTP2::Server.new
|
92
|
-
@interface.on(:frame) { |bytes| conn << bytes }
|
93
|
-
@interface.on(:stream) { |stream| start_stream(stream) }
|
94
|
-
@parse_fiber = Fiber.new { parse_loop(upgrade_headers) }
|
95
|
-
end
|
96
|
-
|
97
|
-
def start_stream(stream)
|
98
|
-
stream.on(:headers) do |headers|
|
99
|
-
@calling_fiber.transfer([stream, headers.to_h])
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def protocol
|
104
|
-
'h2'
|
105
|
-
end
|
106
|
-
|
107
|
-
def parse_loop(upgrade_headers)
|
108
|
-
upgrade(upgrade_headers) if upgrade_headers
|
109
|
-
|
110
|
-
while (data = @conn.readpartial(8192))
|
111
|
-
@interface << data
|
112
|
-
snooze
|
113
|
-
end
|
114
|
-
@calling_fiber.transfer nil
|
115
|
-
rescue SystemCallError, IOError
|
116
|
-
# ignore
|
117
|
-
@calling_fiber.transfer nil
|
118
|
-
rescue => error
|
119
|
-
# an error return value will be raised by the receiving fiber
|
120
|
-
@calling_fiber.transfer error
|
121
|
-
end
|
122
|
-
|
123
|
-
# request API
|
124
|
-
|
125
|
-
UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
|
126
|
-
HTTP/1.1 101 Switching Protocols
|
127
|
-
Connection: Upgrade
|
128
|
-
Upgrade: h2c
|
129
|
-
|
130
|
-
HTTP
|
131
|
-
|
132
|
-
def upgrade(headers)
|
133
|
-
settings = headers['HTTP2-Settings']
|
134
|
-
@conn << UPGRADE_MESSAGE
|
135
|
-
@interface.upgrade(settings, headers, '')
|
136
|
-
end
|
137
|
-
|
138
|
-
# Iterates over incoming requests
|
139
|
-
def each(&block)
|
140
|
-
can_upgrade = true
|
141
|
-
while @parse_fiber.alive?
|
142
|
-
stream, headers = get_headers
|
143
|
-
break unless stream
|
144
|
-
wrapper = StreamWrapper.new(stream, @parse_fiber)
|
145
|
-
request = Request.new(headers, wrapper)
|
146
|
-
Fiber.new { block.(request) }.resume
|
147
|
-
end
|
148
|
-
ensure
|
149
|
-
@conn.close rescue nil
|
150
|
-
end
|
151
|
-
|
152
|
-
# Reads headers from connection
|
153
|
-
def get_headers
|
154
|
-
@calling_fiber = Fiber.current
|
155
|
-
@parse_fiber.safe_transfer
|
156
|
-
end
|
157
|
-
end
|
data/lib/polyphony/http/rack.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export :load
|
4
|
-
|
5
|
-
def run(app)
|
6
|
-
->(req) {
|
7
|
-
response = app.(env(req))
|
8
|
-
respond(req, response)
|
9
|
-
}
|
10
|
-
end
|
11
|
-
|
12
|
-
def load(path)
|
13
|
-
src = IO.read(path)
|
14
|
-
instance_eval(src)
|
15
|
-
end
|
16
|
-
|
17
|
-
def env(request)
|
18
|
-
{ }
|
19
|
-
end
|
20
|
-
|
21
|
-
def respond(request, (status_code, headers, body))
|
22
|
-
headers[':status'] = status_code.to_s
|
23
|
-
body = body.first
|
24
|
-
request.respond(body, headers)
|
25
|
-
end
|
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
export_default :Request
|
4
|
-
|
5
|
-
require 'uri'
|
6
|
-
|
7
|
-
class Request
|
8
|
-
attr_reader :headers, :adapter
|
9
|
-
|
10
|
-
def initialize(headers, adapter)
|
11
|
-
@headers = headers
|
12
|
-
@adapter = adapter
|
13
|
-
end
|
14
|
-
|
15
|
-
def protocol
|
16
|
-
@adapter.protocol
|
17
|
-
end
|
18
|
-
|
19
|
-
def method
|
20
|
-
@method ||= @headers[':method']
|
21
|
-
end
|
22
|
-
|
23
|
-
def scheme
|
24
|
-
@scheme ||= @headers[':scheme']
|
25
|
-
end
|
26
|
-
|
27
|
-
def uri
|
28
|
-
@uri ||= URI.parse(@headers[':path'] || '')
|
29
|
-
end
|
30
|
-
|
31
|
-
def path
|
32
|
-
@path ||= uri.path
|
33
|
-
end
|
34
|
-
|
35
|
-
def query
|
36
|
-
@uri ||= URI.parse(@headers[':path'] || '')
|
37
|
-
return @query if @query
|
38
|
-
|
39
|
-
if (q = uri.query)
|
40
|
-
@query = q.split('&').each_with_object({}) do |kv, h|
|
41
|
-
k, v = kv.split('=')
|
42
|
-
h[k.to_sym] = URI.decode_www_form_component(v)
|
43
|
-
end
|
44
|
-
else
|
45
|
-
@query = {}
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
EMPTY_HASH = {}
|
50
|
-
|
51
|
-
def respond(chunk, headers = EMPTY_HASH)
|
52
|
-
@adapter.respond(chunk, headers)
|
53
|
-
end
|
54
|
-
|
55
|
-
def send_headers(headers = EMPTY_HASH, empty_response = false)
|
56
|
-
@adapter.send_headers(headers, empty_response)
|
57
|
-
end
|
58
|
-
|
59
|
-
def send_body_chunk(body, done: false)
|
60
|
-
@adapter.send_body_chunk(body, done: done)
|
61
|
-
end
|
62
|
-
|
63
|
-
def finish
|
64
|
-
@adapter.finish
|
65
|
-
end
|
66
|
-
end
|
data/test/test_ev.rb
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'bundler/setup'
|
3
|
-
require 'polyphony'
|
4
|
-
|
5
|
-
class EVRunTest < Minitest::Test
|
6
|
-
def setup
|
7
|
-
EV.rerun
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_that_run_loop_returns_immediately_if_no_watchers
|
11
|
-
t0 = Time.now
|
12
|
-
suspend
|
13
|
-
t1 = Time.now
|
14
|
-
assert (t1 - t0) < 0.001
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class EVTimerTest < MiniTest::Test
|
19
|
-
def setup
|
20
|
-
EV.rerun
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_that_one_shot_timer_works
|
24
|
-
count = 0
|
25
|
-
t = EV::Timer.new(0.01, 0)
|
26
|
-
t.start { count += 1}
|
27
|
-
suspend
|
28
|
-
assert_equal(1, count)
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_that_repeating_timer_works
|
32
|
-
count = 0
|
33
|
-
t = EV::Timer.new(0.001, 0.001)
|
34
|
-
t.start { count += 1; t.stop if count >= 3}
|
35
|
-
suspend
|
36
|
-
assert_equal(3, count)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class EVIOTest < MiniTest::Test
|
41
|
-
def setup
|
42
|
-
EV.rerun
|
43
|
-
end
|
44
|
-
|
45
|
-
def test_that_reading_works
|
46
|
-
i, o = IO.pipe
|
47
|
-
data = +''
|
48
|
-
w = EV::IO.new(i, :r)
|
49
|
-
w.start do
|
50
|
-
i.read_nonblock(8192, data)
|
51
|
-
w.stop unless data.empty?
|
52
|
-
end
|
53
|
-
EV::Timer.new(0, 0).start { o << 'hello' }
|
54
|
-
suspend
|
55
|
-
assert_equal('hello', data)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
class EVSignalTest < MiniTest::Test
|
60
|
-
def setup
|
61
|
-
EV.rerun
|
62
|
-
EV.restart
|
63
|
-
end
|
64
|
-
|
65
|
-
def test_that_signal_watcher_receives_signal
|
66
|
-
sig = Signal.list['USR1']
|
67
|
-
count = 0
|
68
|
-
w = EV::Signal.new(sig) { count += 1; w.stop }
|
69
|
-
Thread.new { sync_sleep 0.001; Process.kill(:USR1, Process.pid) }
|
70
|
-
suspend
|
71
|
-
assert_equal(1, count)
|
72
|
-
end
|
73
|
-
|
74
|
-
def test_that_signal_watcher_receives_signal
|
75
|
-
count = 0
|
76
|
-
w = Polyphony.trap(:usr1, true) { count += 1; w.stop }
|
77
|
-
assert_kind_of(EV::Signal, w)
|
78
|
-
Thread.new { sync_sleep 0.001; Process.kill(:USR1, Process.pid) }
|
79
|
-
suspend
|
80
|
-
assert_equal(1, count)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
class EVAsyncTest < MiniTest::Test
|
85
|
-
def setup
|
86
|
-
EV.rerun
|
87
|
-
end
|
88
|
-
|
89
|
-
def test_that_async_watcher_receives_signal_across_threads
|
90
|
-
count = 0
|
91
|
-
a = EV::Async.new { count += 1; a.stop }
|
92
|
-
Thread.new { sync_sleep 0.001; a.signal! }
|
93
|
-
suspend
|
94
|
-
assert_equal(1, count)
|
95
|
-
end
|
96
|
-
|
97
|
-
def test_that_async_watcher_coalesces_signals
|
98
|
-
count = 0
|
99
|
-
a = EV::Async.new do
|
100
|
-
count += 1
|
101
|
-
EV::Timer.new(0.01, 0).start { a.stop }
|
102
|
-
end
|
103
|
-
Thread.new do
|
104
|
-
sync_sleep 0.001
|
105
|
-
3.times { a.signal! }
|
106
|
-
end
|
107
|
-
suspend
|
108
|
-
assert_equal(1, count)
|
109
|
-
end
|
110
|
-
end
|