polyphony 0.32 → 0.33

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.
@@ -22,6 +22,7 @@ void Gyro_ref_count_incr();
22
22
  void Gyro_ref_count_decr();
23
23
 
24
24
  VALUE Gyro_Async_await(VALUE async);
25
+ VALUE Gyro_Async_await_no_raise(VALUE async);
25
26
 
26
27
  VALUE IO_read_watcher(VALUE io);
27
28
  VALUE IO_write_watcher(VALUE io);
@@ -1,7 +1,6 @@
1
1
  #include "gyro.h"
2
2
 
3
3
  struct Gyro_Queue {
4
- VALUE self;
5
4
  VALUE queue;
6
5
  VALUE wait_queue;
7
6
  };
@@ -45,7 +44,6 @@ static VALUE Gyro_Queue_initialize(VALUE self) {
45
44
  struct Gyro_Queue *queue;
46
45
  GetGyro_Queue(self, queue);
47
46
 
48
- queue->self = self;
49
47
  queue->queue = rb_ary_new();
50
48
  queue->wait_queue = rb_ary_new();
51
49
 
@@ -69,15 +67,17 @@ VALUE Gyro_Queue_shift(VALUE self) {
69
67
  struct Gyro_Queue *queue;
70
68
  GetGyro_Queue(self, queue);
71
69
 
72
- while (1) {
73
- if (RARRAY_LEN(queue->queue) > 0) {
74
- return rb_ary_shift(queue->queue);
75
- }
76
-
70
+ if (RARRAY_LEN(queue->queue) == 0) {
77
71
  VALUE async = rb_funcall(cGyro_Async, ID_new, 0);
78
72
  rb_ary_push(queue->wait_queue, async);
79
- Gyro_Async_await(async);
73
+ VALUE ret = Gyro_Async_await_no_raise(async);
74
+ if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
75
+ rb_ary_delete(queue->wait_queue, async);
76
+ return rb_funcall(rb_mKernel, ID_raise, 1, ret);
77
+ }
80
78
  }
79
+
80
+ return rb_ary_shift(queue->queue);
81
81
  }
82
82
 
83
83
  VALUE Gyro_Queue_shift_no_wait(VALUE self) {
@@ -96,7 +96,8 @@ VALUE Gyro_Queue_shift_each(VALUE self) {
96
96
 
97
97
  if (rb_block_given_p()) {
98
98
  long len = RARRAY_LEN(old_queue);
99
- for (long i = 0; i < len; i++) {
99
+ long i;
100
+ for (i = 0; i < len; i++) {
100
101
  rb_yield(RARRAY_AREF(old_queue, i));
101
102
  }
102
103
  RB_GC_GUARD(old_queue);
@@ -85,9 +85,7 @@ static VALUE Gyro_Selector_initialize(VALUE self, VALUE thread) {
85
85
 
86
86
  ev_async_init(&selector->async, dummy_async_callback);
87
87
  ev_async_start(selector->ev_loop, &selector->async);
88
-
89
88
  ev_run(selector->ev_loop, EVRUN_NOWAIT);
90
-
91
89
  return Qnil;
92
90
  }
93
91
 
@@ -137,9 +135,9 @@ VALUE Gyro_Selector_break_out_of_ev_loop(VALUE self) {
137
135
 
138
136
  if (selector->ev_loop_running) {
139
137
  // Since the loop will run until at least one event has occurred, we signal
140
- // the associated async watcher, which will cause the ev loop to return. In
141
- // contrast to using `ev_break` to break out of the loop, which should be
142
- // called from the same thread (from within the ev_loop), using an
138
+ // the selector's associated async watcher, which will cause the ev loop to
139
+ // return. In contrast to using `ev_break` to break out of the loop, which
140
+ // should be called from the same thread (from within the ev_loop), using an
143
141
  // `ev_async` allows us to interrupt the event loop across threads.
144
142
  ev_async_send(selector->ev_loop, &selector->async);
145
143
  return Qtrue;
@@ -7,7 +7,10 @@ static ID ID_empty;
7
7
  static ID ID_fiber_ref_count;
8
8
  static ID ID_ivar_event_selector_proc;
9
9
  static ID ID_ivar_event_selector;
10
+ static ID ID_ivar_join_wait_queue;
10
11
  static ID ID_ivar_main_fiber;
12
+ static ID ID_ivar_result;
13
+ static ID ID_ivar_terminated;
11
14
  static ID ID_pop;
12
15
  static ID ID_push;
13
16
  static ID ID_run_queue;
@@ -247,21 +250,6 @@ VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE fiber, VALUE resume_ob
247
250
  return self;
248
251
  }
249
252
 
250
- VALUE Thread_join_perform(VALUE self) {
251
- if (!RTEST(rb_funcall(self, rb_intern("alive?"), 0))) {
252
- return self;
253
- }
254
-
255
- VALUE async = rb_funcall(cGyro_Async, ID_new, 0);
256
- VALUE wait_queue = rb_ivar_get(self, rb_intern("@join_wait_queue"));
257
-
258
- Gyro_Queue_push(wait_queue, async);
259
-
260
- VALUE ret = Gyro_Async_await(async);
261
- RB_GC_GUARD(async);
262
- return ret;
263
- }
264
-
265
253
  void Init_Thread() {
266
254
  cQueue = rb_const_get(rb_cObject, rb_intern("Queue"));
267
255
 
@@ -282,14 +270,15 @@ void Init_Thread() {
282
270
  Thread_schedule_fiber_with_priority, 2);
283
271
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
284
272
 
285
- rb_define_method(rb_cThread, "join_perform", Thread_join_perform, 0);
286
-
287
273
  ID_create_event_selector = rb_intern("create_event_selector");
288
274
  ID_empty = rb_intern("empty?");
289
275
  ID_fiber_ref_count = rb_intern("fiber_ref_count");
290
276
  ID_ivar_event_selector = rb_intern("@event_selector");
291
277
  ID_ivar_event_selector_proc = rb_intern("@event_selector_proc");
278
+ ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
292
279
  ID_ivar_main_fiber = rb_intern("@main_fiber");
280
+ ID_ivar_result = rb_intern("@result");
281
+ ID_ivar_terminated = rb_intern("@terminated");
293
282
  ID_pop = rb_intern("pop");
294
283
  ID_push = rb_intern("push");
295
284
  ID_run_queue = rb_intern("run_queue");
@@ -23,6 +23,7 @@ module Polyphony
23
23
  exceptions = import './polyphony/core/exceptions'
24
24
  Cancel = exceptions::Cancel
25
25
  MoveOn = exceptions::MoveOn
26
+ Restart = exceptions::Restart
26
27
  Terminate = exceptions::Terminate
27
28
 
28
29
  Net = import './polyphony/net'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :MoveOn, :Cancel, :Terminate
3
+ export :BaseException, :MoveOn, :Cancel, :Terminate, :Restart
4
4
 
5
5
  # Common exception class for interrupting fibers. These exceptions allow
6
6
  # control of fibers. BaseException exceptions can encapsulate a value and thus
@@ -26,3 +26,6 @@ class Cancel < BaseException; end
26
26
 
27
27
  # Terminate is used to interrupt a fiber once its parent fiber has terminated.
28
28
  class Terminate < BaseException; end
29
+
30
+ # Restart is used to restart a fiber
31
+ class Restart < BaseException; end
@@ -73,6 +73,10 @@ module API
73
73
  Fiber.current.receive_pending
74
74
  end
75
75
 
76
+ def supervise(*args, &block)
77
+ Fiber.current.supervise(*args, &block)
78
+ end
79
+
76
80
  def sleep(duration = nil)
77
81
  return sleep_forever unless duration
78
82
 
@@ -8,14 +8,15 @@ Exceptions = import('../core/exceptions')
8
8
 
9
9
  # Exeption overrides
10
10
  class ::Exception
11
+ EXIT_EXCEPTION_CLASSES = [::Interrupt, ::SystemExit].freeze
12
+
11
13
  class << self
12
14
  attr_accessor :__disable_sanitized_backtrace__
13
15
  end
14
16
 
15
- alias_method :orig_initialize, :initialize
16
-
17
- EXIT_EXCEPTION_CLASSES = [::Interrupt, ::SystemExit].freeze
17
+ attr_accessor :source_fiber
18
18
 
19
+ alias_method :orig_initialize, :initialize
19
20
  def initialize(*args)
20
21
  @__raising_fiber__ = Fiber.current
21
22
  orig_initialize(*args)
@@ -27,6 +27,15 @@ module FiberControl
27
27
  end
28
28
  alias_method :stop, :interrupt
29
29
 
30
+ def restart(value = nil)
31
+ raise "Can''t restart main fiber" if @main
32
+ return parent.spin(&@block).tap { |f| f.schedule(value) } unless @running
33
+
34
+ schedule Exceptions::Restart.new(value)
35
+ self
36
+ end
37
+ alias_method :reset, :restart
38
+
30
39
  def cancel!
31
40
  return if @running == false
32
41
 
@@ -52,6 +61,35 @@ module FiberControl
52
61
  else RuntimeError.new
53
62
  end
54
63
  end
64
+
65
+ def supervise(on_error: nil, &block)
66
+ @on_child_done = proc { schedule }
67
+ loop { supervise_perform(on_error, &block) }
68
+ end
69
+
70
+ def supervise_perform(policy, &block)
71
+ suspend
72
+ rescue Polyphony::Restart
73
+ restart_all_children
74
+ rescue Exception => e
75
+ case e.source_fiber
76
+ when nil, self
77
+ Kernel.raise e
78
+ else
79
+ handle_supervisor_exception(e, e.source_fiber, policy, &block)
80
+ end
81
+ end
82
+
83
+ def handle_supervisor_exception(error, fiber, policy, &block)
84
+ return block.call(fiber, error) if block
85
+
86
+ case policy
87
+ when :restart
88
+ fiber.restart
89
+ when :restart_all
90
+ @children.keys.each(&:restart)
91
+ end
92
+ end
55
93
  end
56
94
 
57
95
  # Class methods for controlling fibers (namely await and select)
@@ -151,7 +189,7 @@ module ChildFiberControl
151
189
  (@children ||= {}).keys
152
190
  end
153
191
 
154
- def spin(tag = nil, orig_caller = caller, &block)
192
+ def spin(tag = nil, orig_caller = Kernel.caller, &block)
155
193
  f = Fiber.new { |v| f.run(v) }
156
194
  f.prepare(tag, block, orig_caller, self)
157
195
  (@children ||= {})[f] = true
@@ -160,6 +198,7 @@ module ChildFiberControl
160
198
 
161
199
  def child_done(child_fiber)
162
200
  @children.delete(child_fiber)
201
+ @on_child_done&.(child_fiber)
163
202
  end
164
203
 
165
204
  def terminate_all_children
@@ -174,6 +213,11 @@ module ChildFiberControl
174
213
 
175
214
  Fiber.await(*@children.keys)
176
215
  end
216
+
217
+ def shutdown_all_children
218
+ terminate_all_children
219
+ await_all_children
220
+ end
177
221
  end
178
222
 
179
223
  # Fiber extensions
@@ -187,13 +231,13 @@ class ::Fiber
187
231
  attr_accessor :tag, :thread, :parent
188
232
 
189
233
  def prepare(tag, block, caller, parent)
190
- __fiber_trace__(:fiber_create, self)
191
234
  @thread = Thread.current
192
235
  @tag = tag
193
236
  @parent = parent
194
237
  @caller = caller
195
238
  @block = block
196
239
  @mailbox = Gyro::Queue.new
240
+ __fiber_trace__(:fiber_create, self)
197
241
  schedule
198
242
  end
199
243
 
@@ -207,16 +251,21 @@ class ::Fiber
207
251
  end
208
252
 
209
253
  def run(first_value)
210
- setup(first_value)
211
- uncaught = nil
254
+ setup first_value
212
255
  result = @block.(first_value)
256
+ finalize result
257
+ rescue Exceptions::Restart => e
258
+ restart_self(e.value)
213
259
  rescue Exceptions::MoveOn, Exceptions::Terminate => e
214
- result = e.value
260
+ finalize e.value
215
261
  rescue Exception => e
216
- result = e
217
- uncaught = true
218
- ensure
219
- finalize(result, uncaught)
262
+ e.source_fiber = self
263
+ finalize e, true
264
+ end
265
+
266
+ def restart_self(first_value)
267
+ @mailbox = Gyro::Queue.new
268
+ run(first_value)
220
269
  end
221
270
 
222
271
  def setup(first_value)
@@ -226,8 +275,7 @@ class ::Fiber
226
275
  end
227
276
 
228
277
  def finalize(result, uncaught_exception = false)
229
- terminate_all_children
230
- await_all_children
278
+ result, uncaught_exception = finalize_children(result, uncaught_exception)
231
279
  __fiber_trace__(:fiber_terminate, self, result)
232
280
  @result = result
233
281
  @running = false
@@ -236,6 +284,19 @@ class ::Fiber
236
284
  Thread.current.switch_fiber
237
285
  end
238
286
 
287
+ # Shuts down all children of the current fiber. If any exception occurs while
288
+ # the children are shut down, it is returned along with the uncaught_exception
289
+ # flag set. Otherwise, it returns the given arguments.
290
+ def finalize_children(result, uncaught_exception)
291
+ begin
292
+ shutdown_all_children
293
+ rescue Exception => e
294
+ result = e
295
+ uncaught_exception = true
296
+ end
297
+ [result, uncaught_exception]
298
+ end
299
+
239
300
  def inform_dependants(result, uncaught_exception)
240
301
  @parent.child_done(self)
241
302
  @when_done_procs&.each { |p| p.(result) }
@@ -4,24 +4,27 @@ Exceptions = import '../core/exceptions'
4
4
 
5
5
  # Thread extensions
6
6
  class ::Thread
7
- attr_reader :main_fiber
7
+ attr_reader :main_fiber, :result
8
8
 
9
9
  alias_method :orig_initialize, :initialize
10
10
  def initialize(*args, &block)
11
11
  @join_wait_queue = Gyro::Queue.new
12
12
  @args = args
13
13
  @block = block
14
+ @finalization_mutex = Mutex.new
14
15
  orig_initialize { execute }
15
16
  end
16
17
 
17
18
  def execute
18
19
  setup
20
+ @ready = true
19
21
  result = @block.(*@args)
20
22
  rescue Exceptions::MoveOn, Exceptions::Terminate => e
21
23
  result = e.value
22
24
  rescue Exception => e
23
25
  result = e
24
26
  ensure
27
+ @ready = true
25
28
  finalize(result)
26
29
  end
27
30
 
@@ -36,7 +39,11 @@ class ::Thread
36
39
  Fiber.current.terminate_all_children
37
40
  Fiber.current.await_all_children
38
41
  end
39
- signal_waiters(result)
42
+ @finalization_mutex.synchronize do
43
+ @terminated = true
44
+ @result = result
45
+ signal_waiters(result)
46
+ end
40
47
  stop_event_selector
41
48
  end
42
49
 
@@ -46,11 +53,15 @@ class ::Thread
46
53
 
47
54
  alias_method :orig_join, :join
48
55
  def join(timeout = nil)
49
- if timeout
50
- move_on_after(timeout) { join_perform }
51
- else
52
- join_perform
56
+ async = Gyro::Async.new
57
+ @finalization_mutex.synchronize do
58
+ if @terminated
59
+ @result.is_a?(Exception) ? (raise @result) : (return @result)
60
+ else
61
+ @join_wait_queue.push(async)
62
+ end
53
63
  end
64
+ timeout ? move_on_after(timeout) { async.await } : async.await
54
65
  end
55
66
  alias_method :await, :join
56
67
 
@@ -60,7 +71,9 @@ class ::Thread
60
71
  error = RuntimeError.new if error.nil?
61
72
  error = RuntimeError.new(error) if error.is_a?(String)
62
73
  error = error.new if error.is_a?(Class)
63
- @main_fiber.raise(error)
74
+
75
+ sleep 0.0001 until @ready
76
+ main_fiber&.raise(error)
64
77
  end
65
78
 
66
79
  alias_method :orig_kill, :kill
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.32'
4
+ VERSION = '0.33'
5
5
  end
@@ -4,11 +4,11 @@ Gem::Specification.new do |s|
4
4
  s.name = 'polyphony'
5
5
  s.version = Polyphony::VERSION
6
6
  s.licenses = ['MIT']
7
- s.summary = 'Polyphony: Fiber-based Concurrency for Ruby'
7
+ s.summary = 'Fine grained concurrency for Ruby'
8
8
  s.author = 'Sharon Rosner'
9
9
  s.email = 'ciconia@gmail.com'
10
10
  s.files = `git ls-files`.split
11
- s.homepage = 'https://dfab.gitbook.io/polyphony/'
11
+ s.homepage = 'https://digital-fabric.github.io/polyphony'
12
12
  s.metadata = {
13
13
  "source_code_uri" => "https://github.com/digital-fabric/polyphony",
14
14
  "documentation_uri" => "https://digital-fabric.github.io/polyphony/",
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ @count = 0
4
+
5
+ def run_tests
6
+ @count += 1
7
+ puts "!(#{@count})"
8
+ # output = `ruby test/test_thread.rb -n test_thread_inspect`
9
+ system('ruby test/run.rb')
10
+ return if $?.exitstatus == 0
11
+
12
+ exit!
13
+ # puts
14
+ # puts output
15
+ # exit!
16
+ end
17
+
18
+ loop {
19
+ run_tests
20
+ }