polyphony 0.17 → 0.19

Sign up to get free protection for your applications and to get access to all the features.
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