-
Notifications
You must be signed in to change notification settings - Fork 190
RUST-384 Flush connection read buffer before checking back into pool #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
92b933c
f7c8794
0a1ae23
4ad5c3a
d6c2298
77d897d
67574d2
b995f00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ use std::{ | |
| }; | ||
|
|
||
| use derivative::Derivative; | ||
| use futures::io::AsyncReadExt; | ||
|
|
||
| use self::wire::Message; | ||
| use super::ConnectionPoolInner; | ||
|
|
@@ -41,6 +42,13 @@ pub struct ConnectionInfo { | |
| pub address: StreamAddress, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Default)] | ||
| pub(crate) struct PartialMessageState { | ||
| bytes_remaining: usize, | ||
| needs_response: bool, | ||
| unfinished_write: bool, | ||
| } | ||
|
|
||
| /// A wrapper around Stream that contains all the CMAP information needed to maintain a connection. | ||
| #[derive(Derivative)] | ||
| #[derivative(Debug)] | ||
|
|
@@ -63,6 +71,8 @@ pub(crate) struct Connection { | |
|
|
||
| stream: AsyncStream, | ||
|
|
||
| partial_message_state: PartialMessageState, | ||
|
|
||
| #[derivative(Debug = "ignore")] | ||
| handler: Option<Arc<dyn CmapEventHandler>>, | ||
| } | ||
|
|
@@ -90,6 +100,7 @@ impl Connection { | |
| address, | ||
| handler: options.and_then(|options| options.event_handler), | ||
| stream_description: None, | ||
| partial_message_state: Default::default(), | ||
| }; | ||
|
|
||
| Ok(conn) | ||
|
|
@@ -206,10 +217,26 @@ impl Connection { | |
| command: Command, | ||
| request_id: impl Into<Option<i32>>, | ||
| ) -> Result<CommandResponse> { | ||
| if self.partial_message_state.needs_response { | ||
| Message::read_from(&mut self.stream, &mut self.partial_message_state).await?; | ||
| } | ||
|
|
||
| if self.partial_message_state.bytes_remaining > 0 { | ||
| let mut bytes = vec![0u8; self.partial_message_state.bytes_remaining]; | ||
| self.stream.read_exact(&mut bytes).await?; | ||
| self.partial_message_state.bytes_remaining = 0; | ||
| } | ||
|
|
||
| let message = Message::with_command(command, request_id.into()); | ||
| message.write_to(&mut self.stream).await?; | ||
| message | ||
| .write_to(&mut self.stream, &mut self.partial_message_state) | ||
| .await?; | ||
|
|
||
| self.partial_message_state.unfinished_write = false; | ||
| self.partial_message_state.needs_response = true; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could the future be dropped between after the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if the future gets dropped midway through There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we send an only partially complete message to the server on a connection, then I'm not sure there's much we can do about that. I think the only two options to finish sending the message on the next write and then read and throw away the response or to just close the connection. My instinct is that an unfinished write will be a lot less common than an unfinished read, as there isn't going to be as much of a delay in writing the data as there is for waiting for the response to come back, so closing the connection in this case doesn't seem like quite as big a deal. I think there's also a case to be made that sending an unfinished message when the user stopped awaiting the future is a lot more likely to cause unexpected behavior for the user, since unlike reading the response of an already sent message, completing a sent message could have side effects on the database. Because of this, I've updated the PR to drop the connection in the case of an unfinished write. |
||
|
|
||
| let response_message = Message::read_from(&mut self.stream).await?; | ||
| let response_message = | ||
| Message::read_from(&mut self.stream, &mut self.partial_message_state).await?; | ||
| CommandResponse::new(self.address.clone(), response_message) | ||
| } | ||
|
|
||
|
|
@@ -247,6 +274,7 @@ impl Connection { | |
| stream: std::mem::replace(&mut self.stream, AsyncStream::Null), | ||
| handler: self.handler.take(), | ||
| stream_description: self.stream_description.take(), | ||
| partial_message_state: self.partial_message_state.clone(), | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -262,13 +290,19 @@ impl Drop for Connection { | |
| // dropped while it's not checked out. This means that the pool called the `close_and_drop` | ||
| // helper explicitly, so we don't add it back to the pool or emit any events. | ||
| if let Some(ref weak_pool_ref) = self.pool { | ||
| if let Some(strong_pool_ref) = weak_pool_ref.upgrade() { | ||
| let dropped_connection_state = self.take(); | ||
| RUNTIME.execute(async move { | ||
| strong_pool_ref | ||
| .check_in(dropped_connection_state.into()) | ||
| .await; | ||
| }); | ||
| // If there's an unfinished write on the connection, then we close the connection rather | ||
| // than returning it to the pool. We do this because finishing the write could | ||
| // potentially cause surprising side-effects for the user, who might expect | ||
| // the operation not to occur due to the future being dropped. | ||
| if !self.partial_message_state.unfinished_write { | ||
| if let Some(strong_pool_ref) = weak_pool_ref.upgrade() { | ||
| let dropped_connection_state = self.take(); | ||
| RUNTIME.execute(async move { | ||
| strong_pool_ref | ||
| .check_in(dropped_connection_state.into()) | ||
| .await; | ||
| }); | ||
| } | ||
| } else { | ||
| self.close(ConnectionClosedReason::PoolClosed); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this closed reason needs to be changed, maybe to |
||
| } | ||
|
|
@@ -295,6 +329,7 @@ struct DroppedConnectionState { | |
| #[derivative(Debug = "ignore")] | ||
| handler: Option<Arc<dyn CmapEventHandler>>, | ||
| stream_description: Option<StreamDescription>, | ||
| partial_message_state: PartialMessageState, | ||
| } | ||
|
|
||
| impl Drop for DroppedConnectionState { | ||
|
|
@@ -323,6 +358,7 @@ impl From<DroppedConnectionState> for Connection { | |
| stream_description: state.stream_description.take(), | ||
| ready_and_available_time: None, | ||
| pool: None, | ||
| partial_message_state: state.partial_message_state.clone(), | ||
patrickfreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ use super::{ | |
| }; | ||
| use crate::{ | ||
| bson_util::async_encoding, | ||
| cmap::conn::command::Command, | ||
| cmap::conn::{command::Command, PartialMessageState}, | ||
| error::{ErrorKind, Result}, | ||
| runtime::{AsyncLittleEndianRead, AsyncLittleEndianWrite, AsyncStream}, | ||
| }; | ||
|
|
@@ -75,33 +75,38 @@ impl Message { | |
| } | ||
|
|
||
| /// Reads bytes from `reader` and deserializes them into a Message. | ||
| pub(crate) async fn read_from(reader: &mut AsyncStream) -> Result<Self> { | ||
| let header = Header::read_from(reader).await?; | ||
| let mut length_remaining = header.length - Header::LENGTH as i32; | ||
| pub(crate) async fn read_from( | ||
| reader: &mut AsyncStream, | ||
| partial_message_state: &mut PartialMessageState, | ||
| ) -> Result<Self> { | ||
| let header = Header::read_from(reader, partial_message_state).await?; | ||
|
|
||
| let flags = MessageFlags::from_bits_truncate(reader.read_u32().await?); | ||
| length_remaining -= std::mem::size_of::<u32>() as i32; | ||
| partial_message_state.bytes_remaining -= std::mem::size_of::<u32>(); | ||
|
|
||
| let mut count_reader = CountReader::new(reader); | ||
| let mut sections = Vec::new(); | ||
|
|
||
| while length_remaining - count_reader.bytes_read() as i32 > 4 { | ||
| while partial_message_state.bytes_remaining - count_reader.bytes_read() > 4 { | ||
| sections.push(MessageSection::read(&mut count_reader).await?); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the state needs to be propagated down here too. I wonder if this would be simpler if we just spawned a task via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate on what you mean by spawning a task in execute operation? Do you mean running There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, except a channel wouldn't be needed. We could just use our There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking about it more, it would probably be safest to do the entirety of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just tried to play around with this; it runs into some issues due to the fact that we can only spawn futures that are 'static, which won't be the case if we need to capture over any references. When trying to work around this, I ran into issues with the cursor's getMore operation, so I tried rebasing your sessions PR to see if the new cursor implementation avoided this issue, but this actually made things even more difficult due to the need to capture a reference to the session, which doesn't have the static lifetime. We might be able to make this less error-prone by doing it on our AsyncStream type itself, which would make it impossible to accidentally read or write without checking the partial state. |
||
| } | ||
|
|
||
| length_remaining -= count_reader.bytes_read() as i32; | ||
| partial_message_state.bytes_remaining -= count_reader.bytes_read(); | ||
|
|
||
| let mut checksum = None; | ||
|
|
||
| if length_remaining == 4 && flags.contains(MessageFlags::CHECKSUM_PRESENT) { | ||
| if partial_message_state.bytes_remaining == 4 | ||
| && flags.contains(MessageFlags::CHECKSUM_PRESENT) | ||
| { | ||
| checksum = Some(reader.read_u32().await?); | ||
| } else if length_remaining != 0 { | ||
| } else if partial_message_state.bytes_remaining != 0 { | ||
| return Err(ErrorKind::OperationError { | ||
| message: format!( | ||
| "The server indicated that the reply would be {} bytes long, but it instead \ | ||
| was {}", | ||
| header.length, | ||
| header.length - length_remaining + count_reader.bytes_read() as i32, | ||
| header.length as usize - partial_message_state.bytes_remaining | ||
| + count_reader.bytes_read(), | ||
| ), | ||
| } | ||
| .into()); | ||
|
|
@@ -117,7 +122,11 @@ impl Message { | |
| } | ||
|
|
||
| /// Serializes the Message to bytes and writes them to `writer`. | ||
| pub(crate) async fn write_to(&self, writer: &mut AsyncStream) -> Result<()> { | ||
| pub(crate) async fn write_to( | ||
| &self, | ||
| writer: &mut AsyncStream, | ||
| partial_message_state: &mut PartialMessageState, | ||
| ) -> Result<()> { | ||
| let mut sections_bytes = Vec::new(); | ||
|
|
||
| for section in &self.sections { | ||
|
|
@@ -140,7 +149,7 @@ impl Message { | |
| op_code: OpCode::Message, | ||
| }; | ||
|
|
||
| header.write_to(writer).await?; | ||
| header.write_to(writer, partial_message_state).await?; | ||
| writer.write_u32(self.flags.bits()).await?; | ||
| writer.write_all(§ions_bytes).await?; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.