Avoid reclaiming frames for dead streams. (#262)
In `clear_queue` we drop all the queued frames for a stream, but this doesn't take into account a buffered frame inside of the `FramedWrite`. This can lead to a panic when `reclaim_frame` tries to recover a frame onto a stream that has already been destroyed, or in general cause wrong behaviour. Instead, let's keep track of what frame is currently in-flight; then, when we `clear_queue` a stream with an in-flight data frame, mark the frame to be dropped instead of reclaimed.
This commit is contained in:
		
				
					committed by
					
						 Carl Lerche
						Carl Lerche
					
				
			
			
				
	
			
			
			
						parent
						
							11f914150e
						
					
				
				
					commit
					558e6b6e6c
				
			| @@ -8,7 +8,7 @@ use codec::UserError::*; | ||||
|  | ||||
| use bytes::buf::Take; | ||||
|  | ||||
| use std::{cmp, fmt}; | ||||
| use std::{cmp, fmt, mem}; | ||||
| use std::io; | ||||
|  | ||||
| /// # Warning | ||||
| @@ -48,6 +48,19 @@ pub(super) struct Prioritize { | ||||
|  | ||||
|     /// Stream ID of the last stream opened. | ||||
|     last_opened_id: StreamId, | ||||
|  | ||||
|     /// What `DATA` frame is currently being sent in the codec. | ||||
|     in_flight_data_frame: InFlightData, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq)] | ||||
| enum InFlightData { | ||||
|     /// There is no `DATA` frame in flight. | ||||
|     Nothing, | ||||
|     /// There is a `DATA` frame in flight belonging to the given stream. | ||||
|     DataFrame(store::Key), | ||||
|     /// There was a `DATA` frame, but the stream's queue was since cleared. | ||||
|     Drop, | ||||
| } | ||||
|  | ||||
| pub(crate) struct Prioritized<B> { | ||||
| @@ -79,7 +92,8 @@ impl Prioritize { | ||||
|             pending_capacity: store::Queue::new(), | ||||
|             pending_open: store::Queue::new(), | ||||
|             flow: flow, | ||||
|             last_opened_id: StreamId::ZERO | ||||
|             last_opened_id: StreamId::ZERO, | ||||
|             in_flight_data_frame: InFlightData::Nothing, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -456,6 +470,10 @@ impl Prioritize { | ||||
|                 Some(frame) => { | ||||
|                     trace!("writing frame={:?}", frame); | ||||
|  | ||||
|                     debug_assert_eq!(self.in_flight_data_frame, InFlightData::Nothing); | ||||
|                     if let Frame::Data(ref frame) = frame { | ||||
|                         self.in_flight_data_frame = InFlightData::DataFrame(frame.payload().stream); | ||||
|                     } | ||||
|                     dst.buffer(frame).ok().expect("invalid frame"); | ||||
|  | ||||
|                     // Ensure the codec is ready to try the loop again. | ||||
| @@ -503,12 +521,23 @@ impl Prioritize { | ||||
|             trace!( | ||||
|                 "  -> reclaimed; frame={:?}; sz={}", | ||||
|                 frame, | ||||
|                 frame.payload().remaining() | ||||
|                 frame.payload().inner.get_ref().remaining() | ||||
|             ); | ||||
|  | ||||
|             let mut eos = false; | ||||
|             let key = frame.payload().stream; | ||||
|  | ||||
|             match mem::replace(&mut self.in_flight_data_frame, InFlightData::Nothing) { | ||||
|                 InFlightData::Nothing => panic!("wasn't expecting a frame to reclaim"), | ||||
|                 InFlightData::Drop => { | ||||
|                     trace!("not reclaiming frame for cancelled stream"); | ||||
|                     return false; | ||||
|                 } | ||||
|                 InFlightData::DataFrame(k) => { | ||||
|                     debug_assert_eq!(k, key); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             let mut frame = frame.map(|prioritized| { | ||||
|                 // TODO: Ensure fully written | ||||
|                 eos = prioritized.end_of_stream; | ||||
| @@ -558,6 +587,12 @@ impl Prioritize { | ||||
|  | ||||
|         stream.buffered_send_data = 0; | ||||
|         stream.requested_send_capacity = 0; | ||||
|         if let InFlightData::DataFrame(key) = self.in_flight_data_frame { | ||||
|             if stream.key() == key { | ||||
|                 // This stream could get cleaned up now - don't allow the buffered frame to get reclaimed. | ||||
|                 self.in_flight_data_frame = InFlightData::Drop; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn pop_frame<B>( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user