[PATCH 18/19] libsupport: add background thread manager

Darrick J. Wong
2025-11-06T22:35:52Z

Add some simple code to manage a background thread that wakes up
periodically. This will be needed for MMP in fuse2fs, among other
things.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/support/bthread.h | 27 ++++++
lib/support/Makefile.in | 6 +
lib/support/bthread.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 233 insertions(+), 1 deletion(-)
create mode 100644 lib/support/bthread.h
create mode 100644 lib/support/bthread.c

diff --git a/lib/support/bthread.h b/lib/support/bthread.h
new file mode 100644
index 00000000000000..cb29a655b815e5
--- /dev/null
+++ b/lib/support/bthread.h
@@ -0,0 +1,27 @@
+/*
+ * bthread.h - Background thread manager
+ *
+ * Copyright (C) 2025 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#ifndef __BTHREAD_H__
+#define __BTHREAD_H__
+
+typedef void (*bthread_fn_t)(void *data);
+struct bthread;
+
+int bthread_create(const char *name, bthread_fn_t fn, void *data,
+ unsigned int period, struct bthread **btp);
+void bthread_destroy(struct bthread **btp);
+
+int bthread_start(struct bthread *bt);
+void bthread_stop(struct bthread *bt);
+
+int bthread_cancel(struct bthread *bt);
+int bthread_cancelled(struct bthread *bt);
+
+#endif /* __BTHREAD_H__ */
diff --git a/lib/support/Makefile.in b/lib/support/Makefile.in
index 3f26cd30172f51..6383816fd99cd4 100644
--- a/lib/support/Makefile.in
+++ b/lib/support/Makefile.in
@@ -13,7 +13,8 @@ MKDIR_P = @MKDIR_P@

all::

-OBJS= cstring.o \
+OBJS= bthread.o \
+ cstring.o \
mkquota.o \
plausible.o \
profile.o \
@@ -28,6 +29,7 @@ OBJS= cstring.o \
devname.o

SRCS= $(srcdir)/argv_parse.c \
+ $(srcdir)/bthread.c \
$(srcdir)/cstring.c \
$(srcdir)/mkquota.c \
$(srcdir)/parse_qtype.c \
@@ -108,6 +110,8 @@ $(OBJS):
#
argv_parse.o: $(srcdir)/argv_parse.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/argv_parse.h
+bthread.o: $(srcdir)/bthread.c $(top_builddir)/lib/config.h \
+ $(srcdir)/bthread.h
cstring.o: $(srcdir)/cstring.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/cstring.h
mkquota.o: $(srcdir)/mkquota.c $(top_builddir)/lib/config.h \
diff --git a/lib/support/bthread.c b/lib/support/bthread.c
new file mode 100644
index 00000000000000..f59fde976920f9
--- /dev/null
+++ b/lib/support/bthread.c
@@ -0,0 +1,201 @@
+/*
+ * bthread.c - Background thread manager
+ *
+ * Copyright (C) 2025 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "support/bthread.h"
+
+enum bthread_state {
+ /* waiting to be put in the running state */
+ BT_WAITING,
+ /* running */
+ BT_RUNNING,
+ /* cancelled */
+ BT_CANCELLED,
+};
+
+struct bthread {
+ enum bthread_state state;
+ pthread_t thread;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ bthread_fn_t fn;
+ void *data;
+ unsigned int period; /* seconds */
+ int can_join:1;
+};
+
+/* Wait for a signal or for the periodic interval */
+static inline int bthread_wait(struct bthread *bt)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += bt->period;
+ return pthread_cond_timedwait(&bt->cond, &bt->lock, &ts);
+}
+
+static void *bthread_run(void *arg)
+{
+ struct bthread *bt = arg;
+ int ret;
+
+ while (1) {
+ pthread_mutex_lock(&bt->lock);
+ ret = bthread_wait(bt);
+ switch (bt->state) {
+ case BT_WAITING:
+ /* waiting to be runnable, go around again */
+ pthread_mutex_unlock(&bt->lock);
+ break;
+ case BT_RUNNING:
+ /* running; call our function if we timed out */
+ pthread_mutex_unlock(&bt->lock);
+ if (ret == ETIMEDOUT)
+ bt->fn(bt->data);
+ break;
+ case BT_CANCELLED:
+ /* exit if we're cancelled */
+ pthread_mutex_unlock(&bt->lock);
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+/* Create background thread and have it wait to be started */
+int bthread_create(const char *name, bthread_fn_t fn, void *data,
+ unsigned int period, struct bthread **btp)
+{
+ struct bthread *bt;
+ int error;
+
+ if (!period)
+ return EINVAL;
+
+ bt = calloc(1, sizeof(struct bthread));
+ if (!bt)
+ return ENOMEM;
+ bt->state = BT_WAITING;
+ bt->fn = fn;
+ bt->data = data;
+ bt->period = period;
+ bt->can_join = 1;
+
+ bt->lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
+ bt->cond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
+
+ error = pthread_create(&bt->thread, NULL, bthread_run, bt);
+ if (error)
+ goto out_cond;
+
+ pthread_setname_np(bt->thread, name);
+
+ *btp = bt;
+ return 0;
+
+out_cond:
+ pthread_cond_destroy(&bt->cond);
+ pthread_mutex_destroy(&bt->lock);
+ free(bt);
+ return error;
+}
+
+/* Stop the thread (if running) and tear everything down */
+void bthread_destroy(struct bthread **btp)
+{
+ struct bthread *bt = *btp;
+
+ if (bt) {
+ bthread_stop(bt);
+
+ pthread_cond_destroy(&bt->cond);
+ pthread_mutex_destroy(&bt->lock);
+
+ free(bt);
+ }
+
+ *btp = NULL;
+}
+
+/* Start background thread, put it in waiting state */
+int bthread_start(struct bthread *bt)
+{
+ int err;
+
+ pthread_mutex_lock(&bt->lock);
+ bt->state = BT_RUNNING;
+ err = pthread_cond_signal(&bt->cond);
+ pthread_mutex_unlock(&bt->lock);
+
+ return err;
+}
+
+/* Has this thread been cancelled? */
+int bthread_cancelled(struct bthread *bt)
+{
+ int ret;
+
+ pthread_mutex_lock(&bt->lock);
+ ret = bt->state == BT_CANCELLED;
+ pthread_mutex_unlock(&bt->lock);
+
+ return ret;
+}
+
+/* Ask the thread to cancel itself, but don't wait */
+int bthread_cancel(struct bthread *bt)
+{
+ int err = 0;
+
+ pthread_mutex_lock(&bt->lock);
+ switch (bt->state) {
+ case BT_CANCELLED:
+ break;
+ case BT_WAITING:
+ case BT_RUNNING:
+ bt->state = BT_CANCELLED;
+ err = pthread_cond_signal(&bt->cond);
+ break;
+ }
+ pthread_mutex_unlock(&bt->lock);
+
+ return err;
+}
+
+/* Ask the thread to cancel itself and wait for it */
+void bthread_stop(struct bthread *bt)
+{
+ int need_join = 0;
+
+ pthread_mutex_lock(&bt->lock);
+ switch (bt->state) {
+ case BT_CANCELLED:
+ need_join = bt->can_join;
+ break;
+ case BT_WAITING:
+ case BT_RUNNING:
+ bt->state = BT_CANCELLED;
+ need_join = 1;
+ pthread_cond_signal(&bt->cond);
+ break;
+ }
+ if (need_join)
+ bt->can_join = 0;
+ pthread_mutex_unlock(&bt->lock);
+
+ if (need_join)
+ pthread_join(bt->thread, NULL);
+}