polyphony 0.19 → 0.20

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 (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
+ }