* [PATCH 0/2] libfuse: add support for FUSE_NOTIFY_RESEND @ 2026-10-09 7:54 Baokun Li 2026-10-09 7:54 ` [PATCH 1/2] fuse_lowlevel: " Baokun Li 2026-10-09 7:54 ` [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND Baokun Li 0 siblings, 2 replies; 8+ messages in thread From: Baokun Li @ 2026-10-09 7:54 UTC (permalink / raw) To: fuse-devel; +Cc: bernd, miklos Hi all, The FUSE kernel driver supports re-queuing requests that were already sent to a daemon but never answered, e.g. to recover in-flight requests after a daemon takeover. The kernel side was added in Linux 6.9, protocol 7.40 (FUSE_NOTIFY_RESEND / FUSE_HAS_RESEND), but libfuse has no way to use it yet. Patch 1 adds fuse_lowlevel_notify_resend() to send the notification, exposes kernel support through the new FUSE_CAP_HAS_RESEND capability and adds fuse_req_is_resend() so that file systems can detect resent requests via the FUSE_UNIQUE_RESEND bit in the request ID and handle them idempotently. Patch 2 adds a self-contained regression test: the daemon drops the first GETATTR of a file, triggers a resend and expects the request to come back with FUSE_UNIQUE_RESEND set before answering it. Testing ======= The new test passes on Linux 7.3-rc3 (protocol 7.46). It skips on kernels without FUSE_HAS_RESEND support and over the io-uring transport, which recovers requests on its own. Comments and questions are, as always, welcome. Thanks, Baokun Baokun Li (2): fuse_lowlevel: add support for FUSE_NOTIFY_RESEND test: exercise FUSE_NOTIFY_RESEND ChangeLog.rst | 7 + doc/README.notifications | 2 +- include/fuse_common.h | 13 ++ include/fuse_lowlevel.h | 28 ++++ lib/fuse_cap_names_i.h | 1 + lib/fuse_lowlevel.c | 23 +++ lib/fuse_versionscript | 2 + test/cases/ctests/notify-resend.sh | 18 +++ test/meson.build | 3 + test/test_notify_resend.c | 216 +++++++++++++++++++++++++++++ 10 files changed, 312 insertions(+), 1 deletion(-) create mode 100755 test/cases/ctests/notify-resend.sh create mode 100644 test/test_notify_resend.c base-commit: cb2e761c763559f40e41090aabf008f665f27889 -- 2.43.7 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/2] fuse_lowlevel: add support for FUSE_NOTIFY_RESEND 2026-10-09 7:54 [PATCH 0/2] libfuse: add support for FUSE_NOTIFY_RESEND Baokun Li @ 2026-10-09 7:54 ` Baokun Li 2026-10-09 7:54 ` [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND Baokun Li 1 sibling, 0 replies; 8+ messages in thread From: Baokun Li @ 2026-10-09 7:54 UTC (permalink / raw) To: fuse-devel; +Cc: bernd, miklos The kernel supports resending pending requests since protocol 7.40 (FUSE_NOTIFY_RESEND / FUSE_HAS_RESEND), added in Linux 6.9 by commits 760eac73f9f6 ("fuse: Introduce a new notification type for resend pending requests") and 9e7f5296f475 ("fuse: Use the high bit of request ID for indicating resend requests"). A daemon can ask the kernel to requeue requests that were already sent to the daemon but never answered, e.g. to recover in-flight requests after a daemon takeover. Add fuse_lowlevel_notify_resend() to send the notification, expose kernel support through the new FUSE_CAP_HAS_RESEND capability, and add fuse_req_is_resend() so file systems can detect resent requests via the FUSE_UNIQUE_RESEND bit in the request ID and handle them idempotently. Signed-off-by: Baokun Li <libaokun@linux.alibaba.com> --- ChangeLog.rst | 7 +++++++ doc/README.notifications | 2 +- include/fuse_common.h | 13 +++++++++++++ include/fuse_lowlevel.h | 28 ++++++++++++++++++++++++++++ lib/fuse_cap_names_i.h | 1 + lib/fuse_lowlevel.c | 23 +++++++++++++++++++++++ lib/fuse_versionscript | 2 ++ 7 files changed, 75 insertions(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index d5392b251929..ec8cbc3dd1d5 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,6 +1,13 @@ Unreleased Changes ================== +* ``fuse_lowlevel_notify_resend()`` lets a filesystem ask the kernel to + re-queue requests that were already sent to the daemon but never + answered, for example to recover in-flight requests after a daemon + takeover. Support is reported through the new ``FUSE_CAP_HAS_RESEND`` + capability, and ``fuse_req_is_resend()`` tells whether a request was + redelivered this way. Requires protocol 7.40 (Linux 6.9) or newer. + * ``fuse_session_custom_io()`` is disabled unless libfuse is built with ``-Denable-custom-io=true``, and returns ``-ENOTSUP`` otherwise. The ``hello_ll_uds`` example is built only with that option, and enabling it diff --git a/doc/README.notifications b/doc/README.notifications index 149e47e19c9f..30051ed87b51 100644 --- a/doc/README.notifications +++ b/doc/README.notifications @@ -26,7 +26,7 @@ notifications: | STORE | fuse_lowlevel_notify_store | | RETRIEVE | fuse_lowlevel_notify_retrieve | | DELETE | fuse_lowlevel_notify_delete | -| RESEND | - | +| RESEND | fuse_lowlevel_notify_resend | | PRUNE | fuse_lowlevel_notify_prune | |-------------+----------------------------------| diff --git a/include/fuse_common.h b/include/fuse_common.h index 7533626175db..c2a7211e2f53 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -551,6 +551,19 @@ struct fuse_loop_config_v1 { */ #define FUSE_CAP_IO_URING_BUFPOOL (1ULL << 34) +/** + * Indicates that the kernel supports resending pending requests + * + * The file system can ask the kernel to re-queue requests that were + * already sent to the daemon but never answered, by sending the + * FUSE_NOTIFY_RESEND notification (see fuse_lowlevel_notify_resend()). + * Resent requests carry the FUSE_UNIQUE_RESEND bit in the request ID, + * which can be queried with fuse_req_is_resend(). + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_HAS_RESEND (1ULL << 35) + /** * Ioctl flags * diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index f228bdfd951f..a6313e24a5a8 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -1977,6 +1977,23 @@ int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, int fuse_lowlevel_notify_prune(struct fuse_session *se, fuse_ino_t *nodeids, uint32_t count); +/** + * Notify the kernel to resend pending requests + * + * Ask the kernel to re-queue requests that were already sent to the file + * system but not yet answered, e.g. to recover in-flight requests after + * a daemon takeover. + * + * Resent requests carry the FUSE_UNIQUE_RESEND bit in the request ID; use + * fuse_req_is_resend() to detect them. Since they may have been partially + * processed before, they should be handled idempotently or duplicates + * should be detected. + * + * @param se the session object + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_resend(struct fuse_session *se); + /* ----------------------------------------------------------- * * Utility functions * * ----------------------------------------------------------- */ @@ -2100,6 +2117,17 @@ void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, */ int fuse_req_interrupted(fuse_req_t req); +/** + * Check if a request is a resent request + * + * A resent request was already sent to the file system, but was never + * answered (see fuse_lowlevel_notify_resend()). + * + * @param req request handle + * @return 1 if the request is a resend request, 0 otherwise + */ +int fuse_req_is_resend(fuse_req_t req); + /* ----------------------------------------------------------- * * Inquiry functions * diff --git a/lib/fuse_cap_names_i.h b/lib/fuse_cap_names_i.h index aa85b910200d..36e99147dd89 100644 --- a/lib/fuse_cap_names_i.h +++ b/lib/fuse_cap_names_i.h @@ -63,6 +63,7 @@ static const struct fuse_cap_name fuse_cap_names[] = { { FUSE_CAP_ALLOW_IDMAP, "FUSE_CAP_ALLOW_IDMAP"}, { FUSE_CAP_SECURITY_CTX, "FUSE_CAP_SECURITY_CTX"}, { FUSE_CAP_IO_URING_BUFPOOL, "FUSE_CAP_IO_URING_BUFPOOL"}, + { FUSE_CAP_HAS_RESEND, "FUSE_CAP_HAS_RESEND"}, // Add any new capabilities here { 0, NULL} // Sentinel to mark the end of the array }; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 3951bbb6dd09..bbc58523b033 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2960,6 +2960,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, se->conn.capable_ext |= FUSE_CAP_ALLOW_IDMAP; if (inargflags & FUSE_SECURITY_CTX) se->conn.capable_ext |= FUSE_CAP_SECURITY_CTX; + if (inargflags & FUSE_HAS_RESEND) + se->conn.capable_ext |= FUSE_CAP_HAS_RESEND; } else { se->conn.max_readahead = 0; } @@ -3005,6 +3007,7 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, FUSE_CAP_READDIRPLUS_AUTO); LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING); LL_SET_DEFAULT(se->uring.bufpool, FUSE_CAP_IO_URING_BUFPOOL); + LL_SET_DEFAULT(1, FUSE_CAP_HAS_RESEND); /* This could safely become default, but libfuse needs an API extension * to support it @@ -3133,6 +3136,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, outargflags |= FUSE_SECURITY_CTX; if (se->conn.want_ext & FUSE_CAP_ALLOW_IDMAP) outargflags |= FUSE_ALLOW_IDMAP; + if (se->conn.want_ext & FUSE_CAP_HAS_RESEND) + outargflags |= FUSE_HAS_RESEND; if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) { outargflags |= FUSE_REQUEST_TIMEOUT; @@ -3644,6 +3649,19 @@ int fuse_lowlevel_notify_prune(struct fuse_session *se, return send_notify_iov(se, FUSE_NOTIFY_PRUNE, iov, 3); } +int fuse_lowlevel_notify_resend(struct fuse_session *se) +{ + struct iovec iov[1]; + + if (!se) + return -EINVAL; + + if (!(se->conn.capable_ext & FUSE_CAP_HAS_RESEND)) + return -ENOSYS; + + return send_notify_iov(se, FUSE_NOTIFY_RESEND, iov, 1); +} + void *fuse_req_userdata(fuse_req_t req) { return req->se->userdata; @@ -3768,6 +3786,11 @@ int fuse_req_interrupted(fuse_req_t req) return interrupted; } +int fuse_req_is_resend(fuse_req_t req) +{ + return !!(req->unique & FUSE_UNIQUE_RESEND); +} + bool fuse_req_is_uring(fuse_req_t req) { return req->flags.is_uring; diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index b40f141f7eb9..e074cd4a0831 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -228,6 +228,8 @@ FUSE_3.19 { fuse_session_start_teardown_watchdog; fuse_session_stop_teardown_watchdog; fuse_lowlevel_notify_prune; + fuse_lowlevel_notify_resend; + fuse_req_is_resend; fuse_daemonize_early_start; fuse_daemonize_early_success; fuse_daemonize_early_fail; -- 2.43.7 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND 2026-10-09 7:54 [PATCH 0/2] libfuse: add support for FUSE_NOTIFY_RESEND Baokun Li 2026-10-09 7:54 ` [PATCH 1/2] fuse_lowlevel: " Baokun Li @ 2026-10-09 7:54 ` Baokun Li 2026-10-09 8:19 ` Bernd Schubert 1 sibling, 1 reply; 8+ messages in thread From: Baokun Li @ 2026-10-09 7:54 UTC (permalink / raw) To: fuse-devel; +Cc: bernd, miklos test_notify_resend drops the first GETATTR of a file and asks the kernel to resend pending requests via fuse_lowlevel_notify_resend(). The kernel redelivers the request with the FUSE_UNIQUE_RESEND bit set; the daemon detects it with fuse_req_is_resend(), answers it, and the client's stat() completes. The case skips on kernels without FUSE_HAS_RESEND support and over the io-uring transport, which recovers requests on its own. Signed-off-by: Baokun Li <libaokun@linux.alibaba.com> --- test/cases/ctests/notify-resend.sh | 18 +++ test/meson.build | 3 + test/test_notify_resend.c | 216 +++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100755 test/cases/ctests/notify-resend.sh create mode 100644 test/test_notify_resend.c diff --git a/test/cases/ctests/notify-resend.sh b/test/cases/ctests/notify-resend.sh new file mode 100755 index 000000000000..c64d930dadb5 --- /dev/null +++ b/test/cases/ctests/notify-resend.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# GROUP: ctests +# A request the daemon never answers comes back with the resend bit set in +# the request ID after FUSE_NOTIFY_RESEND. + +. "$TEST_LIB/common.sh" + +# FUSE_NOTIFY_RESEND requeues the pending requests of the char-device +# channel; the io-uring transport has its own recovery path. +[ "${FUSE_URING_ENABLE:-0}" = 0 ] || _notrun "needs the standard transport" + +_require_cap FUSE_CAP_HAS_RESEND + +status=0 +"$FUSE_TEST_BIN_DIR/test_notify_resend" "$TEST_MNT" || status=$? + +[ "$status" -ne 77 ] || _notrun "the kernel did not report FUSE_HAS_RESEND" +[ "$status" -eq 0 ] || _fail "test_notify_resend exited $status" diff --git a/test/meson.build b/test/meson.build index a8518c314804..022a3aea93f0 100644 --- a/test/meson.build +++ b/test/meson.build @@ -35,6 +35,9 @@ td += executable('test_abi', 'test_abi.c', td += executable('test_secctx_splice', 'test_secctx_splice.c', dependencies: [ libfuse_dep, thread_dep ], install: false) +td += executable('test_notify_resend', 'test_notify_resend.c', + dependencies: [ libfuse_dep ], + install: false) td += executable('test_loop_config', 'test_loop_config.c', include_directories: include_dirs, link_with: [ libfuse ], diff --git a/test/test_notify_resend.c b/test/test_notify_resend.c new file mode 100644 index 000000000000..a1671514bcb9 --- /dev/null +++ b/test/test_notify_resend.c @@ -0,0 +1,216 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2026 Baokun Li <libaokun@linux.alibaba.com> + * + * Test FUSE_NOTIFY_RESEND: recovery of an in-flight request that the + * daemon received but never answered. + * + * The daemon (parent process) drops the first FUSE_GETATTR of the test + * file and immediately asks the kernel to resend pending requests via + * fuse_lowlevel_notify_resend(). The kernel re-queues the dropped + * request with the FUSE_UNIQUE_RESEND bit set in the request ID; the + * daemon detects it with fuse_req_is_resend(), answers it and exits the + * session loop. The client (child process) issues the stat() that + * drives the whole sequence and must see it complete successfully. + * + * Kernels without FUSE_HAS_RESEND support and the io-uring transport + * (whose pending requests are recovered by its own path, not by + * FUSE_NOTIFY_RESEND) are reported as a skip. + * + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file GPL2.txt + */ +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19) + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/wait.h> + +/* automake convention: the runner reports this as SKIP. */ +#define TEST_SKIP_EXIT 77 +#define TEST_INO 2 +#define TEST_NAME "testfile" + +static struct fuse_session *g_se; +static int g_cap_has_resend; +static int g_dropped; /* first GETATTR was left unanswered */ +static int g_notify_rc = -1; /* fuse_lowlevel_notify_resend() result */ +static int g_resend_seen; /* second delivery carried the resend bit */ +static int g_uring; /* requests reached the daemon over io-uring */ + +/* + * The loop may be waiting for a request that never comes (the resend was + * never queued). Exit it on the alarm so the cause gets reported instead + * of the default disposition ending the process. + */ +static void t_alarm(int signo) +{ + (void)signo; + fuse_session_exit(g_se); +} + +static void t_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + g_cap_has_resend = fuse_get_feature_flag(conn, FUSE_CAP_HAS_RESEND); + if (!g_cap_has_resend) { + fprintf(stderr, "test_notify_resend: kernel lacks " + "FUSE_HAS_RESEND, skipping\n"); + fuse_session_exit(g_se); + } +} + +static void t_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != FUSE_ROOT_ID || strcmp(name, TEST_NAME) != 0) { + fuse_reply_err(req, ENOENT); + return; + } + + memset(&e, 0, sizeof(e)); + e.ino = TEST_INO; + /* Force the kernel to issue a separate GETATTR */ + e.attr_timeout = 0; + e.entry_timeout = 3600; + e.attr.st_ino = TEST_INO; + e.attr.st_mode = S_IFREG | 0644; + e.attr.st_nlink = 1; + fuse_reply_entry(req, &e); +} + +static void t_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + int is_resend = fuse_req_is_resend(req); + + (void)fi; + + /* FUSE_NOTIFY_RESEND only requeues the requests of the /dev/fuse + * channel; over io-uring there is nothing to resend. Serve the + * request and let the caller report the skip. */ + if (fuse_req_is_uring(req)) + g_uring = 1; + + if (ino == TEST_INO && !is_resend && !g_dropped && !g_uring) { + g_dropped = 1; + /* Simulate a lost request: answer never, ask for resend */ + g_notify_rc = fuse_lowlevel_notify_resend(g_se); + return; + } + + if (ino == TEST_INO && is_resend) + g_resend_seen = 1; + + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + stbuf.st_mode = (ino == FUSE_ROOT_ID) ? + (S_IFDIR | 0755) : (S_IFREG | 0644); + stbuf.st_nlink = 1; + fuse_reply_attr(req, &stbuf, 3600); + + if (ino == TEST_INO) + fuse_session_exit(g_se); +} + +static const struct fuse_lowlevel_ops t_ops = { + .init = t_init, + .lookup = t_lookup, + .getattr = t_getattr, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(1, argv); + struct fuse_session *se; + pid_t pid; + int status = 0, ret = EXIT_FAILURE; + + if (argc != 2) { + fprintf(stderr, "usage: %s <mountpoint>\n", argv[0]); + return 1; + } + + printf("Testing FUSE_NOTIFY_RESEND\n"); + fflush(stdout); /* avoid the child inheriting an unflushed buffer */ + + se = fuse_session_new(&args, &t_ops, sizeof(t_ops), NULL); + if (!se) + return EXIT_FAILURE; + g_se = se; + + if (fuse_session_mount(se, argv[1]) != 0) + return EXIT_FAILURE; + + pid = fork(); + if (pid == -1) + goto out_unmount; + + if (pid == 0) { + struct stat st; + char path[PATH_MAX]; + + /* Child: the client driving the test */ + alarm(20); + snprintf(path, sizeof(path), "%s/" TEST_NAME, argv[1]); + _exit(stat(path, &st) == 0 ? 0 : 4); + } + + /* Parent: the daemon; guard against unexpected hangs */ + signal(SIGALRM, t_alarm); + alarm(30); + fuse_session_loop(se); + + if (g_cap_has_resend && !g_uring && g_dropped && + g_notify_rc == 0 && g_resend_seen) { + /* The resend was answered; the client must complete now */ + if (waitpid(pid, &status, 0) != -1 && WIFEXITED(status) && + WEXITSTATUS(status) == 0) { + printf("test_notify_resend: PASS\n"); + ret = EXIT_SUCCESS; + } else { + fprintf(stderr, "test_notify_resend: client stat " + "failed (status=%d)\n", status); + } + goto out_unmount; + } + + /* + * Skip or failure: the client is blocked in the kernel on a request + * nobody will answer, and a signal that is not fatal does not wake + * it (the kernel waits for the daemon to abort the request), so + * reap it with SIGKILL instead of waiting it out. + */ + kill(pid, SIGKILL); + waitpid(pid, &status, 0); + + if (!g_cap_has_resend) { + /* Kernel without support: report skip (the runner gates on it) */ + ret = TEST_SKIP_EXIT; + } else if (g_uring) { + fprintf(stderr, "test_notify_resend: requests came in over " + "io-uring, which has its own recovery path; skipping\n"); + ret = TEST_SKIP_EXIT; + } else if (!g_dropped || g_notify_rc != 0) { + fprintf(stderr, "test_notify_resend: notify failed " + "(dropped=%d rc=%d)\n", g_dropped, g_notify_rc); + } else { + fprintf(stderr, "test_notify_resend: request was not " + "redelivered with the resend bit\n"); + } + +out_unmount: + fuse_session_unmount(se); + fuse_session_destroy(se); + return ret; +} -- 2.43.7 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND 2026-10-09 7:54 ` [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND Baokun Li @ 2026-10-09 8:19 ` Bernd Schubert 2026-10-09 9:31 ` Baokun Li 0 siblings, 1 reply; 8+ messages in thread From: Bernd Schubert @ 2026-10-09 8:19 UTC (permalink / raw) To: Baokun Li, fuse-devel; +Cc: miklos, Luis Henriques Hi Baokun, On 10/9/26 09:54, Baokun Li wrote: > test_notify_resend drops the first GETATTR of a file and asks the > kernel to resend pending requests via fuse_lowlevel_notify_resend(). > The kernel redelivers the request with the FUSE_UNIQUE_RESEND bit set; > the daemon detects it with fuse_req_is_resend(), answers it, and the > client's stat() completes. to be honest, this is one the features that I have no clue about what it exists for. Why would a daemon drop requests? The use-case I have in my mind is a daemon upgrade, but that would mean that either kernel needs to send a re-lookup of all inodes or that daemon needs to store lookup objects (I personally see these as counter part of kernel inodes). Now, besides not being implemented at all, kernel will have a hard time to send re-lookup requests, unless fuse-server supports lookup by 64 bit node-id. I.e. in my personal opinion the prerequisite of FUSE_NOTIFY_RESEND is missing (which could be LOOKUP_HANDLE and then using that to re-lookup inodes after a daemon termination). The example below also just does a simple exercise of FUSE_NOTIFY_RESEND, without revealing what it would be useful for - that is the part I would like to see fulfilled by an example. Thanks, Bernd > > The case skips on kernels without FUSE_HAS_RESEND support and over the > io-uring transport, which recovers requests on its own. > > Signed-off-by: Baokun Li <libaokun@linux.alibaba.com> > --- > test/cases/ctests/notify-resend.sh | 18 +++ > test/meson.build | 3 + > test/test_notify_resend.c | 216 +++++++++++++++++++++++++++++ > 3 files changed, 237 insertions(+) > create mode 100755 test/cases/ctests/notify-resend.sh > create mode 100644 test/test_notify_resend.c > > diff --git a/test/cases/ctests/notify-resend.sh b/test/cases/ctests/notify-resend.sh > new file mode 100755 > index 000000000000..c64d930dadb5 > --- /dev/null > +++ b/test/cases/ctests/notify-resend.sh > @@ -0,0 +1,18 @@ > +#!/usr/bin/env bash > +# GROUP: ctests > +# A request the daemon never answers comes back with the resend bit set in > +# the request ID after FUSE_NOTIFY_RESEND. > + > +. "$TEST_LIB/common.sh" > + > +# FUSE_NOTIFY_RESEND requeues the pending requests of the char-device > +# channel; the io-uring transport has its own recovery path. > +[ "${FUSE_URING_ENABLE:-0}" = 0 ] || _notrun "needs the standard transport" > + > +_require_cap FUSE_CAP_HAS_RESEND > + > +status=0 > +"$FUSE_TEST_BIN_DIR/test_notify_resend" "$TEST_MNT" || status=$? > + > +[ "$status" -ne 77 ] || _notrun "the kernel did not report FUSE_HAS_RESEND" > +[ "$status" -eq 0 ] || _fail "test_notify_resend exited $status" > diff --git a/test/meson.build b/test/meson.build > index a8518c314804..022a3aea93f0 100644 > --- a/test/meson.build > +++ b/test/meson.build > @@ -35,6 +35,9 @@ td += executable('test_abi', 'test_abi.c', > td += executable('test_secctx_splice', 'test_secctx_splice.c', > dependencies: [ libfuse_dep, thread_dep ], > install: false) > +td += executable('test_notify_resend', 'test_notify_resend.c', > + dependencies: [ libfuse_dep ], > + install: false) > td += executable('test_loop_config', 'test_loop_config.c', > include_directories: include_dirs, > link_with: [ libfuse ], > diff --git a/test/test_notify_resend.c b/test/test_notify_resend.c > new file mode 100644 > index 000000000000..a1671514bcb9 > --- /dev/null > +++ b/test/test_notify_resend.c > @@ -0,0 +1,216 @@ > +/* > + * FUSE: Filesystem in Userspace > + * Copyright (C) 2026 Baokun Li <libaokun@linux.alibaba.com> > + * > + * Test FUSE_NOTIFY_RESEND: recovery of an in-flight request that the > + * daemon received but never answered. > + * > + * The daemon (parent process) drops the first FUSE_GETATTR of the test > + * file and immediately asks the kernel to resend pending requests via > + * fuse_lowlevel_notify_resend(). The kernel re-queues the dropped > + * request with the FUSE_UNIQUE_RESEND bit set in the request ID; the > + * daemon detects it with fuse_req_is_resend(), answers it and exits the > + * session loop. The client (child process) issues the stat() that > + * drives the whole sequence and must see it complete successfully. > + * > + * Kernels without FUSE_HAS_RESEND support and the io-uring transport > + * (whose pending requests are recovered by its own path, not by > + * FUSE_NOTIFY_RESEND) are reported as a skip. > + * > + * This program can be distributed under the terms of the GNU LGPLv2. > + * See the file GPL2.txt > + */ > +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19) > + > +#include "fuse_config.h" > +#include "fuse_lowlevel.h" > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <unistd.h> > +#include <errno.h> > +#include <limits.h> > +#include <signal.h> > +#include <sys/stat.h> > +#include <sys/wait.h> > + > +/* automake convention: the runner reports this as SKIP. */ > +#define TEST_SKIP_EXIT 77 > +#define TEST_INO 2 > +#define TEST_NAME "testfile" > + > +static struct fuse_session *g_se; > +static int g_cap_has_resend; > +static int g_dropped; /* first GETATTR was left unanswered */ > +static int g_notify_rc = -1; /* fuse_lowlevel_notify_resend() result */ > +static int g_resend_seen; /* second delivery carried the resend bit */ > +static int g_uring; /* requests reached the daemon over io-uring */ > + > +/* > + * The loop may be waiting for a request that never comes (the resend was > + * never queued). Exit it on the alarm so the cause gets reported instead > + * of the default disposition ending the process. > + */ > +static void t_alarm(int signo) > +{ > + (void)signo; > + fuse_session_exit(g_se); > +} > + > +static void t_init(void *userdata, struct fuse_conn_info *conn) > +{ > + (void)userdata; > + g_cap_has_resend = fuse_get_feature_flag(conn, FUSE_CAP_HAS_RESEND); > + if (!g_cap_has_resend) { > + fprintf(stderr, "test_notify_resend: kernel lacks " > + "FUSE_HAS_RESEND, skipping\n"); > + fuse_session_exit(g_se); > + } > +} > + > +static void t_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) > +{ > + struct fuse_entry_param e; > + > + if (parent != FUSE_ROOT_ID || strcmp(name, TEST_NAME) != 0) { > + fuse_reply_err(req, ENOENT); > + return; > + } > + > + memset(&e, 0, sizeof(e)); > + e.ino = TEST_INO; > + /* Force the kernel to issue a separate GETATTR */ > + e.attr_timeout = 0; > + e.entry_timeout = 3600; > + e.attr.st_ino = TEST_INO; > + e.attr.st_mode = S_IFREG | 0644; > + e.attr.st_nlink = 1; > + fuse_reply_entry(req, &e); > +} > + > +static void t_getattr(fuse_req_t req, fuse_ino_t ino, > + struct fuse_file_info *fi) > +{ > + struct stat stbuf; > + int is_resend = fuse_req_is_resend(req); > + > + (void)fi; > + > + /* FUSE_NOTIFY_RESEND only requeues the requests of the /dev/fuse > + * channel; over io-uring there is nothing to resend. Serve the > + * request and let the caller report the skip. */ > + if (fuse_req_is_uring(req)) > + g_uring = 1; > + > + if (ino == TEST_INO && !is_resend && !g_dropped && !g_uring) { > + g_dropped = 1; > + /* Simulate a lost request: answer never, ask for resend */ > + g_notify_rc = fuse_lowlevel_notify_resend(g_se); > + return; > + } > + > + if (ino == TEST_INO && is_resend) > + g_resend_seen = 1; > + > + memset(&stbuf, 0, sizeof(stbuf)); > + stbuf.st_ino = ino; > + stbuf.st_mode = (ino == FUSE_ROOT_ID) ? > + (S_IFDIR | 0755) : (S_IFREG | 0644); > + stbuf.st_nlink = 1; > + fuse_reply_attr(req, &stbuf, 3600); > + > + if (ino == TEST_INO) > + fuse_session_exit(g_se); > +} > + > +static const struct fuse_lowlevel_ops t_ops = { > + .init = t_init, > + .lookup = t_lookup, > + .getattr = t_getattr, > +}; > + > +int main(int argc, char *argv[]) > +{ > + struct fuse_args args = FUSE_ARGS_INIT(1, argv); > + struct fuse_session *se; > + pid_t pid; > + int status = 0, ret = EXIT_FAILURE; > + > + if (argc != 2) { > + fprintf(stderr, "usage: %s <mountpoint>\n", argv[0]); > + return 1; > + } > + > + printf("Testing FUSE_NOTIFY_RESEND\n"); > + fflush(stdout); /* avoid the child inheriting an unflushed buffer */ > + > + se = fuse_session_new(&args, &t_ops, sizeof(t_ops), NULL); > + if (!se) > + return EXIT_FAILURE; > + g_se = se; > + > + if (fuse_session_mount(se, argv[1]) != 0) > + return EXIT_FAILURE; > + > + pid = fork(); > + if (pid == -1) > + goto out_unmount; > + > + if (pid == 0) { > + struct stat st; > + char path[PATH_MAX]; > + > + /* Child: the client driving the test */ > + alarm(20); > + snprintf(path, sizeof(path), "%s/" TEST_NAME, argv[1]); > + _exit(stat(path, &st) == 0 ? 0 : 4); > + } > + > + /* Parent: the daemon; guard against unexpected hangs */ > + signal(SIGALRM, t_alarm); > + alarm(30); > + fuse_session_loop(se); > + > + if (g_cap_has_resend && !g_uring && g_dropped && > + g_notify_rc == 0 && g_resend_seen) { > + /* The resend was answered; the client must complete now */ > + if (waitpid(pid, &status, 0) != -1 && WIFEXITED(status) && > + WEXITSTATUS(status) == 0) { > + printf("test_notify_resend: PASS\n"); > + ret = EXIT_SUCCESS; > + } else { > + fprintf(stderr, "test_notify_resend: client stat " > + "failed (status=%d)\n", status); > + } > + goto out_unmount; > + } > + > + /* > + * Skip or failure: the client is blocked in the kernel on a request > + * nobody will answer, and a signal that is not fatal does not wake > + * it (the kernel waits for the daemon to abort the request), so > + * reap it with SIGKILL instead of waiting it out. > + */ > + kill(pid, SIGKILL); > + waitpid(pid, &status, 0); > + > + if (!g_cap_has_resend) { > + /* Kernel without support: report skip (the runner gates on it) */ > + ret = TEST_SKIP_EXIT; > + } else if (g_uring) { > + fprintf(stderr, "test_notify_resend: requests came in over " > + "io-uring, which has its own recovery path; skipping\n"); > + ret = TEST_SKIP_EXIT; > + } else if (!g_dropped || g_notify_rc != 0) { > + fprintf(stderr, "test_notify_resend: notify failed " > + "(dropped=%d rc=%d)\n", g_dropped, g_notify_rc); > + } else { > + fprintf(stderr, "test_notify_resend: request was not " > + "redelivered with the resend bit\n"); > + } > + > +out_unmount: > + fuse_session_unmount(se); > + fuse_session_destroy(se); > + return ret; > +} ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND 2026-10-09 8:19 ` Bernd Schubert @ 2026-10-09 9:31 ` Baokun Li 2026-10-09 13:51 ` Luis Henriques 0 siblings, 1 reply; 8+ messages in thread From: Baokun Li @ 2026-10-09 9:31 UTC (permalink / raw) To: Bernd Schubert; +Cc: fuse-devel, miklos, Luis Henriques Hi Bernd, On 2026/10/9 16:19, Bernd Schubert wrote: > Hi Baokun, > > On 10/9/26 09:54, Baokun Li wrote: >> test_notify_resend drops the first GETATTR of a file and asks the >> kernel to resend pending requests via fuse_lowlevel_notify_resend(). >> The kernel redelivers the request with the FUSE_UNIQUE_RESEND bit set; >> the daemon detects it with fuse_req_is_resend(), answers it, and the >> client's stat() completes. > to be honest, this is one the features that I have no clue about what it > exists for. Why would a daemon drop requests? The daemon does not drop them on purpose - they are lost because it dies with the requests in flight. This is the failover scenario the feature was added for: the old daemon has already read a request from /dev/fuse, but panics before replying to it. Meanwhile the file descriptor of the connection is preserved - by another daemon, or simply by the service manager: systemd's file descriptor store keeps the /dev/fuse fd across a daemon restart (FDSTORE=1, retrieved again with sd_listen_fds()). A newly started daemon then takes the fd over and keeps serving new requests; the requests the old daemon had already read can be recovered with FUSE_NOTIFY_RESEND (760eac73f9f6, "fuse: Introduce a new notification type for resend pending requests"). > The use-case I have in my mind is a daemon upgrade, Yes, a daemon upgrade is indeed one of the scenarios. It is the same flow as the failover case above, only planned: the old daemon hands the fd over to the new daemon instead of dying with it. > but that would mean that either kernel needs > to send a re-lookup of all inodes or that daemon needs to store lookup > objects (I personally see these as counter part of kernel inodes). Now, > besides not being implemented at all, kernel will have a hard time to > send re-lookup requests, unless fuse-server supports lookup by 64 bit > node-id. You're right that the new daemon can only answer the resent requests if it can still map their node IDs (and other state) to its own objects - that is the "daemon stores lookup objects" side you mention, and it is deliberately left to the daemon. FUSE_NOTIFY_RESEND does not attempt to solve state preservation - it only guarantees delivery of the requests that would otherwise be unreachable forever, no matter how much state the daemon kept. > I.e. in my personal opinion the prerequisite of FUSE_NOTIFY_RESEND is > missing (which could be LOOKUP_HANDLE and then using that to re-lookup > inodes after a daemon termination). Something like LOOKUP_HANDLE, where the kernel and the daemon pass file handles directly so no node ID mapping is needed, would be ideal. However, I think a persistent node ID mapping on the daemon side would also work. > The example below also just does a simple exercise of > FUSE_NOTIFY_RESEND, without revealing what it would be useful for - that > is the part I would like to see fulfilled by an example. > Yes, it only checks that the API works. It has no fd preservation mechanism and no persistent node ID mapping either. A full takeover setup needs the fd and state handover, which is daemon specific and not something a libfuse test can realistically contain, so I think explaining the model in the test and the commit message is the right scope. Let me know if you would prefer something different. Thanks, Baokun ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND 2026-10-09 9:31 ` Baokun Li @ 2026-10-09 13:51 ` Luis Henriques 2026-10-10 2:33 ` Baokun Li 0 siblings, 1 reply; 8+ messages in thread From: Luis Henriques @ 2026-10-09 13:51 UTC (permalink / raw) To: Baokun Li; +Cc: Bernd Schubert, fuse-devel, miklos Hi! On Fri, Oct 09 2026, Baokun Li wrote: > Hi Bernd, > > On 2026/10/9 16:19, Bernd Schubert wrote: >> Hi Baokun, >> >> On 10/9/26 09:54, Baokun Li wrote: >>> test_notify_resend drops the first GETATTR of a file and asks the >>> kernel to resend pending requests via fuse_lowlevel_notify_resend(). >>> The kernel redelivers the request with the FUSE_UNIQUE_RESEND bit set; >>> the daemon detects it with fuse_req_is_resend(), answers it, and the >>> client's stat() completes. >> to be honest, this is one the features that I have no clue about what it >> exists for. Why would a daemon drop requests? > > The daemon does not drop them on purpose - they are lost because it > dies with the requests in flight. This is the failover scenario the > feature was added for: the old daemon has already read a request from > /dev/fuse, but panics before replying to it. > > Meanwhile the file descriptor of the connection is preserved - by > another daemon, or simply by the service manager: systemd's file > descriptor store keeps the /dev/fuse fd across a daemon restart > (FDSTORE=1, retrieved again with sd_listen_fds()). > > A newly started daemon then takes the fd over and keeps serving > new requests; the requests the old daemon had already read can be > recovered with FUSE_NOTIFY_RESEND (760eac73f9f6, "fuse: Introduce a > new notification type for resend pending requests"). > >> The use-case I have in my mind is a daemon upgrade, > > Yes, a daemon upgrade is indeed one of the scenarios. It is the same > flow as the failover case above, only planned: the old daemon hands > the fd over to the new daemon instead of dying with it. > >> but that would mean that either kernel needs >> to send a re-lookup of all inodes or that daemon needs to store lookup >> objects (I personally see these as counter part of kernel inodes). Now, >> besides not being implemented at all, kernel will have a hard time to >> send re-lookup requests, unless fuse-server supports lookup by 64 bit >> node-id. > > You're right that the new daemon can only answer the resent > requests if it can still map their node IDs (and other state) to > its own objects - that is the "daemon stores lookup objects" side > you mention, and it is deliberately left to the daemon. > > FUSE_NOTIFY_RESEND does not attempt to solve state preservation - > it only guarantees delivery of the requests that would otherwise > be unreachable forever, no matter how much state the daemon kept. > >> I.e. in my personal opinion the prerequisite of FUSE_NOTIFY_RESEND is >> missing (which could be LOOKUP_HANDLE and then using that to re-lookup >> inodes after a daemon termination). > > Something like LOOKUP_HANDLE, where the kernel and the daemon pass > file handles directly so no node ID mapping is needed, would be > ideal. However, I think a persistent node ID mapping on the daemon > side would also work. > >> The example below also just does a simple exercise of >> FUSE_NOTIFY_RESEND, without revealing what it would be useful for - that >> is the part I would like to see fulfilled by an example. >> > > Yes, it only checks that the API works. It has no fd preservation > mechanism and no persistent node ID mapping either. > > A full takeover setup needs the fd and state handover, which is daemon > specific and not something a libfuse test can realistically contain, > so I think explaining the model in the test and the commit message is > the right scope. Let me know if you would prefer something different. Thank you, Baokun, for the above description (and Bernd for explicitly CC'ing me to a patchset that I could have missed!). I had a PR [1] created long time ago that included something similar to your first patch in this set. The difference was that I also tried to solve yet another problem: setting the fd back into libfuse-specific data structures (i.e. re-initialise libfuse itself). This was an RFC, which later made me start looking into the LOOKUP_HANDLE. Which is currently stale, but hopefully to resume soon. [1] https://github.com/libfuse/libfuse/pull/1219 Cheers, -- Luís ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND 2026-10-09 13:51 ` Luis Henriques @ 2026-10-10 2:33 ` Baokun Li 2026-10-13 8:32 ` Luis Henriques 0 siblings, 1 reply; 8+ messages in thread From: Baokun Li @ 2026-10-10 2:33 UTC (permalink / raw) To: Luis Henriques; +Cc: Bernd Schubert, fuse-devel, miklos Hi Luís, On 2026/10/9 21:51, Luis Henriques wrote: > Hi! > > On Fri, Oct 09 2026, Baokun Li wrote: > >> Hi Bernd, >> >> On 2026/10/9 16:19, Bernd Schubert wrote: >>> Hi Baokun, >>> >>> On 10/9/26 09:54, Baokun Li wrote: >>>> test_notify_resend drops the first GETATTR of a file and asks the >>>> kernel to resend pending requests via fuse_lowlevel_notify_resend(). >>>> The kernel redelivers the request with the FUSE_UNIQUE_RESEND bit set; >>>> the daemon detects it with fuse_req_is_resend(), answers it, and the >>>> client's stat() completes. >>> to be honest, this is one the features that I have no clue about what it >>> exists for. Why would a daemon drop requests? >> The daemon does not drop them on purpose - they are lost because it >> dies with the requests in flight. This is the failover scenario the >> feature was added for: the old daemon has already read a request from >> /dev/fuse, but panics before replying to it. >> >> Meanwhile the file descriptor of the connection is preserved - by >> another daemon, or simply by the service manager: systemd's file >> descriptor store keeps the /dev/fuse fd across a daemon restart >> (FDSTORE=1, retrieved again with sd_listen_fds()). >> >> A newly started daemon then takes the fd over and keeps serving >> new requests; the requests the old daemon had already read can be >> recovered with FUSE_NOTIFY_RESEND (760eac73f9f6, "fuse: Introduce a >> new notification type for resend pending requests"). >> >>> The use-case I have in my mind is a daemon upgrade, >> Yes, a daemon upgrade is indeed one of the scenarios. It is the same >> flow as the failover case above, only planned: the old daemon hands >> the fd over to the new daemon instead of dying with it. >> >>> but that would mean that either kernel needs >>> to send a re-lookup of all inodes or that daemon needs to store lookup >>> objects (I personally see these as counter part of kernel inodes). Now, >>> besides not being implemented at all, kernel will have a hard time to >>> send re-lookup requests, unless fuse-server supports lookup by 64 bit >>> node-id. >> You're right that the new daemon can only answer the resent >> requests if it can still map their node IDs (and other state) to >> its own objects - that is the "daemon stores lookup objects" side >> you mention, and it is deliberately left to the daemon. >> >> FUSE_NOTIFY_RESEND does not attempt to solve state preservation - >> it only guarantees delivery of the requests that would otherwise >> be unreachable forever, no matter how much state the daemon kept. >> >>> I.e. in my personal opinion the prerequisite of FUSE_NOTIFY_RESEND is >>> missing (which could be LOOKUP_HANDLE and then using that to re-lookup >>> inodes after a daemon termination). >> Something like LOOKUP_HANDLE, where the kernel and the daemon pass >> file handles directly so no node ID mapping is needed, would be >> ideal. However, I think a persistent node ID mapping on the daemon >> side would also work. >> >>> The example below also just does a simple exercise of >>> FUSE_NOTIFY_RESEND, without revealing what it would be useful for - that >>> is the part I would like to see fulfilled by an example. >>> >> Yes, it only checks that the API works. It has no fd preservation >> mechanism and no persistent node ID mapping either. >> >> A full takeover setup needs the fd and state handover, which is daemon >> specific and not something a libfuse test can realistically contain, >> so I think explaining the model in the test and the commit message is >> the right scope. Let me know if you would prefer something different. > Thank you, Baokun, for the above description (and Bernd for explicitly > CC'ing me to a patchset that I could have missed!). > > I had a PR [1] created long time ago that included something similar to > your first patch in this set. Sorry for missing your PR #1219. We had been using resend through the early sysfs based scheme all along; only recently, when moving to a newer kernel and preparing the switch to the notify based one, did we find that libfuse had no API for it - hence this patch. > The difference was that I also tried to > solve yet another problem: setting the fd back into libfuse-specific data > structures (i.e. re-initialise libfuse itself). The RFC looks good to me. I did not implement fuse_session_reinitialize, because our daemon restores the session in a hacky way, so we had no need for the interface - but adding it would clearly be better. > This was an RFC, which later made me start looking into the LOOKUP_HANDLE. > Which is currently stale, but hopefully to resume soon. > > [1] https://github.com/libfuse/libfuse/pull/1219 > > Cheers, Note that resend is not strongly tied to LOOKUP_HANDLE: we have been using it without LOOKUP_HANDLE for several years, with the daemon keeping its own state across restarts. That said, LOOKUP_HANDLE is indeed valuable - we have a similar mechanism on our side as well. Cheers, Baokun ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND 2026-10-10 2:33 ` Baokun Li @ 2026-10-13 8:32 ` Luis Henriques 0 siblings, 0 replies; 8+ messages in thread From: Luis Henriques @ 2026-10-13 8:32 UTC (permalink / raw) To: Baokun Li; +Cc: Bernd Schubert, fuse-devel, miklos On Sat, Oct 10 2026, Baokun Li wrote: > Hi Luís, > > On 2026/10/9 21:51, Luis Henriques wrote: >> Hi! >> >> On Fri, Oct 09 2026, Baokun Li wrote: >> >>> Hi Bernd, >>> >>> On 2026/10/9 16:19, Bernd Schubert wrote: >>>> Hi Baokun, >>>> >>>> On 10/9/26 09:54, Baokun Li wrote: >>>>> test_notify_resend drops the first GETATTR of a file and asks the >>>>> kernel to resend pending requests via fuse_lowlevel_notify_resend(). >>>>> The kernel redelivers the request with the FUSE_UNIQUE_RESEND bit set; >>>>> the daemon detects it with fuse_req_is_resend(), answers it, and the >>>>> client's stat() completes. >>>> to be honest, this is one the features that I have no clue about what it >>>> exists for. Why would a daemon drop requests? >>> The daemon does not drop them on purpose - they are lost because it >>> dies with the requests in flight. This is the failover scenario the >>> feature was added for: the old daemon has already read a request from >>> /dev/fuse, but panics before replying to it. >>> >>> Meanwhile the file descriptor of the connection is preserved - by >>> another daemon, or simply by the service manager: systemd's file >>> descriptor store keeps the /dev/fuse fd across a daemon restart >>> (FDSTORE=1, retrieved again with sd_listen_fds()). >>> >>> A newly started daemon then takes the fd over and keeps serving >>> new requests; the requests the old daemon had already read can be >>> recovered with FUSE_NOTIFY_RESEND (760eac73f9f6, "fuse: Introduce a >>> new notification type for resend pending requests"). >>> >>>> The use-case I have in my mind is a daemon upgrade, >>> Yes, a daemon upgrade is indeed one of the scenarios. It is the same >>> flow as the failover case above, only planned: the old daemon hands >>> the fd over to the new daemon instead of dying with it. >>> >>>> but that would mean that either kernel needs >>>> to send a re-lookup of all inodes or that daemon needs to store lookup >>>> objects (I personally see these as counter part of kernel inodes). Now, >>>> besides not being implemented at all, kernel will have a hard time to >>>> send re-lookup requests, unless fuse-server supports lookup by 64 bit >>>> node-id. >>> You're right that the new daemon can only answer the resent >>> requests if it can still map their node IDs (and other state) to >>> its own objects - that is the "daemon stores lookup objects" side >>> you mention, and it is deliberately left to the daemon. >>> >>> FUSE_NOTIFY_RESEND does not attempt to solve state preservation - >>> it only guarantees delivery of the requests that would otherwise >>> be unreachable forever, no matter how much state the daemon kept. >>> >>>> I.e. in my personal opinion the prerequisite of FUSE_NOTIFY_RESEND is >>>> missing (which could be LOOKUP_HANDLE and then using that to re-lookup >>>> inodes after a daemon termination). >>> Something like LOOKUP_HANDLE, where the kernel and the daemon pass >>> file handles directly so no node ID mapping is needed, would be >>> ideal. However, I think a persistent node ID mapping on the daemon >>> side would also work. >>> >>>> The example below also just does a simple exercise of >>>> FUSE_NOTIFY_RESEND, without revealing what it would be useful for - that >>>> is the part I would like to see fulfilled by an example. >>>> >>> Yes, it only checks that the API works. It has no fd preservation >>> mechanism and no persistent node ID mapping either. >>> >>> A full takeover setup needs the fd and state handover, which is daemon >>> specific and not something a libfuse test can realistically contain, >>> so I think explaining the model in the test and the commit message is >>> the right scope. Let me know if you would prefer something different. >> Thank you, Baokun, for the above description (and Bernd for explicitly >> CC'ing me to a patchset that I could have missed!). >> >> I had a PR [1] created long time ago that included something similar to >> your first patch in this set. > > Sorry for missing your PR #1219. We had been using resend through > the early sysfs based scheme all along; only recently, when moving > to a newer kernel and preparing the switch to the notify based one, > did we find that libfuse had no API for it - hence this patch. Sure, no problem. That PR has been stale, and I'm fine with your patch. Feel free to add my 'Acked-by:'. >> The difference was that I also tried to >> solve yet another problem: setting the fd back into libfuse-specific data >> structures (i.e. re-initialise libfuse itself). > > The RFC looks good to me. I did not implement > fuse_session_reinitialize, because our daemon restores the session in > a hacky way, so we had no need for the interface - but adding it would > clearly be better. > >> This was an RFC, which later made me start looking into the LOOKUP_HANDLE. >> Which is currently stale, but hopefully to resume soon. >> >> [1] https://github.com/libfuse/libfuse/pull/1219 >> >> Cheers, > > Note that resend is not strongly tied to LOOKUP_HANDLE: we have been > using it without LOOKUP_HANDLE for several years, with the daemon > keeping its own state across restarts. Yes, absolutely. If you keep the state on user-space and if nodeids are always unique and not reused, then LOOKUP_HANDLE is not really required. > That said, LOOKUP_HANDLE is indeed valuable - we have a similar > mechanism on our side as well. Yeah, this will make it easier for new servers to support a restart without extra complexities. Cheers, -- Luís ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-13 8:31 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-09 7:54 [PATCH 0/2] libfuse: add support for FUSE_NOTIFY_RESEND Baokun Li 2026-10-09 7:54 ` [PATCH 1/2] fuse_lowlevel: " Baokun Li 2026-10-09 7:54 ` [PATCH 2/2] test: exercise FUSE_NOTIFY_RESEND Baokun Li 2026-10-09 8:19 ` Bernd Schubert 2026-10-09 9:31 ` Baokun Li 2026-10-09 13:51 ` Luis Henriques 2026-10-10 2:33 ` Baokun Li 2026-10-13 8:32 ` Luis Henriques
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.