Patchwork [03/10] fsnotify: use reference counting for groups

login
register
mail settings
Submitter Andy Whitcroft
Date April 16, 2012, 6:02 p.m.
Message ID <1334599360-15346-4-git-send-email-apw@canonical.com>
Download mbox | patch
Permalink /patch/152958/
State New
Headers show

Comments

Andy Whitcroft - April 16, 2012, 6:02 p.m.
From: Lino Sanfilippo <LinoSanfilippo@gmx.de>

Get a group ref for each mark that is added to the groups list and release that
ref when the mark is freed in fsnotify_put_mark().
We also use get a group reference for duplicated marks and for private event
data.
Now we dont free a group any more when the number of marks becomes 0 but when
the groups ref count does. Since this will only happen when all marks are removed
from a groups mark list, we dont have to set the groups number of marks to 1 at
group creation.

Beside clearing all marks in fsnotify_destroy_group() we do also flush the
groups event queue. This is since events may hold references to groups (due to
private event data) and we have to put those references first before we get a
chance to put the final ref, which will result in a call to
fsnotify_final_destroy_group().

Signed-off-by: Lino Sanfilippo <LinoSanfilippo@gmx.de>
Signed-off-by: Eric Paris <eparis@redhat.com>

(cherry-picked from commit 0f948ec4aff7eb629b08e7dd3d9761be17a1ae9e git://git.infradead.org/users/eparis/notify.git)
BugLink: http://bugs.launchpad.net/bugs/922906
Signed-off-by: Andy Whitcroft <apw@canonical.com>
---
 fs/notify/group.c                    |   28 ++++++++++------------------
 fs/notify/inotify/inotify_fsnotify.c |    2 ++
 fs/notify/inotify/inotify_user.c     |    1 +
 fs/notify/mark.c                     |   24 ++++++++++++++----------
 4 files changed, 27 insertions(+), 28 deletions(-)
Stefan Bader - April 16, 2012, 6:30 p.m.
On 16.04.2012 20:02, Andy Whitcroft wrote:
> From: Lino Sanfilippo <LinoSanfilippo@gmx.de>
> 
> Get a group ref for each mark that is added to the groups list and release that
> ref when the mark is freed in fsnotify_put_mark().
> We also use get a group reference for duplicated marks and for private event
> data.
> Now we dont free a group any more when the number of marks becomes 0 but when
> the groups ref count does. Since this will only happen when all marks are removed
> from a groups mark list, we dont have to set the groups number of marks to 1 at
> group creation.
> 
> Beside clearing all marks in fsnotify_destroy_group() we do also flush the
> groups event queue. This is since events may hold references to groups (due to
> private event data) and we have to put those references first before we get a
> chance to put the final ref, which will result in a call to
> fsnotify_final_destroy_group().
> 
> Signed-off-by: Lino Sanfilippo <LinoSanfilippo@gmx.de>
> Signed-off-by: Eric Paris <eparis@redhat.com>
> 
> (cherry-picked from commit 0f948ec4aff7eb629b08e7dd3d9761be17a1ae9e git://git.infradead.org/users/eparis/notify.git)
> BugLink: http://bugs.launchpad.net/bugs/922906
> Signed-off-by: Andy Whitcroft <apw@canonical.com>
> ---
>  fs/notify/group.c                    |   28 ++++++++++------------------
>  fs/notify/inotify/inotify_fsnotify.c |    2 ++
>  fs/notify/inotify/inotify_user.c     |    1 +
>  fs/notify/mark.c                     |   24 ++++++++++++++----------
>  4 files changed, 27 insertions(+), 28 deletions(-)
> 
> diff --git a/fs/notify/group.c b/fs/notify/group.c
> index 59a6b53..37569ec 100644
> --- a/fs/notify/group.c
> +++ b/fs/notify/group.c
> @@ -34,9 +34,6 @@
>   */
>  void fsnotify_final_destroy_group(struct fsnotify_group *group)
>  {
> -	/* clear the notification queue of all events */
> -	fsnotify_flush_notify(group);
> -
>  	if (group->ops->free_group_priv)
>  		group->ops->free_group_priv(group);
>  
> @@ -44,12 +41,10 @@ void fsnotify_final_destroy_group(struct fsnotify_group *group)
>  }
>  
>  /*
> - * Trying to get rid of a group.  We need to first get rid of any outstanding
> - * allocations and then free the group.  Remember that fsnotify_clear_marks_by_group
> - * could miss marks that are being freed by inode and those marks could still
> - * hold a reference to this group (via group->num_marks)  If we get into that
> - * situtation, the fsnotify_final_destroy_group will get called when that final
> - * mark is freed.
> + * Trying to get rid of a group. Remove all marks, flush all events and release
> + * the group reference.
> + * Note that another thread calling fsnotify_clear_marks_by_group() may still
> + * hold a ref to the group.
>   */
>  void fsnotify_destroy_group(struct fsnotify_group *group)
>  {
> @@ -58,9 +53,10 @@ void fsnotify_destroy_group(struct fsnotify_group *group)
>  
>  	synchronize_srcu(&fsnotify_mark_srcu);
>  
> -	/* past the point of no return, matches the initial value of 1 */
> -	if (atomic_dec_and_test(&group->num_marks))
> -		fsnotify_final_destroy_group(group);
> +	/* clear the notification queue of all events */
> +	fsnotify_flush_notify(group);
> +
> +	fsnotify_put_group(group);
>  }
>  
>  /*
> @@ -77,7 +73,7 @@ void fsnotify_get_group(struct fsnotify_group *group)
>  void fsnotify_put_group(struct fsnotify_group *group)
>  {
>  	if (atomic_dec_and_test(&group->refcnt))
> -		fsnotify_destroy_group(group);
> +		fsnotify_final_destroy_group(group);
>  }
>  EXPORT_SYMBOL(fsnotify_put_group);
>  
> @@ -94,11 +90,7 @@ struct fsnotify_group *fsnotify_alloc_group(const struct fsnotify_ops *ops)
>  
>  	/* set to 0 when there a no external references to this group */
>  	atomic_set(&group->refcnt, 1);
> -	/*
> -	 * hits 0 when there are no external references AND no marks for
> -	 * this group
> -	 */
> -	atomic_set(&group->num_marks, 1);
> +	atomic_set(&group->num_marks, 0);
>  
>  	mutex_init(&group->notification_mutex);
>  	INIT_LIST_HEAD(&group->notification_list);
> diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c
> index e3cbd74..74977fb 100644
> --- a/fs/notify/inotify/inotify_fsnotify.c
> +++ b/fs/notify/inotify/inotify_fsnotify.c
> @@ -118,6 +118,7 @@ static int inotify_handle_event(struct fsnotify_group *group,
>  
>  	fsn_event_priv = &event_priv->fsnotify_event_priv_data;
>  
> +	fsnotify_get_group(group);
>  	fsn_event_priv->group = group;
>  	event_priv->wd = wd;
>  
> @@ -210,6 +211,7 @@ void inotify_free_event_priv(struct fsnotify_event_private_data *fsn_event_priv)
>  	event_priv = container_of(fsn_event_priv, struct inotify_event_private_data,
>  				  fsnotify_event_priv_data);
>  
> +	fsnotify_put_group(fsn_event_priv->group);
>  	kmem_cache_free(event_priv_cachep, event_priv);
>  }
>  

> diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c
> index dbafbfc..246250f 100644
> --- a/fs/notify/inotify/inotify_user.c
> +++ b/fs/notify/inotify/inotify_user.c
> @@ -531,6 +531,7 @@ void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,
>  
>  	fsn_event_priv = &event_priv->fsnotify_event_priv_data;
>  
> +	fsnotify_get_group(group);
>  	fsn_event_priv->group = group;
>  	event_priv->wd = i_mark->wd;
>  

Want to check with the applied code tomorrow... Just looks a bit curious without
a matching put...

> diff --git a/fs/notify/mark.c b/fs/notify/mark.c
> index 54f36db..5b784aa 100644
> --- a/fs/notify/mark.c
> +++ b/fs/notify/mark.c
> @@ -109,8 +109,11 @@ void fsnotify_get_mark(struct fsnotify_mark *mark)
>  
>  void fsnotify_put_mark(struct fsnotify_mark *mark)
>  {
> -	if (atomic_dec_and_test(&mark->refcnt))
> +	if (atomic_dec_and_test(&mark->refcnt)) {
> +		if (mark->group)
> +			fsnotify_put_group(mark->group);
>  		mark->free_mark(mark);
> +	}
>  }
>  EXPORT_SYMBOL(fsnotify_put_mark);
>  
> @@ -126,12 +129,13 @@ void fsnotify_destroy_mark(struct fsnotify_mark *mark)
>  
>  	spin_lock(&mark->lock);
>  
> +	fsnotify_get_group(mark->group);
>  	group = mark->group;
>  
>  	/* something else already called this function on this mark */
>  	if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE)) {
>  		spin_unlock(&mark->lock);
> -		return;
> +		goto put_group;
>  	}
>  
>  	mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE;
> @@ -178,19 +182,15 @@ void fsnotify_destroy_mark(struct fsnotify_mark *mark)
>  
>  	if (inode && (mark->flags & FSNOTIFY_MARK_FLAG_OBJECT_PINNED))
>  		iput(inode);
> -
>  	/*
>  	 * We don't necessarily have a ref on mark from caller so the above iput
>  	 * may have already destroyed it.  Don't touch from now on.
>  	 */
>  
> -	/*
> -	 * it's possible that this group tried to destroy itself, but this
> -	 * this mark was simultaneously being freed by inode.  If that's the
> -	 * case, we finish freeing the group here.
> -	 */
> -	if (unlikely(atomic_dec_and_test(&group->num_marks)))
> -		fsnotify_final_destroy_group(group);
> +	atomic_dec(&group->num_marks);
> +
> +put_group:
> +	fsnotify_put_group(group);
>  }
>  EXPORT_SYMBOL(fsnotify_destroy_mark);
>  
> @@ -236,6 +236,7 @@ int fsnotify_add_mark(struct fsnotify_mark *mark,
>  
>  	mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE;
>  
> +	fsnotify_get_group(group);
>  	mark->group = group;
>  	list_add(&mark->g_list, &group->marks_list);
>  	atomic_inc(&group->num_marks);
> @@ -267,6 +268,7 @@ int fsnotify_add_mark(struct fsnotify_mark *mark,
>  err:
>  	mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE;
>  	list_del_init(&mark->g_list);
> +	fsnotify_put_group(group);
>  	mark->group = NULL;
>  	atomic_dec(&group->num_marks);
>  
> @@ -320,6 +322,8 @@ void fsnotify_duplicate_mark(struct fsnotify_mark *new, struct fsnotify_mark *ol
>  	assert_spin_locked(&old->lock);
>  	new->i.inode = old->i.inode;
>  	new->m.mnt = old->m.mnt;
> +	if (old->group)
> +		fsnotify_get_group(old->group);
>  	new->group = old->group;
>  	new->mask = old->mask;
>  	new->free_mark = old->free_mark;

Patch

diff --git a/fs/notify/group.c b/fs/notify/group.c
index 59a6b53..37569ec 100644
--- a/fs/notify/group.c
+++ b/fs/notify/group.c
@@ -34,9 +34,6 @@ 
  */
 void fsnotify_final_destroy_group(struct fsnotify_group *group)
 {
-	/* clear the notification queue of all events */
-	fsnotify_flush_notify(group);
-
 	if (group->ops->free_group_priv)
 		group->ops->free_group_priv(group);
 
@@ -44,12 +41,10 @@  void fsnotify_final_destroy_group(struct fsnotify_group *group)
 }
 
 /*
- * Trying to get rid of a group.  We need to first get rid of any outstanding
- * allocations and then free the group.  Remember that fsnotify_clear_marks_by_group
- * could miss marks that are being freed by inode and those marks could still
- * hold a reference to this group (via group->num_marks)  If we get into that
- * situtation, the fsnotify_final_destroy_group will get called when that final
- * mark is freed.
+ * Trying to get rid of a group. Remove all marks, flush all events and release
+ * the group reference.
+ * Note that another thread calling fsnotify_clear_marks_by_group() may still
+ * hold a ref to the group.
  */
 void fsnotify_destroy_group(struct fsnotify_group *group)
 {
@@ -58,9 +53,10 @@  void fsnotify_destroy_group(struct fsnotify_group *group)
 
 	synchronize_srcu(&fsnotify_mark_srcu);
 
-	/* past the point of no return, matches the initial value of 1 */
-	if (atomic_dec_and_test(&group->num_marks))
-		fsnotify_final_destroy_group(group);
+	/* clear the notification queue of all events */
+	fsnotify_flush_notify(group);
+
+	fsnotify_put_group(group);
 }
 
 /*
@@ -77,7 +73,7 @@  void fsnotify_get_group(struct fsnotify_group *group)
 void fsnotify_put_group(struct fsnotify_group *group)
 {
 	if (atomic_dec_and_test(&group->refcnt))
-		fsnotify_destroy_group(group);
+		fsnotify_final_destroy_group(group);
 }
 EXPORT_SYMBOL(fsnotify_put_group);
 
@@ -94,11 +90,7 @@  struct fsnotify_group *fsnotify_alloc_group(const struct fsnotify_ops *ops)
 
 	/* set to 0 when there a no external references to this group */
 	atomic_set(&group->refcnt, 1);
-	/*
-	 * hits 0 when there are no external references AND no marks for
-	 * this group
-	 */
-	atomic_set(&group->num_marks, 1);
+	atomic_set(&group->num_marks, 0);
 
 	mutex_init(&group->notification_mutex);
 	INIT_LIST_HEAD(&group->notification_list);
diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c
index e3cbd74..74977fb 100644
--- a/fs/notify/inotify/inotify_fsnotify.c
+++ b/fs/notify/inotify/inotify_fsnotify.c
@@ -118,6 +118,7 @@  static int inotify_handle_event(struct fsnotify_group *group,
 
 	fsn_event_priv = &event_priv->fsnotify_event_priv_data;
 
+	fsnotify_get_group(group);
 	fsn_event_priv->group = group;
 	event_priv->wd = wd;
 
@@ -210,6 +211,7 @@  void inotify_free_event_priv(struct fsnotify_event_private_data *fsn_event_priv)
 	event_priv = container_of(fsn_event_priv, struct inotify_event_private_data,
 				  fsnotify_event_priv_data);
 
+	fsnotify_put_group(fsn_event_priv->group);
 	kmem_cache_free(event_priv_cachep, event_priv);
 }
 
diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c
index dbafbfc..246250f 100644
--- a/fs/notify/inotify/inotify_user.c
+++ b/fs/notify/inotify/inotify_user.c
@@ -531,6 +531,7 @@  void inotify_ignored_and_remove_idr(struct fsnotify_mark *fsn_mark,
 
 	fsn_event_priv = &event_priv->fsnotify_event_priv_data;
 
+	fsnotify_get_group(group);
 	fsn_event_priv->group = group;
 	event_priv->wd = i_mark->wd;
 
diff --git a/fs/notify/mark.c b/fs/notify/mark.c
index 54f36db..5b784aa 100644
--- a/fs/notify/mark.c
+++ b/fs/notify/mark.c
@@ -109,8 +109,11 @@  void fsnotify_get_mark(struct fsnotify_mark *mark)
 
 void fsnotify_put_mark(struct fsnotify_mark *mark)
 {
-	if (atomic_dec_and_test(&mark->refcnt))
+	if (atomic_dec_and_test(&mark->refcnt)) {
+		if (mark->group)
+			fsnotify_put_group(mark->group);
 		mark->free_mark(mark);
+	}
 }
 EXPORT_SYMBOL(fsnotify_put_mark);
 
@@ -126,12 +129,13 @@  void fsnotify_destroy_mark(struct fsnotify_mark *mark)
 
 	spin_lock(&mark->lock);
 
+	fsnotify_get_group(mark->group);
 	group = mark->group;
 
 	/* something else already called this function on this mark */
 	if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE)) {
 		spin_unlock(&mark->lock);
-		return;
+		goto put_group;
 	}
 
 	mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE;
@@ -178,19 +182,15 @@  void fsnotify_destroy_mark(struct fsnotify_mark *mark)
 
 	if (inode && (mark->flags & FSNOTIFY_MARK_FLAG_OBJECT_PINNED))
 		iput(inode);
-
 	/*
 	 * We don't necessarily have a ref on mark from caller so the above iput
 	 * may have already destroyed it.  Don't touch from now on.
 	 */
 
-	/*
-	 * it's possible that this group tried to destroy itself, but this
-	 * this mark was simultaneously being freed by inode.  If that's the
-	 * case, we finish freeing the group here.
-	 */
-	if (unlikely(atomic_dec_and_test(&group->num_marks)))
-		fsnotify_final_destroy_group(group);
+	atomic_dec(&group->num_marks);
+
+put_group:
+	fsnotify_put_group(group);
 }
 EXPORT_SYMBOL(fsnotify_destroy_mark);
 
@@ -236,6 +236,7 @@  int fsnotify_add_mark(struct fsnotify_mark *mark,
 
 	mark->flags |= FSNOTIFY_MARK_FLAG_ALIVE;
 
+	fsnotify_get_group(group);
 	mark->group = group;
 	list_add(&mark->g_list, &group->marks_list);
 	atomic_inc(&group->num_marks);
@@ -267,6 +268,7 @@  int fsnotify_add_mark(struct fsnotify_mark *mark,
 err:
 	mark->flags &= ~FSNOTIFY_MARK_FLAG_ALIVE;
 	list_del_init(&mark->g_list);
+	fsnotify_put_group(group);
 	mark->group = NULL;
 	atomic_dec(&group->num_marks);
 
@@ -320,6 +322,8 @@  void fsnotify_duplicate_mark(struct fsnotify_mark *new, struct fsnotify_mark *ol
 	assert_spin_locked(&old->lock);
 	new->i.inode = old->i.inode;
 	new->m.mnt = old->m.mnt;
+	if (old->group)
+		fsnotify_get_group(old->group);
 	new->group = old->group;
 	new->mask = old->mask;
 	new->free_mark = old->free_mark;