polyphony 0.26 → 0.27

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +34 -14
  6. data/examples/core/xx-mt-scheduler.rb +349 -0
  7. data/examples/core/xx-queue-async.rb +120 -0
  8. data/examples/core/xx-readpartial.rb +18 -0
  9. data/examples/core/xx-thread-selector-sleep.rb +51 -0
  10. data/examples/core/xx-thread-selector-snooze.rb +46 -0
  11. data/examples/core/xx-thread-sleep.rb +17 -0
  12. data/examples/core/xx-thread-snooze.rb +34 -0
  13. data/examples/performance/snooze.rb +5 -5
  14. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +73 -0
  15. data/examples/performance/thread-vs-fiber/polyphony_server.rb +12 -4
  16. data/ext/gyro/async.c +58 -61
  17. data/ext/gyro/child.c +50 -59
  18. data/ext/gyro/gyro.c +84 -192
  19. data/ext/gyro/gyro.h +29 -2
  20. data/ext/gyro/gyro_ext.c +6 -0
  21. data/ext/gyro/io.c +87 -96
  22. data/ext/gyro/queue.c +109 -0
  23. data/ext/gyro/selector.c +117 -0
  24. data/ext/gyro/signal.c +44 -54
  25. data/ext/gyro/socket.c +15 -20
  26. data/ext/gyro/thread.c +203 -0
  27. data/ext/gyro/timer.c +79 -50
  28. data/ext/libev/ev.c +3 -0
  29. data/lib/polyphony.rb +7 -12
  30. data/lib/polyphony/core/global_api.rb +5 -1
  31. data/lib/polyphony/core/throttler.rb +6 -11
  32. data/lib/polyphony/extensions/core.rb +2 -0
  33. data/lib/polyphony/extensions/fiber.rb +11 -13
  34. data/lib/polyphony/extensions/thread.rb +52 -0
  35. data/lib/polyphony/version.rb +1 -1
  36. data/test/helper.rb +8 -3
  37. data/test/test_fiber.rb +2 -2
  38. data/test/test_global_api.rb +4 -5
  39. data/test/test_gyro.rb +3 -2
  40. data/test/test_io.rb +1 -0
  41. data/test/test_supervisor.rb +3 -3
  42. data/test/test_thread.rb +44 -0
  43. data/test/test_throttler.rb +41 -0
  44. data/test/test_timer.rb +13 -7
  45. metadata +17 -6
  46. data/examples/core/xx-thread_cancel.rb +0 -28
  47. data/examples/performance/thread.rb +0 -27
  48. data/lib/polyphony/core/thread.rb +0 -23
@@ -2,35 +2,40 @@
2
2
 
3
3
  struct Gyro_Timer {
4
4
  struct ev_timer ev_timer;
5
+ struct ev_loop *ev_loop;
5
6
  int active;
6
7
  double after;
7
8
  double repeat;
9
+ int should_free;
8
10
  VALUE self;
9
11
  VALUE fiber;
12
+ VALUE selector;
10
13
  };
11
14
 
12
- static VALUE cGyro_Timer = Qnil;
15
+ VALUE cGyro_Timer = Qnil;
13
16
 
14
- /* Allocator/deallocator */
15
- static VALUE Gyro_Timer_allocate(VALUE klass);
16
- static void Gyro_Timer_mark(void *ptr);
17
- static void Gyro_Timer_free(void *ptr);
18
- static size_t Gyro_Timer_size(const void *ptr);
19
-
20
- /* Methods */
21
- static VALUE Gyro_Timer_initialize(VALUE self, VALUE after, VALUE repeat);
22
-
23
- static VALUE Gyro_Timer_await(VALUE self);
24
-
25
- void Gyro_Timer_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents);
17
+ static void Gyro_Timer_mark(void *ptr) {
18
+ struct Gyro_Timer *timer = ptr;
19
+ if (timer->fiber != Qnil) {
20
+ rb_gc_mark(timer->fiber);
21
+ }
22
+ if (timer->selector != Qnil) {
23
+ rb_gc_mark(timer->selector);
24
+ }
25
+ }
26
26
 
27
- /* Timer encapsulates an timer watcher */
28
- void Init_Gyro_Timer() {
29
- cGyro_Timer = rb_define_class_under(mGyro, "Timer", rb_cData);
30
- rb_define_alloc_func(cGyro_Timer, Gyro_Timer_allocate);
27
+ static void Gyro_Timer_free(void *ptr) {
28
+ struct Gyro_Timer *timer = ptr;
29
+ if (timer->active) {
30
+ printf("Timer watcher garbage collected while still active (%g, %g)!\n", timer->after, timer->repeat);
31
+ timer->should_free = 1;
32
+ } else {
33
+ xfree(timer);
34
+ }
35
+ }
31
36
 
32
- rb_define_method(cGyro_Timer, "initialize", Gyro_Timer_initialize, 2);
33
- rb_define_method(cGyro_Timer, "await", Gyro_Timer_await, 0);
37
+ static size_t Gyro_Timer_size(const void *ptr) {
38
+ return sizeof(struct Gyro_Timer);
34
39
  }
35
40
 
36
41
  static const rb_data_type_t Gyro_Timer_type = {
@@ -44,29 +49,28 @@ static VALUE Gyro_Timer_allocate(VALUE klass) {
44
49
  struct Gyro_Timer *timer = (struct Gyro_Timer *)xmalloc(sizeof(struct Gyro_Timer));
45
50
  return TypedData_Wrap_Struct(klass, &Gyro_Timer_type, timer);
46
51
  }
52
+ #define GetGyro_Timer(obj, timer) \
53
+ TypedData_Get_Struct((obj), struct Gyro_Timer, &Gyro_Timer_type, (timer))
47
54
 
48
- static void Gyro_Timer_mark(void *ptr) {
49
- struct Gyro_Timer *timer = ptr;
50
- if (timer->fiber != Qnil) {
51
- rb_gc_mark(timer->fiber);
55
+ void Gyro_Timer_callback(struct ev_loop *ev_loop, struct ev_timer *ev_timer, int revents) {
56
+ struct Gyro_Timer *timer = (struct Gyro_Timer*)ev_timer;
57
+
58
+ if (timer->should_free) {
59
+ ev_timer_stop(timer->ev_loop, ev_timer);
60
+ xfree(timer);
61
+ return;
52
62
  }
53
- }
54
63
 
55
- static void Gyro_Timer_free(void *ptr) {
56
- struct Gyro_Timer *timer = ptr;
57
- if (timer->active) {
58
- ev_timer_stop(EV_DEFAULT, &timer->ev_timer);
64
+ if (!timer->repeat) {
65
+ timer->active = 0;
66
+ timer->selector = Qnil;
59
67
  }
60
- xfree(timer);
61
- }
62
68
 
63
- static size_t Gyro_Timer_size(const void *ptr) {
64
- return sizeof(struct Gyro_Timer);
69
+ if (timer->fiber != Qnil) {
70
+ Gyro_schedule_fiber(timer->fiber, DBL2NUM(timer->after));
71
+ }
65
72
  }
66
73
 
67
- #define GetGyro_Timer(obj, timer) \
68
- TypedData_Get_Struct((obj), struct Gyro_Timer, &Gyro_Timer_type, (timer))
69
-
70
74
  static VALUE Gyro_Timer_initialize(VALUE self, VALUE after, VALUE repeat) {
71
75
  struct Gyro_Timer *timer;
72
76
 
@@ -77,47 +81,72 @@ static VALUE Gyro_Timer_initialize(VALUE self, VALUE after, VALUE repeat) {
77
81
  timer->after = NUM2DBL(after);
78
82
  timer->repeat = NUM2DBL(repeat);
79
83
  timer->active = 0;
80
-
84
+
85
+ timer->should_free = 0;
86
+
81
87
  ev_timer_init(&timer->ev_timer, Gyro_Timer_callback, timer->after, timer->repeat);
88
+ timer->ev_loop = 0;
89
+ timer->selector = Qnil;
82
90
 
83
91
  return Qnil;
84
92
  }
85
93
 
86
- void Gyro_Timer_callback(struct ev_loop *ev_loop, struct ev_timer *ev_timer, int revents) {
87
- struct Gyro_Timer *timer = (struct Gyro_Timer*)ev_timer;
94
+ VALUE Gyro_Timer_stop(VALUE self) {
95
+ struct Gyro_Timer *timer;
96
+ GetGyro_Timer(self, timer);
88
97
 
89
- if (!timer->repeat) {
98
+ if (timer->active) {
90
99
  timer->active = 0;
91
- }
92
-
93
- if (timer->fiber != Qnil) {
94
- VALUE fiber = timer->fiber;
95
- VALUE resume_value = DBL2NUM(timer->after);
96
-
97
100
  timer->fiber = Qnil;
98
- Gyro_schedule_fiber(fiber, resume_value);
101
+ timer->selector = Qnil;
102
+ ev_timer_stop(timer->ev_loop, &timer->ev_timer);
103
+ timer->ev_loop = 0;
99
104
  }
105
+
106
+ return self;
100
107
  }
101
108
 
102
- static VALUE Gyro_Timer_await(VALUE self) {
109
+ VALUE Gyro_Timer_await(VALUE self) {
103
110
  struct Gyro_Timer *timer;
104
111
  VALUE ret;
105
112
 
106
113
  GetGyro_Timer(self, timer);
107
114
 
108
115
  timer->fiber = rb_fiber_current();
116
+ timer->selector = Thread_current_event_selector();
117
+ timer->ev_loop = Gyro_Selector_ev_loop(timer->selector);
118
+
109
119
  if (timer->active != 1) {
110
120
  timer->active = 1;
111
- ev_timer_start(EV_DEFAULT, &timer->ev_timer);
121
+ ev_timer_start(timer->ev_loop, &timer->ev_timer);
112
122
  }
113
123
 
114
- ret = Gyro_await();
124
+ ret = Fiber_await();
125
+ RB_GC_GUARD(ret);
126
+
127
+ if (timer->active && (timer->repeat == .0)) {
128
+ timer->active = 0;
129
+ timer->fiber = Qnil;
130
+ timer->selector = Qnil;
131
+ ev_timer_stop(timer->ev_loop, &timer->ev_timer);
132
+ }
133
+ RB_GC_GUARD(self);
115
134
 
116
135
  // fiber is resumed, check if resumed value is an exception
117
136
  timer->fiber = Qnil;
137
+ timer->selector = Qnil;
118
138
  if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
119
139
  return rb_funcall(rb_mKernel, ID_raise, 1, ret);
120
140
  }
121
141
  else
122
142
  return ret;
123
- }
143
+ }
144
+
145
+ void Init_Gyro_Timer() {
146
+ cGyro_Timer = rb_define_class_under(mGyro, "Timer", rb_cData);
147
+ rb_define_alloc_func(cGyro_Timer, Gyro_Timer_allocate);
148
+
149
+ rb_define_method(cGyro_Timer, "initialize", Gyro_Timer_initialize, 2);
150
+ rb_define_method(cGyro_Timer, "stop", Gyro_Timer_stop, 0);
151
+ rb_define_method(cGyro_Timer, "await", Gyro_Timer_await, 0);
152
+ }
@@ -3809,7 +3809,10 @@ rb_thread_unsafe_dangerous_crazy_blocking_region_end(...);
3809
3809
 
3810
3810
  poll_args.loop = loop;
3811
3811
  poll_args.waittime = waittime;
3812
+
3812
3813
  rb_thread_call_without_gvl(ev_backend_poll, (void *)&poll_args, RUBY_UBF_IO, 0);
3814
+
3815
+ // backend_poll (EV_A_ waittime);
3813
3816
  /*
3814
3817
  ############################# END PATCHERY ############################
3815
3818
  */
@@ -7,7 +7,11 @@ export_default :Polyphony
7
7
  require 'fiber'
8
8
  require_relative './gyro_ext'
9
9
 
10
+ Thread.event_selector = Gyro::Selector
11
+ Thread.current.setup_fiber_scheduling
12
+
10
13
  import './polyphony/extensions/core'
14
+ import './polyphony/extensions/thread'
11
15
  import './polyphony/extensions/fiber'
12
16
  import './polyphony/extensions/io'
13
17
 
@@ -29,8 +33,8 @@ module Polyphony
29
33
  ResourcePool: './polyphony/core/resource_pool',
30
34
  Supervisor: './polyphony/core/supervisor',
31
35
  Sync: './polyphony/core/sync',
32
- Thread: './polyphony/core/thread',
33
36
  ThreadPool: './polyphony/core/thread_pool',
37
+ Throttler: './polyphony/core/throttler',
34
38
  Websocket: './polyphony/websocket'
35
39
  )
36
40
 
@@ -55,24 +59,15 @@ module Polyphony
55
59
  end
56
60
 
57
61
  def fork(&block)
58
- Gyro.break!
59
62
  pid = Kernel.fork do
60
- setup_forked_process
63
+ Gyro.post_fork
61
64
  block.()
62
65
  end
63
- Gyro.reset!
64
66
  pid
65
67
  end
66
68
 
67
69
  def reset!
68
- Gyro.reset!
69
- Fiber.reset!
70
- end
71
-
72
- private
73
-
74
- def setup_forked_process
75
- Gyro.post_fork
70
+ Thread.current.reset_fiber_scheduling
76
71
  Fiber.reset!
77
72
  end
78
73
  end
@@ -44,6 +44,8 @@ module API
44
44
  timer.await
45
45
  yield
46
46
  end
47
+ ensure
48
+ timer.stop
47
49
  end
48
50
 
49
51
  def move_on_after(interval, with_value: nil, &block)
@@ -74,12 +76,14 @@ module API
74
76
  Supervisor.new.await(&block)
75
77
  end
76
78
 
77
- def throttled_loop(rate, count: nil, &block)
79
+ def throttled_loop(rarote, count: nil, &block)
78
80
  throttler = Throttler.new(rate)
79
81
  if count
80
82
  count.times { throttler.(&block) }
81
83
  else
82
84
  loop { throttler.(&block) }
83
85
  end
86
+ ensure
87
+ throttler.stop
84
88
  end
85
89
  end
@@ -7,25 +7,20 @@ class Throttler
7
7
  def initialize(rate)
8
8
  @rate = rate_from_argument(rate)
9
9
  @min_dt = 1.0 / @rate
10
- @last_iteration_clock = clock - @min_dt
11
- end
12
-
13
- def clock
14
- ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
15
10
  end
16
11
 
17
12
  def call(&block)
18
- now = clock
19
- dt = now - @last_iteration_clock
20
-
21
- sleep(@min_dt - dt) if dt < @min_dt
22
-
23
- @last_iteration_clock = dt > @min_dt ? now : @last_iteration_clock + @min_dt
13
+ @timer ||= Gyro::Timer.new(0, @min_dt)
14
+ @timer.await
24
15
  block.call(self)
25
16
  end
26
17
 
27
18
  alias_method :process, :call
28
19
 
20
+ def stop
21
+ @timer&.stop
22
+ end
23
+
29
24
  private
30
25
 
31
26
  def rate_from_argument(arg)
@@ -102,6 +102,8 @@ module ::Kernel
102
102
  end
103
103
  end
104
104
  true
105
+ rescue SystemCallError
106
+ nil
105
107
  end
106
108
  end
107
109
 
@@ -35,7 +35,7 @@ module FiberControl
35
35
 
36
36
  def raise(*args)
37
37
  error = error_from_raise_args(args)
38
- schedule error
38
+ schedule(error)
39
39
  snooze
40
40
  end
41
41
 
@@ -83,17 +83,11 @@ end
83
83
 
84
84
  # Fiber extensions
85
85
  class ::Fiber
86
- include FiberControl
86
+ prepend FiberControl
87
87
  include FiberMessaging
88
88
 
89
- # map of currently running fibers
90
- def self.root
91
- @root_fiber
92
- end
93
-
94
89
  def self.reset!
95
- @root_fiber = current
96
- @running_fibers_map = { @root_fiber => true }
90
+ @running_fibers_map = { Thread.current.main_fiber => true }
97
91
  end
98
92
 
99
93
  reset!
@@ -132,6 +126,8 @@ class ::Fiber
132
126
  finish_execution(result)
133
127
  rescue Exceptions::MoveOn => e
134
128
  finish_execution(e.value)
129
+ rescue ::Interrupt, ::SystemExit => e
130
+ Thread.current.main_fiber.transfer e.class.new
135
131
  rescue Exception => e
136
132
  finish_execution(e, true)
137
133
  end
@@ -142,13 +138,15 @@ class ::Fiber
142
138
  self.class.map.delete(self)
143
139
  @when_done&.(result)
144
140
  @waiting_fiber&.schedule(result)
145
-
146
141
  return unless uncaught_exception && !@waiting_fiber
147
142
 
148
- parent_fiber = @calling_fiber.running? ? @calling_fiber : Fiber.root
149
- parent_fiber.schedule(result)
143
+ exception_receiving_fiber.schedule(result)
150
144
  ensure
151
- Gyro.run
145
+ Thread.current.switch_fiber
146
+ end
147
+
148
+ def exception_receiving_fiber
149
+ @calling_fiber.running? ? @calling_fiber : Thread.current.main_fiber
152
150
  end
153
151
 
154
152
  attr_reader :result
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ Exceptions = import '../core/exceptions'
4
+
5
+ # Thread extensions
6
+ class ::Thread
7
+ attr_reader :main_fiber
8
+
9
+ alias_method :orig_initialize, :initialize
10
+ def initialize(*args, &block)
11
+ @join_wait_queue = Gyro::Queue.new
12
+ @block = block
13
+ orig_initialize do
14
+ setup_fiber_scheduling
15
+ block.(*args)
16
+ signal_waiters
17
+ ensure
18
+ stop_event_selector
19
+ end
20
+ end
21
+
22
+ def signal_waiters
23
+ @join_wait_queue.shift_each { |w| w.signal!(self) }
24
+ end
25
+
26
+ alias_method :orig_join, :join
27
+ def join(timeout = nil)
28
+ return unless alive?
29
+
30
+ async = Gyro::Async.new
31
+ @join_wait_queue << async
32
+
33
+ if timeout
34
+ move_on_after(timeout) { async.await }
35
+ else
36
+ async.await
37
+ end
38
+ end
39
+
40
+ alias_method :orig_inspect, :inspect
41
+ def inspect
42
+ return orig_inspect if self == Thread.main
43
+
44
+ state = status || 'dead'
45
+ "#<Thread:#{object_id} #{location} (#{state})>"
46
+ end
47
+ alias_method :to_s, :inspect
48
+
49
+ def location
50
+ @block.source_location.join(':')
51
+ end
52
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.26'
4
+ VERSION = '0.27'
5
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
+ require 'polyphony'
4
5
 
5
6
  require 'fileutils'
6
7
  require_relative './eg'
@@ -10,8 +11,6 @@ require_relative './coverage' if ENV['COVERAGE']
10
11
  require 'minitest/autorun'
11
12
  require 'minitest/reporters'
12
13
 
13
- require 'polyphony'
14
-
15
14
  ::Exception.__disable_sanitized_backtrace__ = true
16
15
 
17
16
  Minitest::Reporters.use! [
@@ -19,9 +18,15 @@ Minitest::Reporters.use! [
19
18
  ]
20
19
 
21
20
  class MiniTest::Test
21
+ def setup
22
+ # for some reason, the first call to sleep in the context of tests returns
23
+ # too early
24
+ sleep 0
25
+ end
26
+
22
27
  def teardown
23
28
  # wait for any remaining scheduled work
24
- Gyro.run
29
+ Thread.current.switch_fiber
25
30
  Polyphony.reset!
26
31
  end
27
32
  end