polyphony 0.44.0 → 0.45.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +9 -11
- data/Rakefile +1 -1
- data/TODO.md +12 -7
- data/docs/_posts/2020-07-26-polyphony-0.44.md +77 -0
- data/docs/api-reference/thread.md +1 -1
- data/docs/getting-started/overview.md +14 -14
- data/docs/getting-started/tutorial.md +1 -1
- data/examples/core/{xx-agent.rb → xx-backend.rb} +5 -5
- data/examples/io/xx-pry.rb +18 -0
- data/examples/io/xx-rack_server.rb +71 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +1 -1
- data/ext/polyphony/backend.h +41 -0
- data/ext/polyphony/event.c +3 -3
- data/ext/polyphony/extconf.rb +1 -1
- data/ext/polyphony/{libev_agent.c → libev_backend.c} +175 -175
- data/ext/polyphony/polyphony.c +1 -1
- data/ext/polyphony/polyphony.h +4 -4
- data/ext/polyphony/polyphony_ext.c +2 -2
- data/ext/polyphony/queue.c +2 -2
- data/ext/polyphony/thread.c +21 -21
- data/lib/polyphony.rb +13 -12
- data/lib/polyphony/adapters/irb.rb +2 -17
- data/lib/polyphony/adapters/mysql2.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +5 -5
- data/lib/polyphony/adapters/process.rb +2 -2
- data/lib/polyphony/adapters/readline.rb +17 -0
- data/lib/polyphony/adapters/sequel.rb +1 -1
- data/lib/polyphony/core/global_api.rb +11 -6
- data/lib/polyphony/core/resource_pool.rb +2 -2
- data/lib/polyphony/core/sync.rb +38 -2
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/extensions/core.rb +31 -20
- data/lib/polyphony/extensions/fiber.rb +1 -1
- data/lib/polyphony/extensions/io.rb +7 -8
- data/lib/polyphony/extensions/openssl.rb +6 -6
- data/lib/polyphony/extensions/socket.rb +4 -14
- data/lib/polyphony/extensions/thread.rb +6 -5
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +4 -3
- data/test/helper.rb +1 -1
- data/test/{test_agent.rb → test_backend.rb} +22 -22
- data/test/test_fiber.rb +4 -4
- data/test/test_io.rb +1 -1
- data/test/test_kernel.rb +5 -0
- data/test/test_signal.rb +3 -3
- data/test/test_sync.rb +52 -0
- metadata +40 -30
- data/.gitbook.yaml +0 -4
- data/ext/polyphony/agent.h +0 -41
data/ext/polyphony/polyphony.c
CHANGED
data/ext/polyphony/polyphony.h
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
#include "ruby.h"
|
5
5
|
#include "ruby/io.h"
|
6
6
|
#include "libev.h"
|
7
|
-
#include "
|
7
|
+
#include "backend.h"
|
8
8
|
|
9
9
|
// debugging
|
10
10
|
#define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
|
@@ -25,8 +25,8 @@
|
|
25
25
|
}
|
26
26
|
|
27
27
|
|
28
|
-
extern
|
29
|
-
#define
|
28
|
+
extern backend_interface_t backend_interface;
|
29
|
+
#define __BACKEND__ (backend_interface)
|
30
30
|
|
31
31
|
extern VALUE mPolyphony;
|
32
32
|
extern VALUE cQueue;
|
@@ -39,7 +39,7 @@ extern ID ID_each;
|
|
39
39
|
extern ID ID_fiber_trace;
|
40
40
|
extern ID ID_inspect;
|
41
41
|
extern ID ID_invoke;
|
42
|
-
extern ID
|
42
|
+
extern ID ID_ivar_backend;
|
43
43
|
extern ID ID_ivar_running;
|
44
44
|
extern ID ID_ivar_thread;
|
45
45
|
extern ID ID_new;
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
void Init_Fiber();
|
4
4
|
void Init_Polyphony();
|
5
|
-
void
|
5
|
+
void Init_LibevBackend();
|
6
6
|
void Init_Queue();
|
7
7
|
void Init_Event();
|
8
8
|
void Init_Thread();
|
@@ -12,7 +12,7 @@ void Init_polyphony_ext() {
|
|
12
12
|
ev_set_allocator(xrealloc);
|
13
13
|
|
14
14
|
Init_Polyphony();
|
15
|
-
|
15
|
+
Init_LibevBackend();
|
16
16
|
Init_Queue();
|
17
17
|
Init_Event();
|
18
18
|
|
data/ext/polyphony/queue.c
CHANGED
@@ -80,13 +80,13 @@ VALUE Queue_shift(VALUE self) {
|
|
80
80
|
|
81
81
|
VALUE fiber = rb_fiber_current();
|
82
82
|
VALUE thread = rb_thread_current();
|
83
|
-
VALUE
|
83
|
+
VALUE backend = rb_ivar_get(thread, ID_ivar_backend);
|
84
84
|
|
85
85
|
while (1) {
|
86
86
|
ring_buffer_push(&queue->shift_queue, fiber);
|
87
87
|
if (queue->values.count > 0) Fiber_make_runnable(fiber, Qnil);
|
88
88
|
|
89
|
-
VALUE switchpoint_result =
|
89
|
+
VALUE switchpoint_result = __BACKEND__.wait_event(backend, Qnil);
|
90
90
|
ring_buffer_delete(&queue->shift_queue, fiber);
|
91
91
|
|
92
92
|
if (RTEST(rb_obj_is_kind_of(switchpoint_result, rb_eException)))
|
data/ext/polyphony/thread.c
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#include "polyphony.h"
|
2
2
|
|
3
3
|
ID ID_deactivate_all_watchers_post_fork;
|
4
|
-
ID
|
4
|
+
ID ID_ivar_backend;
|
5
5
|
ID ID_ivar_join_wait_queue;
|
6
6
|
ID ID_ivar_main_fiber;
|
7
7
|
ID ID_ivar_result;
|
@@ -20,20 +20,20 @@ static VALUE Thread_setup_fiber_scheduling(VALUE self) {
|
|
20
20
|
}
|
21
21
|
|
22
22
|
int Thread_fiber_ref_count(VALUE self) {
|
23
|
-
VALUE
|
24
|
-
return NUM2INT(
|
23
|
+
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
24
|
+
return NUM2INT(__BACKEND__.ref_count(backend));
|
25
25
|
}
|
26
26
|
|
27
27
|
inline void Thread_fiber_reset_ref_count(VALUE self) {
|
28
|
-
VALUE
|
29
|
-
|
28
|
+
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
29
|
+
__BACKEND__.reset_ref_count(backend);
|
30
30
|
}
|
31
31
|
|
32
32
|
static VALUE SYM_scheduled_fibers;
|
33
33
|
static VALUE SYM_pending_watchers;
|
34
34
|
|
35
35
|
static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
36
|
-
VALUE
|
36
|
+
VALUE backend = rb_ivar_get(self,ID_ivar_backend);
|
37
37
|
VALUE stats = rb_hash_new();
|
38
38
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
39
39
|
long pending_count;
|
@@ -41,7 +41,7 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
|
41
41
|
long scheduled_count = RARRAY_LEN(queue);
|
42
42
|
rb_hash_aset(stats, SYM_scheduled_fibers, INT2NUM(scheduled_count));
|
43
43
|
|
44
|
-
pending_count =
|
44
|
+
pending_count = __BACKEND__.pending_count(backend);
|
45
45
|
rb_hash_aset(stats, SYM_pending_watchers, INT2NUM(pending_count));
|
46
46
|
|
47
47
|
return stats;
|
@@ -77,8 +77,8 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
77
77
|
// event selector. Otherwise it's gonna be stuck waiting for an event to
|
78
78
|
// happen, not knowing that it there's already a fiber ready to run in its
|
79
79
|
// run queue.
|
80
|
-
VALUE
|
81
|
-
|
80
|
+
VALUE backend = rb_ivar_get(self,ID_ivar_backend);
|
81
|
+
__BACKEND__.wakeup(backend);
|
82
82
|
}
|
83
83
|
}
|
84
84
|
return self;
|
@@ -110,8 +110,8 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
|
|
110
110
|
// event loop. Otherwise it's gonna be stuck waiting for an event to
|
111
111
|
// happen, not knowing that it there's already a fiber ready to run in its
|
112
112
|
// run queue.
|
113
|
-
VALUE
|
114
|
-
|
113
|
+
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
114
|
+
__BACKEND__.wakeup(backend);
|
115
115
|
}
|
116
116
|
return self;
|
117
117
|
}
|
@@ -121,28 +121,28 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
121
121
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
122
122
|
VALUE next_fiber;
|
123
123
|
VALUE value;
|
124
|
-
VALUE
|
124
|
+
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
125
125
|
int ref_count;
|
126
|
-
int
|
126
|
+
int backend_was_polled = 0;
|
127
127
|
|
128
128
|
if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
|
129
129
|
TRACE(2, SYM_fiber_switchpoint, current_fiber);
|
130
130
|
|
131
|
-
ref_count =
|
131
|
+
ref_count = __BACKEND__.ref_count(backend);
|
132
132
|
while (1) {
|
133
133
|
next_fiber = Queue_shift_no_wait(queue);
|
134
134
|
if (next_fiber != Qnil) {
|
135
|
-
if (
|
135
|
+
if (backend_was_polled == 0 && ref_count > 0) {
|
136
136
|
// this mechanism prevents event starvation in case the run queue never
|
137
137
|
// empties
|
138
|
-
|
138
|
+
__BACKEND__.poll(backend, Qtrue, current_fiber, queue);
|
139
139
|
}
|
140
140
|
break;
|
141
141
|
}
|
142
142
|
if (ref_count == 0) break;
|
143
143
|
|
144
|
-
|
145
|
-
|
144
|
+
__BACKEND__.poll(backend, Qnil, current_fiber, queue);
|
145
|
+
backend_was_polled = 1;
|
146
146
|
}
|
147
147
|
|
148
148
|
if (next_fiber == Qnil) return Qnil;
|
@@ -172,12 +172,12 @@ VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
|
172
172
|
}
|
173
173
|
|
174
174
|
VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE fiber, VALUE resume_obj) {
|
175
|
-
VALUE
|
175
|
+
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
176
176
|
if (fiber != Qnil) {
|
177
177
|
Thread_schedule_fiber_with_priority(self, fiber, resume_obj);
|
178
178
|
}
|
179
179
|
|
180
|
-
if (
|
180
|
+
if (__BACKEND__.wakeup(backend) == Qnil) {
|
181
181
|
// we're not inside the ev_loop, so we just do a switchpoint
|
182
182
|
Thread_switch_fiber(self);
|
183
183
|
}
|
@@ -205,7 +205,7 @@ void Init_Thread() {
|
|
205
205
|
rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
|
206
206
|
|
207
207
|
ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
|
208
|
-
|
208
|
+
ID_ivar_backend = rb_intern("@backend");
|
209
209
|
ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
|
210
210
|
ID_ivar_main_fiber = rb_intern("@main_fiber");
|
211
211
|
ID_ivar_result = rb_intern("@result");
|
data/lib/polyphony.rb
CHANGED
@@ -3,28 +3,21 @@
|
|
3
3
|
require 'fiber'
|
4
4
|
require_relative './polyphony_ext'
|
5
5
|
|
6
|
-
module Polyphony
|
7
|
-
# replace core Queue class with our own
|
8
|
-
verbose = $VERBOSE
|
9
|
-
$VERBOSE = nil
|
10
|
-
Object.const_set(:Queue, Polyphony::Queue)
|
11
|
-
$VERBOSE = verbose
|
12
|
-
end
|
13
|
-
|
14
6
|
require_relative './polyphony/extensions/core'
|
15
7
|
require_relative './polyphony/extensions/thread'
|
16
8
|
require_relative './polyphony/extensions/fiber'
|
17
9
|
require_relative './polyphony/extensions/io'
|
18
10
|
|
19
11
|
Thread.current.setup_fiber_scheduling
|
20
|
-
Thread.current.
|
12
|
+
Thread.current.backend = Polyphony::Backend.new
|
21
13
|
|
22
14
|
require_relative './polyphony/core/global_api'
|
23
15
|
require_relative './polyphony/core/resource_pool'
|
16
|
+
require_relative './polyphony/core/sync'
|
24
17
|
require_relative './polyphony/net'
|
25
18
|
require_relative './polyphony/adapters/process'
|
26
19
|
|
27
|
-
#
|
20
|
+
# Polyphony API
|
28
21
|
module Polyphony
|
29
22
|
class << self
|
30
23
|
def fork(&block)
|
@@ -60,7 +53,7 @@ module Polyphony
|
|
60
53
|
def run_forked_block(&block)
|
61
54
|
Thread.current.setup
|
62
55
|
Fiber.current.setup_main_fiber
|
63
|
-
Thread.current.
|
56
|
+
Thread.current.backend.post_fork
|
64
57
|
|
65
58
|
install_terminating_signal_handlers
|
66
59
|
|
@@ -109,12 +102,20 @@ module Polyphony
|
|
109
102
|
# processes (see Polyphony.fork).
|
110
103
|
at_exit do
|
111
104
|
next unless @original_pid == ::Process.pid
|
112
|
-
|
105
|
+
|
113
106
|
Polyphony.terminate_threads
|
114
107
|
Fiber.current.shutdown_all_children
|
115
108
|
end
|
116
109
|
end
|
117
110
|
end
|
111
|
+
|
112
|
+
# replace core Queue class with our own
|
113
|
+
verbose = $VERBOSE
|
114
|
+
$VERBOSE = nil
|
115
|
+
Object.const_set(:Queue, Polyphony::Queue)
|
116
|
+
Object.const_set(:Mutex, Polyphony::Mutex)
|
117
|
+
Object.const_set(:ConditionVariable, Polyphony::ConditionVariable)
|
118
|
+
$VERBOSE = verbose
|
118
119
|
end
|
119
120
|
|
120
121
|
Polyphony.install_terminating_signal_handlers
|
@@ -16,7 +16,7 @@ if Object.constants.include?(:Reline)
|
|
16
16
|
fiber.cancel
|
17
17
|
end
|
18
18
|
read_ios.each do |io|
|
19
|
-
Thread.current.
|
19
|
+
Thread.current.backend.wait_io(io, false)
|
20
20
|
return [io]
|
21
21
|
end
|
22
22
|
rescue Polyphony::Cancel
|
@@ -26,22 +26,7 @@ if Object.constants.include?(:Reline)
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
else
|
29
|
-
|
30
|
-
# thread pool. That way, the reactor loop can keep running while waiting for
|
31
|
-
# readline to return
|
32
|
-
module ::Readline
|
33
|
-
alias_method :orig_readline, :readline
|
34
|
-
|
35
|
-
Workers = Polyphony::ThreadPool.new
|
36
|
-
|
37
|
-
def readline(*args)
|
38
|
-
p :readline
|
39
|
-
# caller.each do |l|
|
40
|
-
# STDOUT.orig_puts l
|
41
|
-
# end
|
42
|
-
Workers.process { orig_readline(*args) }
|
43
|
-
end
|
44
|
-
end
|
29
|
+
require_relative './readline'
|
45
30
|
|
46
31
|
# RubyLex patches
|
47
32
|
class ::RubyLex
|
@@ -15,8 +15,8 @@ module ::PG
|
|
15
15
|
res = conn.connect_poll
|
16
16
|
case res
|
17
17
|
when PGRES_POLLING_FAILED then raise Error, conn.error_message
|
18
|
-
when PGRES_POLLING_READING then Thread.current.
|
19
|
-
when PGRES_POLLING_WRITING then Thread.current.
|
18
|
+
when PGRES_POLLING_READING then Thread.current.backend.wait_io(socket_io, false)
|
19
|
+
when PGRES_POLLING_WRITING then Thread.current.backend.wait_io(socket_io, true)
|
20
20
|
when PGRES_POLLING_OK then return conn.setnonblocking(true)
|
21
21
|
end
|
22
22
|
end
|
@@ -42,7 +42,7 @@ class ::PG::Connection
|
|
42
42
|
|
43
43
|
def get_result(&block)
|
44
44
|
while is_busy
|
45
|
-
Thread.current.
|
45
|
+
Thread.current.backend.wait_io(socket_io, false)
|
46
46
|
consume_input
|
47
47
|
end
|
48
48
|
orig_get_result(&block)
|
@@ -59,7 +59,7 @@ class ::PG::Connection
|
|
59
59
|
|
60
60
|
def block(_timeout = 0)
|
61
61
|
while is_busy
|
62
|
-
Thread.current.
|
62
|
+
Thread.current.backend.wait_io(socket_io, false)
|
63
63
|
consume_input
|
64
64
|
end
|
65
65
|
end
|
@@ -97,7 +97,7 @@ class ::PG::Connection
|
|
97
97
|
return move_on_after(timeout) { wait_for_notify(&block) } if timeout
|
98
98
|
|
99
99
|
loop do
|
100
|
-
Thread.current.
|
100
|
+
Thread.current.backend.wait_io(socket_io, false)
|
101
101
|
consume_input
|
102
102
|
notice = notifies
|
103
103
|
next unless notice
|
@@ -7,7 +7,7 @@ module Polyphony
|
|
7
7
|
def watch(cmd = nil, &block)
|
8
8
|
terminated = nil
|
9
9
|
pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
|
10
|
-
Thread.current.
|
10
|
+
Thread.current.backend.waitpid(pid)
|
11
11
|
terminated = true
|
12
12
|
ensure
|
13
13
|
kill_process(pid) unless terminated || pid.nil?
|
@@ -23,7 +23,7 @@ module Polyphony
|
|
23
23
|
|
24
24
|
def kill_and_await(sig, pid)
|
25
25
|
::Process.kill(sig, pid)
|
26
|
-
Thread.current.
|
26
|
+
Thread.current.backend.waitpid(pid)
|
27
27
|
rescue SystemCallError
|
28
28
|
# ignore
|
29
29
|
puts 'SystemCallError in kill_and_await'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'polyphony'
|
4
|
+
require 'readline'
|
5
|
+
|
6
|
+
# readline blocks the current thread, so we offload it to the blocking-ops
|
7
|
+
# thread pool. That way, the reactor loop can keep running while waiting for
|
8
|
+
# readline to return
|
9
|
+
module ::Readline
|
10
|
+
alias_method :orig_readline, :readline
|
11
|
+
|
12
|
+
Worker = Polyphony::ThreadPool.new(1)
|
13
|
+
|
14
|
+
def readline(*args)
|
15
|
+
Worker.process { orig_readline(*args) }
|
16
|
+
end
|
17
|
+
end
|
@@ -39,7 +39,7 @@ module Polyphony
|
|
39
39
|
# Override Sequel::Database to use FiberConnectionPool by default.
|
40
40
|
Sequel::Database.prepend(Module.new do
|
41
41
|
def connection_pool_default_options
|
42
|
-
{pool_class: FiberConnectionPool}
|
42
|
+
{ pool_class: FiberConnectionPool }
|
43
43
|
end
|
44
44
|
end)
|
45
45
|
end
|
@@ -19,13 +19,18 @@ module Polyphony
|
|
19
19
|
fiber = ::Fiber.current
|
20
20
|
canceller = spin do
|
21
21
|
sleep interval
|
22
|
-
exception = with_exception
|
23
|
-
with_exception.new : RuntimeError.new(with_exception)
|
22
|
+
exception = cancel_exception(with_exception)
|
24
23
|
fiber.schedule exception
|
25
24
|
end
|
26
25
|
block ? cancel_after_wrap_block(canceller, &block) : canceller
|
27
26
|
end
|
28
27
|
|
28
|
+
def cancel_exception(exception)
|
29
|
+
return exception.new if exception.is_a?(Class)
|
30
|
+
|
31
|
+
RuntimeError.new(exception)
|
32
|
+
end
|
33
|
+
|
29
34
|
def cancel_after_wrap_block(canceller, &block)
|
30
35
|
block.call
|
31
36
|
ensure
|
@@ -50,7 +55,7 @@ module Polyphony
|
|
50
55
|
next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
|
51
56
|
loop do
|
52
57
|
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
53
|
-
Thread.current.
|
58
|
+
Thread.current.backend.sleep(next_time - now)
|
54
59
|
yield
|
55
60
|
loop do
|
56
61
|
next_time += interval
|
@@ -98,14 +103,14 @@ module Polyphony
|
|
98
103
|
def sleep(duration = nil)
|
99
104
|
return sleep_forever unless duration
|
100
105
|
|
101
|
-
Thread.current.
|
106
|
+
Thread.current.backend.sleep duration
|
102
107
|
end
|
103
108
|
|
104
109
|
def sleep_forever
|
105
|
-
Thread.current.
|
110
|
+
Thread.current.backend.ref
|
106
111
|
loop { sleep 60 }
|
107
112
|
ensure
|
108
|
-
Thread.current.
|
113
|
+
Thread.current.backend.unref
|
109
114
|
end
|
110
115
|
|
111
116
|
def throttled_loop(rate, count: nil, &block)
|
@@ -28,7 +28,7 @@ module Polyphony
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def acquire_from_stock(fiber)
|
31
|
-
add_to_stock if (@stock.empty? || @stock.pending?) && @size < @limit
|
31
|
+
add_to_stock if (@stock.empty? || @stock.pending?) && @size < @limit
|
32
32
|
resource = @stock.shift
|
33
33
|
@acquired_resources[fiber] = resource
|
34
34
|
yield resource
|
@@ -38,7 +38,7 @@ module Polyphony
|
|
38
38
|
@stock.push resource
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def method_missing(sym, *args, &block)
|
43
43
|
acquire { |r| r.send(sym, *args, &block) }
|
44
44
|
end
|