The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH] writeback_inodes can race with unmount
@ 2004-06-08 19:22 Chris Mason
  2004-06-08 19:57 ` Mika Penttilä
  0 siblings, 1 reply; 7+ messages in thread
From: Chris Mason @ 2004-06-08 19:22 UTC (permalink / raw)
  To: akpm, linux-kernel

There's a small window where the filesystem can be unmounted during
writeback_inodes.  The end result is the iput done by sync_sb_inodes
could be done after the FS put_super and and the super has been removed
from all lists.

The fix is to hold the s_umount sem during sync_sb_inodes to make sure
the FS doesn't get unmounted.

Index: linux.t/fs/fs-writeback.c
===================================================================
--- linux.t.orig/fs/fs-writeback.c	2004-06-08 14:45:49.000000000 -0400
+++ linux.t/fs/fs-writeback.c	2004-06-08 14:47:58.000000000 -0400
@@ -360,9 +360,18 @@ restart:
 	sb = sb_entry(super_blocks.prev);
 	for (; sb != sb_entry(&super_blocks); sb = sb_entry(sb->s_list.prev)) {
 		if (!list_empty(&sb->s_dirty) || !list_empty(&sb->s_io)) {
+			/* we're making our own get_super here */
 			sb->s_count++;
 			spin_unlock(&sb_lock);
-			sync_sb_inodes(sb, wbc);
+			/* if we can't get the readlock, there's no sense in
+			 * waiting around, most of the time the FS is going
+			 * to be unmounted by the time it is released
+			 */
+			if (down_read_trylock(&sb->s_umount)) {
+				if (sb->s_root)
+					sync_sb_inodes(sb, wbc);
+				up_read(&sb->s_umount);
+			}
 			spin_lock(&sb_lock);
 			if (__put_super(sb))
 				goto restart;



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] writeback_inodes can race with unmount
  2004-06-08 19:22 [PATCH] writeback_inodes can race with unmount Chris Mason
@ 2004-06-08 19:57 ` Mika Penttilä
  2004-06-08 20:18   ` Chris Mason
  0 siblings, 1 reply; 7+ messages in thread
From: Mika Penttilä @ 2004-06-08 19:57 UTC (permalink / raw)
  To: Chris Mason; +Cc: akpm, linux-kernel



Chris Mason wrote:

>There's a small window where the filesystem can be unmounted during
>writeback_inodes.  The end result is the iput done by sync_sb_inodes
>could be done after the FS put_super and and the super has been removed
>from all lists.
>  
>

Why don't we have the same race in the sync() path as well? Moving the 
locking to sync_sb_inodes() itself would fix it also.

>The fix is to hold the s_umount sem during sync_sb_inodes to make sure
>the FS doesn't get unmounted.
>
>Index: linux.t/fs/fs-writeback.c
>===================================================================
>--- linux.t.orig/fs/fs-writeback.c	2004-06-08 14:45:49.000000000 -0400
>+++ linux.t/fs/fs-writeback.c	2004-06-08 14:47:58.000000000 -0400
>@@ -360,9 +360,18 @@ restart:
> 	sb = sb_entry(super_blocks.prev);
> 	for (; sb != sb_entry(&super_blocks); sb = sb_entry(sb->s_list.prev)) {
> 		if (!list_empty(&sb->s_dirty) || !list_empty(&sb->s_io)) {
>+			/* we're making our own get_super here */
> 			sb->s_count++;
> 			spin_unlock(&sb_lock);
>-			sync_sb_inodes(sb, wbc);
>+			/* if we can't get the readlock, there's no sense in
>+			 * waiting around, most of the time the FS is going
>+			 * to be unmounted by the time it is released
>+			 */
>+			if (down_read_trylock(&sb->s_umount)) {
>+				if (sb->s_root)
>+					sync_sb_inodes(sb, wbc);
>+				up_read(&sb->s_umount);
>+			}
> 			spin_lock(&sb_lock);
> 			if (__put_super(sb))
> 				goto restart;
>  
>

--Mika



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] writeback_inodes can race with unmount
  2004-06-08 19:57 ` Mika Penttilä
@ 2004-06-08 20:18   ` Chris Mason
  2004-06-08 21:56     ` Andrew Morton
  0 siblings, 1 reply; 7+ messages in thread
From: Chris Mason @ 2004-06-08 20:18 UTC (permalink / raw)
  To: Mika Penttilä; +Cc: akpm, linux-kernel

On Tue, 2004-06-08 at 15:57, Mika Penttilä wrote:
> Chris Mason wrote:
> 
> >There's a small window where the filesystem can be unmounted during
> >writeback_inodes.  The end result is the iput done by sync_sb_inodes
> >could be done after the FS put_super and and the super has been removed
> >from all lists.
> >  
> >
> 
> Why don't we have the same race in the sync() path as well? Moving the 
> locking to sync_sb_inodes() itself would fix it also.

In the sync() path we're already taking a read lock on s_umount sem. 
Moving the locking into sync_sb_inodes would be tricky, it is sometimes
called with the write lock on s_umount_sem held and sometimes with a
read lock.

-chris



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] writeback_inodes can race with unmount
  2004-06-08 20:18   ` Chris Mason
@ 2004-06-08 21:56     ` Andrew Morton
  2004-06-09  1:29       ` Chris Mason
  0 siblings, 1 reply; 7+ messages in thread
From: Andrew Morton @ 2004-06-08 21:56 UTC (permalink / raw)
  To: Chris Mason; +Cc: mika.penttila, linux-kernel

Chris Mason <mason@suse.com> wrote:
>
> On Tue, 2004-06-08 at 15:57, Mika Penttilä wrote:
> > Chris Mason wrote:
> > 
> > >There's a small window where the filesystem can be unmounted during
> > >writeback_inodes.  The end result is the iput done by sync_sb_inodes
> > >could be done after the FS put_super and and the super has been removed
> > >from all lists.
> > >  
> > >
> > 
> > Why don't we have the same race in the sync() path as well? Moving the 
> > locking to sync_sb_inodes() itself would fix it also.
> 
> In the sync() path we're already taking a read lock on s_umount sem. 
> Moving the locking into sync_sb_inodes would be tricky, it is sometimes
> called with the write lock on s_umount_sem held and sometimes with a
> read lock.
> 

Plus we'd be dead if we had to do the above.  If that read_trylock() fails
the sync() will forget to sync stuff.

And, contra your description, we'll fail the trylock relatively frequently
- when some other process is writing back this superblock.

But as this codepath is never used for data-integrity writeout things
should be OK.  I'll slip a comment in there and maybe a
BUG_ON(wbc->sync_mode == WB_SYNC_ALL).

You didn't tell us where the window is?  It's a bit weird that some other
process can come in and unmount the fs while there are non-zero-refcount
inodes floating about on the superblock.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] writeback_inodes can race with unmount
  2004-06-08 21:56     ` Andrew Morton
@ 2004-06-09  1:29       ` Chris Mason
  2004-06-09  3:22         ` Andrew Morton
  0 siblings, 1 reply; 7+ messages in thread
From: Chris Mason @ 2004-06-09  1:29 UTC (permalink / raw)
  To: Andrew Morton; +Cc: mika.penttila, linux-kernel

On Tue, 2004-06-08 at 17:56, Andrew Morton wrote:
> Chris Mason <mason@suse.com> wrote:

> > > Why don't we have the same race in the sync() path as well? Moving the 
> > > locking to sync_sb_inodes() itself would fix it also.
> > 
> > In the sync() path we're already taking a read lock on s_umount sem. 
> > Moving the locking into sync_sb_inodes would be tricky, it is sometimes
> > called with the write lock on s_umount_sem held and sometimes with a
> > read lock.
> > 
> 
> Plus we'd be dead if we had to do the above.  If that read_trylock() fails
> the sync() will forget to sync stuff.
> 
> And, contra your description, we'll fail the trylock relatively frequently
> - when some other process is writing back this superblock.

The write lock is taken much less frequently.  Should be just on
mount/unmount no?

> 
> But as this codepath is never used for data-integrity writeout things
> should be OK.  I'll slip a comment in there and maybe a
> BUG_ON(wbc->sync_mode == WB_SYNC_ALL).
> 
> You didn't tell us where the window is?  It's a bit weird that some other
> process can come in and unmount the fs while there are non-zero-refcount
> inodes floating about on the superblock.

It's the iput in sync_sb_inodes().  The test workload is 8 parallel runs
of this loop (each run to a different /dev/xxx)

while(true) ; do
    mkfs /dev/xxx
    mount /dev/xxx /xxx
    dd if=/dev/zero of=/xxx/foo bs=1MB count=80
    rm /xxx/foo
    umount /dev/xxx
done

So, each FS only has a single dirty inode, and that single inode gets
deleted during the run.  With enough memory pressure, write calls
frequently go into throttling, increasing the chance for sync_sb_inodes
to be called for writeback at the same time as unmount.

Right before the iput in sync_sb_inodes, we've dropped both the sb_lock
and the inode_lock, nothing prevents the unmount from continuing.  The
super block won't be freed because we've got it pinned, but the unmount
can proceed.

So, it looks like we get this:

CPU 0				CPU 1
writeback_inodes		generic_shutdown_super
sync_sb_inodes			
iget(inode)
spin_unlock(&inode_lock)
				sop->put_super(sb)
iput(inode)
generic_delete_inode()





^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] writeback_inodes can race with unmount
  2004-06-09  1:29       ` Chris Mason
@ 2004-06-09  3:22         ` Andrew Morton
  2004-06-09  5:24           ` Andrew Morton
  0 siblings, 1 reply; 7+ messages in thread
From: Andrew Morton @ 2004-06-09  3:22 UTC (permalink / raw)
  To: Chris Mason; +Cc: mika.penttila, linux-kernel

Chris Mason <mason@suse.com> wrote:
>
> On Tue, 2004-06-08 at 17:56, Andrew Morton wrote:
> > Chris Mason <mason@suse.com> wrote:
> 
> > > > Why don't we have the same race in the sync() path as well? Moving the 
> > > > locking to sync_sb_inodes() itself would fix it also.
> > > 
> > > In the sync() path we're already taking a read lock on s_umount sem. 
> > > Moving the locking into sync_sb_inodes would be tricky, it is sometimes
> > > called with the write lock on s_umount_sem held and sometimes with a
> > > read lock.
> > > 
> > 
> > Plus we'd be dead if we had to do the above.  If that read_trylock() fails
> > the sync() will forget to sync stuff.
> > 
> > And, contra your description, we'll fail the trylock relatively frequently
> > - when some other process is writing back this superblock.
> 
> The write lock is taken much less frequently.  Should be just on
> mount/unmount no?

OK.

> So, it looks like we get this:
> 
> CPU 0				CPU 1
> writeback_inodes		generic_shutdown_super
> sync_sb_inodes			
> iget(inode)
> spin_unlock(&inode_lock)
> 				sop->put_super(sb)
> iput(inode)
> generic_delete_inode()

We really shouldn't have got to ->put_super() when there are still live
inodes around.  I'd say that the problem lies on the umount path.  2.4
might have the same problem, but it'll be much harder to hit.

Something like this?  If so, we should probably lock the inode in
prune_icache() and remove iprune_sem.


--- 25/fs/inode.c~a	2004-06-08 20:06:41.905455400 -0700
+++ 25-akpm/fs/inode.c	2004-06-08 20:19:40.300121424 -0700
@@ -294,10 +294,11 @@ static void dispose_list(struct list_hea
 /*
  * Invalidate all inodes for a device.
  */
-static int invalidate_list(struct list_head *head, struct super_block * sb, struct list_head * dispose)
+static int invalidate_list(struct list_head *head, struct super_block *sb,
+				struct list_head *dispose)
 {
 	struct list_head *next;
-	int busy = 0, count = 0;
+	int ret = 0, count = 0;
 
 	next = head->next;
 	for (;;) {
@@ -311,6 +312,17 @@ static int invalidate_list(struct list_h
 		if (inode->i_sb != sb)
 			continue;
 		invalidate_inode_buffers(inode);
+		if (inode->i_state & I_LOCK) {
+			__iget(inode);
+			inodes_stat.nr_unused -= count;
+			count = 0;
+			spin_unlock(&inode_lock);
+			wait_on_inode(inode);
+			iput(inode);
+			ret = 2;
+			spin_lock(&inode_lock);
+			break;
+		}
 		if (!atomic_read(&inode->i_count)) {
 			hlist_del_init(&inode->i_hash);
 			list_move(&inode->i_list, dispose);
@@ -318,11 +330,11 @@ static int invalidate_list(struct list_h
 			count++;
 			continue;
 		}
-		busy = 1;
+		ret = 1;
 	}
 	/* only unused inodes may be cached with i_count zero */
 	inodes_stat.nr_unused -= count;
-	return busy;
+	return ret;
 }
 
 /*
@@ -343,21 +355,23 @@ static int invalidate_list(struct list_h
  */
 int invalidate_inodes(struct super_block * sb)
 {
-	int busy;
+	int state;
 	LIST_HEAD(throw_away);
 
 	down(&iprune_sem);
 	spin_lock(&inode_lock);
-	busy = invalidate_list(&inode_in_use, sb, &throw_away);
-	busy |= invalidate_list(&inode_unused, sb, &throw_away);
-	busy |= invalidate_list(&sb->s_dirty, sb, &throw_away);
-	busy |= invalidate_list(&sb->s_io, sb, &throw_away);
+	do {
+		state = invalidate_list(&inode_in_use, sb, &throw_away);
+		state |= invalidate_list(&inode_unused, sb, &throw_away);
+		state |= invalidate_list(&sb->s_dirty, sb, &throw_away);
+		state |= invalidate_list(&sb->s_io, sb, &throw_away);
+	} while (state & 2);
 	spin_unlock(&inode_lock);
 
 	dispose_list(&throw_away);
 	up(&iprune_sem);
 
-	return busy;
+	return state;
 }
 
 EXPORT_SYMBOL(invalidate_inodes);
_


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] writeback_inodes can race with unmount
  2004-06-09  3:22         ` Andrew Morton
@ 2004-06-09  5:24           ` Andrew Morton
  0 siblings, 0 replies; 7+ messages in thread
From: Andrew Morton @ 2004-06-09  5:24 UTC (permalink / raw)
  To: mason, mika.penttila, linux-kernel

Andrew Morton <akpm@osdl.org> wrote:
>
> Something like this?

No, that's not going to work, is it - we don't hold I_LOCK across the
critical iput().

May have to do it your way, but the trylock is irksome...

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2004-06-09  5:25 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2004-06-08 19:22 [PATCH] writeback_inodes can race with unmount Chris Mason
2004-06-08 19:57 ` Mika Penttilä
2004-06-08 20:18   ` Chris Mason
2004-06-08 21:56     ` Andrew Morton
2004-06-09  1:29       ` Chris Mason
2004-06-09  3:22         ` Andrew Morton
2004-06-09  5:24           ` Andrew Morton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox