polyphony 0.40 → 0.41
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +11 -2
- data/.gitignore +2 -2
- data/.rubocop.yml +30 -0
- data/CHANGELOG.md +6 -2
- data/Gemfile.lock +9 -6
- data/Rakefile +2 -2
- data/TODO.md +18 -97
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/nav.html +5 -5
- data/docs/api-reference/fiber.md +2 -2
- data/docs/main-concepts/design-principles.md +67 -9
- data/docs/main-concepts/extending.md +1 -1
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-sleeping.rb +14 -6
- data/examples/io/xx-irb.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
- data/ext/{gyro → polyphony}/extconf.rb +2 -2
- data/ext/{gyro → polyphony}/fiber.c +15 -19
- data/ext/{gyro → polyphony}/libev.c +0 -0
- data/ext/{gyro → polyphony}/libev.h +0 -0
- data/ext/polyphony/libev_agent.c +503 -0
- data/ext/polyphony/libev_queue.c +214 -0
- data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -25
- data/ext/polyphony/polyphony.h +90 -0
- data/ext/polyphony/polyphony_ext.c +23 -0
- data/ext/{gyro → polyphony}/socket.c +14 -14
- data/ext/{gyro → polyphony}/thread.c +32 -115
- data/ext/{gyro → polyphony}/tracing.c +1 -1
- data/lib/polyphony.rb +16 -12
- data/lib/polyphony/adapters/irb.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +6 -5
- data/lib/polyphony/adapters/process.rb +5 -5
- data/lib/polyphony/adapters/trace.rb +28 -28
- data/lib/polyphony/core/channel.rb +3 -3
- data/lib/polyphony/core/exceptions.rb +1 -1
- data/lib/polyphony/core/global_api.rb +11 -9
- data/lib/polyphony/core/resource_pool.rb +3 -3
- data/lib/polyphony/core/sync.rb +2 -2
- data/lib/polyphony/core/thread_pool.rb +6 -6
- data/lib/polyphony/core/throttler.rb +13 -6
- data/lib/polyphony/event.rb +27 -0
- data/lib/polyphony/extensions/core.rb +20 -11
- data/lib/polyphony/extensions/fiber.rb +4 -4
- data/lib/polyphony/extensions/io.rb +56 -26
- data/lib/polyphony/extensions/openssl.rb +4 -8
- data/lib/polyphony/extensions/socket.rb +27 -9
- data/lib/polyphony/extensions/thread.rb +16 -9
- data/lib/polyphony/net.rb +9 -9
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +2 -2
- data/test/helper.rb +12 -1
- data/test/test_agent.rb +77 -0
- data/test/{test_async.rb → test_event.rb} +13 -7
- data/test/test_ext.rb +25 -4
- data/test/test_fiber.rb +19 -10
- data/test/test_global_api.rb +4 -4
- data/test/test_io.rb +46 -24
- data/test/test_queue.rb +74 -0
- data/test/test_signal.rb +3 -40
- data/test/test_socket.rb +33 -0
- data/test/test_thread.rb +37 -16
- data/test/test_trace.rb +6 -5
- metadata +24 -24
- data/ext/gyro/async.c +0 -132
- data/ext/gyro/child.c +0 -108
- data/ext/gyro/gyro.h +0 -158
- data/ext/gyro/gyro_ext.c +0 -33
- data/ext/gyro/io.c +0 -457
- data/ext/gyro/queue.c +0 -146
- data/ext/gyro/selector.c +0 -205
- data/ext/gyro/signal.c +0 -99
- data/ext/gyro/timer.c +0 -115
- data/test/test_timer.rb +0 -56
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
class Test
|
9
|
+
def test_sleep
|
10
|
+
puts "going to sleep"
|
11
|
+
sleep 1
|
12
|
+
puts "done sleeping"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_spin
|
16
|
+
spin {
|
17
|
+
10.times {
|
18
|
+
STDOUT << '.'
|
19
|
+
sleep 0.1
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
puts "going to sleep\n"
|
24
|
+
sleep 1
|
25
|
+
puts 'woke up'
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_file
|
29
|
+
f = File.open(__FILE__, 'r')
|
30
|
+
puts Thread.current.agent.read(f, +'', 10000, true)
|
31
|
+
|
32
|
+
Thread.current.agent.write(STDOUT, "Write something: ")
|
33
|
+
str = +''
|
34
|
+
Thread.current.agent.read(STDIN, str, 5, false)
|
35
|
+
puts str
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_fork
|
39
|
+
pid = fork do
|
40
|
+
Thread.current.agent.post_fork
|
41
|
+
puts 'child going to sleep'
|
42
|
+
sleep 1
|
43
|
+
puts 'child done sleeping'
|
44
|
+
exit(42)
|
45
|
+
end
|
46
|
+
|
47
|
+
puts "Waiting for pid #{pid}"
|
48
|
+
result = Thread.current.agent.waitpid(pid)
|
49
|
+
puts "Done waiting"
|
50
|
+
p result
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_async
|
54
|
+
async = Polyphony::Event.new
|
55
|
+
|
56
|
+
spin {
|
57
|
+
puts "signaller starting"
|
58
|
+
sleep 1
|
59
|
+
puts "signal"
|
60
|
+
async.signal(:foo)
|
61
|
+
}
|
62
|
+
|
63
|
+
puts "awaiting event"
|
64
|
+
p async.await
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_queue
|
68
|
+
q = Gyro::Queue.new
|
69
|
+
spin {
|
70
|
+
10.times {
|
71
|
+
q << Time.now.to_f
|
72
|
+
sleep 0.2
|
73
|
+
}
|
74
|
+
q << :STOP
|
75
|
+
}
|
76
|
+
|
77
|
+
loop do
|
78
|
+
value = q.shift
|
79
|
+
break if value == :STOP
|
80
|
+
|
81
|
+
p value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_thread
|
86
|
+
t = Thread.new do
|
87
|
+
puts "thread going to sleep"
|
88
|
+
sleep 0.2
|
89
|
+
puts "thread done sleeping"
|
90
|
+
end
|
91
|
+
|
92
|
+
t.await
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
t = Test.new
|
97
|
+
|
98
|
+
t.methods.select { |m| m =~ /^test_/ }.each do |m|
|
99
|
+
puts '*' * 40
|
100
|
+
puts m
|
101
|
+
t.send(m)
|
102
|
+
end
|
@@ -5,13 +5,21 @@ require 'polyphony'
|
|
5
5
|
|
6
6
|
Exception.__disable_sanitized_backtrace__ = true
|
7
7
|
|
8
|
-
spin {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
}
|
8
|
+
# spin {
|
9
|
+
# 10.times {
|
10
|
+
# STDOUT << '.'
|
11
|
+
# sleep 0.1
|
12
|
+
# }
|
13
|
+
# }
|
14
14
|
|
15
15
|
puts 'going to sleep...'
|
16
16
|
sleep 1
|
17
17
|
puts 'woke up'
|
18
|
+
|
19
|
+
counter = 0
|
20
|
+
t = Polyphony::Throttler.new(5)
|
21
|
+
t.process do
|
22
|
+
p counter
|
23
|
+
counter += 1
|
24
|
+
t.stop if counter > 5
|
25
|
+
end
|
data/examples/io/xx-irb.rb
CHANGED
@@ -20,22 +20,22 @@ end
|
|
20
20
|
|
21
21
|
def handle_client(socket)
|
22
22
|
parser = Http::Parser.new
|
23
|
-
|
23
|
+
reqs = []
|
24
24
|
parser.on_message_complete = proc do |env|
|
25
|
-
|
25
|
+
reqs << Object.new # parser
|
26
26
|
end
|
27
27
|
while (data = socket.readpartial(8192)) do
|
28
28
|
parser << data
|
29
|
-
|
29
|
+
while (req = reqs.shift)
|
30
30
|
handle_request(socket, req)
|
31
31
|
req = nil
|
32
|
-
snooze
|
32
|
+
# snooze
|
33
33
|
end
|
34
34
|
end
|
35
35
|
rescue IOError, SystemCallError => e
|
36
36
|
# do nothing
|
37
37
|
ensure
|
38
|
-
socket
|
38
|
+
socket&.close
|
39
39
|
parser.reset!
|
40
40
|
end
|
41
41
|
|
@@ -46,13 +46,14 @@ def handle_request(client, parser)
|
|
46
46
|
client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
47
47
|
end
|
48
48
|
|
49
|
-
$incoming =
|
49
|
+
$incoming = Polyphony::Queue.new
|
50
50
|
|
51
51
|
$threads = (1..4).map {
|
52
52
|
Thread.new {
|
53
53
|
Thread.current.setup_fiber_scheduling
|
54
54
|
loop {
|
55
55
|
conn = $incoming.pop
|
56
|
+
puts "#{Thread.current.inspect} pop #{conn.inspect}"
|
56
57
|
spin { handle_client(conn) }
|
57
58
|
}
|
58
59
|
}
|
@@ -4,21 +4,10 @@ require 'bundler/setup'
|
|
4
4
|
require 'polyphony'
|
5
5
|
require 'http/parser'
|
6
6
|
|
7
|
-
|
8
|
-
def setup_async
|
9
|
-
self.on_message_complete = proc { @request_complete = true }
|
10
|
-
end
|
11
|
-
|
12
|
-
def parse(data)
|
13
|
-
self << data
|
14
|
-
return nil unless @request_complete
|
15
|
-
|
16
|
-
@request_complete = nil
|
17
|
-
self
|
18
|
-
end
|
19
|
-
end
|
7
|
+
$connection_count = 0
|
20
8
|
|
21
9
|
def handle_client(socket)
|
10
|
+
$connection_count += 1
|
22
11
|
parser = Http::Parser.new
|
23
12
|
reqs = []
|
24
13
|
parser.on_message_complete = proc do |env|
|
@@ -29,20 +18,20 @@ def handle_client(socket)
|
|
29
18
|
while (req = reqs.shift)
|
30
19
|
handle_request(socket, req)
|
31
20
|
req = nil
|
32
|
-
|
21
|
+
snooze
|
33
22
|
end
|
34
23
|
end
|
35
24
|
rescue IOError, SystemCallError => e
|
36
25
|
# do nothing
|
37
26
|
ensure
|
38
|
-
|
39
|
-
|
27
|
+
$connection_count -= 1
|
28
|
+
socket&.close
|
40
29
|
end
|
41
30
|
|
42
31
|
def handle_request(client, parser)
|
43
|
-
status_code = 200
|
32
|
+
status_code = "200 OK"
|
44
33
|
data = "Hello world!\n"
|
45
|
-
headers = "Content-Length: #{data.bytesize}\r\n"
|
34
|
+
headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
|
46
35
|
client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
47
36
|
end
|
48
37
|
|
@@ -53,16 +42,16 @@ spin do
|
|
53
42
|
loop do
|
54
43
|
client = server.accept
|
55
44
|
spin { handle_client(client) }
|
56
|
-
# snooze
|
57
45
|
end
|
46
|
+
ensure
|
47
|
+
server&.close
|
58
48
|
end
|
59
49
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
50
|
+
# every(1) {
|
51
|
+
# stats = Thread.current.fiber_scheduling_stats
|
52
|
+
# stats[:connection_count] = $connection_count
|
53
|
+
# puts "#{Time.now} #{stats}"
|
54
|
+
# }
|
66
55
|
|
67
56
|
puts "pid #{Process.pid}"
|
68
57
|
suspend
|
@@ -16,5 +16,5 @@ $defs << "-DHAVE_SYS_RESOURCE_H" if have_header("sys/resource.h")
|
|
16
16
|
|
17
17
|
CONFIG["optflags"] << " -fno-strict-aliasing" unless RUBY_PLATFORM =~ /mswin/
|
18
18
|
|
19
|
-
dir_config "
|
20
|
-
create_makefile "
|
19
|
+
dir_config "polyphony_ext"
|
20
|
+
create_makefile "polyphony_ext"
|
@@ -1,8 +1,7 @@
|
|
1
|
-
#include "
|
1
|
+
#include "polyphony.h"
|
2
2
|
|
3
3
|
ID ID_fiber_trace;
|
4
|
-
ID
|
5
|
-
ID ID_ivar_auto_io;
|
4
|
+
ID ID_ivar_auto_watcher;
|
6
5
|
ID ID_trace_ev_loop_enter;
|
7
6
|
ID ID_trace_ev_loop_leave;
|
8
7
|
ID ID_trace_run;
|
@@ -10,6 +9,8 @@ ID ID_trace_runnable;
|
|
10
9
|
ID ID_trace_terminate;
|
11
10
|
ID ID_trace_wait;
|
12
11
|
|
12
|
+
VALUE cEvent = Qnil;
|
13
|
+
|
13
14
|
VALUE SYM_dead;
|
14
15
|
VALUE SYM_running;
|
15
16
|
VALUE SYM_runnable;
|
@@ -32,22 +33,18 @@ static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
|
|
32
33
|
return ret;
|
33
34
|
}
|
34
35
|
|
35
|
-
inline VALUE
|
36
|
-
VALUE
|
37
|
-
if (
|
38
|
-
|
39
|
-
rb_ivar_set(self, ID_ivar_auto_async, async);
|
36
|
+
inline VALUE Fiber_auto_watcher(VALUE self) {
|
37
|
+
VALUE watcher;
|
38
|
+
if (cEvent == Qnil) {
|
39
|
+
cEvent = rb_const_get(mPolyphony, rb_intern("Event"));
|
40
40
|
}
|
41
|
-
return async;
|
42
|
-
}
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
rb_ivar_set(self, ID_ivar_auto_io, io);
|
42
|
+
watcher = rb_ivar_get(self, ID_ivar_auto_watcher);
|
43
|
+
if (watcher == Qnil) {
|
44
|
+
watcher = rb_funcall(cEvent, ID_new, 0);
|
45
|
+
rb_ivar_set(self, ID_ivar_auto_watcher, watcher);
|
49
46
|
}
|
50
|
-
return
|
47
|
+
return watcher;
|
51
48
|
}
|
52
49
|
|
53
50
|
static VALUE Fiber_schedule(int argc, VALUE *argv, VALUE self) {
|
@@ -80,12 +77,10 @@ void Fiber_make_runnable(VALUE fiber, VALUE value) {
|
|
80
77
|
|
81
78
|
void Init_Fiber() {
|
82
79
|
VALUE cFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
83
|
-
rb_define_method(cFiber, "auto_async", Fiber_auto_async, 0);
|
84
80
|
rb_define_method(cFiber, "safe_transfer", Fiber_safe_transfer, -1);
|
85
81
|
rb_define_method(cFiber, "schedule", Fiber_schedule, -1);
|
86
82
|
rb_define_method(cFiber, "state", Fiber_state, 0);
|
87
|
-
|
88
|
-
ID_ivar_auto_async = rb_intern("@auto_async");
|
83
|
+
rb_define_method(cFiber, "auto_watcher", Fiber_auto_watcher, 0);
|
89
84
|
|
90
85
|
SYM_dead = ID2SYM(rb_intern("dead"));
|
91
86
|
SYM_running = ID2SYM(rb_intern("running"));
|
@@ -97,6 +92,7 @@ void Init_Fiber() {
|
|
97
92
|
rb_global_variable(&SYM_waiting);
|
98
93
|
|
99
94
|
ID_fiber_trace = rb_intern("__fiber_trace__");
|
95
|
+
ID_ivar_auto_watcher = rb_intern("@auto_watcher");
|
100
96
|
|
101
97
|
SYM_fiber_create = ID2SYM(rb_intern("fiber_create"));
|
102
98
|
SYM_fiber_ev_loop_enter = ID2SYM(rb_intern("fiber_ev_loop_enter"));
|
File without changes
|
File without changes
|
@@ -0,0 +1,503 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
#include "../libev/ev.h"
|
3
|
+
#include <sys/socket.h>
|
4
|
+
|
5
|
+
VALUE cLibevAgent = Qnil;
|
6
|
+
VALUE cTCPSocket;
|
7
|
+
|
8
|
+
struct LibevAgent_t {
|
9
|
+
struct ev_loop *ev_loop;
|
10
|
+
struct ev_async break_async;
|
11
|
+
int running;
|
12
|
+
int run_no_wait_count;
|
13
|
+
};
|
14
|
+
|
15
|
+
static size_t LibevAgent_size(const void *ptr) {
|
16
|
+
return sizeof(struct LibevAgent_t);
|
17
|
+
}
|
18
|
+
|
19
|
+
static const rb_data_type_t LibevAgent_type = {
|
20
|
+
"Libev",
|
21
|
+
{0, 0, LibevAgent_size,},
|
22
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
23
|
+
};
|
24
|
+
|
25
|
+
static VALUE LibevAgent_allocate(VALUE klass) {
|
26
|
+
struct LibevAgent_t *agent = ALLOC(struct LibevAgent_t);
|
27
|
+
|
28
|
+
return TypedData_Wrap_Struct(klass, &LibevAgent_type, agent);
|
29
|
+
}
|
30
|
+
|
31
|
+
#define GetLibevAgent(obj, agent) \
|
32
|
+
TypedData_Get_Struct((obj), struct LibevAgent_t, &LibevAgent_type, (agent))
|
33
|
+
|
34
|
+
void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
|
35
|
+
// This callback does nothing, the break async is used solely for breaking out
|
36
|
+
// of a *blocking* event loop (waking it up) in a thread-safe, signal-safe manner
|
37
|
+
}
|
38
|
+
|
39
|
+
static VALUE LibevAgent_initialize(VALUE self) {
|
40
|
+
struct LibevAgent_t *agent;
|
41
|
+
VALUE thread = rb_thread_current();
|
42
|
+
int is_main_thread = (thread == rb_thread_main());
|
43
|
+
|
44
|
+
GetLibevAgent(self, agent);
|
45
|
+
agent->ev_loop = is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
|
46
|
+
|
47
|
+
ev_async_init(&agent->break_async, break_async_callback);
|
48
|
+
ev_async_start(agent->ev_loop, &agent->break_async);
|
49
|
+
ev_unref(agent->ev_loop); // don't count the break_async watcher
|
50
|
+
|
51
|
+
agent->running = 0;
|
52
|
+
agent->run_no_wait_count = 0;
|
53
|
+
|
54
|
+
return Qnil;
|
55
|
+
}
|
56
|
+
|
57
|
+
VALUE LibevAgent_finalize(VALUE self) {
|
58
|
+
struct LibevAgent_t *agent;
|
59
|
+
GetLibevAgent(self, agent);
|
60
|
+
|
61
|
+
ev_async_stop(agent->ev_loop, &agent->break_async);
|
62
|
+
|
63
|
+
if (!ev_is_default_loop(agent->ev_loop)) ev_loop_destroy(agent->ev_loop);
|
64
|
+
|
65
|
+
return self;
|
66
|
+
}
|
67
|
+
|
68
|
+
VALUE LibevAgent_post_fork(VALUE self) {
|
69
|
+
struct LibevAgent_t *agent;
|
70
|
+
GetLibevAgent(self, agent);
|
71
|
+
|
72
|
+
if (!ev_is_default_loop(agent->ev_loop)) {
|
73
|
+
// post_fork is called only for the main thread of the forked process. If
|
74
|
+
// the forked process was forked from a thread other than the main one,
|
75
|
+
// we remove the old non-default ev_loop and use the default one instead.
|
76
|
+
ev_loop_destroy(agent->ev_loop);
|
77
|
+
agent->ev_loop = EV_DEFAULT;
|
78
|
+
}
|
79
|
+
|
80
|
+
ev_loop_fork(agent->ev_loop);
|
81
|
+
|
82
|
+
return self;
|
83
|
+
}
|
84
|
+
|
85
|
+
VALUE LibevAgent_pending_count(VALUE self) {
|
86
|
+
int count;
|
87
|
+
struct LibevAgent_t *agent;
|
88
|
+
GetLibevAgent(self, agent);
|
89
|
+
count = ev_pending_count(agent->ev_loop);
|
90
|
+
return INT2NUM(count);
|
91
|
+
}
|
92
|
+
|
93
|
+
VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue) {
|
94
|
+
int is_nowait = nowait == Qtrue;
|
95
|
+
struct LibevAgent_t *agent;
|
96
|
+
GetLibevAgent(self, agent);
|
97
|
+
|
98
|
+
if (is_nowait) {
|
99
|
+
int runnable_count = RARRAY_LEN(queue);
|
100
|
+
agent->run_no_wait_count++;
|
101
|
+
if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
|
102
|
+
return self;
|
103
|
+
}
|
104
|
+
|
105
|
+
agent->run_no_wait_count = 0;
|
106
|
+
|
107
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
|
108
|
+
agent->running = 1;
|
109
|
+
ev_run(agent->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
|
110
|
+
agent->running = 0;
|
111
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
|
112
|
+
|
113
|
+
return self;
|
114
|
+
}
|
115
|
+
|
116
|
+
VALUE LibevAgent_break(VALUE self) {
|
117
|
+
struct LibevAgent_t *agent;
|
118
|
+
GetLibevAgent(self, agent);
|
119
|
+
|
120
|
+
if (agent->running) {
|
121
|
+
// Since the loop will run until at least one event has occurred, we signal
|
122
|
+
// the selector's associated async watcher, which will cause the ev loop to
|
123
|
+
// return. In contrast to using `ev_break` to break out of the loop, which
|
124
|
+
// should be called from the same thread (from within the ev_loop), using an
|
125
|
+
// `ev_async` allows us to interrupt the event loop across threads.
|
126
|
+
ev_async_send(agent->ev_loop, &agent->break_async);
|
127
|
+
return Qtrue;
|
128
|
+
}
|
129
|
+
|
130
|
+
return Qnil;
|
131
|
+
}
|
132
|
+
|
133
|
+
#include "polyphony.h"
|
134
|
+
#include "../libev/ev.h"
|
135
|
+
|
136
|
+
//////////////////////////////////////////////////////////////////////
|
137
|
+
//////////////////////////////////////////////////////////////////////
|
138
|
+
// the following is copied verbatim from the Ruby source code (io.c)
|
139
|
+
struct io_internal_read_struct {
|
140
|
+
int fd;
|
141
|
+
int nonblock;
|
142
|
+
void *buf;
|
143
|
+
size_t capa;
|
144
|
+
};
|
145
|
+
|
146
|
+
int io_setstrbuf(VALUE *str, long len) {
|
147
|
+
#ifdef _WIN32
|
148
|
+
len = (len + 1) & ~1L; /* round up for wide char */
|
149
|
+
#endif
|
150
|
+
if (NIL_P(*str)) {
|
151
|
+
*str = rb_str_new(0, len);
|
152
|
+
return 1;
|
153
|
+
}
|
154
|
+
else {
|
155
|
+
VALUE s = StringValue(*str);
|
156
|
+
long clen = RSTRING_LEN(s);
|
157
|
+
if (clen >= len) {
|
158
|
+
rb_str_modify(s);
|
159
|
+
return 0;
|
160
|
+
}
|
161
|
+
len -= clen;
|
162
|
+
}
|
163
|
+
rb_str_modify_expand(*str, len);
|
164
|
+
return 0;
|
165
|
+
}
|
166
|
+
|
167
|
+
#define MAX_REALLOC_GAP 4096
|
168
|
+
static void io_shrink_read_string(VALUE str, long n) {
|
169
|
+
if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) {
|
170
|
+
rb_str_resize(str, n);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
void io_set_read_length(VALUE str, long n, int shrinkable) {
|
175
|
+
if (RSTRING_LEN(str) != n) {
|
176
|
+
rb_str_modify(str);
|
177
|
+
rb_str_set_len(str, n);
|
178
|
+
if (shrinkable) io_shrink_read_string(str, n);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
static rb_encoding* io_read_encoding(rb_io_t *fptr) {
|
183
|
+
if (fptr->encs.enc) {
|
184
|
+
return fptr->encs.enc;
|
185
|
+
}
|
186
|
+
return rb_default_external_encoding();
|
187
|
+
}
|
188
|
+
|
189
|
+
VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
|
190
|
+
OBJ_TAINT(str);
|
191
|
+
rb_enc_associate(str, io_read_encoding(fptr));
|
192
|
+
return str;
|
193
|
+
}
|
194
|
+
|
195
|
+
//////////////////////////////////////////////////////////////////////
|
196
|
+
//////////////////////////////////////////////////////////////////////
|
197
|
+
|
198
|
+
struct libev_io {
|
199
|
+
struct ev_io io;
|
200
|
+
VALUE fiber;
|
201
|
+
};
|
202
|
+
|
203
|
+
static void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
|
204
|
+
{
|
205
|
+
struct libev_io *watcher = (struct libev_io *)w;
|
206
|
+
Fiber_make_runnable(watcher->fiber, Qnil);
|
207
|
+
}
|
208
|
+
|
209
|
+
VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
|
210
|
+
struct LibevAgent_t *agent;
|
211
|
+
struct libev_io watcher;
|
212
|
+
rb_io_t *fptr;
|
213
|
+
int len = NUM2INT(length);
|
214
|
+
int shrinkable = io_setstrbuf(&str, len);
|
215
|
+
char *buf = RSTRING_PTR(str);
|
216
|
+
long total = 0;
|
217
|
+
VALUE switchpoint_result = Qnil;
|
218
|
+
int read_to_eof = RTEST(to_eof);
|
219
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
220
|
+
|
221
|
+
GetLibevAgent(self, agent);
|
222
|
+
if (underlying_io != Qnil) io = underlying_io;
|
223
|
+
GetOpenFile(io, fptr);
|
224
|
+
rb_io_check_byte_readable(fptr);
|
225
|
+
rb_io_set_nonblock(fptr);
|
226
|
+
watcher.fiber = Qnil;
|
227
|
+
|
228
|
+
OBJ_TAINT(str);
|
229
|
+
|
230
|
+
while (len > 0) {
|
231
|
+
int n = read(fptr->fd, buf, len);
|
232
|
+
if (n == 0)
|
233
|
+
break;
|
234
|
+
if (n > 0) {
|
235
|
+
total = total + n;
|
236
|
+
buf += n;
|
237
|
+
len -= n;
|
238
|
+
if (!read_to_eof || (len == 0)) break;
|
239
|
+
}
|
240
|
+
else {
|
241
|
+
int e = errno;
|
242
|
+
if ((e == EWOULDBLOCK || e == EAGAIN)) {
|
243
|
+
if (watcher.fiber == Qnil) {
|
244
|
+
watcher.fiber = rb_fiber_current();
|
245
|
+
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
|
246
|
+
}
|
247
|
+
ev_io_start(agent->ev_loop, &watcher.io);
|
248
|
+
switchpoint_result = Polyphony_switchpoint();
|
249
|
+
ev_io_stop(agent->ev_loop, &watcher.io);
|
250
|
+
if (TEST_EXCEPTION(switchpoint_result))
|
251
|
+
goto error;
|
252
|
+
}
|
253
|
+
else
|
254
|
+
rb_syserr_fail(e, strerror(e));
|
255
|
+
// rb_syserr_fail_path(e, fptr->pathv);
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
if (total == 0) return Qnil;
|
260
|
+
|
261
|
+
io_set_read_length(str, total, shrinkable);
|
262
|
+
io_enc_str(str, fptr);
|
263
|
+
|
264
|
+
RB_GC_GUARD(watcher.fiber);
|
265
|
+
RB_GC_GUARD(switchpoint_result);
|
266
|
+
|
267
|
+
return str;
|
268
|
+
error:
|
269
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
270
|
+
}
|
271
|
+
|
272
|
+
VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
273
|
+
struct LibevAgent_t *agent;
|
274
|
+
struct libev_io watcher;
|
275
|
+
rb_io_t *fptr;
|
276
|
+
VALUE switchpoint_result = Qnil;
|
277
|
+
|
278
|
+
char *buf = StringValuePtr(str);
|
279
|
+
int len = RSTRING_LEN(str);
|
280
|
+
int left = len;
|
281
|
+
|
282
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
283
|
+
if (underlying_io != Qnil) io = underlying_io;
|
284
|
+
GetLibevAgent(self, agent);
|
285
|
+
io = rb_io_get_write_io(io);
|
286
|
+
GetOpenFile(io, fptr);
|
287
|
+
watcher.fiber = Qnil;
|
288
|
+
|
289
|
+
while (left > 0) {
|
290
|
+
int result = write(fptr->fd, buf, left);
|
291
|
+
if (result < 0) {
|
292
|
+
if (errno == EAGAIN) {
|
293
|
+
if (watcher.fiber == Qnil) {
|
294
|
+
watcher.fiber = rb_fiber_current();
|
295
|
+
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_WRITE);
|
296
|
+
}
|
297
|
+
ev_io_start(agent->ev_loop, &watcher.io);
|
298
|
+
switchpoint_result = Polyphony_switchpoint();
|
299
|
+
ev_io_stop(agent->ev_loop, &watcher.io);
|
300
|
+
if (TEST_EXCEPTION(switchpoint_result))
|
301
|
+
goto error;
|
302
|
+
}
|
303
|
+
else {
|
304
|
+
// report error
|
305
|
+
|
306
|
+
}
|
307
|
+
}
|
308
|
+
else {
|
309
|
+
buf += result;
|
310
|
+
left -= result;
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
RB_GC_GUARD(watcher.fiber);
|
315
|
+
RB_GC_GUARD(switchpoint_result);
|
316
|
+
|
317
|
+
return INT2NUM(len);
|
318
|
+
error:
|
319
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
320
|
+
}
|
321
|
+
|
322
|
+
///////////////////////////////////////////////////////////////////////////
|
323
|
+
|
324
|
+
struct rsock_send_arg {
|
325
|
+
int fd, flags;
|
326
|
+
VALUE mesg;
|
327
|
+
struct sockaddr *to;
|
328
|
+
socklen_t tolen;
|
329
|
+
};
|
330
|
+
|
331
|
+
#define StringValue(v) rb_string_value(&(v))
|
332
|
+
#define IS_ADDRINFO(obj) rb_typeddata_is_kind_of((obj), &addrinfo_type)
|
333
|
+
|
334
|
+
VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
335
|
+
struct LibevAgent_t *agent;
|
336
|
+
struct libev_io watcher;
|
337
|
+
rb_io_t *fptr;
|
338
|
+
int fd;
|
339
|
+
struct sockaddr addr;
|
340
|
+
socklen_t len = (socklen_t)sizeof addr;
|
341
|
+
VALUE switchpoint_result = Qnil;
|
342
|
+
|
343
|
+
GetLibevAgent(self, agent);
|
344
|
+
GetOpenFile(sock, fptr);
|
345
|
+
rb_io_set_nonblock(fptr);
|
346
|
+
watcher.fiber = Qnil;
|
347
|
+
while (1) {
|
348
|
+
fd = accept(fptr->fd, &addr, &len);
|
349
|
+
if (fd < 0) {
|
350
|
+
int e = errno;
|
351
|
+
if (e == EWOULDBLOCK || e == EAGAIN) {
|
352
|
+
if (watcher.fiber == Qnil) {
|
353
|
+
watcher.fiber = rb_fiber_current();
|
354
|
+
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
|
355
|
+
}
|
356
|
+
ev_io_start(agent->ev_loop, &watcher.io);
|
357
|
+
switchpoint_result = Polyphony_switchpoint();
|
358
|
+
ev_io_stop(agent->ev_loop, &watcher.io);
|
359
|
+
|
360
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
361
|
+
RB_GC_GUARD(watcher.fiber);
|
362
|
+
RB_GC_GUARD(switchpoint_result);
|
363
|
+
}
|
364
|
+
else
|
365
|
+
rb_syserr_fail(e, strerror(e));
|
366
|
+
// rb_syserr_fail_path(e, fptr->pathv);
|
367
|
+
}
|
368
|
+
else {
|
369
|
+
VALUE connection = rb_obj_alloc(cTCPSocket);
|
370
|
+
rb_io_t *fp;
|
371
|
+
MakeOpenFile(connection, fp);
|
372
|
+
rb_update_max_fd(fd);
|
373
|
+
fp->fd = fd;
|
374
|
+
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
375
|
+
rb_io_ascii8bit_binmode(connection);
|
376
|
+
rb_io_set_nonblock(fp);
|
377
|
+
rb_io_synchronized(fp);
|
378
|
+
// if (rsock_do_not_reverse_lookup) {
|
379
|
+
// fp->mode |= FMODE_NOREVLOOKUP;
|
380
|
+
// }
|
381
|
+
|
382
|
+
return connection;
|
383
|
+
}
|
384
|
+
}
|
385
|
+
return Qnil;
|
386
|
+
}
|
387
|
+
|
388
|
+
VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
|
389
|
+
struct LibevAgent_t *agent;
|
390
|
+
struct libev_io watcher;
|
391
|
+
rb_io_t *fptr;
|
392
|
+
VALUE switchpoint_result = Qnil;
|
393
|
+
int events = RTEST(write) ? EV_WRITE : EV_READ;
|
394
|
+
|
395
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
396
|
+
GetLibevAgent(self, agent);
|
397
|
+
if (underlying_io != Qnil) io = underlying_io;
|
398
|
+
GetOpenFile(io, fptr);
|
399
|
+
|
400
|
+
watcher.fiber = rb_fiber_current();
|
401
|
+
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
|
402
|
+
ev_io_start(agent->ev_loop, &watcher.io);
|
403
|
+
switchpoint_result = Polyphony_switchpoint();
|
404
|
+
ev_io_stop(agent->ev_loop, &watcher.io);
|
405
|
+
|
406
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
407
|
+
RB_GC_GUARD(watcher.fiber);
|
408
|
+
RB_GC_GUARD(switchpoint_result);
|
409
|
+
return switchpoint_result;
|
410
|
+
}
|
411
|
+
|
412
|
+
struct libev_timer {
|
413
|
+
struct ev_timer timer;
|
414
|
+
VALUE fiber;
|
415
|
+
};
|
416
|
+
|
417
|
+
static void LibevAgent_timer_callback(EV_P_ ev_timer *w, int revents)
|
418
|
+
{
|
419
|
+
struct libev_timer *watcher = (struct libev_timer *)w;
|
420
|
+
Fiber_make_runnable(watcher->fiber, Qnil);
|
421
|
+
}
|
422
|
+
|
423
|
+
VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
|
424
|
+
struct LibevAgent_t *agent;
|
425
|
+
struct libev_timer watcher;
|
426
|
+
VALUE switchpoint_result = Qnil;
|
427
|
+
|
428
|
+
GetLibevAgent(self, agent);
|
429
|
+
watcher.fiber = rb_fiber_current();
|
430
|
+
ev_timer_init(&watcher.timer, LibevAgent_timer_callback, NUM2DBL(duration), 0.);
|
431
|
+
ev_timer_start(agent->ev_loop, &watcher.timer);
|
432
|
+
|
433
|
+
switchpoint_result = Polyphony_switchpoint();
|
434
|
+
ev_timer_stop(agent->ev_loop, &watcher.timer);
|
435
|
+
|
436
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
437
|
+
RB_GC_GUARD(watcher.fiber);
|
438
|
+
RB_GC_GUARD(switchpoint_result);
|
439
|
+
return switchpoint_result;
|
440
|
+
}
|
441
|
+
|
442
|
+
struct libev_child {
|
443
|
+
struct ev_child child;
|
444
|
+
VALUE fiber;
|
445
|
+
};
|
446
|
+
|
447
|
+
static void LibevAgent_child_callback(EV_P_ ev_child *w, int revents)
|
448
|
+
{
|
449
|
+
struct libev_child *watcher = (struct libev_child *)w;
|
450
|
+
int exit_status = w->rstatus >> 8; // weird, why should we do this?
|
451
|
+
VALUE status;
|
452
|
+
|
453
|
+
status = rb_ary_new_from_args(2, INT2NUM(w->rpid), INT2NUM(exit_status));
|
454
|
+
Fiber_make_runnable(watcher->fiber, status);
|
455
|
+
}
|
456
|
+
|
457
|
+
VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
|
458
|
+
struct LibevAgent_t *agent;
|
459
|
+
struct libev_child watcher;
|
460
|
+
VALUE switchpoint_result = Qnil;
|
461
|
+
GetLibevAgent(self, agent);
|
462
|
+
|
463
|
+
watcher.fiber = rb_fiber_current();
|
464
|
+
ev_child_init(&watcher.child, LibevAgent_child_callback, NUM2INT(pid), 0);
|
465
|
+
ev_child_start(agent->ev_loop, &watcher.child);
|
466
|
+
|
467
|
+
switchpoint_result = Polyphony_switchpoint();
|
468
|
+
ev_child_stop(agent->ev_loop, &watcher.child);
|
469
|
+
|
470
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
471
|
+
RB_GC_GUARD(watcher.fiber);
|
472
|
+
RB_GC_GUARD(switchpoint_result);
|
473
|
+
return switchpoint_result;
|
474
|
+
}
|
475
|
+
|
476
|
+
struct ev_loop *LibevAgent_ev_loop(VALUE self) {
|
477
|
+
struct LibevAgent_t *agent;
|
478
|
+
GetLibevAgent(self, agent);
|
479
|
+
return agent->ev_loop;
|
480
|
+
}
|
481
|
+
|
482
|
+
void Init_LibevAgent() {
|
483
|
+
rb_require("socket");
|
484
|
+
cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
485
|
+
|
486
|
+
cLibevAgent = rb_define_class_under(mPolyphony, "LibevAgent", rb_cData);
|
487
|
+
rb_define_alloc_func(cLibevAgent, LibevAgent_allocate);
|
488
|
+
|
489
|
+
rb_define_method(cLibevAgent, "initialize", LibevAgent_initialize, 0);
|
490
|
+
rb_define_method(cLibevAgent, "finalize", LibevAgent_finalize, 0);
|
491
|
+
rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
|
492
|
+
rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
|
493
|
+
|
494
|
+
rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
|
495
|
+
rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
|
496
|
+
|
497
|
+
rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
|
498
|
+
rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
|
499
|
+
rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
|
500
|
+
rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
|
501
|
+
rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
|
502
|
+
rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
|
503
|
+
}
|