[PATCH 8/8] FS-Cache: Simplify cookie retention for fscache_objects,fixing access problems

From: David Howells
Date: Thu May 02 2013 - 20:34:14 EST


Simplify the way fscache cache objects retain their cookie. The way I
implemented the cookie storage handling made synchronisation a pain (ie. the
object state machine can't rely on the cookie actually still being there).

Instead of the the object being detached from the cookie and the cookie being
freed in __fscache_relinquish_cookie(), we defer both operations:

(*) The detachment of the object from the list in the cookie now takes place
in fscache_drop_object() and is thus governed by the object state machine
(fscache_detach_from_cookie() has been removed).

(*) The release of the cookie is now in fscache_object_destroy() - which is
called by the cache backend just before it frees the object.

This means that the fscache_cookie struct is now available to the cache all the
way through from ->alloc_object() to ->drop_object() and ->put_object() -
meaning that it's no longer necessary to take object->lock to guarantee access.

However, __fscache_relinquish_cookie() doesn't wait for the object to go all
the way through to destruction before letting the netfs proceed. That would
massively slow down the netfs. Since __fscache_relinquish_cookie() leaves the
cookie around, in must therefore break all attachments to the netfs - which
includes ->def, ->netfs_data and any outstanding page read/writes.

To handle this, struct fscache_cookie now has an n_active counter:

(1) This starts off initialised to 1.

(2) Any time the cache needs to get at the netfs data, it calls
fscache_use_cookie() to increment it - if it is not zero. If it was zero,
then access is not permitted.

(3) When the cache has finished with the data, it calls fscache_unuse_cookie()
to decrement it. This does a wake-up on it if it reaches 0.

(4) __fscache_relinquish_cookie() decrements n_active and then waits for it to
reach 0. The initialisation to 1 in step (1) ensures that we only get
wake ups when we're trying to get rid of the cookie.

This leaves __fscache_relinquish_cookie() a lot simpler.


***
This fixes a problem in the current code whereby if fscache_invalidate() is
followed sufficiently quickly by fscache_relinquish_cookie() then it is
possible for __fscache_relinquish_cookie() to have detached the cookie from the
object and cleared the pointer before a thread is dispatched to process the
invalidation state in the object state machine.

Since the pending write clearance was deferred to the invalidation state to
make it asynchronous, we need to either wait in relinquishment for the stores
tree to be cleared in the invalidation state or we need to handle the clearance
in relinquishment.

Further, if the relinquishment code does clear the tree, then the invalidation
state need to make the clearance contingent on still having the cookie to hand
(since that's where the tree is rooted) and we have to prevent the cookie from
disappearing for the duration.

This can lead to an oops like the following:

BUG: unable to handle kernel NULL pointer dereference at 000000000000000c
...
RIP: 0010:[<ffffffff8151023e>] _spin_lock+0xe/0x30
...
CR2: 000000000000000c ...
...
Process kslowd002 (...)
....
Call Trace:
[<ffffffffa01c3278>] fscache_invalidate_writes+0x38/0xd0 [fscache]
[<ffffffff810096f0>] ? __switch_to+0xd0/0x320
[<ffffffff8105e759>] ? find_busiest_queue+0x69/0x150
[<ffffffff8110ddd4>] ? slow_work_enqueue+0x104/0x180
[<ffffffffa01c1303>] fscache_object_slow_work_execute+0x5e3/0x9d0 [fscache]
[<ffffffff81096b67>] ? bit_waitqueue+0x17/0xd0
[<ffffffff8110e233>] slow_work_execute+0x233/0x310
[<ffffffff8110e515>] slow_work_thread+0x205/0x360
[<ffffffff81096ca0>] ? autoremove_wake_function+0x0/0x40
[<ffffffff8110e310>] ? slow_work_thread+0x0/0x360
[<ffffffff81096936>] kthread+0x96/0xa0
[<ffffffff8100c0ca>] child_rip+0xa/0x20
[<ffffffff810968a0>] ? kthread+0x0/0xa0
[<ffffffff8100c0c0>] ? child_rip+0x0/0x20

The parameter to fscache_invalidate_writes() was object->cookie which is NULL.

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---

fs/cachefiles/interface.c | 11 ++
fs/cachefiles/xattr.c | 6 -
fs/fscache/cookie.c | 80 ++++++-----------
fs/fscache/fsdef.c | 1
fs/fscache/internal.h | 3 +
fs/fscache/main.c | 11 ++
fs/fscache/netfs.c | 1
fs/fscache/object-list.c | 93 +++++++++-----------
fs/fscache/object.c | 188 ++++++++++++++++-------------------------
fs/fscache/operation.c | 12 +--
fs/fscache/page.c | 26 ++++--
include/linux/fscache-cache.h | 57 +++++++++---
12 files changed, 232 insertions(+), 257 deletions(-)

diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c
index 3d76321..eeb3f7d 100644
--- a/fs/cachefiles/interface.c
+++ b/fs/cachefiles/interface.c
@@ -212,20 +212,29 @@ static void cachefiles_update_object(struct fscache_object *_object)
object = container_of(_object, struct cachefiles_object, fscache);
cache = container_of(object->fscache.cache, struct cachefiles_cache,
cache);
+
+ if (!fscache_use_cookie(_object)) {
+ _leave(" [relinq]");
+ return;
+ }
+
cookie = object->fscache.cookie;

if (!cookie->def->get_aux) {
+ fscache_unuse_cookie(_object);
_leave(" [no aux]");
return;
}

auxdata = kmalloc(2 + 512 + 3, cachefiles_gfp);
if (!auxdata) {
+ fscache_unuse_cookie(_object);
_leave(" [nomem]");
return;
}

auxlen = cookie->def->get_aux(cookie->netfs_data, auxdata->data, 511);
+ fscache_unuse_cookie(_object);
ASSERTCMP(auxlen, <, 511);

auxdata->len = auxlen + 1;
@@ -263,7 +272,7 @@ static void cachefiles_drop_object(struct fscache_object *_object)
#endif

/* delete retired objects */
- if (test_bit(FSCACHE_OBJECT_RETIRE, &object->fscache.flags) &&
+ if (test_bit(FSCACHE_COOKIE_RETIRED, &object->fscache.cookie->flags) &&
_object != cache->cache.fsdef
) {
_debug("- retire object OBJ%x", object->fscache.debug_id);
diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c
index 73b4628..2476e51 100644
--- a/fs/cachefiles/xattr.c
+++ b/fs/cachefiles/xattr.c
@@ -109,13 +109,12 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object,
struct dentry *dentry = object->dentry;
int ret;

- ASSERT(object->fscache.cookie);
ASSERT(dentry);

_enter("%p,#%d", object, auxdata->len);

/* attempt to install the cache metadata directly */
- _debug("SET %s #%u", object->fscache.cookie->def->name, auxdata->len);
+ _debug("SET #%u", auxdata->len);

ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
&auxdata->type, auxdata->len,
@@ -138,13 +137,12 @@ int cachefiles_update_object_xattr(struct cachefiles_object *object,
struct dentry *dentry = object->dentry;
int ret;

- ASSERT(object->fscache.cookie);
ASSERT(dentry);

_enter("%p,#%d", object, auxdata->len);

/* attempt to install the cache metadata directly */
- _debug("SET %s #%u", object->fscache.cookie->def->name, auxdata->len);
+ _debug("SET #%u", auxdata->len);

ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
&auxdata->type, auxdata->len,
diff --git a/fs/fscache/cookie.c b/fs/fscache/cookie.c
index eee4366..0e91a3c 100644
--- a/fs/fscache/cookie.c
+++ b/fs/fscache/cookie.c
@@ -95,6 +95,11 @@ struct fscache_cookie *__fscache_acquire_cookie(
atomic_set(&cookie->usage, 1);
atomic_set(&cookie->n_children, 0);

+ /* We keep the active count elevated until relinquishment to prevent an
+ * attempt to wake up every time the object operations queue quiesces.
+ */
+ atomic_set(&cookie->n_active, 1);
+
atomic_inc(&parent->usage);
atomic_inc(&parent->n_children);

@@ -177,7 +182,6 @@ static int fscache_acquire_non_index_cookie(struct fscache_cookie *cookie)

cookie->flags =
(1 << FSCACHE_COOKIE_LOOKING_UP) |
- (1 << FSCACHE_COOKIE_CREATING) |
(1 << FSCACHE_COOKIE_NO_DATA_YET);

/* ask the cache to allocate objects for this cookie and its parent
@@ -467,7 +471,6 @@ EXPORT_SYMBOL(__fscache_update_cookie);
*/
void __fscache_relinquish_cookie(struct fscache_cookie *cookie, int retire)
{
- struct fscache_cache *cache;
struct fscache_object *object;

fscache_stat(&fscache_n_relinquishes);
@@ -480,8 +483,11 @@ void __fscache_relinquish_cookie(struct fscache_cookie *cookie, int retire)
return;
}

- _enter("%p{%s,%p},%d",
- cookie, cookie->def->name, cookie->netfs_data, retire);
+ _enter("%p{%s,%p,%d},%d",
+ cookie, cookie->def->name, cookie->netfs_data,
+ atomic_read(&cookie->n_active), retire);
+
+ ASSERTCMP(atomic_read(&cookie->n_active), >, 0);

if (atomic_read(&cookie->n_children) != 0) {
printk(KERN_ERR "FS-Cache: Cookie '%s' still has children\n",
@@ -489,62 +495,28 @@ void __fscache_relinquish_cookie(struct fscache_cookie *cookie, int retire)
BUG();
}

- /* wait for the cookie to finish being instantiated (or to fail) */
- if (test_bit(FSCACHE_COOKIE_CREATING, &cookie->flags)) {
- fscache_stat(&fscache_n_relinquishes_waitcrt);
- wait_on_bit(&cookie->flags, FSCACHE_COOKIE_CREATING,
- fscache_wait_bit, TASK_UNINTERRUPTIBLE);
- }
+ /* No further netfs-accessing operations on this cookie permitted */
+ set_bit(FSCACHE_COOKIE_RELINQUISHED, &cookie->flags);
+ if (retire)
+ set_bit(FSCACHE_COOKIE_RETIRED, &cookie->flags);

-try_again:
spin_lock(&cookie->lock);
-
- /* break links with all the active objects */
- while (!hlist_empty(&cookie->backing_objects)) {
- int n_reads;
- object = hlist_entry(cookie->backing_objects.first,
- struct fscache_object,
- cookie_link);
-
- _debug("RELEASE OBJ%x", object->debug_id);
-
- set_bit(FSCACHE_COOKIE_WAITING_ON_READS, &cookie->flags);
- n_reads = atomic_read(&object->n_reads);
- if (n_reads) {
- int n_ops = object->n_ops;
- int n_in_progress = object->n_in_progress;
- spin_unlock(&cookie->lock);
- printk(KERN_ERR "FS-Cache:"
- " Cookie '%s' still has %d outstanding reads (%d,%d)\n",
- cookie->def->name,
- n_reads, n_ops, n_in_progress);
- wait_on_bit(&cookie->flags, FSCACHE_COOKIE_WAITING_ON_READS,
- fscache_wait_bit, TASK_UNINTERRUPTIBLE);
- printk("Wait finished\n");
- goto try_again;
- }
-
- /* detach each cache object from the object cookie */
- spin_lock(&object->lock);
- hlist_del_init(&object->cookie_link);
-
- cache = object->cache;
- object->cookie = NULL;
- if (retire)
- set_bit(FSCACHE_OBJECT_RETIRE, &object->flags);
+ hlist_for_each_entry(object, &cookie->backing_objects, cookie_link) {
fscache_raise_event(object, FSCACHE_OBJECT_EV_KILL);
- spin_unlock(&object->lock);
-
- if (atomic_dec_and_test(&cookie->usage))
- /* the cookie refcount shouldn't be reduced to 0 yet */
- BUG();
}
+ spin_unlock(&cookie->lock);

- /* detach pointers back to the netfs */
+ /* Wait for cessation of activity requiring access to the netfs (when
+ * n_active reaches 0).
+ */
+ if (!atomic_dec_and_test(&cookie->n_active))
+ wait_on_atomic_t(&cookie->n_active, fscache_wait_atomic_t,
+ TASK_UNINTERRUPTIBLE);
+
+ /* Clear pointers back to the netfs */
cookie->netfs_data = NULL;
cookie->def = NULL;
-
- spin_unlock(&cookie->lock);
+ BUG_ON(cookie->stores.rnode);

if (cookie->parent) {
ASSERTCMP(atomic_read(&cookie->parent->usage), >, 0);
@@ -552,7 +524,7 @@ try_again:
atomic_dec(&cookie->parent->n_children);
}

- /* finally dispose of the cookie */
+ /* Dispose of the netfs's link to the cookie */
ASSERTCMP(atomic_read(&cookie->usage), >, 0);
fscache_cookie_put(cookie);

diff --git a/fs/fscache/fsdef.c b/fs/fscache/fsdef.c
index f5b4bae..10a2ade 100644
--- a/fs/fscache/fsdef.c
+++ b/fs/fscache/fsdef.c
@@ -55,6 +55,7 @@ static struct fscache_cookie_def fscache_fsdef_index_def = {

struct fscache_cookie fscache_fsdef_index = {
.usage = ATOMIC_INIT(1),
+ .n_active = ATOMIC_INIT(1),
.lock = __SPIN_LOCK_UNLOCKED(fscache_fsdef_index.lock),
.backing_objects = HLIST_HEAD_INIT,
.def = &fscache_fsdef_index_def,
diff --git a/fs/fscache/internal.h b/fs/fscache/internal.h
index 3322d3c..12d505b 100644
--- a/fs/fscache/internal.h
+++ b/fs/fscache/internal.h
@@ -93,6 +93,7 @@ static inline bool fscache_object_congested(void)

extern int fscache_wait_bit(void *);
extern int fscache_wait_bit_interruptible(void *);
+extern int fscache_wait_atomic_t(atomic_t *);

/*
* object.c
@@ -106,8 +107,10 @@ extern void fscache_enqueue_object(struct fscache_object *);
extern const struct file_operations fscache_objlist_fops;

extern void fscache_objlist_add(struct fscache_object *);
+extern void fscache_objlist_remove(struct fscache_object *);
#else
#define fscache_objlist_add(object) do {} while(0)
+#define fscache_objlist_remove(object) do {} while(0)
#endif

/*
diff --git a/fs/fscache/main.c b/fs/fscache/main.c
index f9d8567..7c27907 100644
--- a/fs/fscache/main.c
+++ b/fs/fscache/main.c
@@ -205,7 +205,6 @@ int fscache_wait_bit(void *flags)
schedule();
return 0;
}
-EXPORT_SYMBOL(fscache_wait_bit);

/*
* wait_on_bit() sleep function for interruptible waiting
@@ -215,4 +214,12 @@ int fscache_wait_bit_interruptible(void *flags)
schedule();
return signal_pending(current);
}
-EXPORT_SYMBOL(fscache_wait_bit_interruptible);
+
+/*
+ * wait_on_atomic_t() sleep function for uninterruptible waiting
+ */
+int fscache_wait_atomic_t(atomic_t *p)
+{
+ schedule();
+ return 0;
+}
diff --git a/fs/fscache/netfs.c b/fs/fscache/netfs.c
index e028b8e..b1bb611 100644
--- a/fs/fscache/netfs.c
+++ b/fs/fscache/netfs.c
@@ -40,6 +40,7 @@ int __fscache_register_netfs(struct fscache_netfs *netfs)
/* initialise the primary index cookie */
atomic_set(&netfs->primary_index->usage, 1);
atomic_set(&netfs->primary_index->n_children, 0);
+ atomic_set(&netfs->primary_index->n_active, 1);

netfs->primary_index->def = &fscache_fsdef_netfs_def;
netfs->primary_index->parent = &fscache_fsdef_index;
diff --git a/fs/fscache/object-list.c b/fs/fscache/object-list.c
index 4a386b0..e1959ef 100644
--- a/fs/fscache/object-list.c
+++ b/fs/fscache/object-list.c
@@ -70,13 +70,10 @@ void fscache_objlist_add(struct fscache_object *obj)
write_unlock(&fscache_object_list_lock);
}

-/**
- * fscache_object_destroy - Note that a cache object is about to be destroyed
- * @object: The object to be destroyed
- *
- * Note the imminent destruction and deallocation of a cache object record.
+/*
+ * Remove an object from the object list.
*/
-void fscache_object_destroy(struct fscache_object *obj)
+void fscache_objlist_remove(struct fscache_object *obj)
{
write_lock(&fscache_object_list_lock);

@@ -85,7 +82,6 @@ void fscache_object_destroy(struct fscache_object *obj)

write_unlock(&fscache_object_list_lock);
}
-EXPORT_SYMBOL(fscache_object_destroy);

/*
* find the object in the tree on or after the specified index
@@ -166,10 +162,9 @@ static int fscache_objlist_show(struct seq_file *m, void *v)
{
struct fscache_objlist_data *data = m->private;
struct fscache_object *obj = v;
+ struct fscache_cookie *cookie;
unsigned long config = data->config;
- uint16_t keylen, auxlen;
char _type[3], *type;
- bool no_cookie;
u8 *buf = data->buf, *p;

if ((unsigned long) v == 1) {
@@ -216,8 +211,9 @@ static int fscache_objlist_show(struct seq_file *m, void *v)
} \
} while(0)

+ cookie = obj->cookie;
if (~config) {
- FILTER(obj->cookie,
+ FILTER(cookie->def,
COOKIE, NOCOOKIE);
FILTER(fscache_object_is_active(obj) ||
obj->n_ops != 0 ||
@@ -250,48 +246,40 @@ static int fscache_objlist_show(struct seq_file *m, void *v)
obj->flags,
work_busy(&obj->work));

- no_cookie = true;
- keylen = auxlen = 0;
- if (obj->cookie) {
- spin_lock(&obj->lock);
- if (obj->cookie) {
- switch (obj->cookie->def->type) {
- case 0:
- type = "IX";
- break;
- case 1:
- type = "DT";
- break;
- default:
- sprintf(_type, "%02u",
- obj->cookie->def->type);
- type = _type;
- break;
- }
+ if (fscache_use_cookie(obj)) {
+ uint16_t keylen = 0, auxlen = 0;

- seq_printf(m, "%-16s %s %2lx %16p",
- obj->cookie->def->name,
- type,
- obj->cookie->flags,
- obj->cookie->netfs_data);
-
- if (obj->cookie->def->get_key &&
- config & FSCACHE_OBJLIST_CONFIG_KEY)
- keylen = obj->cookie->def->get_key(
- obj->cookie->netfs_data,
- buf, 400);
-
- if (obj->cookie->def->get_aux &&
- config & FSCACHE_OBJLIST_CONFIG_AUX)
- auxlen = obj->cookie->def->get_aux(
- obj->cookie->netfs_data,
- buf + keylen, 512 - keylen);
-
- no_cookie = false;
+ switch (cookie->def->type) {
+ case 0:
+ type = "IX";
+ break;
+ case 1:
+ type = "DT";
+ break;
+ default:
+ sprintf(_type, "%02u", cookie->def->type);
+ type = _type;
+ break;
}
- spin_unlock(&obj->lock);

- if (!no_cookie && (keylen > 0 || auxlen > 0)) {
+ seq_printf(m, "%-16s %s %2lx %16p",
+ cookie->def->name,
+ type,
+ cookie->flags,
+ cookie->netfs_data);
+
+ if (cookie->def->get_key &&
+ config & FSCACHE_OBJLIST_CONFIG_KEY)
+ keylen = cookie->def->get_key(cookie->netfs_data,
+ buf, 400);
+
+ if (cookie->def->get_aux &&
+ config & FSCACHE_OBJLIST_CONFIG_AUX)
+ auxlen = cookie->def->get_aux(cookie->netfs_data,
+ buf + keylen, 512 - keylen);
+ fscache_unuse_cookie(obj);
+
+ if (keylen > 0 || auxlen > 0) {
seq_printf(m, " ");
for (p = buf; keylen > 0; keylen--)
seq_printf(m, "%02x", *p++);
@@ -302,12 +290,11 @@ static int fscache_objlist_show(struct seq_file *m, void *v)
seq_printf(m, "%02x", *p++);
}
}
- }

- if (no_cookie)
- seq_printf(m, "<no_cookie>\n");
- else
seq_printf(m, "\n");
+ } else {
+ seq_printf(m, "<no_netfs>\n");
+ }
return 0;
}

diff --git a/fs/fscache/object.c b/fs/fscache/object.c
index c2d9de0..c2c8c23 100644
--- a/fs/fscache/object.c
+++ b/fs/fscache/object.c
@@ -29,7 +29,6 @@ static const struct fscache_state *fscache_look_up_object(struct fscache_object
static const struct fscache_state *fscache_object_available(struct fscache_object *, int);
static const struct fscache_state *fscache_parent_ready(struct fscache_object *, int);
static const struct fscache_state *fscache_update_object(struct fscache_object *, int);
-static const struct fscache_state *fscache_detach_from_cookie(struct fscache_object *, int);

#define __STATE_NAME(n) fscache_osm_##n
#define STATE(n) (&__STATE_NAME(n))
@@ -71,7 +70,6 @@ static WORK_STATE(LOOKUP_FAILURE, "LCFL", fscache_lookup_failure);
static WORK_STATE(KILL_OBJECT, "KILL", fscache_kill_object);
static WORK_STATE(KILL_DEPENDENTS, "KDEP", fscache_kill_dependents);
static WORK_STATE(DROP_OBJECT, "DROP", fscache_drop_object);
-static WORK_STATE(DETACH_FROM_COOKIE, "DTCH", fscache_detach_from_cookie);
static WORK_STATE(OBJECT_DEAD, "DEAD", (void*)2UL);

static WAIT_STATE(WAIT_FOR_INIT, "?INI",
@@ -127,8 +125,8 @@ static inline void fscache_done_parent_op(struct fscache_object *object)
object->debug_id, parent->debug_id, parent->n_ops);

spin_lock_nested(&parent->lock, 1);
- parent->n_ops--;
parent->n_obj_ops--;
+ parent->n_ops--;
if (parent->n_ops == 0)
fscache_raise_event(parent, FSCACHE_OBJECT_EV_CLEARED);
spin_unlock(&parent->lock);
@@ -303,22 +301,10 @@ EXPORT_SYMBOL(fscache_object_init);
static const struct fscache_state *fscache_abort_initialisation(struct fscache_object *object,
int event)
{
- struct fscache_cookie *cookie;
-
_enter("{OBJ%x},%d", object->debug_id, event);

object->oob_event_mask = 0;
- clear_bit(FSCACHE_OBJECT_IS_LIVE, &object->flags);
-
fscache_dequeue_object(object);
-
- spin_lock(&object->lock);
- cookie = object->cookie;
- clear_bit_unlock(FSCACHE_COOKIE_CREATING, &cookie->flags);
- spin_unlock(&object->lock);
-
- wake_up_bit(&cookie->flags, FSCACHE_COOKIE_CREATING);
-
return transit_to(KILL_OBJECT);
}

@@ -328,8 +314,6 @@ static const struct fscache_state *fscache_abort_initialisation(struct fscache_o
* immediately to do a creation
* - we may need to start the process of creating a parent and we need to wait
* for the parent's lookup and creation to complete if it's not there yet
- * - an object's cookie is pinned until we clear FSCACHE_COOKIE_CREATING on the
- * leaf-most cookies of the object and all its children
*/
static const struct fscache_state *fscache_initialise_object(struct fscache_object *object,
int event)
@@ -344,14 +328,14 @@ static const struct fscache_state *fscache_initialise_object(struct fscache_obje
parent = object->parent;
if (!parent) {
_leave(" [no parent]");
- return transit_to(DETACH_FROM_COOKIE);
+ return transit_to(DROP_OBJECT);
}

- _debug("parent %s", parent->state->name);
+ _debug("parent: %s of:%lx", parent->state->name, parent->flags);

if (fscache_object_is_dying(parent)) {
_leave(" [bad parent]");
- return transit_to(DETACH_FROM_COOKIE);
+ return transit_to(DROP_OBJECT);
}

if (fscache_object_is_available(parent)) {
@@ -373,7 +357,7 @@ static const struct fscache_state *fscache_initialise_object(struct fscache_obje
spin_unlock(&parent->lock);
if (!success) {
_leave(" [grab failed]");
- return transit_to(DETACH_FROM_COOKIE);
+ return transit_to(DROP_OBJECT);
}

/* fscache_acquire_non_index_cookie() uses this
@@ -409,8 +393,6 @@ static const struct fscache_state *fscache_parent_ready(struct fscache_object *o
* look an object up in the cache from which it was allocated
* - we hold an "access lock" on the parent object, so the parent object cannot
* be withdrawn by either party till we've finished
- * - an object's cookie is pinned until we clear FSCACHE_COOKIE_CREATING on the
- * leaf-most cookies of the object and all its children
*/
static const struct fscache_state *fscache_look_up_object(struct fscache_object *object,
int event)
@@ -431,22 +413,21 @@ static const struct fscache_state *fscache_look_up_object(struct fscache_object
ASSERT(fscache_object_is_available(parent));

if (fscache_object_is_dying(parent) ||
- test_bit(FSCACHE_IOERROR, &object->cache->flags)) {
+ test_bit(FSCACHE_IOERROR, &object->cache->flags) ||
+ !fscache_use_cookie(object)) {
_leave(" [unavailable]");
return transit_to(LOOKUP_FAILURE);
}

- _debug("LOOKUP \"%s/%s\" in \"%s\"",
- parent->cookie->def->name, cookie->def->name,
- object->cache->tag->name);
+ _debug("LOOKUP \"%s\" in \"%s\"",
+ cookie->def->name, object->cache->tag->name);

fscache_stat(&fscache_n_object_lookups);
fscache_stat(&fscache_n_cop_lookup_object);
ret = object->cache->ops->lookup_object(object);
fscache_stat_d(&fscache_n_cop_lookup_object);

- if (test_bit(FSCACHE_OBJECT_EV_ERROR, &object->events))
- set_bit(FSCACHE_COOKIE_UNAVAILABLE, &cookie->flags);
+ fscache_unuse_cookie(object);

if (ret == -ETIMEDOUT) {
/* probably stuck behind another object, so move this one to
@@ -528,11 +509,6 @@ void fscache_obtained_object(struct fscache_object *object)
}

set_bit(FSCACHE_OBJECT_IS_AVAILABLE, &object->flags);
-
- /* Permit __fscache_relinquish_cookie() to proceed */
- clear_bit_unlock(FSCACHE_COOKIE_CREATING, &cookie->flags);
- wake_up_bit(&cookie->flags, FSCACHE_COOKIE_CREATING);
-
_leave("");
}
EXPORT_SYMBOL(fscache_obtained_object);
@@ -543,16 +519,12 @@ EXPORT_SYMBOL(fscache_obtained_object);
static const struct fscache_state *fscache_object_available(struct fscache_object *object,
int event)
{
- struct fscache_cookie *cookie = object->cookie;
-
_enter("{OBJ%x},%d", object->debug_id, event);

object->oob_table = fscache_osm_run_oob;

spin_lock(&object->lock);

- ASSERTIF(cookie, !test_bit(FSCACHE_COOKIE_CREATING, &object->cookie->flags));
-
fscache_done_parent_op(object);
if (object->n_in_progress == 0) {
if (object->n_ops > 0) {
@@ -595,7 +567,6 @@ static const struct fscache_state *fscache_lookup_failure(struct fscache_object
int event)
{
struct fscache_cookie *cookie;
- bool wake_looking_up = false;

_enter("{OBJ%x},%d", object->debug_id, event);

@@ -605,19 +576,10 @@ static const struct fscache_state *fscache_lookup_failure(struct fscache_object
object->cache->ops->lookup_complete(object);
fscache_stat_d(&fscache_n_cop_lookup_complete);

- spin_lock(&object->lock);
cookie = object->cookie;
set_bit(FSCACHE_COOKIE_UNAVAILABLE, &cookie->flags);
- if (cookie) {
- if (test_and_clear_bit(FSCACHE_COOKIE_LOOKING_UP, &cookie->flags))
- wake_looking_up = true;
- clear_bit_unlock(FSCACHE_COOKIE_CREATING, &cookie->flags);
- }
- spin_unlock(&object->lock);
-
- if (wake_looking_up)
+ if (test_and_clear_bit(FSCACHE_COOKIE_LOOKING_UP, &cookie->flags))
wake_up_bit(&cookie->flags, FSCACHE_COOKIE_LOOKING_UP);
- wake_up_bit(&cookie->flags, FSCACHE_COOKIE_CREATING);

fscache_done_parent_op(object);
return transit_to(KILL_OBJECT);
@@ -633,21 +595,20 @@ static const struct fscache_state *fscache_kill_object(struct fscache_object *ob
_enter("{OBJ%x,%d,%d},%d",
object->debug_id, object->n_ops, object->n_children, event);

- object->oob_event_mask = 0;
-
- spin_lock(&object->lock);
clear_bit(FSCACHE_OBJECT_IS_LIVE, &object->flags);
- spin_unlock(&object->lock);
+ object->oob_event_mask = 0;

if (list_empty(&object->dependents) &&
object->n_ops == 0 &&
object->n_children == 0)
- return object->cookie ?
- transit_to(DETACH_FROM_COOKIE) : transit_to(DROP_OBJECT);
+ return transit_to(DROP_OBJECT);

- spin_lock(&object->lock);
- fscache_start_operations(object);
- spin_unlock(&object->lock);
+ if (object->n_in_progress == 0) {
+ spin_lock(&object->lock);
+ if (object->n_ops > 0 && object->n_in_progress == 0)
+ fscache_start_operations(object);
+ spin_unlock(&object->lock);
+ }

if (!list_empty(&object->dependents))
return transit_to(KILL_DEPENDENTS);
@@ -669,64 +630,32 @@ static const struct fscache_state *fscache_kill_dependents(struct fscache_object
}

/*
- * withdraw an object from active service
- */
-static const struct fscache_state *fscache_detach_from_cookie(struct fscache_object *object,
- int event)
-{
- struct fscache_cookie *cookie;
- bool detached = false, awaken = false;
-
- _enter("{OBJ%x},%d", object->debug_id, event);
-
- spin_lock(&object->lock);
- cookie = object->cookie;
- if (cookie) {
- /* need to get the cookie lock before the object lock, starting
- * from the object pointer */
- atomic_inc(&cookie->usage);
- spin_unlock(&object->lock);
-
- spin_lock(&cookie->lock);
- spin_lock(&object->lock);
-
- if (object->cookie == cookie) {
- hlist_del_init(&object->cookie_link);
- object->cookie = NULL;
- if (test_and_clear_bit(FSCACHE_COOKIE_INVALIDATING,
- &cookie->flags))
- awaken = true;
- detached = true;
- }
- spin_unlock(&cookie->lock);
- fscache_cookie_put(cookie);
- if (detached)
- fscache_cookie_put(cookie);
- }
-
- spin_unlock(&object->lock);
-
- if (awaken)
- wake_up_bit(&cookie->flags, FSCACHE_COOKIE_INVALIDATING);
-
- fscache_stat(&fscache_n_object_dead);
- _leave("");
- return transit_to(DROP_OBJECT);
-}
-
-/*
* Drop an object's attachments
*/
static const struct fscache_state *fscache_drop_object(struct fscache_object *object,
int event)
{
struct fscache_object *parent = object->parent;
+ struct fscache_cookie *cookie = object->cookie;
struct fscache_cache *cache = object->cache;
+ bool awaken = false;

_enter("{OBJ%x,%d},%d", object->debug_id, object->n_children, event);

- ASSERTCMP(object->cookie, ==, NULL);
- ASSERT(hlist_unhashed(&object->cookie_link));
+ ASSERT(cookie != NULL);
+ ASSERT(!hlist_unhashed(&object->cookie_link));
+
+ /* Make sure the cookie no longer points here and that the netfs isn't
+ * waiting for us.
+ */
+ spin_lock(&cookie->lock);
+ hlist_del_init(&object->cookie_link);
+ if (test_and_clear_bit(FSCACHE_COOKIE_INVALIDATING, &cookie->flags))
+ awaken = true;
+ spin_unlock(&cookie->lock);
+
+ if (awaken)
+ wake_up_bit(&cookie->flags, FSCACHE_COOKIE_INVALIDATING);

/* Prevent a race with our last child, which has to signal EV_CLEARED
* before dropping our spinlock.
@@ -787,6 +716,22 @@ static void fscache_put_object(struct fscache_object *object)
fscache_stat_d(&fscache_n_cop_put_object);
}

+/**
+ * fscache_object_destroy - Note that a cache object is about to be destroyed
+ * @object: The object to be destroyed
+ *
+ * Note the imminent destruction and deallocation of a cache object record.
+ */
+void fscache_object_destroy(struct fscache_object *object)
+{
+ fscache_objlist_remove(object);
+
+ /* We can get rid of the cookie now */
+ fscache_cookie_put(object->cookie);
+ object->cookie = NULL;
+}
+EXPORT_SYMBOL(fscache_object_destroy);
+
/*
* enqueue an object for metadata-type processing
*/
@@ -896,7 +841,10 @@ static void fscache_dequeue_object(struct fscache_object *object)
* @data: The auxiliary data for the object
* @datalen: The size of the auxiliary data
*
- * This function consults the netfs about the coherency state of an object
+ * This function consults the netfs about the coherency state of an object.
+ * The caller must be holding a ref on cookie->n_active (held by
+ * fscache_look_up_object() on behalf of the cache backend during object lookup
+ * and creation).
*/
enum fscache_checkaux fscache_check_aux(struct fscache_object *object,
const void *data, uint16_t datalen)
@@ -945,6 +893,15 @@ static const struct fscache_state *_fscache_invalidate_object(struct fscache_obj

_enter("{OBJ%x},%d", object->debug_id, event);

+ /* We're going to need the cookie. If the cookie is not available then
+ * retire the object instead.
+ */
+ if (!fscache_use_cookie(object)) {
+ ASSERT(object->cookie->stores.rnode == NULL);
+ set_bit(FSCACHE_COOKIE_RETIRED, &cookie->flags);
+ _leave(" [no cookie]");
+ return transit_to(KILL_OBJECT);
+ }

/* Reject any new read/write ops and abort any that are pending. */
fscache_invalidate_writes(cookie);
@@ -953,14 +910,13 @@ static const struct fscache_state *_fscache_invalidate_object(struct fscache_obj

/* Now we have to wait for in-progress reads and writes */
op = kzalloc(sizeof(*op), GFP_KERNEL);
- if (!op) {
- clear_bit(FSCACHE_OBJECT_IS_LIVE, &object->flags);
- _leave(" [ENOMEM]");
- return transit_to(KILL_OBJECT);
- }
+ if (!op)
+ goto nomem;

fscache_operation_init(op, object->cache->ops->invalidate_object, NULL);
- op->flags = FSCACHE_OP_ASYNC | (1 << FSCACHE_OP_EXCLUSIVE);
+ op->flags = FSCACHE_OP_ASYNC |
+ (1 << FSCACHE_OP_EXCLUSIVE) |
+ (1 << FSCACHE_OP_UNUSE_COOKIE);

spin_lock(&cookie->lock);
if (fscache_submit_exclusive_op(object, op) < 0)
@@ -982,6 +938,12 @@ static const struct fscache_state *_fscache_invalidate_object(struct fscache_obj
_leave(" [ok]");
return transit_to(UPDATE_OBJECT);

+nomem:
+ clear_bit(FSCACHE_OBJECT_IS_LIVE, &object->flags);
+ fscache_unuse_cookie(object);
+ _leave(" [ENOMEM]");
+ return transit_to(KILL_OBJECT);
+
submit_op_failed:
clear_bit(FSCACHE_OBJECT_IS_LIVE, &object->flags);
spin_unlock(&cookie->lock);
diff --git a/fs/fscache/operation.c b/fs/fscache/operation.c
index 4da211b..6935901 100644
--- a/fs/fscache/operation.c
+++ b/fs/fscache/operation.c
@@ -424,14 +424,10 @@ void fscache_put_operation(struct fscache_operation *op)

object = op->object;

- if (test_bit(FSCACHE_OP_DEC_READ_CNT, &op->flags)) {
- if (atomic_dec_and_test(&object->n_reads)) {
- clear_bit(FSCACHE_COOKIE_WAITING_ON_READS,
- &object->cookie->flags);
- wake_up_bit(&object->cookie->flags,
- FSCACHE_COOKIE_WAITING_ON_READS);
- }
- }
+ if (test_bit(FSCACHE_OP_DEC_READ_CNT, &op->flags))
+ atomic_dec(&object->n_reads);
+ if (test_bit(FSCACHE_OP_UNUSE_COOKIE, &op->flags))
+ fscache_unuse_cookie(object);

/* now... we may get called with the object spinlock held, so we
* complete the cleanup here only if we can immediately acquire the
diff --git a/fs/fscache/page.c b/fs/fscache/page.c
index b4e4b42..780bac6 100644
--- a/fs/fscache/page.c
+++ b/fs/fscache/page.c
@@ -163,10 +163,12 @@ static void fscache_attr_changed_op(struct fscache_operation *op)

fscache_stat(&fscache_n_attr_changed_calls);

- if (fscache_object_is_active(object)) {
+ if (fscache_object_is_active(object) &&
+ fscache_use_cookie(object)) {
fscache_stat(&fscache_n_cop_attr_changed);
ret = object->cache->ops->attr_changed(object);
fscache_stat_d(&fscache_n_cop_attr_changed);
+ fscache_unuse_cookie(object);
if (ret < 0)
fscache_abort_object(object);
}
@@ -246,6 +248,7 @@ static void fscache_release_retrieval_op(struct fscache_operation *_op)
* allocate a retrieval op
*/
static struct fscache_retrieval *fscache_alloc_retrieval(
+ struct fscache_cookie *cookie,
struct address_space *mapping,
fscache_rw_complete_t end_io_func,
void *context)
@@ -260,7 +263,10 @@ static struct fscache_retrieval *fscache_alloc_retrieval(
}

fscache_operation_init(&op->op, NULL, fscache_release_retrieval_op);
- op->op.flags = FSCACHE_OP_MYTHREAD | (1 << FSCACHE_OP_WAITING);
+ atomic_inc(&cookie->n_active);
+ op->op.flags = FSCACHE_OP_MYTHREAD |
+ (1UL << FSCACHE_OP_WAITING) |
+ (1UL << FSCACHE_OP_UNUSE_COOKIE);
op->mapping = mapping;
op->end_io_func = end_io_func;
op->context = context;
@@ -394,7 +400,8 @@ int __fscache_read_or_alloc_page(struct fscache_cookie *cookie,
if (fscache_wait_for_deferred_lookup(cookie) < 0)
return -ERESTARTSYS;

- op = fscache_alloc_retrieval(page->mapping, end_io_func, context);
+ op = fscache_alloc_retrieval(cookie, page->mapping,
+ end_io_func,context);
if (!op) {
_leave(" = -ENOMEM");
return -ENOMEM;
@@ -465,6 +472,7 @@ nobufs_unlock_dec:
atomic_dec(&object->n_reads);
nobufs_unlock:
spin_unlock(&cookie->lock);
+ atomic_dec(&cookie->n_active);
kfree(op);
nobufs:
fscache_stat(&fscache_n_retrievals_nobufs);
@@ -522,7 +530,7 @@ int __fscache_read_or_alloc_pages(struct fscache_cookie *cookie,
if (fscache_wait_for_deferred_lookup(cookie) < 0)
return -ERESTARTSYS;

- op = fscache_alloc_retrieval(mapping, end_io_func, context);
+ op = fscache_alloc_retrieval(cookie, mapping, end_io_func, context);
if (!op)
return -ENOMEM;
op->n_pages = *nr_pages;
@@ -589,6 +597,7 @@ nobufs_unlock_dec:
atomic_dec(&object->n_reads);
nobufs_unlock:
spin_unlock(&cookie->lock);
+ atomic_dec(&cookie->n_active);
kfree(op);
nobufs:
fscache_stat(&fscache_n_retrievals_nobufs);
@@ -631,7 +640,7 @@ int __fscache_alloc_page(struct fscache_cookie *cookie,
if (fscache_wait_for_deferred_lookup(cookie) < 0)
return -ERESTARTSYS;

- op = fscache_alloc_retrieval(page->mapping, NULL, NULL);
+ op = fscache_alloc_retrieval(cookie, page->mapping, NULL, NULL);
if (!op)
return -ENOMEM;
op->n_pages = 1;
@@ -675,6 +684,7 @@ error:

nobufs_unlock:
spin_unlock(&cookie->lock);
+ atomic_dec(&cookie->n_active);
kfree(op);
nobufs:
fscache_stat(&fscache_n_allocs_nobufs);
@@ -876,7 +886,9 @@ int __fscache_write_page(struct fscache_cookie *cookie,

fscache_operation_init(&op->op, fscache_write_op,
fscache_release_write_op);
- op->op.flags = FSCACHE_OP_ASYNC | (1 << FSCACHE_OP_WAITING);
+ op->op.flags = FSCACHE_OP_ASYNC |
+ (1 << FSCACHE_OP_WAITING) |
+ (1 << FSCACHE_OP_UNUSE_COOKIE);

ret = radix_tree_preload(gfp & ~__GFP_HIGHMEM);
if (ret < 0)
@@ -922,6 +934,7 @@ int __fscache_write_page(struct fscache_cookie *cookie,
op->op.debug_id = atomic_inc_return(&fscache_op_debug_id);
op->store_limit = object->store_limit;

+ atomic_inc(&cookie->n_active);
if (fscache_submit_op(object, &op->op) < 0)
goto submit_failed;

@@ -948,6 +961,7 @@ already_pending:
return 0;

submit_failed:
+ atomic_dec(&cookie->n_active);
spin_lock(&cookie->stores_lock);
radix_tree_delete(&cookie->stores, page->index);
spin_unlock(&cookie->stores_lock);
diff --git a/include/linux/fscache-cache.h b/include/linux/fscache-cache.h
index 9ff516b..e7802a5 100644
--- a/include/linux/fscache-cache.h
+++ b/include/linux/fscache-cache.h
@@ -97,7 +97,8 @@ struct fscache_operation {
#define FSCACHE_OP_WAITING 4 /* cleared when op is woken */
#define FSCACHE_OP_EXCLUSIVE 5 /* exclusive op, other ops must wait */
#define FSCACHE_OP_DEC_READ_CNT 6 /* decrement object->n_reads on destruction */
-#define FSCACHE_OP_KEEP_FLAGS 0x0070 /* flags to keep when repurposing an op */
+#define FSCACHE_OP_UNUSE_COOKIE 7 /* call fscache_unuse_cookie() on completion */
+#define FSCACHE_OP_KEEP_FLAGS 0x00f0 /* flags to keep when repurposing an op */

enum fscache_operation_state state;
atomic_t usage;
@@ -314,6 +315,7 @@ struct fscache_cache_ops {
struct fscache_cookie {
atomic_t usage; /* number of users of this cookie */
atomic_t n_children; /* number of children of this cookie */
+ atomic_t n_active; /* number of active users of netfs ptrs */
spinlock_t lock;
spinlock_t stores_lock; /* lock on page store tree */
struct hlist_head backing_objects; /* object(s) backing this file/index */
@@ -326,11 +328,11 @@ struct fscache_cookie {

unsigned long flags;
#define FSCACHE_COOKIE_LOOKING_UP 0 /* T if non-index cookie being looked up still */
-#define FSCACHE_COOKIE_CREATING 1 /* T if non-index object being created still */
-#define FSCACHE_COOKIE_NO_DATA_YET 2 /* T if new object with no cached data yet */
-#define FSCACHE_COOKIE_UNAVAILABLE 3 /* T if cookie is unavailable (error, etc) */
-#define FSCACHE_COOKIE_WAITING_ON_READS 4 /* T if cookie is waiting on reads */
-#define FSCACHE_COOKIE_INVALIDATING 5 /* T if cookie is being invalidated */
+#define FSCACHE_COOKIE_NO_DATA_YET 1 /* T if new object with no cached data yet */
+#define FSCACHE_COOKIE_UNAVAILABLE 2 /* T if cookie is unavailable (error, etc) */
+#define FSCACHE_COOKIE_INVALIDATING 3 /* T if cookie is being invalidated */
+#define FSCACHE_COOKIE_RELINQUISHED 4 /* T if cookie has been relinquished */
+#define FSCACHE_COOKIE_RETIRED 5 /* T if cookie was retired */
};

extern struct fscache_cookie fscache_fsdef_index;
@@ -392,10 +394,9 @@ struct fscache_object {
#define FSCACHE_OBJECT_LOCK 0 /* T if object is busy being processed */
#define FSCACHE_OBJECT_PENDING_WRITE 1 /* T if object has pending write */
#define FSCACHE_OBJECT_WAITING 2 /* T if object is waiting on its parent */
-#define FSCACHE_OBJECT_RETIRE 3 /* T if object should be retired */
-#define FSCACHE_OBJECT_IS_LIVE 4 /* T if object is not withdrawn or relinquished */
-#define FSCACHE_OBJECT_IS_LOOKED_UP 5 /* T if object has been looked up */
-#define FSCACHE_OBJECT_IS_AVAILABLE 6 /* T if object has become active */
+#define FSCACHE_OBJECT_IS_LIVE 3 /* T if object is not withdrawn or relinquished */
+#define FSCACHE_OBJECT_IS_LOOKED_UP 4 /* T if object has been looked up */
+#define FSCACHE_OBJECT_IS_AVAILABLE 5 /* T if object has become active */

struct list_head cache_link; /* link in cache->object_list */
struct hlist_node cookie_link; /* link in cookie->backing_objects */
@@ -415,16 +416,11 @@ struct fscache_object {

extern void fscache_object_init(struct fscache_object *, struct fscache_cookie *,
struct fscache_cache *);
+extern void fscache_object_destroy(struct fscache_object *);

extern void fscache_object_lookup_negative(struct fscache_object *object);
extern void fscache_obtained_object(struct fscache_object *object);

-#ifdef CONFIG_FSCACHE_OBJECT_LIST
-extern void fscache_object_destroy(struct fscache_object *object);
-#else
-#define fscache_object_destroy(object) do {} while(0)
-#endif
-
static inline bool fscache_object_is_live(struct fscache_object *object)
{
return test_bit(FSCACHE_OBJECT_IS_LIVE, &object->flags);
@@ -512,6 +508,35 @@ static inline void fscache_end_io(struct fscache_retrieval *op,
op->end_io_func(page, op->context, error);
}

+/**
+ * fscache_use_cookie - Request usage of cookie attached to an object
+ * @object: Object description
+ *
+ * Request usage of the cookie attached to an object. NULL is returned if the
+ * relinquishment had reduced the cookie usage count to 0.
+ */
+static inline bool fscache_use_cookie(struct fscache_object *object)
+{
+ struct fscache_cookie *cookie = object->cookie;
+ return atomic_inc_not_zero(&cookie->n_active) != 0;
+}
+
+/**
+ * fscache_unuse_cookie - Cease usage of cookie attached to an object
+ * @object: Object description
+ *
+ * Cease usage of the cookie attached to an object. When the users count
+ * reaches zero then the cookie relinquishment will be permitted to proceed.
+ */
+static inline void fscache_unuse_cookie(struct fscache_object *object)
+{
+ struct fscache_cookie *cookie = object->cookie;
+ if (atomic_dec_and_test(&cookie->n_active)) {
+ printk("*** Reduce to 0: %s\n", cookie->def->name);
+ wake_up_atomic_t(&cookie->n_active);
+ }
+}
+
/*
* out-of-line cache backend functions
*/

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/