polyphony 0.28 → 0.29

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -4
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE +1 -1
  6. data/README.md +23 -21
  7. data/Rakefile +2 -0
  8. data/TODO.md +0 -3
  9. data/docs/_includes/prevnext.html +17 -0
  10. data/docs/_layouts/default.html +106 -0
  11. data/docs/_sass/custom/custom.scss +21 -0
  12. data/docs/faq.md +13 -10
  13. data/docs/getting-started/installing.md +2 -0
  14. data/docs/getting-started/tutorial.md +5 -3
  15. data/docs/index.md +4 -5
  16. data/docs/technical-overview/concurrency.md +21 -19
  17. data/docs/technical-overview/design-principles.md +12 -20
  18. data/docs/technical-overview/exception-handling.md +70 -1
  19. data/docs/technical-overview/extending.md +1 -0
  20. data/docs/technical-overview/fiber-scheduling.md +109 -88
  21. data/docs/user-guide/all-about-timers.md +126 -0
  22. data/docs/user-guide/web-server.md +2 -2
  23. data/docs/user-guide.md +1 -1
  24. data/examples/core/xx-deferring-an-operation.rb +2 -2
  25. data/examples/core/xx-sleep-forever.rb +9 -0
  26. data/examples/core/xx-snooze-starve.rb +16 -0
  27. data/examples/core/xx-spin_error_backtrace.rb +1 -1
  28. data/examples/core/xx-trace.rb +1 -2
  29. data/examples/core/xx-worker-thread.rb +30 -0
  30. data/examples/io/xx-happy-eyeballs.rb +37 -0
  31. data/ext/gyro/gyro.c +8 -3
  32. data/ext/gyro/gyro.h +7 -1
  33. data/ext/gyro/queue.c +35 -3
  34. data/ext/gyro/selector.c +31 -2
  35. data/ext/gyro/thread.c +18 -16
  36. data/lib/polyphony/core/global_api.rb +0 -1
  37. data/lib/polyphony/core/thread_pool.rb +5 -0
  38. data/lib/polyphony/core/throttler.rb +0 -1
  39. data/lib/polyphony/extensions/fiber.rb +14 -3
  40. data/lib/polyphony/extensions/thread.rb +16 -4
  41. data/lib/polyphony/irb.rb +7 -1
  42. data/lib/polyphony/trace.rb +44 -11
  43. data/lib/polyphony/version.rb +1 -1
  44. data/lib/polyphony.rb +1 -0
  45. data/test/helper.rb +1 -3
  46. data/test/test_async.rb +1 -1
  47. data/test/test_cancel_scope.rb +3 -3
  48. data/test/test_fiber.rb +157 -54
  49. data/test/test_global_api.rb +51 -1
  50. data/test/test_gyro.rb +4 -156
  51. data/test/test_io.rb +1 -1
  52. data/test/test_supervisor.rb +2 -2
  53. data/test/test_thread.rb +72 -1
  54. data/test/test_thread_pool.rb +6 -2
  55. data/test/test_throttler.rb +7 -5
  56. data/test/test_trace.rb +6 -6
  57. metadata +10 -5
  58. data/examples/core/xx-extended_fibers.rb +0 -150
  59. data/examples/core/xx-mt-scheduler.rb +0 -349
data/ext/gyro/selector.c CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  struct Gyro_Selector {
4
4
  struct ev_loop *ev_loop;
5
+ long run_no_wait_count;
5
6
  };
6
7
 
7
8
  VALUE cGyro_Selector = Qnil;
@@ -52,6 +53,13 @@ inline struct ev_loop *Gyro_Selector_current_thread_ev_loop() {
52
53
  return selector->ev_loop;
53
54
  }
54
55
 
56
+ inline ev_tstamp Gyro_Selector_now(VALUE self) {
57
+ struct Gyro_Selector *selector;
58
+ GetGyro_Selector(self, selector);
59
+
60
+ return ev_now(selector->ev_loop);
61
+ }
62
+
55
63
  long Gyro_Selector_pending_count(VALUE self) {
56
64
  struct Gyro_Selector *selector;
57
65
  GetGyro_Selector(self, selector);
@@ -65,19 +73,40 @@ static VALUE Gyro_Selector_initialize(VALUE self, VALUE thread) {
65
73
 
66
74
  int use_default_loop = (rb_thread_current() == rb_thread_main());
67
75
  selector->ev_loop = use_default_loop ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
76
+ selector->run_no_wait_count = 0;
77
+
78
+ ev_run(selector->ev_loop, EVRUN_NOWAIT);
68
79
 
69
80
  return Qnil;
70
81
  }
71
82
 
72
- inline VALUE Gyro_Selector_run(VALUE self) {
83
+ inline VALUE Gyro_Selector_run(VALUE self, VALUE current_fiber) {
73
84
  struct Gyro_Selector *selector;
74
85
  GetGyro_Selector(self, selector);
75
86
  if (selector->ev_loop) {
87
+ selector->run_no_wait_count = 0;
88
+ FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
76
89
  ev_run(selector->ev_loop, EVRUN_ONCE);
90
+ FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
77
91
  }
78
92
  return Qnil;
79
93
  }
80
94
 
95
+ inline void Gyro_Selector_run_no_wait(VALUE self, VALUE current_fiber, long runnable_count) {
96
+ struct Gyro_Selector *selector;
97
+ GetGyro_Selector(self, selector);
98
+
99
+ selector->run_no_wait_count++;
100
+ if (selector->run_no_wait_count < runnable_count || selector->run_no_wait_count < 10) {
101
+ return;
102
+ }
103
+
104
+ selector->run_no_wait_count = 0;
105
+ FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
106
+ ev_run(selector->ev_loop, EVRUN_NOWAIT);
107
+ FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
108
+ }
109
+
81
110
  VALUE Gyro_Selector_stop(VALUE self) {
82
111
  struct Gyro_Selector *selector;
83
112
  GetGyro_Selector(self, selector);
@@ -109,7 +138,7 @@ void Init_Gyro_Selector() {
109
138
  rb_define_alloc_func(cGyro_Selector, Gyro_Selector_allocate);
110
139
 
111
140
  rb_define_method(cGyro_Selector, "initialize", Gyro_Selector_initialize, 1);
112
- rb_define_method(cGyro_Selector, "run", Gyro_Selector_run, 0);
141
+ rb_define_method(cGyro_Selector, "run", Gyro_Selector_run, 1);
113
142
  rb_define_method(cGyro_Selector, "stop", Gyro_Selector_stop, 0);
114
143
  rb_define_method(cGyro_Selector, "wait_readable", Gyro_Selector_wait_readable, 1);
115
144
  rb_define_method(cGyro_Selector, "wait_writable", Gyro_Selector_wait_writable, 1);
data/ext/gyro/thread.c CHANGED
@@ -98,13 +98,16 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
98
98
  }
99
99
 
100
100
  inline VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
101
+ FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
101
102
  // if fiber is already scheduled, just set the scheduled value, then return
102
103
  rb_ivar_set(fiber, ID_runnable_value, value);
103
- if (rb_ivar_get(fiber, ID_runnable) == Qnil) {
104
- VALUE queue = rb_ivar_get(self, ID_run_queue);
105
- rb_ary_push(queue, fiber);
106
- rb_ivar_set(fiber, ID_runnable, Qtrue);
104
+ if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
105
+ return self;
107
106
  }
107
+
108
+ VALUE queue = rb_ivar_get(self, ID_run_queue);
109
+ rb_ary_push(queue, fiber);
110
+ rb_ivar_set(fiber, ID_runnable, Qtrue);
108
111
  return self;
109
112
  }
110
113
 
@@ -118,6 +121,7 @@ VALUE Thread_switch_fiber(VALUE self) {
118
121
  }
119
122
  VALUE queue = rb_ivar_get(self, ID_run_queue);
120
123
  VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
124
+
121
125
  VALUE next_fiber;
122
126
 
123
127
  while (1) {
@@ -125,17 +129,18 @@ VALUE Thread_switch_fiber(VALUE self) {
125
129
  // if (break_flag != 0) {
126
130
  // return Qnil;
127
131
  // }
128
- if ((next_fiber != Qnil) || (Thread_fiber_ref_count(self) == 0)) {
132
+ int ref_count = Thread_fiber_ref_count(self);
133
+ if (next_fiber != Qnil) {
134
+ if (ref_count > 0) {
135
+ Gyro_Selector_run_no_wait(selector, current_fiber, RARRAY_LEN(queue));
136
+ }
129
137
  break;
130
138
  }
131
-
132
- if (__tracing_enabled__) {
133
- rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_ev_loop_enter, current_fiber);
134
- }
135
- Gyro_Selector_run(selector);
136
- if (__tracing_enabled__) {
137
- rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_ev_loop_leave, current_fiber);
139
+ if (ref_count == 0) {
140
+ break;
138
141
  }
142
+
143
+ Gyro_Selector_run(selector, current_fiber);
139
144
  }
140
145
 
141
146
  if (next_fiber == Qnil) {
@@ -144,10 +149,7 @@ VALUE Thread_switch_fiber(VALUE self) {
144
149
 
145
150
  // run next fiber
146
151
  VALUE value = rb_ivar_get(next_fiber, ID_runnable_value);
147
-
148
- if (__tracing_enabled__) {
149
- rb_funcall(rb_cObject, ID_fiber_trace, 3, SYM_fiber_run, next_fiber, value);
150
- }
152
+ FIBER_TRACE(3, SYM_fiber_run, next_fiber, value);
151
153
 
152
154
  rb_ivar_set(next_fiber, ID_runnable, Qnil);
153
155
  RB_GC_GUARD(next_fiber);
@@ -32,7 +32,6 @@ module API
32
32
  def spin(tag = nil, &block)
33
33
  Fiber.spin(tag, caller, &block)
34
34
  end
35
- alias_method :defer, :spin
36
35
 
37
36
  def spin_loop(&block)
38
37
  spin { loop(&block) }
@@ -8,6 +8,11 @@ require 'etc'
8
8
  class ThreadPool
9
9
  attr_reader :size
10
10
 
11
+ def self.process(&block)
12
+ @default_pool ||= new
13
+ @default_pool.process(&block)
14
+ end
15
+
11
16
  def initialize(size = Etc.nprocessors)
12
17
  @size = size
13
18
  @task_queue = ::Queue.new
@@ -14,7 +14,6 @@ class Throttler
14
14
  @timer.await
15
15
  block.call(self)
16
16
  end
17
-
18
17
  alias_method :process, :call
19
18
 
20
19
  def stop
@@ -55,11 +55,12 @@ module FiberMessaging
55
55
  if @receive_waiting && @running
56
56
  schedule value
57
57
  else
58
- @queued_messages ||= []
58
+ @queued_messages ||= Gyro::Queue.new
59
59
  @queued_messages << value
60
60
  end
61
61
  snooze
62
62
  end
63
+ alias_method :send, :<<
63
64
 
64
65
  def receive
65
66
  if !@queued_messages || @queued_messages&.empty?
@@ -110,11 +111,11 @@ class ::Fiber
110
111
  f
111
112
  end
112
113
 
113
- attr_accessor :tag
114
- Fiber.current.tag = :main
114
+ attr_accessor :tag, :thread
115
115
 
116
116
  def setup(tag, block, caller)
117
117
  __fiber_trace__(:fiber_create, self)
118
+ @thread = Thread.current
118
119
  @tag = tag
119
120
  @calling_fiber = Fiber.current
120
121
  @caller = caller
@@ -122,12 +123,20 @@ class ::Fiber
122
123
  schedule
123
124
  end
124
125
 
126
+ def setup_main_fiber
127
+ @tag = :main
128
+ @thread = Thread.current
129
+ @running = true
130
+ end
131
+
125
132
  def run(first_value)
126
133
  Kernel.raise first_value if first_value.is_a?(Exception)
127
134
 
128
135
  start_execution(first_value)
129
136
  rescue ::Interrupt, ::SystemExit => e
130
137
  Thread.current.main_fiber.transfer e.class.new
138
+ rescue ::SignalException => e
139
+ Thread.current.main_fiber.transfer e
131
140
  rescue Exceptions::MoveOn => e
132
141
  finish_execution(e.value)
133
142
  rescue Exception => e
@@ -187,3 +196,5 @@ class ::Fiber
187
196
  end
188
197
  end
189
198
  end
199
+
200
+ Fiber.current.setup_main_fiber
@@ -4,6 +4,10 @@ Exceptions = import '../core/exceptions'
4
4
 
5
5
  # Thread extensions
6
6
  class ::Thread
7
+ def self.join_queue_mutex
8
+ @join_queue_mutex ||= Mutex.new
9
+ end
10
+
7
11
  attr_reader :main_fiber
8
12
 
9
13
  alias_method :orig_initialize, :initialize
@@ -11,10 +15,11 @@ class ::Thread
11
15
  @join_wait_queue = Gyro::Queue.new
12
16
  @block = block
13
17
  orig_initialize do
18
+ Fiber.current.setup_main_fiber
14
19
  setup_fiber_scheduling
15
20
  block.(*args)
16
- signal_waiters
17
21
  ensure
22
+ signal_waiters
18
23
  stop_event_selector
19
24
  end
20
25
  end
@@ -25,10 +30,12 @@ class ::Thread
25
30
 
26
31
  alias_method :orig_join, :join
27
32
  def join(timeout = nil)
28
- return unless alive?
29
-
30
33
  async = Gyro::Async.new
31
- @join_wait_queue << async
34
+ Thread.join_queue_mutex.synchronize do
35
+ return unless alive?
36
+
37
+ @join_wait_queue << async
38
+ end
32
39
 
33
40
  if timeout
34
41
  move_on_after(timeout) { async.await }
@@ -49,4 +56,9 @@ class ::Thread
49
56
  def location
50
57
  @block.source_location.join(':')
51
58
  end
59
+
60
+ def <<(value)
61
+ main_fiber << value
62
+ end
63
+ alias_method :send, :<<
52
64
  end
data/lib/polyphony/irb.rb CHANGED
@@ -7,7 +7,13 @@ require 'polyphony'
7
7
  # readline to return
8
8
  module ::Readline
9
9
  alias_method :orig_readline, :readline
10
+
10
11
  def readline(*args)
11
- Polyphony::ThreadPool.process { orig_readline(*args) }
12
+ async = Gyro::Async.new
13
+ Thread.new do
14
+ result = orig_readline(*args)
15
+ async.signal!(result)
16
+ end
17
+ async.await
12
18
  end
13
19
  end
@@ -6,9 +6,10 @@ require 'polyphony'
6
6
 
7
7
  STOCK_EVENTS = %i[line call return c_call c_return b_call b_return].freeze
8
8
 
9
- def new
9
+ def new(*events)
10
10
  start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
11
- ::TracePoint.new(*STOCK_EVENTS) { |tp| yield trace_record(tp, start_stamp) }
11
+ events = STOCK_EVENTS if events.empty?
12
+ ::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
12
13
  end
13
14
 
14
15
  def trace_record(trp, start_stamp)
@@ -16,7 +17,7 @@ def trace_record(trp, start_stamp)
16
17
  { stamp: stamp, self: trp.self, binding: trp.binding, event: trp.event,
17
18
  fiber: tp_fiber(trp), lineno: trp.lineno, method_id: trp.method_id,
18
19
  file: trp.path, parameters: tp_params(trp),
19
- return_value: tp_return_value(trp),
20
+ return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
20
21
  exception: tp_raised_exception(trp) }
21
22
  end
22
23
 
@@ -36,6 +37,12 @@ def tp_return_value(trp)
36
37
  RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
37
38
  end
38
39
 
40
+ SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
41
+
42
+ def tp_schedule_value(trp)
43
+ SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
44
+ end
45
+
39
46
  def tp_raised_exception(trp)
40
47
  trp.event == :raise && trp.raised_exception
41
48
  end
@@ -79,19 +86,45 @@ class FiberTracePoint
79
86
  end
80
87
 
81
88
  class << ::TracePoint
89
+ POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
90
+
82
91
  alias_method :orig_new, :new
83
92
  def new(*args, &block)
84
- polyphony_file_regexp = /^#{::Exception::POLYPHONY_DIR}/
93
+ events_mask, fiber_events_mask = event_masks(args)
85
94
 
86
- orig_new(*args) do |tp|
87
- # next unless !$watched_fiber || Fiber.current == $watched_fiber
95
+ orig_new(*events_mask) do |tp|
96
+ handle_tp_event(tp, fiber_events_mask, &block)
97
+ end
98
+ end
88
99
 
89
- if tp.method_id == :__fiber_trace__
90
- block.(FiberTracePoint.new(tp)) if tp.event == :c_return
91
- else
92
- next if tp.path =~ polyphony_file_regexp
100
+ def handle_tp_event(tpoint, fiber_events_mask)
101
+ # next unless !$watched_fiber || Fiber.current == $watched_fiber
93
102
 
94
- block.(tp)
103
+ if tpoint.method_id == :__fiber_trace__
104
+ return if tpoint.event != :c_return
105
+ return unless fiber_events_mask.include?(tpoint.return_value[0])
106
+
107
+ tpoint = FiberTracePoint.new(tpoint)
108
+ elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
109
+ return
110
+ end
111
+
112
+ yield tpoint
113
+ end
114
+
115
+ ALL_FIBER_EVENTS = %i[
116
+ fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
117
+ fiber_ev_loop_enter fiber_ev_loop_leave
118
+ ].freeze
119
+
120
+ def event_masks(events)
121
+ events.each_with_object([[], []]) do |e, masks|
122
+ case e
123
+ when /fiber_/
124
+ masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
125
+ masks[0] << :c_return unless masks[0].include?(:c_return)
126
+ else
127
+ masks[0] << e
95
128
  end
96
129
  end
97
130
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.28'
4
+ VERSION = '0.29'
5
5
  end
data/lib/polyphony.rb CHANGED
@@ -62,6 +62,7 @@ module Polyphony
62
62
  def fork(&block)
63
63
  pid = Kernel.fork do
64
64
  Gyro.post_fork
65
+ Fiber.current.setup_main_fiber
65
66
  block.()
66
67
  end
67
68
  pid
data/test/helper.rb CHANGED
@@ -20,9 +20,7 @@ Minitest::Reporters.use! [
20
20
 
21
21
  class MiniTest::Test
22
22
  def setup
23
- # for some reason, the first call to sleep in the context of tests returns
24
- # too early
25
- sleep 0
23
+ Fiber.current.setup_main_fiber
26
24
  end
27
25
 
28
26
  def teardown
data/test/test_async.rb CHANGED
@@ -26,7 +26,7 @@ class AsyncTest < MiniTest::Test
26
26
  loop {
27
27
  a.await
28
28
  count += 1
29
- defer { coproc.stop }
29
+ spin { coproc.stop }
30
30
  }
31
31
  }
32
32
  snooze
@@ -6,7 +6,7 @@ class CancelScopeTest < MiniTest::Test
6
6
  def test_that_cancel_scope_can_cancel_provided_block
7
7
  buffer = []
8
8
  Polyphony::CancelScope.new { |scope|
9
- defer { scope.cancel! }
9
+ spin { scope.cancel! }
10
10
  buffer << 1
11
11
  snooze
12
12
  buffer << 2
@@ -77,7 +77,7 @@ class CancelScopeTest < MiniTest::Test
77
77
  scope.call {
78
78
  sleep 0.005
79
79
  scope.reset_timeout
80
- sleep 0.008
80
+ sleep 0.005
81
81
  }
82
82
 
83
83
  assert !scope.cancelled?
@@ -86,7 +86,7 @@ class CancelScopeTest < MiniTest::Test
86
86
  def test_on_cancel
87
87
  buffer = []
88
88
  Polyphony::CancelScope.new { |scope|
89
- defer { scope.cancel! }
89
+ spin { scope.cancel! }
90
90
  scope.on_cancel { buffer << :cancelled }
91
91
  buffer << 1
92
92
  snooze