Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 12 May 2019 15:27:18 +0000 (UTC)
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r347513 - in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs
Message-ID:  <201905121527.x4CFRIxl071684@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: asomers
Date: Sun May 12 15:27:18 2019
New Revision: 347513
URL: https://svnweb.freebsd.org/changeset/base/347513

Log:
  fusefs: Report the number of available ops in kevent(2)
  
  Just like /dev/devctl, /dev/fuse will now report the number of operations
  available for immediate read in the kevent.data field during kevent(2).
  
  Sponsored by:	The FreeBSD Foundation

Modified:
  projects/fuse2/sys/fs/fuse/fuse_device.c
  projects/fuse2/sys/fs/fuse/fuse_ipc.c
  projects/fuse2/sys/fs/fuse/fuse_ipc.h
  projects/fuse2/tests/sys/fs/fusefs/dev_fuse_poll.cc
  projects/fuse2/tests/sys/fs/fusefs/mockfs.cc
  projects/fuse2/tests/sys/fs/fusefs/mockfs.hh

Modified: projects/fuse2/sys/fs/fuse/fuse_device.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_device.c	Sun May 12 15:27:01 2019	(r347512)
+++ projects/fuse2/sys/fs/fuse/fuse_device.c	Sun May 12 15:27:18 2019	(r347513)
@@ -205,11 +205,8 @@ fuse_device_filt_read(struct knote *kn, long hint)
 		kn->kn_data = 1;
 		ready = 1;
 	} else if (STAILQ_FIRST(&data->ms_head)) {
-		/* 
-		 * There is at least one event to read.
-		 * TODO: keep a counter of the number of events to read
-		 */
-		kn->kn_data = 1;
+		MPASS(data->ms_count >= 1);
+		kn->kn_data = data->ms_count;
 		ready = 1;
 	} else {
 		ready = 0;

Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_ipc.c	Sun May 12 15:27:01 2019	(r347512)
+++ projects/fuse2/sys/fs/fuse/fuse_ipc.c	Sun May 12 15:27:18 2019	(r347513)
@@ -204,6 +204,7 @@ fuse_interrupt_send(struct fuse_ticket *otick, int err
 			if (tick == otick) {
 				STAILQ_REMOVE(&otick->tk_data->ms_head, tick,
 					fuse_ticket, tk_ms_link);
+				otick->tk_data->ms_count--;
 				otick->tk_ms_link.stqe_next = NULL;
 				fuse_lck_mtx_unlock(data->ms_mtx);
 
@@ -586,6 +587,7 @@ fdata_alloc(struct cdev *fdev, struct ucred *cred)
 	data->fdev = fdev;
 	mtx_init(&data->ms_mtx, "fuse message list mutex", NULL, MTX_DEF);
 	STAILQ_INIT(&data->ms_head);
+	data->ms_count = 0;
 	knlist_init_mtx(&data->ks_rsel.si_note, &data->ms_mtx);
 	mtx_init(&data->aw_mtx, "fuse answer list mutex", NULL, MTX_DEF);
 	TAILQ_INIT(&data->aw_head);

Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.h
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_ipc.h	Sun May 12 15:27:01 2019	(r347512)
+++ projects/fuse2/sys/fs/fuse/fuse_ipc.h	Sun May 12 15:27:18 2019	(r347513)
@@ -180,6 +180,7 @@ struct fuse_data {
 
 	struct mtx			ms_mtx;
 	STAILQ_HEAD(, fuse_ticket)	ms_head;
+	int				ms_count;
 
 	struct mtx			aw_mtx;
 	TAILQ_HEAD(, fuse_ticket)	aw_head;
@@ -290,6 +291,7 @@ fuse_ms_push(struct fuse_ticket *ftick)
 	mtx_assert(&ftick->tk_data->ms_mtx, MA_OWNED);
 	refcount_acquire(&ftick->tk_refcount);
 	STAILQ_INSERT_TAIL(&ftick->tk_data->ms_head, ftick, tk_ms_link);
+	ftick->tk_data->ms_count++;
 }
 
 /* Insert a new upgoing message to the front of the queue */
@@ -299,6 +301,7 @@ fuse_ms_push_head(struct fuse_ticket *ftick)
 	mtx_assert(&ftick->tk_data->ms_mtx, MA_OWNED);
 	refcount_acquire(&ftick->tk_refcount);
 	STAILQ_INSERT_HEAD(&ftick->tk_data->ms_head, ftick, tk_ms_link);
+	ftick->tk_data->ms_count++;
 }
 
 static inline struct fuse_ticket *
@@ -310,7 +313,9 @@ fuse_ms_pop(struct fuse_data *data)
 
 	if ((ftick = STAILQ_FIRST(&data->ms_head))) {
 		STAILQ_REMOVE_HEAD(&data->ms_head, tk_ms_link);
+		data->ms_count--;
 #ifdef INVARIANTS
+		MPASS(data->ms_count >= 0);
 		ftick->tk_ms_link.stqe_next = NULL;
 #endif
 	}

Modified: projects/fuse2/tests/sys/fs/fusefs/dev_fuse_poll.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/dev_fuse_poll.cc	Sun May 12 15:27:01 2019	(r347512)
+++ projects/fuse2/tests/sys/fs/fusefs/dev_fuse_poll.cc	Sun May 12 15:27:18 2019	(r347513)
@@ -34,6 +34,7 @@
 
 extern "C" {
 #include <fcntl.h>
+#include <semaphore.h>
 #include <unistd.h>
 }
 
@@ -71,6 +72,13 @@ class DevFusePoll: public FuseTest, public WithParamIn
 	}
 };
 
+class Kqueue: public FuseTest {
+	virtual void SetUp() {
+		m_pm = KQ;
+		FuseTest::SetUp();
+	}
+};
+
 TEST_P(DevFusePoll, access)
 {
 	expect_access(1, X_OK, 0);
@@ -91,3 +99,126 @@ TEST_P(DevFusePoll, destroy)
 
 INSTANTIATE_TEST_CASE_P(PM, DevFusePoll,
 		::testing::Values("BLOCKING", "KQ", "POLL", "SELECT"));
+
+static void* statter(void* arg) {
+	const char *name;
+	struct stat sb;
+
+	name = (const char*)arg;
+	stat(name, &sb);
+	return 0;
+}
+
+/*
+ * A kevent's data field should contain the number of operations available to
+ * be immediately rea.
+ */
+TEST_F(Kqueue, data)
+{
+	pthread_t th0, th1, th2;
+	sem_t sem0, sem1;
+	int nready0, nready1, nready2;
+	uint64_t foo_ino = 42;
+	uint64_t bar_ino = 43;
+	uint64_t baz_ino = 44;
+
+	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
+
+	EXPECT_LOOKUP(1, "foo")
+	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.entry_valid = UINT64_MAX;
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = foo_ino;
+	})));
+	EXPECT_LOOKUP(1, "bar")
+	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.entry_valid = UINT64_MAX;
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = bar_ino;
+	})));
+	EXPECT_LOOKUP(1, "baz")
+	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
+		SET_OUT_HEADER_LEN(out, entry);
+		out->body.entry.entry_valid = UINT64_MAX;
+		out->body.entry.attr.mode = S_IFREG | 0644;
+		out->body.entry.nodeid = baz_ino;
+	})));
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == foo_ino);
+		}, Eq(true)),
+		_)
+	)
+	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
+		nready0 = m_mock->m_nready;
+
+		sem_post(&sem0);
+		// Block the daemon so we can accumulate a few more ops
+		sem_wait(&sem1);
+
+		out->header.unique = in->header.unique;
+		out->header.error = -EIO;
+		out->header.len = sizeof(out->header);
+	})));
+
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == bar_ino);
+		}, Eq(true)),
+		_)
+	)
+	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
+		nready1 = m_mock->m_nready;
+		out->header.unique = in->header.unique;
+		out->header.error = -EIO;
+		out->header.len = sizeof(out->header);
+	})));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([=](auto in) {
+			return (in->header.opcode == FUSE_GETATTR &&
+				in->header.nodeid == baz_ino);
+		}, Eq(true)),
+		_)
+	)
+	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
+		nready2 = m_mock->m_nready;
+		out->header.unique = in->header.unique;
+		out->header.error = -EIO;
+		out->header.len = sizeof(out->header);
+	})));
+
+	/* 
+	 * Create cached lookup entries for these files.  It seems that only
+	 * one thread at a time can be in VOP_LOOKUP for a given directory
+	 */
+	access("mountpoint/foo", F_OK);
+	access("mountpoint/bar", F_OK);
+	access("mountpoint/baz", F_OK);
+	ASSERT_EQ(0, pthread_create(&th0, NULL, statter,
+		(void*)"mountpoint/foo")) << strerror(errno);
+	EXPECT_EQ(0, sem_wait(&sem0)) << strerror(errno);
+	ASSERT_EQ(0, pthread_create(&th1, NULL, statter,
+		(void*)"mountpoint/bar")) << strerror(errno);
+	ASSERT_EQ(0, pthread_create(&th2, NULL, statter,
+		(void*)"mountpoint/baz")) << strerror(errno);
+
+	nap();		// Allow th1 and th2 to send their ops to the daemon
+	EXPECT_EQ(0, sem_post(&sem1)) << strerror(errno);
+
+	pthread_join(th0, NULL);
+	pthread_join(th1, NULL);
+	pthread_join(th2, NULL);
+
+	EXPECT_EQ(1, nready0);
+	EXPECT_EQ(2, nready1);
+	EXPECT_EQ(1, nready2);
+
+	sem_destroy(&sem0);
+	sem_destroy(&sem1);
+}

Modified: projects/fuse2/tests/sys/fs/fusefs/mockfs.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/mockfs.cc	Sun May 12 15:27:01 2019	(r347512)
+++ projects/fuse2/tests/sys/fs/fusefs/mockfs.cc	Sun May 12 15:27:18 2019	(r347513)
@@ -294,6 +294,7 @@ MockFS::MockFS(int max_readahead, bool allow_other, bo
 
 	m_daemon_id = NULL;
 	m_maxreadahead = max_readahead;
+	m_nready = -1;
 	m_pm = pm;
 	m_quit = false;
 	if (m_pm == KQ)
@@ -521,6 +522,7 @@ void MockFS::read_request(mockfs_buf_in *in) {
 			FAIL() << strerror(events[0].data);
 		else if (events[0].flags & EV_EOF)
 			FAIL() << strerror(events[0].fflags);
+		m_nready = events[0].data;
 		break;
 	case POLL:
 		fds[0].fd = m_fuse_fd;

Modified: projects/fuse2/tests/sys/fs/fusefs/mockfs.hh
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/mockfs.hh	Sun May 12 15:27:01 2019	(r347512)
+++ projects/fuse2/tests/sys/fs/fusefs/mockfs.hh	Sun May 12 15:27:18 2019	(r347513)
@@ -163,6 +163,14 @@ ProcessMockerT ReturnImmediate(
 	std::function<void(const struct mockfs_buf_in *in,
 			   struct mockfs_buf_out *out)> f);
 
+/* How the daemon should check /dev/fuse for readiness */
+enum poll_method {
+	BLOCKING,
+	SELECT,
+	POLL,
+	KQ
+};
+
 /*
  * Fake FUSE filesystem
  *
@@ -183,12 +191,17 @@ class MockFS {
 	/* file descriptor of /dev/fuse control device */
 	int m_fuse_fd;
 	
+	int m_kq;
+
 	/* The max_readahead filesystem option */
 	uint32_t m_maxreadahead;
 
 	/* pid of the test process */
 	pid_t m_pid;
 
+	/* Method the daemon should use for I/O to and from /dev/fuse */
+	enum poll_method m_pm;
+
 	/* Initialize a session after mounting */
 	void init(uint32_t flags);
 
@@ -205,6 +218,9 @@ class MockFS {
 	/* Read, but do not process, a single request from the kernel */
 	void read_request(mockfs_buf_in*);
 
+	/* Write a single response back to the kernel */
+	void write_response(mockfs_buf_out *out);
+
 	public:
 	/* pid of child process, for two-process test cases */
 	pid_t m_child_pid;
@@ -212,13 +228,19 @@ class MockFS {
 	/* Maximum size of a FUSE_WRITE write */
 	uint32_t m_max_write;
 
+	/* 
+	 * Number of events that were available from /dev/fuse after the last
+	 * kevent call.  Only valid when m_pm = KQ.
+	 */
+	int m_nready;
+
 	/* Tell the daemon to shut down ASAP */
 	bool m_quit;
 
 	/* Create a new mockfs and mount it to a tempdir */
 	MockFS(int max_readahead, bool allow_other,
 		bool default_permissions, bool push_symlinks_in, bool ro,
-		uint32_t flags);
+		enum poll_method pm, uint32_t flags);
 	virtual ~MockFS();
 
 	/* Kill the filesystem daemon without unmounting the filesystem */



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201905121527.x4CFRIxl071684>