al8n / fs4-rs

Extended utilities for working with files and filesystems in Rust. This is a fork of the fs2-rs crate, the aim for this fork is to support async and replace libc with rustix.
Apache License 2.0
51 stars 11 forks source link

Support async locking #17

Open bonsairobo opened 3 months ago

bonsairobo commented 3 months ago

Right now, there are blocking methods of AsyncFileExt like lock_shared and lock_exclusive that could be async for implementations like async_fs or tokio.

I can't see a simple way to do this as a user without wrapping async_fs::File in yet another Arc to pass it into blocking::unblock.

al8n commented 3 months ago

Hi, thanks for opening an issue.

Could you give a code example? I do not quite get the point.

bonsairobo commented 3 months ago

@al8n Sure!

Because lock_shared and lock_exclusive may cause the OS to sleep the thread, this would negatively impact performance in a thread-per-core async runtime. So the FileExt trait is not well-suited for async usage, which seems to conflict with the fact that it is implemented for async_fs::File.

In order to avoid this, I would use the blocking crate, which provides a thread pool specifically for blocking IO like this.

let f = Arc::new(async_fs::File::open("lockfile"));
let f2 = f.clone();
unblock(move || f2.lock_shared()).await?;

// Now `f` refers to a locked file handle.

As you can see, unblock requires a 'static closure, so we must move the file handle into the closure.

The unfortunate thing about this is that it's necessary to wrap the file in Arc. If you look at the definition of async_fs::File, it actually already has an Arc<std::fs::File>. I'm not sure if there is some way we can leverage this implementation detail, or perhaps instead it would be possible to provide a new AsyncFileExt which avoids the Arc by cloning raw file descriptors. I'm not advocating for any particular solution, but it seems there are possibilities.

al8n commented 3 months ago

Ah, I got it. Yes, I will work on this in the near future.

al8n commented 2 weeks ago

Hi, sorry for the delay; after I review the code, you can use try_lock_exclusive or try_lock_shared to acquire a non-blocking file lock.

bonsairobo commented 2 weeks ago

@al8n try_lock is not quite what I want because it will just return immediately on failure instead of yielding the task/future.

al8n commented 2 weeks ago

@al8n try_lock is not quite what I want because it will just return immediately on failure instead of yielding the task/future.

From my point of view, it is hard to make it a "real async task" for locking. We cannot avoid the system call. Even async_fs::File's implementation, for open, write, read, and etc., just use the unblock method to spawn a new task and wait for the task to finish.

Actually, unlike network I/O, you can have great performance improvement using async stuff. For file I/O, there is almost no benefit to using async implementation. This is why databases like redb, sled, and etc use sync File, which may provide some async APIs on the high level.