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