polyphony 0.19 → 0.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +87 -1
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile.lock +17 -6
  6. data/README.md +200 -139
  7. data/Rakefile +4 -4
  8. data/TODO.md +35 -7
  9. data/bin/poly +11 -0
  10. data/docs/getting-started/getting-started.md +1 -1
  11. data/docs/summary.md +3 -0
  12. data/docs/technical-overview/exception-handling.md +94 -0
  13. data/docs/technical-overview/fiber-scheduling.md +99 -0
  14. data/examples/core/cancel.rb +8 -4
  15. data/examples/core/channel_echo.rb +18 -17
  16. data/examples/core/defer.rb +12 -0
  17. data/examples/core/enumerator.rb +4 -4
  18. data/examples/core/fiber_error.rb +9 -0
  19. data/examples/core/fiber_error_with_backtrace.rb +73 -0
  20. data/examples/core/fork.rb +6 -6
  21. data/examples/core/genserver.rb +16 -8
  22. data/examples/core/lock.rb +3 -3
  23. data/examples/core/move_on.rb +4 -3
  24. data/examples/core/move_on_twice.rb +5 -5
  25. data/examples/core/move_on_with_ensure.rb +8 -11
  26. data/examples/core/move_on_with_value.rb +14 -0
  27. data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
  28. data/examples/core/nested_cancel.rb +5 -5
  29. data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
  30. data/examples/core/nested_spin.rb +17 -0
  31. data/examples/core/pingpong.rb +21 -0
  32. data/examples/core/pulse.rb +4 -5
  33. data/examples/core/resource.rb +6 -4
  34. data/examples/core/resource_cancel.rb +6 -9
  35. data/examples/core/resource_delegate.rb +3 -3
  36. data/examples/core/sleep.rb +3 -3
  37. data/examples/core/sleep_spin.rb +19 -0
  38. data/examples/core/snooze.rb +32 -0
  39. data/examples/core/spin.rb +14 -0
  40. data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
  41. data/examples/core/spin_error.rb +17 -0
  42. data/examples/core/spin_error_backtrace.rb +30 -0
  43. data/examples/core/spin_uncaught_error.rb +15 -0
  44. data/examples/core/supervisor.rb +8 -8
  45. data/examples/core/supervisor_with_cancel_scope.rb +7 -7
  46. data/examples/core/supervisor_with_error.rb +8 -8
  47. data/examples/core/supervisor_with_manual_move_on.rb +6 -7
  48. data/examples/core/suspend.rb +13 -0
  49. data/examples/core/thread.rb +1 -1
  50. data/examples/core/thread_cancel.rb +9 -11
  51. data/examples/core/thread_pool.rb +18 -14
  52. data/examples/core/throttle.rb +7 -7
  53. data/examples/core/timeout.rb +3 -3
  54. data/examples/fs/read.rb +7 -9
  55. data/examples/http/config.ru +7 -3
  56. data/examples/http/cuba.ru +22 -0
  57. data/examples/http/happy_eyeballs.rb +6 -4
  58. data/examples/http/http_client.rb +1 -1
  59. data/examples/http/http_get.rb +1 -1
  60. data/examples/http/http_parse_experiment.rb +21 -16
  61. data/examples/http/http_proxy.rb +28 -26
  62. data/examples/http/http_server.rb +10 -10
  63. data/examples/http/http_server_forked.rb +6 -5
  64. data/examples/http/http_server_throttled.rb +3 -3
  65. data/examples/http/http_ws_server.rb +11 -11
  66. data/examples/http/https_raw_client.rb +1 -1
  67. data/examples/http/https_server.rb +8 -8
  68. data/examples/http/https_wss_server.rb +13 -11
  69. data/examples/http/rack_server.rb +2 -2
  70. data/examples/http/rack_server_https.rb +4 -4
  71. data/examples/http/rack_server_https_forked.rb +5 -5
  72. data/examples/http/websocket_secure_server.rb +6 -6
  73. data/examples/http/websocket_server.rb +5 -5
  74. data/examples/interfaces/pg_client.rb +4 -4
  75. data/examples/interfaces/pg_pool.rb +13 -6
  76. data/examples/interfaces/pg_transaction.rb +5 -4
  77. data/examples/interfaces/redis_channels.rb +15 -11
  78. data/examples/interfaces/redis_client.rb +2 -2
  79. data/examples/interfaces/redis_pubsub.rb +2 -1
  80. data/examples/interfaces/redis_pubsub_perf.rb +13 -9
  81. data/examples/io/backticks.rb +11 -0
  82. data/examples/io/cat.rb +4 -5
  83. data/examples/io/echo_client.rb +9 -4
  84. data/examples/io/echo_client_from_stdin.rb +20 -0
  85. data/examples/io/echo_pipe.rb +7 -8
  86. data/examples/io/echo_server.rb +8 -6
  87. data/examples/io/echo_server_with_timeout.rb +13 -10
  88. data/examples/io/echo_stdin.rb +3 -3
  89. data/examples/io/httparty.rb +2 -2
  90. data/examples/io/httparty_multi.rb +8 -4
  91. data/examples/io/httparty_threaded.rb +6 -2
  92. data/examples/io/io_read.rb +2 -2
  93. data/examples/io/irb.rb +16 -4
  94. data/examples/io/net-http.rb +3 -3
  95. data/examples/io/open.rb +17 -0
  96. data/examples/io/system.rb +3 -3
  97. data/examples/io/tcpserver.rb +15 -0
  98. data/examples/io/tcpsocket.rb +6 -5
  99. data/examples/performance/multi_snooze.rb +29 -0
  100. data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
  101. data/examples/performance/snooze_raw.rb +39 -0
  102. data/ext/gyro/async.c +165 -0
  103. data/ext/gyro/child.c +167 -0
  104. data/ext/{ev → gyro}/extconf.rb +4 -3
  105. data/ext/gyro/gyro.c +316 -0
  106. data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
  107. data/ext/gyro/gyro_ext.c +23 -0
  108. data/ext/{ev → gyro}/io.c +65 -57
  109. data/ext/{ev → gyro}/libev.h +0 -0
  110. data/ext/gyro/signal.c +117 -0
  111. data/ext/{ev → gyro}/socket.c +61 -6
  112. data/ext/gyro/timer.c +199 -0
  113. data/ext/libev/Changes +35 -0
  114. data/ext/libev/README +2 -1
  115. data/ext/libev/ev.c +213 -151
  116. data/ext/libev/ev.h +95 -88
  117. data/ext/libev/ev_epoll.c +26 -15
  118. data/ext/libev/ev_kqueue.c +11 -5
  119. data/ext/libev/ev_linuxaio.c +642 -0
  120. data/ext/libev/ev_poll.c +13 -8
  121. data/ext/libev/ev_port.c +5 -2
  122. data/ext/libev/ev_vars.h +14 -3
  123. data/ext/libev/ev_wrap.h +16 -0
  124. data/lib/ev_ext.bundle +0 -0
  125. data/lib/polyphony.rb +46 -50
  126. data/lib/polyphony/auto_run.rb +12 -0
  127. data/lib/polyphony/core/cancel_scope.rb +11 -7
  128. data/lib/polyphony/core/channel.rb +16 -9
  129. data/lib/polyphony/core/coprocess.rb +101 -51
  130. data/lib/polyphony/core/exceptions.rb +14 -12
  131. data/lib/polyphony/core/resource_pool.rb +21 -8
  132. data/lib/polyphony/core/supervisor.rb +10 -5
  133. data/lib/polyphony/core/sync.rb +7 -6
  134. data/lib/polyphony/core/thread.rb +4 -4
  135. data/lib/polyphony/core/thread_pool.rb +4 -4
  136. data/lib/polyphony/core/throttler.rb +6 -4
  137. data/lib/polyphony/extensions/core.rb +253 -0
  138. data/lib/polyphony/extensions/io.rb +28 -16
  139. data/lib/polyphony/extensions/openssl.rb +2 -1
  140. data/lib/polyphony/extensions/socket.rb +47 -52
  141. data/lib/polyphony/http.rb +4 -3
  142. data/lib/polyphony/http/agent.rb +68 -57
  143. data/lib/polyphony/http/server.rb +5 -5
  144. data/lib/polyphony/http/server/http1.rb +268 -0
  145. data/lib/polyphony/http/server/http2.rb +62 -0
  146. data/lib/polyphony/http/server/http2_stream.rb +104 -0
  147. data/lib/polyphony/http/server/rack.rb +64 -0
  148. data/lib/polyphony/http/server/request.rb +119 -0
  149. data/lib/polyphony/net.rb +26 -15
  150. data/lib/polyphony/postgres.rb +17 -13
  151. data/lib/polyphony/redis.rb +16 -15
  152. data/lib/polyphony/version.rb +1 -1
  153. data/lib/polyphony/websocket.rb +11 -4
  154. data/polyphony.gemspec +13 -9
  155. data/test/eg.rb +27 -0
  156. data/test/helper.rb +25 -0
  157. data/test/run.rb +5 -0
  158. data/test/test_async.rb +33 -0
  159. data/test/test_coprocess.rb +239 -77
  160. data/test/test_core.rb +95 -61
  161. data/test/test_gyro.rb +148 -0
  162. data/test/test_http_server.rb +313 -0
  163. data/test/test_io.rb +79 -27
  164. data/test/test_kernel.rb +22 -12
  165. data/test/test_signal.rb +36 -0
  166. data/test/test_timer.rb +24 -0
  167. metadata +89 -33
  168. data/examples/core/nested_async.rb +0 -17
  169. data/examples/core/next_tick.rb +0 -12
  170. data/examples/core/sleep_spawn.rb +0 -19
  171. data/examples/core/spawn.rb +0 -14
  172. data/examples/core/spawn_error.rb +0 -28
  173. data/examples/performance/perf_multi_snooze.rb +0 -21
  174. data/ext/ev/async.c +0 -168
  175. data/ext/ev/child.c +0 -169
  176. data/ext/ev/ev_ext.c +0 -23
  177. data/ext/ev/ev_module.c +0 -242
  178. data/ext/ev/signal.c +0 -119
  179. data/ext/ev/timer.c +0 -197
  180. data/lib/polyphony/core/fiber_pool.rb +0 -98
  181. data/lib/polyphony/extensions/kernel.rb +0 -169
  182. data/lib/polyphony/http/http1_adapter.rb +0 -254
  183. data/lib/polyphony/http/http2_adapter.rb +0 -157
  184. data/lib/polyphony/http/rack.rb +0 -25
  185. data/lib/polyphony/http/request.rb +0 -66
  186. data/test/test_ev.rb +0 -110
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
- server = TCPServer.open(1234)
7
- puts "Echoing on port 1234..."
8
- while client = server.accept
6
+ server = TCPServer.open('127.0.0.1', 1234)
7
+ puts 'Echoing on port 1234...'
8
+ while (client = server.accept)
9
9
  spin do
10
- while data = client.readpartial(8192) rescue nil
11
- client.write("you said: ", data.chomp, "!\n")
10
+ while (data = client.gets)
11
+ client.write('you said: ', data.chomp, "!\n")
12
12
  end
13
+ rescue Errno::ECONNRESET
14
+ 'Connection reset...'
13
15
  end
14
16
  end
@@ -1,25 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
6
  begin
7
- server = Polyphony::Net.tcp_listen(nil, 1234, reuse_addr: true, dont_linger: true)
8
- puts "listening on port 1234..."
7
+ server = Polyphony::Net.tcp_listen(
8
+ nil, 1234, reuse_addr: true, dont_linger: true
9
+ )
10
+ puts 'listening on port 1234...'
9
11
 
10
12
  loop do
11
13
  client = server.accept
14
+ client.write "Hi there\n"
12
15
  spin do
13
- cancel_scope = nil
14
- move_on_after(5) do |s|
15
- cancel_scope = s
16
+ move_on_after(5) do |scope|
17
+ scope.when_cancelled do
18
+ client.write "Disconnecting due to inactivity\n"
19
+ end
16
20
  while (data = client.readpartial(8192))
17
- s.reset_timeout
18
- client.write(data)
21
+ scope.reset_timeout
22
+ client.write "You said: #{data}"
19
23
  end
20
24
  end
21
- client.write "Disconnecting due to inactivity\n" if cancel_scope.cancelled?
22
- rescue => e
25
+ rescue StandardError => e
23
26
  puts "client error: #{e.inspect}"
24
27
  ensure
25
28
  client.close
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
- puts "Write something..."
6
+ puts 'Write something...'
7
7
  move_on_after(5) do |scope|
8
8
  loop do
9
9
  data = STDIN.gets
@@ -11,4 +11,4 @@ move_on_after(5) do |scope|
11
11
  puts "you wrote: #{data}"
12
12
  end
13
13
  end
14
- puts "quitting due to inactivity"
14
+ puts 'quitting due to inactivity'
@@ -6,5 +6,5 @@ require 'httparty'
6
6
 
7
7
  timer = spin { throttled_loop(10) { STDOUT << '.' } }
8
8
 
9
- puts HTTParty.get('http://realiteq.net/?q=time')
10
- timer.stop
9
+ puts HTTParty.get('http://google.com/')
10
+ timer.stop
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
  require 'httparty'
6
6
 
7
7
  url = 'http://127.0.0.1:4411/?q=time'
@@ -18,12 +18,16 @@ move_on_after(3) do
18
18
  results << result
19
19
  STDOUT << '.'
20
20
  end
21
- rescue => e
21
+ rescue StandardError => e
22
22
  p e
23
23
  end
24
24
  end
25
25
  end
26
26
  end
27
- puts "done"
27
+ puts 'done'
28
28
  end
29
- puts "got #{results.size} (#{results.size / (Time.now - t0)}/s)"
29
+ puts format(
30
+ 'got %<count>d (%<rate>0.1f reqs/s)',
31
+ count: results.size,
32
+ rate: results.size / (Time.now - t0)
33
+ )
@@ -21,5 +21,9 @@ end
21
21
 
22
22
  sleep 3
23
23
  threads.each(&:kill)
24
- puts "done"
25
- puts "got #{results.size} (#{results.size / (Time.now - t0)}/s)"
24
+ puts 'done'
25
+ puts format(
26
+ 'got %<count>d (%<rate>0.1f reqs/s)',
27
+ count: results.size,
28
+ rate: results.size / (Time.now - t0)
29
+ )
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
6
  s = IO.read(__FILE__)
7
7
  puts "encoding: #{s.encoding.inspect}"
8
8
  puts s
9
- puts
9
+ puts
data/examples/io/irb.rb CHANGED
@@ -1,15 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
  require 'irb'
6
6
 
7
7
  $counter = 0
8
-
9
8
  timer = spin do
10
- throttled_loop(10) { $counter += 1 }
9
+ throttled_loop(5) do
10
+ $counter += 1
11
+ end
12
+ end
13
+
14
+ # readline blocks the current thread, so we offload it to the blocking-ops
15
+ # thread pool. That way, the reactor loop can keep running while waiting for
16
+ # readline to return
17
+ module ::Readline
18
+ alias_method :orig_readline, :readline
19
+ def readline(*args)
20
+ Polyphony::ThreadPool.process { orig_readline(*args) }
21
+ end
11
22
  end
12
23
 
13
24
  at_exit { timer.stop }
14
25
 
15
- IRB.start
26
+ puts 'try typing $counter to see the counter incremented in the background'
27
+ IRB.start
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
  require 'net/http'
6
6
 
7
7
  uri = URI('http://realiteq.net/?q=time')
8
8
 
9
9
  begin
10
10
  puts Net::HTTP.get(uri)
11
- rescue => e
11
+ rescue StandardError => e
12
12
  p e
13
- puts "*" * 40
13
+ puts '*' * 40
14
14
  puts e.backtrace[0..4].join("\n")
15
15
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+ require 'irb'
6
+
7
+ stdin = IO.open(STDIN.to_i)
8
+
9
+ loop do
10
+ print 'Say something: '
11
+ cancel_after(3) do
12
+ line = stdin.gets
13
+ puts "You said: #{line}"
14
+ end
15
+ rescue Polyphony::Cancel
16
+ puts '<got nothing>'
17
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
- timer = spin {
6
+ timer = spin do
7
7
  throttled_loop(5) { STDOUT << '.' }
8
- }
8
+ end
9
9
 
10
10
  puts system('ruby -e "sleep 1; puts :done; STDOUT.close"')
11
11
  timer.stop
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+
6
+ server = TCPServer.new('127.0.0.1', 1234)
7
+
8
+ puts 'echoing on port 1234'
9
+ while (socket = server.accept)
10
+ spin do
11
+ while (data = socket.gets(8192))
12
+ socket << "you said: #{data}"
13
+ end
14
+ end
15
+ end
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
+ require 'polyphony/extensions/backtrace'
5
6
 
6
- socket = TCPSocket.new('realiteq.net', 80)
7
+ socket = TCPSocket.new('google.com', 80)
7
8
 
8
9
  timer = spin { throttled_loop(20) { STDOUT << '.' } }
9
10
 
10
11
  5.times do
11
- socket.send("GET /?q=time HTTP/1.1\r\nHost: realiteq.net\r\n\r\n", 0)
12
+ socket.send("GET /?q=time HTTP/1.1\r\nHost: google.com\r\n\r\n", 0)
12
13
  socket.recv(8192)
13
- STDOUT << "*"
14
+ STDOUT << '*'
14
15
  end
15
16
 
16
17
  timer.stop
17
18
  socket.close
18
- puts
19
+ puts
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+
6
+ def bm(fibers, iterations)
7
+ count = 0
8
+ t0 = Time.now
9
+ supervise do |s|
10
+ fibers.times do
11
+ s.spin do
12
+ iterations.times do
13
+ snooze
14
+ count += 1
15
+ end
16
+ end
17
+ end
18
+ end
19
+ dt = Time.now - t0
20
+ puts "#{[fibers, iterations].inspect} count: #{count} #{count / dt.to_f}/s"
21
+ end
22
+
23
+ bm(1, 1_000_000)
24
+ bm(10, 100_000)
25
+ bm(100, 10_000)
26
+ bm(1_000, 1_000)
27
+ bm(10_000, 100)
28
+ # bm(100_000, 10)
29
+ # bm(1_000_000, 1)
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
6
  X = 1_000_000
7
7
 
8
- STDOUT << "Fiber.yield: "
8
+ STDOUT << 'Fiber.yield: '
9
9
  f = Fiber.new do
10
10
  loop { Fiber.yield }
11
11
  end
12
12
  t0 = Time.now
13
13
  X.times { f.resume }
14
14
  dt = Time.now - t0
15
- puts "%d/s" % (X / dt)
15
+ puts format('%d/s', (X / dt))
16
16
 
17
17
  # STDOUT << "Kernel#sleep: "
18
18
  # t0 = Time.now
@@ -20,8 +20,10 @@ puts "%d/s" % (X / dt)
20
20
  # dt = Time.now - t0
21
21
  # puts "%d/s" % (X / dt)
22
22
 
23
- STDOUT << "Kernel#snooze: "
23
+ trap('SIGINT') { exit! }
24
+
25
+ STDOUT << 'Kernel#snooze: '
24
26
  t0 = Time.now
25
27
  X.times { snooze }
26
28
  dt = Time.now - t0
27
- puts "%d/s" % (X / dt)
29
+ puts format('%d/s', (X / dt))
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fiber'
4
+
5
+ def worker_loop(tag)
6
+ loop do
7
+ puts "#{Time.now} #{tag}"
8
+ snooze
9
+ end
10
+ end
11
+
12
+ f1 = Fiber.new { worker_loop(:a) }
13
+ f2 = Fiber.new { worker_loop(:b) }
14
+
15
+ $reactor = Fiber.new do
16
+ loop do
17
+ # sleep 0.001
18
+ handle_next_tick
19
+ end
20
+ end
21
+
22
+ $next_tick_items = []
23
+
24
+ def handle_next_tick
25
+ items = $next_tick_items
26
+ $next_tick_items = []
27
+ items.each(&:transfer)
28
+ end
29
+
30
+ module Kernel
31
+ def snooze
32
+ $next_tick_items << Fiber.current
33
+ $reactor.transfer
34
+ end
35
+ end
36
+
37
+ $next_tick_items << f1
38
+ $next_tick_items << f2
39
+ $reactor.transfer
data/ext/gyro/async.c ADDED
@@ -0,0 +1,165 @@
1
+ #include "gyro.h"
2
+
3
+ struct Gyro_Async {
4
+ struct ev_async ev_async;
5
+ int active;
6
+ VALUE callback;
7
+ VALUE fiber;
8
+ };
9
+
10
+ static VALUE cGyro_Async = Qnil;
11
+
12
+ /* Allocator/deallocator */
13
+ static VALUE Gyro_Async_allocate(VALUE klass);
14
+ static void Gyro_Async_mark(void *ptr);
15
+ static void Gyro_Async_free(void *ptr);
16
+ static size_t Gyro_Async_size(const void *ptr);
17
+
18
+ /* Methods */
19
+ static VALUE Gyro_Async_initialize(VALUE self);
20
+
21
+ static VALUE Gyro_Async_start(VALUE self);
22
+ static VALUE Gyro_Async_stop(VALUE self);
23
+ static VALUE Gyro_Async_signal(VALUE self);
24
+ static VALUE Gyro_Async_await(VALUE self);
25
+
26
+ void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *async, int revents);
27
+
28
+ /* async encapsulates an async watcher */
29
+ void Init_Gyro_Async() {
30
+ cGyro_Async = rb_define_class_under(mGyro, "Async", rb_cData);
31
+ rb_define_alloc_func(cGyro_Async, Gyro_Async_allocate);
32
+
33
+ rb_define_method(cGyro_Async, "initialize", Gyro_Async_initialize, 0);
34
+ rb_define_method(cGyro_Async, "start", Gyro_Async_start, 0);
35
+ rb_define_method(cGyro_Async, "stop", Gyro_Async_stop, 0);
36
+ rb_define_method(cGyro_Async, "signal!", Gyro_Async_signal, 0);
37
+ rb_define_method(cGyro_Async, "await", Gyro_Async_await, 0);
38
+ }
39
+
40
+ static const rb_data_type_t Gyro_Async_type = {
41
+ "Gyro_Async",
42
+ {Gyro_Async_mark, Gyro_Async_free, Gyro_Async_size,},
43
+ 0, 0,
44
+ RUBY_TYPED_FREE_IMMEDIATELY,
45
+ };
46
+
47
+ static VALUE Gyro_Async_allocate(VALUE klass) {
48
+ struct Gyro_Async *async = (struct Gyro_Async *)xmalloc(sizeof(struct Gyro_Async));
49
+ return TypedData_Wrap_Struct(klass, &Gyro_Async_type, async);
50
+ }
51
+
52
+ static void Gyro_Async_mark(void *ptr) {
53
+ struct Gyro_Async *async = ptr;
54
+ if (async->callback != Qnil) {
55
+ rb_gc_mark(async->callback);
56
+ }
57
+ if (async->fiber != Qnil) {
58
+ rb_gc_mark(async->fiber);
59
+ }
60
+ }
61
+
62
+ static void Gyro_Async_free(void *ptr) {
63
+ struct Gyro_Async *async = ptr;
64
+ ev_async_stop(EV_DEFAULT, &async->ev_async);
65
+ xfree(async);
66
+ }
67
+
68
+ static size_t Gyro_Async_size(const void *ptr) {
69
+ return sizeof(struct Gyro_Async);
70
+ }
71
+
72
+ #define GetGyro_Async(obj, async) \
73
+ TypedData_Get_Struct((obj), struct Gyro_Async, &Gyro_Async_type, (async))
74
+
75
+ static VALUE Gyro_Async_initialize(VALUE self) {
76
+ struct Gyro_Async *async;
77
+ GetGyro_Async(self, async);
78
+
79
+ if (rb_block_given_p()) {
80
+ async->callback = rb_block_proc();
81
+ }
82
+ async->fiber = Qnil;
83
+
84
+ ev_async_init(&async->ev_async, Gyro_Async_callback);
85
+
86
+ async->active = 1;
87
+ ev_async_start(EV_DEFAULT, &async->ev_async);
88
+
89
+ return Qnil;
90
+ }
91
+
92
+ void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
93
+ VALUE fiber;
94
+ struct Gyro_Async *async = (struct Gyro_Async*)ev_async;
95
+
96
+ if (async->fiber != Qnil) {
97
+ async->active = 0;
98
+ fiber = async->fiber;
99
+ async->fiber = Qnil;
100
+ SCHEDULE_FIBER(fiber, 0);
101
+ }
102
+ else if (async->callback != Qnil) {
103
+ rb_funcall(async->callback, ID_call, 1, Qtrue);
104
+ }
105
+ }
106
+
107
+ static VALUE Gyro_Async_start(VALUE self) {
108
+ struct Gyro_Async *async;
109
+ GetGyro_Async(self, async);
110
+
111
+ if (!async->active) {
112
+ ev_async_start(EV_DEFAULT, &async->ev_async);
113
+ async->active = 1;
114
+ }
115
+
116
+ return self;
117
+ }
118
+
119
+ static VALUE Gyro_Async_stop(VALUE self) {
120
+ struct Gyro_Async *async;
121
+ GetGyro_Async(self, async);
122
+
123
+ if (async->active) {
124
+ ev_async_stop(EV_DEFAULT, &async->ev_async);
125
+ async->active = 0;
126
+ }
127
+
128
+ return self;
129
+ }
130
+
131
+ static VALUE Gyro_Async_signal(VALUE self) {
132
+ struct Gyro_Async *async;
133
+ GetGyro_Async(self, async);
134
+
135
+ ev_async_send(EV_DEFAULT, &async->ev_async);
136
+
137
+ return Qnil;
138
+ }
139
+
140
+ static VALUE Gyro_Async_await(VALUE self) {
141
+ struct Gyro_Async *async;
142
+ VALUE ret;
143
+
144
+ GetGyro_Async(self, async);
145
+
146
+ async->fiber = rb_fiber_current();
147
+ if (!async->active) {
148
+ async->active = 1;
149
+ ev_async_start(EV_DEFAULT, &async->ev_async);
150
+ }
151
+
152
+ ret = YIELD_TO_REACTOR();
153
+
154
+ // fiber is resumed
155
+ if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
156
+ if (async->active) {
157
+ async->active = 0;
158
+ ev_async_stop(EV_DEFAULT, &async->ev_async);
159
+ }
160
+ return rb_funcall(ret, ID_raise, 1, ret);
161
+ }
162
+ else {
163
+ return Qnil;
164
+ }
165
+ }