Skip to content

Commit

Permalink
ext4: Provide function to handle transaction restarts
Browse files Browse the repository at this point in the history
Provide ext4_journal_ensure_credits_fn() function to ensure transaction
has given amount of credits and call helper function to prepare for
restarting a transaction. This allows to remove some boilerplate code
from various places, add proper error handling for the case where
transaction extension or restart fails, and reduces following changes
needed for proper revoke record reservation tracking.

Signed-off-by: Jan Kara <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Theodore Ts'o <[email protected]>
  • Loading branch information
jankara authored and tytso committed Nov 5, 2019
1 parent f289073 commit a413036
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 247 deletions.
4 changes: 3 additions & 1 deletion fs/ext4/ext4.h
Original file line number Diff line number Diff line change
Expand Up @@ -2604,7 +2604,6 @@ extern int ext4_can_truncate(struct inode *inode);
extern int ext4_truncate(struct inode *);
extern int ext4_break_layouts(struct inode *);
extern int ext4_punch_hole(struct inode *inode, loff_t offset, loff_t length);
extern int ext4_truncate_restart_trans(handle_t *, struct inode *, int nblocks);
extern void ext4_set_inode_flags(struct inode *);
extern int ext4_alloc_da_blocks(struct inode *inode);
extern void ext4_set_aops(struct inode *inode);
Expand Down Expand Up @@ -3296,6 +3295,9 @@ extern int ext4_swap_extents(handle_t *handle, struct inode *inode1,
ext4_lblk_t lblk2, ext4_lblk_t count,
int mark_unwritten,int *err);
extern int ext4_clu_mapped(struct inode *inode, ext4_lblk_t lclu);
extern int ext4_datasem_ensure_credits(handle_t *handle, struct inode *inode,
int check_cred, int restart_cred);


/* move_extent.c */
extern void ext4_double_down_write_data_sem(struct inode *first,
Expand Down
11 changes: 11 additions & 0 deletions fs/ext4/ext4_jbd2.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ handle_t *__ext4_journal_start_reserved(handle_t *handle, unsigned int line,
return handle;
}

int __ext4_journal_ensure_credits(handle_t *handle, int check_cred,
int extend_cred)
{
if (!ext4_handle_valid(handle))
return 0;
if (handle->h_buffer_credits >= check_cred)
return 0;
return ext4_journal_extend(handle,
extend_cred - handle->h_buffer_credits);
}

static void ext4_journal_abort_handle(const char *caller, unsigned int line,
const char *err_fn,
struct buffer_head *bh,
Expand Down
48 changes: 48 additions & 0 deletions fs/ext4/ext4_jbd2.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,54 @@ static inline int ext4_journal_restart(handle_t *handle, int nblocks)
return 0;
}

int __ext4_journal_ensure_credits(handle_t *handle, int check_cred,
int extend_cred);


/*
* Ensure @handle has at least @check_creds credits available. If not,
* transaction will be extended or restarted to contain at least @extend_cred
* credits. Before restarting transaction @fn is executed to allow for cleanup
* before the transaction is restarted.
*
* The return value is < 0 in case of error, 0 in case the handle has enough
* credits or transaction extension succeeded, 1 in case transaction had to be
* restarted.
*/
#define ext4_journal_ensure_credits_fn(handle, check_cred, extend_cred, fn) \
({ \
__label__ __ensure_end; \
int err = __ext4_journal_ensure_credits((handle), (check_cred), \
(extend_cred)); \
\
if (err <= 0) \
goto __ensure_end; \
err = (fn); \
if (err < 0) \
goto __ensure_end; \
err = ext4_journal_restart((handle), (extend_cred)); \
if (err == 0) \
err = 1; \
__ensure_end: \
err; \
})

/*
* Ensure given handle has at least requested amount of credits available,
* possibly restarting transaction if needed.
*/
static inline int ext4_journal_ensure_credits(handle_t *handle, int credits)
{
return ext4_journal_ensure_credits_fn(handle, credits, credits, 0);
}

static inline int ext4_journal_ensure_credits_batch(handle_t *handle,
int credits)
{
return ext4_journal_ensure_credits_fn(handle, credits,
EXT4_MAX_TRANS_DATA, 0);
}

static inline int ext4_journal_blocks_per_page(struct inode *inode)
{
if (EXT4_JOURNAL(inode) != NULL)
Expand Down
68 changes: 40 additions & 28 deletions fs/ext4/extents.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,29 +100,40 @@ static int ext4_split_extent_at(handle_t *handle,
static int ext4_find_delayed_extent(struct inode *inode,
struct extent_status *newes);

static int ext4_ext_truncate_extend_restart(handle_t *handle,
struct inode *inode,
int needed)
static int ext4_ext_trunc_restart_fn(struct inode *inode, int *dropped)
{
int err;

if (!ext4_handle_valid(handle))
return 0;
if (handle->h_buffer_credits >= needed)
return 0;
/*
* If we need to extend the journal get a few extra blocks
* while we're at it for efficiency's sake.
* Drop i_data_sem to avoid deadlock with ext4_map_blocks. At this
* moment, get_block can be called only for blocks inside i_size since
* page cache has been already dropped and writes are blocked by
* i_mutex. So we can safely drop the i_data_sem here.
*/
needed += 3;
err = ext4_journal_extend(handle, needed - handle->h_buffer_credits);
if (err <= 0)
return err;
err = ext4_truncate_restart_trans(handle, inode, needed);
if (err == 0)
err = -EAGAIN;
BUG_ON(EXT4_JOURNAL(inode) == NULL);
ext4_discard_preallocations(inode);
up_write(&EXT4_I(inode)->i_data_sem);
*dropped = 1;
return 0;
}

return err;
/*
* Make sure 'handle' has at least 'check_cred' credits. If not, restart
* transaction with 'restart_cred' credits. The function drops i_data_sem
* when restarting transaction and gets it after transaction is restarted.
*
* The function returns 0 on success, 1 if transaction had to be restarted,
* and < 0 in case of fatal error.
*/
int ext4_datasem_ensure_credits(handle_t *handle, struct inode *inode,
int check_cred, int restart_cred)
{
int ret;
int dropped = 0;

ret = ext4_journal_ensure_credits_fn(handle, check_cred, restart_cred,
ext4_ext_trunc_restart_fn(inode, &dropped));
if (dropped)
down_write(&EXT4_I(inode)->i_data_sem);
return ret;
}

/*
Expand Down Expand Up @@ -2820,9 +2831,13 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
}
credits += EXT4_MAXQUOTAS_TRANS_BLOCKS(inode->i_sb);

err = ext4_ext_truncate_extend_restart(handle, inode, credits);
if (err)
err = ext4_datasem_ensure_credits(handle, inode, credits,
credits);
if (err) {
if (err > 0)
err = -EAGAIN;
goto out;
}

err = ext4_ext_get_access(handle, inode, path + depth);
if (err)
Expand Down Expand Up @@ -5206,13 +5221,10 @@ ext4_access_path(handle_t *handle, struct inode *inode,
* descriptor) for each block group; assume two block
* groups
*/
if (handle->h_buffer_credits < 7) {
credits = ext4_writepage_trans_blocks(inode);
err = ext4_ext_truncate_extend_restart(handle, inode, credits);
/* EAGAIN is success */
if (err && err != -EAGAIN)
return err;
}
credits = ext4_writepage_trans_blocks(inode);
err = ext4_datasem_ensure_credits(handle, inode, 7, credits);
if (err < 0)
return err;

err = ext4_ext_get_access(handle, inode, path);
return err;
Expand Down
93 changes: 54 additions & 39 deletions fs/ext4/indirect.c
Original file line number Diff line number Diff line change
Expand Up @@ -699,27 +699,62 @@ int ext4_ind_trans_blocks(struct inode *inode, int nrblocks)
return DIV_ROUND_UP(nrblocks, EXT4_ADDR_PER_BLOCK(inode->i_sb)) + 4;
}

static int ext4_ind_trunc_restart_fn(handle_t *handle, struct inode *inode,
struct buffer_head *bh, int *dropped)
{
int err;

if (bh) {
BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_metadata(handle, inode, bh);
if (unlikely(err))
return err;
}
err = ext4_mark_inode_dirty(handle, inode);
if (unlikely(err))
return err;
/*
* Drop i_data_sem to avoid deadlock with ext4_map_blocks. At this
* moment, get_block can be called only for blocks inside i_size since
* page cache has been already dropped and writes are blocked by
* i_mutex. So we can safely drop the i_data_sem here.
*/
BUG_ON(EXT4_JOURNAL(inode) == NULL);
ext4_discard_preallocations(inode);
up_write(&EXT4_I(inode)->i_data_sem);
*dropped = 1;
return 0;
}

/*
* Truncate transactions can be complex and absolutely huge. So we need to
* be able to restart the transaction at a conventient checkpoint to make
* sure we don't overflow the journal.
*
* Try to extend this transaction for the purposes of truncation. If
* extend fails, we need to propagate the failure up and restart the
* transaction in the top-level truncate loop. --sct
*
* Returns 0 if we managed to create more room. If we can't create more
* room, and the transaction must be restarted we return 1.
* extend fails, we restart transaction.
*/
static int try_to_extend_transaction(handle_t *handle, struct inode *inode)
static int ext4_ind_truncate_ensure_credits(handle_t *handle,
struct inode *inode,
struct buffer_head *bh)
{
if (!ext4_handle_valid(handle))
return 0;
if (ext4_handle_has_enough_credits(handle, EXT4_RESERVE_TRANS_BLOCKS+1))
return 0;
if (!ext4_journal_extend(handle, ext4_blocks_for_truncate(inode)))
return 0;
return 1;
int ret;
int dropped = 0;

ret = ext4_journal_ensure_credits_fn(handle, EXT4_RESERVE_TRANS_BLOCKS,
ext4_blocks_for_truncate(inode),
ext4_ind_trunc_restart_fn(handle, inode, bh, &dropped));
if (dropped)
down_write(&EXT4_I(inode)->i_data_sem);
if (ret <= 0)
return ret;
if (bh) {
BUFFER_TRACE(bh, "retaking write access");
ret = ext4_journal_get_write_access(handle, bh);
if (unlikely(ret))
return ret;
}
return 0;
}

/*
Expand Down Expand Up @@ -854,27 +889,9 @@ static int ext4_clear_blocks(handle_t *handle, struct inode *inode,
return 1;
}

if (try_to_extend_transaction(handle, inode)) {
if (bh) {
BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_metadata(handle, inode, bh);
if (unlikely(err))
goto out_err;
}
err = ext4_mark_inode_dirty(handle, inode);
if (unlikely(err))
goto out_err;
err = ext4_truncate_restart_trans(handle, inode,
ext4_blocks_for_truncate(inode));
if (unlikely(err))
goto out_err;
if (bh) {
BUFFER_TRACE(bh, "retaking write access");
err = ext4_journal_get_write_access(handle, bh);
if (unlikely(err))
goto out_err;
}
}
err = ext4_ind_truncate_ensure_credits(handle, inode, bh);
if (err < 0)
goto out_err;

for (p = first; p < last; p++)
*p = 0;
Expand Down Expand Up @@ -1057,11 +1074,9 @@ static void ext4_free_branches(handle_t *handle, struct inode *inode,
*/
if (ext4_handle_is_aborted(handle))
return;
if (try_to_extend_transaction(handle, inode)) {
ext4_mark_inode_dirty(handle, inode);
ext4_truncate_restart_trans(handle, inode,
ext4_blocks_for_truncate(inode));
}
if (ext4_ind_truncate_ensure_credits(handle, inode,
NULL) < 0)
return;

/*
* The forget flag here is critical because if
Expand Down
26 changes: 0 additions & 26 deletions fs/ext4/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,32 +163,6 @@ int ext4_inode_is_fast_symlink(struct inode *inode)
(inode->i_size < EXT4_N_BLOCKS * 4);
}

/*
* Restart the transaction associated with *handle. This does a commit,
* so before we call here everything must be consistently dirtied against
* this transaction.
*/
int ext4_truncate_restart_trans(handle_t *handle, struct inode *inode,
int nblocks)
{
int ret;

/*
* Drop i_data_sem to avoid deadlock with ext4_map_blocks. At this
* moment, get_block can be called only for blocks inside i_size since
* page cache has been already dropped and writes are blocked by
* i_mutex. So we can safely drop the i_data_sem here.
*/
BUG_ON(EXT4_JOURNAL(inode) == NULL);
jbd_debug(2, "restarting handle %p\n", handle);
up_write(&EXT4_I(inode)->i_data_sem);
ret = ext4_journal_restart(handle, nblocks);
down_write(&EXT4_I(inode)->i_data_sem);
ext4_discard_preallocations(inode);

return ret;
}

/*
* Called at the last iput() if i_nlink is zero.
*/
Expand Down
Loading

0 comments on commit a413036

Please sign in to comment.