polyphony 0.14 → 0.15

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