[PATCH] fuse: fix the bug of missing EPOLLET event wakeup

Zhang Tianci posted 1 patch 1 month, 1 week ago
fs/fuse/file.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
[PATCH] fuse: fix the bug of missing EPOLLET event wakeup
Posted by Zhang Tianci 1 month, 1 week ago
Users using Go have reported an issue to us:
When performing read/write operations with goroutines,
since fuse's file->f_ops->poll is not empty,
read/write operations are conducted via epoll.
Additionally, goroutines use the EPOLLET wake-up mode.

Currently, the implementation of fuse_file_poll has
the following problem:
After receiving EAGAIN during read/write operations,
the goroutine calls epoll_wait again, but then results in
permanent blocking. This is because a wake-up event
is required in EPOLLET mode.

The modification idea of this patch is based on the
implementation of epoll_wait:
After epoll_wait calls ->poll() for EPOLLET events,
it does not reinsert them into the ready list.
In this case, ep_poll_callback() needs to be used to
reinsert them into the ready list, so that the behavior
of EPOLLET is consistent with that of non-EPOLLET.

Reported-by: Geng Xueyu <gengxueyu.520@bytedance.com>
Reported-by: Wang Jian <wangjian.pg@bytedance.com>
Suggested-by: Xie Yongji <xieyongji@bytedance.com>
Signed-off-by: Zhang Tianci <zhangtianci.1997@bytedance.com>
---

Here is the reproducer for the bug:

1. Change libfuse/example/passthrough.c's xmp_read() to
   always return -EAGAIN and mount it on /mnt_fuse. 
2. Then create test file:
   dd if=/dev/zero of=/mnt_fuse/test_poll bs=1M count=10
3. compile and run the following test case.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10
#define BUF_SIZE 4096

int main() {
    int epfd, nfds, fd;
    struct epoll_event ev, events[MAX_EVENTS];
    char buffer[BUF_SIZE];

    fd = open("/mnt_fuse/test_poll", O_RDWR|O_NONBLOCK|O_CREAT, 0644);
    if (fd == -1) {
	perror("open");
	return -1;
    }

    epfd = epoll_create1(0);
    if (epfd == -1) {
        perror("epoll_create1");
	return -1;
    }

    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLET;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        perror("epoll_ctl: fd");
	return -1;
    }

    printf("Epoll is monitoring fd=%d.\n", fd);

    while (1) {
        nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            if (errno == EINTR) {
		continue;
	    }
            perror("epoll_wait");
	    return -1;
        }

        for (int i = 0; i < nfds; ++i) {
            int current_fd = events[i].data.fd;

            if (current_fd == fd) {
                printf("[Notification] Data arrived on fd\n", fd);

                while (1) {
                    ssize_t count = read(current_fd, buffer, BUF_SIZE);

                    if (count == -1) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            printf("[Info] Buffer is empty, waiting for next event...\n");
                            break;
                        } else {
                            perror("read");
                            close(current_fd);
			    return -1;
                        }
                    } else if (count == 0) {
                        printf("[Info] EOF detected. Closing.\n");
                        close(current_fd);
                        return 0;
                    }
                    printf(">>> Read %zd bytes\n", count);
                }
            }
        }
    }

    close(epfd);
    return 0;
}

 fs/fuse/file.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 01bc894e9c2ba..025eea58232c2 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -2735,10 +2735,11 @@ __poll_t fuse_file_poll(struct file *file, poll_table *wait)
 	FUSE_ARGS(args);
 	int err;
 
+	poll_wait(file, &ff->poll_wait, wait);
+
 	if (fm->fc->no_poll)
-		return DEFAULT_POLLMASK;
+		goto no_poll;
 
-	poll_wait(file, &ff->poll_wait, wait);
 	inarg.events = mangle_poll(poll_requested_events(wait));
 
 	/*
@@ -2764,9 +2765,13 @@ __poll_t fuse_file_poll(struct file *file, poll_table *wait)
 		return demangle_poll(outarg.revents);
 	if (err == -ENOSYS) {
 		fm->fc->no_poll = 1;
-		return DEFAULT_POLLMASK;
+		goto no_poll;
 	}
 	return EPOLLERR;
+
+no_poll:
+	wake_up_interruptible_sync(&ff->poll_wait);
+	return DEFAULT_POLLMASK;
 }
 EXPORT_SYMBOL_GPL(fuse_file_poll);
 
-- 
2.39.5
Re: [PATCH] fuse: fix the bug of missing EPOLLET event wakeup
Posted by Markus Elfring 1 week, 3 days ago
…
> After receiving EAGAIN during read/write operations,
> the goroutine calls epoll_wait again, but then results in
> permanent blocking. This is because a wake-up event
> is required in EPOLLET mode.
…

You may occasionally put more than 57 characters into text lines
of such a change description.
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst?h=v6.19-rc7#n659

Regards,
Markus
Re: [PATCH] fuse: fix the bug of missing EPOLLET event wakeup
Posted by Miklos Szeredi 1 week, 3 days ago
On Thu, 25 Dec 2025 at 12:03, Zhang Tianci
<zhangtianci.1997@bytedance.com> wrote:
>
> Users using Go have reported an issue to us:
> When performing read/write operations with goroutines,
> since fuse's file->f_ops->poll is not empty,

Another one of those historical mistakes...

> read/write operations are conducted via epoll.
> Additionally, goroutines use the EPOLLET wake-up mode.
>
> Currently, the implementation of fuse_file_poll has
> the following problem:
> After receiving EAGAIN during read/write operations,

Why is read/write returning EAGAIN?

Thanks,
Miklos
Re: [PATCH] fuse: fix the bug of missing EPOLLET event wakeup
Posted by Zhang Tianci 1 month ago
gently ping...
Re: [PATCH] fuse: fix the bug of missing EPOLLET event wakeup
Posted by Zhang Tianci 1 week, 3 days ago
gently ping...