polyphony 0.44.0 → 0.45.0
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/.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
|