polyphony 0.43.11 → 0.44.0

Sign up to get free protection for your applications and to get access to all the features.
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