polyphony 0.17 → 0.19

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Gemfile.lock +11 -3
  4. data/README.md +18 -18
  5. data/TODO.md +5 -21
  6. data/examples/core/channel_echo.rb +3 -3
  7. data/examples/core/enumerator.rb +1 -1
  8. data/examples/core/fork.rb +1 -1
  9. data/examples/core/genserver.rb +1 -1
  10. data/examples/core/lock.rb +3 -3
  11. data/examples/core/multiple_spawn.rb +2 -2
  12. data/examples/core/nested_async.rb +1 -1
  13. data/examples/core/nested_multiple_spawn.rb +3 -3
  14. data/examples/core/resource_cancel.rb +1 -1
  15. data/examples/core/sleep_spawn.rb +2 -2
  16. data/examples/core/spawn.rb +1 -1
  17. data/examples/core/spawn_cancel.rb +1 -1
  18. data/examples/core/spawn_error.rb +4 -4
  19. data/examples/core/supervisor.rb +1 -1
  20. data/examples/core/supervisor_with_error.rb +1 -1
  21. data/examples/core/supervisor_with_manual_move_on.rb +1 -1
  22. data/examples/core/thread.rb +2 -2
  23. data/examples/core/thread_cancel.rb +2 -2
  24. data/examples/core/thread_pool.rb +1 -1
  25. data/examples/core/throttle.rb +3 -3
  26. data/examples/core/timeout.rb +10 -0
  27. data/examples/fs/read.rb +1 -1
  28. data/examples/http/http_client.rb +1 -1
  29. data/examples/http/http_get.rb +7 -0
  30. data/examples/http/http_parse_experiment.rb +118 -0
  31. data/examples/http/http_proxy.rb +81 -0
  32. data/examples/http/http_server.rb +15 -4
  33. data/examples/http/http_server_forked.rb +2 -2
  34. data/examples/http/http_server_throttled.rb +1 -1
  35. data/examples/http/http_ws_server.rb +2 -2
  36. data/examples/http/https_server.rb +5 -1
  37. data/examples/http/https_wss_server.rb +1 -1
  38. data/examples/http/rack_server_https_forked.rb +1 -1
  39. data/examples/interfaces/pg_client.rb +1 -1
  40. data/examples/interfaces/pg_pool.rb +1 -1
  41. data/examples/interfaces/redis_channels.rb +5 -5
  42. data/examples/interfaces/redis_pubsub.rb +2 -2
  43. data/examples/interfaces/redis_pubsub_perf.rb +3 -3
  44. data/examples/io/echo_client.rb +2 -2
  45. data/examples/io/echo_pipe.rb +17 -0
  46. data/examples/io/echo_server.rb +1 -1
  47. data/examples/io/echo_server_with_timeout.rb +1 -1
  48. data/examples/io/httparty.rb +10 -0
  49. data/examples/io/httparty_multi.rb +29 -0
  50. data/examples/io/httparty_threaded.rb +25 -0
  51. data/examples/io/irb.rb +15 -0
  52. data/examples/io/net-http.rb +15 -0
  53. data/examples/io/system.rb +1 -1
  54. data/examples/io/tcpsocket.rb +18 -0
  55. data/examples/performance/perf_multi_snooze.rb +2 -2
  56. data/examples/performance/perf_snooze.rb +17 -20
  57. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -1
  58. data/ext/ev/ev.h +9 -1
  59. data/ext/ev/ev_ext.c +4 -1
  60. data/ext/ev/ev_module.c +36 -22
  61. data/ext/ev/extconf.rb +1 -1
  62. data/ext/ev/io.c +23 -23
  63. data/ext/ev/signal.c +1 -1
  64. data/ext/ev/socket.c +161 -0
  65. data/lib/polyphony/core/coprocess.rb +1 -1
  66. data/lib/polyphony/core/fiber_pool.rb +2 -2
  67. data/lib/polyphony/core/supervisor.rb +2 -18
  68. data/lib/polyphony/extensions/io.rb +19 -6
  69. data/lib/polyphony/extensions/kernel.rb +17 -5
  70. data/lib/polyphony/extensions/socket.rb +40 -1
  71. data/lib/polyphony/http/agent.rb +56 -25
  72. data/lib/polyphony/http/http1_adapter.rb +254 -0
  73. data/lib/polyphony/http/http2_adapter.rb +157 -0
  74. data/lib/polyphony/http/{http2_request.rb → request.rb} +25 -22
  75. data/lib/polyphony/http/server.rb +19 -11
  76. data/lib/polyphony/net.rb +10 -6
  77. data/lib/polyphony/version.rb +1 -1
  78. data/polyphony.gemspec +6 -5
  79. data/test/test_coprocess.rb +9 -9
  80. data/test/test_core.rb +14 -14
  81. data/test/test_io.rb +4 -4
  82. data/test/test_kernel.rb +1 -1
  83. metadata +48 -23
  84. data/lib/polyphony/http/http1.rb +0 -124
  85. data/lib/polyphony/http/http1_request.rb +0 -83
  86. data/lib/polyphony/http/http2.rb +0 -65
@@ -18,8 +18,8 @@ end
18
18
 
19
19
  def threaded
20
20
  t0 = Time.now
21
- data = Polyphony::Thread.coproc { lengthy_op }.await
22
- X.times { Polyphony::Thread.coproc { lengthy_op }.await }
21
+ data = Polyphony::Thread.spin { lengthy_op }.await
22
+ X.times { Polyphony::Thread.spin { lengthy_op }.await }
23
23
  puts "read threaded #{data.bytesize} bytes (#{Time.now - t0}s)"
24
24
  end
25
25
 
@@ -13,10 +13,10 @@ def lengthy_op
13
13
  acc / count
14
14
  end
15
15
 
16
- coproc do
16
+ spin do
17
17
  t0 = Time.now
18
18
  cancel_after(0.01) do
19
- data = Polyphony::Thread.coproc { lengthy_op }.await
19
+ data = Polyphony::Thread.spin { lengthy_op }.await
20
20
  puts "read #{data.bytesize} bytes (#{Time.now - t0}s)"
21
21
  end
22
22
  rescue Exception => e
@@ -54,4 +54,4 @@ rescue Exception => e
54
54
  end
55
55
  end
56
56
 
57
- coproc { compare_performance }
57
+ spin { compare_performance }
@@ -3,14 +3,14 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
 
6
- coproc {
6
+ spin {
7
7
  throttled_loop(3) { STDOUT << '.' }
8
8
  }
9
9
 
10
- coproc {
10
+ spin {
11
11
  throttled_loop(rate: 2) { STDOUT << '?' }
12
12
  }
13
13
 
14
- coproc {
14
+ spin {
15
15
  throttled_loop(interval: 1) { STDOUT << '*' }
16
16
  }
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ puts "going to sleep..."
7
+ Timeout.timeout(1) do
8
+ sleep 60
9
+ end
10
+ puts "woke up"
@@ -26,7 +26,7 @@ def thread_pool_read_file(x, y)
26
26
  t0 = Time.now
27
27
  supervise do |s|
28
28
  y.times {
29
- s.coproc { x.times { IO.read(PATH) } }
29
+ s.spin { x.times { IO.read(PATH) } }
30
30
  }
31
31
  end
32
32
  puts "thread_pool_read_file: #{Time.now - t0}"
@@ -11,7 +11,7 @@ X = 10
11
11
  puts "Making #{X} requests..."
12
12
  t0 = Time.now
13
13
  supervise do |s|
14
- X.times { s.coproc { get_server_time } }
14
+ X.times { s.spin { get_server_time } }
15
15
  end
16
16
  elapsed = Time.now - t0
17
17
  puts "count: #{X} elapsed: #{elapsed} rate: #{X / elapsed} reqs/s"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/http'
5
+
6
+ resp = Polyphony::HTTP::Agent.get('https://google.com/')
7
+ p resp.body
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ require 'http/parser'
7
+ require 'fiber'
8
+
9
+ i, o = IO.pipe
10
+
11
+ class ParseLoop
12
+ def initialize(conn)
13
+ @parser = HTTP::Parser.new(self)
14
+ @conn = conn
15
+ @parse_fiber = Fiber.new do
16
+ while (data = conn.readpartial(8192))
17
+ @parser << data
18
+ snooze
19
+ end
20
+ rescue => e
21
+ conn.close
22
+ e
23
+ ensure
24
+ @message_in_train = nil
25
+ end
26
+ @state = nil
27
+ end
28
+
29
+ def on_headers_complete(headers)
30
+ @calling_fiber.transfer(headers)
31
+ end
32
+
33
+ def on_body(chunk)
34
+ @calling_fiber.transfer(chunk) if @read_body
35
+ end
36
+
37
+ def on_message_begin
38
+ @message_in_train = true
39
+ end
40
+
41
+ def on_message_complete
42
+ @message_in_train = nil
43
+ @calling_fiber.transfer nil
44
+ end
45
+
46
+ def parse_headers
47
+ @calling_fiber = Fiber.current
48
+ @parse_fiber.safe_transfer
49
+ end
50
+
51
+ def parse_body_chunk
52
+ @calling_fiber = Fiber.current
53
+ @read_body = true
54
+ @parse_fiber.safe_transfer
55
+ end
56
+
57
+ def consume_request
58
+ return unless @message_in_train
59
+
60
+ @calling_fiber = Fiber.current
61
+ @read_body = false
62
+ @parse_fiber.safe_transfer while @message_in_train
63
+ end
64
+
65
+ def alive?
66
+ @parse_fiber.alive?
67
+ end
68
+
69
+ def busy?
70
+ @message_in_train
71
+ end
72
+ end
73
+
74
+ def handle(parser)
75
+ headers = parser.parse_headers
76
+ return unless headers
77
+ puts "headers: #{headers.inspect}"
78
+ content_length = headers['Content-Length']
79
+ # if content_length && (content_length.to_i < 1000)
80
+ while chunk = parser.parse_body_chunk
81
+ puts "chunk: #{chunk.inspect}"
82
+ end
83
+ # else
84
+ # parser.consume_request
85
+ # end
86
+ puts "end of request"
87
+ rescue => e
88
+ puts "error: #{e.inspect}"
89
+ raise e
90
+ end
91
+
92
+ writer = spin {
93
+ o << "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 6\r\n\r\n"
94
+ o << "Hello!"
95
+
96
+ o << "POST / HTTP/1.1\r\nHost: example.com\r\n\r\n"
97
+
98
+ # o << "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 8192\r\n\r\n"
99
+ # o << ("Bye!" * 2048)
100
+
101
+ o << "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\n"
102
+ o << "Bye!"
103
+
104
+ (o << ("BLAH" * 100000)) rescue nil
105
+ o.close
106
+ }
107
+
108
+ begin
109
+ parse_loop = ParseLoop.new(i)
110
+ while parse_loop.alive?
111
+ puts "*" * 40
112
+ handle(parse_loop)
113
+ end
114
+ rescue => e
115
+ writer.stop
116
+ puts "#{e.class}: #{e.message}"
117
+ puts e.backtrace
118
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/http'
5
+ require 'localhost/authority'
6
+
7
+ # p Polyphony::HTTP::Agent.get('https://ui.realiteq.net/', q: :time)
8
+
9
+ BASE_URL = 'http://realiteq.net'
10
+
11
+ CACHE = {}
12
+
13
+ def proxy(uri, opts)
14
+ now = Time.now
15
+ uri = BASE_URL + uri
16
+ entry = CACHE[uri]
17
+ if entry && entry[:expires] >= now
18
+ return entry[:response]
19
+ else
20
+ puts "proxy => #{uri} (#{opts.inspect})"
21
+ response = Polyphony::HTTP::Agent.get(uri, opts)
22
+ # CACHE[uri] = {
23
+ # expires: now + 60,
24
+ # response: response
25
+ # }
26
+ response
27
+ end
28
+ end
29
+
30
+ HEADERS_BLACK_LIST = %w{
31
+ Transfer-Encoding Date Server Connection Content-Length Cache-Control
32
+ :method :authority :scheme :path
33
+ }
34
+
35
+ def sanitize_headers(headers)
36
+ headers.reject { |k, v| HEADERS_BLACK_LIST.include?(k) }
37
+ end
38
+
39
+ def sanitize_html(html)
40
+ # html.gsub('http://martigny-le-comte.fr/', '/')
41
+ end
42
+
43
+ # authority = Localhost::Authority.fetch
44
+
45
+ rsa_cert = OpenSSL::X509::Certificate.new(IO.read('../reality/aws/config/ssl/full_chain.pem'))
46
+ rsa_pkey = OpenSSL::PKey.read(IO.read('../reality/aws/config/ssl/private_key.pem'))
47
+ ctx = OpenSSL::SSL::SSLContext.new
48
+ ctx.add_certificate(rsa_cert, rsa_pkey)
49
+
50
+ opts = {
51
+ reuse_addr: true,
52
+ dont_linger: true,
53
+ secure_context: ctx#authority.server_context
54
+ }
55
+
56
+ spin {
57
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
58
+ puts "#{req.method} #{req.uri.to_s}"
59
+ puts "headers <: #{req.headers.inspect}"
60
+ # h = {
61
+ # uri: req.uri.to_s,
62
+ # protocol: req.protocol,
63
+ # headers: req.headers,
64
+ # body: req.body,
65
+ # }
66
+ response = proxy(req.uri.to_s, headers: sanitize_headers(req.headers))
67
+ headers = sanitize_headers(response[:headers])
68
+ body = response[:body]
69
+ # puts "body class: #{body.class}"
70
+ puts "headers >: #{response[:headers].inspect}"
71
+ # body = sanitize_html(body) if headers['Content-Type'] =~ /text\/html/
72
+ req.respond(body, headers)
73
+ rescue => e
74
+ puts "error"
75
+ p e
76
+ puts e.backtrace.join("\n")
77
+ end
78
+ }
79
+
80
+ puts "pid: #{Process.pid}"
81
+ puts "Listening on port 1234..."
@@ -3,12 +3,23 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony/http'
5
5
 
6
- opts = { reuse_addr: true, dont_linger: true }
7
- coproc {
6
+ opts = {
7
+ reuse_addr: true,
8
+ dont_linger: true
9
+ }
10
+
11
+ spin do
8
12
  Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
9
- req.respond("Hello world!\n")
13
+ req.respond('Hello world!')
14
+ # req.send_headers
15
+ # req.send_body_chunk("Method: #{req.method}\n")
16
+ # req.send_body_chunk("Path: #{req.path}\n")
17
+ # req.send_body_chunk("Query: #{req.query.inspect}\n", done: true)
10
18
  end
11
- }
19
+ rescue Exception => e
20
+ puts "*" * 40
21
+ p e
22
+ end
12
23
 
13
24
  puts "pid: #{Process.pid}"
14
25
  puts "Listening on port 1234..."
@@ -16,8 +16,8 @@ child_pids = []
16
16
  4.times do
17
17
  child_pids << Polyphony.fork do
18
18
  puts "forked pid: #{Process.pid}"
19
- Polyphony::HTTP::Server.accept_loop(server, opts) do |req|
20
- req.respond("Hello world!\n")
19
+ server.each do |req|
20
+ req.respond("Hello world! from pid: #{Process.pid}\n")
21
21
  end
22
22
  rescue Interrupt
23
23
  end
@@ -5,7 +5,7 @@ require 'polyphony/http'
5
5
 
6
6
  $throttler = throttle(1000)
7
7
  opts = { reuse_addr: true, dont_linger: true }
8
- coproc {
8
+ spin {
9
9
  Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
10
10
  $throttler.call { req.respond("Hello world!\n") }
11
11
  end
@@ -7,7 +7,7 @@ require 'polyphony/http'
7
7
  require 'polyphony/websocket'
8
8
 
9
9
  def ws_handler(conn)
10
- timer = coproc {
10
+ timer = spin {
11
11
  throttled_loop(1) {
12
12
  conn << Time.now.to_s
13
13
  }
@@ -29,7 +29,7 @@ opts = {
29
29
 
30
30
  HTML = IO.read(File.join(__dir__, 'ws_page.html'))
31
31
 
32
- coproc {
32
+ spin {
33
33
  server = Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
34
34
  req.respond(HTML, 'Content-Type' => 'text/html')
35
35
  end
@@ -14,5 +14,9 @@ opts = {
14
14
  puts "pid: #{Process.pid}"
15
15
  puts "Listening on port 1234..."
16
16
  Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
17
- req.respond("Hello world!\n")
17
+ req.respond('Hello world!')
18
+ # req.send_headers
19
+ # req.send_body_chunk("Method: #{req.method}\n")
20
+ # req.send_body_chunk("Path: #{req.path}\n")
21
+ # req.send_body_chunk("Query: #{req.query.inspect}\n", done: true)
18
22
  end
@@ -7,7 +7,7 @@ require 'localhost/authority'
7
7
  STDOUT.sync = true
8
8
 
9
9
  def ws_handler(conn)
10
- timer = coproc {
10
+ timer = spin {
11
11
  throttled_loop(1) {
12
12
  conn << Time.now.to_s rescue nil
13
13
  }
@@ -20,7 +20,7 @@ child_pids = []
20
20
  4.times do
21
21
  child_pids << Polyphony.fork do
22
22
  puts "forked pid: #{Process.pid}"
23
- Polyphony::HTTP::Server.accept_loop(server, opts, &app)
23
+ Polyphony::HTTP::Server.each(server, opts, &app)
24
24
  end
25
25
  end
26
26
 
@@ -11,7 +11,7 @@ rescue => e
11
11
  puts e.backtrace.join("\n")
12
12
  end
13
13
 
14
- time_printer = coproc do
14
+ time_printer = spin do
15
15
  last = Time.now
16
16
  throttled_loop(10) do
17
17
  now = Time.now
@@ -28,7 +28,7 @@ DBPOOL.preheat!
28
28
  t0 = Time.now
29
29
  count = 0
30
30
  coprocs = CONCURRENCY.times.map {
31
- coproc { loop { DBPOOL.acquire { |db| get_records(db); count += 1 } } }
31
+ spin { loop { DBPOOL.acquire { |db| get_records(db); count += 1 } } }
32
32
  }
33
33
  sleep 5
34
34
  puts "count: #{count} query rate: #{count / (Time.now - t0)} queries/s"
@@ -16,7 +16,7 @@ class RedisChannel < Polyphony::Channel
16
16
 
17
17
  def self.start_monitor
18
18
  @channels = {}
19
- @monitor = coproc do
19
+ @monitor = spin do
20
20
  subscribe_connection.subscribe(CHANNEL_MASTER_TOPIC) do |on|
21
21
  on.message do |topic, message|
22
22
  message = Marshal.load(message)
@@ -47,7 +47,7 @@ class RedisChannel < Polyphony::Channel
47
47
 
48
48
  def self.watch(channel)
49
49
  @channels[channel.topic] = channel
50
- coproc do
50
+ spin do
51
51
  publish_connection.publish(CHANNEL_MASTER_TOPIC, Marshal.dump({
52
52
  kind: :subscribe,
53
53
  topic: channel.topic
@@ -57,7 +57,7 @@ class RedisChannel < Polyphony::Channel
57
57
 
58
58
  def self.unwatch(channel)
59
59
  @channels.delete(channel.topic)
60
- coproc do
60
+ spin do
61
61
  publish_connection.publish(CHANNEL_MASTER_TOPIC, Marshal.dump({
62
62
  kind: :unsubscribe,
63
63
  topic: channel.topic
@@ -99,14 +99,14 @@ end
99
99
  RedisChannel.start_monitor
100
100
  channel = RedisChannel.new('channel1')
101
101
 
102
- coproc do
102
+ spin do
103
103
  loop do
104
104
  message = channel.receive
105
105
  puts "got message: #{message}"
106
106
  end
107
107
  end
108
108
 
109
- coproc do
109
+ spin do
110
110
  move_on_after(3) do
111
111
  throttled_loop(1) do
112
112
  channel << Time.now