diff --git a/lib/protocol/http2/connection.rb b/lib/protocol/http2/connection.rb index 0be2ce8..c4c0561 100644 --- a/lib/protocol/http2/connection.rb +++ b/lib/protocol/http2/connection.rb @@ -298,12 +298,13 @@ def receive_goaway(frame) # Write a single frame to the connection. # @parameter frame [Frame] The frame to write. + # @raises [EOFError] If the connection is closed. def write_frame(frame) synchronize do - @framer.write_frame(frame) + open_framer.write_frame(frame) end - @framer.flush + open_framer.flush end # Write multiple frames within a synchronized block. @@ -311,15 +312,11 @@ def write_frame(frame) # @parameter framer [Framer] The framer instance. # @raises [EOFError] If the connection is closed. def write_frames - if @framer - synchronize do - yield @framer - end - - @framer.flush - else - raise EOFError, "Connection closed!" + synchronize do + yield open_framer end + + open_framer.flush end # Update local settings and adjust stream window capacities. @@ -668,6 +665,13 @@ def receive_continuation(frame) def receive_frame(frame) # ignore. end + + private + + # The connection is closed, and `@framer` cleared, by the task reading frames, which can happen while a writer waits for the lock or for the socket, so it is checked again at each use. + def open_framer + @framer or raise EOFError, "Connection closed!" + end end end end diff --git a/releases.md b/releases.md index 8c74acd..904c5ca 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - `Connection#write_frame` and `#write_frames` raise `EOFError` when the connection closes while the write waits for the lock or the socket, rather than `NoMethodError` on the cleared framer. + ## v0.29.1 - Sign releases with the Socketry release certificate. diff --git a/test/protocol/http2/connection.rb b/test/protocol/http2/connection.rb index 289b1ae..807300d 100644 --- a/test/protocol/http2/connection.rb +++ b/test/protocol/http2/connection.rb @@ -104,6 +104,30 @@ end.to raise_exception(EOFError, message: be =~ /Connection closed/) end + with "a connection which closes while a write waits for the lock" do + def before + super + + def connection.synchronize + close + super + end + end + + it "can't write a frame" do + expect do + connection.write_frame(Protocol::HTTP2::PingFrame.new) + end.to raise_exception(EOFError, message: be =~ /Connection closed/) + end + + it "can't write frames" do + expect do + connection.write_frames do |framer| + end + end.to raise_exception(EOFError, message: be =~ /Connection closed/) + end + end + with "#close" do it "closes the connection without active streams" do # No active streams