From 51f6cb1030f1c2cb8e8c637bb358da5cead239a0 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 30 Mar 2024 09:55:02 +1300 Subject: [PATCH 1/3] Some initial tests. --- fixtures/async/http/a_graceful_stop.rb | 84 +++++++++++++++++++++++++ lib/async/http/protocol/http1/server.rb | 3 +- test/async/http/protocol/http10.rb | 2 + 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 fixtures/async/http/a_graceful_stop.rb diff --git a/fixtures/async/http/a_graceful_stop.rb b/fixtures/async/http/a_graceful_stop.rb new file mode 100644 index 00000000..045bbed3 --- /dev/null +++ b/fixtures/async/http/a_graceful_stop.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2018-2023, by Samuel Williams. +# Copyright, 2020, by Igor Sidorov. + +require 'async' +require 'async/http/client' +require 'async/http/server' +require 'async/http/endpoint' +require 'async/http/body/hijack' +require 'tempfile' + +module Async + module HTTP + AGracefulStop = Sus::Shared("a graceful stop") do + include Sus::Fixtures::Async::HTTP::ServerContext + + let(:events) {Async::Queue.new} + + with 'a streaming server (defered stop body)' do + let(:app) do + ::Protocol::HTTP::Middleware.for do |request| + body = ::Async::HTTP::Body::Writable.new + + Async do |task| + task.defer_stop do + 10.times do + body.write("Hello, World!\n") + task.sleep(0.1) + end + end + rescue => error + events.enqueue(error) + ensure + body.close(error) + end + + ::Protocol::HTTP::Response[200, {}, body] + end + end + + it "should stop gracefully" do + response = client.get("/") + expect(response).to be(:success?) + + @server_task.stop + + expect(response.read).to be == "Hello, World!\n" * 10 + end + end + + with 'a streaming server' do + let(:app) do + ::Protocol::HTTP::Middleware.for do |request| + body = ::Async::HTTP::Body::Writable.new + + Async do |task| + 10.times do + body.write("Hello, World!\n") + task.sleep(0.1) + end + rescue => error + events.enqueue(error) + ensure + body.close(error) + end + + ::Protocol::HTTP::Response[200, {}, body] + end + end + + it "should stop gracefully" do + response = client.get("/") + expect(response).to be(:success?) + + @server_task.stop + + inform(response.read) + end + end + end + end +end diff --git a/lib/async/http/protocol/http1/server.rb b/lib/async/http/protocol/http1/server.rb index 901d25c3..6281b0a9 100644 --- a/lib/async/http/protocol/http1/server.rb +++ b/lib/async/http/protocol/http1/server.rb @@ -55,7 +55,8 @@ def each(task: Task.current) return end - task.defer_stop do + # task.defer_stop do + begin # If a response was generated, send it: if response trailer = response.headers.trailer! diff --git a/test/async/http/protocol/http10.rb b/test/async/http/protocol/http10.rb index 26ae0be4..d53ddbe5 100644 --- a/test/async/http/protocol/http10.rb +++ b/test/async/http/protocol/http10.rb @@ -5,7 +5,9 @@ require 'async/http/protocol/http10' require 'async/http/a_protocol' +require 'async/http/a_graceful_stop' describe Async::HTTP::Protocol::HTTP10 do it_behaves_like Async::HTTP::AProtocol + it_behaves_like Async::HTTP::AGracefulStop end From fb3460a06c5edc69247faa4195769472e4990c54 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 4 Apr 2024 19:55:12 +1300 Subject: [PATCH 2/3] Move yield out of `defer_stop`. --- lib/async/http/protocol/http1/server.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/async/http/protocol/http1/server.rb b/lib/async/http/protocol/http1/server.rb index 6281b0a9..861532f0 100644 --- a/lib/async/http/protocol/http1/server.rb +++ b/lib/async/http/protocol/http1/server.rb @@ -100,14 +100,14 @@ def each(task: Task.current) # Gracefully finish reading the request body if it was not already done so. request&.each{} - - # This ensures we yield at least once every iteration of the loop and allow other fibers to execute. - task.yield rescue => error raise ensure body&.close(error) end + + # This ensures we yield at least once every iteration of the loop and allow other fibers to execute. + task.yield end end From b2adb2f239faf43d091c3cedd978343f79f0d7a5 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 4 Apr 2024 20:56:53 +1300 Subject: [PATCH 3/3] Move around tests and fix Writable body error handling. --- fixtures/async/http/a_graceful_stop.rb | 84 ---------------------- lib/async/http/body/writable.rb | 6 ++ lib/async/http/protocol/http1/server.rb | 3 +- test/async/http/protocol/graceful_stop.rb | 88 +++++++++++++++++++++++ test/async/http/protocol/http10.rb | 1 - 5 files changed, 95 insertions(+), 87 deletions(-) delete mode 100644 fixtures/async/http/a_graceful_stop.rb create mode 100644 test/async/http/protocol/graceful_stop.rb diff --git a/fixtures/async/http/a_graceful_stop.rb b/fixtures/async/http/a_graceful_stop.rb deleted file mode 100644 index 045bbed3..00000000 --- a/fixtures/async/http/a_graceful_stop.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -# Released under the MIT License. -# Copyright, 2018-2023, by Samuel Williams. -# Copyright, 2020, by Igor Sidorov. - -require 'async' -require 'async/http/client' -require 'async/http/server' -require 'async/http/endpoint' -require 'async/http/body/hijack' -require 'tempfile' - -module Async - module HTTP - AGracefulStop = Sus::Shared("a graceful stop") do - include Sus::Fixtures::Async::HTTP::ServerContext - - let(:events) {Async::Queue.new} - - with 'a streaming server (defered stop body)' do - let(:app) do - ::Protocol::HTTP::Middleware.for do |request| - body = ::Async::HTTP::Body::Writable.new - - Async do |task| - task.defer_stop do - 10.times do - body.write("Hello, World!\n") - task.sleep(0.1) - end - end - rescue => error - events.enqueue(error) - ensure - body.close(error) - end - - ::Protocol::HTTP::Response[200, {}, body] - end - end - - it "should stop gracefully" do - response = client.get("/") - expect(response).to be(:success?) - - @server_task.stop - - expect(response.read).to be == "Hello, World!\n" * 10 - end - end - - with 'a streaming server' do - let(:app) do - ::Protocol::HTTP::Middleware.for do |request| - body = ::Async::HTTP::Body::Writable.new - - Async do |task| - 10.times do - body.write("Hello, World!\n") - task.sleep(0.1) - end - rescue => error - events.enqueue(error) - ensure - body.close(error) - end - - ::Protocol::HTTP::Response[200, {}, body] - end - end - - it "should stop gracefully" do - response = client.get("/") - expect(response).to be(:success?) - - @server_task.stop - - inform(response.read) - end - end - end - end -end diff --git a/lib/async/http/body/writable.rb b/lib/async/http/body/writable.rb index a86d4a8a..0847cc27 100644 --- a/lib/async/http/body/writable.rb +++ b/lib/async/http/body/writable.rb @@ -25,6 +25,7 @@ def initialize(length = nil, queue: Async::Queue.new) @count = 0 + # Whether there is any more data to read from this body: @finished = false @closed = false @@ -66,6 +67,11 @@ def read unless chunk = @queue.dequeue @finished = true + + # If the queue was closed, and there was an error, raise it. + if @closed and @error + raise(@error) + end end return chunk diff --git a/lib/async/http/protocol/http1/server.rb b/lib/async/http/protocol/http1/server.rb index 861532f0..55800f21 100644 --- a/lib/async/http/protocol/http1/server.rb +++ b/lib/async/http/protocol/http1/server.rb @@ -55,8 +55,7 @@ def each(task: Task.current) return end - # task.defer_stop do - begin + task.defer_stop do # If a response was generated, send it: if response trailer = response.headers.trailer! diff --git a/test/async/http/protocol/graceful_stop.rb b/test/async/http/protocol/graceful_stop.rb new file mode 100644 index 00000000..d7036431 --- /dev/null +++ b/test/async/http/protocol/graceful_stop.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2018-2023, by Samuel Williams. +# Copyright, 2020, by Igor Sidorov. + +require 'async' +require 'async/http/client' +require 'async/http/server' +require 'async/http/endpoint' +require 'async/http/body/hijack' +require 'tempfile' + +require 'async/http/protocol/http10' +require 'sus/fixtures/async/http/server_context' + +AGracefulStop = Sus::Shared("a graceful stop") do + include Sus::Fixtures::Async::HTTP::ServerContext + + let(:chunks) {Async::Queue.new} + + with 'a streaming server (defered stop body)' do + let(:app) do + ::Protocol::HTTP::Middleware.for do |request| + body = ::Async::HTTP::Body::Writable.new + + Async do |task| + task.defer_stop do + while chunk = chunks.dequeue + body.write(chunk) + end + end + ensure + body.close($!) + end + + ::Protocol::HTTP::Response[200, {}, body] + end + end + + it "should stop gracefully" do + response = client.get("/") + expect(response).to be(:success?) + + @server_task.stop + + chunks.enqueue("Hello, World!") + expect(response.body.read).to be == "Hello, World!" + chunks.enqueue(nil) + ensure + response&.close + end + end + + with 'a streaming server' do + let(:app) do + ::Protocol::HTTP::Middleware.for do |request| + body = ::Async::HTTP::Body::Writable.new + + Async do |task| + while chunk = chunks.dequeue + body.write(chunk) + end + ensure + body.close($!) + end + + ::Protocol::HTTP::Response[200, {}, body] + end + end + + it "should stop gracefully" do + response = client.get("/") + expect(response).to be(:success?) + + @server_task.stop + + chunks.enqueue("Hello, World!") + expect do + response.read + end.to raise_exception(EOFError) + end + end +end + +describe Async::HTTP::Protocol::HTTP11 do + it_behaves_like AGracefulStop +end diff --git a/test/async/http/protocol/http10.rb b/test/async/http/protocol/http10.rb index d53ddbe5..7d5197c3 100644 --- a/test/async/http/protocol/http10.rb +++ b/test/async/http/protocol/http10.rb @@ -9,5 +9,4 @@ describe Async::HTTP::Protocol::HTTP10 do it_behaves_like Async::HTTP::AProtocol - it_behaves_like Async::HTTP::AGracefulStop end