Add a test for missing data device fsyncs

This commit is contained in:
Vitaliy Filippov
2026-05-18 01:17:56 +03:00
parent a5d9a6996a
commit 4b926e2223
3 changed files with 69 additions and 11 deletions
+7 -6
View File
@@ -166,8 +166,9 @@ void ring_loop_mock_t::mark_completed(ring_data_t *data)
wakeup(); 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->size = size;
this->data = (uint8_t*)malloc_or_die(size); this->data = (uint8_t*)malloc_or_die(size);
this->buffered = buffered; this->buffered = buffered;
@@ -242,7 +243,7 @@ void disk_mock_t::discard_buffers(bool all, uint32_t seed)
if (all) if (all)
{ {
if (trace) 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) for (auto & b: buffers)
free(b.second.iov_base); free(b.second.iov_base);
buffers.clear(); buffers.clear();
@@ -250,7 +251,7 @@ void disk_mock_t::discard_buffers(bool all, uint32_t seed)
else else
{ {
if (trace) 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); std::mt19937 rnd(seed);
for (auto it = buffers.begin(); it != buffers.end(); ) 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); size_t cur = (off + v[i].iov_len > size ? size-off : v[i].iov_len);
if (trace) 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); memcpy(to + off - base_offset, v[i].iov_base, cur);
off += v[i].iov_len; 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); size_t cur = (off + v[i].iov_len > size ? size-off : v[i].iov_len);
if (trace) 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()) if (buffers.size())
read_item((uint8_t*)v[i].iov_base, off, cur); read_item((uint8_t*)v[i].iov_base, off, cur);
else else
@@ -374,7 +375,7 @@ bool disk_mock_t::submit(io_uring_sqe *sqe)
else if (sqe->opcode == IORING_OP_FSYNC) else if (sqe->opcode == IORING_OP_FSYNC)
{ {
if (trace) if (trace)
printf("disk: fsync\n"); printf("%s: fsync\n", name.c_str());
if (buffers.size()) if (buffers.size())
{ {
for (auto & b: buffers) for (auto & b: buffers)
+2 -1
View File
@@ -45,6 +45,7 @@ class disk_mock_t
{ {
uint8_t *data = NULL; uint8_t *data = NULL;
std::map<uint64_t, iovec> buffers; std::map<uint64_t, iovec> buffers;
std::string name;
size_t size = 0; size_t size = 0;
bool buffered = false; bool buffered = false;
@@ -53,7 +54,7 @@ class disk_mock_t
void read_item(uint8_t *to, uint64_t offset, uint64_t len); void read_item(uint8_t *to, uint64_t offset, uint64_t len);
public: public:
bool trace = false; 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(); ~disk_mock_t();
void clear(size_t offset, size_t len); void clear(size_t offset, size_t len);
void discard_buffers(bool all, uint32_t seed); void discard_buffers(bool all, uint32_t seed);
+60 -4
View File
@@ -106,13 +106,13 @@ struct bs_test_t
} }
if (!data_disk) 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"])); data_disk->clear(0, parse_size(config["data_offset"]));
} }
uint64_t meta_size = parse_size(config["meta_device_size"]); uint64_t meta_size = parse_size(config["meta_device_size"]);
if (meta_size && !meta_disk) 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); meta_disk->clear(0, meta_size);
} }
if (!bs) if (!bs)
@@ -230,6 +230,14 @@ static void test_simple()
free(op.buf); 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) static void test_fsync(bool separate_meta)
{ {
printf("\n-- test_fsync%s\n", separate_meta ? " 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; test.meta_disk->trace = 1;
// Write // Write
printf("writing\n"); printf("writing 16K+4K v1\n");
blockstore_op_t op; blockstore_op_t op;
op.opcode = BS_OP_WRITE; op.opcode = BS_OP_WRITE_STABLE;
op.oid = { .inode = 1, .stripe = 0 }; op.oid = { .inode = 1, .stripe = 0 };
op.version = 1; op.version = 1;
op.offset = 16384; 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(memcmp(op2.buf+16*1024, op.buf, 4*1024) == 0);
assert(is_zero(op2.buf+20*1024, 108*1024)); 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(op.buf);
free(op2.buf); free(op2.buf);
} }