polyphony 0.43 → 0.43.5
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 +1 -1
- data/CHANGELOG.md +29 -0
- data/Gemfile.lock +2 -2
- data/README.md +0 -1
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/overview.md +2 -2
- data/docs/index.md +6 -3
- data/docs/main-concepts/design-principles.md +23 -34
- data/docs/main-concepts/fiber-scheduling.md +1 -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/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +11 -9
- data/ext/polyphony/libev_agent.c +181 -151
- data/ext/polyphony/libev_queue.c +129 -57
- data/ext/polyphony/polyphony.c +0 -6
- data/ext/polyphony/polyphony.h +12 -5
- data/ext/polyphony/polyphony_ext.c +0 -2
- data/ext/polyphony/ring_buffer.c +120 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +13 -13
- 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/core/thread_pool.rb +1 -1
- data/lib/polyphony/extensions/core.rb +40 -0
- data/lib/polyphony/extensions/fiber.rb +8 -13
- data/lib/polyphony/extensions/io.rb +17 -16
- data/lib/polyphony/extensions/socket.rb +12 -2
- data/lib/polyphony/version.rb +1 -1
- data/test/q.rb +24 -0
- data/test/test_agent.rb +13 -7
- data/test/test_fiber.rb +3 -3
- data/test/test_global_api.rb +50 -17
- data/test/test_io.rb +10 -2
- data/test/test_queue.rb +26 -1
- data/test/test_resource_pool.rb +12 -0
- data/test/test_throttler.rb +6 -5
- metadata +11 -3
- data/ext/polyphony/socket.c +0 -213
@@ -0,0 +1,28 @@
|
|
1
|
+
#ifndef RING_BUFFER_H
|
2
|
+
#define RING_BUFFER_H
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
|
6
|
+
typedef struct ring_buffer {
|
7
|
+
VALUE *entries;
|
8
|
+
unsigned int size;
|
9
|
+
unsigned int count;
|
10
|
+
unsigned int head;
|
11
|
+
unsigned int tail;
|
12
|
+
} ring_buffer;
|
13
|
+
|
14
|
+
void ring_buffer_init(ring_buffer *buffer);
|
15
|
+
void ring_buffer_free(ring_buffer *buffer);
|
16
|
+
void ring_buffer_mark(ring_buffer *buffer);
|
17
|
+
int ring_buffer_empty_p(ring_buffer *buffer);
|
18
|
+
void ring_buffer_clear(ring_buffer *buffer);
|
19
|
+
|
20
|
+
VALUE ring_buffer_shift(ring_buffer *buffer);
|
21
|
+
void ring_buffer_unshift(ring_buffer *buffer, VALUE value);
|
22
|
+
void ring_buffer_push(ring_buffer *buffer, VALUE value);
|
23
|
+
|
24
|
+
void ring_buffer_shift_each(ring_buffer *buffer);
|
25
|
+
VALUE ring_buffer_shift_all(ring_buffer *buffer);
|
26
|
+
void ring_buffer_delete(ring_buffer *buffer, VALUE value);
|
27
|
+
|
28
|
+
#endif /* RING_BUFFER_H */
|
data/ext/polyphony/thread.c
CHANGED
@@ -1,23 +1,19 @@
|
|
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;
|
15
12
|
|
16
13
|
static VALUE Thread_setup_fiber_scheduling(VALUE self) {
|
17
|
-
VALUE queue;
|
14
|
+
VALUE queue = rb_funcall(cLibevQueue, ID_new, 0);
|
18
15
|
|
19
16
|
rb_ivar_set(self, ID_ivar_main_fiber, rb_fiber_current());
|
20
|
-
queue = rb_ary_new();
|
21
17
|
rb_ivar_set(self, ID_run_queue, queue);
|
22
18
|
|
23
19
|
return self;
|
@@ -64,7 +60,7 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
64
60
|
}
|
65
61
|
|
66
62
|
queue = rb_ivar_get(self, ID_run_queue);
|
67
|
-
|
63
|
+
LibevQueue_push(queue, fiber);
|
68
64
|
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
69
65
|
|
70
66
|
if (rb_thread_current() != self) {
|
@@ -91,13 +87,13 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
|
|
91
87
|
|
92
88
|
// if fiber is already scheduled, remove it from the run queue
|
93
89
|
if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
|
94
|
-
|
90
|
+
LibevQueue_delete(queue, fiber);
|
95
91
|
} else {
|
96
92
|
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
97
93
|
}
|
98
94
|
|
99
95
|
// the fiber is given priority by putting it at the front of the run queue
|
100
|
-
|
96
|
+
LibevQueue_unshift(queue, fiber);
|
101
97
|
|
102
98
|
if (rb_thread_current() != self) {
|
103
99
|
// if the fiber scheduling is done across threads, we need to make sure the
|
@@ -127,7 +123,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
127
123
|
|
128
124
|
ref_count = LibevAgent_ref_count(agent);
|
129
125
|
while (1) {
|
130
|
-
next_fiber =
|
126
|
+
next_fiber = LibevQueue_shift_no_wait(queue);
|
131
127
|
if (next_fiber != Qnil) {
|
132
128
|
if (ref_count > 0) {
|
133
129
|
// this mechanism prevents event starvation in case the run queue never
|
@@ -154,9 +150,15 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
154
150
|
value : rb_funcall(next_fiber, ID_transfer, 1, value);
|
155
151
|
}
|
156
152
|
|
153
|
+
VALUE Thread_run_queue_trace(VALUE self) {
|
154
|
+
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
155
|
+
LibevQueue_trace(queue);
|
156
|
+
return self;
|
157
|
+
}
|
158
|
+
|
157
159
|
VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
158
160
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
159
|
-
|
161
|
+
LibevQueue_clear(queue);
|
160
162
|
Thread_fiber_reset_ref_count(self);
|
161
163
|
return self;
|
162
164
|
}
|
@@ -185,16 +187,14 @@ void Init_Thread() {
|
|
185
187
|
rb_define_method(rb_cThread, "schedule_fiber_with_priority",
|
186
188
|
Thread_schedule_fiber_with_priority, 2);
|
187
189
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
190
|
+
rb_define_method(rb_cThread, "run_queue_trace", Thread_run_queue_trace, 0);
|
188
191
|
|
189
192
|
ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
|
190
|
-
ID_empty = rb_intern("empty?");
|
191
193
|
ID_ivar_agent = rb_intern("@agent");
|
192
194
|
ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
|
193
195
|
ID_ivar_main_fiber = rb_intern("@main_fiber");
|
194
196
|
ID_ivar_result = rb_intern("@result");
|
195
197
|
ID_ivar_terminated = rb_intern("@terminated");
|
196
|
-
ID_pop = rb_intern("pop");
|
197
|
-
ID_push = rb_intern("push");
|
198
198
|
ID_run_queue = rb_intern("run_queue");
|
199
199
|
ID_runnable_next = rb_intern("runnable_next");
|
200
200
|
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
|
|
@@ -101,6 +107,15 @@ module ::Kernel
|
|
101
107
|
$stdin.gets
|
102
108
|
end
|
103
109
|
|
110
|
+
alias_method :orig_p, :p
|
111
|
+
def p(*args)
|
112
|
+
strs = args.inject([]) do |m, a|
|
113
|
+
m << a.inspect << "\n"
|
114
|
+
end
|
115
|
+
STDOUT.write *strs
|
116
|
+
args.size == 1 ? args.first : args
|
117
|
+
end
|
118
|
+
|
104
119
|
alias_method :orig_system, :system
|
105
120
|
def system(*args)
|
106
121
|
Open3.popen2(*args) do |i, o, _t|
|
@@ -120,6 +135,31 @@ module ::Kernel
|
|
120
135
|
break
|
121
136
|
end
|
122
137
|
end
|
138
|
+
|
139
|
+
alias_method :orig_trap, :trap
|
140
|
+
def trap(sig, command = nil, &block)
|
141
|
+
return orig_trap(sig, command) if command.is_a? String
|
142
|
+
|
143
|
+
block = command if command.respond_to?(:call) && !block
|
144
|
+
exception = command.is_a?(Class) && command.new
|
145
|
+
|
146
|
+
# The signal trap can be invoked at any time, including while the system
|
147
|
+
# agent is blocking while polling for events. In order to deal with this
|
148
|
+
# correctly, we spin a fiber that will run the signal handler code, then
|
149
|
+
# call break_out_of_ev_loop, which will put the fiber at the front of the
|
150
|
+
# run queue, then wake up the system agent.
|
151
|
+
#
|
152
|
+
# If the command argument is an exception class however, it will be raised
|
153
|
+
# directly in the context of the main fiber.
|
154
|
+
orig_trap(sig) do
|
155
|
+
if exception
|
156
|
+
Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
|
157
|
+
else
|
158
|
+
fiber = spin { snooze; block.call }
|
159
|
+
Thread.current.break_out_of_ev_loop(fiber, nil)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
123
163
|
end
|
124
164
|
|
125
165
|
# Override Timeout to use cancel scope
|
@@ -189,7 +189,7 @@ module Polyphony
|
|
189
189
|
end
|
190
190
|
|
191
191
|
def receive_pending
|
192
|
-
@mailbox.
|
192
|
+
@mailbox.shift_all
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
@@ -221,6 +221,13 @@ module Polyphony
|
|
221
221
|
def await_all_children
|
222
222
|
return unless @children && !@children.empty?
|
223
223
|
|
224
|
+
# @results = @children.dup
|
225
|
+
# @on_child_done = proc do |c, r|
|
226
|
+
# @results[c] = r
|
227
|
+
# self.schedule if @children.empty?
|
228
|
+
# end
|
229
|
+
# suspend
|
230
|
+
# @results.values
|
224
231
|
Fiber.await(*@children.keys)
|
225
232
|
end
|
226
233
|
|
@@ -370,15 +377,3 @@ class ::Fiber
|
|
370
377
|
end
|
371
378
|
|
372
379
|
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
|
@@ -108,19 +108,23 @@ class ::IO
|
|
108
108
|
end
|
109
109
|
|
110
110
|
alias_method :orig_readpartial, :read
|
111
|
-
def readpartial(len)
|
111
|
+
def readpartial(len, str = nil)
|
112
112
|
@read_buffer ||= +''
|
113
113
|
result = Thread.current.agent.read(self, @read_buffer, len, false)
|
114
114
|
raise EOFError unless result
|
115
115
|
|
116
|
-
|
116
|
+
if str
|
117
|
+
str << @read_buffer
|
118
|
+
else
|
119
|
+
str = @read_buffer
|
120
|
+
end
|
117
121
|
@read_buffer = +''
|
118
|
-
|
122
|
+
str
|
119
123
|
end
|
120
124
|
|
121
125
|
alias_method :orig_write, :write
|
122
|
-
def write(str)
|
123
|
-
Thread.current.agent.write(self, str)
|
126
|
+
def write(str, *args)
|
127
|
+
Thread.current.agent.write(self, str, *args)
|
124
128
|
end
|
125
129
|
|
126
130
|
alias_method :orig_write_chevron, :<<
|
@@ -166,16 +170,13 @@ class ::IO
|
|
166
170
|
return
|
167
171
|
end
|
168
172
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
str << a
|
175
|
-
str << "\n" unless a =~ /\n$/
|
176
|
-
end
|
173
|
+
strs = args.inject([]) do |m, a|
|
174
|
+
a = a.to_s
|
175
|
+
m << a
|
176
|
+
m << "\n" unless a =~ /\n$/
|
177
|
+
m
|
177
178
|
end
|
178
|
-
write
|
179
|
+
write *strs
|
179
180
|
nil
|
180
181
|
end
|
181
182
|
|
@@ -193,7 +194,7 @@ class ::IO
|
|
193
194
|
|
194
195
|
alias_method :orig_write_nonblock, :write_nonblock
|
195
196
|
def write_nonblock(string, _options = {})
|
196
|
-
write(string
|
197
|
+
write(string)
|
197
198
|
end
|
198
199
|
|
199
200
|
alias_method :orig_read_nonblock, :read_nonblock
|