Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

libfuse: exiting fuse_session_loop

Tags:

fuse

Context: Ubuntu 11.10 and libfuse 2.8.4-1.4ubuntu1 Linux 3.0.0-14-generic #23-Ubuntu SMP Mon Nov 21 20:28:43 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux

I'm trying to use libfuse. I want to cause fuse_session_loop to exit (from a signal handler or a different thread), but when I call fuse_session_exit nothing happens until the session receives a new request.

fuse_session_exit sets a flag that is read by fuse_session_exited. Debugging into fuse_session_loop it appears to block on fuse_chan_recv, so it doesn't check fuse_session_exited again until the top of the loop...

int fuse_session_loop(struct fuse_session *se)
{
    int res = 0;
    struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
    size_t bufsize = fuse_chan_bufsize(ch);
    char *buf = (char *) malloc(bufsize);
    if (!buf) {
        fprintf(stderr, "fuse: failed to allocate read buffer\n");
        return -1;
    }

    while (!fuse_session_exited(se)) {
        struct fuse_chan *tmpch = ch;
        res = fuse_chan_recv(&tmpch, buf, bufsize); <--- BLOCKING
        if (res == -EINTR)
            continue;
        if (res <= 0)
            break;
        fuse_session_process(se, buf, res, tmpch);
    }

    free(buf);
    fuse_session_reset(se);
    return res < 0 ? -1 : 0;
}

fuse_chan_recv calls fuse_kern_chan_receive which blocks on the "read" syscall of the "/dev/fuse" device, so even though the fuse_session_exited flag is set nothing happens yet.

static int fuse_kern_chan_receive(struct fuse_chan **chp, char *buf,
                  size_t size)
{
    struct fuse_chan *ch = *chp;
    int err;
    ssize_t res;
    struct fuse_session *se = fuse_chan_session(ch);
    assert(se != NULL);

restart:
    res = read(fuse_chan_fd(ch), buf, size); <--- BLOCKING
    err = errno;

    if (fuse_session_exited(se))
        return 0;
    if (res == -1) {
        /* ENOENT means the operation was interrupted, it's safe
           to restart */
        if (err == ENOENT)
            goto restart;

        if (err == ENODEV) {
            fuse_session_exit(se);
            return 0;
        }
        /* Errors occuring during normal operation: EINTR (read
           interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
           umounted) */
        if (err != EINTR && err != EAGAIN)
            perror("fuse: reading device");
        return -err;
    }
    if ((size_t) res < sizeof(struct fuse_in_header)) {
        fprintf(stderr, "short read on fuse device\n");
        return -EIO;
    }
    return res;
}

This problem seems to effect the hello_ll.c example provided with libfuse as well as my program. It makes me think that perhaps there is some mechanism that is not working that should. Perhaps fuse_session_exit is supposed to be also doing something that interrupts the read call, that for some reason is not working on my system.

Any ideas?

like image 523
Andrew Tomazos Avatar asked Jan 18 '12 00:01

Andrew Tomazos


3 Answers

This might be worth a bug report; it might also be closed as "working as expected".

That said, if you also send a signal to interrupt the read() call executing in the fuse_kern_chan_receive() function, it appears prepared to propagate the error up through the stack, which will trigger the continue in the higher-level call, which will notice the exited flag, and hopefully terminate the loop as cleanly as possible.

Try adding a pthread_kill(3) to kill the specific thread in question. fuse_signals.c installs handlers for SIGHUP, SIGINT and SIGTERM that call fuse_session_exit().

like image 180
sarnold Avatar answered Jan 03 '23 16:01

sarnold


Normally if a signal handler is executed while a system call (such as read(2)) is blocking, the system call returns immediately (after the signal handler is completed executing) with an EINTR. This is clearly the behaviour that fuse_session_loop and fuse_session_exit were designed for.

However if the signal handler is installed with the SA_RESTART flag set (see sigaction(2)) the system call will not return with EINTR after the signal handler has executed. The system call will resume blocking instead.

For some reason on my system (Ubuntu 11.10 x86_64) the default behaviour of signal(2) is to install the signal handler with the SA_RESTART flag.

ie the strace of the following program...

#include <stdlib.h>
#include <signal.h>

void f(int signum) {}

int main()
{
    signal(SIGINT,f);
    return EXIT_SUCCESS;
}

...is as follows...

rt_sigaction(SIGINT, {0x400524, [INT], SA_RESTORER|SA_RESTART, 0x7f4997e1f420}, {SIG_DFL, [], 0}, 8) = 0

For this reason the signals (in the examples provided with fuse and my own program) did not interrupt the blocking read in fuse_kern_chan_receive as their authors expected them too.

The fix was to use sigaction(2) (and leave SA_RESTART bit zeroed) to install the handler (instead of signal(2)).

An open question that still remains is why does a call to signal(2) have the SA_RESTART flag on by default? I would expect interrupting (not restarting) is the expected default behavior.

like image 43
Andrew Tomazos Avatar answered Jan 03 '23 17:01

Andrew Tomazos


I couldn't solve the porblem by zeroing SA_RESTART flag for fuse 2.9.2.

Instead, I used a fake read when I want to exit.

  1. Open the file before calling fuse_session_exit
  2. Call fuse_session_exit
  3. Read a byte from the file
like image 25
Ricardo Cristian Ramirez Avatar answered Jan 03 '23 17:01

Ricardo Cristian Ramirez