mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 02:49:02 -04:00
Limit unsynced parity sweep to the invalid window tracked during content parsing
Some checks are pending
Build / build-mac (push) Waiting to run
Build / build-linux (push) Waiting to run
Build / build-arm32 (push) Waiting to run
Build / build-win (push) Waiting to run
Build / build-freebsd (push) Waiting to run
Build / build-musl (push) Waiting to run
CodeQL Advanced / Analyze (c-cpp) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
Some checks are pending
Build / build-mac (push) Waiting to run
Build / build-linux (push) Waiting to run
Build / build-arm32 (push) Waiting to run
Build / build-win (push) Waiting to run
Build / build-freebsd (push) Waiting to run
Build / build-musl (push) Waiting to run
CodeQL Advanced / Analyze (c-cpp) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
This commit is contained in:
parent
2a60279674
commit
8fe9331f42
5 changed files with 76 additions and 32 deletions
|
|
@ -1389,6 +1389,20 @@ static int extent_parity_inside_compare_unlock(const void* void_a, const void* v
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct snapraid_extent* fs_par2extent_find_next(struct snapraid_disk* disk, block_off_t parity_pos)
|
||||
{
|
||||
struct extent_parity_inside arg = { parity_pos };
|
||||
struct snapraid_extent* extent;
|
||||
|
||||
fs_lock(disk);
|
||||
|
||||
/* non-overlapping extents let this search find the containing extent or the next one */
|
||||
extent = tommy_tree_search_greater_equal_compare(&disk->fs_parity, extent_parity_inside_compare_unlock, &arg);
|
||||
|
||||
fs_unlock(disk);
|
||||
return extent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the extent at the specified parity position.
|
||||
* The search is optimized for sequential accesses.
|
||||
|
|
|
|||
|
|
@ -1243,6 +1243,13 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t pos);
|
|||
*/
|
||||
struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos);
|
||||
|
||||
/**
|
||||
* Find the first extent ending after the specified parity position.
|
||||
* Return 0 if no such extent exists. The caller must ensure that the extent
|
||||
* trees remain unchanged while using the returned extent or its tree node.
|
||||
*/
|
||||
struct snapraid_extent* fs_par2extent_find_next(struct snapraid_disk* disk, block_off_t parity_pos);
|
||||
|
||||
/**
|
||||
* Get the file position from the parity position.
|
||||
* Return 0 if no file is using it.
|
||||
|
|
|
|||
|
|
@ -2270,7 +2270,7 @@ static void test_unsynced_sweep(void)
|
|||
state.block_size = 4096;
|
||||
tommy_list_init(&state.disklist);
|
||||
|
||||
if (fs_count_unsynced(&state, 100) != 0) {
|
||||
if (fs_count_unsynced(&state, 0, 100) != 0) {
|
||||
log_fatal(EINTERNAL, "test_unsynced_sweep: no disks failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
|
@ -2281,7 +2281,7 @@ static void test_unsynced_sweep(void)
|
|||
tommy_list_insert_tail(&state.disklist, &disk2->node, disk2);
|
||||
|
||||
/* initially both disks are empty */
|
||||
if (fs_count_unsynced(&state, 100) != 0) {
|
||||
if (fs_count_unsynced(&state, 10, 100) != 0) {
|
||||
log_fatal(EINTERNAL, "test_unsynced_sweep: empty disks failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
|
@ -2301,7 +2301,7 @@ static void test_unsynced_sweep(void)
|
|||
fs_allocate(disk2, 0, file2, 0, 20);
|
||||
|
||||
/* parity 0..14 has only BLK or EMPTY; parity 15..19 has CHG on disk1 and BLK on disk2 -> 5 unsynced */
|
||||
if (fs_count_unsynced(&state, 20) != 5) {
|
||||
if (fs_count_unsynced(&state, 15, 20) != 5) {
|
||||
log_fatal(EINTERNAL, "test_unsynced_sweep: mixed block run failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
|
@ -2313,7 +2313,7 @@ static void test_unsynced_sweep(void)
|
|||
fs_allocate(disk1, 20, del, 0, 3);
|
||||
|
||||
/* disk2 is empty at 20..22 -> DELETED alone does not count as unsynced */
|
||||
if (fs_count_unsynced(&state, 23) != 5) {
|
||||
if (fs_count_unsynced(&state, 15, 23) != 5) {
|
||||
log_fatal(EINTERNAL, "test_unsynced_sweep: deleted without other file failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
|
@ -2330,7 +2330,7 @@ static void test_unsynced_sweep(void)
|
|||
|
||||
/* parity 20: DELETED on d1, empty on d2 -> not unsynced;
|
||||
* parity 21..22: DELETED on d1, BLK on d2 -> unsynced (2 blocks). Total 5 + 2 = 7 */
|
||||
if (fs_count_unsynced(&state, 23) != 7) {
|
||||
if (fs_count_unsynced(&state, 15, 23) != 7) {
|
||||
log_fatal(EINTERNAL, "test_unsynced_sweep: deleted with file failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
|
@ -2349,14 +2349,19 @@ static void test_unsynced_sweep(void)
|
|||
* Positions 30..33 are all unsynced: REP, REP, CHG, REBUILD respectively.
|
||||
* At 32 and 33 the invalid block changes disks at the same event boundary;
|
||||
* count each position once using the refreshed flags from both disks.
|
||||
* Check limits in gaps, within extents and runs, and at their boundaries.
|
||||
* Check every window between limits in gaps, within extents and runs,
|
||||
* and at their boundaries, including empty windows and nonzero starts.
|
||||
*/
|
||||
for (i = 0; i < sizeof(bounds) / sizeof(bounds[0]); ++i) {
|
||||
uint64_t count = fs_count_unsynced(&state, bounds[i].blockmax);
|
||||
if (count != bounds[i].unsynced) {
|
||||
log_fatal(EINTERNAL, "test_unsynced_sweep: limit %" PRIu64 " expected %" PRIu64 ", got %" PRIu64 "\n",
|
||||
bounds[i].blockmax, bounds[i].unsynced, count);
|
||||
exit(EXIT_FAILURE);
|
||||
block_off_t j;
|
||||
for (j = 0; j <= i; ++j) {
|
||||
uint64_t expected = bounds[i].unsynced - bounds[j].unsynced;
|
||||
uint64_t count = fs_count_unsynced(&state, bounds[j].blockmax, bounds[i].blockmax);
|
||||
if (count != expected) {
|
||||
log_fatal(EINTERNAL, "test_unsynced_sweep: window [%" PRIu64 ", %" PRIu64 ") expected %" PRIu64 ", got %" PRIu64 "\n",
|
||||
bounds[j].blockmax, bounds[i].blockmax, expected, count);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2242,6 +2242,10 @@ static inline void disk_advance(
|
|||
block_off_t run_rem = 1;
|
||||
const unsigned char* ptr = (const unsigned char*)block;
|
||||
|
||||
/* stop inspecting block flags at the end of the requested window */
|
||||
if (extent_rem > blockmax - pos)
|
||||
extent_rem = blockmax - pos;
|
||||
|
||||
*has_file_ptr = has_file;
|
||||
*has_invalid_ptr = has_invalid;
|
||||
|
||||
|
|
@ -2264,14 +2268,16 @@ static inline void disk_advance(
|
|||
* block, and at least one disk has an invalid parity block (CHG, REP, REBUILD,
|
||||
* or DELETED).
|
||||
*
|
||||
* The sweep takes O(B + R * D) time, where B is the number of blocks inspected,
|
||||
* Counts only positions in [blockmin, blockmax).
|
||||
* The sweep takes O(D * log(E) + B + R * D) time, where E is the number of
|
||||
* extents per disk, B is the number of blocks inspected within the window,
|
||||
* R is the number of event positions processed (including initialization), and
|
||||
* D is the number of disks. Cached event boundaries avoid rescanning block runs.
|
||||
*
|
||||
* The caller must ensure that the disk list, extent trees, and block states
|
||||
* remain unchanged during the count, as this function does not acquire locks.
|
||||
* remain unchanged during the count, as no locks are held during the sweep.
|
||||
*/
|
||||
uint64_t fs_count_unsynced(struct snapraid_state* state, block_off_t blockmax)
|
||||
uint64_t fs_count_unsynced(struct snapraid_state* state, block_off_t blockmin, block_off_t blockmax)
|
||||
{
|
||||
tommy_tree_node* cursors[RAID_DATA_MAX];
|
||||
block_off_t next_events[RAID_DATA_MAX];
|
||||
|
|
@ -2280,7 +2286,7 @@ uint64_t fs_count_unsynced(struct snapraid_state* state, block_off_t blockmax)
|
|||
unsigned disk_count = 0;
|
||||
tommy_node* node;
|
||||
uint64_t total_unsynced = 0;
|
||||
block_off_t pos = 0;
|
||||
block_off_t pos = blockmin;
|
||||
size_t stride = block_sizeof();
|
||||
unsigned d;
|
||||
|
||||
|
|
@ -2294,9 +2300,10 @@ uint64_t fs_count_unsynced(struct snapraid_state* state, block_off_t blockmax)
|
|||
|
||||
for (node = state->disklist; node != 0; node = node->next) {
|
||||
struct snapraid_disk* disk = node->data;
|
||||
cursors[disk_count] = tommy_tree_head(&disk->fs_parity);
|
||||
struct snapraid_extent* ext = fs_par2extent_find_next(disk, blockmin);
|
||||
cursors[disk_count] = ext ? &ext->parity_node : 0;
|
||||
/* force an initial event so flags are set before their first use */
|
||||
next_events[disk_count] = 0;
|
||||
next_events[disk_count] = blockmin;
|
||||
++disk_count;
|
||||
}
|
||||
|
||||
|
|
@ -2508,6 +2515,8 @@ struct state_read_context {
|
|||
static void state_read_content(struct snapraid_state* state, const char* path, STREAM* f, struct state_read_context* context)
|
||||
{
|
||||
block_off_t blockmax;
|
||||
block_off_t inv_min;
|
||||
block_off_t inv_max;
|
||||
uint64_t count_file;
|
||||
uint64_t count_hardlink;
|
||||
uint64_t count_symlink;
|
||||
|
|
@ -2523,7 +2532,6 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
int has_hash;
|
||||
int has_prevhash;
|
||||
int has_info;
|
||||
int has_invalid_parity;
|
||||
char buffer[PATH_MAX];
|
||||
ssize_t ret;
|
||||
tommy_array disk_mapping;
|
||||
|
|
@ -2532,6 +2540,8 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
tommy_hashdyn bucket_hash;
|
||||
|
||||
blockmax = 0;
|
||||
inv_min = BLOCK_MAX;
|
||||
inv_max = 0;
|
||||
count_file = 0;
|
||||
count_hardlink = 0;
|
||||
count_symlink = 0;
|
||||
|
|
@ -2547,7 +2557,6 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
has_hash = 0;
|
||||
has_prevhash = 0;
|
||||
has_info = 0;
|
||||
has_invalid_parity = 0;
|
||||
mapping_max = 0;
|
||||
memset(disk_file_count, 0, sizeof(disk_file_count));
|
||||
tommy_array_init(&disk_mapping);
|
||||
|
|
@ -2819,8 +2828,12 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
}
|
||||
|
||||
/* all file block states except BLK require a parity update */
|
||||
if (v_state != BLOCK_STATE_BLK)
|
||||
has_invalid_parity = 1;
|
||||
if (v_state != BLOCK_STATE_BLK) {
|
||||
if (v_pos < inv_min)
|
||||
inv_min = v_pos;
|
||||
if (v_pos + v_count > inv_max)
|
||||
inv_max = v_pos + v_count;
|
||||
}
|
||||
|
||||
v_file_pos = v_idx;
|
||||
ret = state_read_block_run(f, file, v_file_pos, v_count, v_state, discard_hash);
|
||||
|
|
@ -2989,14 +3002,6 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A synchronized content has no invalid block state, so its unsynced
|
||||
* count is certainly zero. Scan the extent maps only when needed.
|
||||
*/
|
||||
if (has_invalid_parity) {
|
||||
count_unsynced = fs_count_unsynced(state, blockmax);
|
||||
}
|
||||
} else if (c == 'h') {
|
||||
/* hole */
|
||||
block_off_t v_pos;
|
||||
|
|
@ -3049,7 +3054,10 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
switch (c) {
|
||||
case 'o' :
|
||||
/* if it's a run of deleted blocks */
|
||||
has_invalid_parity = 1;
|
||||
if (v_pos < inv_min)
|
||||
inv_min = v_pos;
|
||||
if (v_pos + v_count > inv_max)
|
||||
inv_max = v_pos + v_count;
|
||||
|
||||
/* ensure the converted byte size fits in signed data_off_t without overflow */
|
||||
if (v_count > INT64_MAX / (uint64_t)state->block_size) {
|
||||
|
|
@ -4023,6 +4031,16 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/*
|
||||
* Every invalid file or deleted run expands this window while parsing.
|
||||
* Outside it all parity is valid, so no position can be unsynced.
|
||||
* Count only after all records are loaded, independently of the info record.
|
||||
* A synchronized content leaves the window empty and needs no sweep.
|
||||
*/
|
||||
if (inv_min < inv_max) {
|
||||
count_unsynced = fs_count_unsynced(state, inv_min, inv_max);
|
||||
}
|
||||
|
||||
context->blockmax = blockmax;
|
||||
context->count_map = mapping_max;
|
||||
context->count_file = count_file;
|
||||
|
|
|
|||
|
|
@ -242,10 +242,10 @@ void state_read(struct snapraid_state* state);
|
|||
|
||||
/**
|
||||
* Count the number of unsynced parity positions using an event sweep across extents.
|
||||
* Counts positions in [0, blockmax). The caller must ensure that the disk list,
|
||||
* Counts positions in [blockmin, blockmax). The caller must ensure that the disk list,
|
||||
* extent trees, and block states remain unchanged for the duration of the call.
|
||||
*/
|
||||
uint64_t fs_count_unsynced(struct snapraid_state* state, block_off_t blockmax);
|
||||
uint64_t fs_count_unsynced(struct snapraid_state* state, block_off_t blockmin, block_off_t blockmax);
|
||||
|
||||
/**
|
||||
* Write the new state.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue