libfuse: exiting fuse_session_loop
Asked Answered
P

4

6

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?

Placate answered 18/1, 2012 at 0:6 Comment(0)
P
1

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.

Placate answered 18/1, 2012 at 22:41 Comment(0)
P
2

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().

Poser answered 18/1, 2012 at 0:30 Comment(1)
I think Ive figured it out, I'm installing the signal handler with just a regular signal call which for some reason is getting passed to the kernel as: rt_sigaction(SIGINT, {0x40af90, [INT], SA_RESTORER|SA_RESTART, 0x7fcac93ea420}, {SIG_DFL, [], 0}, 8) = 0. This has the SA_RESTART flag set for some reason so when the signal handler is called, the read syscall is not interrupted but resumed.Placate
P
1

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.

Placate answered 18/1, 2012 at 22:41 Comment(0)
C
1

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
Commutable answered 8/4, 2018 at 14:50 Comment(0)
C
0

Read the manpage on signal: http://man7.org/linux/man-pages/man2/signal.2.html

The behavior of signal() varies across UNIX versions, and has also varied historically across different versions of Linux. Avoid its use: use sigaction(2) instead. See Portability below.

The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.

The situation on Linux is as follows:

  • The kernel's signal() system call provides System V semantics.

  • By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls sigaction(2) using flags that supply BSD semantics. This default behavior is provided as long as the _BSD_SOURCE feature test macro is defined. By default, _BSD_SOURCE is defined; it is also implicitly defined if one defines _GNU_SOURCE, and can of course be explicitly defined.

  • On glibc 2 and later, if the _BSD_SOURCE feature test macro is not defined, then signal() provides System V semantics. (The default implicit definition of _BSD_SOURCE is not provided if one invokes gcc(1) in one of its standard modes (-std=xxx or -ansi) or defines various other feature test macros such as _POSIX_SOURCE, _XOPEN_SOURCE, or _SVID_SOURCE; see feature_test_macros(7).)

So GLibc used to have signal not restart afterwards but they changed it to make it more compatible with BSD.

Cynthea answered 17/11, 2014 at 22:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.