polyphony 0.43.11 → 0.44.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/CHANGELOG.md +8 -0
- data/Gemfile.lock +5 -1
- data/TODO.md +11 -8
- data/examples/adapters/sequel_mysql.rb +23 -0
- data/examples/adapters/sequel_mysql_pool.rb +33 -0
- data/ext/polyphony/libev_agent.c +16 -18
- data/ext/polyphony/polyphony.c +2 -0
- data/ext/polyphony/polyphony.h +11 -4
- data/ext/polyphony/thread.c +13 -9
- data/lib/polyphony.rb +11 -25
- data/lib/polyphony/adapters/mysql2.rb +19 -0
- data/lib/polyphony/adapters/sequel.rb +45 -0
- data/lib/polyphony/core/exceptions.rb +11 -0
- data/lib/polyphony/core/resource_pool.rb +18 -5
- data/lib/polyphony/extensions/core.rb +15 -8
- data/lib/polyphony/extensions/fiber.rb +4 -0
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +2 -0
- data/test/test_fiber.rb +16 -0
- data/test/test_resource_pool.rb +29 -4
- data/test/test_signal.rb +2 -26
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04c1c1221d46b1b9169aa04c5896c8d68c20fa11d64554e76b7452710423f142
|
4
|
+
data.tar.gz: 519528e0a3dfd323620869d4a7e43e4769cf3c1b5f0ea405cf341efc6fd534f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de8ac460dd05b051057ffab1c155c6b2d675cc623b98549333ba1defbc99f4efb738ff4a34a9f8c14bd3cb9ab0cab5356aa34a2308daa73273a6ef8719565bc4
|
7
|
+
data.tar.gz: c26b254559e484774574555dc895cf072bda8061b92fed26d8e61e7172523fe0faf2750cd2f924db3a25b59583b673ebe5f14749fd6f1279ca2e7e7b7745490b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 0.44.0 2020-07-25
|
2
|
+
|
3
|
+
* Fix reentrant `ResourcePool` (#38)
|
4
|
+
* Add `ResourcePool#discard!` (#35)
|
5
|
+
* Add `Mysql2::Client` and `Sequel::ConnectionPool` adapters (#35)
|
6
|
+
* Reimplement `Kernel.trap` using `Fiber#interject`
|
7
|
+
* Add `Fiber#interject` for running arbitrary code on arbitrary fibers (#39)
|
8
|
+
|
1
9
|
## 0.43.11 2020-07-24
|
2
10
|
|
3
11
|
* Dump uncaught exception info for forked process (#36)
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
polyphony (0.
|
4
|
+
polyphony (0.44.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -72,6 +72,7 @@ GEM
|
|
72
72
|
minitest (>= 5.0)
|
73
73
|
ruby-progressbar
|
74
74
|
multi_xml (0.6.0)
|
75
|
+
mysql2 (0.5.3)
|
75
76
|
parallel (1.19.1)
|
76
77
|
parser (2.7.0.2)
|
77
78
|
ast (~> 2.4.0)
|
@@ -109,6 +110,7 @@ GEM
|
|
109
110
|
sass-listen (4.0.0)
|
110
111
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
111
112
|
rb-inotify (~> 0.9, >= 0.9.7)
|
113
|
+
sequel (5.34.0)
|
112
114
|
simplecov (0.17.1)
|
113
115
|
docile (~> 1.1)
|
114
116
|
json (>= 1.8, < 3)
|
@@ -130,11 +132,13 @@ DEPENDENCIES
|
|
130
132
|
localhost (= 1.1.4)
|
131
133
|
minitest (= 5.13.0)
|
132
134
|
minitest-reporters (= 1.4.2)
|
135
|
+
mysql2 (= 0.5.3)
|
133
136
|
pg (= 1.1.4)
|
134
137
|
polyphony!
|
135
138
|
rake-compiler (= 1.0.5)
|
136
139
|
redis (= 4.1.0)
|
137
140
|
rubocop (= 0.85.1)
|
141
|
+
sequel (= 5.34.0)
|
138
142
|
simplecov (= 0.17.1)
|
139
143
|
|
140
144
|
BUNDLED WITH
|
data/TODO.md
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
+
- Adapter for Pry, or maybe just an adapter for readline
|
2
|
+
- Rename Agent to Backend
|
3
|
+
- Review all code
|
4
|
+
- Cleanup C code
|
5
|
+
- Cleanup Ruby code (use Rubocop)
|
6
|
+
- Cleanup and annotate examples (and remove all the examples used for
|
7
|
+
debugging). Focus on examples that serve as "how-to".
|
8
|
+
|
1
9
|
- Debugging
|
2
10
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
3
11
|
test fails
|
4
|
-
- Needs to work with Pry (can write perhaps an extension for pry)
|
5
12
|
- First impl in Ruby using `TracePoint` API
|
6
13
|
- Mode of operation:
|
7
14
|
- Two parts: tracer and controller
|
@@ -111,7 +118,7 @@
|
|
111
118
|
- discuss using `snooze` for ensuring responsiveness when executing CPU-bound work
|
112
119
|
|
113
120
|
|
114
|
-
## 0.
|
121
|
+
## 0.45
|
115
122
|
|
116
123
|
### Some more API work, more docs
|
117
124
|
|
@@ -124,13 +131,13 @@
|
|
124
131
|
- proceed from there
|
125
132
|
|
126
133
|
|
127
|
-
## 0.
|
134
|
+
## 0.46
|
128
135
|
|
129
136
|
### Sinatra / Sidekiq
|
130
137
|
|
131
138
|
- Pull out redis/postgres code, put into new `polyphony-xxx` gems
|
132
139
|
|
133
|
-
## 0.
|
140
|
+
## 0.47
|
134
141
|
|
135
142
|
### Testing && Docs
|
136
143
|
|
@@ -142,10 +149,6 @@
|
|
142
149
|
- `IO.foreach`
|
143
150
|
- `Process.waitpid`
|
144
151
|
|
145
|
-
## 0.47
|
146
|
-
|
147
|
-
### Real IO#gets and IO#read
|
148
|
-
|
149
152
|
## 0.48 DNS
|
150
153
|
|
151
154
|
### DNS client
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony/adapters/sequel'
|
5
|
+
require 'polyphony/adapters/mysql2'
|
6
|
+
|
7
|
+
time_printer = spin do
|
8
|
+
last = Time.now
|
9
|
+
throttled_loop(10) do
|
10
|
+
now = Time.now
|
11
|
+
puts now - last
|
12
|
+
last = now
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
db = Sequel.connect('mysql2://localhost/test')
|
17
|
+
|
18
|
+
x = 10_000
|
19
|
+
t0 = Time.now
|
20
|
+
x.times { db.execute('select 1 as test') }
|
21
|
+
puts "query rate: #{x / (Time.now - t0)} reqs/s"
|
22
|
+
|
23
|
+
time_printer.stop
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony/adapters/sequel'
|
5
|
+
require 'polyphony/adapters/mysql2'
|
6
|
+
|
7
|
+
CONCURRENCY = ARGV.first ? ARGV.first.to_i : 1000
|
8
|
+
puts "concurrency: #{CONCURRENCY}"
|
9
|
+
|
10
|
+
db = Sequel.connect(
|
11
|
+
'mysql2://localhost/test',
|
12
|
+
max_connections: 100,
|
13
|
+
preconnect: true
|
14
|
+
)
|
15
|
+
|
16
|
+
t0 = Time.now
|
17
|
+
count = 0
|
18
|
+
|
19
|
+
fibers = Array.new(CONCURRENCY) do
|
20
|
+
spin do
|
21
|
+
loop do
|
22
|
+
db.execute('select sleep(0.001) as test')
|
23
|
+
count += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
sleep 0.1
|
29
|
+
fibers.first.terminate # Interrupt mid-query
|
30
|
+
|
31
|
+
sleep 2
|
32
|
+
puts "query rate: #{count / (Time.now - t0)} reqs/s"
|
33
|
+
fibers.each(&:interrupt)
|
data/ext/polyphony/libev_agent.c
CHANGED
@@ -77,15 +77,13 @@ VALUE LibevAgent_post_fork(VALUE self) {
|
|
77
77
|
LibevAgent_t *agent;
|
78
78
|
GetLibevAgent(self, agent);
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
ev_loop_fork(agent->ev_loop);
|
80
|
+
// After fork there may be some watchers still active left over from the
|
81
|
+
// parent, so we destroy the loop, even if it's the default one, then use the
|
82
|
+
// default one, as post_fork is called only from the main thread of the forked
|
83
|
+
// process. That way we don't need to call ev_loop_fork, since the loop is
|
84
|
+
// always a fresh one.
|
85
|
+
ev_loop_destroy(agent->ev_loop);
|
86
|
+
agent->ev_loop = EV_DEFAULT;
|
89
87
|
|
90
88
|
return self;
|
91
89
|
}
|
@@ -142,11 +140,11 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
|
|
142
140
|
|
143
141
|
agent->run_no_wait_count = 0;
|
144
142
|
|
145
|
-
|
143
|
+
COND_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
|
146
144
|
agent->running = 1;
|
147
145
|
ev_run(agent->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
|
148
146
|
agent->running = 0;
|
149
|
-
|
147
|
+
COND_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
|
150
148
|
|
151
149
|
return self;
|
152
150
|
}
|
@@ -376,7 +374,7 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
376
374
|
|
377
375
|
return str;
|
378
376
|
error:
|
379
|
-
return
|
377
|
+
return RAISE_EXCEPTION(switchpoint_result);
|
380
378
|
}
|
381
379
|
|
382
380
|
VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
@@ -456,7 +454,7 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
|
456
454
|
|
457
455
|
return io;
|
458
456
|
error:
|
459
|
-
return
|
457
|
+
return RAISE_EXCEPTION(switchpoint_result);
|
460
458
|
}
|
461
459
|
|
462
460
|
VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
@@ -500,7 +498,7 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
500
498
|
|
501
499
|
return INT2NUM(len);
|
502
500
|
error:
|
503
|
-
return
|
501
|
+
return RAISE_EXCEPTION(switchpoint_result);
|
504
502
|
}
|
505
503
|
|
506
504
|
VALUE LibevAgent_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
|
@@ -570,7 +568,7 @@ VALUE LibevAgent_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
|
|
570
568
|
return INT2NUM(total_written);
|
571
569
|
error:
|
572
570
|
free(iov);
|
573
|
-
return
|
571
|
+
return RAISE_EXCEPTION(switchpoint_result);
|
574
572
|
}
|
575
573
|
|
576
574
|
VALUE LibevAgent_write_m(int argc, VALUE *argv, VALUE self) {
|
@@ -636,7 +634,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
636
634
|
RB_GC_GUARD(switchpoint_result);
|
637
635
|
return Qnil;
|
638
636
|
error:
|
639
|
-
return
|
637
|
+
return RAISE_EXCEPTION(switchpoint_result);
|
640
638
|
}
|
641
639
|
|
642
640
|
VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
@@ -692,7 +690,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
|
692
690
|
RB_GC_GUARD(switchpoint_result);
|
693
691
|
return Qnil;
|
694
692
|
error:
|
695
|
-
return
|
693
|
+
return RAISE_EXCEPTION(switchpoint_result);
|
696
694
|
}
|
697
695
|
|
698
696
|
VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
|
@@ -728,7 +726,7 @@ VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
|
|
728
726
|
RB_GC_GUARD(switchpoint_result);
|
729
727
|
return sock;
|
730
728
|
error:
|
731
|
-
return
|
729
|
+
return RAISE_EXCEPTION(switchpoint_result);
|
732
730
|
}
|
733
731
|
|
734
732
|
VALUE libev_wait_fd(LibevAgent_t *agent, int fd, int events, int raise_exception) {
|
data/ext/polyphony/polyphony.c
CHANGED
@@ -7,6 +7,7 @@ ID ID_caller;
|
|
7
7
|
ID ID_clear;
|
8
8
|
ID ID_each;
|
9
9
|
ID ID_inspect;
|
10
|
+
ID ID_invoke;
|
10
11
|
ID ID_new;
|
11
12
|
ID ID_raise;
|
12
13
|
ID ID_ivar_running;
|
@@ -60,6 +61,7 @@ void Init_Polyphony() {
|
|
60
61
|
ID_clear = rb_intern("clear");
|
61
62
|
ID_each = rb_intern("each");
|
62
63
|
ID_inspect = rb_intern("inspect");
|
64
|
+
ID_invoke = rb_intern("invoke");
|
63
65
|
ID_ivar_running = rb_intern("@running");
|
64
66
|
ID_ivar_thread = rb_intern("@thread");
|
65
67
|
ID_new = rb_intern("new");
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -8,17 +8,23 @@
|
|
8
8
|
|
9
9
|
// debugging
|
10
10
|
#define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
|
11
|
-
#define INSPECT(str, obj) { printf(str); VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); printf("%s\n", StringValueCStr(s));}
|
12
|
-
#define
|
13
|
-
|
11
|
+
#define INSPECT(str, obj) { printf(str); VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); printf("%s\n", StringValueCStr(s)); }
|
12
|
+
#define TRACE_CALLER() { VALUE c = rb_funcall(rb_mKernel, rb_intern("caller"), 0); INSPECT("caller: ", c); }
|
13
|
+
|
14
|
+
// tracing
|
15
|
+
#define TRACE(...) rb_funcall(rb_cObject, ID_fiber_trace, __VA_ARGS__)
|
16
|
+
#define COND_TRACE(...) if (__tracing_enabled__) { \
|
17
|
+
TRACE(__VA_ARGS__); \
|
14
18
|
}
|
15
19
|
|
16
20
|
#define TEST_EXCEPTION(ret) (RTEST(rb_obj_is_kind_of(ret, rb_eException)))
|
17
21
|
|
22
|
+
#define RAISE_EXCEPTION(e) rb_funcall(e, ID_invoke, 0);
|
18
23
|
#define TEST_RESUME_EXCEPTION(ret) if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) { \
|
19
|
-
return
|
24
|
+
return RAISE_EXCEPTION(ret); \
|
20
25
|
}
|
21
26
|
|
27
|
+
|
22
28
|
extern agent_interface_t agent_interface;
|
23
29
|
#define __AGENT__ (agent_interface)
|
24
30
|
|
@@ -32,6 +38,7 @@ extern ID ID_clear;
|
|
32
38
|
extern ID ID_each;
|
33
39
|
extern ID ID_fiber_trace;
|
34
40
|
extern ID ID_inspect;
|
41
|
+
extern ID ID_invoke;
|
35
42
|
extern ID ID_ivar_agent;
|
36
43
|
extern ID ID_ivar_running;
|
37
44
|
extern ID ID_ivar_thread;
|
data/ext/polyphony/thread.c
CHANGED
@@ -64,7 +64,7 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
64
64
|
}
|
65
65
|
|
66
66
|
rb_ivar_set(fiber, ID_runnable_value, value);
|
67
|
-
|
67
|
+
COND_TRACE(3, SYM_fiber_schedule, fiber, value);
|
68
68
|
|
69
69
|
if (!already_runnable) {
|
70
70
|
queue = rb_ivar_get(self, ID_run_queue);
|
@@ -89,7 +89,7 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
|
|
89
89
|
|
90
90
|
if (rb_fiber_alive_p(fiber) != Qtrue) return self;
|
91
91
|
|
92
|
-
|
92
|
+
COND_TRACE(3, SYM_fiber_schedule, fiber, value);
|
93
93
|
rb_ivar_set(fiber, ID_runnable_value, value);
|
94
94
|
|
95
95
|
queue = rb_ivar_get(self, ID_run_queue);
|
@@ -123,13 +123,10 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
123
123
|
VALUE value;
|
124
124
|
VALUE agent = rb_ivar_get(self, ID_ivar_agent);
|
125
125
|
int ref_count;
|
126
|
-
int agent_was_polled = 0;
|
126
|
+
int agent_was_polled = 0;
|
127
127
|
|
128
|
-
if (__tracing_enabled__)
|
129
|
-
|
130
|
-
rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_switchpoint, current_fiber);
|
131
|
-
}
|
132
|
-
}
|
128
|
+
if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
|
129
|
+
TRACE(2, SYM_fiber_switchpoint, current_fiber);
|
133
130
|
|
134
131
|
ref_count = __AGENT__.ref_count(agent);
|
135
132
|
while (1) {
|
@@ -152,7 +149,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
152
149
|
|
153
150
|
// run next fiber
|
154
151
|
value = rb_ivar_get(next_fiber, ID_runnable_value);
|
155
|
-
|
152
|
+
COND_TRACE(3, SYM_fiber_run, next_fiber, value);
|
156
153
|
|
157
154
|
rb_ivar_set(next_fiber, ID_runnable, Qnil);
|
158
155
|
RB_GC_GUARD(next_fiber);
|
@@ -188,6 +185,11 @@ VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE fiber, VALUE resume_ob
|
|
188
185
|
return self;
|
189
186
|
}
|
190
187
|
|
188
|
+
VALUE Thread_debug(VALUE self) {
|
189
|
+
rb_ivar_set(self, rb_intern("@__debug__"), Qtrue);
|
190
|
+
return self;
|
191
|
+
}
|
192
|
+
|
191
193
|
void Init_Thread() {
|
192
194
|
rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
|
193
195
|
rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
|
@@ -200,6 +202,8 @@ void Init_Thread() {
|
|
200
202
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
201
203
|
rb_define_method(rb_cThread, "run_queue_trace", Thread_run_queue_trace, 0);
|
202
204
|
|
205
|
+
rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
|
206
|
+
|
203
207
|
ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
|
204
208
|
ID_ivar_agent = rb_intern("@agent");
|
205
209
|
ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
|
data/lib/polyphony.rb
CHANGED
@@ -27,26 +27,19 @@ require_relative './polyphony/adapters/process'
|
|
27
27
|
# Main Polyphony API
|
28
28
|
module Polyphony
|
29
29
|
class << self
|
30
|
-
def wait_for_signal(sig)
|
31
|
-
raise "should be reimplemented"
|
32
|
-
|
33
|
-
fiber = Fiber.current
|
34
|
-
# Polyphony.ref
|
35
|
-
old_trap = trap(sig) do
|
36
|
-
# Polyphony.unref
|
37
|
-
fiber.schedule(sig)
|
38
|
-
trap(sig, old_trap)
|
39
|
-
end
|
40
|
-
suspend
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
30
|
def fork(&block)
|
45
31
|
Kernel.fork do
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
32
|
+
# A race condition can arise if a TERM or INT signal is received before
|
33
|
+
# the forked process has finished initializing. To prevent this we restore
|
34
|
+
# the default signal handlers, and then reinstall the custom Polyphony
|
35
|
+
# handlers just before running the given block.
|
36
|
+
trap('SIGTERM', 'DEFAULT')
|
37
|
+
trap('SIGINT', 'DEFAULT')
|
38
|
+
|
39
|
+
# Since the fiber doing the fork will become the main fiber of the
|
40
|
+
# forked process, we leave it behind by transferring to a new fiber
|
41
|
+
# created in the context of the forked process, which rescues *all*
|
42
|
+
# exceptions, including Interrupt and SystemExit.
|
50
43
|
spin_forked_block(&block).transfer
|
51
44
|
end
|
52
45
|
end
|
@@ -65,13 +58,6 @@ module Polyphony
|
|
65
58
|
end
|
66
59
|
|
67
60
|
def run_forked_block(&block)
|
68
|
-
# A race condition can arise if a TERM or INT signal is received before
|
69
|
-
# the forked process has finished initializing. To prevent this we restore
|
70
|
-
# the default signal handlers, and then reinstall the custom Polyphony
|
71
|
-
# handlers just before running the given block.
|
72
|
-
trap('SIGTERM', 'DEFAULT')
|
73
|
-
trap('SIGINT', 'DEFAULT')
|
74
|
-
|
75
61
|
Thread.current.setup
|
76
62
|
Fiber.current.setup_main_fiber
|
77
63
|
Thread.current.agent.post_fork
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../polyphony'
|
4
|
+
require 'mysql2/client'
|
5
|
+
|
6
|
+
# Mysql2::Client overrides
|
7
|
+
Mysql2::Client.prepend(Module.new do
|
8
|
+
def initialize(config)
|
9
|
+
config[:async] = true
|
10
|
+
super
|
11
|
+
@io = ::IO.for_fd(socket)
|
12
|
+
end
|
13
|
+
|
14
|
+
def query(sql, **options)
|
15
|
+
super
|
16
|
+
Thread.current.agent.wait_io(@io, false)
|
17
|
+
async_result
|
18
|
+
end
|
19
|
+
end)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../polyphony'
|
4
|
+
require 'sequel'
|
5
|
+
|
6
|
+
module Polyphony
|
7
|
+
# Sequel ConnectionPool that delegates to Polyphony::ResourcePool.
|
8
|
+
class FiberConnectionPool < Sequel::ConnectionPool
|
9
|
+
def initialize(db, opts = OPTS)
|
10
|
+
super
|
11
|
+
max_size = Integer(opts[:max_connections] || 4)
|
12
|
+
@pool = Polyphony::ResourcePool.new(limit: max_size) { make_new(:default) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def hold(_server = nil)
|
16
|
+
@pool.acquire do |conn|
|
17
|
+
yield conn
|
18
|
+
rescue Polyphony::BaseException
|
19
|
+
# The connection may be in an unrecoverable state if interrupted,
|
20
|
+
# discard the connection from the pool so it isn't reused.
|
21
|
+
@pool.discard!
|
22
|
+
raise
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def size
|
27
|
+
@pool.size
|
28
|
+
end
|
29
|
+
|
30
|
+
def max_size
|
31
|
+
@pool.limit
|
32
|
+
end
|
33
|
+
|
34
|
+
def preconnect(_concurrent = false)
|
35
|
+
@pool.preheat!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Override Sequel::Database to use FiberConnectionPool by default.
|
40
|
+
Sequel::Database.prepend(Module.new do
|
41
|
+
def connection_pool_default_options
|
42
|
+
{pool_class: FiberConnectionPool}
|
43
|
+
end
|
44
|
+
end)
|
45
|
+
end
|
@@ -33,4 +33,15 @@ module Polyphony
|
|
33
33
|
|
34
34
|
# Restart is used to restart a fiber
|
35
35
|
class Restart < BaseException; end
|
36
|
+
|
37
|
+
# Interjection is used to run arbitrary code on arbitrary fibers at any point
|
38
|
+
class Interjection < BaseException
|
39
|
+
def initialize(proc)
|
40
|
+
@proc = proc
|
41
|
+
end
|
42
|
+
|
43
|
+
def invoke
|
44
|
+
@proc.call
|
45
|
+
end
|
46
|
+
end
|
36
47
|
end
|
@@ -20,17 +20,23 @@ module Polyphony
|
|
20
20
|
@stock.size
|
21
21
|
end
|
22
22
|
|
23
|
-
def acquire
|
23
|
+
def acquire(&block)
|
24
24
|
fiber = Fiber.current
|
25
|
-
return @acquired_resources[fiber] if @acquired_resources[fiber]
|
25
|
+
return yield @acquired_resources[fiber] if @acquired_resources[fiber]
|
26
26
|
|
27
|
+
acquire_from_stock(fiber, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def acquire_from_stock(fiber)
|
27
31
|
add_to_stock if (@stock.empty? || @stock.pending?) && @size < @limit
|
28
32
|
resource = @stock.shift
|
29
33
|
@acquired_resources[fiber] = resource
|
30
34
|
yield resource
|
31
35
|
ensure
|
32
|
-
@acquired_resources
|
33
|
-
|
36
|
+
if resource && @acquired_resources[fiber] == resource
|
37
|
+
@acquired_resources.delete(fiber)
|
38
|
+
@stock.push resource
|
39
|
+
end
|
34
40
|
end
|
35
41
|
|
36
42
|
def method_missing(sym, *args, &block)
|
@@ -45,7 +51,14 @@ module Polyphony
|
|
45
51
|
# @return [any] allocated resource
|
46
52
|
def add_to_stock
|
47
53
|
@size += 1
|
48
|
-
|
54
|
+
resource = @allocator.call
|
55
|
+
@stock << resource
|
56
|
+
end
|
57
|
+
|
58
|
+
# Discards the currently-acquired resource
|
59
|
+
# instead of returning it to the pool when done.
|
60
|
+
def discard!
|
61
|
+
@size -= 1 if @acquired_resources.delete(Fiber.current)
|
49
62
|
end
|
50
63
|
|
51
64
|
def preheat!
|
@@ -46,6 +46,10 @@ class ::Exception
|
|
46
46
|
|
47
47
|
backtrace.reject { |l| l[POLYPHONY_DIR] }
|
48
48
|
end
|
49
|
+
|
50
|
+
def invoke
|
51
|
+
Kernel.raise(self)
|
52
|
+
end
|
49
53
|
end
|
50
54
|
|
51
55
|
# Overrides for Process
|
@@ -140,8 +144,16 @@ module ::Kernel
|
|
140
144
|
def trap(sig, command = nil, &block)
|
141
145
|
return orig_trap(sig, command) if command.is_a? String
|
142
146
|
|
143
|
-
block = command if command.respond_to?(:call)
|
144
|
-
|
147
|
+
block = command if !block && command.respond_to?(:call)
|
148
|
+
if block
|
149
|
+
exception = Polyphony::Interjection.new(block)
|
150
|
+
else
|
151
|
+
exception = command.is_a?(Class) && command.new
|
152
|
+
end
|
153
|
+
|
154
|
+
unless exception
|
155
|
+
raise ArgumentError, "Must supply block or exception or callable object"
|
156
|
+
end
|
145
157
|
|
146
158
|
# The signal trap can be invoked at any time, including while the system
|
147
159
|
# agent is blocking while polling for events. In order to deal with this
|
@@ -152,12 +164,7 @@ module ::Kernel
|
|
152
164
|
# If the command argument is an exception class however, it will be raised
|
153
165
|
# directly in the context of the main fiber.
|
154
166
|
orig_trap(sig) do
|
155
|
-
|
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
|
167
|
+
Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
|
161
168
|
end
|
162
169
|
end
|
163
170
|
end
|
data/lib/polyphony/version.rb
CHANGED
data/polyphony.gemspec
CHANGED
@@ -32,6 +32,8 @@ Gem::Specification.new do |s|
|
|
32
32
|
s.add_development_dependency 'redis', '4.1.0'
|
33
33
|
s.add_development_dependency 'hiredis', '0.6.3'
|
34
34
|
s.add_development_dependency 'http_parser.rb', '~>0.6.0'
|
35
|
+
s.add_development_dependency 'mysql2', '0.5.3'
|
36
|
+
s.add_development_dependency 'sequel', '5.34.0'
|
35
37
|
|
36
38
|
s.add_development_dependency 'jekyll', '~>3.8.6'
|
37
39
|
s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
|
data/test/test_fiber.rb
CHANGED
@@ -413,6 +413,22 @@ class FiberTest < MiniTest::Test
|
|
413
413
|
f2&.stop
|
414
414
|
end
|
415
415
|
|
416
|
+
def test_interject
|
417
|
+
buf = []
|
418
|
+
f = spin_loop { sleep }
|
419
|
+
snooze
|
420
|
+
f.interject { buf << Fiber.current }
|
421
|
+
snooze
|
422
|
+
assert_equal [f], buf
|
423
|
+
assert_equal :waiting, f.state
|
424
|
+
|
425
|
+
f.interject { buf << :foo; raise Polyphony::MoveOn }
|
426
|
+
snooze
|
427
|
+
|
428
|
+
assert_equal [f, :foo], buf
|
429
|
+
assert_equal :dead, f.state
|
430
|
+
end
|
431
|
+
|
416
432
|
def test_state
|
417
433
|
counter = 0
|
418
434
|
f = spin do
|
data/test/test_resource_pool.rb
CHANGED
@@ -27,6 +27,27 @@ class ResourcePoolTest < MiniTest::Test
|
|
27
27
|
assert_equal ['a', 'b', 'a', 'b'], results
|
28
28
|
end
|
29
29
|
|
30
|
+
def test_discard
|
31
|
+
resources = [+'a', +'b']
|
32
|
+
pool = Polyphony::ResourcePool.new(limit: 2) { resources.shift }
|
33
|
+
|
34
|
+
results = []
|
35
|
+
4.times {
|
36
|
+
spin {
|
37
|
+
snooze
|
38
|
+
pool.acquire { |resource|
|
39
|
+
results << resource
|
40
|
+
pool.discard! if resource == 'b'
|
41
|
+
snooze
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
Fiber.current.await_all_children
|
46
|
+
|
47
|
+
assert_equal ['a', 'b', 'a', 'a'], results
|
48
|
+
assert_equal 1, pool.size
|
49
|
+
end
|
50
|
+
|
30
51
|
def test_single_resource_limit
|
31
52
|
resources = [+'a', +'b']
|
32
53
|
pool = Polyphony::ResourcePool.new(limit: 1) { resources.shift }
|
@@ -41,7 +62,7 @@ class ResourcePoolTest < MiniTest::Test
|
|
41
62
|
}
|
42
63
|
}
|
43
64
|
}
|
44
|
-
|
65
|
+
Fiber.current.await_all_children
|
45
66
|
|
46
67
|
assert_equal ['a'] * 10, results
|
47
68
|
end
|
@@ -75,12 +96,16 @@ class ResourcePoolTest < MiniTest::Test
|
|
75
96
|
resources = [+'a', +'b']
|
76
97
|
pool = Polyphony::ResourcePool.new(limit: 1) { resources.shift }
|
77
98
|
|
99
|
+
results = []
|
78
100
|
pool.acquire do |r|
|
79
|
-
|
80
|
-
|
81
|
-
|
101
|
+
results << r
|
102
|
+
2.times do
|
103
|
+
pool.acquire do |r|
|
104
|
+
results << r
|
105
|
+
end
|
82
106
|
end
|
83
107
|
end
|
108
|
+
assert_equal ['a']*3, results
|
84
109
|
end
|
85
110
|
|
86
111
|
def test_overloaded_resource_pool
|
data/test/test_signal.rb
CHANGED
@@ -56,35 +56,11 @@ class SignalTrapTest < Minitest::Test
|
|
56
56
|
assert_equal "3 - interrupted\n2 - terminated\n1 - terminated\n", buffer
|
57
57
|
end
|
58
58
|
|
59
|
-
def
|
59
|
+
def test_interrupt_signal_scheduling
|
60
60
|
i, o = IO.pipe
|
61
61
|
pid = Polyphony.fork do
|
62
62
|
i.close
|
63
|
-
|
64
|
-
f2 = spin do
|
65
|
-
# this fiber will try to create a race condition by
|
66
|
-
# - being scheduled before f1 is scheduled with the Interrupt exception
|
67
|
-
# - scheduling f1 without an exception
|
68
|
-
suspend
|
69
|
-
f1.schedule
|
70
|
-
rescue ::Interrupt => e
|
71
|
-
o.puts '2-interrupt'
|
72
|
-
raise e
|
73
|
-
end
|
74
|
-
f1 = spin do
|
75
|
-
# this fiber is the one that will be current when the
|
76
|
-
# signal is trapped
|
77
|
-
sleep 1
|
78
|
-
o << 'boom'
|
79
|
-
rescue ::Interrupt => e
|
80
|
-
o.puts '1-interrupt'
|
81
|
-
raise e
|
82
|
-
end
|
83
|
-
old_trap = trap('INT') do
|
84
|
-
f2.schedule
|
85
|
-
old_trap.()
|
86
|
-
end
|
87
|
-
Fiber.current.await_all_children
|
63
|
+
sleep
|
88
64
|
rescue ::Interrupt => e
|
89
65
|
o.puts '3-interrupt'
|
90
66
|
ensure
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: polyphony
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.44.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -164,6 +164,34 @@ dependencies:
|
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: 0.6.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: mysql2
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.5.3
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - '='
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 0.5.3
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: sequel
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - '='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 5.34.0
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - '='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 5.34.0
|
167
195
|
- !ruby/object:Gem::Dependency
|
168
196
|
name: jekyll
|
169
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -293,6 +321,8 @@ files:
|
|
293
321
|
- examples/adapters/redis_client.rb
|
294
322
|
- examples/adapters/redis_pubsub.rb
|
295
323
|
- examples/adapters/redis_pubsub_perf.rb
|
324
|
+
- examples/adapters/sequel_mysql.rb
|
325
|
+
- examples/adapters/sequel_mysql_pool.rb
|
296
326
|
- examples/core/01-spinning-up-fibers.rb
|
297
327
|
- examples/core/02-awaiting-fibers.rb
|
298
328
|
- examples/core/03-interrupting.rb
|
@@ -407,9 +437,11 @@ files:
|
|
407
437
|
- lib/polyphony.rb
|
408
438
|
- lib/polyphony/adapters/fs.rb
|
409
439
|
- lib/polyphony/adapters/irb.rb
|
440
|
+
- lib/polyphony/adapters/mysql2.rb
|
410
441
|
- lib/polyphony/adapters/postgres.rb
|
411
442
|
- lib/polyphony/adapters/process.rb
|
412
443
|
- lib/polyphony/adapters/redis.rb
|
444
|
+
- lib/polyphony/adapters/sequel.rb
|
413
445
|
- lib/polyphony/adapters/trace.rb
|
414
446
|
- lib/polyphony/core/channel.rb
|
415
447
|
- lib/polyphony/core/exceptions.rb
|