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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d1eb9e2b1eef7f180107b3952fc6974e9c816268ebd8d2368ddfaac276d2086
4
- data.tar.gz: dafb382dc7606b92b97fb98125b57abc72e1b1d6b07bb877abff608a41ee51a1
3
+ metadata.gz: 04c1c1221d46b1b9169aa04c5896c8d68c20fa11d64554e76b7452710423f142
4
+ data.tar.gz: 519528e0a3dfd323620869d4a7e43e4769cf3c1b5f0ea405cf341efc6fd534f6
5
5
  SHA512:
6
- metadata.gz: 9f91b787b3eec2c19f6a02fd8a77469c8ae5d7a9302799c3e2a5957376b9f4b0135d2cd958ec7dcf326701e8ba81d85a28201a140ce5806ccc057cedafe907d5
7
- data.tar.gz: df865277e2f37c0d7fde0ffd38f52fc466bf28033fb968bd012af7455df9e7805989152467e1e8e84719194f173c99c6290496987ad8a2b48fe26076e62c85bd
6
+ metadata.gz: de8ac460dd05b051057ffab1c155c6b2d675cc623b98549333ba1defbc99f4efb738ff4a34a9f8c14bd3cb9ab0cab5356aa34a2308daa73273a6ef8719565bc4
7
+ data.tar.gz: c26b254559e484774574555dc895cf072bda8061b92fed26d8e61e7172523fe0faf2750cd2f924db3a25b59583b673ebe5f14749fd6f1279ca2e7e7b7745490b
@@ -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)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.43.11)
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.44
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.45
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.46
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)
@@ -77,15 +77,13 @@ VALUE LibevAgent_post_fork(VALUE self) {
77
77
  LibevAgent_t *agent;
78
78
  GetLibevAgent(self, agent);
79
79
 
80
- if (!ev_is_default_loop(agent->ev_loop)) {
81
- // post_fork is called only for the main thread of the forked process. If
82
- // the forked process was forked from a thread other than the main one,
83
- // we remove the old non-default ev_loop and use the default one instead.
84
- ev_loop_destroy(agent->ev_loop);
85
- agent->ev_loop = EV_DEFAULT;
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
- FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
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
- FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
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 rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
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 rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
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 rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
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 rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
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 rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
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 rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
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 rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
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) {
@@ -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");
@@ -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 FIBER_TRACE(...) if (__tracing_enabled__) { \
13
- rb_funcall(rb_cObject, ID_fiber_trace, __VA_ARGS__); \
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 rb_funcall(rb_mKernel, ID_raise, 1, ret); \
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;
@@ -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
- FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
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
- FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
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;1;
126
+ int agent_was_polled = 0;
127
127
 
128
- if (__tracing_enabled__) {
129
- if (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse) {
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
- FIBER_TRACE(3, SYM_fiber_run, next_fiber, value);
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");
@@ -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
- # # Since the fiber doing the fork will become the main fiber of the
47
- # # forked process, we leave it behind by transferring to a new fiber
48
- # # created in the context of the forked process, which rescues *all*
49
- # # exceptions, including Interrupt and SystemExit.
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.delete(fiber)
33
- @stock.push resource if resource
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
- @stock << @allocator.call
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) && !block
144
- exception = command.is_a?(Class) && command.new
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
- 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
167
+ Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
161
168
  end
162
169
  end
163
170
  end
@@ -67,6 +67,10 @@ module Polyphony
67
67
  else RuntimeError.new
68
68
  end
69
69
  end
70
+
71
+ def interject(&block)
72
+ raise Polyphony::Interjection.new(block)
73
+ end
70
74
  end
71
75
 
72
76
  # Fiber supervision
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.43.11'
4
+ VERSION = '0.44.0'
5
5
  end
@@ -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'
@@ -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
@@ -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
- 21.times { snooze }
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
- assert_equal 'a', r
80
- pool.acquire do |r|
81
- assert_equal 'a', r
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
@@ -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 test_signal_exception_possible_race_condition
59
+ def test_interrupt_signal_scheduling
60
60
  i, o = IO.pipe
61
61
  pid = Polyphony.fork do
62
62
  i.close
63
- f1 = nil
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.43.11
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-24 00:00:00.000000000 Z
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