polyphony 0.32 → 0.33

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