I'm working on a project which involves building a file system. This "file system" just wraps standard File
structs and implements encryption and authentication for the data stored in them. The file system struct uses three tiers of locks to synchronize access to files:
- There's a top-level reader-writer lock on a table indexed by inode.
- For each inode, the table entry is protected by another reader-writer lock.
- Finally, inside each table entry there is another table of handles. Each value of this table
(each file handle) is protected by a mutex.
I've been struggling to make the reading of file data zero-copy and able to be done in async code. My last approach was to create an RAII guard type which contains the guards for the three locks described above, and which can be dereferenced as a slice.
struct FirstGuard<'a> {
handle_guard: MutexGuard<'a, Vec<u8>>,
inode_guard: RwLockReadGuard<'a, InodeTableEntry>,
table_guard: RwLockReadGuard<'a, InodeTable>,
}
impl<'a> FirstGuard<'a> {
async fn new(table: &'a RwLock<InodeTable>, inode: Inode, handle: Handle) -> FirstGuard<'a> {
let table_guard = table.read().await;
let inode_guard = table_guard.get(&inode).unwrap().read().await;
let handle_guard = inode_guard.handles.get(&handle).unwrap().lock().await;
Self {
table_guard,
inode_guard,
handle_guard,
}
}
}
(I'm using Vec<u8>
to stand-in for files.)
This fails with an error because table_guard
and inode_guard
cannot be moved while they are borrowed, which makes sense. So I thought I could get around this by having the caller pass in a mutable reference to the guard. Then I could initialize this reference inside my FS struct after which the caller could dereference it. The full example is on the playground. This does not work due to what appears to be a limitation in the borrow checker's support for Generic Associated Types.
Does anyone have any suggestions on how I can work-around this error? Or, taking a step back, is there a better way to solve this problem?
Before trying this I was using a callback-based approach, where the callback was executed while the locks were held so the caller didn't need to own any guards. This approach worked for synchronous callbacks, but failed for ones which returned futures, which I posted about here.