polyphony 0.43.4 → 0.43.10
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 +45 -0
- data/Gemfile.lock +1 -1
- data/README.md +21 -4
- data/TODO.md +1 -6
- 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 +2 -2
- 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/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +11 -9
- data/examples/xx-spin.rb +32 -0
- data/ext/polyphony/agent.h +39 -0
- data/ext/polyphony/event.c +86 -0
- data/ext/polyphony/fiber.c +0 -5
- data/ext/polyphony/libev_agent.c +231 -79
- data/ext/polyphony/polyphony.c +2 -2
- data/ext/polyphony/polyphony.h +19 -16
- data/ext/polyphony/polyphony_ext.c +4 -2
- data/ext/polyphony/queue.c +194 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +48 -31
- data/lib/polyphony.rb +5 -6
- data/lib/polyphony/core/channel.rb +3 -34
- 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 +9 -0
- data/lib/polyphony/extensions/fiber.rb +9 -2
- data/lib/polyphony/extensions/io.rb +16 -15
- data/lib/polyphony/extensions/openssl.rb +8 -0
- data/lib/polyphony/extensions/socket.rb +13 -9
- data/lib/polyphony/extensions/thread.rb +1 -1
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +2 -2
- data/test/q.rb +24 -0
- data/test/test_agent.rb +2 -2
- 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 +16 -5
- data/ext/polyphony/libev_queue.c +0 -217
- data/lib/polyphony/event.rb +0 -27
@@ -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)
|
@@ -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,39 @@
|
|
1
|
+
#ifndef AGENT_H
|
2
|
+
#define AGENT_H
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
|
6
|
+
// agent interface function signatures
|
7
|
+
|
8
|
+
// VALUE LibevAgent_accept(VALUE self, VALUE sock);
|
9
|
+
// VALUE LibevAgent_accept_loop(VALUE self, VALUE sock);
|
10
|
+
// VALUE libev_agent_await(VALUE self);
|
11
|
+
// VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port);
|
12
|
+
// VALUE LibevAgent_finalize(VALUE self);
|
13
|
+
// VALUE LibevAgent_post_fork(VALUE self);
|
14
|
+
// VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof);
|
15
|
+
// VALUE LibevAgent_read_loop(VALUE self, VALUE io);
|
16
|
+
// VALUE LibevAgent_ref(VALUE self);
|
17
|
+
// VALUE LibevAgent_sleep(VALUE self, VALUE duration);
|
18
|
+
// VALUE LibevAgent_unref(VALUE self);
|
19
|
+
// VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write);
|
20
|
+
// VALUE LibevAgent_wait_pid(VALUE self, VALUE pid);
|
21
|
+
// VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self);
|
22
|
+
|
23
|
+
typedef VALUE (* agent_pending_count_t)(VALUE self);
|
24
|
+
typedef VALUE (*agent_poll_t)(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue);
|
25
|
+
typedef int (* agent_ref_count_t)(VALUE self);
|
26
|
+
typedef void (* agent_reset_ref_count_t)(VALUE self);
|
27
|
+
typedef VALUE (* agent_wait_event_t)(VALUE self, VALUE raise_on_exception);
|
28
|
+
typedef VALUE (* agent_wakeup_t)(VALUE self);
|
29
|
+
|
30
|
+
typedef struct agent_interface {
|
31
|
+
agent_pending_count_t pending_count;
|
32
|
+
agent_poll_t poll;
|
33
|
+
agent_ref_count_t ref_count;
|
34
|
+
agent_reset_ref_count_t reset_ref_count;
|
35
|
+
agent_wait_event_t wait_event;
|
36
|
+
agent_wakeup_t wakeup;
|
37
|
+
} agent_interface_t;
|
38
|
+
|
39
|
+
#endif /* AGENT_H */
|
@@ -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 = __AGENT__.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) {
|
data/ext/polyphony/libev_agent.c
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
#include <netdb.h>
|
2
2
|
#include <sys/socket.h>
|
3
|
+
#include <sys/uio.h>
|
4
|
+
#include <unistd.h>
|
5
|
+
#include <fcntl.h>
|
6
|
+
#include <netinet/in.h>
|
7
|
+
#include <arpa/inet.h>
|
3
8
|
|
4
9
|
#include "polyphony.h"
|
5
10
|
#include "../libev/ev.h"
|
6
11
|
|
7
|
-
VALUE cLibevAgent = Qnil;
|
8
12
|
VALUE cTCPSocket;
|
9
13
|
|
10
14
|
struct LibevAgent_t {
|
@@ -130,7 +134,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
|
|
130
134
|
GetLibevAgent(self, agent);
|
131
135
|
|
132
136
|
if (is_nowait) {
|
133
|
-
long runnable_count =
|
137
|
+
long runnable_count = Queue_len(queue);
|
134
138
|
agent->run_no_wait_count++;
|
135
139
|
if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
|
136
140
|
return self;
|
@@ -147,7 +151,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
|
|
147
151
|
return self;
|
148
152
|
}
|
149
153
|
|
150
|
-
VALUE
|
154
|
+
VALUE LibevAgent_wakeup(VALUE self) {
|
151
155
|
struct LibevAgent_t *agent;
|
152
156
|
GetLibevAgent(self, agent);
|
153
157
|
|
@@ -277,6 +281,31 @@ VALUE libev_snooze() {
|
|
277
281
|
return Thread_switch_fiber(rb_thread_current());
|
278
282
|
}
|
279
283
|
|
284
|
+
ID ID_ivar_is_nonblocking;
|
285
|
+
|
286
|
+
// Since we need to ensure that fd's are non-blocking before every I/O
|
287
|
+
// operation, here we improve upon Ruby's rb_io_set_nonblock by caching the
|
288
|
+
// "nonblock" state in an instance variable. Calling rb_ivar_get on every read
|
289
|
+
// is still much cheaper than doing a fcntl syscall on every read! Preliminary
|
290
|
+
// benchmarks (with a "hello world" HTTP server) show throughput is improved
|
291
|
+
// by 10-13%.
|
292
|
+
inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
|
293
|
+
#ifdef _WIN32
|
294
|
+
return rb_w32_set_nonblock(fptr->fd);
|
295
|
+
#elif defined(F_GETFL)
|
296
|
+
VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
|
297
|
+
if (is_nonblocking == Qnil) {
|
298
|
+
rb_ivar_set(io, ID_ivar_is_nonblocking, Qtrue);
|
299
|
+
int oflags = fcntl(fptr->fd, F_GETFL);
|
300
|
+
if (oflags == -1) return;
|
301
|
+
if (oflags & O_NONBLOCK) return;
|
302
|
+
oflags |= O_NONBLOCK;
|
303
|
+
fcntl(fptr->fd, F_SETFL, oflags);
|
304
|
+
}
|
305
|
+
#endif
|
306
|
+
return;
|
307
|
+
}
|
308
|
+
|
280
309
|
VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
|
281
310
|
struct LibevAgent_t *agent;
|
282
311
|
struct libev_io watcher;
|
@@ -294,11 +323,20 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
294
323
|
if (underlying_io != Qnil) io = underlying_io;
|
295
324
|
GetOpenFile(io, fptr);
|
296
325
|
rb_io_check_byte_readable(fptr);
|
297
|
-
|
326
|
+
io_set_nonblock(fptr, io);
|
298
327
|
watcher.fiber = Qnil;
|
299
328
|
|
300
329
|
OBJ_TAINT(str);
|
301
330
|
|
331
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
332
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
333
|
+
// find out if that's the case.
|
334
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
335
|
+
if (fptr->rbuf.len > 0) {
|
336
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
337
|
+
fptr->rbuf.len = 0;
|
338
|
+
}
|
339
|
+
|
302
340
|
while (1) {
|
303
341
|
ssize_t n = read(fptr->fd, buf, len - total);
|
304
342
|
if (n < 0) {
|
@@ -348,6 +386,7 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
|
348
386
|
shrinkable = io_setstrbuf(&str, len); \
|
349
387
|
buf = RSTRING_PTR(str); \
|
350
388
|
total = 0; \
|
389
|
+
OBJ_TAINT(str); \
|
351
390
|
}
|
352
391
|
|
353
392
|
#define YIELD_STR() { \
|
@@ -374,10 +413,17 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
|
374
413
|
if (underlying_io != Qnil) io = underlying_io;
|
375
414
|
GetOpenFile(io, fptr);
|
376
415
|
rb_io_check_byte_readable(fptr);
|
377
|
-
|
416
|
+
io_set_nonblock(fptr, io);
|
378
417
|
watcher.fiber = Qnil;
|
379
418
|
|
380
|
-
|
419
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
420
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
421
|
+
// find out if that's the case.
|
422
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
423
|
+
if (fptr->rbuf.len > 0) {
|
424
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
425
|
+
fptr->rbuf.len = 0;
|
426
|
+
}
|
381
427
|
|
382
428
|
while (1) {
|
383
429
|
ssize_t n = read(fptr->fd, buf, len);
|
@@ -418,12 +464,12 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
418
464
|
struct libev_io watcher;
|
419
465
|
rb_io_t *fptr;
|
420
466
|
VALUE switchpoint_result = Qnil;
|
421
|
-
|
467
|
+
VALUE underlying_io;
|
422
468
|
char *buf = StringValuePtr(str);
|
423
469
|
long len = RSTRING_LEN(str);
|
424
470
|
long left = len;
|
425
471
|
|
426
|
-
|
472
|
+
underlying_io = rb_iv_get(io, "@io");
|
427
473
|
if (underlying_io != Qnil) io = underlying_io;
|
428
474
|
GetLibevAgent(self, agent);
|
429
475
|
io = rb_io_get_write_io(io);
|
@@ -435,19 +481,20 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
435
481
|
if (n < 0) {
|
436
482
|
int e = errno;
|
437
483
|
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
438
|
-
|
439
484
|
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
440
485
|
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
441
486
|
}
|
442
487
|
else {
|
443
|
-
switchpoint_result = libev_snooze();
|
444
|
-
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
445
|
-
|
446
488
|
buf += n;
|
447
489
|
left -= n;
|
448
490
|
}
|
449
491
|
}
|
450
492
|
|
493
|
+
if (watcher.fiber == Qnil) {
|
494
|
+
switchpoint_result = libev_snooze();
|
495
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
496
|
+
}
|
497
|
+
|
451
498
|
RB_GC_GUARD(watcher.fiber);
|
452
499
|
RB_GC_GUARD(switchpoint_result);
|
453
500
|
|
@@ -456,6 +503,86 @@ error:
|
|
456
503
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
457
504
|
}
|
458
505
|
|
506
|
+
VALUE LibevAgent_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
|
507
|
+
struct LibevAgent_t *agent;
|
508
|
+
struct libev_io watcher;
|
509
|
+
rb_io_t *fptr;
|
510
|
+
VALUE switchpoint_result = Qnil;
|
511
|
+
VALUE underlying_io;
|
512
|
+
long total_length = 0;
|
513
|
+
long total_written = 0;
|
514
|
+
struct iovec *iov = 0;
|
515
|
+
struct iovec *iov_ptr = 0;
|
516
|
+
int iov_count = argc;
|
517
|
+
|
518
|
+
underlying_io = rb_iv_get(io, "@io");
|
519
|
+
if (underlying_io != Qnil) io = underlying_io;
|
520
|
+
GetLibevAgent(self, agent);
|
521
|
+
io = rb_io_get_write_io(io);
|
522
|
+
GetOpenFile(io, fptr);
|
523
|
+
watcher.fiber = Qnil;
|
524
|
+
|
525
|
+
iov = malloc(iov_count * sizeof(struct iovec));
|
526
|
+
for (int i = 0; i < argc; i++) {
|
527
|
+
VALUE str = argv[i];
|
528
|
+
iov[i].iov_base = StringValuePtr(str);
|
529
|
+
iov[i].iov_len = RSTRING_LEN(str);
|
530
|
+
total_length += iov[i].iov_len;
|
531
|
+
}
|
532
|
+
iov_ptr = iov;
|
533
|
+
|
534
|
+
while (1) {
|
535
|
+
ssize_t n = writev(fptr->fd, iov_ptr, iov_count);
|
536
|
+
if (n < 0) {
|
537
|
+
int e = errno;
|
538
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
539
|
+
|
540
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
541
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
542
|
+
}
|
543
|
+
else {
|
544
|
+
total_written += n;
|
545
|
+
if (total_written == total_length) break;
|
546
|
+
|
547
|
+
while (n > 0) {
|
548
|
+
if ((size_t) n < iov_ptr[0].iov_len) {
|
549
|
+
iov_ptr[0].iov_base = (char *) iov_ptr[0].iov_base + n;
|
550
|
+
iov_ptr[0].iov_len -= n;
|
551
|
+
n = 0;
|
552
|
+
}
|
553
|
+
else {
|
554
|
+
n -= iov_ptr[0].iov_len;
|
555
|
+
iov_ptr += 1;
|
556
|
+
iov_count -= 1;
|
557
|
+
}
|
558
|
+
}
|
559
|
+
}
|
560
|
+
}
|
561
|
+
if (watcher.fiber == Qnil) {
|
562
|
+
switchpoint_result = libev_snooze();
|
563
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
564
|
+
}
|
565
|
+
|
566
|
+
RB_GC_GUARD(watcher.fiber);
|
567
|
+
RB_GC_GUARD(switchpoint_result);
|
568
|
+
|
569
|
+
free(iov);
|
570
|
+
return INT2NUM(total_written);
|
571
|
+
error:
|
572
|
+
free(iov);
|
573
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
574
|
+
}
|
575
|
+
|
576
|
+
VALUE LibevAgent_write_m(int argc, VALUE *argv, VALUE self) {
|
577
|
+
if (argc < 2)
|
578
|
+
// TODO: raise ArgumentError
|
579
|
+
rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
|
580
|
+
|
581
|
+
return (argc == 2) ?
|
582
|
+
LibevAgent_write(self, argv[0], argv[1]) :
|
583
|
+
LibevAgent_writev(self, argv[0], argc - 1, argv + 1);
|
584
|
+
}
|
585
|
+
|
459
586
|
///////////////////////////////////////////////////////////////////////////
|
460
587
|
|
461
588
|
VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
@@ -471,7 +598,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
471
598
|
|
472
599
|
GetLibevAgent(self, agent);
|
473
600
|
GetOpenFile(sock, fptr);
|
474
|
-
|
601
|
+
io_set_nonblock(fptr, sock);
|
475
602
|
watcher.fiber = Qnil;
|
476
603
|
while (1) {
|
477
604
|
fd = accept(fptr->fd, &addr, &len);
|
@@ -497,7 +624,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
497
624
|
fp->fd = fd;
|
498
625
|
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
499
626
|
rb_io_ascii8bit_binmode(socket);
|
500
|
-
|
627
|
+
io_set_nonblock(fp, socket);
|
501
628
|
rb_io_synchronized(fp);
|
502
629
|
|
503
630
|
// if (rsock_do_not_reverse_lookup) {
|
@@ -526,7 +653,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
|
526
653
|
|
527
654
|
GetLibevAgent(self, agent);
|
528
655
|
GetOpenFile(sock, fptr);
|
529
|
-
|
656
|
+
io_set_nonblock(fptr, sock);
|
530
657
|
watcher.fiber = Qnil;
|
531
658
|
|
532
659
|
while (1) {
|
@@ -552,7 +679,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
|
552
679
|
fp->fd = fd;
|
553
680
|
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
554
681
|
rb_io_ascii8bit_binmode(socket);
|
555
|
-
|
682
|
+
io_set_nonblock(fp, socket);
|
556
683
|
rb_io_synchronized(fp);
|
557
684
|
|
558
685
|
rb_yield(socket);
|
@@ -568,46 +695,41 @@ error:
|
|
568
695
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
569
696
|
}
|
570
697
|
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
// RB_GC_GUARD(switchpoint_result);
|
607
|
-
// return Qnil;
|
608
|
-
// error:
|
609
|
-
// return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
610
|
-
// }
|
698
|
+
VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
|
699
|
+
struct LibevAgent_t *agent;
|
700
|
+
struct libev_io watcher;
|
701
|
+
rb_io_t *fptr;
|
702
|
+
struct sockaddr_in addr;
|
703
|
+
char *host_buf = StringValueCStr(host);
|
704
|
+
VALUE switchpoint_result = Qnil;
|
705
|
+
VALUE underlying_sock = rb_iv_get(sock, "@io");
|
706
|
+
if (underlying_sock != Qnil) sock = underlying_sock;
|
707
|
+
|
708
|
+
GetLibevAgent(self, agent);
|
709
|
+
GetOpenFile(sock, fptr);
|
710
|
+
io_set_nonblock(fptr, sock);
|
711
|
+
watcher.fiber = Qnil;
|
712
|
+
|
713
|
+
addr.sin_family = AF_INET;
|
714
|
+
addr.sin_addr.s_addr = inet_addr(host_buf);
|
715
|
+
addr.sin_port = htons(NUM2INT(port));
|
716
|
+
|
717
|
+
int result = connect(fptr->fd, (struct sockaddr *)&addr, sizeof(addr));
|
718
|
+
if (result < 0) {
|
719
|
+
int e = errno;
|
720
|
+
if (e != EINPROGRESS) rb_syserr_fail(e, strerror(e));
|
721
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
722
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
723
|
+
}
|
724
|
+
else {
|
725
|
+
switchpoint_result = libev_snooze();
|
726
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
727
|
+
}
|
728
|
+
RB_GC_GUARD(switchpoint_result);
|
729
|
+
return sock;
|
730
|
+
error:
|
731
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
732
|
+
}
|
611
733
|
|
612
734
|
VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
|
613
735
|
struct LibevAgent_t *agent;
|
@@ -655,6 +777,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
|
|
655
777
|
ev_timer_start(agent->ev_loop, &watcher.timer);
|
656
778
|
|
657
779
|
switchpoint_result = libev_await(agent);
|
780
|
+
|
658
781
|
ev_timer_stop(agent->ev_loop, &watcher.timer);
|
659
782
|
|
660
783
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -703,31 +826,60 @@ struct ev_loop *LibevAgent_ev_loop(VALUE self) {
|
|
703
826
|
return agent->ev_loop;
|
704
827
|
}
|
705
828
|
|
829
|
+
void LibevAgent_async_callback(EV_P_ ev_async *w, int revents) { }
|
830
|
+
|
831
|
+
VALUE LibevAgent_wait_event(VALUE self, VALUE raise) {
|
832
|
+
struct LibevAgent_t *agent;
|
833
|
+
struct ev_async async;
|
834
|
+
VALUE switchpoint_result = Qnil;
|
835
|
+
GetLibevAgent(self, agent);
|
836
|
+
|
837
|
+
ev_async_init(&async, LibevAgent_async_callback);
|
838
|
+
ev_async_start(agent->ev_loop, &async);
|
839
|
+
|
840
|
+
switchpoint_result = libev_await(agent);
|
841
|
+
ev_async_stop(agent->ev_loop, &async);
|
842
|
+
|
843
|
+
if (RTEST(raise)) TEST_RESUME_EXCEPTION(switchpoint_result);
|
844
|
+
RB_GC_GUARD(switchpoint_result);
|
845
|
+
return switchpoint_result;
|
846
|
+
}
|
847
|
+
|
706
848
|
void Init_LibevAgent() {
|
707
849
|
rb_require("socket");
|
708
850
|
cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
709
851
|
|
710
|
-
|
711
|
-
rb_define_alloc_func(
|
712
|
-
|
713
|
-
rb_define_method(
|
714
|
-
rb_define_method(
|
715
|
-
rb_define_method(
|
716
|
-
rb_define_method(
|
717
|
-
|
718
|
-
rb_define_method(
|
719
|
-
rb_define_method(
|
720
|
-
|
721
|
-
rb_define_method(
|
722
|
-
rb_define_method(
|
723
|
-
|
724
|
-
rb_define_method(
|
725
|
-
rb_define_method(
|
726
|
-
rb_define_method(
|
727
|
-
rb_define_method(
|
728
|
-
rb_define_method(
|
729
|
-
|
730
|
-
rb_define_method(
|
731
|
-
rb_define_method(
|
732
|
-
rb_define_method(
|
852
|
+
VALUE cAgent = rb_define_class_under(mPolyphony, "Agent", rb_cData);
|
853
|
+
rb_define_alloc_func(cAgent, LibevAgent_allocate);
|
854
|
+
|
855
|
+
rb_define_method(cAgent, "initialize", LibevAgent_initialize, 0);
|
856
|
+
rb_define_method(cAgent, "finalize", LibevAgent_finalize, 0);
|
857
|
+
rb_define_method(cAgent, "post_fork", LibevAgent_post_fork, 0);
|
858
|
+
rb_define_method(cAgent, "pending_count", LibevAgent_pending_count, 0);
|
859
|
+
|
860
|
+
rb_define_method(cAgent, "ref", LibevAgent_ref, 0);
|
861
|
+
rb_define_method(cAgent, "unref", LibevAgent_unref, 0);
|
862
|
+
|
863
|
+
rb_define_method(cAgent, "poll", LibevAgent_poll, 3);
|
864
|
+
rb_define_method(cAgent, "break", LibevAgent_wakeup, 0);
|
865
|
+
|
866
|
+
rb_define_method(cAgent, "read", LibevAgent_read, 4);
|
867
|
+
rb_define_method(cAgent, "read_loop", LibevAgent_read_loop, 1);
|
868
|
+
rb_define_method(cAgent, "write", LibevAgent_write_m, -1);
|
869
|
+
rb_define_method(cAgent, "accept", LibevAgent_accept, 1);
|
870
|
+
rb_define_method(cAgent, "accept_loop", LibevAgent_accept_loop, 1);
|
871
|
+
rb_define_method(cAgent, "connect", LibevAgent_connect, 3);
|
872
|
+
rb_define_method(cAgent, "wait_io", LibevAgent_wait_io, 2);
|
873
|
+
rb_define_method(cAgent, "sleep", LibevAgent_sleep, 1);
|
874
|
+
rb_define_method(cAgent, "waitpid", LibevAgent_waitpid, 1);
|
875
|
+
rb_define_method(cAgent, "wait_event", LibevAgent_wait_event, 1);
|
876
|
+
|
877
|
+
ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
|
878
|
+
|
879
|
+
__AGENT__.wakeup = LibevAgent_wakeup;
|
880
|
+
__AGENT__.pending_count = LibevAgent_pending_count;
|
881
|
+
__AGENT__.poll = LibevAgent_poll;
|
882
|
+
__AGENT__.ref_count = LibevAgent_ref_count;
|
883
|
+
__AGENT__.reset_ref_count = LibevAgent_reset_ref_count;
|
884
|
+
__AGENT__.wait_event = LibevAgent_wait_event;
|
733
885
|
}
|