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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +34 -14
- data/examples/core/xx-mt-scheduler.rb +349 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/performance/snooze.rb +5 -5
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +73 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +12 -4
- data/ext/gyro/async.c +58 -61
- data/ext/gyro/child.c +50 -59
- data/ext/gyro/gyro.c +84 -192
- data/ext/gyro/gyro.h +29 -2
- data/ext/gyro/gyro_ext.c +6 -0
- data/ext/gyro/io.c +87 -96
- data/ext/gyro/queue.c +109 -0
- data/ext/gyro/selector.c +117 -0
- data/ext/gyro/signal.c +44 -54
- data/ext/gyro/socket.c +15 -20
- data/ext/gyro/thread.c +203 -0
- data/ext/gyro/timer.c +79 -50
- data/ext/libev/ev.c +3 -0
- data/lib/polyphony.rb +7 -12
- data/lib/polyphony/core/global_api.rb +5 -1
- data/lib/polyphony/core/throttler.rb +6 -11
- data/lib/polyphony/extensions/core.rb +2 -0
- data/lib/polyphony/extensions/fiber.rb +11 -13
- data/lib/polyphony/extensions/thread.rb +52 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +8 -3
- data/test/test_fiber.rb +2 -2
- data/test/test_global_api.rb +4 -5
- data/test/test_gyro.rb +3 -2
- data/test/test_io.rb +1 -0
- data/test/test_supervisor.rb +3 -3
- data/test/test_thread.rb +44 -0
- data/test/test_throttler.rb +41 -0
- data/test/test_timer.rb +13 -7
- metadata +17 -6
- data/examples/core/xx-thread_cancel.rb +0 -28
- data/examples/performance/thread.rb +0 -27
- data/lib/polyphony/core/thread.rb +0 -23
data/ext/gyro/timer.c
CHANGED
@@ -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
|
-
|
15
|
+
VALUE cGyro_Timer = Qnil;
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
49
|
-
struct Gyro_Timer *timer =
|
50
|
-
|
51
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
87
|
-
struct Gyro_Timer *timer
|
94
|
+
VALUE Gyro_Timer_stop(VALUE self) {
|
95
|
+
struct Gyro_Timer *timer;
|
96
|
+
GetGyro_Timer(self, timer);
|
88
97
|
|
89
|
-
if (
|
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
|
-
|
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
|
-
|
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(
|
121
|
+
ev_timer_start(timer->ev_loop, &timer->ev_timer);
|
112
122
|
}
|
113
123
|
|
114
|
-
ret =
|
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
|
+
}
|
data/ext/libev/ev.c
CHANGED
@@ -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
|
*/
|
data/lib/polyphony.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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(
|
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
|
-
|
19
|
-
|
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)
|
@@ -35,7 +35,7 @@ module FiberControl
|
|
35
35
|
|
36
36
|
def raise(*args)
|
37
37
|
error = error_from_raise_args(args)
|
38
|
-
schedule
|
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
|
-
|
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
|
-
@
|
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
|
-
|
149
|
-
parent_fiber.schedule(result)
|
143
|
+
exception_receiving_fiber.schedule(result)
|
150
144
|
ensure
|
151
|
-
|
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
|
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -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
|
-
|
29
|
+
Thread.current.switch_fiber
|
25
30
|
Polyphony.reset!
|
26
31
|
end
|
27
32
|
end
|