diff options
author | Hugh Dickins <hughd@google.com> | 2011-05-11 18:13:38 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-05-11 21:50:45 -0400 |
commit | 59a16ead572330deb38e5848151d30ed1af754bc (patch) | |
tree | 792b1f35e11e0f49170b0d306d45706de54afeae /mm | |
parent | 778dd893ae785c5fd505dac30b5fc40aae188bf1 (diff) |
tmpfs: fix spurious ENOSPC when racing with unswap
Testing the shmem_swaplist replacements for igrab() revealed another bug:
writes to /dev/loop0 on a tmpfs file which fills its filesystem were
sometimes failing with "Buffer I/O error"s.
These came from ENOSPC failures of shmem_getpage(), when racing with
swapoff: the same could happen when racing with another shmem_getpage(),
pulling the page in from swap in between our find_lock_page() and our
taking the info->lock (though not in the single-threaded loop case).
This is unacceptable, and surprising that I've not noticed it before:
it dates back many years, but (presumably) was made a lot easier to
reproduce in 2.6.36, which sited a page preallocation in the race window.
Fix it by rechecking the page cache before settling on an ENOSPC error.
Signed-off-by: Hugh Dickins <hughd@google.com>
Cc: Konstantin Khlebnikov <khlebnikov@openvz.org>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm')
-rw-r--r-- | mm/shmem.c | 32 |
1 files changed, 22 insertions, 10 deletions
diff --git a/mm/shmem.c b/mm/shmem.c index dc17551d060a..9e755c166cc5 100644 --- a/mm/shmem.c +++ b/mm/shmem.c | |||
@@ -1407,20 +1407,14 @@ repeat: | |||
1407 | if (sbinfo->max_blocks) { | 1407 | if (sbinfo->max_blocks) { |
1408 | if (percpu_counter_compare(&sbinfo->used_blocks, | 1408 | if (percpu_counter_compare(&sbinfo->used_blocks, |
1409 | sbinfo->max_blocks) >= 0 || | 1409 | sbinfo->max_blocks) >= 0 || |
1410 | shmem_acct_block(info->flags)) { | 1410 | shmem_acct_block(info->flags)) |
1411 | spin_unlock(&info->lock); | 1411 | goto nospace; |
1412 | error = -ENOSPC; | ||
1413 | goto failed; | ||
1414 | } | ||
1415 | percpu_counter_inc(&sbinfo->used_blocks); | 1412 | percpu_counter_inc(&sbinfo->used_blocks); |
1416 | spin_lock(&inode->i_lock); | 1413 | spin_lock(&inode->i_lock); |
1417 | inode->i_blocks += BLOCKS_PER_PAGE; | 1414 | inode->i_blocks += BLOCKS_PER_PAGE; |
1418 | spin_unlock(&inode->i_lock); | 1415 | spin_unlock(&inode->i_lock); |
1419 | } else if (shmem_acct_block(info->flags)) { | 1416 | } else if (shmem_acct_block(info->flags)) |
1420 | spin_unlock(&info->lock); | 1417 | goto nospace; |
1421 | error = -ENOSPC; | ||
1422 | goto failed; | ||
1423 | } | ||
1424 | 1418 | ||
1425 | if (!filepage) { | 1419 | if (!filepage) { |
1426 | int ret; | 1420 | int ret; |
@@ -1500,6 +1494,24 @@ done: | |||
1500 | error = 0; | 1494 | error = 0; |
1501 | goto out; | 1495 | goto out; |
1502 | 1496 | ||
1497 | nospace: | ||
1498 | /* | ||
1499 | * Perhaps the page was brought in from swap between find_lock_page | ||
1500 | * and taking info->lock? We allow for that at add_to_page_cache_lru, | ||
1501 | * but must also avoid reporting a spurious ENOSPC while working on a | ||
1502 | * full tmpfs. (When filepage has been passed in to shmem_getpage, it | ||
1503 | * is already in page cache, which prevents this race from occurring.) | ||
1504 | */ | ||
1505 | if (!filepage) { | ||
1506 | struct page *page = find_get_page(mapping, idx); | ||
1507 | if (page) { | ||
1508 | spin_unlock(&info->lock); | ||
1509 | page_cache_release(page); | ||
1510 | goto repeat; | ||
1511 | } | ||
1512 | } | ||
1513 | spin_unlock(&info->lock); | ||
1514 | error = -ENOSPC; | ||
1503 | failed: | 1515 | failed: |
1504 | if (*pagep != filepage) { | 1516 | if (*pagep != filepage) { |
1505 | unlock_page(filepage); | 1517 | unlock_page(filepage); |