public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Laurent Vivier <lvivier@redhat.com>
To: passt-dev@passt.top
Cc: Laurent Vivier <lvivier@redhat.com>
Subject: [PATCH 2/7] threading: Add basic threading infrastructure
Date: Fri, 31 Jul 2026 18:46:23 +0200	[thread overview]
Message-ID: <20260731164628.3556997-3-lvivier@redhat.com> (raw)
In-Reply-To: <20260731164628.3556997-1-lvivier@redhat.com>

Introduce a threading framework to support multi-threaded operation,
particularly needed for vhost-user with multiqueue support. The
infrastructure provides:

- Thread pool management with epoll-based event loops
- Worker thread creation and teardown
- Per-thread epoll file descriptors for event handling
- Integration with existing context structure

This change modifies PID namespace handling to be compatible with
pthread_create(), as CLONE_THREAD and CLONE_NEWPID are mutually
exclusive for vhost-user mode.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 Makefile    |   8 +-
 isolation.c |   6 +-
 passt.c     |   7 +-
 passt.h     |   5 ++
 threading.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 threading.h |  22 ++++++
 6 files changed, 245 insertions(+), 10 deletions(-)
 create mode 100644 threading.c
 create mode 100644 threading.h

diff --git a/Makefile b/Makefile
index 77474d695915..1907646f9e93 100644
--- a/Makefile
+++ b/Makefile
@@ -37,8 +37,8 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \
 	epoll_ctl.c flow.c fwd.c fwd_rule.c icmp.c igmp.c inany.c iov.c ip.c \
 	isolation.c lineread.c log.c mld.c ndp.c netlink.c migrate.c packet.c \
 	parse.c passt.c pasta.c pcap.c pif.c repair.c serialise.c tap.c tcp.c \
-	tcp_buf.c tcp_splice.c tcp_vu.c udp.c udp_flow.c udp_vu.c util.c \
-	vhost_user.c virtio.c vu_common.c
+	tcp_buf.c tcp_splice.c tcp_vu.c threading.c udp.c udp_flow.c udp_vu.c \
+	util.c vhost_user.c virtio.c vu_common.c
 PASST_REPAIR_SRCS = passt-repair.c
 PESTO_SRCS = pesto.c bitmap.c fwd_rule.c inany.c ip.c lineread.c parse.c \
 	serialise.c
@@ -51,8 +51,8 @@ PASST_HEADERS = arch.h arp.h bitmap.h checksum.h conf.h dhcp.h dhcpv6.h \
 	inany.h iov.h ip.h isolation.h lineread.h log.h migrate.h ndp.h \
 	netlink.h packet.h parse.h passt.h pasta.h pcap.h pif.h repair.h \
 	serialise.h siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h \
-	tcp_splice.h tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h \
-	vhost_user.h virtio.h vu_common.h
+	tcp_splice.h tcp_vu.h threading.h udp.h udp_flow.h udp_internal.h \
+	udp_vu.h util.h vhost_user.h virtio.h vu_common.h
 PASST_REPAIR_HEADERS = linux_dep.h
 PESTO_HEADERS = bitmap.h common.h fwd_rule.h inany.h ip.h log.h parse.h \
 	pesto.h serialise.h
diff --git a/isolation.c b/isolation.c
index 94cbe7f843fe..a8055b6e21ee 100644
--- a/isolation.c
+++ b/isolation.c
@@ -394,8 +394,12 @@ int isolate_prefork(const struct ctx *c)
 	/* If we run in foreground, we have no chance to actually move to a new
 	 * PID namespace. For passt, use CLONE_NEWPID anyway, in case somebody
 	 * ever gets around seccomp profiles -- there's no harm in passing it.
+	 * We cannot use CLONE_NEWPID with vhost-user as we need to use
+	 * pthread_create() for the multithreading. pthread_create() calls
+	 * clone3(), with CLONE_THREAD that is not compatible with CLONE_NEWPID.
+	 * (see EINVAL in clone(2))
 	 */
-	if (!c->foreground || c->mode != MODE_PASTA)
+	if (!c->foreground || c->mode == MODE_PASST)
 		flags |= CLONE_NEWPID;
 
 	if (unshare(flags)) {
diff --git a/passt.c b/passt.c
index 25e1212b9452..8a06838c5ed8 100644
--- a/passt.c
+++ b/passt.c
@@ -54,11 +54,7 @@
 #include "repair.h"
 #include "netlink.h"
 #include "epoll_ctl.h"
-
-#define NUM_EPOLL_EVENTS	8
-
-#define TIMER_INTERVAL_		MIN(TCP_TIMER_INTERVAL, FWD_PORT_SCAN_INTERVAL)
-#define TIMER_INTERVAL		MIN(TIMER_INTERVAL_, FLOW_TIMER_INTERVAL)
+#include "threading.h"
 
 char pkt_buf[PKT_BUF_BYTES]	__attribute__ ((aligned(PAGE_SIZE)));
 
@@ -380,6 +376,7 @@ int main(int argc, char **argv)
 
 	madvise(pkt_buf, sizeof(pkt_buf), MADV_HUGEPAGE);
 
+	threading_init();
 	c->epollfd = epoll_create1(EPOLL_CLOEXEC);
 	if (c->epollfd == -1)
 		die_perror("Failed to create epoll file descriptor");
diff --git a/passt.h b/passt.h
index ec936c3d3f32..00ef5a8f85e5 100644
--- a/passt.h
+++ b/passt.h
@@ -9,6 +9,11 @@
 #define UNIX_SOCK_MAX		100
 #define UNIX_SOCK_PATH		"/tmp/passt_%i.socket"
 
+#define NUM_EPOLL_EVENTS		8
+
+#define TIMER_INTERVAL_		MIN(TCP_TIMER_INTERVAL, FWD_PORT_SCAN_INTERVAL)
+#define TIMER_INTERVAL		MIN(TIMER_INTERVAL_, FLOW_TIMER_INTERVAL)
+
 union epoll_ref;
 
 #include <stdbool.h>
diff --git a/threading.c b/threading.c
new file mode 100644
index 000000000000..fd5b30ed7ff6
--- /dev/null
+++ b/threading.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* PASST - Plug A Simple Socket Transport
+ *  for qemu/UNIX domain socket mode
+ *
+ * PASTA - Pack A Subtle Tap Abstraction
+ *  for network namespace/tap device mode
+ *
+ * threading.c - Basic threading infrastructure
+ *
+ * Copyright Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdatomic.h>
+
+#include "util.h"
+#include "passt.h"
+#include "threading.h"
+
+/**
+ * struct threading_context - Per-thread execution context
+ * @epollfd:	Epoll file descriptor for this thread
+ * @pthread:	Pthread identifier
+ * @worker:	Worker function to execute in this thread
+ * @opaque:	Opaque data passed to worker function
+ * @quit:	Flag to signal thread to exit
+ */
+struct threading_context {
+	pthread_t pthread;
+	void (*worker)(void *, int, struct epoll_event *);
+	void *opaque;
+	int epollfd;
+	_Atomic bool quit;
+};
+
+/*
+ *  index 0 is reserved for main process
+ *  new threads start at index 1
+ */
+struct threading_context threads[MAX_NUM_THREADS + 1];
+
+/**
+ * threading_init() - Initialize threading infrastructure
+ */
+void threading_init(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(threads); i++) {
+		threads[i].epollfd = epoll_create1(EPOLL_CLOEXEC);
+		if (threads[i].epollfd == -1)
+			die_perror("Failed to create epoll for threads #%d",
+				   i);
+	}
+}
+
+/**
+ * threading_worker_set() - Set worker function for a thread
+ * @threadid:		Thread index
+ * @worker:		Worker function to assign to the thread
+ * @is_valid:	Validation function to select a thread
+ * @opaque:		Opaque data to pass to worker function
+ *
+ * Return: 0 on success, -1 if thread index is invalid
+ */
+/* cppcheck-suppress unusedFunction */
+int threading_worker_set(unsigned int threadid,
+			 void (*worker)(void *, int, struct epoll_event *),
+			 void *opaque)
+{
+	struct threading_context *tc;
+
+	if (threadid >= ARRAY_SIZE(threads))
+		return -1;
+
+	tc = &threads[threadid];
+
+	tc->worker = worker;
+	tc->opaque = opaque;
+
+	return 0;
+}
+
+/**
+ * threading_epollfd() - Get epoll file descriptor for a thread
+ * @threadid:	Thread index
+ *
+ * Return: epoll file descriptor for the specified thread, -1 if index invalid
+ */
+/* cppcheck-suppress unusedFunction */
+int threading_epollfd(unsigned int threadid)
+{
+	if (threadid >= ARRAY_SIZE(threads))
+		return -1;
+
+	return threads[threadid].epollfd;
+}
+
+/**
+ * threading_worker() - Main worker thread function
+ * @opaque:	Pointer to threading_context for this thread
+ *
+ * Return: NULL on thread exit
+ *
+ * #syscalls poll write futex
+ */
+static void *threading_worker(void *opaque)
+{
+	struct threading_context *tc = opaque;
+
+	while (!tc->quit) {
+		struct epoll_event events[NUM_EPOLL_EVENTS];
+		int nfds;
+
+		/* NOLINTBEGIN(bugprone-branch-clone): intervals can be the same */
+		/* cppcheck-suppress [duplicateValueTernary, unmatchedSuppression] */
+		nfds = epoll_wait(tc->epollfd, events, NUM_EPOLL_EVENTS,
+				  TIMER_INTERVAL);
+		/* NOLINTEND(bugprone-branch-clone) */
+		if (nfds == -1 && errno != EINTR)
+			die_perror("epoll_wait() failed in thread loop");
+
+		tc->worker(tc->opaque, nfds, events);
+	}
+
+	return NULL;
+}
+
+/**
+ * threading_start_thread() - Start a worker thread
+ * @threadid:	Thread index to start
+ *
+ * #syscalls rt_sigaction rt_sigprocmask mprotect getrandom brk clone3 rseq set_robust_list clock_nanosleep
+ */
+/* cppcheck-suppress unusedFunction */
+void threading_start_thread(unsigned int threadid)
+{
+	struct threading_context *tc;
+	int ret;
+
+	if (threadid >= ARRAY_SIZE(threads))
+		die_perror("Invalid thread index %u, max is %u\n",
+			   threadid, MAX_NUM_THREADS);
+
+	tc = &threads[threadid];
+
+	if (!tc->worker)
+		die_perror("No worker for thread #%u\n", threadid);
+
+	if (tc->pthread)
+		return;
+
+	/* thread #0 is the main process */
+	if (threadid == THREADING_ID_DEFAULT) {
+		tc->pthread = -1; /* Mark thread is active */
+		tc->quit = false;
+		/* Thread #0 is main process, call is blocking */
+		threading_worker(tc);	/* no return */
+	}
+
+	tc->quit = false;
+	ret = pthread_create(&tc->pthread, NULL, &threading_worker, tc);
+	if (ret != 0)
+		die_perror("pthread_create() failed: thread index %u ret %d",
+			   threadid, ret);
+}
+
+/**
+ * threading_stop_thread() - Stop and join a worker thread
+ * @threadid:	Thread index to stop
+ */
+/* cppcheck-suppress unusedFunction */
+void threading_stop_thread(unsigned int threadid)
+{
+	if (threadid >= ARRAY_SIZE(threads))
+		die_perror("Invalid thread index %u, max is %u\n",
+		           threadid, ARRAY_SIZE(threads));
+
+	if (threadid == THREADING_ID_DEFAULT) {
+		/* Thread #0 is main process, cannot be stopped */
+		return;
+	}
+
+	threads[threadid].quit = true;
+	pthread_join(threads[threadid].pthread, NULL);
+	threads[threadid].pthread = 0;
+}
+
+/**
+ * threading_is_active() - Check if a worker thread is active
+ * @threadid:	Thread index to check
+ *
+ * Return: true if the thread at the given index has been created and is active,
+ *         false otherwise
+ */
+/* cppcheck-suppress unusedFunction */
+bool threading_is_active(unsigned int threadid)
+{
+	if (threadid >= ARRAY_SIZE(threads))
+		die_perror("Invalid thread index %u, max is %u\n",
+		           threadid, ARRAY_SIZE(threads));
+
+	return !!threads[threadid].pthread;
+}
diff --git a/threading.h b/threading.h
new file mode 100644
index 000000000000..88f397a80441
--- /dev/null
+++ b/threading.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright (c) 2025 Red Hat
+ * Author: Laurent Vivier <lvivier@redhat.com>
+ */
+
+#ifndef THREADING_H
+#define THREADING_H
+
+#define MAX_NUM_THREADS 32
+
+#define THREADING_ID_DEFAULT	0
+
+void threading_init(void);
+int threading_worker_set(unsigned int threadid,
+			 void (*worker)(void *, int, struct epoll_event *),
+			 void *opaque);
+int threading_epollfd(unsigned int threadid);
+void threading_start_thread(unsigned int threadid);
+void threading_stop_thread(unsigned int threadid);
+bool threading_is_active(unsigned int threadid);
+
+#endif /* THREADING_H */
-- 
2.54.0


  parent reply	other threads:[~2026-07-31 16:46 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31 16:46 [PATCH 0/7] multithreading: Add worker threads for queue pair processing Laurent Vivier
2026-07-31 16:46 ` [PATCH 1/7] vhost_user: Reset vq enable flag in vu_cleanup() Laurent Vivier
2026-07-31 16:46 ` Laurent Vivier [this message]
2026-07-31 16:46 ` [PATCH 3/7] passt: Integrate main event loop with threading infrastructure Laurent Vivier
2026-07-31 16:46 ` [PATCH 4/7] flow: Delegate epoll file descriptor management to threading subsystem Laurent Vivier
2026-07-31 16:46 ` [PATCH 5/7] ctx: Remove epollfd from context structure Laurent Vivier
2026-07-31 16:46 ` [PATCH 6/7] vhost-user: Add per-qpair worker threads Laurent Vivier
2026-07-31 16:46 ` [PATCH 7/7] virtio: Prevent crash on virtqueue exhaustion with temporary workaround Laurent Vivier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260731164628.3556997-3-lvivier@redhat.com \
    --to=lvivier@redhat.com \
    --cc=passt-dev@passt.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).