Files
garage/src/net/recv.rs
T
Gwen Lg 6dbcdcd784 style: improve use of std api
- replace get(0) by first()
lint message: accessing first element with `objs.get(0)`
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#get_first
- use io::Error::other method available since Rust 1.87
lint message: this can be `std::io::Error::other(_)`
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#io_other_error
- implement From<_> instead of Into<_>
lint message: an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true
help: `impl From<Local> for Foreign` is allowed by the orphan rules, for more information see
            https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#from_over_into
- use next_back instead of rev + next
lint message: manual backwards iteration
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#manual_next_back
- use saturating_sub instead of manual implement it
lint message: manual arithmetic check found
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#implicit_saturating_sub
- add Default implementation for Checksummer
use derived Default ans use it in new, as new set all value to None
lint message: you should consider adding a `Default` implementation for `Checksummer`
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#new_without_default
- use `any` instead of `find` + `is_some`
lint message: called `is_some()` after searching an `Iterator` with `find`
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#search_is_some
- replace `and_then` + `Some(_)` with `map`
lint message: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#bind_instead_of_map
- replace `skip_while` + `next` with `find`
lint message: called `skip_while(<p>).next()` on an `Iterator`
help: this is more succinctly expressed by calling `.find(!<p>)` instead
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#skip_while_next
- use `matches!` instead of manual implementation
lint message: match expression looks like `matches!` macro
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#match_like_matches_macro
- use `keys` and `values methods insteads of iterating and ignoring either the keys or values.
help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#iter_kv_map
2026-01-29 20:49:28 +01:00

149 lines
4.2 KiB
Rust

use std::collections::HashMap;
use std::sync::Arc;
use bytes::Bytes;
use log::*;
use futures::AsyncReadExt;
use tokio::sync::mpsc;
use crate::error::*;
use crate::send::*;
use crate::stream::*;
/// Structure to warn when the sender is dropped before end of stream was reached, like when
/// connection to some remote drops while transmitting data
struct Sender {
inner: Option<mpsc::UnboundedSender<Packet>>,
}
impl Sender {
fn new(inner: mpsc::UnboundedSender<Packet>) -> Self {
Sender { inner: Some(inner) }
}
fn send(&self, packet: Packet) {
let _ = self.inner.as_ref().unwrap().send(packet);
}
fn end(&mut self) {
self.inner = None;
}
}
impl Drop for Sender {
fn drop(&mut self) {
if let Some(inner) = self.inner.take() {
let _ = inner.send(Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"Netapp connection dropped before end of stream",
)));
}
}
}
/// The RecvLoop trait, which is implemented both by the client and the server
/// connection objects (ServerConn and ClientConn) adds a method `.recv_loop()`
/// and a prototype of a handler for received messages `.recv_handler()` that
/// must be filled by implementors. `.recv_loop()` receives messages in a loop
/// according to the protocol defined above: chunks of message in progress of being
/// received are stored in a buffer, and when the last chunk of a message is received,
/// the full message is passed to the receive handler.
pub(crate) trait RecvLoop: Sync + 'static {
fn recv_handler(self: &Arc<Self>, id: RequestID, stream: ByteStream);
fn cancel_handler(self: &Arc<Self>, _id: RequestID) {}
async fn recv_loop<R>(self: Arc<Self>, mut read: R, debug_name: String) -> Result<(), Error>
where
R: AsyncReadExt + Unpin + Send + Sync,
{
let mut streams: HashMap<RequestID, Sender> = HashMap::new();
loop {
trace!(
"recv_loop({}): in_progress = {:?}",
debug_name,
streams.keys().collect::<Vec<_>>()
);
let mut header_id = [0u8; RequestID::BITS as usize / 8];
match read.read_exact(&mut header_id[..]).await {
Ok(_) => (),
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e.into()),
};
let id = RequestID::from_be_bytes(header_id);
let mut header_size = [0u8; ChunkLength::BITS as usize / 8];
read.read_exact(&mut header_size[..]).await?;
let size = ChunkLength::from_be_bytes(header_size);
if size == CANCEL_REQUEST {
if let Some(mut stream) = streams.remove(&id) {
stream.send(Err(std::io::Error::other("netapp: cancel requested")));
stream.end();
}
self.cancel_handler(id);
continue;
}
let has_cont = (size & CHUNK_FLAG_HAS_CONTINUATION) != 0;
let is_error = (size & CHUNK_FLAG_ERROR) != 0;
let size = (size & CHUNK_LENGTH_MASK) as usize;
let mut next_slice = vec![0; size as usize];
read.read_exact(&mut next_slice[..]).await?;
let packet = if is_error {
let kind = u8_to_io_errorkind(next_slice[0]);
let msg =
std::str::from_utf8(&next_slice[1..]).unwrap_or("<invalid utf8 error message>");
debug!(
"recv_loop({}): got id {}, error {:?}: {}",
debug_name, id, kind, msg
);
Some(Err(std::io::Error::new(kind, msg.to_string())))
} else {
trace!(
"recv_loop({}): got id {}, size {}, has_cont {}",
debug_name,
id,
size,
has_cont
);
if !next_slice.is_empty() {
Some(Ok(Bytes::from(next_slice)))
} else {
None
}
};
let mut sender = if let Some(send) = streams.remove(&(id)) {
send
} else {
let (send, recv) = mpsc::unbounded_channel();
trace!("recv_loop({}): id {} is new channel", debug_name, id);
self.recv_handler(
id,
Box::pin(tokio_stream::wrappers::UnboundedReceiverStream::new(recv)),
);
Sender::new(send)
};
if let Some(packet) = packet {
// If we cannot put packet in channel, it means that the
// receiving end of the channel is disconnected.
// We still need to reach eos before dropping this sender
sender.send(packet);
}
if has_cont {
assert!(!is_error);
streams.insert(id, sender);
} else {
trace!("recv_loop({}): close channel id {}", debug_name, id);
sender.end();
}
}
Ok(())
}
}