polyphony 0.49.2 → 0.50.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18278f9c013191fd5a96520a07e3b6e5c4bc088665a1b1a769b02552bd0e5448
4
- data.tar.gz: 4e1e42fa5e2a6ddc6dcff7982415dd16246a0d75212aa2eb9ae7a97e030a2bdb
3
+ metadata.gz: 2aac80a08ca16498c3ee0044b0ba6de039b179425051cbcee67af4c9b948c349
4
+ data.tar.gz: bbe016d6b2ad3c5c89129089f9ebb8457a37159e03be2ff84f2572454880b0c2
5
5
  SHA512:
6
- metadata.gz: 483158709045a9dc59fa75c912067c4fafbb02885e15f3fa57f482b97903a0c919abbb5ad19bea85837222a44d5ec2778f1e1421839c2e6a595a64e84f2d707d
7
- data.tar.gz: c1162de70de5aa2d92804e7c4ccb3a372c5839d5600a7f68ae6f84363677a52f23923dbe8e407a84607ac4f0145736c7a5b9ada8b4956fd9c742cec182ed3a70
6
+ metadata.gz: 2f91d0b40bceb4600e4bbd22f39cdbd6bfd4ac5b5fc038f77deb7d26a464ce3cad177884f9aadde60c78bc48df170ab6909c44d133283af3abf77a090ac58332
7
+ data.tar.gz: 37dfa412a25f41dd704a203a865817bf273a23db0ccbe1bbec1a3f6017b9cd953301d67077e3b58601485f4dd96db94bef77da3d46cabd57fb563d3652173175
@@ -1,3 +1,11 @@
1
+ ## 0.50.0
2
+
3
+ - Use `Process::CLOCK_MONOTONIC` in Timer
4
+ - Add `Timer#sleep`, `Timer#after`, `Timer#every`
5
+ - Prevent fiber from being resumed after terminating
6
+ - Add `Thread#fiber_index_of` method
7
+ - Use `Backend#wait_event` in `Fiber#await`
8
+
1
9
  ## 0.49.2
2
10
 
3
11
  - Fix hang with 100s or more child fibers when terminating
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.49.2)
4
+ polyphony (0.50.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/TODO.md CHANGED
@@ -1,6 +1,9 @@
1
1
  - Check segfault when resetting a `cancel_after` timeout lots of times at very high rate
2
2
  - Check why `throttled_loop` inside of `move_on_after` fails to stop
3
3
 
4
+ - Commented out `io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);` in `io_uring_backend_defer_submit_and_await`:
5
+ - This flag should be set for I/O ops, not for other stuff
6
+
4
7
  - Override stock `::SizedQueue` impl with Queue with capacity
5
8
 
6
9
  - Add support for `break` and `StopIteration` in all loops (with tests)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ def process
7
+ p :b_start
8
+ sleep 1
9
+ p :b_stop
10
+ end
11
+
12
+ spin do
13
+ p :a_start
14
+ spin { process }
15
+ sleep 60
16
+ p :a_stop
17
+ end
18
+
19
+ p :main_start
20
+ sleep 120
21
+ p :main_stop
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ main = Fiber.current
7
+ spin do
8
+ sleep 0.1
9
+ main.schedule(:foo)
10
+ end
11
+
12
+ v = suspend
13
+ puts "v => #{v.inspect}"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ main = Fiber.current
7
+ spin do
8
+ sleep 0.1
9
+ main.terminate
10
+ end
11
+
12
+ sleep
@@ -114,7 +114,7 @@ inline double current_time() {
114
114
  struct timespec ts;
115
115
  clock_gettime(CLOCK_MONOTONIC, &ts);
116
116
  long long ns = ts.tv_sec;
117
- ns = ns * 1000000000 + ts.tv_nsec;
117
+ ns = ns * 1e9 + ts.tv_nsec;
118
118
  double t = ns;
119
119
  return t / 1e9;
120
120
  }
@@ -101,7 +101,8 @@ VALUE Fiber_await(VALUE self) {
101
101
  }
102
102
  rb_hash_aset(waiting_fibers, fiber, Qtrue);
103
103
 
104
- result = Thread_switch_fiber(rb_thread_current());
104
+ VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
105
+ result = Backend_wait_event(backend, Qnil);
105
106
 
106
107
  rb_hash_delete(waiting_fibers, fiber);
107
108
  RAISE_IF_EXCEPTION(result);
@@ -84,6 +84,7 @@ void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule);
84
84
  void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule);
85
85
  runqueue_entry Runqueue_shift(VALUE self);
86
86
  void Runqueue_delete(VALUE self, VALUE fiber);
87
+ int Runqueue_index_of(VALUE self, VALUE fiber);
87
88
  void Runqueue_clear(VALUE self);
88
89
  long Runqueue_len(VALUE self);
89
90
  int Runqueue_empty_p(VALUE self);
@@ -74,6 +74,12 @@ void Runqueue_delete(VALUE self, VALUE fiber) {
74
74
  runqueue_ring_buffer_delete(&runqueue->entries, fiber);
75
75
  }
76
76
 
77
+ int Runqueue_index_of(VALUE self, VALUE fiber) {
78
+ Runqueue_t *runqueue;
79
+ GetRunqueue(self, runqueue);
80
+ return runqueue_ring_buffer_index_of(&runqueue->entries, fiber);
81
+ }
82
+
77
83
  void Runqueue_clear(VALUE self) {
78
84
  Runqueue_t *runqueue;
79
85
  GetRunqueue(self, runqueue);
@@ -80,6 +80,15 @@ void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
80
80
  }
81
81
  }
82
82
 
83
+ int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
84
+ for (unsigned int i = 0; i < buffer->count; i++) {
85
+ unsigned int idx = (buffer->head + i) % buffer->size;
86
+ if (buffer->entries[idx].fiber == fiber)
87
+ return i;
88
+ }
89
+ return -1;
90
+ }
91
+
83
92
  void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
84
93
  buffer->count = buffer->head = buffer->tail = 0;
85
94
  }
@@ -27,5 +27,6 @@ void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VAL
27
27
  void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
28
28
 
29
29
  void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
30
+ int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
30
31
 
31
32
  #endif /* RUNQUEUE_RING_BUFFER_H */
@@ -59,6 +59,18 @@ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
59
59
  }
60
60
  }
61
61
 
62
+ VALUE Thread_fiber_scheduling_index(VALUE self, VALUE fiber) {
63
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
64
+
65
+ return INT2NUM(Runqueue_index_of(runqueue, fiber));
66
+ }
67
+
68
+ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
69
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
70
+ Runqueue_delete(runqueue, fiber);
71
+ return self;
72
+ }
73
+
62
74
  VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
63
75
  schedule_fiber(self, fiber, value, 0);
64
76
  return self;
@@ -135,6 +147,8 @@ void Init_Thread() {
135
147
  rb_define_method(rb_cThread, "schedule_fiber_with_priority",
136
148
  Thread_schedule_fiber_with_priority, 2);
137
149
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
150
+ rb_define_method(rb_cThread, "fiber_scheduling_index", Thread_fiber_scheduling_index, 1);
151
+ rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
138
152
 
139
153
  rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
140
154
 
@@ -13,6 +13,7 @@ module Polyphony
13
13
  def initialize(value = nil)
14
14
  @caller_backtrace = caller
15
15
  @value = value
16
+ super
16
17
  end
17
18
  end
18
19
 
@@ -29,7 +29,7 @@ module Polyphony
29
29
  spin do
30
30
  sleep interval
31
31
  exception = cancel_exception(with_exception)
32
- exception.__raising_fiber__ = nil
32
+ exception.raising_fiber = nil
33
33
  fiber.schedule exception
34
34
  end
35
35
  end
@@ -11,12 +11,45 @@ module Polyphony
11
11
  def stop
12
12
  @fiber.stop
13
13
  end
14
+
15
+ def sleep(duration)
16
+ fiber = Fiber.current
17
+ @timeouts[fiber] = {
18
+ interval: duration,
19
+ target_stamp: now + duration
20
+ }
21
+ Thread.current.backend.wait_event(true)
22
+ ensure
23
+ @timeouts.delete(fiber)
24
+ end
25
+
26
+ def after(interval, &block)
27
+ spin do
28
+ self.sleep interval
29
+ block.()
30
+ end
31
+ end
32
+
33
+ def every(interval)
34
+ fiber = Fiber.current
35
+ @timeouts[fiber] = {
36
+ interval: interval,
37
+ target_stamp: now + interval,
38
+ recurring: true
39
+ }
40
+ while true
41
+ Thread.current.backend.wait_event(true)
42
+ yield
43
+ end
44
+ ensure
45
+ @timeouts.delete(fiber)
46
+ end
14
47
 
15
- def cancel_after(duration, with_exception: Polyphony::Cancel)
48
+ def cancel_after(interval, with_exception: Polyphony::Cancel)
16
49
  fiber = Fiber.current
17
50
  @timeouts[fiber] = {
18
- duration: duration,
19
- target_stamp: Time.now + duration,
51
+ interval: interval,
52
+ target_stamp: now + interval,
20
53
  exception: with_exception
21
54
  }
22
55
  yield
@@ -24,12 +57,12 @@ module Polyphony
24
57
  @timeouts.delete(fiber)
25
58
  end
26
59
 
27
- def move_on_after(duration, with_value: nil)
60
+ def move_on_after(interval, with_value: nil)
28
61
  fiber = Fiber.current
29
62
  @timeouts[fiber] = {
30
- duration: duration,
31
- target_stamp: Time.now + duration,
32
- value: with_value
63
+ interval: interval,
64
+ target_stamp: now + interval,
65
+ exception: [Polyphony::MoveOn, with_value]
33
66
  }
34
67
  yield
35
68
  rescue Polyphony::MoveOn => e
@@ -37,36 +70,46 @@ module Polyphony
37
70
  ensure
38
71
  @timeouts.delete(fiber)
39
72
  end
40
-
73
+
41
74
  def reset
42
75
  record = @timeouts[Fiber.current]
43
76
  return unless record
44
77
 
45
- record[:target_stamp] = Time.now + record[:duration]
78
+ record[:target_stamp] = now + record[:interval]
46
79
  end
47
-
80
+
48
81
  private
49
82
 
83
+ def now
84
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
85
+ end
86
+
50
87
  def timeout_exception(record)
51
88
  case (exception = record[:exception])
52
- when Class then exception.new
53
- when Array then exception[0].new(exception[1])
54
- when nil then Polyphony::MoveOn.new(record[:value])
55
- else RuntimeError.new(exception)
89
+ when Array
90
+ exception[0].new(exception[1])
91
+ when Class
92
+ exception.new
93
+ else
94
+ RuntimeError.new(exception)
56
95
  end
57
96
  end
58
97
 
59
98
  def update
60
- now = Time.now
61
- # elapsed = nil
99
+ return if @timeouts.empty?
100
+
62
101
  @timeouts.each do |fiber, record|
63
102
  next if record[:target_stamp] > now
64
103
 
65
- exception = timeout_exception(record)
66
- # (elapsed ||= []) << fiber
67
- fiber.schedule exception
104
+ value = record[:exception] ? timeout_exception(record) : record[:value]
105
+ fiber.schedule value
106
+
107
+ next unless record[:recurring]
108
+
109
+ while record[:target_stamp] <= now
110
+ record[:target_stamp] += record[:interval]
111
+ end
68
112
  end
69
- # elapsed&.each { |f| @timeouts.delete(f) }
70
113
  end
71
114
  end
72
115
  end
@@ -12,11 +12,11 @@ class ::Exception
12
12
  attr_accessor :__disable_sanitized_backtrace__
13
13
  end
14
14
 
15
- attr_accessor :source_fiber, :__raising_fiber__
15
+ attr_accessor :source_fiber, :raising_fiber
16
16
 
17
17
  alias_method :orig_initialize, :initialize
18
18
  def initialize(*args)
19
- @__raising_fiber__ = Fiber.current
19
+ @raising_fiber = Fiber.current
20
20
  orig_initialize(*args)
21
21
  end
22
22
 
@@ -31,10 +31,10 @@ class ::Exception
31
31
  end
32
32
 
33
33
  def sanitized_backtrace
34
- return sanitize(orig_backtrace) unless @__raising_fiber__
34
+ return sanitize(orig_backtrace) unless @raising_fiber
35
35
 
36
36
  backtrace = orig_backtrace || []
37
- sanitize(backtrace + @__raising_fiber__.caller)
37
+ sanitize(backtrace + @raising_fiber.caller)
38
38
  end
39
39
 
40
40
  POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
@@ -316,6 +316,8 @@ module Polyphony
316
316
  @running = false
317
317
  inform_dependants(result, uncaught_exception)
318
318
  ensure
319
+ # Prevent fiber from being resumed after terminating
320
+ @thread.fiber_unschedule(self)
319
321
  Thread.current.switch_fiber
320
322
  end
321
323
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.49.2'
4
+ VERSION = '0.50.0'
5
5
  end
@@ -349,6 +349,22 @@ class FiberTest < MiniTest::Test
349
349
  assert_equal [:foo, :terminate], buffer
350
350
  end
351
351
 
352
+ CMD_TERMINATE_MAIN_FIBER = <<~BASH
353
+ ruby -rbundler/setup -rpolyphony -e"spin { sleep 0.1; Thread.current.main_fiber.terminate }; begin; sleep; rescue Polyphony::Terminate; STDOUT << 'terminated'; end" 2>&1
354
+ BASH
355
+
356
+ CMD_TERMINATE_CHILD_FIBER = <<~BASH
357
+ ruby -rbundler/setup -rpolyphony -e"f = spin { sleep }; spin { sleep 0.1; f.terminate }; f.await" 2>&1
358
+ BASH
359
+
360
+ def test_terminate_main_fiber
361
+ output = `#{CMD_TERMINATE_CHILD_FIBER}`
362
+ assert_equal '', output
363
+
364
+ output = `#{CMD_TERMINATE_MAIN_FIBER}`
365
+ assert_equal 'terminated', output
366
+ end
367
+
352
368
  def test_interrupt_timer
353
369
  result = []
354
370
  f = Fiber.current.spin do
@@ -11,7 +11,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
11
11
  @timer.stop
12
12
  end
13
13
 
14
- def test_move_on_after
14
+ def test_timer_move_on_after
15
15
  t0 = Time.now
16
16
  v = @timer.move_on_after(0.1) do
17
17
  sleep 1
@@ -23,7 +23,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
23
23
  assert_nil v
24
24
  end
25
25
 
26
- def test_move_on_after_with_value
26
+ def test_timer_move_on_after_with_value
27
27
  t0 = Time.now
28
28
  v = @timer.move_on_after(0.01, with_value: :bar) do
29
29
  sleep 1
@@ -35,7 +35,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
35
35
  assert_equal :bar, v
36
36
  end
37
37
 
38
- def test_move_on_after_with_reset
38
+ def test_timer_move_on_after_with_reset
39
39
  t0 = Time.now
40
40
  v = @timer.move_on_after(0.01, with_value: :moved_on) do
41
41
  sleep 0.007
@@ -48,7 +48,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
48
48
  t1 = Time.now
49
49
 
50
50
  assert_nil v
51
- assert_in_range 0.02..0.03, t1 - t0
51
+ assert_in_range 0.015..0.03, t1 - t0
52
52
  end
53
53
  end
54
54
 
@@ -61,7 +61,7 @@ class TimerCancelAfterTest < MiniTest::Test
61
61
  @timer.stop
62
62
  end
63
63
 
64
- def test_cancel_after
64
+ def test_timer_cancel_after
65
65
  t0 = Time.now
66
66
 
67
67
  assert_raises Polyphony::Cancel do
@@ -74,7 +74,7 @@ class TimerCancelAfterTest < MiniTest::Test
74
74
  assert_in_range 0.01..0.03, t1 - t0
75
75
  end
76
76
 
77
- def test_cancel_after_with_reset
77
+ def test_timer_cancel_after_with_reset
78
78
  t0 = Time.now
79
79
  @timer.cancel_after(0.01) do
80
80
  sleep 0.007
@@ -82,13 +82,13 @@ class TimerCancelAfterTest < MiniTest::Test
82
82
  sleep 0.007
83
83
  end
84
84
  t1 = Time.now
85
- assert_in_range 0.014..0.024, t1 - t0
85
+ assert_in_range 0.013..0.024, t1 - t0
86
86
  end
87
87
 
88
88
  class CustomException < Exception
89
89
  end
90
90
 
91
- def test_cancel_after_with_custom_exception
91
+ def test_timer_cancel_after_with_custom_exception
92
92
  assert_raises CustomException do
93
93
  @timer.cancel_after(0.01, with_exception: CustomException) do
94
94
  sleep 1
@@ -122,3 +122,36 @@ class TimerCancelAfterTest < MiniTest::Test
122
122
  end
123
123
  end
124
124
  end
125
+
126
+ class TimerMiscTest < MiniTest::Test
127
+ def setup
128
+ @timer = Polyphony::Timer.new(resolution: 0.001)
129
+ sleep 0
130
+ end
131
+
132
+ def teardown
133
+ @timer.stop
134
+ end
135
+
136
+ def test_timer_after
137
+ buffer = []
138
+ f = @timer.after(0.01) { buffer << 2 }
139
+ assert_kind_of Fiber, f
140
+ snooze
141
+ assert_equal [], buffer
142
+ sleep 0.1
143
+ p :post_sleep
144
+ assert_equal [2], buffer
145
+ end
146
+
147
+ def test_timer_every
148
+ buffer = []
149
+ t0 = Time.now
150
+ f = spin do
151
+ @timer.every(0.01) { buffer << 1 }
152
+ end
153
+ sleep 0.05
154
+ f.stop
155
+ assert_in_range 4..6, buffer.size
156
+ end
157
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.49.2
4
+ version: 0.50.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-19 00:00:00.000000000 Z
11
+ date: 2021-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -353,6 +353,7 @@ files:
353
353
  - examples/core/forking.rb
354
354
  - examples/core/handling-signals.rb
355
355
  - examples/core/interrupt.rb
356
+ - examples/core/nested.rb
356
357
  - examples/core/pingpong.rb
357
358
  - examples/core/recurrent-timer.rb
358
359
  - examples/core/resource_delegate.rb
@@ -360,6 +361,8 @@ files:
360
361
  - examples/core/spin_error_backtrace.rb
361
362
  - examples/core/supervise-process.rb
362
363
  - examples/core/supervisor.rb
364
+ - examples/core/suspend.rb
365
+ - examples/core/terminate_main_fiber.rb
363
366
  - examples/core/thread-sleep.rb
364
367
  - examples/core/thread_pool.rb
365
368
  - examples/core/throttling.rb