On Thu, Jun 24, 2010 at 10:26:37AM -0700, John Stultz wrote: > On Thu, 2010-06-24 at 13:02 +1000, npiggin@xxxxxxx wrote: > > plain text document attachment (fs-dcache_lock-multi-step.patch) > > The remaining usages for dcache_lock is to allow atomic, multi-step read-side > > operations over the directory tree by excluding modifications to the tree. > > Also, to walk in the leaf->root direction in the tree where we don't have > > a natural d_lock ordering. > > > > This could be accomplished by taking every d_lock, but this would mean a > > huge number of locks and actually gets very tricky. > > > > Solve this instead by using the rename seqlock for multi-step read-side > > operations. Insert operations are not serialised. Delete operations are > > tricky when walking up the directory our parent might have been deleted > > when dropping locks so also need to check and retry for that. > > > > XXX: hmm, we could of course just take the rename lock if there is any worry > > about livelock. Most of these are slow paths. > > I'll try to point out exactly the spot I think we were hitting in the > -rt tree (once the dcache_lock is removed). > > > > @@ -1030,9 +1056,15 @@ EXPORT_SYMBOL(have_submounts); > > */ > > static int select_parent(struct dentry * parent) > > { > > - struct dentry *this_parent = parent; > > + struct dentry *this_parent; > > struct list_head *next; > > - int found = 0; > > + unsigned seq; > > + int found; > > + > > +rename_retry: > > + found = 0; > > + this_parent = parent; > > + seq = read_seqbegin(&rename_lock); > > > > spin_lock(&dcache_lock); > > spin_lock(&this_parent->d_lock); > > @@ -1043,7 +1075,6 @@ resume: > > struct list_head *tmp = next; > > struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); > > next = tmp->next; > > - BUG_ON(this_parent == dentry); > > > > spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); > > dentry_lru_del_init(dentry); > > @@ -1084,17 +1115,33 @@ resume: > > */ > > if (this_parent != parent) { > > struct dentry *tmp; > > - next = this_parent->d_u.d_child.next; > > + struct dentry *child; > > + > > tmp = this_parent->d_parent; > > + rcu_read_lock(); > > spin_unlock(&this_parent->d_lock); > > - BUG_ON(tmp == this_parent); > > + child = this_parent; > > this_parent = tmp; > > Ok. So right here, we get preempted, or dput() is called by another cpu > on the child dentry, or the child->d_u.d_child.next dentry and its > d_kill'ed. > > > spin_lock(&this_parent->d_lock); > > + /* might go back up the wrong parent if we have had a rename > > + * or deletion */ > > + if (this_parent != child->d_parent || > > + // d_unlinked(this_parent) || XXX > > + read_seqretry(&rename_lock, seq)) { > > + spin_unlock(&this_parent->d_lock); > > + spin_unlock(&dcache_lock); > > + rcu_read_unlock(); > > + goto rename_retry; > > + } > > + rcu_read_unlock(); > > + next = child->d_u.d_child.next; > > Then at this point, next may point to junk. But see the test above it. We ensure that child->d_parent still points to this_parent with this_parent d_lock held. Oh, I'm not clearing d_parent! d_kill() should have dentry->d_parent = NULL; when it removes dentry from the list. That should fix it I'd hope. Thanks, Nick -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html