raw_write_seqcount_begin(&dentry->d_seq);
__d_clear_type_and_inode(dentry);
hlist_del_init(&dentry->d_alias);
+ /*
+ * dentry becomes negative, so the space occupied by ->d_alias
+ * belongs to ->waiters now; we could use __hlist_del() instead
+ * of hlist_del_init(), if not for the stunt pulled by nfs
+ * dummy root dentries - positive dentry *not* included into
+ * the alias list of its inode. Open-coding hlist_del_init()
+ * and removing zeroing would be too clumsy...
+ */
+ dentry->waiters = NULL;
raw_write_seqcount_end(&dentry->d_seq);
spin_unlock(&dentry->d_lock);
spin_unlock(&inode->i_lock);
}
EXPORT_SYMBOL(d_drop);
+struct completion_list {
+ struct completion_list *next;
+ struct completion completion;
+};
+
+/*
+ * shrink_dcache_tree() needs to be notified when dentry in process of
+ * being evicted finally gets unlisted. Such dentries are
+ * already with negative ->d_count
+ * already negative
+ * already not in in-lookup hash
+ * reachable only via ->d_sib.
+ *
+ * Use ->waiters for a single-linked list of struct completion_list of
+ * waiters.
+ */
+static inline void d_add_waiter(struct dentry *dentry, struct completion_list *p)
+{
+ struct completion_list *v = dentry->waiters;
+ init_completion(&p->completion);
+ p->next = v;
+ dentry->waiters = p;
+}
+
+static inline void d_complete_waiters(struct dentry *dentry)
+{
+ struct completion_list *v = dentry->waiters;
+ if (unlikely(v)) {
+ /* some shrink_dcache_tree() instances are waiting */
+ dentry->waiters = NULL;
+ while (v) {
+ struct completion *r = &v->completion;
+ v = v->next;
+ complete(r);
+ }
+ }
+}
+
static inline void dentry_unlist(struct dentry *dentry)
{
struct dentry *next;
* attached to the dentry tree
*/
dentry->d_flags |= DCACHE_DENTRY_KILLED;
+ d_complete_waiters(dentry);
if (unlikely(hlist_unhashed(&dentry->d_sib)))
return;
__hlist_del(&dentry->d_sib);
return D_WALK_QUIT;
}
to_shrink_list(dentry, &data->dispose);
+ } else if (dentry->d_lockref.count < 0) {
+ rcu_read_lock();
+ data->victim = dentry;
+ return D_WALK_QUIT;
}
/*
* We can return to the caller if we have found some (this
data.victim = NULL;
d_walk(parent, &data, select_collect2);
if (data.victim) {
- spin_lock(&data.victim->d_lock);
- if (!lock_for_kill(data.victim)) {
- spin_unlock(&data.victim->d_lock);
+ struct dentry *v = data.victim;
+
+ spin_lock(&v->d_lock);
+ if (v->d_lockref.count < 0 &&
+ !(v->d_flags & DCACHE_DENTRY_KILLED)) {
+ struct completion_list wait;
+ // It's busy dying; have it notify us once
+ // it becomes invisible to d_walk().
+ d_add_waiter(v, &wait);
+ spin_unlock(&v->d_lock);
+ rcu_read_unlock();
+ if (!list_empty(&data.dispose))
+ shrink_dentry_list(&data.dispose);
+ wait_for_completion(&wait.completion);
+ continue;
+ }
+ if (!lock_for_kill(v)) {
+ spin_unlock(&v->d_lock);
rcu_read_unlock();
} else {
- shrink_kill(data.victim);
+ shrink_kill(v);
}
}
if (!list_empty(&data.dispose))
INIT_HLIST_BL_NODE(&dentry->d_hash);
INIT_LIST_HEAD(&dentry->d_lru);
INIT_HLIST_HEAD(&dentry->d_children);
- INIT_HLIST_NODE(&dentry->d_alias);
+ dentry->waiters = NULL;
INIT_HLIST_NODE(&dentry->d_sib);
if (dentry->d_op && dentry->d_op->d_init) {
d_wait = dentry->d_wait;
dentry->d_wait = NULL;
hlist_bl_unlock(b);
- INIT_HLIST_NODE(&dentry->d_alias);
+ dentry->waiters = NULL;
INIT_LIST_HEAD(&dentry->d_lru);
return d_wait;
}
#define d_lock d_lockref.lock
#define d_iname d_shortname.string
+struct completion_list;
struct dentry {
/* RCU lookup touched fields */
struct hlist_node d_sib; /* child of parent list */
struct hlist_head d_children; /* our children */
/*
- * d_alias and d_rcu can share memory
+ * the following members can share memory - their uses are
+ * mutually exclusive.
*/
union {
- struct hlist_node d_alias; /* inode alias list */
- struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
+ /* positives: inode alias list */
+ struct hlist_node d_alias;
+ /* in-lookup ones (all negative, live): hash chain */
+ struct hlist_bl_node d_in_lookup_hash;
+ /* killed ones: (already negative) used to schedule freeing */
struct rcu_head d_rcu;
+ /*
+ * live non-in-lookup negatives: used if shrink_dcache_tree()
+ * races with eviction by another thread and needs to wait for
+ * this dentry to get killed . Remains NULL for almost all
+ * negative dentries.
+ */
+ struct completion_list *waiters;
};
};