From 3e3436c37860074dd0ebb8650206156f32848e8b Mon Sep 17 00:00:00 2001 From: Vlad Tudose Date: Thu, 6 Aug 2026 23:57:21 +0000 Subject: [PATCH] trimwrite: do not terminate between a trim and its paired write In trimwrite mode each block is trimmed and then written back by two consecutive io_us, paired via the per-file last_start markers in set_rw_ddir(). The do_io() termination checks (runtime_exceeded and the bytes_issued budget) run once per io_u with no awareness of the pairing, so a time_based or byte-bounded run can stop after issuing a trim but before its paired write: the final block is left deallocated ("dangling trim") and replay-based verification of the write_iolog false-fails with "bad magic" on that range. Reproduces on effectively every time_based trimwrite run. Defer termination while a pair is open so the loop issues exactly one more io_u (the paired write) and terminates on the next iteration. Validated on a loop device: 10/10 time_based runs ended on a dangling trim (trims == writes+1, iolog replay failing "bad magic") before, 0/10 after with replay passing; loops-bounded runs issue identical op counts (already pair-complete), so their behavior is unchanged. Fixes: https://github.com/axboe/fio/issues/2122 Signed-off-by: Vlad Tudose --- backend.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/backend.c b/backend.c index 35f522e597..d31c20fdfc 100644 --- a/backend.c +++ b/backend.c @@ -1150,6 +1150,17 @@ void on_fsync_completed(struct thread_data *td, struct io_u *io_u) * * Returns number of bytes written and trimmed. */ +static bool trimwrite_mid_pair(struct thread_data *td) +{ + struct fio_file *f; + + if (!td_trimwrite(td) || td->o.nr_files != 1) + return false; + + f = td->files[0]; + return f->last_start[DDIR_WRITE] != f->last_start[DDIR_TRIM]; +} + static void do_io(struct thread_data *td, uint64_t *bytes_done) { unsigned int i; @@ -1211,7 +1222,8 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) if (runtime_exceeded(td, &td->ts_cache)) { __update_ts_cache(td); - if (runtime_exceeded(td, &td->ts_cache)) { + if (runtime_exceeded(td, &td->ts_cache) && + !trimwrite_mid_pair(td)) { fio_mark_td_terminate(td); break; } @@ -1228,6 +1240,7 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) */ if (bytes_issued >= total_bytes && !td->o.read_iolog_file && + !trimwrite_mid_pair(td) && (!td->o.time_based || (td->o.time_based && td->o.verify != VERIFY_NONE))) break;