polyphony 0.40 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +11 -2
  3. data/.gitignore +2 -2
  4. data/.rubocop.yml +30 -0
  5. data/CHANGELOG.md +6 -2
  6. data/Gemfile.lock +9 -6
  7. data/Rakefile +2 -2
  8. data/TODO.md +18 -97
  9. data/docs/_includes/head.html +40 -0
  10. data/docs/_includes/nav.html +5 -5
  11. data/docs/api-reference/fiber.md +2 -2
  12. data/docs/main-concepts/design-principles.md +67 -9
  13. data/docs/main-concepts/extending.md +1 -1
  14. data/examples/core/xx-agent.rb +102 -0
  15. data/examples/core/xx-sleeping.rb +14 -6
  16. data/examples/io/xx-irb.rb +1 -1
  17. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
  19. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  20. data/ext/{gyro → polyphony}/fiber.c +15 -19
  21. data/ext/{gyro → polyphony}/libev.c +0 -0
  22. data/ext/{gyro → polyphony}/libev.h +0 -0
  23. data/ext/polyphony/libev_agent.c +503 -0
  24. data/ext/polyphony/libev_queue.c +214 -0
  25. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -25
  26. data/ext/polyphony/polyphony.h +90 -0
  27. data/ext/polyphony/polyphony_ext.c +23 -0
  28. data/ext/{gyro → polyphony}/socket.c +14 -14
  29. data/ext/{gyro → polyphony}/thread.c +32 -115
  30. data/ext/{gyro → polyphony}/tracing.c +1 -1
  31. data/lib/polyphony.rb +16 -12
  32. data/lib/polyphony/adapters/irb.rb +1 -1
  33. data/lib/polyphony/adapters/postgres.rb +6 -5
  34. data/lib/polyphony/adapters/process.rb +5 -5
  35. data/lib/polyphony/adapters/trace.rb +28 -28
  36. data/lib/polyphony/core/channel.rb +3 -3
  37. data/lib/polyphony/core/exceptions.rb +1 -1
  38. data/lib/polyphony/core/global_api.rb +11 -9
  39. data/lib/polyphony/core/resource_pool.rb +3 -3
  40. data/lib/polyphony/core/sync.rb +2 -2
  41. data/lib/polyphony/core/thread_pool.rb +6 -6
  42. data/lib/polyphony/core/throttler.rb +13 -6
  43. data/lib/polyphony/event.rb +27 -0
  44. data/lib/polyphony/extensions/core.rb +20 -11
  45. data/lib/polyphony/extensions/fiber.rb +4 -4
  46. data/lib/polyphony/extensions/io.rb +56 -26
  47. data/lib/polyphony/extensions/openssl.rb +4 -8
  48. data/lib/polyphony/extensions/socket.rb +27 -9
  49. data/lib/polyphony/extensions/thread.rb +16 -9
  50. data/lib/polyphony/net.rb +9 -9
  51. data/lib/polyphony/version.rb +1 -1
  52. data/polyphony.gemspec +2 -2
  53. data/test/helper.rb +12 -1
  54. data/test/test_agent.rb +77 -0
  55. data/test/{test_async.rb → test_event.rb} +13 -7
  56. data/test/test_ext.rb +25 -4
  57. data/test/test_fiber.rb +19 -10
  58. data/test/test_global_api.rb +4 -4
  59. data/test/test_io.rb +46 -24
  60. data/test/test_queue.rb +74 -0
  61. data/test/test_signal.rb +3 -40
  62. data/test/test_socket.rb +33 -0
  63. data/test/test_thread.rb +37 -16
  64. data/test/test_trace.rb +6 -5
  65. metadata +24 -24
  66. data/ext/gyro/async.c +0 -132
  67. data/ext/gyro/child.c +0 -108
  68. data/ext/gyro/gyro.h +0 -158
  69. data/ext/gyro/gyro_ext.c +0 -33
  70. data/ext/gyro/io.c +0 -457
  71. data/ext/gyro/queue.c +0 -146
  72. data/ext/gyro/selector.c +0 -205
  73. data/ext/gyro/signal.c +0 -99
  74. data/ext/gyro/timer.c +0 -115
  75. data/test/test_timer.rb +0 -56
@@ -5,7 +5,7 @@ nav_order: 4
5
5
  parent: Main Concepts
6
6
  permalink: /main-concepts/extending/
7
7
  prev_title: Exception Handling
8
- next_title: Design Principles
8
+ next_title: The Design of Polyphony
9
9
  ---
10
10
  # Extending Polyphony
11
11
 
@@ -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.times {
10
- STDOUT << '.'
11
- sleep 0.1
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'irb'
5
- require 'polyphony/irb'
5
+ require 'polyphony/adapters/irb'
6
6
 
7
7
  $counter = 0
8
8
  timer = spin do
@@ -20,22 +20,22 @@ end
20
20
 
21
21
  def handle_client(socket)
22
22
  parser = Http::Parser.new
23
- req = nil
23
+ reqs = []
24
24
  parser.on_message_complete = proc do |env|
25
- req = parser
25
+ reqs << Object.new # parser
26
26
  end
27
27
  while (data = socket.readpartial(8192)) do
28
28
  parser << data
29
- if req
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.close rescue nil
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 = Gyro::Queue.new
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
- class Http::Parser
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
- # snooze
21
+ snooze
33
22
  end
34
23
  end
35
24
  rescue IOError, SystemCallError => e
36
25
  # do nothing
37
26
  ensure
38
- socket.close rescue nil
39
- parser.reset!
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
- spin do
61
- loop do
62
- sleep 1
63
- puts "#{Time.now} #{Thread.current.fiber_scheduling_stats}"
64
- end
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 "gyro_ext"
20
- create_makefile "gyro_ext"
19
+ dir_config "polyphony_ext"
20
+ create_makefile "polyphony_ext"
@@ -1,8 +1,7 @@
1
- #include "gyro.h"
1
+ #include "polyphony.h"
2
2
 
3
3
  ID ID_fiber_trace;
4
- ID ID_ivar_auto_async;
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 Fiber_auto_async(VALUE self) {
36
- VALUE async = rb_ivar_get(self, ID_ivar_auto_async);
37
- if (async == Qnil) {
38
- async = rb_funcall(cGyro_Async, ID_new, 0);
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
- inline VALUE Fiber_auto_io(VALUE self) {
45
- VALUE io = rb_ivar_get(self, ID_ivar_auto_io);
46
- if (io == Qnil) {
47
- io = rb_funcall(cGyro_IO, ID_new, 2, Qnil, Qnil);
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 io;
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
+ }