From 4b926e22232e4a7aaecad8ad29547fa780192837 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Mon, 11 May 2026 21:58:18 +0300 Subject: [PATCH] Add a test for missing data device fsyncs --- src/test/ringloop_mock.cpp | 13 ++++---- src/test/ringloop_mock.h | 3 +- src/test/test_blockstore.cpp | 64 +++++++++++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/test/ringloop_mock.cpp b/src/test/ringloop_mock.cpp index f2cc37fa..ed694cab 100644 --- a/src/test/ringloop_mock.cpp +++ b/src/test/ringloop_mock.cpp @@ -166,8 +166,9 @@ void ring_loop_mock_t::mark_completed(ring_data_t *data) wakeup(); } -disk_mock_t::disk_mock_t(size_t size, bool buffered) +disk_mock_t::disk_mock_t(const std::string & name, size_t size, bool buffered) { + this->name = name; this->size = size; this->data = (uint8_t*)malloc_or_die(size); this->buffered = buffered; @@ -242,7 +243,7 @@ void disk_mock_t::discard_buffers(bool all, uint32_t seed) if (all) { if (trace) - printf("disk: discard all buffers (%zu)\n", buffers.size()); + printf("%s: discard all buffers (%zu)\n", name.c_str(), buffers.size()); for (auto & b: buffers) free(b.second.iov_base); buffers.clear(); @@ -250,7 +251,7 @@ void disk_mock_t::discard_buffers(bool all, uint32_t seed) else { if (trace) - printf("disk: discard random buffers seed=%u\n", seed); + printf("%s: discard random buffers seed=%u\n", name.c_str(), seed); std::mt19937 rnd(seed); for (auto it = buffers.begin(); it != buffers.end(); ) { @@ -279,7 +280,7 @@ ssize_t disk_mock_t::copy_from_sqe(io_uring_sqe *sqe, uint8_t *to, uint64_t base } size_t cur = (off + v[i].iov_len > size ? size-off : v[i].iov_len); if (trace) - printf("disk: write %zu+%zu from %jx\n", off, cur, (uint64_t)v[i].iov_base); + printf("%s: write %zu+%zu from %jx\n", name.c_str(), off, cur, (uint64_t)v[i].iov_base); memcpy(to + off - base_offset, v[i].iov_base, cur); off += v[i].iov_len; } @@ -332,7 +333,7 @@ bool disk_mock_t::submit(io_uring_sqe *sqe) { size_t cur = (off + v[i].iov_len > size ? size-off : v[i].iov_len); if (trace) - printf("disk: read %zu+%zu to %jx\n", off, cur, (uint64_t)v[i].iov_base); + printf("%s: read %zu+%zu to %jx\n", name.c_str(), off, cur, (uint64_t)v[i].iov_base); if (buffers.size()) read_item((uint8_t*)v[i].iov_base, off, cur); else @@ -374,7 +375,7 @@ bool disk_mock_t::submit(io_uring_sqe *sqe) else if (sqe->opcode == IORING_OP_FSYNC) { if (trace) - printf("disk: fsync\n"); + printf("%s: fsync\n", name.c_str()); if (buffers.size()) { for (auto & b: buffers) diff --git a/src/test/ringloop_mock.h b/src/test/ringloop_mock.h index 639df98a..148f2d00 100644 --- a/src/test/ringloop_mock.h +++ b/src/test/ringloop_mock.h @@ -45,6 +45,7 @@ class disk_mock_t { uint8_t *data = NULL; std::map buffers; + std::string name; size_t size = 0; bool buffered = false; @@ -53,7 +54,7 @@ class disk_mock_t void read_item(uint8_t *to, uint64_t offset, uint64_t len); public: bool trace = false; - disk_mock_t(size_t size, bool buffered); + disk_mock_t(const std::string & name, size_t size, bool buffered); ~disk_mock_t(); void clear(size_t offset, size_t len); void discard_buffers(bool all, uint32_t seed); diff --git a/src/test/test_blockstore.cpp b/src/test/test_blockstore.cpp index cef438bc..7e40aa2c 100644 --- a/src/test/test_blockstore.cpp +++ b/src/test/test_blockstore.cpp @@ -106,13 +106,13 @@ struct bs_test_t } if (!data_disk) { - data_disk = new disk_mock_t(parse_size(config["data_device_size"]), config["disable_data_fsync"] != "1"); + data_disk = new disk_mock_t("data disk", parse_size(config["data_device_size"]), config["disable_data_fsync"] != "1"); data_disk->clear(0, parse_size(config["data_offset"])); } uint64_t meta_size = parse_size(config["meta_device_size"]); if (meta_size && !meta_disk) { - meta_disk = new disk_mock_t(meta_size, config["disable_meta_fsync"] != "1"); + meta_disk = new disk_mock_t("meta disk", meta_size, config["disable_meta_fsync"] != "1"); meta_disk->clear(0, meta_size); } if (!bs) @@ -230,6 +230,14 @@ static void test_simple() free(op.buf); } +static bool memcmp_byte(uint8_t *buf, uint8_t c, size_t len) +{ + for (size_t i = 0; i < len; i++) + if (buf[i] != c) + return false; + return true; +} + static void test_fsync(bool separate_meta) { printf("\n-- test_fsync%s\n", separate_meta ? " separate_meta" : ""); @@ -251,9 +259,9 @@ static void test_fsync(bool separate_meta) test.meta_disk->trace = 1; // Write - printf("writing\n"); + printf("writing 16K+4K v1\n"); blockstore_op_t op; - op.opcode = BS_OP_WRITE; + op.opcode = BS_OP_WRITE_STABLE; op.oid = { .inode = 1, .stripe = 0 }; op.version = 1; op.offset = 16384; @@ -313,6 +321,54 @@ static void test_fsync(bool separate_meta) assert(memcmp(op2.buf+16*1024, op.buf, 4*1024) == 0); assert(is_zero(op2.buf+20*1024, 108*1024)); + // Check fsync during compaction - do a small write + printf("writing 20K+4K v2\n"); + op.opcode = BS_OP_WRITE_STABLE; + op.oid = { .inode = 1, .stripe = 0 }; + op.version = 2; + op.offset = 20*1024; + op.len = 4096; + memset(op.buf, 0xab, 4096); + test.exec_op(&op); + assert(op.retval == op.len); + + op.opcode = BS_OP_SYNC; + test.exec_op(&op); + assert(op.retval == 0); + + // Check it by a read op + op2.version = UINT64_MAX; + test.exec_op(&op2); + assert(op2.retval == op2.len); + assert(is_zero(op2.buf, 16*1024)); + assert(memcmp_byte(op2.buf+16*1024, 0xaa, 4*1024)); + assert(memcmp_byte(op2.buf+20*1024, 0xab, 4*1024)); + assert(is_zero(op2.buf+24*1024, 104*1024)); + + // Trigger & wait compaction + test.bs->flusher->dump_diagnostics(); + test.bs->flusher->request_trim(); + while (test.bs->heap->get_compact_queue_size()) + test.ringloop->loop(); + while (test.bs->flusher->is_active()) + test.ringloop->loop(); + test.bs->flusher->release_trim(); + // Check that compaction succeeded + assert(!test.bs->heap->get_to_compact_count()); + + // Restart and check data again + test.destroy_bs(); + test.data_disk->discard_buffers(true, 0); + test.init(); + + op2.version = UINT64_MAX; + test.exec_op(&op2); + assert(op2.retval == op2.len); + assert(is_zero(op2.buf, 16*1024)); + assert(memcmp_byte(op2.buf+16*1024, 0xaa, 4*1024)); + assert(memcmp_byte(op2.buf+20*1024, 0xab, 4*1024)); // <- would be lost without data device fsync + assert(is_zero(op2.buf+24*1024, 104*1024)); + free(op.buf); free(op2.buf); }