polyphony 0.14 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -2
  3. data/Gemfile.lock +1 -1
  4. data/TODO.md +5 -0
  5. data/examples/core/genserver.rb +1 -1
  6. data/examples/{io → http}/config.ru +0 -0
  7. data/examples/{io → http}/happy_eyeballs.rb +7 -4
  8. data/examples/{io → http}/http_client.rb +2 -2
  9. data/examples/{io → http}/http_server.js +0 -0
  10. data/examples/http/http_server.rb +15 -0
  11. data/examples/{io → http}/http_server_forked.rb +7 -7
  12. data/examples/http/http_server_throttled.rb +16 -0
  13. data/examples/{io → http}/http_ws_server.rb +7 -10
  14. data/examples/http/https_raw_client.rb +13 -0
  15. data/examples/http/https_server.rb +19 -0
  16. data/examples/{io → http}/https_wss_server.rb +4 -10
  17. data/examples/http/rack_server.rb +14 -0
  18. data/examples/http/rack_server_https.rb +20 -0
  19. data/examples/{io → http}/rack_server_https_forked.rb +4 -8
  20. data/examples/http/websocket_secure_server.rb +30 -0
  21. data/examples/{io → http}/websocket_server.rb +4 -10
  22. data/examples/{io → http}/ws_page.html +0 -0
  23. data/examples/{io → http}/wss_page.html +0 -0
  24. data/examples/performance/perf_multi_snooze.rb +1 -1
  25. data/examples/performance/perf_snooze.rb +1 -1
  26. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -1
  27. data/ext/ev/ev_module.c +9 -4
  28. data/ext/ev/io.c +7 -1
  29. data/lib/polyphony/core/channel.rb +1 -1
  30. data/lib/polyphony/core/coprocess.rb +1 -1
  31. data/lib/polyphony/core/supervisor.rb +3 -2
  32. data/lib/polyphony/core.rb +1 -1
  33. data/lib/polyphony/extensions/io.rb +46 -45
  34. data/lib/polyphony/http/agent.rb +4 -4
  35. data/lib/polyphony/http/http1.rb +3 -3
  36. data/lib/polyphony/http/http2.rb +7 -7
  37. data/lib/polyphony/http/server.rb +9 -11
  38. data/lib/polyphony/http.rb +7 -0
  39. data/lib/polyphony/net.rb +1 -0
  40. data/lib/polyphony/version.rb +1 -1
  41. data/lib/polyphony.rb +10 -1
  42. data/test/test_io.rb +15 -5
  43. metadata +21 -20
  44. data/examples/io/http_server.rb +0 -16
  45. data/examples/io/http_server_throttled.rb +0 -16
  46. data/examples/io/https_client.rb +0 -17
  47. data/examples/io/https_server.rb +0 -23
  48. data/examples/io/rack_server.rb +0 -19
  49. data/examples/io/rack_server_https.rb +0 -24
  50. data/lib/polyphony/testing.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78b1d2bf93b1562b6ef1010acdf4a30993d6fd8f40e001ab13a00606e0ed2137
4
- data.tar.gz: ea0f410c483ee06da41e1acdd342f9fccadf87c51bbca258571d8f5d2c6048a5
3
+ metadata.gz: 3b20863a05d3f48a1b2af107ebeecb67c08cdeb20a7973cdcf6cf0e7ad3d31f7
4
+ data.tar.gz: 62753e931ed71137ef4cc3c2b1b011f69cfaf2e578da65c6b193b3c40dc88e5f
5
5
  SHA512:
6
- metadata.gz: 70af3ec736856b611bd1d4242f6430761b83382f88239a31a1a7e4124fbe6804153e08d248c03c61510c272f9511cdf7f6f4ada277eab2cbd6809f2e41daaa39
7
- data.tar.gz: 678dad06dab6f6dbbccc23e39ac45e8045b4fdf0b2ea29959fd34e243d9d0f19f624ef80a8022f7dde4790e080302f59b06e65ec3ca26c9fe5efc4a70856f2c2
6
+ metadata.gz: 075bbc81be5b7818f550186d6d5d4094e53245cd425743fff407b15573bcd7b4a7c9219d9628167589b13ea538c6202e72b5f782675135d397a6346323133391
7
+ data.tar.gz: d4c25633ff9f79a14ae978fbf3c8a2e51ce1b7a7c82d5ab4aa007dae26780d7b29deca39bf285c885f197b67f290f78c91bccf0faaeb22b13a0481cc664d5a57
data/CHANGELOG.md CHANGED
@@ -1,11 +1,20 @@
1
+ 0.15 2019-05-20
2
+ ---------------
3
+
4
+ * Optimize `#next_tick` callback (about 6% faster than before)
5
+ * Fix IO#<< to return self
6
+ * Refactor HTTP code and examples
7
+ * Fix race condition in `Supervisor#stop!`
8
+ * Add `Kernel#snooze` method (`EV.snooze` will be deprecated eventually)
9
+
1
10
  0.14 2019-05-17
2
11
  ---------------
3
12
 
4
13
  * Use chunked encoding in HTTP 1 response
5
- * Rewrite IO#read, #readpartial, #write in C (about 30% performance improvement)
14
+ * Rewrite `IO#read`, `#readpartial`, `#write` in C (about 30% performance improvement)
6
15
  * Add method delegation to `ResourcePool`
7
16
  * Optimize PG::Connection#async_exec
8
- * Fix Coprocess#cancel!
17
+ * Fix `Coprocess#cancel!`
9
18
  * Preliminary support for websocket (see `examples/io/http_ws_server.rb`)
10
19
  * Rename `Coroutine` to `Coprocess`
11
20
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.14)
4
+ polyphony (0.15)
5
5
  http-2 (= 0.10.0)
6
6
  http_parser.rb (= 0.6.0)
7
7
  modulation (= 0.23)
data/TODO.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## Full or almost full functionality of IO using monkey patching
2
+
3
+ - stuff like gets, getc etc should be using the ev reactor
4
+
5
+
1
6
  ## Testing
2
7
 
3
8
  - test IO
@@ -15,7 +15,7 @@ class GenServer
15
15
  end
16
16
  end
17
17
  build_api(coprocess, receiver)
18
- EV.snooze
18
+ snooze
19
19
  coprocess
20
20
  end
21
21
 
File without changes
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # idea taken from the example given in trio:
4
+ # https://www.youtube.com/watch?v=oLkfnc_UMcE
5
+
3
6
  require 'modulation'
4
7
  Polyphony = import('../../lib/polyphony')
5
8
 
6
- async def try_connect(supervisor, target)
9
+ async def try_connect(target, supervisor)
7
10
  puts "trying #{target[2]}"
8
11
  socket = Polyphony::Net.tcp_connect(target[2], 80)
9
12
  supervisor.stop!([target[2], socket])
@@ -17,11 +20,11 @@ def happy_eyeballs(hostname, port, max_wait_time: 0.025)
17
20
  success = supervise do |supervisor|
18
21
  targets.each_with_index do |t, idx|
19
22
  sleep(max_wait_time) if idx > 0
20
- supervisor.spawn try_connect(supervisor, t)
23
+ supervisor.spawn try_connect(t, supervisor)
21
24
  end
22
25
  end
23
26
  if success
24
- puts "success: #{success[0]} (#{Time.now - t0}s)"
27
+ puts "success: %s (%.3fs)" % [success[0], Time.now - t0]
25
28
  else
26
29
  puts "timed out (#{Time.now - t0}s)"
27
30
  end
@@ -29,4 +32,4 @@ def happy_eyeballs(hostname, port, max_wait_time: 0.025)
29
32
  end
30
33
 
31
34
  # Let's try it out:
32
- happy_eyeballs("debian.org", "https")
35
+ happy_eyeballs("debian.org", "https")
@@ -9,11 +9,11 @@ def get_server_time
9
9
  Agent.get('https://ui.realiteq.net/', q: :time).json
10
10
  end
11
11
 
12
- X = 50
12
+ X = 1
13
13
  puts "Making #{X} requests..."
14
14
  t0 = Time.now
15
15
  supervise do |s|
16
16
  X.times { get_server_time }
17
17
  end
18
18
  elapsed = Time.now - t0
19
- puts "count: #{X} elapsed: #{elapsed} rate: #{X / elapsed} reqs/s"
19
+ puts "count: #{X} elapsed: #{elapsed} rate: #{X / elapsed} reqs/s"
File without changes
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ opts = { reuse_addr: true, dont_linger: true }
8
+ spawn {
9
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
10
+ req.respond("Hello world!\n")
11
+ end
12
+ }
13
+
14
+ puts "pid: #{Process.pid}"
15
+ puts "Listening on port 1234..."
@@ -1,18 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'modulation'
4
- require 'localhost/authority'
5
4
 
6
5
  Polyphony = import('../../lib/polyphony')
7
- HTTPServer = import('../../lib/polyphony/http/server')
8
6
 
9
7
  opts = {
10
8
  reuse_addr: true,
11
9
  dont_linger: true,
12
10
  }
13
- runner = HTTPServer.listener('0.0.0.0', 1234, opts) do |req|
14
- req.respond("Hello world!\n")
15
- end
11
+
12
+ server = Polyphony::HTTP::Server.listen('0.0.0.0', 1234, opts)
16
13
 
17
14
  puts "Listening on port 1234"
18
15
 
@@ -20,8 +17,11 @@ child_pids = []
20
17
  4.times do
21
18
  child_pids << Polyphony.fork do
22
19
  puts "forked pid: #{Process.pid}"
23
- spawn(&runner)
20
+ Polyphony::HTTP::Server.accept_loop(server, opts) do |req|
21
+ req.respond("Hello world!\n")
22
+ end
23
+ rescue Interrupt
24
24
  end
25
25
  end
26
26
 
27
- child_pids.each { |pid| EV::Child.new(pid).await }
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
+
7
+ $throttler = throttle(1000)
8
+ opts = { reuse_addr: true, dont_linger: true }
9
+ spawn {
10
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
11
+ $throttler.call { req.respond("Hello world!\n") }
12
+ end
13
+ }
14
+
15
+ puts "pid: #{Process.pid}"
16
+ puts "Listening on port 1234..."
@@ -5,8 +5,6 @@ require 'modulation'
5
5
  STDOUT.sync = true
6
6
 
7
7
  Polyphony = import('../../lib/polyphony')
8
- HTTPServer = import('../../lib/polyphony/http/server')
9
- Websocket = import('../../lib/polyphony/websocket')
10
8
 
11
9
  def ws_handler(conn)
12
10
  timer = spawn {
@@ -25,18 +23,17 @@ opts = {
25
23
  reuse_addr: true,
26
24
  dont_linger: true,
27
25
  upgrade: {
28
- websocket: Websocket.handler(&method(:ws_handler))
26
+ websocket: Polyphony::Websocket.handler(&method(:ws_handler))
29
27
  }
30
28
  }
31
29
 
32
30
  HTML = IO.read(File.join(__dir__, 'ws_page.html'))
33
31
 
34
- server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
35
- req.respond(HTML, 'Content-Type' => 'text/html')
36
- end
32
+ spawn {
33
+ server = Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
34
+ req.respond(HTML, 'Content-Type' => 'text/html')
35
+ end
36
+ }
37
37
 
38
38
  puts "pid: #{Process.pid}"
39
- puts "Listening on port 1234..."
40
- server.await
41
- puts "bye bye"
42
-
39
+ puts "Listening on port 1234..."
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ t0 = Time.now
8
+ io = Polyphony::Net.tcp_connect('realiteq.net', 443, secure: true)
9
+ io.write("GET /?q=time HTTP/1.0\r\nHost: realiteq.net\r\n\r\n")
10
+ reply = io.read
11
+ puts "time: #{Time.now - t0}"
12
+ puts
13
+ puts reply
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'localhost/authority'
5
+
6
+ Polyphony = import('../../lib/polyphony')
7
+
8
+ authority = Localhost::Authority.fetch
9
+ opts = {
10
+ reuse_addr: true,
11
+ dont_linger: true,
12
+ secure_context: authority.server_context
13
+ }
14
+
15
+ puts "pid: #{Process.pid}"
16
+ puts "Listening on port 1234..."
17
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
18
+ req.respond("Hello world!\n")
19
+ end
@@ -6,8 +6,6 @@ require 'localhost/authority'
6
6
  STDOUT.sync = true
7
7
 
8
8
  Polyphony = import('../../lib/polyphony')
9
- HTTPServer = import('../../lib/polyphony/http/server')
10
- Websocket = import('../../lib/polyphony/websocket')
11
9
 
12
10
  def ws_handler(conn)
13
11
  timer = spawn {
@@ -29,18 +27,14 @@ opts = {
29
27
  dont_linger: true,
30
28
  secure_context: authority.server_context,
31
29
  upgrade: {
32
- websocket: Websocket.handler(&method(:ws_handler))
30
+ websocket: Polyphony::Websocket.handler(&method(:ws_handler))
33
31
  }
34
32
  }
35
33
 
36
34
  HTML = IO.read(File.join(__dir__, 'wss_page.html'))
37
35
 
38
- server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
39
- req.respond(HTML, 'Content-Type' => 'text/html')
40
- end
41
-
42
36
  puts "pid: #{Process.pid}"
43
37
  puts "Listening on port 1234..."
44
- server.await
45
- puts "bye bye"
46
-
38
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
39
+ req.respond(HTML, 'Content-Type' => 'text/html')
40
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'localhost/authority'
5
+
6
+ Polyphony = import('../../lib/polyphony')
7
+
8
+ app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
9
+ app = Polyphony::HTTP::Rack.load(app_path)
10
+ opts = { reuse_addr: true, dont_linger: true }
11
+
12
+ puts "listening on port 1234"
13
+ puts "pid: #{Process.pid}"
14
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts, &app)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'localhost/authority'
5
+
6
+ Polyphony = import('../../lib/polyphony')
7
+
8
+ app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
9
+ app = Polyphony::HTTP::Rack.load(app_path)
10
+
11
+ authority = Localhost::Authority.fetch
12
+ opts = {
13
+ reuse_addr: true,
14
+ dont_linger: true,
15
+ secure_context: authority.server_context
16
+ }
17
+
18
+ puts "listening on port 1234"
19
+ puts "pid: #{Process.pid}"
20
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts, &app)
@@ -4,11 +4,9 @@ require 'modulation'
4
4
  require 'localhost/authority'
5
5
 
6
6
  Polyphony = import('../../lib/polyphony')
7
- HTTPServer = import('../../lib/polyphony/http/server')
8
- Rack = import('../../lib/polyphony/http/rack')
9
7
 
10
8
  app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
11
- rack = Rack.load(app_path)
9
+ app = Polyphony::HTTP::Rack.load(app_path)
12
10
 
13
11
  authority = Localhost::Authority.fetch
14
12
  opts = {
@@ -16,17 +14,15 @@ opts = {
16
14
  dont_linger: true,
17
15
  secure_context: authority.server_context
18
16
  }
19
- runner = HTTPServer.listener('0.0.0.0', 1234, opts, &rack)
17
+ server = Polyphony::HTTP::Server.listen('0.0.0.0', 1234, opts)
20
18
  puts "Listening on port 1234"
21
19
 
22
20
  child_pids = []
23
21
  4.times do
24
22
  child_pids << Polyphony.fork do
25
23
  puts "forked pid: #{Process.pid}"
26
- spawn(&runner)
24
+ Polyphony::HTTP::Server.accept_loop(server, opts, &app)
27
25
  end
28
26
  end
29
27
 
30
- spawn do
31
- child_pids.each { |pid| EV::Child.new(pid).await }
32
- end
28
+ child_pids.each { |pid| EV::Child.new(pid).await }
@@ -0,0 +1,30 @@
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
+
10
+ def ws_handler(conn)
11
+ while msg = conn.recv
12
+ conn << "you said: #{msg}"
13
+ end
14
+ end
15
+
16
+ authority = Localhost::Authority.fetch
17
+ opts = {
18
+ reuse_addr: true,
19
+ dont_linger: true,
20
+ upgrade: {
21
+ websocket: Polyphony::Websocket.handler(&method(:ws_handler))
22
+ },
23
+ secure_context: authority.server_context
24
+ }
25
+
26
+ puts "pid: #{Process.pid}"
27
+ puts "Listening on port 1234..."
28
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
29
+ req.respond("Hello world!\n")
30
+ end
@@ -5,8 +5,6 @@ require 'modulation'
5
5
  STDOUT.sync = true
6
6
 
7
7
  Polyphony = import('../../lib/polyphony')
8
- HTTPServer = import('../../lib/polyphony/http/server')
9
- Websocket = import('../../lib/polyphony/websocket')
10
8
 
11
9
  def ws_handler(conn)
12
10
  while msg = conn.recv
@@ -18,16 +16,12 @@ opts = {
18
16
  reuse_addr: true,
19
17
  dont_linger: true,
20
18
  upgrade: {
21
- websocket: Websocket.handler(&method(:ws_handler))
19
+ websocket: Polyphony::Websocket.handler(&method(:ws_handler))
22
20
  }
23
21
  }
24
22
 
25
- server = HTTPServer.serve('0.0.0.0', 1234, opts) do |req|
26
- req.respond("Hello world!\n")
27
- end
28
-
29
23
  puts "pid: #{Process.pid}"
30
24
  puts "Listening on port 1234..."
31
- server.await
32
- puts "bye bye"
33
-
25
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
26
+ req.respond("Hello world!\n")
27
+ end
File without changes
File without changes
@@ -12,7 +12,7 @@ spawn do
12
12
  supervise do |s|
13
13
  FIBERS.times do
14
14
  s.spawn do
15
- ITERATIONS.times { EV.snooze; count += 1 }
15
+ ITERATIONS.times { snooze; count += 1 }
16
16
  end
17
17
  end
18
18
  end
@@ -24,7 +24,7 @@ X = 1_000_000
24
24
  # snooze
25
25
  spawn do
26
26
  t0 = Time.now
27
- X.times { EV.snooze }
27
+ X.times { snooze }
28
28
  dt = Time.now - t0
29
29
  puts "#{X / dt.to_f}/s"
30
30
  end
@@ -30,7 +30,7 @@ async def handle_client(socket)
30
30
  if req
31
31
  handle_request(socket, req)
32
32
  req = nil
33
- EV.snooze
33
+ snooze
34
34
  end
35
35
  end
36
36
  rescue IOError, SystemCallError => e
data/ext/ev/ev_module.c CHANGED
@@ -55,6 +55,7 @@ void Init_EV() {
55
55
  rb_define_singleton_method(mEV, "schedule_fiber", EV_schedule_fiber, 2);
56
56
 
57
57
  rb_define_method(rb_mKernel, "suspend", EV_suspend, 0);
58
+ rb_define_method(rb_mKernel, "snooze", EV_snooze, 0);
58
59
 
59
60
  ID_call = rb_intern("call");
60
61
  ID_caller = rb_intern("caller");
@@ -175,7 +176,7 @@ static VALUE EV_post_fork(VALUE self) {
175
176
  return Qnil;
176
177
  }
177
178
 
178
- VALUE EV_next_tick_caller(VALUE proc, VALUE data, int argc, VALUE* argv) {
179
+ VALUE EV_next_tick_runner(VALUE proc) {
179
180
  if (rb_obj_is_proc(proc)) {
180
181
  rb_funcall(proc, ID_call, 1, Qtrue);
181
182
  }
@@ -186,9 +187,13 @@ VALUE EV_next_tick_caller(VALUE proc, VALUE data, int argc, VALUE* argv) {
186
187
  }
187
188
 
188
189
  void EV_next_tick_callback(ev_loop *ev_loop, struct ev_timer *timer, int revents) {
189
- VALUE scheduled_procs = rb_ary_dup(next_tick_procs);
190
- rb_funcall(next_tick_procs, ID_clear, 0);
191
- rb_block_call(scheduled_procs, ID_each, 0, NULL, EV_next_tick_caller, Qnil);
190
+ VALUE scheduled_procs = next_tick_procs;
191
+ next_tick_procs = rb_ary_new();
192
+
193
+ for (long i = 0; i < RARRAY_LEN(scheduled_procs); i++) {
194
+ EV_next_tick_runner(RARRAY_AREF(scheduled_procs, i));
195
+ }
196
+
192
197
  if (rb_array_len(next_tick_procs) > 0) {
193
198
  ev_timer_start(EV_DEFAULT, &next_tick_timer);
194
199
  } else {
data/ext/ev/io.c CHANGED
@@ -35,6 +35,7 @@ static int EV_IO_symbol2event_mask(VALUE sym);
35
35
  static VALUE IO_read(int argc, VALUE *argv, VALUE io);
36
36
  static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io);
37
37
  static VALUE IO_write(int argc, VALUE *argv, VALUE io);
38
+ static VALUE IO_write_chevron(VALUE io, VALUE str);
38
39
 
39
40
  void Init_EV_IO() {
40
41
  mEV = rb_define_module("EV");
@@ -51,7 +52,7 @@ void Init_EV_IO() {
51
52
  rb_define_method(cIO, "readpartial", IO_readpartial, -1);
52
53
  rb_define_method(cIO, "write", IO_write, -1);
53
54
  rb_define_method(cIO, "write_nonblock", IO_write, -1);
54
- rb_define_method(cIO, "<<", IO_write, -1);
55
+ rb_define_method(cIO, "<<", IO_write_chevron, 1);
55
56
  }
56
57
 
57
58
  static const rb_data_type_t EV_IO_type = {
@@ -403,3 +404,8 @@ static VALUE IO_write(int argc, VALUE *argv, VALUE io) {
403
404
 
404
405
  return LONG2FIX(total);
405
406
  }
407
+
408
+ static VALUE IO_write_chevron(VALUE io, VALUE str) {
409
+ IO_write(1, &str, io);
410
+ return io;
411
+ }
@@ -21,7 +21,7 @@ class Channel
21
21
  else
22
22
  @waiting_queue.shift&.schedule(o)
23
23
  end
24
- EV.snooze
24
+ snooze
25
25
  end
26
26
 
27
27
  def receive
@@ -47,7 +47,7 @@ class Coprocess
47
47
  @mailbox ||= []
48
48
  @mailbox << o
49
49
  @fiber&.schedule if @receive_waiting
50
- EV.snooze
50
+ snooze
51
51
  end
52
52
 
53
53
  def receive
@@ -54,9 +54,10 @@ class Supervisor
54
54
  end
55
55
 
56
56
  def stop!(result = nil)
57
- return unless @supervisor_fiber
57
+ return unless @supervisor_fiber && !@stopped
58
58
 
59
- @supervisor_fiber&.transfer Exceptions::MoveOn.new(nil, result)
59
+ @stopped = true
60
+ @supervisor_fiber.transfer Exceptions::MoveOn.new(nil, result)
60
61
  end
61
62
 
62
63
  def stop_all_tasks
@@ -41,5 +41,5 @@ at_exit do
41
41
  # in most cases, by the main fiber is done there are still pending or other
42
42
  # or asynchronous operations going on. If the reactor loop is not done, we
43
43
  # suspend the root fiber until it is done
44
- suspend if $__reactor_fiber__.alive?
44
+ suspend if $__reactor_fiber__&.alive?
45
45
  end
@@ -14,50 +14,51 @@ class ::IO
14
14
  @write_watcher&.stop
15
15
  end
16
16
 
17
- # NO_EXCEPTION = { exception: false }.freeze
18
-
19
- # def read(max = 8192, outbuf = nil)
20
- # outbuf ||= (@read_buffer ||= +'')
21
- # loop do
22
- # result = read_nonblock(max, outbuf, NO_EXCEPTION)
23
- # case result
24
- # when nil then raise IOError
25
- # when :wait_readable then read_watcher.await
26
- # else return result
27
- # end
28
- # end
29
- # ensure
30
- # @read_watcher&.stop
31
- # end
32
-
33
- # def write(data)
34
- # loop do
35
- # result = write_nonblock(data, NO_EXCEPTION)
36
- # case result
37
- # when nil then raise IOError
38
- # when :wait_writable then write_watcher.await
39
- # else
40
- # (result == data.bytesize) ? (return result) : (data = data[result..-1])
41
- # end
42
- # end
43
- # ensure
44
- # @write_watcher&.stop
45
- # end
46
-
47
- # # write_list is inspired by
48
- # def write_list(*list)
49
- # list.each do |data|
50
- # loop do
51
- # result = write_nonblock(data, NO_EXCEPTION)
52
- # case result
53
- # when nil then raise IOError
54
- # when :wait_writable then write_watcher.await
55
- # else
56
- # (result == data.bytesize) ? (break result) : (data = data[result..-1])
57
- # end
58
- # end
59
- # end
60
- # ensure
61
- # @write_watcher&.stop
17
+ # def each(sep = $/, limit = nil, chomp: nil)
18
+ # sep, limit = $/, sep if sep.is_a?(Integer)
19
+ # end
20
+ # alias_method :each_line, :each
21
+
22
+ # def each_byte
23
+ # end
24
+
25
+ # def each_char
26
+ # end
27
+
28
+ # def each_codepoint
29
+ # end
30
+
31
+ # def getbyte
32
+ # end
33
+
34
+ # def getc
35
+ # end
36
+
37
+ # def gets(sep = $/, limit = nil, chomp: nil)
38
+ # sep, limit = $/, sep if sep.is_a?(Integer)
39
+ # end
40
+
41
+ # def print(*args)
42
+ # end
43
+
44
+ # def printf(format, *args)
45
+ # end
46
+
47
+ # def putc(obj)
48
+ # end
49
+
50
+ # def puts(*args)
51
+ # end
52
+
53
+ # def readbyte
54
+ # end
55
+
56
+ # def readchar
57
+ # end
58
+
59
+ # def readline(sep = $/, limit = nil, chomp: nil)
60
+ # end
61
+
62
+ # def readlines(sep = $/, limit = nil, chomp: nil)
62
63
  # end
63
64
  end
@@ -51,8 +51,8 @@ class Agent
51
51
 
52
52
  def request(url, opts = OPTS_DEFAULT)
53
53
  ctx = request_ctx(url, opts)
54
- response = do_request(ctx)
55
54
 
55
+ response = do_request(ctx)
56
56
  case response[:status_code]
57
57
  when 301, 302
58
58
  request(response[:headers]['Location'])
@@ -115,7 +115,7 @@ class Agent
115
115
 
116
116
  state[:socket] << request
117
117
  while !done
118
- parser << state[:socket].read
118
+ parser << state[:socket].readpartial(8192)
119
119
  end
120
120
 
121
121
  {
@@ -159,14 +159,14 @@ class Agent
159
159
  stream.on(:close) {
160
160
  done = true
161
161
  return {
162
- protocol: 'http1.1',
162
+ protocol: 'http2',
163
163
  status_code: headers && headers[':status'].to_i,
164
164
  headers: headers || {},
165
165
  body: body
166
166
  }
167
167
  }
168
168
 
169
- while data = state[:socket].read
169
+ while data = state[:socket].readpartial(8192)
170
170
  state[:http2_client] << data
171
171
  end
172
172
  ensure
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :run
3
+ export :call
4
4
 
5
5
  require 'http/parser'
6
6
 
@@ -26,7 +26,7 @@ end
26
26
  # @param socket [Net::Socket] socket
27
27
  # @param handler [Proc] request handler
28
28
  # @return [void]
29
- def run(socket, opts, handler)
29
+ def call(socket, opts, &handler)
30
30
  ctx = connection_context(socket, opts, handler)
31
31
  ctx[:parser].on_body = proc { |chunk| handle_body_chunk(ctx, chunk) }
32
32
 
@@ -35,7 +35,7 @@ def run(socket, opts, handler)
35
35
  break unless data
36
36
  if ctx[:parser].parse(data)
37
37
  break unless handle_request(ctx)
38
- EV.snooze
38
+ snooze
39
39
  end
40
40
  end
41
41
  rescue IOError, SystemCallError => e
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :run, :upgrade
3
+ export :call, :upgrade
4
4
 
5
5
  require 'http/2'
6
6
 
7
7
  Request = import('./http2_request')
8
8
 
9
+ def call(socket, opts, &handler)
10
+ interface = prepare(socket, handler)
11
+ client_loop(socket, interface)
12
+ end
13
+
9
14
  UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
10
15
  HTTP/1.1 101 Switching Protocols
11
16
  Connection: Upgrade
@@ -28,17 +33,12 @@ def prepare(socket, handler)
28
33
  end
29
34
  end
30
35
 
31
- def run(socket, opts, handler)
32
- interface = prepare(socket, handler)
33
- client_loop(socket, interface)
34
- end
35
-
36
36
  def client_loop(socket, interface)
37
37
  loop do
38
38
  data = socket.readpartial(8192)
39
39
  break unless data
40
40
  interface << data
41
- EV.snooze
41
+ snooze
42
42
  end
43
43
  rescue IOError, SystemCallError => e
44
44
  # do nothing
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :serve, :listener
3
+ export :serve, :listen, :accept_loop
4
4
 
5
5
  Net = import('../net')
6
6
  HTTP1 = import('./http1')
@@ -9,31 +9,29 @@ HTTP2 = import('./http2')
9
9
  ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
10
10
  H2_PROTOCOL = 'h2'
11
11
 
12
- async def serve(host, port, opts = {}, &handler)
12
+ def serve(host, port, opts = {}, &handler)
13
13
  opts[:alpn_protocols] = ALPN_PROTOCOLS
14
14
  server = Net.tcp_listen(host, port, opts)
15
-
16
- accept_loop(server, opts, handler)
15
+ accept_loop(server, opts, &handler)
17
16
  end
18
17
 
19
- def listener(host, port, opts = {}, &handler)
18
+ def listen(host, port, opts = {})
20
19
  opts[:alpn_protocols] = ALPN_PROTOCOLS
21
- server = Net.tcp_listen(host, port, opts)
22
- proc { accept_loop(server, opts, handler) }
20
+ Net.tcp_listen(host, port, opts)
23
21
  end
24
22
 
25
- def accept_loop(server, opts, handler)
23
+ def accept_loop(server, opts, &handler)
26
24
  while true
27
25
  client = server.accept
28
- spawn client_task(client, opts, handler)
26
+ spawn { client_task(client, opts, &handler) }
29
27
  end
30
28
  rescue OpenSSL::SSL::SSLError
31
29
  retry # disregard
32
30
  end
33
31
 
34
- async def client_task(client, opts, handler)
32
+ def client_task(client, opts, &handler)
35
33
  client.no_delay rescue nil
36
- protocol_module(client).run(client, opts, handler)
34
+ protocol_module(client).(client, opts, &handler)
37
35
  end
38
36
 
39
37
  def protocol_module(socket)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ export :Server
4
+
5
+ auto_import(
6
+ Server: './http/server'
7
+ )
data/lib/polyphony/net.rb CHANGED
@@ -8,6 +8,7 @@ import('./extensions/socket')
8
8
  import('./extensions/ssl')
9
9
 
10
10
  def tcp_connect(host, port, opts = {})
11
+ puts "tcp_connect(#{host.inspect}, #{port.inspect}, #{opts.inspect})"
11
12
  socket = ::Socket.new(:INET, :STREAM).tap { |s|
12
13
  addr = ::Socket.sockaddr_in(port, host)
13
14
  s.connect(addr)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.14'
4
+ VERSION = '0.15'
5
5
  end
data/lib/polyphony.rb CHANGED
@@ -26,6 +26,15 @@ module Polyphony
26
26
  FS: './polyphony/fs',
27
27
  # Net: './polyphony/net',
28
28
  ResourcePool: './polyphony/resource_pool',
29
- Supervisor: './polyphony/supervisor'
29
+ Supervisor: './polyphony/supervisor',
30
+ Websocket: './polyphony/websocket'
30
31
  )
32
+
33
+ module HTTP
34
+ auto_import(
35
+ Agent: './polyphony/http/agent',
36
+ Rack: './polyphony/http/rack',
37
+ Server: './polyphony/http/server',
38
+ )
39
+ end
31
40
  end
data/test/test_io.rb CHANGED
@@ -8,16 +8,16 @@ module IOTests
8
8
  class IOTest < MiniTest::Test
9
9
  def setup
10
10
  EV.rerun
11
+ @i, @o = IO.pipe
11
12
  end
12
13
 
13
- def test_that_io_does_not_block
14
- i, o = IO.pipe
14
+ def test_that_io_op_yields_to_other_fibers
15
15
  count = 0
16
16
  msg = nil
17
17
  [
18
18
  spawn {
19
- o.write("hello")
20
- o.close
19
+ @o.write("hello")
20
+ @o.close
21
21
  },
22
22
 
23
23
  spawn {
@@ -28,11 +28,21 @@ module IOTests
28
28
  },
29
29
 
30
30
  spawn {
31
- msg = i.read
31
+ msg = @i.read
32
32
  }
33
33
  ].each(&:await)
34
34
  assert_equal(5, count)
35
35
  assert_equal("hello", msg)
36
36
  end
37
+
38
+ def test_that_double_chevron_method_returns_io
39
+ assert_equal(@o, @o << 'foo')
40
+
41
+ @o << 'bar' << 'baz'
42
+ @o.close
43
+ assert_equal('foobarbaz', @i.read)
44
+ end
45
+
46
+ def test_
37
47
  end
38
48
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.14'
4
+ version: '0.15'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-18 00:00:00.000000000 Z
11
+ date: 2019-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -148,6 +148,24 @@ files:
148
148
  - examples/core/thread_pool.rb
149
149
  - examples/core/throttle.rb
150
150
  - examples/fs/read.rb
151
+ - examples/http/config.ru
152
+ - examples/http/happy_eyeballs.rb
153
+ - examples/http/http_client.rb
154
+ - examples/http/http_server.js
155
+ - examples/http/http_server.rb
156
+ - examples/http/http_server_forked.rb
157
+ - examples/http/http_server_throttled.rb
158
+ - examples/http/http_ws_server.rb
159
+ - examples/http/https_raw_client.rb
160
+ - examples/http/https_server.rb
161
+ - examples/http/https_wss_server.rb
162
+ - examples/http/rack_server.rb
163
+ - examples/http/rack_server_https.rb
164
+ - examples/http/rack_server_https_forked.rb
165
+ - examples/http/websocket_secure_server.rb
166
+ - examples/http/websocket_server.rb
167
+ - examples/http/ws_page.html
168
+ - examples/http/wss_page.html
151
169
  - examples/interfaces/pg_client.rb
152
170
  - examples/interfaces/pg_pool.rb
153
171
  - examples/interfaces/pg_query.rb
@@ -155,27 +173,10 @@ files:
155
173
  - examples/interfaces/redis_client.rb
156
174
  - examples/interfaces/redis_pubsub.rb
157
175
  - examples/interfaces/redis_pubsub_perf.rb
158
- - examples/io/config.ru
159
176
  - examples/io/echo_client.rb
160
177
  - examples/io/echo_server.rb
161
178
  - examples/io/echo_server_with_timeout.rb
162
179
  - examples/io/echo_stdin.rb
163
- - examples/io/happy_eyeballs.rb
164
- - examples/io/http_client.rb
165
- - examples/io/http_server.js
166
- - examples/io/http_server.rb
167
- - examples/io/http_server_forked.rb
168
- - examples/io/http_server_throttled.rb
169
- - examples/io/http_ws_server.rb
170
- - examples/io/https_client.rb
171
- - examples/io/https_server.rb
172
- - examples/io/https_wss_server.rb
173
- - examples/io/rack_server.rb
174
- - examples/io/rack_server_https.rb
175
- - examples/io/rack_server_https_forked.rb
176
- - examples/io/websocket_server.rb
177
- - examples/io/ws_page.html
178
- - examples/io/wss_page.html
179
180
  - examples/performance/perf_multi_snooze.rb
180
181
  - examples/performance/perf_snooze.rb
181
182
  - examples/performance/thread-vs-fiber/polyphony_server.rb
@@ -226,6 +227,7 @@ files:
226
227
  - lib/polyphony/extensions/socket.rb
227
228
  - lib/polyphony/extensions/ssl.rb
228
229
  - lib/polyphony/fs.rb
230
+ - lib/polyphony/http.rb
229
231
  - lib/polyphony/http/agent.rb
230
232
  - lib/polyphony/http/http1.rb
231
233
  - lib/polyphony/http/http1_request.rb
@@ -237,7 +239,6 @@ files:
237
239
  - lib/polyphony/net.rb
238
240
  - lib/polyphony/resource_pool.rb
239
241
  - lib/polyphony/server_task.rb
240
- - lib/polyphony/testing.rb
241
242
  - lib/polyphony/version.rb
242
243
  - lib/polyphony/websocket.rb
243
244
  - polyphony.gemspec
@@ -1,16 +0,0 @@
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
-
@@ -1,16 +0,0 @@
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"
@@ -1,17 +0,0 @@
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
@@ -1,23 +0,0 @@
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..."
@@ -1,19 +0,0 @@
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
- Rack = import('../../lib/polyphony/http/rack')
9
-
10
- app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
11
- rack = Rack.load(app_path)
12
-
13
- spawn do
14
- opts = { reuse_addr: true, dont_linger: true }
15
- server = HTTPServer.serve('0.0.0.0', 1234, opts, &rack)
16
- puts "listening on port 1234"
17
- puts "pid: #{Process.pid}"
18
- server.await
19
- end
@@ -1,24 +0,0 @@
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
- Rack = import('../../lib/polyphony/http/rack')
9
-
10
- app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
11
- rack = Rack.load(app_path)
12
-
13
- spawn do
14
- authority = Localhost::Authority.fetch
15
- opts = {
16
- reuse_addr: true,
17
- dont_linger: true,
18
- secure_context: authority.server_context
19
- }
20
- server = HTTPServer.serve('0.0.0.0', 1234, opts, &rack)
21
- puts "listening on port 1234"
22
- puts "pid: #{Process.pid}"
23
- server.await
24
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Core = import('./core')
4
-
5
- # Fiber used for running reactor loop
6
- ReactorLoopFiber = Fiber.new do
7
- Polyphony.run_reactor
8
- end
9
-
10
- # Monkey-patch core module with async/await methods
11
- module Core
12
- # Processes a promise by running reactor loop until promise is completed
13
- # @param promise [Promise] promise
14
- # @param more [Array<Promise>] more promises
15
- # @return [any] resolved value
16
- def self.await(promise = {}, *more)
17
- return await_all(promise, *more) unless more.empty?
18
-
19
- # raise FiberError, AWAIT_ERROR_MSG unless Fiber.current.async?
20
- if promise.completed?
21
- return_value = promise.clear_result
22
- else
23
- ReactorLoopFiber.transfer until promise.completed?
24
- return_value = promise.result
25
- end
26
- return_value.is_a?(Exception) ? raise(return_value) : return_value
27
- end
28
-
29
- # Runs given block
30
- # @return [void]
31
- def self.async(&block)
32
- block.()
33
- end
34
- end