fscache: Implement cache registration
authorDavid Howells <dhowells@redhat.com>
Wed, 20 Oct 2021 14:00:26 +0000 (15:00 +0100)
committerDavid Howells <dhowells@redhat.com>
Fri, 7 Jan 2022 09:22:19 +0000 (09:22 +0000)
Implement a register of caches and provide functions to manage it.

Two functions are provided for the cache backend to use:

 (1) Acquire a cache cookie:

struct fscache_cache *fscache_acquire_cache(const char *name)

     This gets the cache cookie for a cache of the specified name and moves
     it to the preparation state.  If a nameless cache cookie exists, that
     will be given this name and used.

 (2) Relinquish a cache cookie:

void fscache_relinquish_cache(struct fscache_cache *cache);

     This relinquishes a cache cookie, cleans it and makes it available if
     it's still referenced by a network filesystem.

Note that network filesystems don't deal with cache cookies directly, but
rather go straight to the volume registration.

Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
Link: https://lore.kernel.org/r/163819587157.215744.13523139317322503286.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/163906889665.143852.10378009165231294456.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/163967085081.1823006.2218944206363626210.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/164021494847.640689.10109692261640524343.stgit@warthog.procyon.org.uk/
fs/fscache/Makefile
fs/fscache/cache.c [new file with mode: 0644]
fs/fscache/internal.h
fs/fscache/proc.c
include/linux/fscache-cache.h
include/trace/events/fscache.h

index f9722de..d9fc22c 100644 (file)
@@ -4,6 +4,7 @@
 #
 
 fscache-y := \
+       cache.o \
        main.o
 
 fscache-$(CONFIG_PROC_FS) += proc.o
diff --git a/fs/fscache/cache.c b/fs/fscache/cache.c
new file mode 100644 (file)
index 0000000..8db77bb
--- /dev/null
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* FS-Cache cache handling
+ *
+ * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#define FSCACHE_DEBUG_LEVEL CACHE
+#include <linux/export.h>
+#include <linux/slab.h>
+#include "internal.h"
+
+static LIST_HEAD(fscache_caches);
+DECLARE_RWSEM(fscache_addremove_sem);
+EXPORT_SYMBOL(fscache_addremove_sem);
+
+static atomic_t fscache_cache_debug_id;
+
+/*
+ * Allocate a cache cookie.
+ */
+static struct fscache_cache *fscache_alloc_cache(const char *name)
+{
+       struct fscache_cache *cache;
+
+       cache = kzalloc(sizeof(*cache), GFP_KERNEL);
+       if (cache) {
+               if (name) {
+                       cache->name = kstrdup(name, GFP_KERNEL);
+                       if (!cache->name) {
+                               kfree(cache);
+                               return NULL;
+                       }
+               }
+               refcount_set(&cache->ref, 1);
+               INIT_LIST_HEAD(&cache->cache_link);
+               cache->debug_id = atomic_inc_return(&fscache_cache_debug_id);
+       }
+       return cache;
+}
+
+static bool fscache_get_cache_maybe(struct fscache_cache *cache,
+                                   enum fscache_cache_trace where)
+{
+       bool success;
+       int ref;
+
+       success = __refcount_inc_not_zero(&cache->ref, &ref);
+       if (success)
+               trace_fscache_cache(cache->debug_id, ref + 1, where);
+       return success;
+}
+
+/*
+ * Look up a cache cookie.
+ */
+struct fscache_cache *fscache_lookup_cache(const char *name, bool is_cache)
+{
+       struct fscache_cache *candidate, *cache, *unnamed = NULL;
+
+       /* firstly check for the existence of the cache under read lock */
+       down_read(&fscache_addremove_sem);
+
+       list_for_each_entry(cache, &fscache_caches, cache_link) {
+               if (cache->name && name && strcmp(cache->name, name) == 0 &&
+                   fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
+                       goto got_cache_r;
+               if (!cache->name && !name &&
+                   fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
+                       goto got_cache_r;
+       }
+
+       if (!name) {
+               list_for_each_entry(cache, &fscache_caches, cache_link) {
+                       if (cache->name &&
+                           fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
+                               goto got_cache_r;
+               }
+       }
+
+       up_read(&fscache_addremove_sem);
+
+       /* the cache does not exist - create a candidate */
+       candidate = fscache_alloc_cache(name);
+       if (!candidate)
+               return ERR_PTR(-ENOMEM);
+
+       /* write lock, search again and add if still not present */
+       down_write(&fscache_addremove_sem);
+
+       list_for_each_entry(cache, &fscache_caches, cache_link) {
+               if (cache->name && name && strcmp(cache->name, name) == 0 &&
+                   fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
+                       goto got_cache_w;
+               if (!cache->name) {
+                       unnamed = cache;
+                       if (!name &&
+                           fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
+                               goto got_cache_w;
+               }
+       }
+
+       if (unnamed && is_cache &&
+           fscache_get_cache_maybe(unnamed, fscache_cache_get_acquire))
+               goto use_unnamed_cache;
+
+       if (!name) {
+               list_for_each_entry(cache, &fscache_caches, cache_link) {
+                       if (cache->name &&
+                           fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
+                               goto got_cache_w;
+               }
+       }
+
+       list_add_tail(&candidate->cache_link, &fscache_caches);
+       trace_fscache_cache(candidate->debug_id,
+                           refcount_read(&candidate->ref),
+                           fscache_cache_new_acquire);
+       up_write(&fscache_addremove_sem);
+       return candidate;
+
+got_cache_r:
+       up_read(&fscache_addremove_sem);
+       return cache;
+use_unnamed_cache:
+       cache = unnamed;
+       cache->name = candidate->name;
+       candidate->name = NULL;
+got_cache_w:
+       up_write(&fscache_addremove_sem);
+       kfree(candidate->name);
+       kfree(candidate);
+       return cache;
+}
+
+/**
+ * fscache_acquire_cache - Acquire a cache-level cookie.
+ * @name: The name of the cache.
+ *
+ * Get a cookie to represent an actual cache.  If a name is given and there is
+ * a nameless cache record available, this will acquire that and set its name,
+ * directing all the volumes using it to this cache.
+ *
+ * The cache will be switched over to the preparing state if not currently in
+ * use, otherwise -EBUSY will be returned.
+ */
+struct fscache_cache *fscache_acquire_cache(const char *name)
+{
+       struct fscache_cache *cache;
+
+       ASSERT(name);
+       cache = fscache_lookup_cache(name, true);
+       if (IS_ERR(cache))
+               return cache;
+
+       if (!fscache_set_cache_state_maybe(cache,
+                                          FSCACHE_CACHE_IS_NOT_PRESENT,
+                                          FSCACHE_CACHE_IS_PREPARING)) {
+               pr_warn("Cache tag %s in use\n", name);
+               fscache_put_cache(cache, fscache_cache_put_cache);
+               return ERR_PTR(-EBUSY);
+       }
+
+       return cache;
+}
+EXPORT_SYMBOL(fscache_acquire_cache);
+
+/**
+ * fscache_put_cache - Release a cache-level cookie.
+ * @cache: The cache cookie to be released
+ * @where: An indication of where the release happened
+ *
+ * Release the caller's reference on a cache-level cookie.  The @where
+ * indication should give information about the circumstances in which the call
+ * occurs and will be logged through a tracepoint.
+ */
+void fscache_put_cache(struct fscache_cache *cache,
+                      enum fscache_cache_trace where)
+{
+       unsigned int debug_id = cache->debug_id;
+       bool zero;
+       int ref;
+
+       if (IS_ERR_OR_NULL(cache))
+               return;
+
+       zero = __refcount_dec_and_test(&cache->ref, &ref);
+       trace_fscache_cache(debug_id, ref - 1, where);
+
+       if (zero) {
+               down_write(&fscache_addremove_sem);
+               list_del_init(&cache->cache_link);
+               up_write(&fscache_addremove_sem);
+               kfree(cache->name);
+               kfree(cache);
+       }
+}
+
+/**
+ * fscache_relinquish_cache - Reset cache state and release cookie
+ * @cache: The cache cookie to be released
+ *
+ * Reset the state of a cache and release the caller's reference on a cache
+ * cookie.
+ */
+void fscache_relinquish_cache(struct fscache_cache *cache)
+{
+       enum fscache_cache_trace where =
+               (cache->state == FSCACHE_CACHE_IS_PREPARING) ?
+               fscache_cache_put_prep_failed :
+               fscache_cache_put_relinquish;
+
+       cache->cache_priv = NULL;
+       smp_store_release(&cache->state, FSCACHE_CACHE_IS_NOT_PRESENT);
+       fscache_put_cache(cache, where);
+}
+EXPORT_SYMBOL(fscache_relinquish_cache);
+
+#ifdef CONFIG_PROC_FS
+static const char fscache_cache_states[NR__FSCACHE_CACHE_STATE] = "-PAEW";
+
+/*
+ * Generate a list of caches in /proc/fs/fscache/caches
+ */
+static int fscache_caches_seq_show(struct seq_file *m, void *v)
+{
+       struct fscache_cache *cache;
+
+       if (v == &fscache_caches) {
+               seq_puts(m,
+                        "CACHE    REF   VOLS  OBJS  ACCES S NAME\n"
+                        "======== ===== ===== ===== ===== = ===============\n"
+                        );
+               return 0;
+       }
+
+       cache = list_entry(v, struct fscache_cache, cache_link);
+       seq_printf(m,
+                  "%08x %5d %5d %5d %5d %c %s\n",
+                  cache->debug_id,
+                  refcount_read(&cache->ref),
+                  atomic_read(&cache->n_volumes),
+                  atomic_read(&cache->object_count),
+                  atomic_read(&cache->n_accesses),
+                  fscache_cache_states[cache->state],
+                  cache->name ?: "-");
+       return 0;
+}
+
+static void *fscache_caches_seq_start(struct seq_file *m, loff_t *_pos)
+       __acquires(fscache_addremove_sem)
+{
+       down_read(&fscache_addremove_sem);
+       return seq_list_start_head(&fscache_caches, *_pos);
+}
+
+static void *fscache_caches_seq_next(struct seq_file *m, void *v, loff_t *_pos)
+{
+       return seq_list_next(v, &fscache_caches, _pos);
+}
+
+static void fscache_caches_seq_stop(struct seq_file *m, void *v)
+       __releases(fscache_addremove_sem)
+{
+       up_read(&fscache_addremove_sem);
+}
+
+const struct seq_operations fscache_caches_seq_ops = {
+       .start  = fscache_caches_seq_start,
+       .next   = fscache_caches_seq_next,
+       .stop   = fscache_caches_seq_stop,
+       .show   = fscache_caches_seq_show,
+};
+#endif /* CONFIG_PROC_FS */
index f345bdb..8fd39e7 100644 (file)
 #include <linux/seq_file.h>
 
 /*
+ * cache.c
+ */
+#ifdef CONFIG_PROC_FS
+extern const struct seq_operations fscache_caches_seq_ops;
+#endif
+struct fscache_cache *fscache_lookup_cache(const char *name, bool is_cache);
+void fscache_put_cache(struct fscache_cache *cache, enum fscache_cache_trace where);
+
+static inline enum fscache_cache_state fscache_cache_state(const struct fscache_cache *cache)
+{
+       return smp_load_acquire(&cache->state);
+}
+
+static inline bool fscache_cache_is_live(const struct fscache_cache *cache)
+{
+       return fscache_cache_state(cache) == FSCACHE_CACHE_IS_ACTIVE;
+}
+
+static inline void fscache_set_cache_state(struct fscache_cache *cache,
+                                          enum fscache_cache_state new_state)
+{
+       smp_store_release(&cache->state, new_state);
+
+}
+
+static inline bool fscache_set_cache_state_maybe(struct fscache_cache *cache,
+                                                enum fscache_cache_state old_state,
+                                                enum fscache_cache_state new_state)
+{
+       return try_cmpxchg_release(&cache->state, &old_state, new_state);
+}
+
+/*
  * main.c
  */
 extern unsigned fscache_debug;
index 4d866ac..93b9257 100644 (file)
@@ -19,6 +19,10 @@ int __init fscache_proc_init(void)
        if (!proc_mkdir("fs/fscache", NULL))
                goto error_dir;
 
+       if (!proc_create_seq("fs/fscache/caches", S_IFREG | 0444, NULL,
+                            &fscache_caches_seq_ops))
+               goto error;
+
 #ifdef CONFIG_FSCACHE_STATS
        if (!proc_create_single("fs/fscache/stats", S_IFREG | 0444, NULL,
                                fscache_stats_show))
index d6910a9..18cd5c9 100644 (file)
 
 #include <linux/fscache.h>
 
+enum fscache_cache_trace;
+enum fscache_access_trace;
+
+enum fscache_cache_state {
+       FSCACHE_CACHE_IS_NOT_PRESENT,   /* No cache is present for this name */
+       FSCACHE_CACHE_IS_PREPARING,     /* A cache is preparing to come live */
+       FSCACHE_CACHE_IS_ACTIVE,        /* Attached cache is active and can be used */
+       FSCACHE_CACHE_GOT_IOERROR,      /* Attached cache stopped on I/O error */
+       FSCACHE_CACHE_IS_WITHDRAWN,     /* Attached cache is being withdrawn */
+#define NR__FSCACHE_CACHE_STATE (FSCACHE_CACHE_IS_WITHDRAWN + 1)
+};
+
+/*
+ * Cache cookie.
+ */
+struct fscache_cache {
+       struct list_head        cache_link;     /* Link in cache list */
+       void                    *cache_priv;    /* Private cache data (or NULL) */
+       refcount_t              ref;
+       atomic_t                n_volumes;      /* Number of active volumes; */
+       atomic_t                n_accesses;     /* Number of in-progress accesses on the cache */
+       atomic_t                object_count;   /* no. of live objects in this cache */
+       unsigned int            debug_id;
+       enum fscache_cache_state state;
+       char                    *name;
+};
+
 extern struct workqueue_struct *fscache_wq;
 
+/*
+ * out-of-line cache backend functions
+ */
+extern struct rw_semaphore fscache_addremove_sem;
+extern struct fscache_cache *fscache_acquire_cache(const char *name);
+extern void fscache_relinquish_cache(struct fscache_cache *cache);
+
 #endif /* _LINUX_FSCACHE_CACHE_H */
index fe214c5..3b8e059 100644 (file)
 #ifndef __FSCACHE_DECLARE_TRACE_ENUMS_ONCE_ONLY
 #define __FSCACHE_DECLARE_TRACE_ENUMS_ONCE_ONLY
 
+enum fscache_cache_trace {
+       fscache_cache_collision,
+       fscache_cache_get_acquire,
+       fscache_cache_new_acquire,
+       fscache_cache_put_cache,
+       fscache_cache_put_prep_failed,
+       fscache_cache_put_relinquish,
+};
+
 #endif
 
 /*
  * Declare tracing information enums and their string mappings for display.
  */
+#define fscache_cache_traces                                           \
+       EM(fscache_cache_collision,             "*COLLIDE*")            \
+       EM(fscache_cache_get_acquire,           "GET acq  ")            \
+       EM(fscache_cache_new_acquire,           "NEW acq  ")            \
+       EM(fscache_cache_put_cache,             "PUT cache")            \
+       EM(fscache_cache_put_prep_failed,       "PUT pfail")            \
+       E_(fscache_cache_put_relinquish,        "PUT relnq")
 
 /*
  * Export enum symbols via userspace.
@@ -33,6 +49,8 @@
 #define EM(a, b) TRACE_DEFINE_ENUM(a);
 #define E_(a, b) TRACE_DEFINE_ENUM(a);
 
+fscache_cache_traces;
+
 /*
  * Now redefine the EM() and E_() macros to map the enums to the strings that
  * will be printed in the output.
 #define E_(a, b)       { a, b }
 
 
+TRACE_EVENT(fscache_cache,
+           TP_PROTO(unsigned int cache_debug_id,
+                    int usage,
+                    enum fscache_cache_trace where),
+
+           TP_ARGS(cache_debug_id, usage, where),
+
+           TP_STRUCT__entry(
+                   __field(unsigned int,               cache           )
+                   __field(int,                        usage           )
+                   __field(enum fscache_cache_trace,   where           )
+                            ),
+
+           TP_fast_assign(
+                   __entry->cache      = cache_debug_id;
+                   __entry->usage      = usage;
+                   __entry->where      = where;
+                          ),
+
+           TP_printk("C=%08x %s r=%d",
+                     __entry->cache,
+                     __print_symbolic(__entry->where, fscache_cache_traces),
+                     __entry->usage)
+           );
+
 #endif /* _TRACE_FSCACHE_H */
 
 /* This part must be outside protection */