From bcfd2734b6eb7f779c3e08e37472b1a090a26b97 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Thu, 24 Sep 2026 19:41:52 +0200 Subject: [PATCH] Quiesce scrub read-ahead before autosave and distinguish read/write barriers --- cmdline/parity.h | 23 +++++++++++++++++------ cmdline/scrub.c | 1 + cmdline/state.c | 15 +++++++-------- cmdline/sync.c | 14 +++++++------- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/cmdline/parity.h b/cmdline/parity.h index 86c29a06..dab95dd7 100644 --- a/cmdline/parity.h +++ b/cmdline/parity.h @@ -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 diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 2344a442..481e6d1d 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -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); diff --git a/cmdline/state.c b/cmdline/state.c index 0924a8de..976dce98 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -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; } diff --git a/cmdline/sync.c b/cmdline/sync.c index 50ac681b..fda79533 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -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.