@@ -1486,16 +1486,17 @@ def _receive_frame(self, frame):
14861486 # I don't love using __class__ here, maybe reconsider it.
14871487 frames , events = self ._frame_dispatch_table [frame .__class__ ](frame )
14881488 except StreamClosedError as e :
1489- # If the stream was closed by RST_STREAM, we just send a RST_STREAM
1490- # to the remote peer. Otherwise, this is a connection error, and so
1491- # we will re-raise to trigger one.
1492- if self ._stream_is_closed_by_reset (e .stream_id ):
1489+ if e ._connection_error :
1490+ raise
1491+ else :
1492+ # A StreamClosedError is raised when a stream wants to send a
1493+ # RST_STREAM frame. Since the H2Stream is the authoritative source
1494+ # of its own state, we always respect its wishes here.
1495+
14931496 f = RstStreamFrame (e .stream_id )
14941497 f .error_code = e .error_code
14951498 self ._prepare_for_sending ([f ])
14961499 events = e ._events
1497- else :
1498- raise
14991500 except StreamIDTooLowError as e :
15001501 # The stream ID seems invalid. This may happen when the closed
15011502 # stream has been cleaned up, or when the remote peer has opened a
@@ -1506,10 +1507,18 @@ def _receive_frame(self, frame):
15061507 # is either a stream error or a connection error.
15071508 if self ._stream_is_closed_by_reset (e .stream_id ):
15081509 # Closed by RST_STREAM is a stream error.
1509- f = RstStreamFrame (e .stream_id )
1510- f .error_code = ErrorCodes .STREAM_CLOSED
1511- self ._prepare_for_sending ([f ])
1512- events = []
1510+ if self ._stream_is_closed_by_peer_reset (e .stream_id ):
1511+ self ._closed_streams [e .stream_id ] = StreamClosedBy .SEND_RST_STREAM
1512+
1513+ f = RstStreamFrame (e .stream_id )
1514+ f .error_code = ErrorCodes .STREAM_CLOSED
1515+ self ._prepare_for_sending ([f ])
1516+ events = []
1517+ else :
1518+ # Stream was closed by a local reset. A stream SHOULD NOT
1519+ # send additional RST_STREAM frames. Ignore.
1520+ events = []
1521+ pass
15131522 elif self ._stream_is_closed_by_end (e .stream_id ):
15141523 # Closed by END_STREAM is a connection error.
15151524 raise StreamClosedError (e .stream_id )
@@ -1655,13 +1664,32 @@ def _handle_data_on_closed_stream(self, events, exc, frame):
16551664 "auto-emitted a WINDOW_UPDATE by %d" ,
16561665 frame .stream_id , conn_increment
16571666 )
1658- f = RstStreamFrame (exc .stream_id )
1659- f .error_code = exc .error_code
1660- frames .append (f )
1661- self .config .logger .debug (
1662- "Stream %d already CLOSED or cleaned up - "
1663- "auto-emitted a RST_FRAME" % frame .stream_id
1664- )
1667+
1668+ send_rst_frame = False
1669+
1670+ if frame .stream_id in self ._closed_streams :
1671+ closed_by = self ._closed_streams [frame .stream_id ]
1672+
1673+ if closed_by == StreamClosedBy .RECV_RST_STREAM :
1674+ self ._closed_streams [frame .stream_id ] = StreamClosedBy .SEND_RST_STREAM
1675+ send_rst_frame = True
1676+ elif closed_by == StreamClosedBy .SEND_RST_STREAM :
1677+ # Do not send additional RST_STREAM frames
1678+ pass
1679+ else :
1680+ # Protocol error
1681+ raise StreamClosedError (frame .stream_id )
1682+ else :
1683+ send_rst_frame = True
1684+
1685+ if send_rst_frame :
1686+ f = RstStreamFrame (exc .stream_id )
1687+ f .error_code = exc .error_code
1688+ frames .append (f )
1689+ self .config .logger .debug (
1690+ "Stream %d already CLOSED or cleaned up - "
1691+ "auto-emitted a RST_FRAME" % frame .stream_id
1692+ )
16651693 return frames , events + exc ._events
16661694
16671695 def _receive_data_frame (self , frame ):
@@ -1677,6 +1705,8 @@ def _receive_data_frame(self, frame):
16771705 flow_controlled_length
16781706 )
16791707
1708+ stream = None
1709+
16801710 try :
16811711 stream = self ._get_stream_by_id (frame .stream_id )
16821712 frames , stream_events = stream .receive_data (
@@ -1685,6 +1715,11 @@ def _receive_data_frame(self, frame):
16851715 flow_controlled_length
16861716 )
16871717 except StreamClosedError as e :
1718+ # If this exception originated from a yet-to-be clenaed up stream,
1719+ # check if it should be a connection error
1720+ if stream is not None and e ._connection_error :
1721+ raise
1722+
16881723 # This stream is either marked as CLOSED or already gone from our
16891724 # internal state.
16901725 return self ._handle_data_on_closed_stream (events , e , frame )
@@ -1962,7 +1997,7 @@ def _stream_closed_by(self, stream_id):
19621997 before opening this one.
19631998 """
19641999 if stream_id in self .streams :
1965- return self .streams [stream_id ].closed_by
2000+ return self .streams [stream_id ].closed_by # pragma: no cover
19662001 if stream_id in self ._closed_streams :
19672002 return self ._closed_streams [stream_id ]
19682003 return None
@@ -1976,6 +2011,14 @@ def _stream_is_closed_by_reset(self, stream_id):
19762011 StreamClosedBy .RECV_RST_STREAM , StreamClosedBy .SEND_RST_STREAM
19772012 )
19782013
2014+ def _stream_is_closed_by_peer_reset (self , stream_id ):
2015+ """
2016+ Returns ``True`` if the stream was closed by sending or receiving a
2017+ RST_STREAM frame. Returns ``False`` otherwise.
2018+ """
2019+ return (self ._stream_closed_by (stream_id ) ==
2020+ StreamClosedBy .RECV_RST_STREAM )
2021+
19792022 def _stream_is_closed_by_end (self , stream_id ):
19802023 """
19812024 Returns ``True`` if the stream was closed by sending or receiving an
0 commit comments