Re: [PATCH v3 04/19] VFS: use wait_var_event for waiting in d_alloc_parallel()

Al Viro <[email protected]>
Newsgroups org.kernel.vger.linux-unionfs,org.kernel.vger.linux-efi,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-nfs
Message-ID <20260428033738.GV3518998@ZenIV>
On Mon, Apr 27, 2026 at 02:01:22PM +1000, NeilBrown wrote:
> From: NeilBrown <[email protected]>
> 
> d_alloc_parallel() currently requires a wait_queue_head to be passed in.
> This must have a life time which extends until the lookup is completed.
> 
> This makes it awkward to use and particularly make it hard to use in
> lookup_one_qstr_excl() which I hope to do in the future.
> 
> This patch changes d_alloc_parallel() to use wake_up_var_locked() to
> wake up waiters, and wait_var_event_spinlock() to wait.  dentry->d_lock
> is used for synchronisation as it is already held and the relevant
> times.
> 
> In most cases there will be no waiters so the wake_up_var_locked()
> call is a complete waste.  To optimise this a new ->d_flags flag is
> added: DCACHE_LOCK_WAITERS.  This is set whenever any thread prepares to
> wait for the dentry, and if it isn't set when DCACHE_PAR_LOOKUP is
> cleared, no wakeup is sent.
> (The name is deliberately generic as I plan to replace DCACHE_PAR_LOOKUP
> with more generic per-dentry locking in the future).
> 
> __d_lookup_unhash() now returns a bool rather than a wq.  This is true
> if DCACHE_LOCK_WAITERS was sent and is used to decide to send the wake
> up.  It would be easier to send the wakeup immediately when clearing
> DCACHE_LOCK_WAITERS, but then the waiter could wake a bit earlier and
> then spend time spinning on ->d_lock.  I don't know if that cost is
> interesting.

I definitely like the calling conventions change, so much that I'd be glad
to pick that one Right Fucking Now.  I'd probably make the store in
d_must_wait() conditional, though - ->d_flags and ->d_lock are in different
cachelines and there's no need to dirty both every time we are called.
IOW, have d_must_wait() do this:
	if (!d_in_lookup(dentry))
		return false;
	if (!(dentry->d_flags & DCACHE_LOCK_WAITER))
		dentry->d_flags |= DCACHE_LOCK_WAITER;
	return true;
Not sure if it would flow better with return values inverted (and
negation removed from wait_var_event_spinlock(), that is)...

>  static inline void end_dir_add(struct inode *dir, unsigned int n,
> -			       wait_queue_head_t *d_wait)
> +			       bool do_wake, struct dentry *de)
>  {
>  	smp_store_release(&dir->i_dir_seq, n + 2);
>  	preempt_enable_nested();
> -	if (wq_has_sleeper(d_wait))
> -		wake_up_all(d_wait);
> +	if (do_wake)
> +		wake_up_var_locked(&de->d_flags, &de->d_lock);
>  }

This calling conventions change, OTOH, I don't like at all.  I mean,
(dir, n, false, unused) vs. (dir, n, true, never_NULL) is seriously
asking to be reduced to (dir, n, NULL) vs. (dir, n, never_NULL).

> @@ -2800,29 +2793,29 @@ EXPORT_SYMBOL(d_alloc_parallel);
>   * - Retrieve and clear the waitqueue head in dentry
>   * - Return the waitqueue head

... not anymore.  The entire comment needs replacement, TBH - 2 lines of
3 are stale with that patch and remaining one is... ambiguous, since
there are *two* hashes around and "Unhash the dentry" usually refers to
the other one.

Something like

/*
 * Move dentry from in-lookup state to busy-negative one.
 *
 * From now on d_in_lookup(dentry) will return false and dentry is gone from
 * in-lookup hash.
 *
 * Anyone who had been waiting on it in d_alloc_parallel() is free to
 * proceed after that.  Note that waking such waiters up is left to
 * the callers; we might be called in write-side critical area for ->i_dir_seq,
 * and PREEMPT_RT kernels can't have that wakeup done in those.
 *
 * Returns whether there are waiters to be woken up.
 */

perhaps?
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.