-
Notifications
You must be signed in to change notification settings - Fork 824
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
Abstracted WASI FS #2412
Abstracted WASI FS #2412
Conversation
The next step is to split it (and WasiFile, etc.) out into an independent crate and implement WasiFS in terms of that.
#[inline] | ||
pub fn downcast_ref<T: 'static>(&'_ self) -> Option<&'_ T> { | ||
self.upcast_any_ref().downcast_ref::<T>() | ||
} | ||
#[inline] | ||
pub fn downcast_mut<T: 'static>(&'_ mut self) -> Option<&'_ mut T> { | ||
self.upcast_any_mut().downcast_mut::<T>() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this can simplified a bit if we have a as_any
method? (and we move the downcasting back to the code that uses and needs it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, we're dealing with Box<dyn VirtualFile>
(previously Box<dyn WasiFile>
), due to the way this trait works, everything just works and users don't have to do anything but they can use these methods if they want, so I think it's pretty good. We can discuss possible improvements though
pub trait Upcastable { | ||
fn upcast_any_ref(&'_ self) -> &'_ dyn Any; | ||
fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any; | ||
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is not used anywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a public method, we can review its utility but it's useful when working with dyn Thing
and you might, as a user, care about getting your concrete thing out of the dynamic thing. This is a useful part of the WasiFile
API which allows users to define whatever logic they want for WasiFile
s
/// Trait needed to get downcasting from `VirtualFile` to work. | ||
pub trait Upcastable { | ||
fn upcast_any_ref(&'_ self) -> &'_ dyn Any; | ||
fn upcast_any_mut(&'_ mut self) -> &'_ mut dyn Any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe a better name is as_any_mut
// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 . | ||
/// Trait needed to get downcasting from `VirtualFile` to work. | ||
pub trait Upcastable { | ||
fn upcast_any_ref(&'_ self) -> &'_ dyn Any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as_any
?
fn get_raw_fd(&self) -> Option<i32> { | ||
None | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is not used publicly, so I believe we can remove it.
At the same time by removing it we also assure that we shield from any specific host requirement in the Virtual FS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small correction. Is indeed used in state.rs
for polling.
I think we can abstract the polling as well into the Virtual FS. This means removing get_raw_fd
and adding a new method for polling.
That way we don't need to expose any host requirements in the VirtualFS itself.
pub struct MemFileSystemInner { | ||
// done for recursion purposes | ||
fs: MemKind, | ||
inodes: HashMap<u64, Box<dyn VirtualFile>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think inodes should not be needed for a MemoryFilesystem? Or is this designed with symlinks in mind?
Why do we need the MemFileHandle?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we have MemFileHandle
and MemFile
as two ways of having a file in the memfs. I'm still trying to wrap my head around on why do we need two?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit of a band-aid to cover up a mismatch: the API is written in terms of Box<dyn VirtualFile>
but because it's also the backing storage we need to share it. So the internal file implementation here (which is also implemented in terms of VirtualFile
but doesn't have to be) is shared with Arc
and we return a Box<dyn VirtualFile>
for the API itself. This can be fixed after more iteration, I just didn't want to make too many big changes all at once
} | ||
|
||
fn set_created_time(&self, created_time: u64) { | ||
let inner = self.fs.inner.lock().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If set_created_time
uses &mut self
we can move from interior mutability to exterior very easily (so no locks are needed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case we need still need it because it's shared. Interior mutability wouldn't work unless we use atomics. Which is possible here but probably not worth it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean is that if we do the following instead perhaps we can get rid of the lock on this layer:
fn set_created_time(&mut self, created_time: u64) {
let inner = self.inner;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah if we make OpenOptions
have a lifetime then maybe that can work if we change the whole api to use &mut
and require locking from API users (like our existing wasmer code)
pub struct VfsFileSystem { | ||
inner: Arc<dyn vfs::FileSystem>, | ||
} | ||
|
||
impl FileSystem for VfsFileSystem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a VfsFileSystem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't, strictly, but the code is pretty much written. We'll probably delete it before shipping but I'll keep it around until the PR starts being less of draft in case we decide it is something that we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was trying to find a use case for it and was not able to find any. Let's keep the file up until merging just in case it results useful
|
||
pub trait FileSystem: Send + Sync + 'static { | ||
fn read_dir(&self, path: &Path) -> Result<std::fs::ReadDir, FsError>; | ||
fn create_dir(&self, path: &Path) -> Result<(), FsError>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have this as &mut self
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it could be, I'm not sure it's any better though, then users of dyn FileSystem
will need to do the sharing themselves, which is a fine pattern, this way is kind of simpler for users of the API though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case their code is single-threaded (for example, when compiled to wasm and used in js) it will make things easier and not lock dependent (since threads are not available in Wasm WASI), so I think it will good to do that to unlock it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, interesting, so the use case for Wasmer absolutely needs locking due to how WasmerEnv
works and how we'd need to make things work in WASI and emscripten... that's a good point though, if it's for use in Wasm, maybe it's worth it to move the locking to the API user as needed
fn set_len(&mut self, new_size: u64) -> Result<(), FsError>; | ||
|
||
/// Request deletion of the file | ||
fn unlink(&mut self) -> Result<(), FsError>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have this as self
(so it's consumed)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking more, I think perhaps unlink
and rename
can be in the FileSystem
api rather than in the VirtualFile
itself. It makes things a bit easier to reason about I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's due to WasiFile that it is this way, I agree that it's probably possible to move some of that logic away, it's really just a clean up for the file itself. I think self
is problematic in trait objects, Drop::drop
takes self by &mut
so I think this is fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I added a side comment on other place... perhaps we can delete this method and use only the filesystem one (remove_file
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm in no rush to do this yet (as I haven't done it) because I think depending on how we want to compose different types of VirtualFiles and file systems it may still make sense to have this as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working out the exact lines between the abstractions is still a work in progress
/// Store file contents and metadata to disk | ||
/// Default implementation returns `Ok(())`. You should implement this method if you care | ||
/// about flushing your cache to permanent storage | ||
fn sync_to_disk(&self) -> Result<(), FsError> { | ||
Ok(()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this only make sense for some kind of filesystems. Can we move it to other layer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it's fine for filesystems to not implement it, if this is in WasiFile then it's fine. But WASI needs a method like this to call for things to be emulated accurately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not move it to the WASI filesystem layer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would we? This is the layer through which WASI is interacting with the filesystem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's important to have not only these types of methods, but a lot more if we want this to be generally useful as a drop-in replacement abstraction for real file system usage...
This is already addressed by #2491. Closing the PR |
Description
This PR is in progress (created so I can comment and give feedback)
Review