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 +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +3 -0
- data/examples/core/nested.rb +21 -0
- data/examples/core/suspend.rb +13 -0
- data/examples/core/terminate_main_fiber.rb +12 -0
- data/ext/polyphony/backend_common.h +1 -1
- data/ext/polyphony/fiber.c +2 -1
- data/ext/polyphony/polyphony.h +1 -0
- data/ext/polyphony/runqueue.c +6 -0
- data/ext/polyphony/runqueue_ring_buffer.c +9 -0
- data/ext/polyphony/runqueue_ring_buffer.h +1 -0
- data/ext/polyphony/thread.c +14 -0
- data/lib/polyphony/core/exceptions.rb +1 -0
- data/lib/polyphony/core/global_api.rb +1 -1
- data/lib/polyphony/core/timer.rb +63 -20
- data/lib/polyphony/extensions/core.rb +4 -4
- data/lib/polyphony/extensions/fiber.rb +2 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/test_fiber.rb +16 -0
- data/test/test_timer.rb +41 -8
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aac80a08ca16498c3ee0044b0ba6de039b179425051cbcee67af4c9b948c349
|
4
|
+
data.tar.gz: bbe016d6b2ad3c5c89129089f9ebb8457a37159e03be2ff84f2572454880b0c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f91d0b40bceb4600e4bbd22f39cdbd6bfd4ac5b5fc038f77deb7d26a464ce3cad177884f9aadde60c78bc48df170ab6909c44d133283af3abf77a090ac58332
|
7
|
+
data.tar.gz: 37dfa412a25f41dd704a203a865817bf273a23db0ccbe1bbec1a3f6017b9cd953301d67077e3b58601485f4dd96db94bef77da3d46cabd57fb563d3652173175
|
data/CHANGELOG.md
CHANGED
@@ -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
|
data/Gemfile.lock
CHANGED
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
|
data/ext/polyphony/fiber.c
CHANGED
@@ -101,7 +101,8 @@ VALUE Fiber_await(VALUE self) {
|
|
101
101
|
}
|
102
102
|
rb_hash_aset(waiting_fibers, fiber, Qtrue);
|
103
103
|
|
104
|
-
|
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);
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -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);
|
data/ext/polyphony/runqueue.c
CHANGED
@@ -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 */
|
data/ext/polyphony/thread.c
CHANGED
@@ -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
|
|
data/lib/polyphony/core/timer.rb
CHANGED
@@ -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(
|
48
|
+
def cancel_after(interval, with_exception: Polyphony::Cancel)
|
16
49
|
fiber = Fiber.current
|
17
50
|
@timeouts[fiber] = {
|
18
|
-
|
19
|
-
target_stamp:
|
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(
|
60
|
+
def move_on_after(interval, with_value: nil)
|
28
61
|
fiber = Fiber.current
|
29
62
|
@timeouts[fiber] = {
|
30
|
-
|
31
|
-
target_stamp:
|
32
|
-
|
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] =
|
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
|
53
|
-
|
54
|
-
when
|
55
|
-
|
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
|
-
|
61
|
-
|
99
|
+
return if @timeouts.empty?
|
100
|
+
|
62
101
|
@timeouts.each do |fiber, record|
|
63
102
|
next if record[:target_stamp] > now
|
64
103
|
|
65
|
-
|
66
|
-
|
67
|
-
|
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, :
|
15
|
+
attr_accessor :source_fiber, :raising_fiber
|
16
16
|
|
17
17
|
alias_method :orig_initialize, :initialize
|
18
18
|
def initialize(*args)
|
19
|
-
@
|
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 @
|
34
|
+
return sanitize(orig_backtrace) unless @raising_fiber
|
35
35
|
|
36
36
|
backtrace = orig_backtrace || []
|
37
|
-
sanitize(backtrace + @
|
37
|
+
sanitize(backtrace + @raising_fiber.caller)
|
38
38
|
end
|
39
39
|
|
40
40
|
POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
|
data/lib/polyphony/version.rb
CHANGED
data/test/test_fiber.rb
CHANGED
@@ -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
|
data/test/test_timer.rb
CHANGED
@@ -11,7 +11,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
|
|
11
11
|
@timer.stop
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
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
|
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
|
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.
|
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
|
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
|
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.
|
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
|
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.
|
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-
|
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
|