pxar: fuse: cache goodbye table for each directory on opendir and release it on releasedir
Cache not only the goodbye table for the last directory but for each opened directory. The opendir fuse callback will fill the cache with the goodbye table and releasedir will remove it from the cache. This should reduce the number of chuncks fetched from the server in some cases. Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
parent
02491b8fc6
commit
25ad4cbf63
115
src/pxar/fuse.rs
115
src/pxar/fuse.rs
|
@ -86,30 +86,12 @@ struct FuseArgs {
|
||||||
/// offset within the archive for the i-node given by the caller.
|
/// offset within the archive for the i-node given by the caller.
|
||||||
struct Context {
|
struct Context {
|
||||||
decoder: Decoder,
|
decoder: Decoder,
|
||||||
goodbye_cache: Option<(Inode, Vec<(PxarGoodbyeItem, Offset, Offset)>)>,
|
goodbye_cache: HashMap<Inode, Vec<(PxarGoodbyeItem, Offset, Offset)>>,
|
||||||
attr_cache: Option<(Inode, PxarAttributes)>,
|
attr_cache: Option<(Inode, PxarAttributes)>,
|
||||||
ino_offset: Offset,
|
ino_offset: Offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Update the goodbye table to the one corresponding to the i-node offset, both
|
|
||||||
/// given in the `Context`.
|
|
||||||
fn update_goodbye_cache(&mut self) -> Result<(), i32> {
|
|
||||||
if let Some((off, _)) = self.goodbye_cache {
|
|
||||||
if off == self.ino_offset {
|
|
||||||
// Cache contains already the correct goodbye table
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Inode did not match or cache is empty, need to update the cache
|
|
||||||
let gbt = self.decoder
|
|
||||||
.goodbye_table(None, self.ino_offset + GOODBYE_ITEM_SIZE)
|
|
||||||
.map_err(|_| libc::EIO)?;
|
|
||||||
self.goodbye_cache = Some((self.ino_offset, gbt));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lookup the goodbye item identified by `filename` and its corresponding `hash`
|
/// Lookup the goodbye item identified by `filename` and its corresponding `hash`
|
||||||
///
|
///
|
||||||
/// Updates the goodbye table cache to contain the table for the directory given
|
/// Updates the goodbye table cache to contain the table for the directory given
|
||||||
|
@ -129,43 +111,40 @@ impl Context {
|
||||||
filename: &CStr,
|
filename: &CStr,
|
||||||
hash: u64,
|
hash: u64,
|
||||||
) -> Result<(u64, PxarEntry, PxarAttributes, u64), i32> {
|
) -> Result<(u64, PxarEntry, PxarAttributes, u64), i32> {
|
||||||
self.update_goodbye_cache()?;
|
let gbt = self.goodbye_cache.get(&self.ino_offset)
|
||||||
if let Some((_, gbt)) = &self.goodbye_cache {
|
.ok_or_else(|| libc::EIO)?;
|
||||||
let mut start_idx = 0;
|
let mut start_idx = 0;
|
||||||
let mut skip_multiple = 0;
|
let mut skip_multiple = 0;
|
||||||
loop {
|
loop {
|
||||||
// Search for the next goodbye entry with matching hash.
|
// Search for the next goodbye entry with matching hash.
|
||||||
let idx = search_binary_tree_by(
|
let idx = search_binary_tree_by(
|
||||||
start_idx,
|
start_idx,
|
||||||
gbt.len(),
|
gbt.len(),
|
||||||
skip_multiple,
|
skip_multiple,
|
||||||
|idx| hash.cmp(&gbt[idx].0.hash),
|
|idx| hash.cmp(&gbt[idx].0.hash),
|
||||||
).ok_or(libc::ENOENT)?;
|
).ok_or(libc::ENOENT)?;
|
||||||
|
|
||||||
let (_item, start, end) = &gbt[idx];
|
let (_item, start, end) = &gbt[idx];
|
||||||
|
|
||||||
// At this point it is not clear if the item is a directory or not, this
|
// At this point it is not clear if the item is a directory or not, this
|
||||||
// has to be decided based on the entry mode.
|
// has to be decided based on the entry mode.
|
||||||
// `Decoder`s attributes function accepts both, offsets pointing to
|
// `Decoder`s attributes function accepts both, offsets pointing to
|
||||||
// the start of an item (PXAR_FILENAME) or the GOODBYE_TAIL_MARKER in case
|
// the start of an item (PXAR_FILENAME) or the GOODBYE_TAIL_MARKER in case
|
||||||
// of directories, so the use of start offset is fine for both cases.
|
// of directories, so the use of start offset is fine for both cases.
|
||||||
let (entry_name, entry, attr, payload_size) =
|
let (entry_name, entry, attr, payload_size) =
|
||||||
self.decoder.attributes(*start).map_err(|_| libc::EIO)?;
|
self.decoder.attributes(*start).map_err(|_| libc::EIO)?;
|
||||||
|
|
||||||
// Possible hash collision, need to check if the found entry is indeed
|
// Possible hash collision, need to check if the found entry is indeed
|
||||||
// the filename to lookup.
|
// the filename to lookup.
|
||||||
if entry_name.as_bytes() == filename.to_bytes() {
|
if entry_name.as_bytes() == filename.to_bytes() {
|
||||||
let child_offset = find_offset(&entry, *start, *end);
|
let child_offset = find_offset(&entry, *start, *end);
|
||||||
return Ok((child_offset, entry, attr, payload_size));
|
return Ok((child_offset, entry, attr, payload_size));
|
||||||
}
|
|
||||||
// Hash collision, check the next entry in the goodbye table by starting
|
|
||||||
// from given index but skipping one more match (so hash at index itself).
|
|
||||||
start_idx = idx;
|
|
||||||
skip_multiple = 1;
|
|
||||||
}
|
}
|
||||||
|
// Hash collision, check the next entry in the goodbye table by starting
|
||||||
|
// from given index but skipping one more match (so hash at index itself).
|
||||||
|
start_idx = idx;
|
||||||
|
skip_multiple = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(libc::ENOENT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,16 +231,21 @@ impl Session {
|
||||||
/// Options have to be provided as comma separated OsStr, e.g.
|
/// Options have to be provided as comma separated OsStr, e.g.
|
||||||
/// ("ro,default_permissions").
|
/// ("ro,default_permissions").
|
||||||
pub fn new(
|
pub fn new(
|
||||||
decoder: Decoder,
|
mut decoder: Decoder,
|
||||||
options: &OsStr,
|
options: &OsStr,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let args = Self::setup_args(options, verbose)?;
|
let args = Self::setup_args(options, verbose)?;
|
||||||
let oprs = Self::setup_callbacks();
|
let oprs = Self::setup_callbacks();
|
||||||
|
|
||||||
|
let root_ino_offset = decoder.root_end_offset() - GOODBYE_ITEM_SIZE;
|
||||||
|
let root_goodbye_table = decoder.goodbye_table(None, root_ino_offset + GOODBYE_ITEM_SIZE)?;
|
||||||
|
let mut goodbye_cache = HashMap::new();
|
||||||
|
goodbye_cache.insert(root_ino_offset, root_goodbye_table);
|
||||||
|
|
||||||
let ctx = Context {
|
let ctx = Context {
|
||||||
decoder,
|
decoder,
|
||||||
goodbye_cache: None,
|
goodbye_cache,
|
||||||
attr_cache: None,
|
attr_cache: None,
|
||||||
ino_offset: 0,
|
ino_offset: 0,
|
||||||
};
|
};
|
||||||
|
@ -323,6 +307,7 @@ impl Session {
|
||||||
oprs.read = Some(Self::read);
|
oprs.read = Some(Self::read);
|
||||||
oprs.opendir = Some(Self::opendir);
|
oprs.opendir = Some(Self::opendir);
|
||||||
oprs.readdir = Some(Self::readdir);
|
oprs.readdir = Some(Self::readdir);
|
||||||
|
oprs.releasedir = Some(Self::releasedir);
|
||||||
oprs
|
oprs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,14 +503,12 @@ impl Session {
|
||||||
/// state identifies the directory as opened.
|
/// state identifies the directory as opened.
|
||||||
extern "C" fn opendir(req: Request, inode: u64, fileinfo: MutPtr) {
|
extern "C" fn opendir(req: Request, inode: u64, fileinfo: MutPtr) {
|
||||||
Self::run_in_context(req, inode, |ctx| {
|
Self::run_in_context(req, inode, |ctx| {
|
||||||
let (_, entry, _, _) = ctx
|
let gbt = ctx.decoder
|
||||||
.decoder
|
.goodbye_table(None, ctx.ino_offset + GOODBYE_ITEM_SIZE)
|
||||||
.attributes(ctx.ino_offset)
|
|
||||||
.map_err(|_| libc::EIO)?;
|
.map_err(|_| libc::EIO)?;
|
||||||
if (entry.mode as u32 & libc::S_IFMT) != libc::S_IFDIR {
|
ctx.goodbye_cache.insert(ctx.ino_offset, gbt);
|
||||||
return Err(libc::ENOENT);
|
|
||||||
}
|
let _ret = unsafe { fuse_reply_open(req, fileinfo as MutPtr) };
|
||||||
let _ret = unsafe { fuse_reply_open(req, fileinfo) };
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
@ -543,8 +526,8 @@ impl Session {
|
||||||
let offset = offset as usize;
|
let offset = offset as usize;
|
||||||
|
|
||||||
Self::run_in_context(req, inode, |ctx| {
|
Self::run_in_context(req, inode, |ctx| {
|
||||||
ctx.update_goodbye_cache()?;
|
let gb_table = ctx.goodbye_cache.get(&ctx.ino_offset)
|
||||||
let gb_table = &ctx.goodbye_cache.as_ref().unwrap().1;
|
.ok_or_else(|| libc::EIO)?;
|
||||||
let n_entries = gb_table.len();
|
let n_entries = gb_table.len();
|
||||||
let mut buf = ReplyBuf::new(req, size, offset);
|
let mut buf = ReplyBuf::new(req, size, offset);
|
||||||
|
|
||||||
|
@ -603,6 +586,14 @@ impl Session {
|
||||||
buf.reply_filled()
|
buf.reply_filled()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn releasedir(req: Request, inode: u64, _fileinfo: MutPtr) {
|
||||||
|
Self::run_in_context(req, inode, |ctx| {
|
||||||
|
let _gbt = ctx.goodbye_cache.remove(&ctx.ino_offset);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Session {
|
impl Drop for Session {
|
||||||
|
|
Loading…
Reference in New Issue