Persist content state and bad block markings on sync errors while preserving deallocation state

This commit is contained in:
Andrea Mazzoleni 2026-09-03 16:35:16 +02:00
parent 0b64fb2eae
commit 45c032cd61

View file

@ -930,7 +930,7 @@ static void sync_parity_writer(struct snapraid_worker* worker, struct snapraid_t
task->state = TASK_STATE_DONE;
}
static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax)
static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax, int* sync_durable)
{
struct snapraid_io io;
struct snapraid_plan plan;
@ -961,6 +961,8 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
unsigned waiting_mac;
bit_vect_t* block_enabled;
*sync_durable = 0;
/* get the present time */
now = time(0);
@ -1728,6 +1730,9 @@ end:
goto bail;
/* LCOV_EXCL_STOP */
}
*sync_durable = 1;
if (state->opt.kill_after_sync) {
log_fatal(EUSER, "WARNING! Killing due --test-kill-after-sync option.\n");
exit(EXIT_SUCCESS);
@ -1897,6 +1902,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
unsigned l;
int skip_sync = 0;
int parity_shrink = 0;
int sync_durable = 0;
msg_progress("Initializing...\n");
@ -2129,7 +2135,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
log_fatal(EUSER, "WARNING! Killing due --test-kill-before-sync option.\n");
exit(EXIT_SUCCESS);
} else if (blockstart < blockmax) {
ret = state_sync_process(state, parity_handle, blockstart, blockmax);
ret = state_sync_process(state, parity_handle, blockstart, blockmax, &sync_durable);
if (ret == -1) {
/* LCOV_EXCL_START */
++process_error;
@ -2138,6 +2144,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
}
} else {
msg_status("Nothing to sync.\n");
sync_durable = 1;
}
}
@ -2149,27 +2156,53 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
log_fatal_errno(errno, lev_config_name(l));
++process_error;
sync_durable = 0;
/* continue, as we are already exiting */
/* LCOV_EXCL_STOP */
}
}
/*
* On close errors, abort without committing or persisting state,
* keeping the previous content file intact for recovery on the next run.
* Only a clean full sync makes all deallocation records obsolete.
*
* When process_error == 0, parity has been fully updated without the deleted
* files, so state_commit() clears the dealloclist.
*
* If any error occurred, state_commit() is skipped so the dealloclist remains
* intact. Calling state_write() below will then persist both the accumulated
* deallocation records and any error markings to the content file, keeping
* recovery data available across consecutive interrupted or failed runs.
*/
if (process_error == 0) {
msg_status("Everything OK\n");
state_commit(state);
}
/*
* Persist progress, error markings, and pending deallocations only after
* all parity handles are closed AND sync durability has been confirmed.
*
* If an asynchronous parity write, state_barrier(), or parity_close() failed,
* parity on disk is uncertain or incomplete. In that case, we must not publish
* in-memory BLK/EMPTY transitions to the content file, keeping the previous
* content file intact for recovery on the next run.
*
* Similarly, if sync was skipped before parity modification (e.g. on prehash
* failure), state_snapshot_pending() was bypassed (leaving SNAPSHOT_SCAN
* unpromoted), and legacy parity splits or newly added levels may still have
* split sizes set to PARITY_SIZE_INVALID (only resolved in the parity handles).
* Requiring sync durability ensures that snapshots, split sizes, and physical
* parity are completely consistent and serializable before invoking state_write().
*/
if (sync_durable) {
if (state->need_write || state->opt.force_content_write)
state_write(state);
}
if (process_error != 0)
return -1;
msg_status("Everything OK\n");
/* only a clean full sync makes all deallocation records obsolete */
state_commit(state);
/* persist progress and error markings only after all parity handles are closed */
if (state->need_write || state->opt.force_content_write)
state_write(state);
return 0;
}