Skip to content

Commit

Permalink
ext4: retry block allocation for failed DIO and DAX writes
Browse files Browse the repository at this point in the history
Currently if block allocation for DIO or DAX write fails due to ENOSPC,
we just returned it to userspace. However these ENOSPC errors can be
transient because the transaction freeing blocks has not yet committed.
This demonstrates as failures of generic/102 test when the filesystem is
mounted with 'dax' mount option.

Fix the problem by properly retrying the allocation in case of ENOSPC
error in get blocks functions used for direct IO.

Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Tested-by: Ross Zwisler <ross.zwisler@linux.intel.com>
  • Loading branch information
Jan Kara authored and Theodore Ts'o committed Apr 1, 2016
1 parent daf647d commit e84dfbe
Showing 1 changed file with 28 additions and 30 deletions.
58 changes: 28 additions & 30 deletions fs/ext4/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -763,39 +763,47 @@ int ext4_get_block_unwritten(struct inode *inode, sector_t iblock,
/* Maximum number of blocks we map for direct IO at once. */
#define DIO_MAX_BLOCKS 4096

static handle_t *start_dio_trans(struct inode *inode,
struct buffer_head *bh_result)
/*
* Get blocks function for the cases that need to start a transaction -
* generally difference cases of direct IO and DAX IO. It also handles retries
* in case of ENOSPC.
*/
static int ext4_get_block_trans(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int flags)
{
int dio_credits;
handle_t *handle;
int retries = 0;
int ret;

/* Trim mapping request to maximum we can map at once for DIO */
if (bh_result->b_size >> inode->i_blkbits > DIO_MAX_BLOCKS)
bh_result->b_size = DIO_MAX_BLOCKS << inode->i_blkbits;
dio_credits = ext4_chunk_trans_blocks(inode,
bh_result->b_size >> inode->i_blkbits);
return ext4_journal_start(inode, EXT4_HT_MAP_BLOCKS, dio_credits);
retry:
handle = ext4_journal_start(inode, EXT4_HT_MAP_BLOCKS, dio_credits);
if (IS_ERR(handle))
return PTR_ERR(handle);

ret = _ext4_get_block(inode, iblock, bh_result, flags);
ext4_journal_stop(handle);

if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
goto retry;
return ret;
}

/* Get block function for DIO reads and writes to inodes without extents */
int ext4_dio_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh, int create)
{
handle_t *handle;
int ret;

/* We don't expect handle for direct IO */
WARN_ON_ONCE(ext4_journal_current_handle());

if (create) {
handle = start_dio_trans(inode, bh);
if (IS_ERR(handle))
return PTR_ERR(handle);
}
ret = _ext4_get_block(inode, iblock, bh,
create ? EXT4_GET_BLOCKS_CREATE : 0);
if (create)
ext4_journal_stop(handle);
return ret;
if (!create)
return _ext4_get_block(inode, iblock, bh, 0);
return ext4_get_block_trans(inode, iblock, bh, EXT4_GET_BLOCKS_CREATE);
}

/*
Expand All @@ -806,18 +814,13 @@ int ext4_dio_get_block(struct inode *inode, sector_t iblock,
static int ext4_dio_get_block_unwritten_async(struct inode *inode,
sector_t iblock, struct buffer_head *bh_result, int create)
{
handle_t *handle;
int ret;

/* We don't expect handle for direct IO */
WARN_ON_ONCE(ext4_journal_current_handle());

handle = start_dio_trans(inode, bh_result);
if (IS_ERR(handle))
return PTR_ERR(handle);
ret = _ext4_get_block(inode, iblock, bh_result,
EXT4_GET_BLOCKS_IO_CREATE_EXT);
ext4_journal_stop(handle);
ret = ext4_get_block_trans(inode, iblock, bh_result,
EXT4_GET_BLOCKS_IO_CREATE_EXT);

/*
* When doing DIO using unwritten extents, we need io_end to convert
Expand Down Expand Up @@ -850,18 +853,13 @@ static int ext4_dio_get_block_unwritten_async(struct inode *inode,
static int ext4_dio_get_block_unwritten_sync(struct inode *inode,
sector_t iblock, struct buffer_head *bh_result, int create)
{
handle_t *handle;
int ret;

/* We don't expect handle for direct IO */
WARN_ON_ONCE(ext4_journal_current_handle());

handle = start_dio_trans(inode, bh_result);
if (IS_ERR(handle))
return PTR_ERR(handle);
ret = _ext4_get_block(inode, iblock, bh_result,
EXT4_GET_BLOCKS_IO_CREATE_EXT);
ext4_journal_stop(handle);
ret = ext4_get_block_trans(inode, iblock, bh_result,
EXT4_GET_BLOCKS_IO_CREATE_EXT);

/*
* Mark inode as having pending DIO writes to unwritten extents.
Expand Down

0 comments on commit e84dfbe

Please sign in to comment.