polyphony 0.49.2 → 0.50.0

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.
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