polyphony 0.29 → 0.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +15 -10
  6. data/docs/getting-started/tutorial.md +3 -3
  7. data/docs/index.md +2 -3
  8. data/docs/{technical-overview → main-concepts}/concurrency.md +62 -15
  9. data/docs/{technical-overview → main-concepts}/design-principles.md +21 -8
  10. data/docs/{technical-overview → main-concepts}/exception-handling.md +80 -38
  11. data/docs/{technical-overview → main-concepts}/extending.md +4 -3
  12. data/docs/{technical-overview → main-concepts}/fiber-scheduling.md +3 -3
  13. data/docs/{technical-overview.md → main-concepts.md} +2 -2
  14. data/examples/core/xx-at_exit.rb +29 -0
  15. data/examples/core/xx-fork-terminate.rb +27 -0
  16. data/examples/core/xx-pingpong.rb +18 -0
  17. data/examples/core/xx-stop.rb +20 -0
  18. data/ext/gyro/async.c +1 -1
  19. data/ext/gyro/extconf.rb +0 -3
  20. data/ext/gyro/gyro.c +7 -8
  21. data/ext/gyro/gyro.h +2 -0
  22. data/ext/gyro/queue.c +6 -6
  23. data/ext/gyro/selector.c +32 -1
  24. data/ext/gyro/thread.c +55 -9
  25. data/ext/gyro/timer.c +1 -0
  26. data/lib/polyphony/core/exceptions.rb +4 -1
  27. data/lib/polyphony/core/global_api.rb +1 -6
  28. data/lib/polyphony/core/thread_pool.rb +3 -3
  29. data/lib/polyphony/extensions/core.rb +7 -1
  30. data/lib/polyphony/extensions/fiber.rb +159 -72
  31. data/lib/polyphony/extensions/io.rb +2 -4
  32. data/lib/polyphony/extensions/openssl.rb +0 -17
  33. data/lib/polyphony/extensions/thread.rb +46 -22
  34. data/lib/polyphony/version.rb +1 -1
  35. data/lib/polyphony.rb +20 -18
  36. data/test/coverage.rb +1 -1
  37. data/test/helper.rb +7 -3
  38. data/test/test_fiber.rb +285 -72
  39. data/test/test_global_api.rb +7 -52
  40. data/test/test_io.rb +8 -0
  41. data/test/test_signal.rb +1 -0
  42. data/test/test_thread.rb +76 -56
  43. data/test/test_thread_pool.rb +27 -5
  44. data/test/test_throttler.rb +1 -0
  45. metadata +12 -12
  46. data/lib/polyphony/core/supervisor.rb +0 -114
  47. data/lib/polyphony/line_reader.rb +0 -82
  48. data/test/test_gyro.rb +0 -25
  49. data/test/test_supervisor.rb +0 -180
data/ext/gyro/gyro.c CHANGED
@@ -47,11 +47,11 @@ VALUE SYM_fiber_switchpoint;
47
47
  VALUE SYM_fiber_terminate;
48
48
 
49
49
 
50
- // static VALUE Gyro_break_set(VALUE self) {
51
- // break_flag = 1;
52
- // ev_break(EV_DEFAULT, EVBREAK_ALL);
53
- // return Qnil;
54
- // }
50
+ static VALUE Gyro_break_set(VALUE self) {
51
+ // break_flag = 1;
52
+ ev_break(Gyro_Selector_current_thread_ev_loop(), EVBREAK_ALL);
53
+ return Qnil;
54
+ }
55
55
 
56
56
  // static VALUE Gyro_break_get(VALUE self) {
57
57
  // return (break_flag == 0) ? Qfalse : Qtrue;
@@ -82,7 +82,6 @@ static VALUE Gyro_unref(VALUE self) {
82
82
  }
83
83
 
84
84
  static VALUE Gyro_suspend(VALUE self) {
85
- rb_ivar_set(self, ID_runnable_value, Qnil);
86
85
  VALUE ret = Thread_switch_fiber(rb_thread_current());
87
86
 
88
87
  if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
@@ -117,7 +116,7 @@ static VALUE Fiber_state(VALUE self) {
117
116
  return SYM_waiting;
118
117
  }
119
118
 
120
- inline void Gyro_schedule_fiber(VALUE fiber, VALUE value) {
119
+ void Gyro_schedule_fiber(VALUE fiber, VALUE value) {
121
120
  VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
122
121
  if (thread != Qnil) {
123
122
  Thread_schedule_fiber(thread, fiber, value);
@@ -140,7 +139,7 @@ void Init_Gyro() {
140
139
  rb_define_singleton_method(mGyro, "unref", Gyro_unref, 0);
141
140
  rb_define_singleton_method(mGyro, "trace", Gyro_trace, 1);
142
141
 
143
- // rb_define_singleton_method(mGyro, "break!", Gyro_break_set, 0);
142
+ rb_define_singleton_method(mGyro, "break!", Gyro_break_set, 0);
144
143
  // rb_define_singleton_method(mGyro, "break?", Gyro_break_get, 0);
145
144
 
146
145
  rb_define_global_function("snooze", Gyro_snooze, 0);
data/ext/gyro/gyro.h CHANGED
@@ -48,7 +48,9 @@ VALUE Thread_switch_fiber(VALUE thread);
48
48
  VALUE Fiber_await();
49
49
  VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
50
50
  VALUE Thread_post_fork(VALUE thread);
51
+ VALUE Gyro_Selector_break_out_of_ev_loop(VALUE self);
51
52
 
53
+ VALUE Gyro_Queue_push(VALUE self, VALUE value);
52
54
 
53
55
  #define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
54
56
  #define INSPECT(...) (rb_funcall(rb_cObject, rb_intern("p"), __VA_ARGS__))
data/ext/gyro/queue.c CHANGED
@@ -75,7 +75,9 @@ VALUE Gyro_Queue_shift(VALUE self) {
75
75
 
76
76
  VALUE async = rb_funcall(cGyro_Async, ID_new, 0);
77
77
  rb_ary_push(queue->wait_queue, async);
78
- return Gyro_Async_await(async);
78
+ VALUE ret = Gyro_Async_await(async);
79
+ RB_GC_GUARD(async);
80
+ return ret;
79
81
  }
80
82
 
81
83
  VALUE Gyro_Queue_shift_no_wait(VALUE self) {
@@ -85,7 +87,7 @@ VALUE Gyro_Queue_shift_no_wait(VALUE self) {
85
87
  return rb_ary_shift(queue->queue);
86
88
  }
87
89
 
88
- VALUE Gyro_Queue_shift_all(VALUE self) {
90
+ VALUE Gyro_Queue_shift_each(VALUE self) {
89
91
  struct Gyro_Queue *queue;
90
92
  GetGyro_Queue(self, queue);
91
93
 
@@ -97,9 +99,7 @@ VALUE Gyro_Queue_shift_all(VALUE self) {
97
99
  for (long i = 0; i < len; i++) {
98
100
  rb_yield(RARRAY_AREF(old_queue, i));
99
101
  }
100
- // while (RARRAY_LEN(old_queue) > 0) {
101
- // rb_yield(rb_ary_shift(old_queue));
102
- // }
102
+ RB_GC_GUARD(old_queue);
103
103
  return self;
104
104
  }
105
105
  else {
@@ -135,7 +135,7 @@ void Init_Gyro_Queue() {
135
135
 
136
136
  rb_define_method(cGyro_Queue, "shift_no_wait", Gyro_Queue_shift_no_wait, 0);
137
137
 
138
- rb_define_method(cGyro_Queue, "shift_each", Gyro_Queue_shift_all, 0);
138
+ rb_define_method(cGyro_Queue, "shift_each", Gyro_Queue_shift_each, 0);
139
139
  rb_define_method(cGyro_Queue, "clear", Gyro_Queue_clear, 0);
140
140
  rb_define_method(cGyro_Queue, "empty?", Gyro_Queue_empty_p, 0);
141
141
  }
data/ext/gyro/selector.c CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  struct Gyro_Selector {
4
4
  struct ev_loop *ev_loop;
5
- long run_no_wait_count;
5
+ long run_no_wait_count;
6
+ int ev_loop_running;
7
+ struct ev_async async;
6
8
  };
7
9
 
8
10
  VALUE cGyro_Selector = Qnil;
@@ -13,6 +15,7 @@ static void Gyro_Selector_mark(void *ptr) {
13
15
 
14
16
  static void Gyro_Selector_free(void *ptr) {
15
17
  struct Gyro_Selector *selector = ptr;
18
+ ev_async_stop(selector->ev_loop, &selector->async);
16
19
  if (selector->ev_loop && !ev_is_default_loop(selector->ev_loop)) {
17
20
  // printf("Selector garbage collected before being stopped!\n");
18
21
  ev_loop_destroy(selector->ev_loop);
@@ -67,6 +70,11 @@ long Gyro_Selector_pending_count(VALUE self) {
67
70
  return ev_pending_count(selector->ev_loop);
68
71
  }
69
72
 
73
+ void dummy_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
74
+ // This callback does nothing, the selector's async is used solely for waking
75
+ // up the event loop.
76
+ }
77
+
70
78
  static VALUE Gyro_Selector_initialize(VALUE self, VALUE thread) {
71
79
  struct Gyro_Selector *selector;
72
80
  GetGyro_Selector(self, selector);
@@ -75,6 +83,9 @@ static VALUE Gyro_Selector_initialize(VALUE self, VALUE thread) {
75
83
  selector->ev_loop = use_default_loop ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
76
84
  selector->run_no_wait_count = 0;
77
85
 
86
+ ev_async_init(&selector->async, dummy_async_callback);
87
+ ev_async_start(selector->ev_loop, &selector->async);
88
+
78
89
  ev_run(selector->ev_loop, EVRUN_NOWAIT);
79
90
 
80
91
  return Qnil;
@@ -86,7 +97,9 @@ inline VALUE Gyro_Selector_run(VALUE self, VALUE current_fiber) {
86
97
  if (selector->ev_loop) {
87
98
  selector->run_no_wait_count = 0;
88
99
  FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
100
+ selector->ev_loop_running = 1;
89
101
  ev_run(selector->ev_loop, EVRUN_ONCE);
102
+ selector->ev_loop_running = 0;
90
103
  FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
91
104
  }
92
105
  return Qnil;
@@ -118,6 +131,23 @@ VALUE Gyro_Selector_stop(VALUE self) {
118
131
  return Qnil;
119
132
  }
120
133
 
134
+ VALUE Gyro_Selector_break_out_of_ev_loop(VALUE self) {
135
+ struct Gyro_Selector *selector;
136
+ GetGyro_Selector(self, selector);
137
+
138
+ if (selector->ev_loop_running) {
139
+ // 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
143
+ // `ev_async` allows us to interrupt the event loop across threads.
144
+ ev_async_send(selector->ev_loop, &selector->async);
145
+ return Qtrue;
146
+ }
147
+
148
+ return Qnil;
149
+ }
150
+
121
151
  inline static VALUE Gyro_Selector_wait_readable(VALUE self, VALUE io) {
122
152
  VALUE watcher = IO_read_watcher(io);
123
153
  return Gyro_IO_await(watcher);
@@ -143,4 +173,5 @@ void Init_Gyro_Selector() {
143
173
  rb_define_method(cGyro_Selector, "wait_readable", Gyro_Selector_wait_readable, 1);
144
174
  rb_define_method(cGyro_Selector, "wait_writable", Gyro_Selector_wait_writable, 1);
145
175
  rb_define_method(cGyro_Selector, "wait_timeout", Gyro_Selector_wait_timeout, 1);
176
+ rb_define_method(cGyro_Selector, "break_out_of_ev_loop", Gyro_Selector_break_out_of_ev_loop, 0);
146
177
  }
data/ext/gyro/thread.c CHANGED
@@ -53,30 +53,33 @@ static VALUE Thread_stop_event_selector(VALUE self) {
53
53
  if (selector != Qnil) {
54
54
  rb_funcall(selector, ID_stop, 0);
55
55
  }
56
+ // Nullify the selector in order to prevent running the
57
+ // selector after the thread is done running.
58
+ rb_ivar_set(self, ID_ivar_event_selector, Qnil);
56
59
 
57
60
  return self;
58
61
  }
59
62
 
60
- inline VALUE Thread_ref(VALUE self) {
63
+ VALUE Thread_ref(VALUE self) {
61
64
  VALUE count = rb_ivar_get(self, ID_fiber_ref_count);
62
65
  int new_count = NUM2INT(count) + 1;
63
66
  rb_ivar_set(self, ID_fiber_ref_count, INT2NUM(new_count));
64
67
  return self;
65
68
  }
66
69
 
67
- inline VALUE Thread_unref(VALUE self) {
70
+ VALUE Thread_unref(VALUE self) {
68
71
  VALUE count = rb_ivar_get(self, ID_fiber_ref_count);
69
72
  int new_count = NUM2INT(count) - 1;
70
73
  rb_ivar_set(self, ID_fiber_ref_count, INT2NUM(new_count));
71
74
  return self;
72
75
  }
73
76
 
74
- inline int Thread_fiber_ref_count(VALUE self) {
77
+ int Thread_fiber_ref_count(VALUE self) {
75
78
  VALUE count = rb_ivar_get(self, ID_fiber_ref_count);
76
79
  return NUM2INT(count);
77
80
  }
78
81
 
79
- inline void Thread_fiber_reset_ref_count(VALUE self) {
82
+ void Thread_fiber_reset_ref_count(VALUE self) {
80
83
  rb_ivar_set(self, ID_fiber_ref_count, INT2NUM(0));
81
84
  }
82
85
 
@@ -97,7 +100,10 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
97
100
  return stats;
98
101
  }
99
102
 
100
- inline VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
103
+ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
104
+ if (rb_fiber_alive_p(fiber) != Qtrue) {
105
+ return self;
106
+ }
101
107
  FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
102
108
  // if fiber is already scheduled, just set the scheduled value, then return
103
109
  rb_ivar_set(fiber, ID_runnable_value, value);
@@ -108,13 +114,24 @@ inline VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
108
114
  VALUE queue = rb_ivar_get(self, ID_run_queue);
109
115
  rb_ary_push(queue, fiber);
110
116
  rb_ivar_set(fiber, ID_runnable, Qtrue);
117
+
118
+ if (rb_thread_current() != self) {
119
+ // if the fiber scheduling is done across threads, we need to make sure the
120
+ // target thread is woken up in case it is in the middle of running its
121
+ // event selector. Otherwise it's gonna be stuck waiting for an event to
122
+ // happen, not knowing that it there's already a fiber ready to run in its
123
+ // run queue.
124
+ VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
125
+ if (selector != Qnil) {
126
+ Gyro_Selector_break_out_of_ev_loop(selector);
127
+ }
128
+ }
111
129
  return self;
112
130
  }
113
131
 
114
132
  VALUE Thread_switch_fiber(VALUE self) {
115
- VALUE current_fiber;
133
+ VALUE current_fiber = rb_fiber_current();
116
134
  if (__tracing_enabled__) {
117
- current_fiber = rb_fiber_current();
118
135
  if (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse) {
119
136
  rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_switchpoint, current_fiber);
120
137
  }
@@ -170,7 +187,7 @@ VALUE Thread_post_fork(VALUE self) {
170
187
  return self;
171
188
  }
172
189
 
173
- inline VALUE Fiber_await() {
190
+ VALUE Fiber_await() {
174
191
  VALUE thread = rb_thread_current();
175
192
  Thread_ref(thread);
176
193
  VALUE ret = Thread_switch_fiber(thread);
@@ -179,10 +196,37 @@ inline VALUE Fiber_await() {
179
196
  return ret;
180
197
  }
181
198
 
182
- inline VALUE Thread_current_event_selector() {
199
+ VALUE Thread_current_event_selector() {
183
200
  return rb_ivar_get(rb_thread_current(), ID_ivar_event_selector);
184
201
  }
185
202
 
203
+ VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE resume_obj) {
204
+ VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
205
+ Thread_schedule_fiber(self, rb_fiber_current(), resume_obj);
206
+
207
+ if (Gyro_Selector_break_out_of_ev_loop(selector) == Qnil) {
208
+ // we're not inside the ev_loop, so we just do a switchpoint
209
+ Thread_switch_fiber(self);
210
+ }
211
+
212
+ return self;
213
+ }
214
+
215
+ VALUE Thread_join_perform(VALUE self) {
216
+ if (!RTEST(rb_funcall(self, rb_intern("alive?"), 0))) {
217
+ return self;
218
+ }
219
+
220
+ VALUE async = rb_funcall(cGyro_Async, ID_new, 0);
221
+ VALUE wait_queue = rb_ivar_get(self, rb_intern("@join_wait_queue"));
222
+
223
+ Gyro_Queue_push(wait_queue, async);
224
+
225
+ VALUE ret = Gyro_Async_await(async);
226
+ RB_GC_GUARD(async);
227
+ return ret;
228
+ }
229
+
186
230
  void Init_Thread() {
187
231
  cQueue = rb_const_get(rb_cObject, rb_intern("Queue"));
188
232
 
@@ -196,10 +240,12 @@ void Init_Thread() {
196
240
  rb_define_method(rb_cThread, "stop_event_selector", Thread_stop_event_selector, 0);
197
241
  rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
198
242
  rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
243
+ rb_define_method(rb_cThread, "break_out_of_ev_loop", Thread_fiber_break_out_of_ev_loop, 1);
199
244
 
200
245
  rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
201
246
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
202
247
 
248
+ rb_define_method(rb_cThread, "join_perform", Thread_join_perform, 0);
203
249
 
204
250
  ID_create_event_selector = rb_intern("create_event_selector");
205
251
  ID_empty = rb_intern("empty?");
data/ext/gyro/timer.c CHANGED
@@ -129,6 +129,7 @@ VALUE Gyro_Timer_await(VALUE self) {
129
129
  timer->fiber = Qnil;
130
130
  timer->selector = Qnil;
131
131
  ev_timer_stop(timer->ev_loop, &timer->ev_timer);
132
+ timer->ev_loop = 0;
132
133
  }
133
134
  RB_GC_GUARD(self);
134
135
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :Interrupt, :MoveOn, :Cancel
3
+ export :Interrupt, :MoveOn, :Cancel, :Terminate
4
4
 
5
5
  # Common exception class for interrupting fibers. These exceptions allow
6
6
  # control of fibers. Interrupt exceptions can encapsulate a value and thus
@@ -24,3 +24,6 @@ class MoveOn < Interrupt; end
24
24
  # Cancel is used to interrupt a long-running blocking operation, bubbling the
25
25
  # exception up through cancel scopes and supervisors.
26
26
  class Cancel < Interrupt; end
27
+
28
+ # Terminate is used to interrupt a fiber once its parent fiber has terminated.
29
+ class Terminate < Interrupt; end
@@ -6,7 +6,6 @@ import '../extensions/core'
6
6
  import '../extensions/fiber'
7
7
 
8
8
  Exceptions = import '../core/exceptions'
9
- Supervisor = import '../core/supervisor'
10
9
  Throttler = import '../core/throttler'
11
10
 
12
11
  # Global API methods to be included in ::Object
@@ -30,7 +29,7 @@ module API
30
29
  end
31
30
 
32
31
  def spin(tag = nil, &block)
33
- Fiber.spin(tag, caller, &block)
32
+ Fiber.current.spin(tag, caller, &block)
34
33
  end
35
34
 
36
35
  def spin_loop(&block)
@@ -78,10 +77,6 @@ module API
78
77
  Thread.current.fiber_unref
79
78
  end
80
79
 
81
- def supervise(&block)
82
- Supervisor.new.await(&block)
83
- end
84
-
85
80
  def throttled_loop(rate, count: nil, &block)
86
81
  throttler = Throttler.new(rate)
87
82
  if count
@@ -22,9 +22,9 @@ class ThreadPool
22
22
  def process(&block)
23
23
  setup unless @task_queue
24
24
 
25
- watcher = Gyro::Async.new
26
- @task_queue << [block, watcher]
27
- watcher.await
25
+ async = Gyro::Async.new
26
+ @task_queue << [block, async]
27
+ async.await
28
28
  end
29
29
 
30
30
  def cast(&block)
@@ -14,6 +14,8 @@ class ::Exception
14
14
 
15
15
  alias_method :orig_initialize, :initialize
16
16
 
17
+ EXIT_EXCEPTION_CLASSES = [::Interrupt, ::SystemExit].freeze
18
+
17
19
  def initialize(*args)
18
20
  @__raising_fiber__ = Fiber.current
19
21
  orig_initialize(*args)
@@ -21,11 +23,15 @@ class ::Exception
21
23
 
22
24
  alias_method_once :orig_backtrace, :backtrace
23
25
  def backtrace
24
- unless @first_backtrace_call
26
+ unless @first_backtrace_call || EXIT_EXCEPTION_CLASSES.include?(self.class)
25
27
  @first_backtrace_call = true
26
28
  return orig_backtrace
27
29
  end
28
30
 
31
+ sanitized_backtrace
32
+ end
33
+
34
+ def sanitized_backtrace
29
35
  if @__raising_fiber__
30
36
  backtrace = orig_backtrace || []
31
37
  sanitize(backtrace + @__raising_fiber__.caller)