Skip to content

Commit fc1b1e1

Browse files
amir73ilgregkh
authored andcommitted
fsnotify: clear PARENT_WATCHED flags lazily
[ Upstream commit 172e422 ] In some setups directories can have many (usually negative) dentries. Hence __fsnotify_update_child_dentry_flags() function can take a significant amount of time. Since the bulk of this function happens under inode->i_lock this causes a significant contention on the lock when we remove the watch from the directory as the __fsnotify_update_child_dentry_flags() call from fsnotify_recalc_mask() races with __fsnotify_update_child_dentry_flags() calls from __fsnotify_parent() happening on children. This can lead upto softlockup reports reported by users. Fix the problem by calling fsnotify_update_children_dentry_flags() to set PARENT_WATCHED flags only when parent starts watching children. When parent stops watching children, clear false positive PARENT_WATCHED flags lazily in __fsnotify_parent() for each accessed child. Suggested-by: Jan Kara <[email protected]> Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Stephen Brennan <[email protected]> Signed-off-by: Jan Kara <[email protected]> Signed-off-by: Sasha Levin <[email protected]>
1 parent 3b9f2d9 commit fc1b1e1

File tree

4 files changed

+56
-17
lines changed

4 files changed

+56
-17
lines changed

fs/notify/fsnotify.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,13 @@ void fsnotify_sb_delete(struct super_block *sb)
103103
* parent cares. Thus when an event happens on a child it can quickly tell
104104
* if there is a need to find a parent and send the event to the parent.
105105
*/
106-
void __fsnotify_update_child_dentry_flags(struct inode *inode)
106+
void fsnotify_set_children_dentry_flags(struct inode *inode)
107107
{
108108
struct dentry *alias;
109-
int watched;
110109

111110
if (!S_ISDIR(inode->i_mode))
112111
return;
113112

114-
/* determine if the children should tell inode about their events */
115-
watched = fsnotify_inode_watches_children(inode);
116-
117113
spin_lock(&inode->i_lock);
118114
/* run all of the dentries associated with this inode. Since this is a
119115
* directory, there damn well better only be one item on this list */
@@ -129,17 +125,32 @@ void __fsnotify_update_child_dentry_flags(struct inode *inode)
129125
continue;
130126

131127
spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
132-
if (watched)
133-
child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED;
134-
else
135-
child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;
128+
child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED;
136129
spin_unlock(&child->d_lock);
137130
}
138131
spin_unlock(&alias->d_lock);
139132
}
140133
spin_unlock(&inode->i_lock);
141134
}
142135

136+
/*
137+
* Lazily clear false positive PARENT_WATCHED flag for child whose parent had
138+
* stopped watching children.
139+
*/
140+
static void fsnotify_clear_child_dentry_flag(struct inode *pinode,
141+
struct dentry *dentry)
142+
{
143+
spin_lock(&dentry->d_lock);
144+
/*
145+
* d_lock is a sufficient barrier to prevent observing a non-watched
146+
* parent state from before the fsnotify_set_children_dentry_flags()
147+
* or fsnotify_update_flags() call that had set PARENT_WATCHED.
148+
*/
149+
if (!fsnotify_inode_watches_children(pinode))
150+
dentry->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;
151+
spin_unlock(&dentry->d_lock);
152+
}
153+
143154
/* Are inode/sb/mount interested in parent and name info with this event? */
144155
static bool fsnotify_event_needs_parent(struct inode *inode, struct mount *mnt,
145156
__u32 mask)
@@ -208,7 +219,7 @@ int __fsnotify_parent(struct dentry *dentry, __u32 mask, const void *data,
208219
p_inode = parent->d_inode;
209220
p_mask = fsnotify_inode_watches_children(p_inode);
210221
if (unlikely(parent_watched && !p_mask))
211-
__fsnotify_update_child_dentry_flags(p_inode);
222+
fsnotify_clear_child_dentry_flag(p_inode, dentry);
212223

213224
/*
214225
* Include parent/name in notification either if some notification

fs/notify/fsnotify.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static inline void fsnotify_clear_marks_by_sb(struct super_block *sb)
7474
* update the dentry->d_flags of all of inode's children to indicate if inode cares
7575
* about events that happen to its children.
7676
*/
77-
extern void __fsnotify_update_child_dentry_flags(struct inode *inode);
77+
extern void fsnotify_set_children_dentry_flags(struct inode *inode);
7878

7979
extern struct kmem_cache *fsnotify_mark_connector_cachep;
8080

fs/notify/mark.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,24 @@ static void *__fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
176176
return fsnotify_update_iref(conn, want_iref);
177177
}
178178

179+
static bool fsnotify_conn_watches_children(
180+
struct fsnotify_mark_connector *conn)
181+
{
182+
if (conn->type != FSNOTIFY_OBJ_TYPE_INODE)
183+
return false;
184+
185+
return fsnotify_inode_watches_children(fsnotify_conn_inode(conn));
186+
}
187+
188+
static void fsnotify_conn_set_children_dentry_flags(
189+
struct fsnotify_mark_connector *conn)
190+
{
191+
if (conn->type != FSNOTIFY_OBJ_TYPE_INODE)
192+
return;
193+
194+
fsnotify_set_children_dentry_flags(fsnotify_conn_inode(conn));
195+
}
196+
179197
/*
180198
* Calculate mask of events for a list of marks. The caller must make sure
181199
* connector and connector->obj cannot disappear under us. Callers achieve
@@ -184,15 +202,23 @@ static void *__fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
184202
*/
185203
void fsnotify_recalc_mask(struct fsnotify_mark_connector *conn)
186204
{
205+
bool update_children;
206+
187207
if (!conn)
188208
return;
189209

190210
spin_lock(&conn->lock);
211+
update_children = !fsnotify_conn_watches_children(conn);
191212
__fsnotify_recalc_mask(conn);
213+
update_children &= fsnotify_conn_watches_children(conn);
192214
spin_unlock(&conn->lock);
193-
if (conn->type == FSNOTIFY_OBJ_TYPE_INODE)
194-
__fsnotify_update_child_dentry_flags(
195-
fsnotify_conn_inode(conn));
215+
/*
216+
* Set children's PARENT_WATCHED flags only if parent started watching.
217+
* When parent stops watching, we clear false positive PARENT_WATCHED
218+
* flags lazily in __fsnotify_parent().
219+
*/
220+
if (update_children)
221+
fsnotify_conn_set_children_dentry_flags(conn);
196222
}
197223

198224
/* Free all connectors queued for freeing once SRCU period ends */

include/linux/fsnotify_backend.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,12 +563,14 @@ static inline __u32 fsnotify_parent_needed_mask(__u32 mask)
563563

564564
static inline int fsnotify_inode_watches_children(struct inode *inode)
565565
{
566+
__u32 parent_mask = READ_ONCE(inode->i_fsnotify_mask);
567+
566568
/* FS_EVENT_ON_CHILD is set if the inode may care */
567-
if (!(inode->i_fsnotify_mask & FS_EVENT_ON_CHILD))
569+
if (!(parent_mask & FS_EVENT_ON_CHILD))
568570
return 0;
569571
/* this inode might care about child events, does it care about the
570572
* specific set of events that can happen on a child? */
571-
return inode->i_fsnotify_mask & FS_EVENTS_POSS_ON_CHILD;
573+
return parent_mask & FS_EVENTS_POSS_ON_CHILD;
572574
}
573575

574576
/*
@@ -582,7 +584,7 @@ static inline void fsnotify_update_flags(struct dentry *dentry)
582584
/*
583585
* Serialisation of setting PARENT_WATCHED on the dentries is provided
584586
* by d_lock. If inotify_inode_watched changes after we have taken
585-
* d_lock, the following __fsnotify_update_child_dentry_flags call will
587+
* d_lock, the following fsnotify_set_children_dentry_flags call will
586588
* find our entry, so it will spin until we complete here, and update
587589
* us with the new state.
588590
*/

0 commit comments

Comments
 (0)