polyphony 0.13 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +5 -0
  3. data/.gitignore +55 -0
  4. data/.rubocop.yml +49 -0
  5. data/CHANGELOG.md +13 -2
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +31 -0
  8. data/LICENSE +21 -0
  9. data/README.md +35 -18
  10. data/Rakefile +20 -0
  11. data/TODO.md +49 -0
  12. data/docs/getting-started/getting-started.md +10 -0
  13. data/docs/getting-started/tutorial.md +2 -0
  14. data/docs/summary.md +9 -0
  15. data/examples/core/cancel.rb +10 -0
  16. data/examples/core/channel_echo.rb +43 -0
  17. data/examples/core/enumerator.rb +14 -0
  18. data/examples/core/fork.rb +22 -0
  19. data/examples/core/genserver.rb +74 -0
  20. data/examples/core/lock.rb +20 -0
  21. data/examples/core/move_on.rb +11 -0
  22. data/examples/core/move_on_twice.rb +17 -0
  23. data/examples/core/move_on_with_ensure.rb +17 -0
  24. data/examples/core/multiple_async.rb +17 -0
  25. data/examples/core/nested_async.rb +18 -0
  26. data/examples/core/nested_cancel.rb +41 -0
  27. data/examples/core/nested_multiple_async.rb +19 -0
  28. data/examples/core/next_tick.rb +13 -0
  29. data/examples/core/pulse.rb +13 -0
  30. data/examples/core/resource.rb +29 -0
  31. data/examples/core/resource_cancel.rb +34 -0
  32. data/examples/core/resource_delegate.rb +32 -0
  33. data/examples/core/sleep.rb +9 -0
  34. data/examples/core/sleep2.rb +13 -0
  35. data/examples/core/spawn.rb +15 -0
  36. data/examples/core/spawn_cancel.rb +19 -0
  37. data/examples/core/spawn_error.rb +28 -0
  38. data/examples/core/supervisor.rb +22 -0
  39. data/examples/core/supervisor_with_cancel_scope.rb +24 -0
  40. data/examples/core/supervisor_with_error.rb +23 -0
  41. data/examples/core/supervisor_with_manual_move_on.rb +25 -0
  42. data/examples/core/thread.rb +30 -0
  43. data/examples/core/thread_cancel.rb +30 -0
  44. data/examples/core/thread_pool.rb +60 -0
  45. data/examples/core/throttle.rb +17 -0
  46. data/examples/fs/read.rb +37 -0
  47. data/examples/interfaces/pg_client.rb +38 -0
  48. data/examples/interfaces/pg_pool.rb +37 -0
  49. data/examples/interfaces/pg_query.rb +32 -0
  50. data/examples/interfaces/redis_channels.rb +119 -0
  51. data/examples/interfaces/redis_client.rb +21 -0
  52. data/examples/interfaces/redis_pubsub.rb +26 -0
  53. data/examples/interfaces/redis_pubsub_perf.rb +65 -0
  54. data/examples/io/config.ru +3 -0
  55. data/examples/io/echo_client.rb +22 -0
  56. data/examples/io/echo_server.rb +14 -0
  57. data/examples/io/echo_server_with_timeout.rb +33 -0
  58. data/examples/io/echo_stdin.rb +15 -0
  59. data/examples/io/happy_eyeballs.rb +32 -0
  60. data/examples/io/http_client.rb +19 -0
  61. data/examples/io/http_server.js +24 -0
  62. data/examples/io/http_server.rb +16 -0
  63. data/examples/io/http_server_forked.rb +27 -0
  64. data/examples/io/http_server_throttled.rb +16 -0
  65. data/examples/io/http_ws_server.rb +42 -0
  66. data/examples/io/https_client.rb +17 -0
  67. data/examples/io/https_server.rb +23 -0
  68. data/examples/io/https_wss_server.rb +46 -0
  69. data/examples/io/rack_server.rb +19 -0
  70. data/examples/io/rack_server_https.rb +24 -0
  71. data/examples/io/rack_server_https_forked.rb +32 -0
  72. data/examples/io/websocket_server.rb +33 -0
  73. data/examples/io/ws_page.html +34 -0
  74. data/examples/io/wss_page.html +34 -0
  75. data/examples/performance/perf_multi_snooze.rb +21 -0
  76. data/examples/performance/perf_snooze.rb +30 -0
  77. data/examples/performance/thread-vs-fiber/polyphony_server.rb +63 -0
  78. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  79. data/examples/streams/lines.rb +27 -0
  80. data/examples/streams/stdio.rb +18 -0
  81. data/ext/ev/async.c +168 -0
  82. data/ext/ev/child.c +169 -0
  83. data/ext/ev/ev.h +32 -0
  84. data/ext/ev/ev_ext.c +20 -0
  85. data/ext/ev/ev_module.c +222 -0
  86. data/ext/ev/io.c +405 -0
  87. data/ext/ev/libev.h +9 -0
  88. data/ext/ev/signal.c +119 -0
  89. data/ext/ev/timer.c +197 -0
  90. data/ext/libev/Changes +513 -0
  91. data/ext/libev/LICENSE +37 -0
  92. data/ext/libev/README +58 -0
  93. data/ext/libev/README.embed +3 -0
  94. data/ext/libev/ev.c +5214 -0
  95. data/ext/libev/ev.h +849 -0
  96. data/ext/libev/ev_epoll.c +285 -0
  97. data/ext/libev/ev_kqueue.c +218 -0
  98. data/ext/libev/ev_poll.c +151 -0
  99. data/ext/libev/ev_port.c +189 -0
  100. data/ext/libev/ev_select.c +316 -0
  101. data/ext/libev/ev_vars.h +204 -0
  102. data/ext/libev/ev_win32.c +162 -0
  103. data/ext/libev/ev_wrap.h +200 -0
  104. data/ext/libev/test_libev_win32.c +123 -0
  105. data/lib/polyphony.rb +7 -2
  106. data/lib/polyphony/core.rb +1 -1
  107. data/lib/polyphony/core/{coroutine.rb → coprocess.rb} +10 -10
  108. data/lib/polyphony/core/exceptions.rb +5 -5
  109. data/lib/polyphony/core/supervisor.rb +16 -16
  110. data/lib/polyphony/core/thread.rb +1 -1
  111. data/lib/polyphony/extensions/io.rb +43 -42
  112. data/lib/polyphony/extensions/kernel.rb +10 -34
  113. data/lib/polyphony/extensions/postgres.rb +3 -2
  114. data/lib/polyphony/extensions/redis.rb +1 -1
  115. data/lib/polyphony/extensions/socket.rb +8 -4
  116. data/lib/polyphony/extensions/ssl.rb +0 -54
  117. data/lib/polyphony/http/agent.rb +4 -10
  118. data/lib/polyphony/http/http1.rb +25 -25
  119. data/lib/polyphony/http/http1_request.rb +38 -26
  120. data/lib/polyphony/http/http2.rb +4 -5
  121. data/lib/polyphony/http/http2_request.rb +12 -18
  122. data/lib/polyphony/http/rack.rb +1 -3
  123. data/lib/polyphony/http/server.rb +9 -9
  124. data/lib/polyphony/net.rb +2 -2
  125. data/lib/polyphony/resource_pool.rb +5 -1
  126. data/lib/polyphony/version.rb +1 -1
  127. data/lib/polyphony/websocket.rb +52 -0
  128. data/polyphony.gemspec +31 -0
  129. data/test/test_coprocess.rb +131 -0
  130. data/test/test_core.rb +274 -0
  131. data/test/test_ev.rb +117 -0
  132. data/test/test_io.rb +38 -0
  133. metadata +113 -7
  134. data/lib/polyphony/core/async.rb +0 -36
  135. data/lib/polyphony/net_old.rb +0 -299
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ import('../../lib/polyphony/extensions/redis')
7
+
8
+ redis = Redis.new
9
+
10
+ X = 10000
11
+
12
+ t0 = Time.now
13
+ X.times { redis.get('abc') }
14
+ puts "get rate: #{X / (Time.now - t0)} reqs/s"
15
+
16
+ puts "abc = #{redis.get('abc')}"
17
+
18
+ puts "updating value..."
19
+ redis.set('abc', Time.now.to_s)
20
+
21
+ puts "abc = #{redis.get('abc')}"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ import('../../lib/polyphony/extensions/redis')
7
+
8
+ spawn do
9
+ redis = Redis.new
10
+ redis.subscribe('redis-channel') do |on|
11
+ on.message do |channel, message|
12
+ puts "##{channel}: #{message}"
13
+ redis.unsubscribe if message == "exit"
14
+ end
15
+ end
16
+ end
17
+
18
+ spawn do
19
+ redis = Redis.new
20
+ move_on_after(3) do
21
+ throttled_loop(1) do
22
+ redis.publish('redis-channel', Time.now)
23
+ end
24
+ end
25
+ redis.publish('redis-channel', 'exit')
26
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'json'
5
+
6
+ Polyphony = import('../../lib/polyphony')
7
+ import('../../lib/polyphony/extensions/redis')
8
+
9
+ X_SESSIONS = 1000
10
+ X_NODES = 10000
11
+ X_SUBSCRIPTIONS_PER_SESSION = 100
12
+
13
+ $sessions = []
14
+ X_SESSIONS.times do
15
+ $sessions << {
16
+ subscriptions: X_SUBSCRIPTIONS_PER_SESSION.times.map {
17
+ "node#{rand(X_NODES)}"
18
+ }.uniq
19
+ }
20
+ end
21
+
22
+ spawn do
23
+ redis = Redis.new
24
+ redis.subscribe('events') do |on|
25
+ on.message do |_, message|
26
+ distribute_event(JSON.parse(message, symbolize_names: true))
27
+ end
28
+ end
29
+ end
30
+
31
+ $update_count = 0
32
+
33
+ def distribute_event(event)
34
+ $update_count += 1
35
+ t0 = Time.now
36
+ count = 0
37
+ $sessions.each do |s|
38
+ count += 1 if s[:subscriptions].include?(event[:path])
39
+ end
40
+ elapsed = Time.now - t0
41
+ rate = X_SESSIONS / elapsed
42
+ # puts "elapsed: #{elapsed} (#{rate}/s)" if $update_count % 100 == 0
43
+ end
44
+
45
+ spawn do
46
+ redis = Redis.new
47
+ throttled_loop(1000) do
48
+ redis.publish('events', {path: "node#{rand(X_NODES)}"}.to_json)
49
+ end
50
+ end
51
+
52
+ spawn do
53
+ last_count = 0
54
+ last_stamp = Time.now
55
+ throttled_loop(1) do
56
+ now = Time.now
57
+ elapsed = now - last_stamp
58
+ delta = $update_count - last_count
59
+ puts "update rate: #{delta.to_f/elapsed}"
60
+ last_stamp = now
61
+ last_count = $update_count
62
+ end
63
+ end
64
+
65
+ Polyphony.trap(:int) { puts "bye..."; exit! }
@@ -0,0 +1,3 @@
1
+ run Proc.new { |env|
2
+ ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']]
3
+ }
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ socket = Polyphony::Net.tcp_connect('127.0.0.1', 1234)
8
+
9
+ writer = spawn do
10
+ throttled_loop(1) { socket << "#{Time.now}\n" }
11
+ end
12
+
13
+ reader = spawn do
14
+ puts "received from echo server:"
15
+ while data = socket.read
16
+ STDOUT << data
17
+ end
18
+ end
19
+
20
+ sleep(5)
21
+ [reader, writer].each(&:stop)
22
+ socket.close
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ Polyphony = import('../../lib/polyphony')
5
+
6
+ server = TCPServer.open(1234)
7
+ puts "Echoing on port 1234..."
8
+ while client = server.accept
9
+ spawn do
10
+ while data = client.readpartial(8192) rescue nil
11
+ client.write("you said: ", data.chomp, "!\n")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ begin
8
+ server = Polyphony::Net.tcp_listen(nil, 1234, reuse_addr: true, dont_linger: true)
9
+ puts "listening on port 1234..."
10
+
11
+ loop do
12
+ client = server.accept
13
+ spawn do
14
+ cancel_scope = nil
15
+ move_on_after(5) do |s|
16
+ cancel_scope = s
17
+ loop do
18
+ data = client.read
19
+ s.reset_timeout
20
+ client.write(data)
21
+ end
22
+ end
23
+ client.write "Disconnecting due to inactivity\n" if cancel_scope.cancelled?
24
+ rescue => e
25
+ puts "client error: #{e.inspect}"
26
+ ensure
27
+ client.close
28
+ end
29
+ end
30
+ rescue Exception => e
31
+ puts "uncaught exception: #{e.inspect}"
32
+ server&.close
33
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ puts "Write something..."
8
+ move_on_after(5) do |scope|
9
+ loop do
10
+ data = STDIN.read
11
+ scope.reset_timeout
12
+ puts "you wrote: #{data}"
13
+ end
14
+ end
15
+ puts "quitting due to inactivity"
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ Polyphony = import('../../lib/polyphony')
5
+
6
+ async def try_connect(supervisor, target)
7
+ puts "trying #{target[2]}"
8
+ socket = Polyphony::Net.tcp_connect(target[2], 80)
9
+ supervisor.stop!([target[2], socket])
10
+ rescue IOError, SystemCallError
11
+ end
12
+
13
+ def happy_eyeballs(hostname, port, max_wait_time: 0.025)
14
+ targets = Socket.getaddrinfo(hostname, port, :INET, :STREAM)
15
+ t0 = Time.now
16
+ cancel_after(5) do
17
+ success = supervise do |supervisor|
18
+ targets.each_with_index do |t, idx|
19
+ sleep(max_wait_time) if idx > 0
20
+ supervisor.spawn try_connect(supervisor, t)
21
+ end
22
+ end
23
+ if success
24
+ puts "success: #{success[0]} (#{Time.now - t0}s)"
25
+ else
26
+ puts "timed out (#{Time.now - t0}s)"
27
+ end
28
+ end
29
+ end
30
+
31
+ # Let's try it out:
32
+ happy_eyeballs("debian.org", "https")
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ Agent = import('../../lib/polyphony/http/agent')
7
+
8
+ def get_server_time
9
+ Agent.get('https://ui.realiteq.net/', q: :time).json
10
+ end
11
+
12
+ X = 50
13
+ puts "Making #{X} requests..."
14
+ t0 = Time.now
15
+ supervise do |s|
16
+ X.times { get_server_time }
17
+ end
18
+ elapsed = Time.now - t0
19
+ puts "count: #{X} elapsed: #{elapsed} rate: #{X / elapsed} reqs/s"
@@ -0,0 +1,24 @@
1
+ // For the sake of comparing performance, here's a node.js-based HTTP server
2
+ // doing roughly the same thing as http_server. Preliminary benchmarking shows
3
+ // the ruby version has a throughput (req/s) of about 2/3 of the JS version.
4
+
5
+ const http = require('http');
6
+
7
+ const MSG = 'Hello World';
8
+
9
+ const server = http.createServer((req, res) => {
10
+ // let requestCopy = {
11
+ // method: req.method,
12
+ // request_url: req.url,
13
+ // headers: req.headers
14
+ // };
15
+
16
+ // res.writeHead(200, { 'Content-Type': 'application/json' });
17
+ // res.end(JSON.stringify(requestCopy));
18
+
19
+ res.writeHead(200);
20
+ res.end(MSG)
21
+ });
22
+
23
+ server.listen(1235);
24
+ console.log('Listening on port 1235');
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ HTTPServer = import('../../lib/polyphony/http/server')
7
+
8
+ opts = { reuse_addr: true, dont_linger: true }
9
+ server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
10
+ req.respond("Hello world!\n")
11
+ end
12
+ puts "pid: #{Process.pid}"
13
+ puts "Listening on port 1234..."
14
+ server.await
15
+ puts "bye bye"
16
+
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'localhost/authority'
5
+
6
+ Polyphony = import('../../lib/polyphony')
7
+ HTTPServer = import('../../lib/polyphony/http/server')
8
+
9
+ opts = {
10
+ reuse_addr: true,
11
+ dont_linger: true,
12
+ }
13
+ runner = HTTPServer.listener('0.0.0.0', 1234, opts) do |req|
14
+ req.respond("Hello world!\n")
15
+ end
16
+
17
+ puts "Listening on port 1234"
18
+
19
+ child_pids = []
20
+ 4.times do
21
+ child_pids << Polyphony.fork do
22
+ puts "forked pid: #{Process.pid}"
23
+ spawn(&runner)
24
+ end
25
+ end
26
+
27
+ child_pids.each { |pid| EV::Child.new(pid).await }
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ HTTPServer = import('../../lib/polyphony/http/server')
7
+
8
+ $throttler = throttle(1000)
9
+ opts = { reuse_addr: true, dont_linger: true }
10
+ server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
11
+ $throttler.call { req.respond("Hello world!\n") }
12
+ end
13
+ puts "pid: #{Process.pid}"
14
+ puts "Listening on port 1234..."
15
+ server.await
16
+ puts "bye bye"
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ STDOUT.sync = true
6
+
7
+ Polyphony = import('../../lib/polyphony')
8
+ HTTPServer = import('../../lib/polyphony/http/server')
9
+ Websocket = import('../../lib/polyphony/websocket')
10
+
11
+ def ws_handler(conn)
12
+ timer = spawn {
13
+ throttled_loop(1) {
14
+ conn << Time.now.to_s
15
+ }
16
+ }
17
+ while msg = conn.recv
18
+ # conn << "you said: #{msg}"
19
+ end
20
+ ensure
21
+ timer.stop
22
+ end
23
+
24
+ opts = {
25
+ reuse_addr: true,
26
+ dont_linger: true,
27
+ upgrade: {
28
+ websocket: Websocket.handler(&method(:ws_handler))
29
+ }
30
+ }
31
+
32
+ HTML = IO.read(File.join(__dir__, 'ws_page.html'))
33
+
34
+ server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
35
+ req.respond(HTML, 'Content-Type' => 'text/html')
36
+ end
37
+
38
+ puts "pid: #{Process.pid}"
39
+ puts "Listening on port 1234..."
40
+ server.await
41
+ puts "bye bye"
42
+
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ spawn do
8
+ t0 = Time.now
9
+ io = Polyphony::Net.tcp_connect('google.com', 443, secure: true)
10
+ io.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
11
+ reply = io.read(2**16)
12
+ puts "time: #{Time.now - t0}"
13
+ puts
14
+ puts reply
15
+ rescue => e
16
+ p e
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'localhost/authority'
5
+
6
+ Polyphony = import('../../lib/polyphony')
7
+ HTTPServer = import('../../lib/polyphony/http/server')
8
+
9
+ spawn do
10
+ authority = Localhost::Authority.fetch
11
+ opts = {
12
+ reuse_addr: true,
13
+ dont_linger: true,
14
+ secure_context: authority.server_context
15
+ }
16
+ server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
17
+ req.respond("Hello world!\n")
18
+ end
19
+ server.await
20
+ end
21
+
22
+ puts "pid: #{Process.pid}"
23
+ puts "Listening on port 1234..."
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'localhost/authority'
5
+
6
+ STDOUT.sync = true
7
+
8
+ Polyphony = import('../../lib/polyphony')
9
+ HTTPServer = import('../../lib/polyphony/http/server')
10
+ Websocket = import('../../lib/polyphony/websocket')
11
+
12
+ def ws_handler(conn)
13
+ timer = spawn {
14
+ throttled_loop(1) {
15
+ conn << Time.now.to_s rescue nil
16
+ }
17
+ }
18
+ while msg = conn.recv
19
+ puts "msg: #{msg}"
20
+ # conn << "you said: #{msg}"
21
+ end
22
+ ensure
23
+ timer.stop
24
+ end
25
+
26
+ authority = Localhost::Authority.fetch
27
+ opts = {
28
+ reuse_addr: true,
29
+ dont_linger: true,
30
+ secure_context: authority.server_context,
31
+ upgrade: {
32
+ websocket: Websocket.handler(&method(:ws_handler))
33
+ }
34
+ }
35
+
36
+ HTML = IO.read(File.join(__dir__, 'wss_page.html'))
37
+
38
+ server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
39
+ req.respond(HTML, 'Content-Type' => 'text/html')
40
+ end
41
+
42
+ puts "pid: #{Process.pid}"
43
+ puts "Listening on port 1234..."
44
+ server.await
45
+ puts "bye bye"
46
+