[PATCH v2 18/53] fscache: Implement simple cookie state machine

From: David Howells
Date: Fri Oct 22 2021 - 15:03:44 EST


Implement a very simple cookie state machine to handle lookup, withdrawal,
relinquishment and, eventually, timed committing and invalidation.

Changes
=======
ver #2)
- Fix a number of oopses when the cache tries to access cookie->object,
but the cache withdrew the object due to lookup failure at just the
wrong time (fscache_lookup_cookie() should call
fscache_withdraw_cookie() rather than calling the cache directly).

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
cc: linux-cachefs@xxxxxxxxxx
Link: https://lore.kernel.org/r/CAB9dFdumxi0U_339S3PfC4TL83Srqn+qGz2AAbJ995NiLhbxnw@xxxxxxxxxxxxxx/
---

fs/fscache/cookie.c | 170 +++++++++++++++++++++++++++++++++++++++-
include/linux/fscache-cache.h | 10 ++
include/trace/events/fscache.h | 2
3 files changed, 179 insertions(+), 3 deletions(-)

diff --git a/fs/fscache/cookie.c b/fs/fscache/cookie.c
index 9b6ddbc01825..b7373ebcaf56 100644
--- a/fs/fscache/cookie.c
+++ b/fs/fscache/cookie.c
@@ -15,7 +15,9 @@

struct kmem_cache *fscache_cookie_jar;

+static void fscache_cookie_worker(struct work_struct *work);
static void fscache_drop_cookie(struct fscache_cookie *cookie);
+static void fscache_lookup_cookie(struct fscache_cookie *cookie);

#define fscache_cookie_hash_shift 15
static struct hlist_bl_head fscache_cookie_hash[1 << fscache_cookie_hash_shift];
@@ -57,13 +59,26 @@ static void fscache_free_cookie(struct fscache_cookie *cookie)
kmem_cache_free(fscache_cookie_jar, cookie);
}

+static void __fscache_queue_cookie(struct fscache_cookie *cookie)
+{
+ if (!queue_work(fscache_wq, &cookie->work))
+ fscache_put_cookie(cookie, fscache_cookie_put_over_queued);
+}
+
+static void fscache_queue_cookie(struct fscache_cookie *cookie,
+ enum fscache_cookie_trace where)
+{
+ fscache_get_cookie(cookie, where);
+ __fscache_queue_cookie(cookie);
+}
+
static void __fscache_end_cookie_access(struct fscache_cookie *cookie)
{
if (test_bit(FSCACHE_COOKIE_DO_RELINQUISH, &cookie->flags))
fscache_set_cookie_stage(cookie, FSCACHE_COOKIE_STAGE_RELINQUISHING);
else if (test_bit(FSCACHE_COOKIE_DO_WITHDRAW, &cookie->flags))
fscache_set_cookie_stage(cookie, FSCACHE_COOKIE_STAGE_WITHDRAWING);
- // PLACEHOLDER: Schedule cookie cleanup
+ fscache_queue_cookie(cookie, fscache_cookie_get_end_access);
}

/*
@@ -252,7 +267,7 @@ static struct fscache_cookie *fscache_alloc_cookie(
cookie->stage = FSCACHE_COOKIE_STAGE_QUIESCENT;
spin_lock_init(&cookie->lock);
INIT_LIST_HEAD(&cookie->commit_link);
- INIT_WORK(&cookie->work, NULL /* PLACEHOLDER */);
+ INIT_WORK(&cookie->work, fscache_cookie_worker);

write_lock(&fscache_cookies_lock);
list_add_tail(&cookie->proc_link, &fscache_cookies);
@@ -374,6 +389,136 @@ struct fscache_cookie *__fscache_acquire_cookie(
}
EXPORT_SYMBOL(__fscache_acquire_cookie);

+/*
+ * Prepare a cache object to be written to.
+ */
+static void fscache_prepare_to_write(struct fscache_cookie *cookie)
+{
+ cookie->volume->cache->ops->prepare_to_write(cookie);
+}
+
+/*
+ * Look up a cookie to the cache.
+ */
+static void fscache_lookup_cookie(struct fscache_cookie *cookie)
+{
+ bool changed_stage = false, need_withdraw = false, prep_write = false;
+
+ _enter("");
+
+ if (!cookie->volume->cache_priv) {
+ fscache_create_volume(cookie->volume, true);
+ if (!cookie->volume->cache_priv) {
+ fscache_set_cookie_stage(cookie, FSCACHE_COOKIE_STAGE_QUIESCENT);
+ goto out;
+ }
+ }
+
+ if (!cookie->volume->cache->ops->lookup_cookie(cookie)) {
+ if (cookie->stage != FSCACHE_COOKIE_STAGE_FAILED)
+ fscache_set_cookie_stage(cookie, FSCACHE_COOKIE_STAGE_QUIESCENT);
+ need_withdraw = true;
+ _leave(" [fail]");
+ goto out;
+ }
+
+ spin_lock(&cookie->lock);
+ if (cookie->stage != FSCACHE_COOKIE_STAGE_RELINQUISHING) {
+ prep_write = test_bit(FSCACHE_COOKIE_LOCAL_WRITE, &cookie->flags);
+ __fscache_set_cookie_stage(cookie, FSCACHE_COOKIE_STAGE_ACTIVE);
+ fscache_see_cookie(cookie, fscache_cookie_see_active);
+ changed_stage = true;
+ }
+ spin_unlock(&cookie->lock);
+ if (changed_stage)
+ wake_up_cookie_stage(cookie);
+ if (prep_write)
+ fscache_prepare_to_write(cookie);
+
+out:
+ fscache_end_cookie_access(cookie, fscache_access_lookup_cookie_end);
+ if (need_withdraw)
+ fscache_withdraw_cookie(cookie);
+ fscache_end_volume_access(cookie->volume, fscache_access_lookup_cookie_end);
+}
+
+/*
+ * Perform work upon the cookie, such as committing its cache state,
+ * relinquishing it or withdrawing the backing cache. We're protected from the
+ * cache going away under us as object withdrawal must come through this
+ * non-reentrant work item.
+ */
+static void __fscache_cookie_worker(struct fscache_cookie *cookie)
+{
+ _enter("c=%x", cookie->debug_id);
+
+again:
+ switch (READ_ONCE(cookie->stage)) {
+ case FSCACHE_COOKIE_STAGE_ACTIVE:
+ if (test_and_clear_bit(FSCACHE_COOKIE_DO_PREP_TO_WRITE, &cookie->flags))
+ fscache_prepare_to_write(cookie);
+ break;
+
+ case FSCACHE_COOKIE_STAGE_LOOKING_UP:
+ fscache_lookup_cookie(cookie);
+ goto again;
+
+ case FSCACHE_COOKIE_STAGE_CREATING:
+ WARN_ONCE(1, "Cookie %x in unexpected stage %u\n",
+ cookie->debug_id, cookie->stage);
+ break;
+
+ case FSCACHE_COOKIE_STAGE_FAILED:
+ break;
+
+ case FSCACHE_COOKIE_STAGE_RELINQUISHING:
+ case FSCACHE_COOKIE_STAGE_WITHDRAWING:
+ if (test_and_clear_bit(FSCACHE_COOKIE_IS_CACHING, &cookie->flags) &&
+ cookie->cache_priv)
+ cookie->volume->cache->ops->withdraw_cookie(cookie);
+ if (cookie->stage == FSCACHE_COOKIE_STAGE_RELINQUISHING) {
+ fscache_see_cookie(cookie, fscache_cookie_see_relinquish);
+ fscache_drop_cookie(cookie);
+ break;
+ } else {
+ fscache_see_cookie(cookie, fscache_cookie_see_withdraw);
+ }
+ fallthrough;
+
+ case FSCACHE_COOKIE_STAGE_QUIESCENT:
+ case FSCACHE_COOKIE_STAGE_DROPPED:
+ clear_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &cookie->flags);
+ clear_bit(FSCACHE_COOKIE_DO_WITHDRAW, &cookie->flags);
+ clear_bit(FSCACHE_COOKIE_DO_COMMIT, &cookie->flags);
+ clear_bit(FSCACHE_COOKIE_DO_PREP_TO_WRITE, &cookie->flags);
+ set_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags);
+ fscache_set_cookie_stage(cookie, FSCACHE_COOKIE_STAGE_QUIESCENT);
+ break;
+ }
+ _leave("");
+}
+
+static void fscache_cookie_worker(struct work_struct *work)
+{
+ struct fscache_cookie *cookie = container_of(work, struct fscache_cookie, work);
+
+ fscache_see_cookie(cookie, fscache_cookie_see_work);
+ __fscache_cookie_worker(cookie);
+ fscache_put_cookie(cookie, fscache_cookie_put_work);
+}
+
+/*
+ * Wait for the object to become inactive. The cookie's work item will be
+ * scheduled when someone transitions n_accesses to 0.
+ */
+static void __fscache_withdraw_cookie(struct fscache_cookie *cookie)
+{
+ if (test_and_clear_bit(FSCACHE_COOKIE_NACC_ELEVATED, &cookie->flags))
+ fscache_end_cookie_access(cookie, fscache_access_cache_unpin);
+ else
+ __fscache_end_cookie_access(cookie);
+}
+
/*
* Remove a cookie from the hash table.
*/
@@ -404,6 +549,25 @@ static void fscache_drop_cookie(struct fscache_cookie *cookie)
fscache_stat(&fscache_n_relinquishes_dropped);
}

+static void fscache_drop_withdraw_cookie(struct fscache_cookie *cookie)
+{
+ __fscache_withdraw_cookie(cookie);
+}
+
+/**
+ * fscache_withdraw_cookie - Mark a cookie for withdrawal
+ * @cookie: The cookie to be withdrawn.
+ *
+ * Allow the cache backend to withdraw the backing for a cookie for its own
+ * reasons, even if that cookie is in active use.
+ */
+void fscache_withdraw_cookie(struct fscache_cookie *cookie)
+{
+ set_bit(FSCACHE_COOKIE_DO_WITHDRAW, &cookie->flags);
+ fscache_drop_withdraw_cookie(cookie);
+}
+EXPORT_SYMBOL(fscache_withdraw_cookie);
+
/*
* Allow the netfs to release a cookie back to the cache.
* - the object will be marked as recyclable on disk if retire is true
@@ -432,7 +596,7 @@ void __fscache_relinquish_cookie(struct fscache_cookie *cookie, bool retire)
set_bit(FSCACHE_COOKIE_DO_RELINQUISH, &cookie->flags);

if (test_bit(FSCACHE_COOKIE_HAS_BEEN_CACHED, &cookie->flags))
- ; // PLACEHOLDER: Do something here if the cookie was cached
+ fscache_drop_withdraw_cookie(cookie);
else
fscache_drop_cookie(cookie);
fscache_put_cookie(cookie, fscache_cookie_put_relinquish);
diff --git a/include/linux/fscache-cache.h b/include/linux/fscache-cache.h
index dfecabfd4a0e..f6d63dc0ffa9 100644
--- a/include/linux/fscache-cache.h
+++ b/include/linux/fscache-cache.h
@@ -59,6 +59,15 @@ struct fscache_cache_ops {

/* Free the cache's data attached to a volume */
void (*free_volume)(struct fscache_volume *volume);
+
+ /* Look up a cookie in the cache */
+ bool (*lookup_cookie)(struct fscache_cookie *cookie);
+
+ /* Withdraw an object without any cookie access counts held */
+ void (*withdraw_cookie)(struct fscache_cookie *cookie);
+
+ /* Prepare to write to a live cache object */
+ void (*prepare_to_write)(struct fscache_cookie *cookie);
};

static inline enum fscache_cache_state fscache_cache_state(const struct fscache_cache *cache)
@@ -96,6 +105,7 @@ extern int fscache_add_cache(struct fscache_cache *cache,
extern void fscache_put_cache(struct fscache_cache *cache,
enum fscache_cache_trace where);
extern void fscache_withdraw_cache(struct fscache_cache *cache);
+extern void fscache_withdraw_cookie(struct fscache_cookie *cookie);

extern void fscache_io_error(struct fscache_cache *cache);

diff --git a/include/trace/events/fscache.h b/include/trace/events/fscache.h
index 3476cc7fdb25..00ffe0f8e6d3 100644
--- a/include/trace/events/fscache.h
+++ b/include/trace/events/fscache.h
@@ -67,6 +67,7 @@ enum fscache_access_trace {
fscache_access_acquire_volume_end,
fscache_access_cache_pin,
fscache_access_cache_unpin,
+ fscache_access_lookup_cookie_end,
fscache_access_relinquish_volume,
fscache_access_relinquish_volume_end,
fscache_access_unlive,
@@ -122,6 +123,7 @@ enum fscache_access_trace {
EM(fscache_access_acquire_volume_end, "END acq_vol") \
EM(fscache_access_cache_pin, "PIN cache ") \
EM(fscache_access_cache_unpin, "UNPIN cache ") \
+ EM(fscache_access_lookup_cookie_end, "END lookup ") \
EM(fscache_access_relinquish_volume, "BEGIN rlq_vol") \
EM(fscache_access_relinquish_volume_end,"END rlq_vol") \
E_(fscache_access_unlive, "END unlive ")