Commit 0637c6f4 authored by Theodore Ts'o's avatar Theodore Ts'o

ext4: Patch up how we claim metadata blocks for quota purposes

As reported in Kernel Bugzilla #14936, commit d21cd8f1 triggered a BUG
in the function ext4_da_update_reserve_space() found in
fs/ext4/inode.c.  The root cause of this BUG() was caused by the fact
that ext4_calc_metadata_amount() can severely over-estimate how many
metadata blocks will be needed, especially when using direct
block-mapped files.

In addition, it can also badly *under* estimate how much space is
needed, since ext4_calc_metadata_amount() assumes that the blocks are
contiguous, and this is not always true.  If the application is
writing blocks to a sparse file, the number of metadata blocks
necessary can be severly underestimated by the functions
ext4_da_reserve_space(), ext4_da_update_reserve_space() and
ext4_da_release_space().  This was the cause of the dq_claim_space
reports found on kerneloops.org.

Unfortunately, doing this right means that we need to massively
over-estimate the amount of free space needed.  So in some cases we
may need to force the inode to be written to disk asynchronously in
to avoid spurious quota failures.

http://bugzilla.kernel.org/show_bug.cgi?id=14936Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent 515f41c3
...@@ -1043,43 +1043,47 @@ static int ext4_calc_metadata_amount(struct inode *inode, int blocks) ...@@ -1043,43 +1043,47 @@ static int ext4_calc_metadata_amount(struct inode *inode, int blocks)
return ext4_indirect_calc_metadata_amount(inode, blocks); return ext4_indirect_calc_metadata_amount(inode, blocks);
} }
/*
* Called with i_data_sem down, which is important since we can call
* ext4_discard_preallocations() from here.
*/
static void ext4_da_update_reserve_space(struct inode *inode, int used) static void ext4_da_update_reserve_space(struct inode *inode, int used)
{ {
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
int total, mdb, mdb_free, mdb_claim = 0; struct ext4_inode_info *ei = EXT4_I(inode);
int mdb_free = 0;
spin_lock(&EXT4_I(inode)->i_block_reservation_lock);
/* recalculate the number of metablocks still need to be reserved */ spin_lock(&ei->i_block_reservation_lock);
total = EXT4_I(inode)->i_reserved_data_blocks - used; if (unlikely(used > ei->i_reserved_data_blocks)) {
mdb = ext4_calc_metadata_amount(inode, total); ext4_msg(inode->i_sb, KERN_NOTICE, "%s: ino %lu, used %d "
"with only %d reserved data blocks\n",
/* figure out how many metablocks to release */ __func__, inode->i_ino, used,
BUG_ON(mdb > EXT4_I(inode)->i_reserved_meta_blocks); ei->i_reserved_data_blocks);
mdb_free = EXT4_I(inode)->i_reserved_meta_blocks - mdb; WARN_ON(1);
used = ei->i_reserved_data_blocks;
}
if (mdb_free) { /* Update per-inode reservations */
/* Account for allocated meta_blocks */ ei->i_reserved_data_blocks -= used;
mdb_claim = EXT4_I(inode)->i_allocated_meta_blocks; used += ei->i_allocated_meta_blocks;
BUG_ON(mdb_free < mdb_claim); ei->i_reserved_meta_blocks -= ei->i_allocated_meta_blocks;
mdb_free -= mdb_claim; ei->i_allocated_meta_blocks = 0;
percpu_counter_sub(&sbi->s_dirtyblocks_counter, used);
/* update fs dirty blocks counter */ if (ei->i_reserved_data_blocks == 0) {
/*
* We can release all of the reserved metadata blocks
* only when we have written all of the delayed
* allocation blocks.
*/
mdb_free = ei->i_allocated_meta_blocks;
percpu_counter_sub(&sbi->s_dirtyblocks_counter, mdb_free); percpu_counter_sub(&sbi->s_dirtyblocks_counter, mdb_free);
EXT4_I(inode)->i_allocated_meta_blocks = 0; ei->i_allocated_meta_blocks = 0;
EXT4_I(inode)->i_reserved_meta_blocks = mdb;
} }
/* update per-inode reservations */
BUG_ON(used > EXT4_I(inode)->i_reserved_data_blocks);
EXT4_I(inode)->i_reserved_data_blocks -= used;
percpu_counter_sub(&sbi->s_dirtyblocks_counter, used + mdb_claim);
spin_unlock(&EXT4_I(inode)->i_block_reservation_lock); spin_unlock(&EXT4_I(inode)->i_block_reservation_lock);
vfs_dq_claim_block(inode, used + mdb_claim); /* Update quota subsystem */
vfs_dq_claim_block(inode, used);
/*
* free those over-booking quota for metadata blocks
*/
if (mdb_free) if (mdb_free)
vfs_dq_release_reservation_block(inode, mdb_free); vfs_dq_release_reservation_block(inode, mdb_free);
...@@ -1088,7 +1092,8 @@ static void ext4_da_update_reserve_space(struct inode *inode, int used) ...@@ -1088,7 +1092,8 @@ static void ext4_da_update_reserve_space(struct inode *inode, int used)
* there aren't any writers on the inode, we can discard the * there aren't any writers on the inode, we can discard the
* inode's preallocations. * inode's preallocations.
*/ */
if (!total && (atomic_read(&inode->i_writecount) == 0)) if ((ei->i_reserved_data_blocks == 0) &&
(atomic_read(&inode->i_writecount) == 0))
ext4_discard_preallocations(inode); ext4_discard_preallocations(inode);
} }
...@@ -1801,7 +1806,8 @@ static int ext4_da_reserve_space(struct inode *inode, int nrblocks) ...@@ -1801,7 +1806,8 @@ static int ext4_da_reserve_space(struct inode *inode, int nrblocks)
{ {
int retries = 0; int retries = 0;
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
unsigned long md_needed, mdblocks, total = 0; struct ext4_inode_info *ei = EXT4_I(inode);
unsigned long md_needed, md_reserved, total = 0;
/* /*
* recalculate the amount of metadata blocks to reserve * recalculate the amount of metadata blocks to reserve
...@@ -1809,35 +1815,44 @@ static int ext4_da_reserve_space(struct inode *inode, int nrblocks) ...@@ -1809,35 +1815,44 @@ static int ext4_da_reserve_space(struct inode *inode, int nrblocks)
* worse case is one extent per block * worse case is one extent per block
*/ */
repeat: repeat:
spin_lock(&EXT4_I(inode)->i_block_reservation_lock); spin_lock(&ei->i_block_reservation_lock);
total = EXT4_I(inode)->i_reserved_data_blocks + nrblocks; md_reserved = ei->i_reserved_meta_blocks;
mdblocks = ext4_calc_metadata_amount(inode, total); md_needed = ext4_calc_metadata_amount(inode, nrblocks);
BUG_ON(mdblocks < EXT4_I(inode)->i_reserved_meta_blocks);
md_needed = mdblocks - EXT4_I(inode)->i_reserved_meta_blocks;
total = md_needed + nrblocks; total = md_needed + nrblocks;
spin_unlock(&EXT4_I(inode)->i_block_reservation_lock); spin_unlock(&ei->i_block_reservation_lock);
/* /*
* Make quota reservation here to prevent quota overflow * Make quota reservation here to prevent quota overflow
* later. Real quota accounting is done at pages writeout * later. Real quota accounting is done at pages writeout
* time. * time.
*/ */
if (vfs_dq_reserve_block(inode, total)) if (vfs_dq_reserve_block(inode, total)) {
/*
* We tend to badly over-estimate the amount of
* metadata blocks which are needed, so if we have
* reserved any metadata blocks, try to force out the
* inode and see if we have any better luck.
*/
if (md_reserved && retries++ <= 3)
goto retry;
return -EDQUOT; return -EDQUOT;
}
if (ext4_claim_free_blocks(sbi, total)) { if (ext4_claim_free_blocks(sbi, total)) {
vfs_dq_release_reservation_block(inode, total); vfs_dq_release_reservation_block(inode, total);
if (ext4_should_retry_alloc(inode->i_sb, &retries)) { if (ext4_should_retry_alloc(inode->i_sb, &retries)) {
retry:
if (md_reserved)
write_inode_now(inode, (retries == 3));
yield(); yield();
goto repeat; goto repeat;
} }
return -ENOSPC; return -ENOSPC;
} }
spin_lock(&EXT4_I(inode)->i_block_reservation_lock); spin_lock(&ei->i_block_reservation_lock);
EXT4_I(inode)->i_reserved_data_blocks += nrblocks; ei->i_reserved_data_blocks += nrblocks;
EXT4_I(inode)->i_reserved_meta_blocks += md_needed; ei->i_reserved_meta_blocks += md_needed;
spin_unlock(&EXT4_I(inode)->i_block_reservation_lock); spin_unlock(&ei->i_block_reservation_lock);
return 0; /* success */ return 0; /* success */
} }
...@@ -1845,49 +1860,45 @@ static int ext4_da_reserve_space(struct inode *inode, int nrblocks) ...@@ -1845,49 +1860,45 @@ static int ext4_da_reserve_space(struct inode *inode, int nrblocks)
static void ext4_da_release_space(struct inode *inode, int to_free) static void ext4_da_release_space(struct inode *inode, int to_free)
{ {
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
int total, mdb, mdb_free, release; struct ext4_inode_info *ei = EXT4_I(inode);
if (!to_free) if (!to_free)
return; /* Nothing to release, exit */ return; /* Nothing to release, exit */
spin_lock(&EXT4_I(inode)->i_block_reservation_lock); spin_lock(&EXT4_I(inode)->i_block_reservation_lock);
if (!EXT4_I(inode)->i_reserved_data_blocks) { if (unlikely(to_free > ei->i_reserved_data_blocks)) {
/* /*
* if there is no reserved blocks, but we try to free some * if there aren't enough reserved blocks, then the
* then the counter is messed up somewhere. * counter is messed up somewhere. Since this
* but since this function is called from invalidate * function is called from invalidate page, it's
* page, it's harmless to return without any action * harmless to return without any action.
*/ */
printk(KERN_INFO "ext4 delalloc try to release %d reserved " ext4_msg(inode->i_sb, KERN_NOTICE, "ext4_da_release_space: "
"blocks for inode %lu, but there is no reserved " "ino %lu, to_free %d with only %d reserved "
"data blocks\n", to_free, inode->i_ino); "data blocks\n", inode->i_ino, to_free,
spin_unlock(&EXT4_I(inode)->i_block_reservation_lock); ei->i_reserved_data_blocks);
return; WARN_ON(1);
to_free = ei->i_reserved_data_blocks;
} }
ei->i_reserved_data_blocks -= to_free;
/* recalculate the number of metablocks still need to be reserved */ if (ei->i_reserved_data_blocks == 0) {
total = EXT4_I(inode)->i_reserved_data_blocks - to_free; /*
mdb = ext4_calc_metadata_amount(inode, total); * We can release all of the reserved metadata blocks
* only when we have written all of the delayed
/* figure out how many metablocks to release */ * allocation blocks.
BUG_ON(mdb > EXT4_I(inode)->i_reserved_meta_blocks); */
mdb_free = EXT4_I(inode)->i_reserved_meta_blocks - mdb; to_free += ei->i_allocated_meta_blocks;
ei->i_allocated_meta_blocks = 0;
release = to_free + mdb_free; }
/* update fs dirty blocks counter for truncate case */
percpu_counter_sub(&sbi->s_dirtyblocks_counter, release);
/* update per-inode reservations */ /* update fs dirty blocks counter */
BUG_ON(to_free > EXT4_I(inode)->i_reserved_data_blocks); percpu_counter_sub(&sbi->s_dirtyblocks_counter, to_free);
EXT4_I(inode)->i_reserved_data_blocks -= to_free;
BUG_ON(mdb > EXT4_I(inode)->i_reserved_meta_blocks);
EXT4_I(inode)->i_reserved_meta_blocks = mdb;
spin_unlock(&EXT4_I(inode)->i_block_reservation_lock); spin_unlock(&EXT4_I(inode)->i_block_reservation_lock);
vfs_dq_release_reservation_block(inode, release); vfs_dq_release_reservation_block(inode, to_free);
} }
static void ext4_da_page_release_reservation(struct page *page, static void ext4_da_page_release_reservation(struct page *page,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment