aboutsummaryrefslogtreecommitdiffstats
path: root/mm
diff options
context:
space:
mode:
authorHugh Dickins <hughd@google.com>2011-05-11 18:13:38 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2011-05-11 21:50:45 -0400
commit59a16ead572330deb38e5848151d30ed1af754bc (patch)
tree792b1f35e11e0f49170b0d306d45706de54afeae /mm
parent778dd893ae785c5fd505dac30b5fc40aae188bf1 (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.c32
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
1497nospace:
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;
1503failed: 1515failed:
1504 if (*pagep != filepage) { 1516 if (*pagep != filepage) {
1505 unlock_page(filepage); 1517 unlock_page(filepage);