ext4: reimplement uninit extent optimization for move_extent_per_page()
Uninitialized extent may became initialized(parallel writeback task) at any moment after we drop i_data_sem, so we have to recheck extent's state after we hold page's lock and i_data_sem. If we about to change page's mapping we must hold page's lock in order to serialize other users. Signed-off-by: Dmitry Monakhov <dmonakhov@openvz.org> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
This commit is contained in:
parent
bb55748805
commit
8c85447391
|
@ -594,6 +594,43 @@ mext_calc_swap_extents(struct ext4_extent *tmp_dext,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mext_check_coverage - Check that all extents in range has the same type
|
||||
*
|
||||
* @inode: inode in question
|
||||
* @from: block offset of inode
|
||||
* @count: block count to be checked
|
||||
* @uninit: extents expected to be uninitialized
|
||||
* @err: pointer to save error value
|
||||
*
|
||||
* Return 1 if all extents in range has expected type, and zero otherwise.
|
||||
*/
|
||||
static int
|
||||
mext_check_coverage(struct inode *inode, ext4_lblk_t from, ext4_lblk_t count,
|
||||
int uninit, int *err)
|
||||
{
|
||||
struct ext4_ext_path *path = NULL;
|
||||
struct ext4_extent *ext;
|
||||
ext4_lblk_t last = from + count;
|
||||
while (from < last) {
|
||||
*err = get_ext_path(inode, from, &path);
|
||||
if (*err)
|
||||
return 0;
|
||||
ext = path[ext_depth(inode)].p_ext;
|
||||
if (!ext) {
|
||||
ext4_ext_drop_refs(path);
|
||||
return 0;
|
||||
}
|
||||
if (uninit != ext4_ext_is_uninitialized(ext)) {
|
||||
ext4_ext_drop_refs(path);
|
||||
return 0;
|
||||
}
|
||||
from += ext4_ext_get_actual_len(ext);
|
||||
ext4_ext_drop_refs(path);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* mext_replace_branches - Replace original extents with new extents
|
||||
*
|
||||
|
@ -629,9 +666,6 @@ mext_replace_branches(handle_t *handle, struct inode *orig_inode,
|
|||
int replaced_count = 0;
|
||||
int dext_alen;
|
||||
|
||||
/* Protect extent trees against block allocations via delalloc */
|
||||
double_down_write_data_sem(orig_inode, donor_inode);
|
||||
|
||||
/* Get the original extent for the block "orig_off" */
|
||||
*err = get_ext_path(orig_inode, orig_off, &orig_path);
|
||||
if (*err)
|
||||
|
@ -730,8 +764,6 @@ out:
|
|||
ext4_ext_invalidate_cache(orig_inode);
|
||||
ext4_ext_invalidate_cache(donor_inode);
|
||||
|
||||
double_up_write_data_sem(orig_inode, donor_inode);
|
||||
|
||||
return replaced_count;
|
||||
}
|
||||
|
||||
|
@ -925,7 +957,46 @@ again:
|
|||
pagep);
|
||||
if (unlikely(*err < 0))
|
||||
goto stop_journal;
|
||||
/*
|
||||
* If orig extent was uninitialized it can become initialized
|
||||
* at any time after i_data_sem was dropped, in order to
|
||||
* serialize with delalloc we have recheck extent while we
|
||||
* hold page's lock, if it is still the case data copy is not
|
||||
* necessary, just swap data blocks between orig and donor.
|
||||
*/
|
||||
if (uninit) {
|
||||
double_down_write_data_sem(orig_inode, donor_inode);
|
||||
/* If any of extents in range became initialized we have to
|
||||
* fallback to data copying */
|
||||
uninit = mext_check_coverage(orig_inode, orig_blk_offset,
|
||||
block_len_in_page, 1, err);
|
||||
if (*err)
|
||||
goto drop_data_sem;
|
||||
|
||||
uninit &= mext_check_coverage(donor_inode, orig_blk_offset,
|
||||
block_len_in_page, 1, err);
|
||||
if (*err)
|
||||
goto drop_data_sem;
|
||||
|
||||
if (!uninit) {
|
||||
double_up_write_data_sem(orig_inode, donor_inode);
|
||||
goto data_copy;
|
||||
}
|
||||
if ((page_has_private(pagep[0]) &&
|
||||
!try_to_release_page(pagep[0], 0)) ||
|
||||
(page_has_private(pagep[1]) &&
|
||||
!try_to_release_page(pagep[1], 0))) {
|
||||
*err = -EBUSY;
|
||||
goto drop_data_sem;
|
||||
}
|
||||
replaced_count = mext_replace_branches(handle, orig_inode,
|
||||
donor_inode, orig_blk_offset,
|
||||
block_len_in_page, err);
|
||||
drop_data_sem:
|
||||
double_up_write_data_sem(orig_inode, donor_inode);
|
||||
goto unlock_pages;
|
||||
}
|
||||
data_copy:
|
||||
*err = mext_page_mkuptodate(pagep[0], from, from + replaced_size);
|
||||
if (*err)
|
||||
goto unlock_pages;
|
||||
|
|
Loading…
Reference in New Issue