polyphony 0.42 → 0.43.4
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/CHANGELOG.md +27 -0
- data/Gemfile.lock +2 -2
- data/README.md +0 -1
- data/TODO.md +1 -2
- data/docs/_config.yml +2 -2
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/overview.md +3 -24
- data/docs/index.md +5 -1
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/performance/mem-usage.rb +34 -28
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +1 -1
- data/ext/polyphony/libev_agent.c +161 -146
- data/ext/polyphony/libev_queue.c +5 -4
- data/ext/polyphony/polyphony.c +0 -6
- data/ext/polyphony/polyphony_ext.c +0 -2
- data/ext/polyphony/thread.c +0 -6
- data/lib/polyphony.rb +26 -10
- data/lib/polyphony/adapters/redis.rb +3 -2
- data/lib/polyphony/core/global_api.rb +5 -3
- data/lib/polyphony/core/resource_pool.rb +19 -9
- data/lib/polyphony/extensions/core.rb +31 -0
- data/lib/polyphony/extensions/fiber.rb +0 -12
- data/lib/polyphony/extensions/io.rb +5 -1
- data/lib/polyphony/extensions/openssl.rb +34 -10
- data/lib/polyphony/extensions/socket.rb +2 -2
- data/lib/polyphony/version.rb +1 -1
- data/test/test_agent.rb +13 -7
- data/test/test_fiber.rb +3 -3
- data/test/test_global_api.rb +48 -15
- data/test/test_resource_pool.rb +12 -0
- data/test/test_socket.rb +5 -4
- data/test/test_throttler.rb +6 -5
- metadata +7 -3
- data/ext/polyphony/socket.c +0 -213
data/ext/polyphony/libev_queue.c
CHANGED
@@ -120,11 +120,12 @@ static VALUE LibevQueue_initialize(VALUE self) {
|
|
120
120
|
|
121
121
|
VALUE LibevQueue_push(VALUE self, VALUE value) {
|
122
122
|
LibevQueue_t *queue;
|
123
|
-
struct async_watcher *watcher;
|
124
123
|
GetQueue(self, queue);
|
125
|
-
|
126
|
-
|
127
|
-
|
124
|
+
if (queue->shift_queue.count > 0) {
|
125
|
+
struct async_watcher *watcher = async_queue_pop(&queue->shift_queue);
|
126
|
+
if (watcher) {
|
127
|
+
ev_async_send(watcher->ev_loop, &watcher->async);
|
128
|
+
}
|
128
129
|
}
|
129
130
|
rb_ary_push(queue->items, value);
|
130
131
|
return self;
|
data/ext/polyphony/polyphony.c
CHANGED
@@ -7,11 +7,8 @@ ID ID_call;
|
|
7
7
|
ID ID_caller;
|
8
8
|
ID ID_clear;
|
9
9
|
ID ID_each;
|
10
|
-
ID ID_empty;
|
11
10
|
ID ID_inspect;
|
12
11
|
ID ID_new;
|
13
|
-
ID ID_pop;
|
14
|
-
ID ID_push;
|
15
12
|
ID ID_raise;
|
16
13
|
ID ID_ivar_running;
|
17
14
|
ID ID_ivar_thread;
|
@@ -62,13 +59,10 @@ void Init_Polyphony() {
|
|
62
59
|
ID_caller = rb_intern("caller");
|
63
60
|
ID_clear = rb_intern("clear");
|
64
61
|
ID_each = rb_intern("each");
|
65
|
-
ID_empty = rb_intern("empty?");
|
66
62
|
ID_inspect = rb_intern("inspect");
|
67
63
|
ID_ivar_running = rb_intern("@running");
|
68
64
|
ID_ivar_thread = rb_intern("@thread");
|
69
65
|
ID_new = rb_intern("new");
|
70
|
-
ID_pop = rb_intern("pop");
|
71
|
-
ID_push = rb_intern("push");
|
72
66
|
ID_raise = rb_intern("raise");
|
73
67
|
ID_runnable = rb_intern("runnable");
|
74
68
|
ID_runnable_value = rb_intern("runnable_value");
|
@@ -4,7 +4,6 @@ void Init_Fiber();
|
|
4
4
|
void Init_Polyphony();
|
5
5
|
void Init_LibevAgent();
|
6
6
|
void Init_LibevQueue();
|
7
|
-
void Init_Socket();
|
8
7
|
void Init_Thread();
|
9
8
|
void Init_Tracing();
|
10
9
|
|
@@ -16,7 +15,6 @@ void Init_polyphony_ext() {
|
|
16
15
|
Init_LibevQueue();
|
17
16
|
|
18
17
|
Init_Fiber();
|
19
|
-
Init_Socket();
|
20
18
|
Init_Thread();
|
21
19
|
|
22
20
|
Init_Tracing();
|
data/ext/polyphony/thread.c
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
#include "polyphony.h"
|
2
2
|
|
3
3
|
ID ID_deactivate_all_watchers_post_fork;
|
4
|
-
ID ID_empty;
|
5
4
|
ID ID_ivar_agent;
|
6
5
|
ID ID_ivar_join_wait_queue;
|
7
6
|
ID ID_ivar_main_fiber;
|
8
7
|
ID ID_ivar_result;
|
9
8
|
ID ID_ivar_terminated;
|
10
|
-
ID ID_pop;
|
11
|
-
ID ID_push;
|
12
9
|
ID ID_run_queue;
|
13
10
|
ID ID_runnable_next;
|
14
11
|
ID ID_stop;
|
@@ -187,14 +184,11 @@ void Init_Thread() {
|
|
187
184
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
188
185
|
|
189
186
|
ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
|
190
|
-
ID_empty = rb_intern("empty?");
|
191
187
|
ID_ivar_agent = rb_intern("@agent");
|
192
188
|
ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
|
193
189
|
ID_ivar_main_fiber = rb_intern("@main_fiber");
|
194
190
|
ID_ivar_result = rb_intern("@result");
|
195
191
|
ID_ivar_terminated = rb_intern("@terminated");
|
196
|
-
ID_pop = rb_intern("pop");
|
197
|
-
ID_push = rb_intern("push");
|
198
192
|
ID_run_queue = rb_intern("run_queue");
|
199
193
|
ID_runnable_next = rb_intern("runnable_next");
|
200
194
|
ID_stop = rb_intern("stop");
|
data/lib/polyphony.rb
CHANGED
@@ -6,6 +6,12 @@ require_relative './polyphony_ext'
|
|
6
6
|
module Polyphony
|
7
7
|
# Map Queue to Libev queue implementation
|
8
8
|
Queue = LibevQueue
|
9
|
+
|
10
|
+
# replace core Queue class with our own
|
11
|
+
verbose = $VERBOSE
|
12
|
+
$VERBOSE = nil
|
13
|
+
Object.const_set(:Queue, Polyphony::Queue)
|
14
|
+
$VERBOSE = verbose
|
9
15
|
end
|
10
16
|
|
11
17
|
require_relative './polyphony/extensions/core'
|
@@ -94,17 +100,9 @@ module Polyphony
|
|
94
100
|
Polyphony::Process.watch(cmd, &block)
|
95
101
|
end
|
96
102
|
|
97
|
-
def emit_signal_exception(exception, fiber = Thread.main.main_fiber)
|
98
|
-
Thread.current.break_out_of_ev_loop(fiber, exception)
|
99
|
-
end
|
100
|
-
|
101
|
-
def install_terminating_signal_handler(signal, exception_class)
|
102
|
-
trap(signal) { emit_signal_exception(exception_class.new) }
|
103
|
-
end
|
104
|
-
|
105
103
|
def install_terminating_signal_handlers
|
106
|
-
|
107
|
-
|
104
|
+
trap('SIGTERM', SystemExit)
|
105
|
+
trap('SIGINT', Interrupt)
|
108
106
|
end
|
109
107
|
|
110
108
|
def terminate_threads
|
@@ -114,7 +112,25 @@ module Polyphony
|
|
114
112
|
threads.each(&:kill)
|
115
113
|
threads.each(&:join)
|
116
114
|
end
|
115
|
+
|
116
|
+
attr_accessor :original_pid
|
117
|
+
|
118
|
+
def install_at_exit_handler
|
119
|
+
@original_pid = ::Process.pid
|
120
|
+
|
121
|
+
# This at_exit handler is needed only when the original process exits. Due to
|
122
|
+
# the behaviour of fibers on fork (and especially on exit from forked
|
123
|
+
# processes,) we use a separate mechanism to terminate fibers in forked
|
124
|
+
# processes (see Polyphony.fork).
|
125
|
+
at_exit do
|
126
|
+
next unless @original_pid == ::Process.pid
|
127
|
+
|
128
|
+
Polyphony.terminate_threads
|
129
|
+
Fiber.current.shutdown_all_children
|
130
|
+
end
|
131
|
+
end
|
117
132
|
end
|
118
133
|
end
|
119
134
|
|
120
135
|
Polyphony.install_terminating_signal_handlers
|
136
|
+
Polyphony.install_at_exit_handler
|
@@ -6,7 +6,7 @@ require 'redis'
|
|
6
6
|
require 'hiredis/reader'
|
7
7
|
|
8
8
|
# Polyphony-based Redis driver
|
9
|
-
class
|
9
|
+
class Polyphony::RedisDriver
|
10
10
|
def self.connect(config)
|
11
11
|
raise 'unix sockets not supported' if config[:scheme] == 'unix'
|
12
12
|
|
@@ -43,6 +43,7 @@ class Driver
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def format_command(args)
|
46
|
+
args = args.flatten
|
46
47
|
(+"*#{args.size}\r\n").tap do |s|
|
47
48
|
args.each do |a|
|
48
49
|
a = a.to_s
|
@@ -63,4 +64,4 @@ class Driver
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
66
|
-
Redis::Connection.drivers <<
|
67
|
+
Redis::Connection.drivers << Polyphony::RedisDriver
|
@@ -15,11 +15,13 @@ module Polyphony
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def cancel_after(interval, &block)
|
18
|
+
def cancel_after(interval, with_exception: Polyphony::Cancel, &block)
|
19
19
|
fiber = ::Fiber.current
|
20
20
|
canceller = spin do
|
21
21
|
sleep interval
|
22
|
-
|
22
|
+
exception = with_exception.is_a?(Class) ?
|
23
|
+
with_exception.new : RuntimeError.new(with_exception)
|
24
|
+
fiber.schedule exception
|
23
25
|
end
|
24
26
|
block ? cancel_after_wrap_block(canceller, &block) : canceller
|
25
27
|
end
|
@@ -101,7 +103,7 @@ module Polyphony
|
|
101
103
|
|
102
104
|
def sleep_forever
|
103
105
|
Thread.current.agent.ref
|
104
|
-
|
106
|
+
loop { sleep 60 }
|
105
107
|
ensure
|
106
108
|
Thread.current.agent.unref
|
107
109
|
end
|
@@ -13,6 +13,7 @@ module Polyphony
|
|
13
13
|
|
14
14
|
@stock = []
|
15
15
|
@queue = []
|
16
|
+
@acquired_resources = {}
|
16
17
|
|
17
18
|
@limit = opts[:limit] || 4
|
18
19
|
@size = 0
|
@@ -23,16 +24,25 @@ module Polyphony
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def acquire
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
27
|
+
fiber = Fiber.current
|
28
|
+
if @acquired_resources[fiber]
|
29
|
+
yield @acquired_resources[fiber]
|
30
|
+
else
|
31
|
+
begin
|
32
|
+
Thread.current.agent.ref
|
33
|
+
resource = wait_for_resource
|
34
|
+
return unless resource
|
35
|
+
|
36
|
+
@acquired_resources[fiber] = resource
|
37
|
+
yield resource
|
38
|
+
ensure
|
39
|
+
@acquired_resources[fiber] = nil
|
40
|
+
Thread.current.agent.unref
|
41
|
+
release(resource) if resource
|
42
|
+
end
|
43
|
+
end
|
34
44
|
end
|
35
|
-
|
45
|
+
|
36
46
|
def wait_for_resource
|
37
47
|
fiber = Fiber.current
|
38
48
|
@queue << fiber
|
@@ -57,6 +57,12 @@ module ::Process
|
|
57
57
|
fiber.define_singleton_method(:pid) { pid }
|
58
58
|
fiber
|
59
59
|
end
|
60
|
+
|
61
|
+
alias_method :orig_daemon, :daemon
|
62
|
+
def daemon(*args)
|
63
|
+
orig_daemon(*args)
|
64
|
+
Polyphony.original_pid = Process.pid
|
65
|
+
end
|
60
66
|
end
|
61
67
|
end
|
62
68
|
|
@@ -120,6 +126,31 @@ module ::Kernel
|
|
120
126
|
break
|
121
127
|
end
|
122
128
|
end
|
129
|
+
|
130
|
+
alias_method :orig_trap, :trap
|
131
|
+
def trap(sig, command = nil, &block)
|
132
|
+
return orig_trap(sig, command) if command.is_a? String
|
133
|
+
|
134
|
+
block = command if command.respond_to?(:call) && !block
|
135
|
+
exception = command.is_a?(Class) && command.new
|
136
|
+
|
137
|
+
# The signal trap can be invoked at any time, including while the system
|
138
|
+
# agent is blocking while polling for events. In order to deal with this
|
139
|
+
# correctly, we spin a fiber that will run the signal handler code, then
|
140
|
+
# call break_out_of_ev_loop, which will put the fiber at the front of the
|
141
|
+
# run queue, then wake up the system agent.
|
142
|
+
#
|
143
|
+
# If the command argument is an exception class however, it will be raised
|
144
|
+
# directly in the context of the main fiber.
|
145
|
+
orig_trap(sig) do
|
146
|
+
if exception
|
147
|
+
Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
|
148
|
+
else
|
149
|
+
fiber = spin { snooze; block.call }
|
150
|
+
Thread.current.break_out_of_ev_loop(fiber, nil)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
123
154
|
end
|
124
155
|
|
125
156
|
# Override Timeout to use cancel scope
|
@@ -370,15 +370,3 @@ class ::Fiber
|
|
370
370
|
end
|
371
371
|
|
372
372
|
Fiber.current.setup_main_fiber
|
373
|
-
|
374
|
-
# This at_exit handler is needed only when the original process exits. Due to
|
375
|
-
# the behaviour of fibers on fork (and especially on exit from forked
|
376
|
-
# processes,) we use a separate mechanism to terminate fibers in forked
|
377
|
-
# processes (see Polyphony.fork).
|
378
|
-
orig_pid = Process.pid
|
379
|
-
at_exit do
|
380
|
-
next unless orig_pid == Process.pid
|
381
|
-
|
382
|
-
Polyphony.terminate_threads
|
383
|
-
Fiber.current.shutdown_all_children
|
384
|
-
end
|
@@ -97,7 +97,7 @@ class ::IO
|
|
97
97
|
# end
|
98
98
|
|
99
99
|
alias_method :orig_read, :read
|
100
|
-
def read(len =
|
100
|
+
def read(len = nil)
|
101
101
|
@read_buffer ||= +''
|
102
102
|
result = Thread.current.agent.read(self, @read_buffer, len, true)
|
103
103
|
return nil unless result
|
@@ -201,6 +201,10 @@ class ::IO
|
|
201
201
|
buf ? readpartial(maxlen, buf) : readpartial(maxlen)
|
202
202
|
end
|
203
203
|
|
204
|
+
def read_loop(&block)
|
205
|
+
Thread.current.agent.read_loop(self, &block)
|
206
|
+
end
|
207
|
+
|
204
208
|
# alias_method :orig_read, :read
|
205
209
|
# def read(length = nil, outbuf = nil)
|
206
210
|
# if length
|
@@ -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,12 +17,36 @@ class ::OpenSSL::SSL::SSLSocket
|
|
18
17
|
io.reuse_addr
|
19
18
|
end
|
20
19
|
|
21
|
-
|
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 = +'')
|
22
35
|
loop do
|
23
36
|
case (result = read_nonblock(maxlen, buf, exception: false))
|
24
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))
|
25
47
|
when :wait_writable then Thread.current.agent.wait_io(io, true)
|
26
|
-
else
|
48
|
+
else
|
49
|
+
return result
|
27
50
|
end
|
28
51
|
end
|
29
52
|
end
|
@@ -37,13 +60,14 @@ class ::OpenSSL::SSL::SSLSocket
|
|
37
60
|
# @sync = osync
|
38
61
|
end
|
39
62
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
47
71
|
end
|
48
72
|
end
|
49
73
|
end
|
@@ -128,11 +128,11 @@ class ::TCPServer
|
|
128
128
|
|
129
129
|
alias_method :orig_accept, :accept
|
130
130
|
def accept
|
131
|
-
@io
|
131
|
+
@io.accept
|
132
132
|
end
|
133
133
|
|
134
134
|
alias_method :orig_close, :close
|
135
135
|
def close
|
136
|
-
@io
|
136
|
+
@io.close
|
137
137
|
end
|
138
138
|
end
|
data/lib/polyphony/version.rb
CHANGED
data/test/test_agent.rb
CHANGED
@@ -21,10 +21,13 @@ class AgentTest < MiniTest::Test
|
|
21
21
|
spin {
|
22
22
|
@agent.sleep 0.01
|
23
23
|
count += 1
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
@agent.sleep 0.01
|
25
|
+
count += 1
|
26
|
+
@agent.sleep 0.01
|
27
|
+
count += 1
|
28
|
+
}.await
|
29
|
+
assert Time.now - t0 >= 0.03
|
30
|
+
assert_equal 3, count
|
28
31
|
end
|
29
32
|
|
30
33
|
def test_write_read_partial
|
@@ -88,10 +91,13 @@ class AgentTest < MiniTest::Test
|
|
88
91
|
buf << :done
|
89
92
|
end
|
90
93
|
|
94
|
+
# writing always causes snoozing
|
91
95
|
o << 'foo'
|
92
96
|
o << 'bar'
|
93
97
|
o.close
|
94
|
-
|
98
|
+
|
99
|
+
# read_loop will snooze after every read
|
100
|
+
4.times { snooze }
|
95
101
|
|
96
102
|
assert_equal [:ready, 'foo', 'bar', :done], buf
|
97
103
|
end
|
@@ -105,12 +111,12 @@ class AgentTest < MiniTest::Test
|
|
105
111
|
end
|
106
112
|
|
107
113
|
c1 = TCPSocket.new('127.0.0.1', 1234)
|
108
|
-
snooze
|
114
|
+
10.times { snooze }
|
109
115
|
|
110
116
|
assert_equal 1, clients.size
|
111
117
|
|
112
118
|
c2 = TCPSocket.new('127.0.0.1', 1234)
|
113
|
-
snooze
|
119
|
+
10.times { snooze }
|
114
120
|
|
115
121
|
assert_equal 2, clients.size
|
116
122
|
|