bcachefs: enumerated_ref.c
authorKent Overstreet <kent.overstreet@linux.dev>
Fri, 18 Apr 2025 18:56:09 +0000 (14:56 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Thu, 22 May 2025 00:14:27 +0000 (20:14 -0400)
Factor out the debug code for rw filesystem refs into a small library.

In release mode an enumerated ref is a normal percpu refcount, but in
debug mode all enumerated users of the ref get their own atomic_long_t
ref - making it much easier to chase down refcount usage bugs for when a
refcount has many users.

For debugging, we have enumerated_ref_to_text(), which prints the
current value of each different user.

Additionally, in debug mode enumerated_ref_stop() has a 10 second
timeout, after which it will dump outstanding refcounts.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/Makefile
fs/bcachefs/enumerated_ref.c [new file with mode: 0644]
fs/bcachefs/enumerated_ref.h [new file with mode: 0644]
fs/bcachefs/enumerated_ref_types.h [new file with mode: 0644]

index 9af65079374fa94af75a19d47cd89db4060f9e8a..d2b8aec6ed8c4d85fa1c5b9c47ef12f3841d5ed5 100644 (file)
@@ -35,6 +35,7 @@ bcachefs-y            :=      \
        disk_accounting.o       \
        disk_groups.o           \
        ec.o                    \
+       enumerated_ref.o        \
        errcode.o               \
        error.o                 \
        extents.o               \
diff --git a/fs/bcachefs/enumerated_ref.c b/fs/bcachefs/enumerated_ref.c
new file mode 100644 (file)
index 0000000..56ab430
--- /dev/null
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "bcachefs.h"
+#include "enumerated_ref.h"
+#include "util.h"
+
+#include <linux/completion.h>
+
+#ifdef ENUMERATED_REF_DEBUG
+void enumerated_ref_get(struct enumerated_ref *ref, unsigned idx)
+{
+       BUG_ON(idx >= ref->nr);
+       atomic_long_inc(&ref->refs[idx]);
+}
+
+bool __enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx)
+{
+       BUG_ON(idx >= ref->nr);
+       return atomic_long_inc_not_zero(&ref->refs[idx]);
+}
+
+bool enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx)
+{
+       BUG_ON(idx >= ref->nr);
+       return !ref->dying &&
+               atomic_long_inc_not_zero(&ref->refs[idx]);
+}
+
+void enumerated_ref_put(struct enumerated_ref *ref, unsigned idx)
+{
+       BUG_ON(idx >= ref->nr);
+       long v = atomic_long_dec_return(&ref->refs[idx]);
+
+       BUG_ON(v < 0);
+       if (v)
+               return;
+
+       for (unsigned i = 0; i < ref->nr; i++)
+               if (atomic_long_read(&ref->refs[i]))
+                       return;
+
+       if (ref->stop_fn)
+               ref->stop_fn(ref);
+       complete(&ref->stop_complete);
+}
+#endif
+
+#ifndef ENUMERATED_REF_DEBUG
+static void enumerated_ref_kill_cb(struct percpu_ref *percpu_ref)
+{
+       struct enumerated_ref *ref =
+               container_of(percpu_ref, struct enumerated_ref, ref);
+
+       if (ref->stop_fn)
+               ref->stop_fn(ref);
+       complete(&ref->stop_complete);
+}
+#endif
+
+void enumerated_ref_stop_async(struct enumerated_ref *ref)
+{
+       reinit_completion(&ref->stop_complete);
+
+#ifndef ENUMERATED_REF_DEBUG
+       percpu_ref_kill(&ref->ref);
+#else
+       ref->dying = true;
+       for (unsigned i = 0; i < ref->nr; i++)
+               enumerated_ref_put(ref, i);
+#endif
+}
+
+void enumerated_ref_stop(struct enumerated_ref *ref,
+                        const char * const names[])
+{
+       enumerated_ref_stop_async(ref);
+       while (!wait_for_completion_timeout(&ref->stop_complete, HZ * 10)) {
+               struct printbuf buf = PRINTBUF;
+
+               prt_str(&buf, "Waited for 10 seconds to shutdown enumerated ref\n");
+               prt_str(&buf, "Outstanding refs:\n");
+               enumerated_ref_to_text(&buf, ref, names);
+               printk(KERN_ERR "%s", buf.buf);
+               printbuf_exit(&buf);
+       }
+}
+
+void enumerated_ref_start(struct enumerated_ref *ref)
+{
+#ifndef ENUMERATED_REF_DEBUG
+       percpu_ref_reinit(&ref->ref);
+#else
+       ref->dying = false;
+       for (unsigned i = 0; i < ref->nr; i++) {
+               BUG_ON(atomic_long_read(&ref->refs[i]));
+               atomic_long_inc(&ref->refs[i]);
+       }
+#endif
+}
+
+void enumerated_ref_exit(struct enumerated_ref *ref)
+{
+#ifndef ENUMERATED_REF_DEBUG
+       percpu_ref_exit(&ref->ref);
+#else
+       kfree(ref->refs);
+       ref->refs = NULL;
+       ref->nr = 0;
+#endif
+}
+
+int enumerated_ref_init(struct enumerated_ref *ref, unsigned nr,
+                       void (*stop_fn)(struct enumerated_ref *))
+{
+       init_completion(&ref->stop_complete);
+       ref->stop_fn = stop_fn;
+
+#ifndef ENUMERATED_REF_DEBUG
+       return percpu_ref_init(&ref->ref, enumerated_ref_kill_cb,
+                           PERCPU_REF_INIT_DEAD, GFP_KERNEL);
+#else
+       ref->refs = kzalloc(sizeof(ref->refs[0]) * nr, GFP_KERNEL);
+       if (!ref->refs)
+               return -ENOMEM;
+
+       ref->nr = nr;
+       return 0;
+#endif
+}
+
+void enumerated_ref_to_text(struct printbuf *out,
+                           struct enumerated_ref *ref,
+                           const char * const names[])
+{
+#ifdef ENUMERATED_REF_DEBUG
+       bch2_printbuf_tabstop_push(out, 32);
+
+       for (unsigned i = 0; i < ref->nr; i++)
+               prt_printf(out, "%s\t%li\n", names[i],
+                          atomic_long_read(&ref->refs[i]));
+#else
+       prt_str(out, "(not in debug mode)\n");
+#endif
+}
diff --git a/fs/bcachefs/enumerated_ref.h b/fs/bcachefs/enumerated_ref.h
new file mode 100644 (file)
index 0000000..ec01cf5
--- /dev/null
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BCACHEFS_ENUMERATED_REF_H
+#define _BCACHEFS_ENUMERATED_REF_H
+
+#include "enumerated_ref_types.h"
+
+/*
+ * A refcount where the users are enumerated: in debug mode, we create sepate
+ * refcounts for each user, to make leaks and refcount errors easy to track
+ * down:
+ */
+
+#ifdef ENUMERATED_REF_DEBUG
+void enumerated_ref_get(struct enumerated_ref *, unsigned);
+bool __enumerated_ref_tryget(struct enumerated_ref *, unsigned);
+bool enumerated_ref_tryget(struct enumerated_ref *, unsigned);
+void enumerated_ref_put(struct enumerated_ref *, unsigned);
+#else
+
+static inline void enumerated_ref_get(struct enumerated_ref *ref, unsigned idx)
+{
+       percpu_ref_get(&ref->ref);
+}
+
+static inline bool __enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx)
+{
+       return percpu_ref_tryget(&ref->ref);
+}
+
+static inline bool enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx)
+{
+       return percpu_ref_tryget_live(&ref->ref);
+}
+
+static inline void enumerated_ref_put(struct enumerated_ref *ref, unsigned idx)
+{
+       percpu_ref_put(&ref->ref);
+}
+#endif
+
+static inline bool enumerated_ref_is_zero(struct enumerated_ref *ref)
+{
+#ifndef ENUMERATED_REF_DEBUG
+       return percpu_ref_is_zero(&ref->ref);
+#else
+       for (unsigned i = 0; i < ref->nr; i++)
+               if (atomic_long_read(&ref->refs[i]))
+                       return false;
+       return true;
+#endif
+}
+
+void enumerated_ref_stop_async(struct enumerated_ref *);
+void enumerated_ref_stop(struct enumerated_ref *, const char * const[]);
+void enumerated_ref_start(struct enumerated_ref *);
+
+void enumerated_ref_exit(struct enumerated_ref *);
+int enumerated_ref_init(struct enumerated_ref *, unsigned,
+                       void (*stop_fn)(struct enumerated_ref *));
+
+struct printbuf;
+void enumerated_ref_to_text(struct printbuf *,
+                           struct enumerated_ref *,
+                           const char * const[]);
+
+#endif /* _BCACHEFS_ENUMERATED_REF_H */
diff --git a/fs/bcachefs/enumerated_ref_types.h b/fs/bcachefs/enumerated_ref_types.h
new file mode 100644 (file)
index 0000000..0e6076f
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BCACHEFS_ENUMERATED_REF_TYPES_H
+#define _BCACHEFS_ENUMERATED_REF_TYPES_H
+
+#include <linux/percpu-refcount.h>
+
+struct enumerated_ref {
+#ifdef ENUMERATED_REF_DEBUG
+       unsigned                nr;
+       bool                    dying;
+       atomic_long_t           *refs;
+#else
+       struct percpu_ref       ref;
+#endif
+       void                    (*stop_fn)(struct enumerated_ref *);
+       struct completion       stop_complete;
+};
+
+#endif /* _BCACHEFS_ENUMERATED_REF_TYPES_H */