polyphony 0.38 → 0.43

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.
Files changed (112) 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 +25 -2
  6. data/Gemfile.lock +15 -12
  7. data/README.md +2 -1
  8. data/Rakefile +3 -3
  9. data/TODO.md +27 -97
  10. data/docs/_config.yml +56 -7
  11. data/docs/_sass/custom/custom.scss +0 -30
  12. data/docs/_sass/overrides.scss +0 -46
  13. data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
  14. data/docs/_user-guide/index.md +9 -0
  15. data/docs/{user-guide → _user-guide}/web-server.md +0 -0
  16. data/docs/api-reference/fiber.md +2 -2
  17. data/docs/api-reference/index.md +9 -0
  18. data/docs/api-reference/polyphony-process.md +1 -1
  19. data/docs/api-reference/thread.md +1 -1
  20. data/docs/faq.md +21 -11
  21. data/docs/getting-started/index.md +10 -0
  22. data/docs/getting-started/installing.md +2 -6
  23. data/docs/getting-started/overview.md +486 -0
  24. data/docs/getting-started/tutorial.md +27 -19
  25. data/docs/index.md +1 -1
  26. data/docs/main-concepts/concurrency.md +0 -5
  27. data/docs/main-concepts/design-principles.md +69 -21
  28. data/docs/main-concepts/extending.md +1 -1
  29. data/docs/main-concepts/index.md +9 -0
  30. data/examples/core/01-spinning-up-fibers.rb +1 -0
  31. data/examples/core/03-interrupting.rb +4 -1
  32. data/examples/core/04-handling-signals.rb +19 -0
  33. data/examples/core/xx-agent.rb +102 -0
  34. data/examples/core/xx-sleeping.rb +14 -6
  35. data/examples/io/tunnel.rb +48 -0
  36. data/examples/io/xx-irb.rb +1 -1
  37. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  38. data/examples/performance/thread-vs-fiber/polyphony_server.rb +13 -36
  39. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  40. data/examples/performance/xx-array.rb +11 -0
  41. data/examples/performance/xx-fiber-switch.rb +9 -0
  42. data/examples/performance/xx-snooze.rb +15 -0
  43. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  44. data/ext/{gyro → polyphony}/fiber.c +17 -23
  45. data/ext/{gyro → polyphony}/libev.c +0 -0
  46. data/ext/{gyro → polyphony}/libev.h +0 -0
  47. data/ext/polyphony/libev_agent.c +718 -0
  48. data/ext/polyphony/libev_queue.c +216 -0
  49. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -40
  50. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +19 -39
  51. data/ext/polyphony/polyphony_ext.c +23 -0
  52. data/ext/{gyro → polyphony}/socket.c +21 -18
  53. data/ext/polyphony/thread.c +206 -0
  54. data/ext/{gyro → polyphony}/tracing.c +1 -1
  55. data/lib/polyphony.rb +19 -14
  56. data/lib/polyphony/adapters/irb.rb +1 -1
  57. data/lib/polyphony/adapters/postgres.rb +6 -5
  58. data/lib/polyphony/adapters/process.rb +5 -5
  59. data/lib/polyphony/adapters/trace.rb +28 -28
  60. data/lib/polyphony/core/channel.rb +3 -3
  61. data/lib/polyphony/core/exceptions.rb +1 -1
  62. data/lib/polyphony/core/global_api.rb +13 -11
  63. data/lib/polyphony/core/resource_pool.rb +3 -3
  64. data/lib/polyphony/core/sync.rb +2 -2
  65. data/lib/polyphony/core/thread_pool.rb +6 -6
  66. data/lib/polyphony/core/throttler.rb +13 -6
  67. data/lib/polyphony/event.rb +27 -0
  68. data/lib/polyphony/extensions/core.rb +22 -14
  69. data/lib/polyphony/extensions/fiber.rb +4 -4
  70. data/lib/polyphony/extensions/io.rb +59 -25
  71. data/lib/polyphony/extensions/openssl.rb +36 -16
  72. data/lib/polyphony/extensions/socket.rb +27 -9
  73. data/lib/polyphony/extensions/thread.rb +16 -9
  74. data/lib/polyphony/net.rb +9 -9
  75. data/lib/polyphony/version.rb +1 -1
  76. data/polyphony.gemspec +4 -4
  77. data/test/helper.rb +14 -1
  78. data/test/test_agent.rb +124 -0
  79. data/test/{test_async.rb → test_event.rb} +15 -7
  80. data/test/test_ext.rb +25 -4
  81. data/test/test_fiber.rb +19 -10
  82. data/test/test_global_api.rb +4 -4
  83. data/test/test_io.rb +46 -24
  84. data/test/test_queue.rb +74 -0
  85. data/test/test_signal.rb +3 -40
  86. data/test/test_socket.rb +34 -0
  87. data/test/test_thread.rb +37 -16
  88. data/test/test_trace.rb +6 -5
  89. metadata +40 -43
  90. data/docs/_includes/nav.html +0 -51
  91. data/docs/_includes/prevnext.html +0 -17
  92. data/docs/_layouts/default.html +0 -106
  93. data/docs/api-reference.md +0 -11
  94. data/docs/api-reference/gyro-async.md +0 -57
  95. data/docs/api-reference/gyro-child.md +0 -29
  96. data/docs/api-reference/gyro-queue.md +0 -44
  97. data/docs/api-reference/gyro-timer.md +0 -51
  98. data/docs/api-reference/gyro.md +0 -25
  99. data/docs/getting-started.md +0 -10
  100. data/docs/main-concepts.md +0 -10
  101. data/docs/user-guide.md +0 -10
  102. data/examples/core/forever_sleep.rb +0 -19
  103. data/ext/gyro/async.c +0 -162
  104. data/ext/gyro/child.c +0 -141
  105. data/ext/gyro/gyro_ext.c +0 -33
  106. data/ext/gyro/io.c +0 -489
  107. data/ext/gyro/queue.c +0 -142
  108. data/ext/gyro/selector.c +0 -228
  109. data/ext/gyro/signal.c +0 -133
  110. data/ext/gyro/thread.c +0 -308
  111. data/ext/gyro/timer.c +0 -149
  112. data/test/test_timer.rb +0 -56
@@ -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,40 +18,28 @@ 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
 
49
- spin do
50
- server = TCPServer.open('0.0.0.0', 1234)
51
- puts "listening on port 1234"
52
-
53
- loop do
54
- client = server.accept
55
- spin { handle_client(client) }
56
- # snooze
57
- end
58
- end
38
+ server = TCPServer.open('0.0.0.0', 1234)
39
+ puts "pid #{Process.pid}"
40
+ puts "listening on port 1234"
59
41
 
60
- spin do
61
- loop do
62
- sleep 1
63
- puts "#{Time.now} #{Thread.current.fiber_scheduling_stats}"
64
- end
42
+ loop do
43
+ client = server.accept
44
+ spin { handle_client(client) }
65
45
  end
66
-
67
- puts "pid #{Process.pid}"
68
- suspend
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'http/parser'
6
+
7
+ $connection_count = 0
8
+
9
+ def handle_client(socket)
10
+ $connection_count += 1
11
+ parser = Http::Parser.new
12
+ reqs = []
13
+ parser.on_message_complete = proc do |env|
14
+ reqs << Object.new # parser
15
+ end
16
+ socket.read_loop do |data|
17
+ parser << data
18
+ while (req = reqs.shift)
19
+ handle_request(socket, req)
20
+ end
21
+ end
22
+ rescue IOError, SystemCallError => e
23
+ # do nothing
24
+ ensure
25
+ $connection_count -= 1
26
+ socket&.close
27
+ end
28
+
29
+ def handle_request(client, parser)
30
+ status_code = "200 OK"
31
+ data = "Hello world!\n"
32
+ headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
33
+ client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
34
+ end
35
+
36
+ spin do
37
+ server = TCPServer.open('0.0.0.0', 1234)
38
+ puts "listening on port 1234"
39
+
40
+ Thread.current.agent.accept_loop(server) do |client|
41
+ spin { handle_client(client) }
42
+ end
43
+ # loop do
44
+ # client = server.accept
45
+ # spin { handle_client(client) }
46
+ # end
47
+ ensure
48
+ server&.close
49
+ end
50
+
51
+ # every(1) {
52
+ # stats = Thread.current.fiber_scheduling_stats
53
+ # stats[:connection_count] = $connection_count
54
+ # puts "#{Time.now} #{stats}"
55
+ # }
56
+
57
+ puts "pid #{Process.pid}"
58
+ suspend
@@ -0,0 +1,11 @@
1
+ X = ARGV[0] ? ARGV[0].to_i : 10
2
+ a = (1..X).to_a
3
+
4
+ Y = 1_000_000
5
+ t0 = Time.now
6
+ Y.times do
7
+ i = a.shift
8
+ a.push i
9
+ end
10
+
11
+ puts "rate: #{Y / (Time.now - t0)}"
@@ -0,0 +1,9 @@
1
+ X = 1_000_000
2
+ f = Fiber.new do
3
+ loop { Fiber.yield }
4
+ end
5
+
6
+ t0 = Time.now
7
+ X.times { f.resume }
8
+ dt = Time.now - t0
9
+ puts "#{X / dt.to_f}/s"
@@ -0,0 +1,15 @@
1
+ require 'bundler/setup'
2
+ require 'polyphony'
3
+
4
+ Y = ARGV[0] ? ARGV[0].to_i : 1
5
+
6
+ count = 0
7
+ Y.times do
8
+ spin { loop { count += 1; snooze } }
9
+ end
10
+
11
+ t0 = Time.now
12
+ sleep 10
13
+ elapsed = Time.now - t0
14
+ rate = count / elapsed
15
+ puts "concurrency: #{Y} rate: #{rate} switchpoints per second"
@@ -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;
@@ -26,28 +27,24 @@ VALUE SYM_fiber_terminate;
26
27
  static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
27
28
  VALUE arg = (argc == 0) ? Qnil : argv[0];
28
29
  VALUE ret = rb_funcall(self, ID_transfer, 1, arg);
29
-
30
+
30
31
  TEST_RESUME_EXCEPTION(ret);
31
32
  RB_GC_GUARD(ret);
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) {
@@ -61,7 +58,7 @@ static VALUE Fiber_state(VALUE self) {
61
58
  return SYM_dead;
62
59
  if (rb_fiber_current() == self) return SYM_running;
63
60
  if (rb_ivar_get(self, ID_runnable) != Qnil) return SYM_runnable;
64
-
61
+
65
62
  return SYM_waiting;
66
63
  }
67
64
 
@@ -72,19 +69,15 @@ void Fiber_make_runnable(VALUE fiber, VALUE value) {
72
69
  }
73
70
  else {
74
71
  rb_warn("No thread set for fiber (fiber, value, caller):");
75
- VALUE caller = rb_funcall(rb_cObject, rb_intern("caller"), 0);
76
- INSPECT(3, fiber, value, caller);
77
72
  }
78
73
  }
79
74
 
80
75
  void Init_Fiber() {
81
76
  VALUE cFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
82
- rb_define_method(cFiber, "auto_async", Fiber_auto_async, 0);
83
77
  rb_define_method(cFiber, "safe_transfer", Fiber_safe_transfer, -1);
84
78
  rb_define_method(cFiber, "schedule", Fiber_schedule, -1);
85
79
  rb_define_method(cFiber, "state", Fiber_state, 0);
86
-
87
- ID_ivar_auto_async = rb_intern("@auto_async");
80
+ rb_define_method(cFiber, "auto_watcher", Fiber_auto_watcher, 0);
88
81
 
89
82
  SYM_dead = ID2SYM(rb_intern("dead"));
90
83
  SYM_running = ID2SYM(rb_intern("running"));
@@ -96,6 +89,7 @@ void Init_Fiber() {
96
89
  rb_global_variable(&SYM_waiting);
97
90
 
98
91
  ID_fiber_trace = rb_intern("__fiber_trace__");
92
+ ID_ivar_auto_watcher = rb_intern("@auto_watcher");
99
93
 
100
94
  SYM_fiber_create = ID2SYM(rb_intern("fiber_create"));
101
95
  SYM_fiber_ev_loop_enter = ID2SYM(rb_intern("fiber_ev_loop_enter"));
File without changes
File without changes
@@ -0,0 +1,718 @@
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 ref_count;
13
+ int run_no_wait_count;
14
+ };
15
+
16
+ static size_t LibevAgent_size(const void *ptr) {
17
+ return sizeof(struct LibevAgent_t);
18
+ }
19
+
20
+ static const rb_data_type_t LibevAgent_type = {
21
+ "Libev",
22
+ {0, 0, LibevAgent_size,},
23
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
24
+ };
25
+
26
+ static VALUE LibevAgent_allocate(VALUE klass) {
27
+ struct LibevAgent_t *agent = ALLOC(struct LibevAgent_t);
28
+
29
+ return TypedData_Wrap_Struct(klass, &LibevAgent_type, agent);
30
+ }
31
+
32
+ #define GetLibevAgent(obj, agent) \
33
+ TypedData_Get_Struct((obj), struct LibevAgent_t, &LibevAgent_type, (agent))
34
+
35
+ void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
36
+ // This callback does nothing, the break async is used solely for breaking out
37
+ // of a *blocking* event loop (waking it up) in a thread-safe, signal-safe manner
38
+ }
39
+
40
+ static VALUE LibevAgent_initialize(VALUE self) {
41
+ struct LibevAgent_t *agent;
42
+ VALUE thread = rb_thread_current();
43
+ int is_main_thread = (thread == rb_thread_main());
44
+
45
+ GetLibevAgent(self, agent);
46
+ agent->ev_loop = is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
47
+
48
+ ev_async_init(&agent->break_async, break_async_callback);
49
+ ev_async_start(agent->ev_loop, &agent->break_async);
50
+ ev_unref(agent->ev_loop); // don't count the break_async watcher
51
+
52
+ agent->running = 0;
53
+ agent->ref_count = 0;
54
+ agent->run_no_wait_count = 0;
55
+
56
+ return Qnil;
57
+ }
58
+
59
+ VALUE LibevAgent_finalize(VALUE self) {
60
+ struct LibevAgent_t *agent;
61
+ GetLibevAgent(self, agent);
62
+
63
+ ev_async_stop(agent->ev_loop, &agent->break_async);
64
+
65
+ if (!ev_is_default_loop(agent->ev_loop)) ev_loop_destroy(agent->ev_loop);
66
+
67
+ return self;
68
+ }
69
+
70
+ VALUE LibevAgent_post_fork(VALUE self) {
71
+ struct LibevAgent_t *agent;
72
+ GetLibevAgent(self, agent);
73
+
74
+ if (!ev_is_default_loop(agent->ev_loop)) {
75
+ // post_fork is called only for the main thread of the forked process. If
76
+ // the forked process was forked from a thread other than the main one,
77
+ // we remove the old non-default ev_loop and use the default one instead.
78
+ ev_loop_destroy(agent->ev_loop);
79
+ agent->ev_loop = EV_DEFAULT;
80
+ }
81
+
82
+ ev_loop_fork(agent->ev_loop);
83
+
84
+ return self;
85
+ }
86
+
87
+ VALUE LibevAgent_ref(VALUE self) {
88
+ struct LibevAgent_t *agent;
89
+ GetLibevAgent(self, agent);
90
+
91
+ agent->ref_count++;
92
+ return self;
93
+ }
94
+
95
+ VALUE LibevAgent_unref(VALUE self) {
96
+ struct LibevAgent_t *agent;
97
+ GetLibevAgent(self, agent);
98
+
99
+ agent->ref_count--;
100
+ return self;
101
+ }
102
+
103
+ int LibevAgent_ref_count(VALUE self) {
104
+ struct LibevAgent_t *agent;
105
+ GetLibevAgent(self, agent);
106
+
107
+ return agent->ref_count;
108
+ }
109
+
110
+ void LibevAgent_reset_ref_count(VALUE self) {
111
+ struct LibevAgent_t *agent;
112
+ GetLibevAgent(self, agent);
113
+
114
+ agent->ref_count = 0;
115
+ }
116
+
117
+ VALUE LibevAgent_pending_count(VALUE self) {
118
+ int count;
119
+ struct LibevAgent_t *agent;
120
+ GetLibevAgent(self, agent);
121
+ count = ev_pending_count(agent->ev_loop);
122
+ return INT2NUM(count);
123
+ }
124
+
125
+ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue) {
126
+ int is_nowait = nowait == Qtrue;
127
+ struct LibevAgent_t *agent;
128
+ GetLibevAgent(self, agent);
129
+
130
+ if (is_nowait) {
131
+ int runnable_count = RARRAY_LEN(queue);
132
+ agent->run_no_wait_count++;
133
+ if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
134
+ return self;
135
+ }
136
+
137
+ agent->run_no_wait_count = 0;
138
+
139
+ FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
140
+ agent->running = 1;
141
+ ev_run(agent->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
142
+ agent->running = 0;
143
+ FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
144
+
145
+ return self;
146
+ }
147
+
148
+ VALUE LibevAgent_break(VALUE self) {
149
+ struct LibevAgent_t *agent;
150
+ GetLibevAgent(self, agent);
151
+
152
+ if (agent->running) {
153
+ // Since the loop will run until at least one event has occurred, we signal
154
+ // the selector's associated async watcher, which will cause the ev loop to
155
+ // return. In contrast to using `ev_break` to break out of the loop, which
156
+ // should be called from the same thread (from within the ev_loop), using an
157
+ // `ev_async` allows us to interrupt the event loop across threads.
158
+ ev_async_send(agent->ev_loop, &agent->break_async);
159
+ return Qtrue;
160
+ }
161
+
162
+ return Qnil;
163
+ }
164
+
165
+ #include "polyphony.h"
166
+ #include "../libev/ev.h"
167
+
168
+ //////////////////////////////////////////////////////////////////////
169
+ //////////////////////////////////////////////////////////////////////
170
+ // the following is copied verbatim from the Ruby source code (io.c)
171
+ struct io_internal_read_struct {
172
+ int fd;
173
+ int nonblock;
174
+ void *buf;
175
+ size_t capa;
176
+ };
177
+
178
+ #define StringValue(v) rb_string_value(&(v))
179
+
180
+ int io_setstrbuf(VALUE *str, long len) {
181
+ #ifdef _WIN32
182
+ len = (len + 1) & ~1L; /* round up for wide char */
183
+ #endif
184
+ if (NIL_P(*str)) {
185
+ *str = rb_str_new(0, len);
186
+ return 1;
187
+ }
188
+ else {
189
+ VALUE s = StringValue(*str);
190
+ long clen = RSTRING_LEN(s);
191
+ if (clen >= len) {
192
+ rb_str_modify(s);
193
+ return 0;
194
+ }
195
+ len -= clen;
196
+ }
197
+ rb_str_modify_expand(*str, len);
198
+ return 0;
199
+ }
200
+
201
+ #define MAX_REALLOC_GAP 4096
202
+ static void io_shrink_read_string(VALUE str, long n) {
203
+ if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) {
204
+ rb_str_resize(str, n);
205
+ }
206
+ }
207
+
208
+ void io_set_read_length(VALUE str, long n, int shrinkable) {
209
+ if (RSTRING_LEN(str) != n) {
210
+ rb_str_modify(str);
211
+ rb_str_set_len(str, n);
212
+ if (shrinkable) io_shrink_read_string(str, n);
213
+ }
214
+ }
215
+
216
+ static rb_encoding* io_read_encoding(rb_io_t *fptr) {
217
+ if (fptr->encs.enc) {
218
+ return fptr->encs.enc;
219
+ }
220
+ return rb_default_external_encoding();
221
+ }
222
+
223
+ VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
224
+ OBJ_TAINT(str);
225
+ rb_enc_associate(str, io_read_encoding(fptr));
226
+ return str;
227
+ }
228
+
229
+ //////////////////////////////////////////////////////////////////////
230
+ //////////////////////////////////////////////////////////////////////
231
+
232
+ struct libev_io {
233
+ struct ev_io io;
234
+ VALUE fiber;
235
+ };
236
+
237
+ static void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
238
+ {
239
+ struct libev_io *watcher = (struct libev_io *)w;
240
+ Fiber_make_runnable(watcher->fiber, Qnil);
241
+ }
242
+
243
+ inline VALUE libev_await(struct LibevAgent_t *agent) {
244
+ VALUE ret;
245
+ agent->ref_count++;
246
+ ret = Thread_switch_fiber(rb_thread_current());
247
+ agent->ref_count--;
248
+ RB_GC_GUARD(ret);
249
+ return ret;
250
+ }
251
+
252
+ VALUE libev_agent_await(VALUE self) {
253
+ struct LibevAgent_t *agent;
254
+ GetLibevAgent(self, agent);
255
+ return libev_await(agent);
256
+ }
257
+
258
+ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
259
+ struct LibevAgent_t *agent;
260
+ struct libev_io watcher;
261
+ rb_io_t *fptr;
262
+ int len = NUM2INT(length);
263
+ int shrinkable = io_setstrbuf(&str, len);
264
+ char *buf = RSTRING_PTR(str);
265
+ long total = 0;
266
+ VALUE switchpoint_result = Qnil;
267
+ int read_to_eof = RTEST(to_eof);
268
+ VALUE underlying_io = rb_iv_get(io, "@io");
269
+
270
+ GetLibevAgent(self, agent);
271
+ if (underlying_io != Qnil) io = underlying_io;
272
+ GetOpenFile(io, fptr);
273
+ rb_io_check_byte_readable(fptr);
274
+ rb_io_set_nonblock(fptr);
275
+ watcher.fiber = Qnil;
276
+
277
+ OBJ_TAINT(str);
278
+
279
+ while (len > 0) {
280
+ int n = read(fptr->fd, buf, len);
281
+ if (n == 0)
282
+ break;
283
+ if (n > 0) {
284
+ total = total + n;
285
+ buf += n;
286
+ len -= n;
287
+ if (!read_to_eof || (len == 0)) break;
288
+ }
289
+ else {
290
+ int e = errno;
291
+ if ((e == EWOULDBLOCK || e == EAGAIN)) {
292
+ if (watcher.fiber == Qnil) {
293
+ watcher.fiber = rb_fiber_current();
294
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
295
+ }
296
+ ev_io_start(agent->ev_loop, &watcher.io);
297
+ switchpoint_result = libev_await(agent);
298
+ ev_io_stop(agent->ev_loop, &watcher.io);
299
+ if (TEST_EXCEPTION(switchpoint_result)) {
300
+ goto error;
301
+ }
302
+ }
303
+ else
304
+ rb_syserr_fail(e, strerror(e));
305
+ // rb_syserr_fail_path(e, fptr->pathv);
306
+ }
307
+ }
308
+
309
+ if (watcher.fiber == Qnil) {
310
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
311
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
312
+ if (TEST_EXCEPTION(switchpoint_result)) {
313
+ goto error;
314
+ }
315
+ }
316
+
317
+ if (total == 0) return Qnil;
318
+
319
+ io_set_read_length(str, total, shrinkable);
320
+ io_enc_str(str, fptr);
321
+
322
+ RB_GC_GUARD(watcher.fiber);
323
+ RB_GC_GUARD(switchpoint_result);
324
+
325
+ return str;
326
+ error:
327
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
328
+ }
329
+
330
+ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
331
+
332
+ #define PREPARE_STR() { \
333
+ str = Qnil; \
334
+ shrinkable = io_setstrbuf(&str, len); \
335
+ buf = RSTRING_PTR(str); \
336
+ total = 0; \
337
+ }
338
+
339
+ #define YIELD_STR() { \
340
+ io_set_read_length(str, total, shrinkable); \
341
+ io_enc_str(str, fptr); \
342
+ rb_yield(str); \
343
+ PREPARE_STR(); \
344
+ }
345
+
346
+ struct LibevAgent_t *agent;
347
+ struct libev_io watcher;
348
+ rb_io_t *fptr;
349
+ VALUE str;
350
+ int total;
351
+ int len = 8192;
352
+ int shrinkable;
353
+ char *buf;
354
+ VALUE switchpoint_result = Qnil;
355
+ VALUE underlying_io = rb_iv_get(io, "@io");
356
+
357
+ PREPARE_STR();
358
+
359
+ GetLibevAgent(self, agent);
360
+ if (underlying_io != Qnil) io = underlying_io;
361
+ GetOpenFile(io, fptr);
362
+ rb_io_check_byte_readable(fptr);
363
+ rb_io_set_nonblock(fptr);
364
+ watcher.fiber = Qnil;
365
+
366
+ OBJ_TAINT(str);
367
+
368
+ while (1) {
369
+ int n = read(fptr->fd, buf, len);
370
+ if (n == 0)
371
+ break;
372
+ if (n > 0) {
373
+ total = n;
374
+ YIELD_STR();
375
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
376
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
377
+ if (TEST_EXCEPTION(switchpoint_result)) {
378
+ goto error;
379
+ }
380
+ }
381
+ else {
382
+ int e = errno;
383
+ if ((e == EWOULDBLOCK || e == EAGAIN)) {
384
+ if (watcher.fiber == Qnil) {
385
+ watcher.fiber = rb_fiber_current();
386
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
387
+ }
388
+ ev_io_start(agent->ev_loop, &watcher.io);
389
+ switchpoint_result = libev_await(agent);
390
+ ev_io_stop(agent->ev_loop, &watcher.io);
391
+ if (TEST_EXCEPTION(switchpoint_result)) {
392
+ goto error;
393
+ }
394
+ }
395
+ else
396
+ rb_syserr_fail(e, strerror(e));
397
+ // rb_syserr_fail_path(e, fptr->pathv);
398
+ }
399
+ }
400
+
401
+ RB_GC_GUARD(str);
402
+ RB_GC_GUARD(watcher.fiber);
403
+ RB_GC_GUARD(switchpoint_result);
404
+
405
+ return io;
406
+ error:
407
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
408
+ }
409
+
410
+ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
411
+ struct LibevAgent_t *agent;
412
+ struct libev_io watcher;
413
+ rb_io_t *fptr;
414
+ VALUE switchpoint_result = Qnil;
415
+
416
+ char *buf = StringValuePtr(str);
417
+ int len = RSTRING_LEN(str);
418
+ int left = len;
419
+
420
+ VALUE underlying_io = rb_iv_get(io, "@io");
421
+ if (underlying_io != Qnil) io = underlying_io;
422
+ GetLibevAgent(self, agent);
423
+ io = rb_io_get_write_io(io);
424
+ GetOpenFile(io, fptr);
425
+ watcher.fiber = Qnil;
426
+
427
+ while (left > 0) {
428
+ int result = write(fptr->fd, buf, left);
429
+ if (result < 0) {
430
+ int e = errno;
431
+ if (e == EAGAIN) {
432
+ if (watcher.fiber == Qnil) {
433
+ watcher.fiber = rb_fiber_current();
434
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_WRITE);
435
+ }
436
+ ev_io_start(agent->ev_loop, &watcher.io);
437
+ switchpoint_result = libev_await(agent);
438
+ ev_io_stop(agent->ev_loop, &watcher.io);
439
+ if (TEST_EXCEPTION(switchpoint_result))
440
+ goto error;
441
+ }
442
+ else {
443
+ rb_syserr_fail(e, strerror(e));
444
+ // rb_syserr_fail_path(e, fptr->pathv);
445
+
446
+ }
447
+ }
448
+ else {
449
+ buf += result;
450
+ left -= result;
451
+ }
452
+ }
453
+
454
+ if (watcher.fiber == Qnil) {
455
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
456
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
457
+ if (TEST_EXCEPTION(switchpoint_result)) {
458
+ goto error;
459
+ }
460
+ }
461
+
462
+ RB_GC_GUARD(watcher.fiber);
463
+ RB_GC_GUARD(switchpoint_result);
464
+
465
+ return INT2NUM(len);
466
+ error:
467
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
468
+ }
469
+
470
+ ///////////////////////////////////////////////////////////////////////////
471
+
472
+ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
473
+ struct LibevAgent_t *agent;
474
+ struct libev_io watcher;
475
+ rb_io_t *fptr;
476
+ int fd;
477
+ struct sockaddr addr;
478
+ socklen_t len = (socklen_t)sizeof addr;
479
+ VALUE switchpoint_result = Qnil;
480
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
481
+ if (underlying_sock != Qnil) sock = underlying_sock;
482
+
483
+ GetLibevAgent(self, agent);
484
+ GetOpenFile(sock, fptr);
485
+ rb_io_set_nonblock(fptr);
486
+ watcher.fiber = Qnil;
487
+ while (1) {
488
+ fd = accept(fptr->fd, &addr, &len);
489
+ if (fd < 0) {
490
+ int e = errno;
491
+ if (e == EWOULDBLOCK || e == EAGAIN) {
492
+ if (watcher.fiber == Qnil) {
493
+ watcher.fiber = rb_fiber_current();
494
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
495
+ }
496
+ ev_io_start(agent->ev_loop, &watcher.io);
497
+ switchpoint_result = libev_await(agent);
498
+ ev_io_stop(agent->ev_loop, &watcher.io);
499
+
500
+ TEST_RESUME_EXCEPTION(switchpoint_result);
501
+ RB_GC_GUARD(watcher.fiber);
502
+ RB_GC_GUARD(switchpoint_result);
503
+ }
504
+ else
505
+ rb_syserr_fail(e, strerror(e));
506
+ // rb_syserr_fail_path(e, fptr->pathv);
507
+ }
508
+ else {
509
+ VALUE connection = rb_obj_alloc(cTCPSocket);
510
+ rb_io_t *fp;
511
+ MakeOpenFile(connection, fp);
512
+ rb_update_max_fd(fd);
513
+ fp->fd = fd;
514
+ fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
515
+ rb_io_ascii8bit_binmode(connection);
516
+ rb_io_set_nonblock(fp);
517
+ rb_io_synchronized(fp);
518
+
519
+ // if (rsock_do_not_reverse_lookup) {
520
+ // fp->mode |= FMODE_NOREVLOOKUP;
521
+ // }
522
+
523
+ if (watcher.fiber == Qnil) {
524
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
525
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
526
+ if (TEST_EXCEPTION(switchpoint_result)) {
527
+ return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
528
+ }
529
+ }
530
+
531
+ return connection;
532
+ }
533
+ }
534
+ return Qnil;
535
+ }
536
+
537
+ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
538
+ struct LibevAgent_t *agent;
539
+ struct libev_io watcher;
540
+ rb_io_t *fptr;
541
+ int fd;
542
+ struct sockaddr addr;
543
+ socklen_t len = (socklen_t)sizeof addr;
544
+ VALUE switchpoint_result = Qnil;
545
+ VALUE connection = Qnil;
546
+ VALUE underlying_sock = rb_iv_get(sock, "@io");
547
+ if (underlying_sock != Qnil) sock = underlying_sock;
548
+
549
+ GetLibevAgent(self, agent);
550
+ GetOpenFile(sock, fptr);
551
+ rb_io_set_nonblock(fptr);
552
+ watcher.fiber = Qnil;
553
+
554
+ while (1) {
555
+ fd = accept(fptr->fd, &addr, &len);
556
+ if (fd < 0) {
557
+ int e = errno;
558
+ if (e == EWOULDBLOCK || e == EAGAIN) {
559
+ if (watcher.fiber == Qnil) {
560
+ watcher.fiber = rb_fiber_current();
561
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
562
+ }
563
+ ev_io_start(agent->ev_loop, &watcher.io);
564
+ switchpoint_result = libev_await(agent);
565
+ ev_io_stop(agent->ev_loop, &watcher.io);
566
+
567
+ TEST_RESUME_EXCEPTION(switchpoint_result);
568
+ }
569
+ else
570
+ rb_syserr_fail(e, strerror(e));
571
+ // rb_syserr_fail_path(e, fptr->pathv);
572
+ }
573
+ else {
574
+ rb_io_t *fp;
575
+ connection = rb_obj_alloc(cTCPSocket);
576
+ MakeOpenFile(connection, fp);
577
+ rb_update_max_fd(fd);
578
+ fp->fd = fd;
579
+ fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
580
+ rb_io_ascii8bit_binmode(connection);
581
+ rb_io_set_nonblock(fp);
582
+ rb_io_synchronized(fp);
583
+
584
+ rb_yield(connection);
585
+ connection = Qnil;
586
+
587
+ Fiber_make_runnable(rb_fiber_current(), Qnil);
588
+ switchpoint_result = Thread_switch_fiber(rb_thread_current());
589
+ TEST_RESUME_EXCEPTION(switchpoint_result);
590
+ }
591
+ }
592
+
593
+ RB_GC_GUARD(connection);
594
+ RB_GC_GUARD(watcher.fiber);
595
+ RB_GC_GUARD(switchpoint_result);
596
+ }
597
+
598
+ VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
599
+ struct LibevAgent_t *agent;
600
+ struct libev_io watcher;
601
+ rb_io_t *fptr;
602
+ VALUE switchpoint_result = Qnil;
603
+ int events = RTEST(write) ? EV_WRITE : EV_READ;
604
+
605
+ VALUE underlying_io = rb_iv_get(io, "@io");
606
+ GetLibevAgent(self, agent);
607
+ if (underlying_io != Qnil) io = underlying_io;
608
+ GetOpenFile(io, fptr);
609
+
610
+ watcher.fiber = rb_fiber_current();
611
+ ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
612
+ ev_io_start(agent->ev_loop, &watcher.io);
613
+ switchpoint_result = libev_await(agent);
614
+ ev_io_stop(agent->ev_loop, &watcher.io);
615
+
616
+ TEST_RESUME_EXCEPTION(switchpoint_result);
617
+ RB_GC_GUARD(watcher.fiber);
618
+ RB_GC_GUARD(switchpoint_result);
619
+ return switchpoint_result;
620
+ }
621
+
622
+ struct libev_timer {
623
+ struct ev_timer timer;
624
+ VALUE fiber;
625
+ };
626
+
627
+ static void LibevAgent_timer_callback(EV_P_ ev_timer *w, int revents)
628
+ {
629
+ struct libev_timer *watcher = (struct libev_timer *)w;
630
+ Fiber_make_runnable(watcher->fiber, Qnil);
631
+ }
632
+
633
+ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
634
+ struct LibevAgent_t *agent;
635
+ struct libev_timer watcher;
636
+ VALUE switchpoint_result = Qnil;
637
+
638
+ GetLibevAgent(self, agent);
639
+ watcher.fiber = rb_fiber_current();
640
+ ev_timer_init(&watcher.timer, LibevAgent_timer_callback, NUM2DBL(duration), 0.);
641
+ ev_timer_start(agent->ev_loop, &watcher.timer);
642
+
643
+ switchpoint_result = libev_await(agent);
644
+ ev_timer_stop(agent->ev_loop, &watcher.timer);
645
+
646
+ TEST_RESUME_EXCEPTION(switchpoint_result);
647
+ RB_GC_GUARD(watcher.fiber);
648
+ RB_GC_GUARD(switchpoint_result);
649
+ return switchpoint_result;
650
+ }
651
+
652
+ struct libev_child {
653
+ struct ev_child child;
654
+ VALUE fiber;
655
+ };
656
+
657
+ static void LibevAgent_child_callback(EV_P_ ev_child *w, int revents)
658
+ {
659
+ struct libev_child *watcher = (struct libev_child *)w;
660
+ int exit_status = w->rstatus >> 8; // weird, why should we do this?
661
+ VALUE status;
662
+
663
+ status = rb_ary_new_from_args(2, INT2NUM(w->rpid), INT2NUM(exit_status));
664
+ Fiber_make_runnable(watcher->fiber, status);
665
+ }
666
+
667
+ VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
668
+ struct LibevAgent_t *agent;
669
+ struct libev_child watcher;
670
+ VALUE switchpoint_result = Qnil;
671
+ GetLibevAgent(self, agent);
672
+
673
+ watcher.fiber = rb_fiber_current();
674
+ ev_child_init(&watcher.child, LibevAgent_child_callback, NUM2INT(pid), 0);
675
+ ev_child_start(agent->ev_loop, &watcher.child);
676
+
677
+ switchpoint_result = libev_await(agent);
678
+ ev_child_stop(agent->ev_loop, &watcher.child);
679
+
680
+ TEST_RESUME_EXCEPTION(switchpoint_result);
681
+ RB_GC_GUARD(watcher.fiber);
682
+ RB_GC_GUARD(switchpoint_result);
683
+ return switchpoint_result;
684
+ }
685
+
686
+ struct ev_loop *LibevAgent_ev_loop(VALUE self) {
687
+ struct LibevAgent_t *agent;
688
+ GetLibevAgent(self, agent);
689
+ return agent->ev_loop;
690
+ }
691
+
692
+ void Init_LibevAgent() {
693
+ rb_require("socket");
694
+ cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
695
+
696
+ cLibevAgent = rb_define_class_under(mPolyphony, "LibevAgent", rb_cData);
697
+ rb_define_alloc_func(cLibevAgent, LibevAgent_allocate);
698
+
699
+ rb_define_method(cLibevAgent, "initialize", LibevAgent_initialize, 0);
700
+ rb_define_method(cLibevAgent, "finalize", LibevAgent_finalize, 0);
701
+ rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
702
+ rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
703
+
704
+ rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
705
+ rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
706
+
707
+ rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
708
+ rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
709
+
710
+ rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
711
+ rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
712
+ rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
713
+ rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
714
+ rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
715
+ rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
716
+ rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
717
+ rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
718
+ }