polyphony 0.26 → 0.27

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