Polish off streaming reader support
This commit is contained in:
parent
86d94e62fa
commit
3b9f9ab7cd
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -845,6 +845,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"bare-metrics-recorder",
|
"bare-metrics-recorder",
|
||||||
"clap",
|
"clap",
|
||||||
|
"dashmap 5.0.0",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fuser",
|
"fuser",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -16,6 +16,7 @@ tracing-futures = { version = "0.2.5", features = ["tokio"] }
|
|||||||
|
|
||||||
# Asynchronous
|
# Asynchronous
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
|
dashmap = "5.0.0"
|
||||||
|
|
||||||
# Serialisation
|
# Serialisation
|
||||||
serde = { version = "1.0.133", features = ["derive", "rc"] }
|
serde = { version = "1.0.133", features = ["derive", "rc"] }
|
||||||
|
@ -6,6 +6,8 @@ use std::sync::Arc;
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct OlivefsClientConfiguration {
|
pub struct OlivefsClientConfiguration {
|
||||||
pub connection: ConnectionConfiguration,
|
pub connection: ConnectionConfiguration,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
pub performance: PerformanceConfiguration,
|
pub performance: PerformanceConfiguration,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ pub struct ConnectionConfiguration {
|
|||||||
pub keep_alive: u32,
|
pub keep_alive: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||||
pub struct PerformanceConfiguration {
|
pub struct PerformanceConfiguration {
|
||||||
/// If specified, we will use a streaming reader to improve read performance.
|
/// If specified, we will use a streaming reader to improve read performance.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -9,6 +9,7 @@ use std::collections::HashMap;
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
|
use crate::configuration::StreamingReaderConfig;
|
||||||
use crate::Requester;
|
use crate::Requester;
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use fuser::consts::FOPEN_DIRECT_IO;
|
use fuser::consts::FOPEN_DIRECT_IO;
|
||||||
@ -24,7 +25,9 @@ pub mod encryption;
|
|||||||
|
|
||||||
pub const ROOT_INODE: u64 = 0x01;
|
pub const ROOT_INODE: u64 = 0x01;
|
||||||
|
|
||||||
pub struct OliveFilesystemSettings {}
|
pub struct OliveFilesystemSettings {
|
||||||
|
pub streaming_reader: Option<Arc<StreamingReaderConfig>>,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO support multiple filesystems per FUSE fs so that operations between multiple of them
|
// TODO support multiple filesystems per FUSE fs so that operations between multiple of them
|
||||||
// can be made efficient.
|
// can be made efficient.
|
||||||
@ -301,7 +304,7 @@ impl Filesystem for OliveFilesystem {
|
|||||||
/// structure in <fuse_common.h> for more details.
|
/// structure in <fuse_common.h> for more details.
|
||||||
fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) {
|
fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) {
|
||||||
let requester = self.requester.clone();
|
let requester = self.requester.clone();
|
||||||
// let buffers = self.buffers.clone();
|
let streaming_reader_opts = self.settings.streaming_reader.clone();
|
||||||
|
|
||||||
// TODO This needs more careful consideration before using to write files
|
// TODO This needs more careful consideration before using to write files
|
||||||
// How does truncation work?
|
// How does truncation work?
|
||||||
@ -350,6 +353,21 @@ impl Filesystem for OliveFilesystem {
|
|||||||
// rather than letting the kernel assume the getattr size is valid
|
// rather than letting the kernel assume the getattr size is valid
|
||||||
// and caching up to that point.
|
// and caching up to that point.
|
||||||
// We might wind up wanting to do our own buffering...
|
// We might wind up wanting to do our own buffering...
|
||||||
|
|
||||||
|
if let Some(config) = streaming_reader_opts {
|
||||||
|
requester
|
||||||
|
.start_streaming_reader(file_handle, config)
|
||||||
|
.await?;
|
||||||
|
debug!(
|
||||||
|
"open(ino: {:#x?}) = fh {:?} (streaming reader: yes)",
|
||||||
|
ino, file_handle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"open(ino: {:#x?}) = fh {:?} (streaming reader: no)",
|
||||||
|
ino, file_handle
|
||||||
|
);
|
||||||
|
|
||||||
reply.opened(file_handle as u64, FOPEN_DIRECT_IO);
|
reply.opened(file_handle as u64, FOPEN_DIRECT_IO);
|
||||||
}
|
}
|
||||||
DataResponse::Error { code, message } => {
|
DataResponse::Error { code, message } => {
|
||||||
|
@ -64,7 +64,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
requester: Arc::new(requester),
|
requester: Arc::new(requester),
|
||||||
buffers: Default::default(),
|
buffers: Default::default(),
|
||||||
tokio_runtime: Handle::current(),
|
tokio_runtime: Handle::current(),
|
||||||
settings: OliveFilesystemSettings {},
|
settings: OliveFilesystemSettings {
|
||||||
|
streaming_reader: config.performance.streaming_reader.clone(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convenience: try to unmount the filesystem at that path if there is one
|
// Convenience: try to unmount the filesystem at that path if there is one
|
||||||
|
@ -2,16 +2,20 @@ use log::info;
|
|||||||
use quinn::{Endpoint, RecvStream, SendStream};
|
use quinn::{Endpoint, RecvStream, SendStream};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::{anyhow, bail};
|
||||||
use rustls::{PrivateKey, RootCertStore};
|
use rustls::{PrivateKey, RootCertStore};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
use dashmap::mapref::entry::Entry;
|
||||||
|
use dashmap::DashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::configuration::StreamingReaderConfig;
|
||||||
|
use crate::requester::streaming_reader::StreamingReader;
|
||||||
use olivefs_common::io::read_file;
|
use olivefs_common::io::read_file;
|
||||||
use olivefs_common::messages::{
|
use olivefs_common::messages::{
|
||||||
DataCommand, DataResponse, DirectoryEntry, FileMetadata, OpenMode, VnodeId,
|
DataCommand, DataResponse, DirectoryEntry, FileMetadata, OpenMode, VnodeId,
|
||||||
@ -25,6 +29,7 @@ pub mod streaming_reader;
|
|||||||
|
|
||||||
pub struct Requester {
|
pub struct Requester {
|
||||||
internal: RequesterInternal,
|
internal: RequesterInternal,
|
||||||
|
streaming_readers: DashMap<u32, StreamingReader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RequesterInternal {
|
pub struct RequesterInternal {
|
||||||
@ -152,7 +157,10 @@ impl RequesterInternal {
|
|||||||
|
|
||||||
impl Requester {
|
impl Requester {
|
||||||
pub fn new(internal: RequesterInternal) -> Requester {
|
pub fn new(internal: RequesterInternal) -> Requester {
|
||||||
Requester { internal }
|
Requester {
|
||||||
|
internal,
|
||||||
|
streaming_readers: Default::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn getattr(&self, vnode: VnodeId) -> anyhow::Result<DataResponse<FileMetadata>> {
|
pub async fn getattr(&self, vnode: VnodeId) -> anyhow::Result<DataResponse<FileMetadata>> {
|
||||||
@ -195,9 +203,15 @@ impl Requester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn release(&self, file_handle: u32) -> anyhow::Result<DataResponse<()>> {
|
pub async fn release(&self, file_handle: u32) -> anyhow::Result<DataResponse<()>> {
|
||||||
self.internal
|
if let Some((_, streaming_reader)) = self.streaming_readers.remove(&file_handle) {
|
||||||
.command(&DataCommand::ReleaseFile { file_handle })
|
let (tx, rx) = streaming_reader.close().await?;
|
||||||
.await
|
self.internal.release_stream_to_pool(tx, rx).await?;
|
||||||
|
Ok(DataResponse::Success(()))
|
||||||
|
} else {
|
||||||
|
self.internal
|
||||||
|
.command(&DataCommand::ReleaseFile { file_handle })
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read(
|
pub async fn read(
|
||||||
@ -206,12 +220,33 @@ impl Requester {
|
|||||||
offset: i64,
|
offset: i64,
|
||||||
size: u32,
|
size: u32,
|
||||||
) -> anyhow::Result<DataResponse<Vec<u8>>> {
|
) -> anyhow::Result<DataResponse<Vec<u8>>> {
|
||||||
self.internal
|
if let Some(streaming_reader) = self.streaming_readers.get(&file_handle) {
|
||||||
.command(&DataCommand::ReadFile {
|
streaming_reader.read(offset, size).await
|
||||||
file_handle,
|
} else {
|
||||||
offset,
|
self.internal
|
||||||
size,
|
.command(&DataCommand::ReadFile {
|
||||||
})
|
file_handle,
|
||||||
.await
|
offset,
|
||||||
|
size,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_streaming_reader(
|
||||||
|
&self,
|
||||||
|
file_handle: u32,
|
||||||
|
config: Arc<StreamingReaderConfig>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
match self.streaming_readers.entry(file_handle) {
|
||||||
|
Entry::Occupied(_oe) => {
|
||||||
|
bail!("Streaming reader already started for FH{:?}", file_handle);
|
||||||
|
}
|
||||||
|
Entry::Vacant(ve) => {
|
||||||
|
let (tx, rx) = self.internal.acquire_stream_from_pool().await?;
|
||||||
|
ve.insert(StreamingReader::new(file_handle, tx, rx, config));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ pub struct StreamingReaderState {
|
|||||||
pub responses: Receiver<DataResponse<Vec<u8>>>,
|
pub responses: Receiver<DataResponse<Vec<u8>>>,
|
||||||
pub command_rx: Receiver<StreamingReaderCmd>,
|
pub command_rx: Receiver<StreamingReaderCmd>,
|
||||||
pub rx_retrieval: OneshotReceiver<RecvStream>,
|
pub rx_retrieval: OneshotReceiver<RecvStream>,
|
||||||
pub read_queue: VecDeque<(Range<i64>, OneshotSender<Vec<u8>>)>,
|
pub read_queue: VecDeque<(Range<i64>, OneshotSender<DataResponse<Vec<u8>>>)>,
|
||||||
pub tx: SendStream,
|
pub tx: SendStream,
|
||||||
pub close_command_reply: Option<OneshotSender<(SendStream, RecvStream)>>,
|
pub close_command_reply: Option<OneshotSender<(SendStream, RecvStream)>>,
|
||||||
pub running: bool,
|
pub running: bool,
|
||||||
@ -67,7 +67,7 @@ pub enum StreamingReaderCmd {
|
|||||||
Read {
|
Read {
|
||||||
offset: i64,
|
offset: i64,
|
||||||
size: u32,
|
size: u32,
|
||||||
reply: OneshotSender<Vec<u8>>,
|
reply: OneshotSender<DataResponse<Vec<u8>>>,
|
||||||
},
|
},
|
||||||
Close {
|
Close {
|
||||||
/// The response gives the stream back to the application so it can be put back into the
|
/// The response gives the stream back to the application so it can be put back into the
|
||||||
@ -100,7 +100,7 @@ impl StreamingReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read(&self, offset: i64, size: u32) -> anyhow::Result<Vec<u8>> {
|
pub async fn read(&self, offset: i64, size: u32) -> anyhow::Result<DataResponse<Vec<u8>>> {
|
||||||
let (reply_tx, reply_rx) = oneshot::channel();
|
let (reply_tx, reply_rx) = oneshot::channel();
|
||||||
self.command_queue_tx
|
self.command_queue_tx
|
||||||
.send(StreamingReaderCmd::Read {
|
.send(StreamingReaderCmd::Read {
|
||||||
@ -209,7 +209,7 @@ impl StreamingReaderState {
|
|||||||
fn satisfy_read(
|
fn satisfy_read(
|
||||||
&mut self,
|
&mut self,
|
||||||
wanted_byte_range: Range<i64>,
|
wanted_byte_range: Range<i64>,
|
||||||
reply_to: OneshotSender<Vec<u8>>,
|
reply_to: OneshotSender<DataResponse<Vec<u8>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let skip = wanted_byte_range.start - self.ring_buffer_read_next_index;
|
let skip = wanted_byte_range.start - self.ring_buffer_read_next_index;
|
||||||
let size = wanted_byte_range.end - wanted_byte_range.start;
|
let size = wanted_byte_range.end - wanted_byte_range.start;
|
||||||
@ -222,7 +222,7 @@ impl StreamingReaderState {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
reply_to
|
reply_to
|
||||||
.send(response_buf)
|
.send(DataResponse::Success(response_buf))
|
||||||
.map_err(|_| anyhow!("Reply to Read failed"))?;
|
.map_err(|_| anyhow!("Reply to Read failed"))?;
|
||||||
|
|
||||||
self.ring_buffer_read_next_index += drain;
|
self.ring_buffer_read_next_index += drain;
|
||||||
@ -373,7 +373,14 @@ impl StreamingReaderState {
|
|||||||
}
|
}
|
||||||
Some(DataResponse::Error { code, message }) => {
|
Some(DataResponse::Error { code, message }) => {
|
||||||
// should probably bubble this up to the reads...
|
// should probably bubble this up to the reads...
|
||||||
todo!()
|
for (_, reply) in self.read_queue.drain(..) {
|
||||||
|
reply
|
||||||
|
.send(DataResponse::Error {
|
||||||
|
code,
|
||||||
|
message: message.clone(),
|
||||||
|
})
|
||||||
|
.map_err(|_| anyhow!("Can't reply to in-flight read with error"))?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
bail!("Cascade failure: stream reader suddenly shut down.");
|
bail!("Cascade failure: stream reader suddenly shut down.");
|
||||||
|
Loading…
Reference in New Issue
Block a user