polyphony 0.43.3 → 0.43.9
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/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +44 -0
- data/Gemfile.lock +1 -1
- data/README.md +21 -4
- data/TODO.md +1 -2
- data/bin/stress.rb +28 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_user-guide/web-server.md +11 -11
- data/docs/getting-started/overview.md +4 -4
- data/docs/index.md +4 -3
- data/docs/main-concepts/design-principles.md +23 -34
- data/docs/main-concepts/fiber-scheduling.md +1 -1
- data/docs/polyphony-logo.png +0 -0
- data/examples/core/xx-channels.rb +4 -2
- data/examples/core/xx-using-a-mutex.rb +2 -1
- data/examples/io/xx-happy-eyeballs.rb +21 -22
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/mem-usage.rb +34 -28
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +11 -9
- data/examples/xx-spin.rb +32 -0
- data/ext/polyphony/event.c +86 -0
- data/ext/polyphony/fiber.c +0 -5
- data/ext/polyphony/libev_agent.c +181 -24
- data/ext/polyphony/polyphony.c +0 -2
- data/ext/polyphony/polyphony.h +14 -7
- data/ext/polyphony/polyphony_ext.c +4 -2
- data/ext/polyphony/queue.c +187 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +18 -12
- data/lib/polyphony.rb +5 -14
- data/lib/polyphony/core/channel.rb +3 -34
- data/lib/polyphony/core/global_api.rb +1 -1
- data/lib/polyphony/core/resource_pool.rb +13 -75
- data/lib/polyphony/core/sync.rb +12 -9
- data/lib/polyphony/core/thread_pool.rb +1 -1
- data/lib/polyphony/extensions/core.rb +34 -0
- data/lib/polyphony/extensions/fiber.rb +9 -2
- data/lib/polyphony/extensions/io.rb +17 -16
- data/lib/polyphony/extensions/openssl.rb +8 -0
- data/lib/polyphony/extensions/socket.rb +12 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/q.rb +24 -0
- data/test/test_agent.rb +1 -1
- data/test/test_event.rb +12 -0
- data/test/test_global_api.rb +2 -2
- data/test/test_io.rb +24 -2
- data/test/test_queue.rb +59 -1
- data/test/test_resource_pool.rb +0 -43
- data/test/test_trace.rb +18 -17
- metadata +15 -5
- data/ext/polyphony/libev_queue.c +0 -217
- data/lib/polyphony/event.rb +0 -27
data/docs/polyphony-logo.png
CHANGED
Binary file
|
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony'
|
5
|
+
require 'polyphony/core/channel'
|
5
6
|
|
6
7
|
def echo(cin, cout)
|
7
8
|
puts 'start echoer'
|
8
9
|
while (msg = cin.receive)
|
10
|
+
puts "echoer received #{msg}"
|
9
11
|
cout << "you said: #{msg}"
|
10
12
|
end
|
11
13
|
ensure
|
@@ -20,7 +22,7 @@ spin do
|
|
20
22
|
puts 'start receiver'
|
21
23
|
while (msg = chan2.receive)
|
22
24
|
puts msg
|
23
|
-
$main.
|
25
|
+
$main.schedule if msg =~ /world/
|
24
26
|
end
|
25
27
|
ensure
|
26
28
|
puts 'receiver stopped'
|
@@ -42,4 +44,4 @@ $main = spin do
|
|
42
44
|
puts "done #{Time.now - t0}"
|
43
45
|
end
|
44
46
|
|
45
|
-
|
47
|
+
$main.await
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony'
|
5
|
+
require 'polyphony/core/sync'
|
5
6
|
|
6
7
|
def loop_it(number, lock)
|
7
8
|
loop do
|
@@ -13,7 +14,7 @@ def loop_it(number, lock)
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
lock = Polyphony::
|
17
|
+
lock = Polyphony::Mutex.new
|
17
18
|
spin { loop_it(1, lock) }
|
18
19
|
spin { loop_it(2, lock) }
|
19
20
|
spin { loop_it(3, lock) }
|
@@ -1,37 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# idea taken from the example given in trio:
|
4
|
-
# https://www.youtube.com/watch?v=oLkfnc_UMcE
|
5
|
-
|
6
|
-
require 'bundler/setup'
|
7
3
|
require 'polyphony'
|
8
4
|
|
9
|
-
def try_connect(
|
10
|
-
puts "trying #{
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
def try_connect(ip_address, port, supervisor)
|
6
|
+
puts "trying #{ip_address}"
|
7
|
+
sleep rand * 0.2
|
8
|
+
socket = TCPSocket.new(ip_address, port)
|
9
|
+
puts "connected to #{ip_address}"
|
10
|
+
supervisor.schedule [ip_address, socket]
|
14
11
|
rescue IOError, SystemCallError
|
15
12
|
# ignore error
|
16
13
|
end
|
17
14
|
|
18
|
-
def happy_eyeballs(hostname, port, max_wait_time: 0.
|
15
|
+
def happy_eyeballs(hostname, port, max_wait_time: 0.010)
|
19
16
|
targets = Socket.getaddrinfo(hostname, port, :INET, :STREAM)
|
20
17
|
t0 = Time.now
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
if success
|
29
|
-
puts format('success: %s (%.3fs)', success[0], Time.now - t0)
|
30
|
-
else
|
31
|
-
puts "timed out (#{Time.now - t0}s)"
|
18
|
+
fibers = []
|
19
|
+
supervisor = Fiber.current
|
20
|
+
spin do
|
21
|
+
targets.each do |t|
|
22
|
+
spin { try_connect(t[2], t[1], supervisor) }
|
23
|
+
sleep(max_wait_time)
|
32
24
|
end
|
25
|
+
suspend
|
26
|
+
end
|
27
|
+
target, socket = move_on_after(5) { suspend }
|
28
|
+
supervisor.shutdown_all_children
|
29
|
+
if target
|
30
|
+
puts format('success: %s (%.3fs)', target, Time.now - t0)
|
31
|
+
else
|
32
|
+
puts 'timed out'
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
# Let's try it out:
|
37
36
|
happy_eyeballs('debian.org', 'https')
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
i, o = IO.pipe
|
8
|
+
|
9
|
+
w = Zlib::GzipWriter.new(o)
|
10
|
+
|
11
|
+
s = (1..1000).map { (65 + rand(26)).chr }.join
|
12
|
+
puts "full length: #{s.bytesize}"
|
13
|
+
w << s
|
14
|
+
w.close
|
15
|
+
o.close
|
16
|
+
|
17
|
+
|
18
|
+
z = i.read
|
19
|
+
puts "zipped length: #{z.bytesize}"
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
class Fiber
|
6
|
+
attr_accessor :next
|
7
|
+
end
|
8
|
+
|
9
|
+
# This program shows how the performance
|
10
|
+
|
11
|
+
def run(num_fibers)
|
12
|
+
count = 0
|
13
|
+
|
14
|
+
GC.disable
|
15
|
+
|
16
|
+
first = nil
|
17
|
+
last = nil
|
18
|
+
supervisor = Fiber.current
|
19
|
+
num_fibers.times do
|
20
|
+
fiber = Fiber.new do
|
21
|
+
loop do
|
22
|
+
count += 1
|
23
|
+
if count == 1_000_000
|
24
|
+
supervisor.transfer
|
25
|
+
else
|
26
|
+
Fiber.current.next.transfer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
first ||= fiber
|
31
|
+
last.next = fiber if last
|
32
|
+
last = fiber
|
33
|
+
end
|
34
|
+
|
35
|
+
last.next = first
|
36
|
+
|
37
|
+
t0 = Time.now
|
38
|
+
first.transfer
|
39
|
+
elapsed = Time.now - t0
|
40
|
+
|
41
|
+
puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
|
42
|
+
GC.start
|
43
|
+
end
|
44
|
+
|
45
|
+
run(100)
|
46
|
+
run(1000)
|
47
|
+
run(10000)
|
@@ -4,47 +4,53 @@ def mem_usage
|
|
4
4
|
`ps -o rss #{$$}`.split.last.to_i
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
7
|
+
def calculate_memory_cost(name, count, &block)
|
8
|
+
GC.enable
|
9
|
+
ObjectSpace.garbage_collect
|
10
|
+
sleep 0.5
|
8
11
|
GC.disable
|
9
12
|
rss0 = mem_usage
|
10
|
-
|
13
|
+
count0 = ObjectSpace.count_objects[:TOTAL] - ObjectSpace.count_objects[:FREE]
|
14
|
+
a = []
|
15
|
+
count.times { a << block.call }
|
11
16
|
rss1 = mem_usage
|
12
|
-
|
17
|
+
count1 = ObjectSpace.count_objects[:TOTAL] - ObjectSpace.count_objects[:FREE]
|
18
|
+
p [count0, count1]
|
19
|
+
# sleep 0.5
|
13
20
|
cost = (rss1 - rss0).to_f / count
|
21
|
+
count_delta = (count1 - count0) / count
|
14
22
|
|
15
|
-
puts "
|
23
|
+
puts "#{name} rss cost: #{cost}KB object count: #{count_delta}"
|
16
24
|
end
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
def calculate_thread_memory_cost(count)
|
21
|
-
GC.disable
|
22
|
-
rss0 = mem_usage
|
23
|
-
count.times { Thread.new { sleep 1 } }
|
24
|
-
sleep 0.5
|
25
|
-
rss1 = mem_usage
|
26
|
-
sleep 0.5
|
27
|
-
GC.start
|
28
|
-
cost = (rss1 - rss0).to_f / count
|
26
|
+
f = Fiber.new { |f| f.transfer }
|
27
|
+
f.transfer Fiber.current
|
29
28
|
|
30
|
-
|
29
|
+
calculate_memory_cost('fiber', 10000) do
|
30
|
+
f = Fiber.new { |f| f.transfer :foo }
|
31
|
+
f.transfer Fiber.current
|
32
|
+
f
|
31
33
|
end
|
32
34
|
|
33
|
-
|
35
|
+
t = Thread.new { sleep 1}
|
36
|
+
t.kill
|
37
|
+
t.join
|
38
|
+
|
39
|
+
calculate_memory_cost('thread', 500) do
|
40
|
+
t = Thread.new { sleep 1 }
|
41
|
+
sleep 0.001
|
42
|
+
t
|
43
|
+
end
|
44
|
+
(Thread.list - [Thread.current]).each(&:kill).each(&:join)
|
34
45
|
|
35
46
|
require 'bundler/setup'
|
36
47
|
require 'polyphony'
|
37
48
|
|
38
|
-
|
39
|
-
|
40
|
-
rss0 = mem_usage
|
41
|
-
count.times { spin { :foo } }
|
42
|
-
snooze
|
43
|
-
rss1 = mem_usage
|
44
|
-
GC.start
|
45
|
-
cost = (rss1 - rss0).to_f / count
|
49
|
+
f = spin { sleep 0.1 }
|
50
|
+
f.await
|
46
51
|
|
47
|
-
|
52
|
+
calculate_memory_cost('polyphony fiber', 10000) do
|
53
|
+
f = spin { :foo }
|
54
|
+
f.await
|
55
|
+
f
|
48
56
|
end
|
49
|
-
|
50
|
-
calculate_extended_fiber_memory_cost(10000)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
X = 1_000_000
|
7
|
+
|
8
|
+
GC.disable
|
9
|
+
|
10
|
+
count = 0
|
11
|
+
|
12
|
+
pong = spin_loop do
|
13
|
+
msg, ping = receive
|
14
|
+
count += 1
|
15
|
+
ping << 'pong'
|
16
|
+
end
|
17
|
+
|
18
|
+
ping = spin do
|
19
|
+
X.times do
|
20
|
+
pong << ['ping', Fiber.current]
|
21
|
+
msg = receive
|
22
|
+
count += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
t0 = Time.now
|
27
|
+
ping.await
|
28
|
+
dt = Time.now - t0
|
29
|
+
puts format('message rate: %d/s', (X / dt))
|
@@ -5,19 +5,20 @@ require 'polyphony'
|
|
5
5
|
|
6
6
|
def bm(fibers, iterations)
|
7
7
|
count = 0
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
count += 1
|
15
|
-
end
|
8
|
+
t_pre = Time.now
|
9
|
+
fibers.times do
|
10
|
+
spin do
|
11
|
+
iterations.times do
|
12
|
+
snooze
|
13
|
+
count += 1
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
17
|
+
t0 = Time.now
|
18
|
+
Fiber.current.await_all_children
|
19
19
|
dt = Time.now - t0
|
20
|
-
puts "#{[fibers, iterations].inspect} count: #{count} #{count / dt.to_f}/s"
|
20
|
+
puts "#{[fibers, iterations].inspect} setup: #{t0 - t_pre}s count: #{count} #{count / dt.to_f}/s"
|
21
|
+
Thread.current.run_queue_trace
|
21
22
|
end
|
22
23
|
|
23
24
|
GC.disable
|
@@ -27,5 +28,6 @@ bm(10, 100_000)
|
|
27
28
|
bm(100, 10_000)
|
28
29
|
bm(1_000, 1_000)
|
29
30
|
bm(10_000, 100)
|
31
|
+
|
30
32
|
# bm(100_000, 10)
|
31
33
|
# bm(1_000_000, 1)
|
data/examples/xx-spin.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
puts "pid: #{Process.pid}"
|
7
|
+
GC.disable
|
8
|
+
|
9
|
+
def mem_usage
|
10
|
+
# orig_backtick('ps -o rss #{$$}').split.last.to_i
|
11
|
+
`ps -o rss #{$$}`.split.last.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
f = File.open('spin.log', 'w+')
|
15
|
+
|
16
|
+
m0 = mem_usage
|
17
|
+
|
18
|
+
X = ARGV[0] ? ARGV[0].to_i : 10
|
19
|
+
STDOUT.orig_write "Starting #{X} fibers...\n"
|
20
|
+
t0 = Time.now
|
21
|
+
x = nil
|
22
|
+
X.times do |i|
|
23
|
+
spin { p i; suspend }
|
24
|
+
end
|
25
|
+
|
26
|
+
suspend
|
27
|
+
f.close
|
28
|
+
t1 = Time.now
|
29
|
+
m1 = mem_usage
|
30
|
+
rate = X / (t1 - t0)
|
31
|
+
mem_cost = (m1 - m0) / X.to_f
|
32
|
+
STDOUT.orig_write("#{ { time: t1 - t0, spin_rate: rate, fiber_mem_cost: mem_cost }.inspect }\n")
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
#include "ring_buffer.h"
|
3
|
+
|
4
|
+
typedef struct event {
|
5
|
+
VALUE waiting_fiber;
|
6
|
+
} Event_t;
|
7
|
+
|
8
|
+
VALUE cEvent = Qnil;
|
9
|
+
|
10
|
+
static void Event_mark(void *ptr) {
|
11
|
+
Event_t *event = ptr;
|
12
|
+
rb_gc_mark(event->waiting_fiber);
|
13
|
+
}
|
14
|
+
|
15
|
+
static void Event_free(void *ptr) {
|
16
|
+
xfree(ptr);
|
17
|
+
}
|
18
|
+
|
19
|
+
static size_t Event_size(const void *ptr) {
|
20
|
+
return sizeof(Event_t);
|
21
|
+
}
|
22
|
+
|
23
|
+
static const rb_data_type_t Event_type = {
|
24
|
+
"Event",
|
25
|
+
{Event_mark, Event_free, Event_size,},
|
26
|
+
0, 0, 0
|
27
|
+
};
|
28
|
+
|
29
|
+
static VALUE Event_allocate(VALUE klass) {
|
30
|
+
Event_t *event;
|
31
|
+
|
32
|
+
event = ALLOC(Event_t);
|
33
|
+
return TypedData_Wrap_Struct(klass, &Event_type, event);
|
34
|
+
}
|
35
|
+
|
36
|
+
#define GetEvent(obj, event) \
|
37
|
+
TypedData_Get_Struct((obj), Event_t, &Event_type, (event))
|
38
|
+
|
39
|
+
static VALUE Event_initialize(VALUE self) {
|
40
|
+
Event_t *event;
|
41
|
+
GetEvent(self, event);
|
42
|
+
|
43
|
+
event->waiting_fiber = Qnil;
|
44
|
+
|
45
|
+
return self;
|
46
|
+
}
|
47
|
+
|
48
|
+
VALUE Event_signal(int argc, VALUE *argv, VALUE self) {
|
49
|
+
VALUE value = argc > 0 ? argv[0] : Qnil;
|
50
|
+
Event_t *event;
|
51
|
+
GetEvent(self, event);
|
52
|
+
|
53
|
+
if (event->waiting_fiber != Qnil) {
|
54
|
+
Fiber_make_runnable(event->waiting_fiber, value);
|
55
|
+
event->waiting_fiber = Qnil;
|
56
|
+
}
|
57
|
+
return self;
|
58
|
+
}
|
59
|
+
|
60
|
+
VALUE Event_await(VALUE self) {
|
61
|
+
Event_t *event;
|
62
|
+
GetEvent(self, event);
|
63
|
+
|
64
|
+
if (event->waiting_fiber != Qnil)
|
65
|
+
rb_raise(rb_eRuntimeError, "Event is already awaited by another fiber");
|
66
|
+
|
67
|
+
VALUE agent = rb_ivar_get(rb_thread_current(), ID_ivar_agent);
|
68
|
+
event->waiting_fiber = rb_fiber_current();
|
69
|
+
VALUE switchpoint_result = LibevAgent_wait_event(agent, Qnil);
|
70
|
+
event->waiting_fiber = Qnil;
|
71
|
+
|
72
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
73
|
+
RB_GC_GUARD(agent);
|
74
|
+
RB_GC_GUARD(switchpoint_result);
|
75
|
+
|
76
|
+
return switchpoint_result;
|
77
|
+
}
|
78
|
+
|
79
|
+
void Init_Event() {
|
80
|
+
cEvent = rb_define_class_under(mPolyphony, "Event", rb_cData);
|
81
|
+
rb_define_alloc_func(cEvent, Event_allocate);
|
82
|
+
|
83
|
+
rb_define_method(cEvent, "initialize", Event_initialize, 0);
|
84
|
+
rb_define_method(cEvent, "await", Event_await, 0);
|
85
|
+
rb_define_method(cEvent, "signal", Event_signal, -1);
|
86
|
+
}
|
data/ext/polyphony/fiber.c
CHANGED
@@ -9,8 +9,6 @@ ID ID_trace_runnable;
|
|
9
9
|
ID ID_trace_terminate;
|
10
10
|
ID ID_trace_wait;
|
11
11
|
|
12
|
-
VALUE cEvent = Qnil;
|
13
|
-
|
14
12
|
VALUE SYM_dead;
|
15
13
|
VALUE SYM_running;
|
16
14
|
VALUE SYM_runnable;
|
@@ -35,9 +33,6 @@ static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
|
|
35
33
|
|
36
34
|
inline VALUE Fiber_auto_watcher(VALUE self) {
|
37
35
|
VALUE watcher;
|
38
|
-
if (cEvent == Qnil) {
|
39
|
-
cEvent = rb_const_get(mPolyphony, rb_intern("Event"));
|
40
|
-
}
|
41
36
|
|
42
37
|
watcher = rb_ivar_get(self, ID_ivar_auto_watcher);
|
43
38
|
if (watcher == Qnil) {
|