diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml new file mode 100644 index 00000000..4b73eaaa --- /dev/null +++ b/.github/workflows/development.yml @@ -0,0 +1,52 @@ +name: Development + +on: [push, pull_request] + +jobs: + test: + runs-on: ${{matrix.os}}-latest + continue-on-error: ${{matrix.experimental}} + + strategy: + matrix: + experimental: [false] + + os: + - ubuntu + - macos + + ruby: + - 2.5 + - 2.6 + - 2.7 + + include: + - experimental: true + os: ubuntu + ruby: truffleruby + - experimental: true + os: ubuntu + ruby: jruby + - experimental: true + os: ubuntu + ruby: head + - experimental: true + os: ubuntu + ruby: 2.6 + env: COVERAGE=PartialSummary,Coveralls + + steps: + - uses: actions/checkout@v1 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + + - name: Installing packages (ubuntu) + if: matrix.os == 'ubuntu' + run: sudo apt-get install apache2-utils + + - name: Install dependencies + run: ${{matrix.env}} bundle install + + - name: Run tests + run: ${{matrix.env}} bundle exec rspec diff --git a/async-http.gemspec b/async-http.gemspec index e4f2b3b8..4e3b12b4 100644 --- a/async-http.gemspec +++ b/async-http.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.add_dependency("protocol-http", "~> 0.18.0") spec.add_dependency("protocol-http1", "~> 0.12.0") - spec.add_dependency("protocol-http2", "~> 0.13.0") + spec.add_dependency("protocol-http2", "~> 0.14.0") # spec.add_dependency("openssl") diff --git a/lib/async/http/body/pipe.rb b/lib/async/http/body/pipe.rb index 4ac02229..266d9da3 100644 --- a/lib/async/http/body/pipe.rb +++ b/lib/async/http/body/pipe.rb @@ -60,7 +60,7 @@ def close def reader(task) @reader = task - task.annotate "pipe reader" + task.annotate "#{self.class} reader." while chunk = @input.read @head.write(chunk) @@ -80,7 +80,7 @@ def reader(task) def writer(task) @writer = task - task.annotate "pipe writer" + task.annotate "#{self.class} writer." while chunk = @head.read_partial @output.write(chunk) diff --git a/lib/async/http/client.rb b/lib/async/http/client.rb index 17e4dc1f..837f9c2b 100755 --- a/lib/async/http/client.rb +++ b/lib/async/http/client.rb @@ -83,7 +83,7 @@ def self.open(*arguments, **options, &block) def close while @pool.busy? - Async.logger.warn(self) {"Waiting for pool to drain: #{@pool}"} + Async.logger.warn(self) {"Waiting for #{@protocol} pool to drain: #{@pool}"} @pool.wait end diff --git a/lib/async/http/protocol/http1/server.rb b/lib/async/http/protocol/http1/server.rb index 902d4cbd..ada8484a 100644 --- a/lib/async/http/protocol/http1/server.rb +++ b/lib/async/http/protocol/http1/server.rb @@ -45,7 +45,9 @@ def next_request return request rescue Async::TimeoutError - fail_request(408) + # For an interesting discussion about this behaviour, see https://trac.nginx.org/nginx/ticket/1005 + # If you enable this, you will see some spec failures... + # fail_request(408) raise rescue fail_request(400) @@ -54,6 +56,8 @@ def next_request # Server loop. def each(task: Task.current) + task.annotate("Reading #{version} requests for #{self.class}.") + while request = next_request response = yield(request, self) diff --git a/lib/async/http/protocol/http2/connection.rb b/lib/async/http/protocol/http2/connection.rb index 0d381635..0e1ad449 100644 --- a/lib/async/http/protocol/http2/connection.rb +++ b/lib/async/http/protocol/http2/connection.rb @@ -66,7 +66,7 @@ def http2? end def start_connection - @reader ||= read_in_background + @reader || read_in_background end def close(error = nil) @@ -93,7 +93,11 @@ def write_frames(&block) end def read_in_background(parent: Task.current) + raise RuntimeError, "Connection is closed!" if closed? + parent.async do |task| + @reader = task + task.annotate("#{version} reading data for #{self.class}.") begin @@ -130,7 +134,7 @@ def viable? end def reusable? - !(self.closed? || @stream.closed?) + !self.closed? end def version diff --git a/lib/async/http/protocol/http2/server.rb b/lib/async/http/protocol/http2/server.rb index f3b8ea7c..3db051fe 100644 --- a/lib/async/http/protocol/http2/server.rb +++ b/lib/async/http/protocol/http2/server.rb @@ -52,17 +52,18 @@ def accept_stream(stream_id) end def close(error = nil) - # This invokes Framer#close which closes the stream: - super - if @requests # Stop the request loop: - @requests.enqueue nil + @requests.enqueue(nil) @requests = nil end + + super end - def each + def each(task: Task.current) + task.annotate("Reading #{version} requests for #{self.class}.") + # It's possible the connection has died before we get here... @requests&.async do |task, request| task.annotate("Incoming request: #{request.method} #{request.path.inspect}.") @@ -80,6 +81,8 @@ def each request.send_response(response) end end + + # Maybe we should add some synchronisation here - i.e. only exit once all requests are finished. end end end diff --git a/lib/async/http/version.rb b/lib/async/http/version.rb index 3d208fb8..dd0f06f6 100644 --- a/lib/async/http/version.rb +++ b/lib/async/http/version.rb @@ -22,6 +22,6 @@ module Async module HTTP - VERSION = "0.51.2" + VERSION = "0.51.6" end end diff --git a/spec/async/http/protocol/shared_examples.rb b/spec/async/http/protocol/shared_examples.rb index 8db26a0a..6c7326dd 100644 --- a/spec/async/http/protocol/shared_examples.rb +++ b/spec/async/http/protocol/shared_examples.rb @@ -167,6 +167,25 @@ expect(server.scheme).to be == "http" end + it "disconnects slow clients" do + response = client.get("/") + response.read + + # We expect this connection to be closed: + connection = response.connection + + reactor.sleep(1.0) + + response = client.get("/") + response.read + + expect(connection).to_not be_reusable + + # client.close + # reactor.sleep(0.1) + # reactor.print_hierarchy + end + context 'using GET method' do let(:expected) {"GET #{protocol::VERSION}"} diff --git a/spec/async/http/proxy_spec.rb b/spec/async/http/proxy_spec.rb index 41ea475e..e7e3c09d 100644 --- a/spec/async/http/proxy_spec.rb +++ b/spec/async/http/proxy_spec.rb @@ -142,23 +142,25 @@ upstream = Async::IO::Stream.new(endpoint.connect) Async.logger.debug(self) {"Connected to #{upstream}..."} - reader = Async do + reader = Async do |task| + task.annotate "Upstream reader." + while chunk = upstream.read_partial stream.write(chunk) stream.flush end - ensure Async.logger.debug(self) {"Finished reading from upstream..."} stream.close_write end - writer = Async do + writer = Async do |task| + task.annotate "Upstream writer." + while chunk = stream.read_partial upstream.write(chunk) upstream.flush end - rescue Async::Wrapper::Cancelled #ignore ensure diff --git a/spec/async/http/server_context.rb b/spec/async/http/server_context.rb index 12e9e5d9..80251bd4 100644 --- a/spec/async/http/server_context.rb +++ b/spec/async/http/server_context.rb @@ -26,7 +26,7 @@ include_context Async::RSpec::Reactor let(:protocol) {described_class} - let(:endpoint) {Async::HTTP::Endpoint.parse('http://127.0.0.1:9294', reuse_port: true)} + let(:endpoint) {Async::HTTP::Endpoint.parse('http://127.0.0.1:9294', timeout: 0.8, reuse_port: true)} let(:retries) {1} let!(:client) {Async::HTTP::Client.new(endpoint, protocol, retries: retries)}