Quiesce scrub read-ahead before autosave and distinguish read/write barriers

This commit is contained in:
Andrea Mazzoleni 2026-09-24 19:41:52 +02:00
parent fba3f74538
commit bcfd2734b6
4 changed files with 32 additions and 21 deletions

View file

@ -136,13 +136,24 @@ int parity_read(struct snapraid_parity_handle* handle, block_off_t pos, unsigned
int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigned char* block_buffer, unsigned block_size, int skip_fallocate);
/**
* Complete all pending I/O and sync the parity files.
*
* This waits for all parity writes, reports their errors, and flushes the
* parity data to disk. It also waits for all scheduled read-ahead to complete
* without consuming the results, which remain available to the caller.
* Wait for all scheduled read-ahead before writing content state.
* state_write() bypasses extent locks and may remove obsolete extents, while
* reader workers look up extents and update fs_last. They must finish first to
* avoid races. Completed results remain queued for later use.
*/
int state_barrier(struct snapraid_state* state, struct snapraid_io* io, struct snapraid_parity_handle* parity_handle, block_off_t blockcur);
void state_read_barrier(struct snapraid_io* io);
/**
* Establish the durability and read-ahead barrier before publishing sync state.
* Wait for parity writes and reject writer errors, then sync the parity files.
* Content must not record completed BLK/EMPTY states until their parity is
* durable. Finally wait for read-ahead so state_write() can access extent trees
* without racing reader workers; completed results remain queued for later use.
*
* If io is 0, only the parity files are synced. Return -1 on a write or sync
* failure; the caller must not publish completed state in that case.
*/
int state_write_barrier(struct snapraid_state* state, struct snapraid_io* io, struct snapraid_parity_handle* parity_handle, block_off_t blockcur);
#endif

View file

@ -976,6 +976,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
state_progress_stop(state);
msg_progress("Autosaving...\n");
state_read_barrier(&io);
state_write(state);
state_progress_restart(state);

View file

@ -6651,13 +6651,12 @@ void generate_configuration(const char* path)
state_done(&state);
}
/**
* Establish a durable I/O barrier: drain asynchronous writes, collect their
* errors, sync parity only if all writes succeeded, then quiesce read-ahead.
* A failed writer invalidates the whole barrier because parity must not be
* treated as coherent, or recorded in content state, after a failed write.
*/
int state_barrier(struct snapraid_state* state, struct snapraid_io* io, struct snapraid_parity_handle* parity_handle, block_off_t blockcur)
void state_read_barrier(struct snapraid_io* io)
{
io_quiesce(io);
}
int state_write_barrier(struct snapraid_state* state, struct snapraid_io* io, struct snapraid_parity_handle* parity_handle, block_off_t blockcur)
{
unsigned l;
@ -6689,7 +6688,7 @@ int state_barrier(struct snapraid_state* state, struct snapraid_io* io, struct s
/* complete all read-ahead without consuming the results */
if (io)
io_quiesce(io);
state_read_barrier(io);
return 0;
}

View file

@ -159,7 +159,7 @@
* 3. Sync reads current data and computes new parity. Blocks may already be
* changed to BLK/EMPTY in memory while parity writes are still pending.
*
* 4. Before an autosave publishes these completed states, state_barrier()
* 4. Before an autosave publishes these completed states, state_write_barrier()
* drains pending parity writes and synchronizes the parity files.
*
* 5. Only after that barrier may BLK/EMPTY progress be written to content.
@ -1730,7 +1730,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
state_progress_stop(state);
/* before spinning down flush all the caches */
ret = state_barrier(state, &io, parity_handle, blockcur);
ret = state_write_barrier(state, &io, parity_handle, blockcur);
if (ret == -1) {
/* LCOV_EXCL_START */
log_fatal(errno, "Stopping at block %" PRIu64 "\n", blockcur);
@ -1778,7 +1778,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
* Before writing the new content file we ensure that
* the parity is really written flushing the disk cache
*/
ret = state_barrier(state, &io, parity_handle, blockcur);
ret = state_write_barrier(state, &io, parity_handle, blockcur);
if (ret == -1) {
/* LCOV_EXCL_START */
log_fatal(EIO, "Stopping at block %" PRIu64 "\n", blockcur);
@ -1804,7 +1804,7 @@ end:
* Before returning we ensure that
* the parity is really written flushing the disk cache
*/
ret = state_barrier(state, &io, parity_handle, blockcur);
ret = state_write_barrier(state, &io, parity_handle, blockcur);
if (ret == -1) {
/* LCOV_EXCL_START */
log_fatal(errno, "Stopping at block %" PRIu64 "\n", blockcur);
@ -1859,7 +1859,7 @@ bail_durable:
* parity generated up to the previous block so the partial progress
* and any bad block markings can be made durable in the content file.
*/
ret = state_barrier(state, &io, parity_handle, blockcur);
ret = state_write_barrier(state, &io, parity_handle, blockcur);
if (ret == -1) {
/* LCOV_EXCL_START */
log_fatal(errno, "Stopping at block %" PRIu64 "\n", blockcur);
@ -2259,7 +2259,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
* Ensure any parity files created or resized prior to this branch
* are physically committed to disk before declaring sync durable.
*/
ret = state_barrier(state, 0, parity_handle, blockmax);
ret = state_write_barrier(state, 0, parity_handle, blockmax);
if (ret == -1) {
/* LCOV_EXCL_START */
++process_error;
@ -2304,7 +2304,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
* 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,
* If an asynchronous parity write, state_write_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.