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?
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()
.
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.
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.
fuse_session_exit
fuse_session_exit
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With