Repository navigation
Let a tokio application drive FuseDevTask and take over a connection - #267
Merged
jiangliu merged 5 commits intoOct 29, 2026
Merged
Conversation
Kranzes
requested review from
bergwolf,
eryugey,
imeoer,
jiangliu and
liubogithub
as code owners
October 13, 2026 15:53
Kranzes
force-pushed
the
async-runtime-takeover
branch
3 times, most recently
from
October 14, 2026 12:41
deb6321 to
057a26b
Compare
This was referenced Oct 14, 2026
Kranzes
marked this pull request as draft
October 15, 2026 10:46
Kranzes
force-pushed
the
async-runtime-takeover
branch
from
October 19, 2026 16:42
057a26b to
0b86c75
Compare
Kranzes
marked this pull request as ready for review
October 19, 2026 16:47
Kranzes
force-pushed
the
async-runtime-takeover
branch
from
October 19, 2026 17:19
0b86c75 to
3ffc7cd
Compare
jiangliu
approved these changes
Oct 27, 2026
Collaborator
|
@Kranzes The GitHub runner recently upgraded its kernel, which is causing CI test failures. Could you please help rebase the PR to resolve this? |
The crate detects the runtime type on first use, and only the FUSE_BACKEND_RS_ASYNC_RUNTIME environment variable overrides it. A daemon that drives FuseDevTask from its own tokio runtime has to pick tokio, because that runtime can't poll tokio-uring files. Setting the variable from the daemon needs std::env::set_var, which is unsafe once other threads may read the environment. Add async_runtime::set_runtime_type() to select the type in code. It overrides the variable and fails if the type in effect is a different one. Add async_runtime::runtime_type() to read the type, and make RuntimeType public for both. Signed-off-by: Ilan Joselevich <personal@ilanjoselevich.com>
With the tokio runtime, FuseDevTask reads the nonblocking fuse device with plain preadv(). While no request is pending, the read fails with EAGAIN, and poll_handler() yields and reads again right away, so an idle task keeps a CPU busy. Register the fd with the runtime's IO driver and wait until it is readable before each read and after EAGAIN. Wait for errors too, because the kernel reports a torn down connection with EPOLLERR alone. A 100ms timer wakes the wait, so an idle task still returns once its state flag is set. Without the timer, stopping a worker whose unmount fails would hang. An error that isn't an OS error, such as the runtime shutting down, ends the task. The io_uring runtime is unchanged. AsyncFd::register_with_interest() needs tokio 1.53.3, which deprecates with_interest(). Registering panics outside of a tokio runtime, so a task that another executor polls keeps retrying reads as before. The docs say that dropping poll_handler() to stop an idle task is only safe with the tokio runtime. tokio-uring leaves its read pending in the kernel, which then copies the next request into the freed buffer. One test checks that an idle task uses little CPU and returns on unmount. Before this change it used all of the 500ms it idled. Another checks that the state flag stops an idle task while the mount stays up. A third serves a mount without any tokio runtime. Signed-off-by: Ilan Joselevich <personal@ilanjoselevich.com>
A daemon can hand its FUSE device fds to a new process, which then serves the connection without a FUSE_INIT. The new Server can't learn what the old one negotiated, so it parses requests as if no option were enabled, such as the FUSE_EXT_GROUPS extension of create requests. Add Server::init_state() to read the protocol version and the options enabled at INIT, and Server::restore_init() to set them on the new server. Add abi::fuse_abi::UNIQUE_RESEND, the bit the kernel sets in requests that notify_resend() makes it send again. The server already replies with the unique id unchanged, and a test now covers that. Document that a thread that serves requests must not call notify_inval_entry(). It waits for the parent's lock, which a lookup in that directory holds until its reply. Signed-off-by: Ilan Joselevich <personal@ilanjoselevich.com>
Add tests that serve a mount with FuseDevTask on a current-thread tokio runtime, from a file system that can make a lookup wait. They cover two cases: - A waiting lookup doesn't hold up another lookup in the same directory, and notify_inval_entry() from another thread works while the task serves. - A second server takes over the connection from one that stopped with a lookup in flight. It restores the INIT state, sends notify_resend() and answers the lookup that the kernel sends again. The file system enables FUSE_PARALLEL_DIROPS, because without it the kernel serializes lookups in a directory. Signed-off-by: Ilan Joselevich <personal@ilanjoselevich.com>
The async server logged a read error it didn't know and read again. Now that a tokio task waits for the device to become readable, a device that stays readable but fails every read made the task spin at full CPU and log on every pass. EINVAL for a buffer smaller than the kernel's requests does that. Retry ENOENT, which the kernel returns when a request was interrupted before the read, and stop the task on any other error. The synchronous server does the same. Signed-off-by: Ilan Joselevich <personal@ilanjoselevich.com>
Kranzes
force-pushed
the
async-runtime-takeover
branch
from
October 28, 2026 14:52
c392fce to
a51ee32
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
These changes let an application drive
FuseDevTaskfrom its own tokio runtime, and let a new server take over a connection that another one initialized, for example after a daemon restart that hands over the FUSE device fds.async_runtime::set_runtime_type(), so the application doesn't have to setFUSE_BACKEND_RS_ASYNC_RUNTIMEwithstd::env::set_var, which is unsafe once other threads run.FuseDevTaskon tokio retriedpreadv()onEAGAINin a loop and kept a CPU busy. It now waits until the fd is readable, and a 100ms timer lets an idle task see its state flag. Outside a tokio runtime the task keeps retrying reads as before.Server::init_state()andServer::restore_init(), so a server that never sees INIT parses requests with the options the old one negotiated.fuse-backend-fusedevnow needs tokio 1.53.3 forAsyncFd::register_with_interest(), because 1.53.3 deprecateswith_interest()andmake checkruns clippy with-Dwarnings.Dropping
poll_handler()to stop an idle task is only safe on tokio, and the docs say so. tokio-uring leaves its read pending in the kernel, which then copies the next request into the freed buffer.The mount tests need root or
fusermount3and skip without them. I ran them in a user namespace on Linux 7.2.10 with both runtimes.