From: Stephen Brennan <stephen.s.brennan@oracle.com>
To: Amir Goldstein <amir73il@gmail.com>
Cc: Jan Kara <jack@suse.cz>,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
Al Viro <viro@zeniv.linux.org.uk>
Subject: Re: [PATCH v3 3/3] fsnotify: allow sleepable child flag update
Date: Tue, 01 Nov 2022 14:25:50 -0700 [thread overview]
Message-ID: <874jvigye9.fsf@oracle.com> (raw)
In-Reply-To: <CAOQ4uxh8c2vbv50p8+rNnoV0H=L=+XRGuFP1dmGrrCrt6EjFYQ@mail.gmail.com>
Amir Goldstein <amir73il@gmail.com> writes:
> On Fri, Oct 28, 2022 at 3:10 AM Stephen Brennan
> <stephen.s.brennan@oracle.com> wrote:
>>
>> With very large d_subdirs lists, iteration can take a long time. Since
>> iteration needs to hold parent->d_lock, this can trigger soft lockups.
>> It would be best to make this iteration sleepable. Since we have the
>> inode locked exclusive, we can drop the parent->d_lock and sleep,
>> holding a reference to a child dentry, and continue iteration once we
>> wake.
>>
>> Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
>> ---
>>
>
> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
>
> some comment nits and one fortify suggestion
>
>> Notes:
>> v3:
>> - removed if statements around dput()
>> v2:
>> - added a check for child->d_parent != alias and retry logic
>>
>> fs/notify/fsnotify.c | 36 ++++++++++++++++++++++++++++++++----
>> 1 file changed, 32 insertions(+), 4 deletions(-)
>>
>> diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
>> index ccb8a3a6c522..34e5d18235a7 100644
>> --- a/fs/notify/fsnotify.c
>> +++ b/fs/notify/fsnotify.c
>> @@ -102,10 +102,12 @@ void fsnotify_sb_delete(struct super_block *sb)
>> * on a child we run all of our children and set a dentry flag saying that the
>> * parent cares. Thus when an event happens on a child it can quickly tell
>> * if there is a need to find a parent and send the event to the parent.
>> + *
>> + * Context: inode locked exclusive
>> */
>> static bool __fsnotify_update_children_dentry_flags(struct inode *inode)
>> {
>> - struct dentry *alias, *child;
>> + struct dentry *child, *alias, *last_ref = NULL;
>> int watched;
>>
>> if (!S_ISDIR(inode->i_mode))
>> @@ -120,12 +122,37 @@ static bool __fsnotify_update_children_dentry_flags(struct inode *inode)
>> alias = d_find_any_alias(inode);
>>
>> /*
>> - * run all of the children of the original inode and fix their
>> - * d_flags to indicate parental interest (their parent is the
>> - * original inode)
>> + * These lists can get very long, so we may need to sleep during
>> + * iteration. Normally this would be impossible without a cursor,
>> + * but since we have the inode locked exclusive, we're guaranteed
>> + * that the directory won't be modified, so whichever dentry we
>> + * pick to sleep on won't get moved. So, start a manual iteration
>> + * over d_subdirs which will allow us to sleep.
>> */
>> spin_lock(&alias->d_lock);
>> +retry:
>> list_for_each_entry(child, &alias->d_subdirs, d_child) {
>> + if (need_resched()) {
>> + /*
>> + * We need to hold a reference while we sleep. But when
>> + * we wake, dput() could free the dentry, invalidating
>> + * the list pointers. We can't look at the list pointers
>> + * until we re-lock the parent, and we can't dput() once
>> + * we have the parent locked. So the solution is to hold
>> + * onto our reference and free it the *next* time we drop
>> + * alias->d_lock: either at the end of the function, or
>> + * at the time of the next sleep.
>> + */
>
> My personal preference would be to move this above if (needed_reschd())
> it is not any less clear when this comment is above the condition
> and less indented will read nicer.
Definitely.
>> + dget(child);
>> + spin_unlock(&alias->d_lock);
>> + dput(last_ref);
>> + last_ref = child;
>> + cond_resched();
>> + spin_lock(&alias->d_lock);
>> + if (child->d_parent != alias)
>> + goto retry;
>
> Is this expected? If not, then we need a WARN_ON_ONCE().
> Also, I wonder if it would be better to break out and leave
> dentry flags as they are instead of risking some endless
> or very long retry loop?
>
> And how about asserting on unexpected !list_empty(&child->d_child)
> to avoid an endless loop in list_for_each_entry()?
I was trying to think this through as I prepared v3, and ended I up
leaving it as-is, out of a hope that it was doing something helpful. But
I'm pretty sure Al would ask why I believe that this could happen (e.g.
what scenario am I guarding against happening?). I didn't have a clear
idea of what I was guarding against here when I wrote it, which is my
fault. What's necessary is an audit of the places where d_child is
modified, so we can understand the risky places.
What we're doing here is dropping parent->d_lock and going to sleep,
while still having parent->d_inode->i_rwsem held in write mode. The risk
we have is that this particular dentry is removed from this list while
we slept. Checking d_parent seems particularly unhelpful for that, since
what we care far more about is the d_child list pointers. I did a code
audit of all locations where d_child is modified. Here's all I could
identify:
1. dentry_unlist() in fs/dcache.c
2. __d_move() in fs/dcache.c
3. Initialization code in d_alloc() family
4. Cursor code in fs/libfs.c for dcache_readdir()
For case #1, dentry_unlist() is a helper of dentry_kill(). The real guts
of dentry_kill() must happen with parent->d_lock held. Since we get a
reference to the child with parent->d_lock held, we can be confident
that any dentry_kill() which could have been happening concurrently will
bail out after seeing that we got a reference. At least, that's my
reading of the code.
For case #2, __d_move() can (should?) NOT happen, because the caller
must hold the i_mutex (read i_rwsem in write mode), but we are holding
that ourselves.
For case #3, it doesn't matter... it's initialization time, and we
couldn't have gotten a reference to the dentry until the dentry is put
into the tree.
For case #4, the simple_readdir() code should hold the inode rwsem in
read mode, so we have mutual exclusion from this code too. While it's
possible that we could go to sleep holding a reference to a cursor, I
believe that the cursor could not move without holding i_rwsem in read
mode. So we could never wake up to find we've skipped part of the list.
So to summarize, I don't think there's a case where we could actually
expect that d_child pointers get updated while we sleep with a reference
held and the parent i_rwsem held exclusive. Consequently, no place where
d_parent would change from under our feet. So I will remove this check
form the patch.
Thanks,
Stephen
next prev parent reply other threads:[~2022-11-01 21:26 UTC|newest]
Thread overview: 68+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-10-13 22:27 [RFC] fsnotify: allow sleepable child dentry flag update Stephen Brennan
2022-10-13 23:51 ` Al Viro
2022-11-01 21:47 ` Stephen Brennan
2022-10-14 8:01 ` Amir Goldstein
2022-10-17 7:59 ` Stephen Brennan
2022-10-17 11:44 ` Amir Goldstein
2022-10-17 16:59 ` Stephen Brennan
2022-10-17 17:42 ` Amir Goldstein
2022-10-17 9:09 ` Jan Kara
2022-10-18 4:12 ` [PATCH 0/2] fsnotify: fix softlockups iterating over d_subdirs Stephen Brennan
2022-10-18 4:12 ` [PATCH 1/2] fsnotify: Protect i_fsnotify_mask and child flags with inode rwsem Stephen Brennan
2022-10-18 7:39 ` Amir Goldstein
2022-10-21 0:33 ` Stephen Brennan
2022-10-21 7:22 ` Amir Goldstein
2022-10-18 4:12 ` [PATCH 2/2] fsnotify: allow sleepable child flag update Stephen Brennan
2022-10-18 5:36 ` Amir Goldstein
2022-10-27 7:50 ` kernel test robot
2022-10-27 8:44 ` Yujie Liu
2022-10-27 22:12 ` Stephen Brennan
2022-10-18 8:07 ` [PATCH 0/2] fsnotify: fix softlockups iterating over d_subdirs Amir Goldstein
2022-10-18 23:52 ` Stephen Brennan
2022-10-19 5:33 ` Amir Goldstein
2022-10-27 22:06 ` Stephen Brennan
2022-10-28 8:58 ` Amir Goldstein
2022-10-21 1:03 ` [PATCH v2 0/3] " Stephen Brennan
2022-10-21 1:03 ` [PATCH v2 1/3] fsnotify: Use d_find_any_alias to get dentry associated with inode Stephen Brennan
2022-10-21 9:25 ` Amir Goldstein
2022-10-21 1:03 ` [PATCH v2 2/3] fsnotify: Protect i_fsnotify_mask and child flags with inode rwsem Stephen Brennan
2022-10-21 4:01 ` kernel test robot
2022-10-21 8:22 ` Amir Goldstein
2022-10-21 9:18 ` Amir Goldstein
2022-10-25 18:02 ` Stephen Brennan
2022-10-26 5:41 ` Amir Goldstein
2022-10-21 9:17 ` Christian Brauner
2022-10-21 9:21 ` Amir Goldstein
2022-10-21 1:03 ` [PATCH v2 3/3] fsnotify: allow sleepable child flag update Stephen Brennan
2022-10-28 0:10 ` [PATCH v3 0/3] fsnotify: fix softlockups iterating over d_subdirs Stephen Brennan
2022-10-28 0:10 ` [PATCH v3 1/3] fsnotify: Use d_find_any_alias to get dentry associated with inode Stephen Brennan
2022-11-10 1:12 ` Stephen Brennan
2022-10-28 0:10 ` [PATCH v3 2/3] fsnotify: Protect i_fsnotify_mask and child flags with inode rwsem Stephen Brennan
2022-10-28 9:11 ` Amir Goldstein
2022-11-10 0:03 ` kernel test robot
2022-11-10 1:06 ` Stephen Brennan
2022-10-28 0:10 ` [PATCH v3 3/3] fsnotify: allow sleepable child flag update Stephen Brennan
2022-10-28 9:32 ` Amir Goldstein
2022-11-01 21:25 ` Stephen Brennan [this message]
2022-11-01 17:51 ` [PATCH v3 0/3] fsnotify: fix softlockups iterating over d_subdirs Jan Kara
2022-11-01 20:48 ` Stephen Brennan
2022-11-02 8:55 ` Amir Goldstein
2022-11-10 20:04 ` Stephen Brennan
[not found] ` <CAOQ4uxjRVRjTNJ-2CSX9QwLVC9oQN9r4GHqCn=XZrisZo6DN2w@mail.gmail.com>
[not found] ` <87eduafg6d.fsf@oracle.com>
2022-11-11 7:56 ` Amir Goldstein
2022-11-02 17:52 ` Jan Kara
2022-11-04 23:33 ` Stephen Brennan
2022-11-07 11:56 ` Jan Kara
2022-11-11 22:06 ` [PATCH v4 0/5] " Stephen Brennan
2022-11-11 22:06 ` [PATCH v4 1/5] fsnotify: clear PARENT_WATCHED flags lazily Stephen Brennan
2022-11-11 22:06 ` [PATCH v4 2/5] fsnotify: Use d_find_any_alias to get dentry associated with inode Stephen Brennan
2022-11-12 8:53 ` Amir Goldstein
2022-11-11 22:06 ` [PATCH v4 3/5] dnotify: move fsnotify_recalc_mask() outside spinlock Stephen Brennan
2022-11-12 9:06 ` Amir Goldstein
2022-11-11 22:06 ` [PATCH v4 4/5] fsnotify: allow sleepable child flag update Stephen Brennan
2022-11-12 10:00 ` Amir Goldstein
2022-11-15 7:10 ` kernel test robot
2022-11-11 22:06 ` [PATCH v4 5/5] fsnotify: require inode lock held during " Stephen Brennan
2022-11-12 9:42 ` Amir Goldstein
2022-11-11 22:08 ` [PATCH v4 0/5] fsnotify: fix softlockups iterating over d_subdirs Stephen Brennan
2022-11-22 11:50 ` Jan Kara
2022-11-22 14:03 ` Amir Goldstein
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=874jvigye9.fsf@oracle.com \
--to=stephen.s.brennan@oracle.com \
--cc=amir73il@gmail.com \
--cc=jack@suse.cz \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).