diff --git a/sys/fs/fuse/fuse_device.c b/sys/fs/fuse/fuse_device.c index 8f5f9bfade96..203da2c6f7ba 100644 --- a/sys/fs/fuse/fuse_device.c +++ b/sys/fs/fuse/fuse_device.c @@ -157,6 +157,13 @@ fdata_dtor(void *arg) fuse_ticket_drop(tick); } fuse_lck_mtx_unlock(fdata->aw_mtx); + + /* Cleanup unsent operations */ + fuse_lck_mtx_lock(fdata->ms_mtx); + while ((tick = fuse_ms_pop(fdata))) { + fuse_ticket_drop(tick); + } + fuse_lck_mtx_unlock(fdata->ms_mtx); FUSE_UNLOCK(); fdata_trydestroy(fdata); diff --git a/tests/sys/fs/fusefs/destroy.cc b/tests/sys/fs/fusefs/destroy.cc index 5b88ba7c2624..2264ba4e1e8c 100644 --- a/tests/sys/fs/fusefs/destroy.cc +++ b/tests/sys/fs/fusefs/destroy.cc @@ -28,13 +28,106 @@ * SUCH DAMAGE. */ +extern "C" { +#include +#include +#include +} + #include "mockfs.hh" #include "utils.hh" using namespace testing; +/* Tests for orderly unmounts */ class Destroy: public FuseTest {}; +/* Tests for unexpected deaths of the server */ +class Death: public FuseTest{}; + +static void* open_th(void* arg) { + int fd; + const char *path = (const char*)arg; + + fd = open(path, O_RDONLY); + EXPECT_EQ(-1, fd); + EXPECT_EQ(ENOTCONN, errno); + return 0; +} + +/* + * The server dies with unsent operations still on the message queue. + * Check for any memory leaks like this: + * 1) kldunload fusefs, if necessary + * 2) kldload fusefs + * 3) ./destroy --gtest_filter=Destroy.unsent_operations + * 4) kldunload fusefs + * 5) check /var/log/messages for anything like this: +Freed UMA keg (fuse_ticket) was not empty (31 items). Lost 2 pages of memory. +Warning: memory type fuse_msgbuf leaked memory on destroy (68 allocations, 428800 bytes leaked). + */ +TEST_F(Death, unsent_operations) +{ + const char FULLPATH0[] = "mountpoint/some_file.txt"; + const char FULLPATH1[] = "mountpoint/other_file.txt"; + const char RELPATH0[] = "some_file.txt"; + const char RELPATH1[] = "other_file.txt"; + pthread_t th0, th1; + ino_t ino0 = 42, ino1 = 43; + sem_t sem; + mode_t mode = S_IFREG | 0644; + + sem_init(&sem, 0, 0); + + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0) + .WillRepeatedly(Invoke( + ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, entry); + out.body.entry.attr.mode = mode; + out.body.entry.nodeid = ino0; + out.body.entry.attr.nlink = 1; + }))); + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1) + .WillRepeatedly(Invoke( + ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, entry); + out.body.entry.attr.mode = mode; + out.body.entry.nodeid = ino1; + out.body.entry.attr.nlink = 1; + }))); + + EXPECT_CALL(*m_mock, process( + ResultOf([&](auto in) { + return (in.header.opcode == FUSE_OPEN); + }, Eq(true)), + _) + ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { + sem_post(&sem); + pause(); + })); + + /* + * One thread's operation will be sent to the daemon and block, and the + * other's will be stuck in the message queue. + */ + ASSERT_EQ(0, pthread_create(&th0, NULL, open_th, (void*)FULLPATH0)) + << strerror(errno); + ASSERT_EQ(0, pthread_create(&th1, NULL, open_th, (void*)FULLPATH1)) + << strerror(errno); + + /* Wait for the first thread to block */ + sem_wait(&sem); + /* Give the second thread time to block */ + nap(); + + m_mock->kill_daemon(); + + pthread_join(th0, NULL); + pthread_join(th1, NULL); + + sem_destroy(&sem); +} + /* * On unmount the kernel should send a FUSE_DESTROY operation. It should also * send FUSE_FORGET operations for all inodes with lookup_count > 0.