Write a test for store v1 & journal corruption preservation, fix MULTIPLE bugs

This commit is contained in:
Vitaliy Filippov
2026-06-14 11:23:36 +03:00
parent fd2b8b8792
commit 6a4627b625
7 changed files with 431 additions and 58 deletions
+44 -20
View File
@@ -71,6 +71,11 @@ bool journal_flusher_t::is_active()
return active_flushers > 0 || dequeuing;
}
size_t journal_flusher_t::get_queue_size()
{
return flush_queue.size();
}
void journal_flusher_t::loop()
{
target_flusher_count = bs->write_iodepth*2;
@@ -384,6 +389,7 @@ stop_flusher:
wait_state = 0;
return true;
}
copy_count = 0;
try_trim = true;
cur.oid = flusher->flush_queue.front();
cur.version = flusher->flush_versions[cur.oid];
@@ -511,6 +517,31 @@ resume_2:
{
uo_it->second.was_changed = true;
}
if (!bs->journal.inmemory)
{
// Verify journaled data checksums (but not COALESCED)
for (it = v.begin(); it != v.end(); it++)
{
if (it->copy_flags == COPY_BUF_JOURNAL)
{
iovec iov = { .iov_base = it->buf, .iov_len = it->len };
bs->verify_journal_checksums(
it->csum_buf, it->offset, &iov, 1,
[&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
{
printf(
"Checksum mismatch in object %jx:%jx v%ju in journal at 0x%jx, checksum block #%u: got %08x, expected %08x\n",
cur.oid.inode, cur.oid.stripe, cur.version, it->disk_offset,
bad_block / bs->dsk.csum_block_size, calc_csum, stored_csum
);
bad_block += it->offset;
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
mangle_csum_blocks.insert(bad_block);
}
);
}
}
}
}
// Submit data writes
for (it = v.begin(); it != v.end(); it++)
@@ -634,6 +665,7 @@ resume_2:
}
// All done
flusher->active_flushers--;
copy_count = 0; // used by is_mutated()...
wait_state = 0;
goto resume_0;
}
@@ -815,35 +847,21 @@ bool journal_flusher_co::clear_incomplete_csum_block_bits(int wait_base)
bs->verify_padded_checksums(new_clean_bitmap, new_clean_bitmap + 2*bs->dsk.clean_entry_bitmap_size,
v[i].offset, &iov, 1, [&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
{
printf("Checksum mismatch in object %jx:%jx v%ju in data area at offset 0x%jx+0x%x: got %08x, expected %08x\n",
printf("Checksum mismatch in object %jx:%jx v%ju in data area at offset 0x%jx+0x%x during flush: got %08x, expected %08x\n",
cur.oid.inode, cur.oid.stripe, old_clean_ver, old_clean_loc, bad_block, calc_csum, stored_csum);
for (uint32_t j = 0; j < bs->dsk.csum_block_size; j += bs->dsk.bitmap_granularity)
{
// Simplest method of mangling: flip one byte in every sector
((uint8_t*)v[i].buf)[j+bad_block-v[i].offset] ^= 0xff;
}
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
mangle_csum_blocks.insert(bad_block);
});
}
else
{
bs->verify_journal_checksums(v[i].csum_buf, v[i].offset, &iov, 1, [&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
{
printf("Checksum mismatch in object %jx:%jx v%ju in journal at offset 0x%jx+0x%x (block offset 0x%jx): got %08x, expected %08x\n",
printf("Checksum mismatch in object %jx:%jx v%ju in journal at offset 0x%jx+0x%x (block offset 0x%jx) during flush: got %08x, expected %08x\n",
cur.oid.inode, cur.oid.stripe, old_clean_ver,
v[i].disk_offset, bad_block, v[i].offset, calc_csum, stored_csum);
bad_block += (v[i].offset/bs->dsk.csum_block_size) * bs->dsk.csum_block_size;
uint32_t bad_block_end = bad_block + bs->dsk.csum_block_size + (v[i].offset/bs->dsk.csum_block_size) * bs->dsk.csum_block_size;
if (bad_block < v[i].offset)
bad_block = v[i].offset;
if (bad_block_end > v[i].offset+v[i].len)
bad_block_end = v[i].offset+v[i].len;
bad_block -= v[i].offset;
bad_block_end -= v[i].offset;
for (uint32_t j = bad_block; j < bad_block_end; j += bs->dsk.bitmap_granularity)
{
// Simplest method of mangling: flip one byte in every sector
((uint8_t*)v[i].buf)[j] ^= 0xff;
}
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
mangle_csum_blocks.insert(bad_block);
});
}
}
@@ -952,6 +970,11 @@ void journal_flusher_co::calc_block_checksums(uint32_t *new_data_csums, bool ski
}
// `v` should contain aligned items, possibly split into pieces
assert(!block_done);
for (uint32_t mangle_block: mangle_csum_blocks)
{
// Flip 1 bit
new_data_csums[mangle_block / bs->dsk.csum_block_size] ^= 1;
}
}
void journal_flusher_co::scan_dirty()
@@ -1118,6 +1141,7 @@ bool journal_flusher_co::read_dirty(int wait_base)
if (wait_state == wait_base) goto resume_0;
else if (wait_state == wait_base+1) goto resume_1;
wait_count = wait_journal_count = 0;
mangle_csum_blocks.clear();
if (bs->journal.inmemory && !read_to_fill_incomplete)
{
// Happy path: nothing to read :)
+2
View File
@@ -66,6 +66,7 @@ class journal_flusher_co
uint64_t clean_bitmap_offset, clean_bitmap_len;
uint8_t *clean_init_dyn_ptr;
uint8_t *new_clean_bitmap;
std::unordered_set<uint32_t> mangle_csum_blocks;
uint64_t new_trim_pos;
@@ -123,6 +124,7 @@ public:
void loop();
bool is_trim_wanted() { return trim_wanted; }
bool is_active();
size_t get_queue_size();
void mark_trim_possible();
void request_trim();
void release_trim();
+2 -1
View File
@@ -6,11 +6,12 @@
namespace v1 {
blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd)
blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd, bool mock_mode)
{
assert(sizeof(blockstore_op_private_t) <= BS_OP_PRIVATE_DATA_SIZE);
this->tfd = tfd;
this->ringloop = ringloop;
dsk.mock_mode = mock_mode;
ring_consumer.loop = [this]() { loop(); };
ringloop->register_consumer(&ring_consumer);
initialized = 0;
+5 -1
View File
@@ -30,6 +30,8 @@
//#define BLOCKSTORE_DEBUG
struct bs_test_t;
namespace v1 {
#include "journal.h"
@@ -122,6 +124,7 @@ typedef uint64_t pool_pg_id_t;
class blockstore_impl_t: public blockstore_i
{
friend struct ::bs_test_t;
blockstore_disk_t dsk;
/******* OPTIONS *******/
@@ -220,6 +223,7 @@ class blockstore_impl_t: public blockstore_i
// Read
int dequeue_read(blockstore_op_t *read_op);
void release_clean(blockstore_op_t *op);
void find_holes(std::vector<copy_buffer_t> & read_vec, uint32_t item_start, uint32_t item_end,
std::function<int(int, bool, uint32_t, uint32_t)> callback);
int fulfill_read(blockstore_op_t *read_op,
@@ -281,7 +285,7 @@ class blockstore_impl_t: public blockstore_i
public:
blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd);
blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd, bool mock_mode = false);
~blockstore_impl_t();
void parse_config(blockstore_config_t & config);
+86 -36
View File
@@ -101,8 +101,8 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *read_op,
.copy_flags = COPY_BUF_JOURNAL|COPY_BUF_CSUM_FILL,
.offset = blk_begin,
.len = blk_end-blk_begin,
.csum_buf = (csum + (blk_begin/dsk.csum_block_size -
item_start/dsk.csum_block_size) * (dsk.data_csum_type & 0xFF)),
.csum_buf = (!csum ? NULL : (csum + (blk_begin/dsk.csum_block_size -
item_start/dsk.csum_block_size) * (dsk.data_csum_type & 0xFF))),
.dyn_data = dyn_data,
});
if (dyn_data)
@@ -134,7 +134,7 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *read_op,
// If we don't track it then we may IN THEORY read another object's data:
// submit read -> remove the object -> flush remove -> overwrite with another object -> finish read
// Very improbable, but possible
PRIV(read_op)->clean_loc_used = 1;
PRIV(read_op)->clean_loc_used = UINT64_MAX;
}
rv.insert(rv.begin() + pos, el);
fulfilled += el.len;
@@ -178,7 +178,7 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
uint32_t end_block = 0;
while (start_block <= last_block)
{
if (read_range_fulfilled(rv, fulfilled, read_buf, clean_entry_bitmap,
if (read_range_fulfilled(rv, fulfilled, read_buf, from_journal ? NULL : clean_entry_bitmap,
start_block*dsk.csum_block_size < read_offset ? read_offset : start_block*dsk.csum_block_size,
(start_block+1)*dsk.csum_block_size > read_end ? read_end : (start_block+1)*dsk.csum_block_size))
{
@@ -190,7 +190,7 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
// Find a sequence of checksum blocks required to be read
end_block = start_block;
while ((end_block+1)*dsk.csum_block_size < read_end &&
!read_range_fulfilled(rv, fulfilled, read_buf, clean_entry_bitmap,
!read_range_fulfilled(rv, fulfilled, read_buf, from_journal ? NULL : clean_entry_bitmap,
(end_block+1)*dsk.csum_block_size < read_offset ? read_offset : (end_block+1)*dsk.csum_block_size,
(end_block+2)*dsk.csum_block_size > read_end ? read_end : (end_block+2)*dsk.csum_block_size))
{
@@ -202,7 +202,7 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
.copy_flags = COPY_BUF_CSUM_FILL | (from_journal ? COPY_BUF_JOURNALED_BIG : 0),
.offset = start_block*dsk.csum_block_size,
.len = (end_block-start_block)*dsk.csum_block_size,
// save clean_entry_bitmap if we're reading clean data from the journal
// save clean_entry_bitmap if we're reading clean data from the journal -- for checksums
.csum_buf = from_journal ? clean_entry_bitmap : NULL,
.dyn_data = dyn_data,
});
@@ -226,6 +226,11 @@ bool blockstore_impl_t::read_range_fulfilled(std::vector<copy_buffer_t> & rv, ui
{
if (alloc)
return 0;
if (!clean_entry_bitmap)
{
all_done = false;
return 0;
}
int diff = 0;
uint32_t bmp_start = cur_start/dsk.bitmap_granularity;
uint32_t bmp_end = cur_end/dsk.bitmap_granularity;
@@ -323,7 +328,7 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
{
iov[n_iov++] = (struct iovec){ (uint8_t*)op->buf+cur_start-op->offset, lim_end-cur_start };
rv.insert(rv.begin() + pos, (copy_buffer_t){
.copy_flags = COPY_BUF_DATA,
.copy_flags = COPY_BUF_DATA|COPY_BUF_COALESCED,
.offset = cur_start,
.len = lim_end-cur_start,
});
@@ -376,7 +381,7 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
{
// Reads running parallel to flushes of the same clean block may read
// a mixture of old and new data. So we don't verify checksums for such blocks.
PRIV(op)->clean_loc_used = 1;
PRIV(op)->clean_loc_used = UINT64_MAX;
}
return true;
}
@@ -515,6 +520,7 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
return 2;
undo_read:
// need to wait. undo added requests, don't dequeue op
release_clean(read_op);
if (dsk.csum_block_size > dsk.bitmap_granularity)
{
for (auto & vec: rv)
@@ -535,6 +541,32 @@ undo_read:
return 0;
}
void blockstore_impl_t::release_clean(blockstore_op_t *op)
{
if (PRIV(op)->clean_loc_used == UINT64_MAX)
{
PRIV(op)->clean_loc_used = 0;
}
if (PRIV(op)->clean_loc_used)
{
// Release clean data block
auto uo_it = used_clean_objects.find(PRIV(op)->clean_loc_used - 1);
if (uo_it != used_clean_objects.end())
{
uo_it->second.refs--;
if (uo_it->second.refs <= 0)
{
if (uo_it->second.was_freed)
{
data_alloc->set((PRIV(op)->clean_loc_used - 1) / dsk.data_block_size, false);
}
used_clean_objects.erase(uo_it);
}
}
PRIV(op)->clean_loc_used = 0;
}
}
int blockstore_impl_t::pad_journal_read(std::vector<copy_buffer_t> & rv, copy_buffer_t & cp,
// FIXME Passing dirty_entry& would be nicer
uint64_t dirty_offset, uint64_t dirty_end, uint64_t dirty_loc, uint8_t *csum_ptr, int *dyn_data,
@@ -615,7 +647,7 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
return false;
}
}
PRIV(read_op)->clean_loc_used = req > 0;
PRIV(read_op)->clean_loc_used = req > 0 ? UINT64_MAX : 0;
}
else if (from_journal)
{
@@ -679,13 +711,13 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
}
}
// Increment reference counter if clean data is being read from the disk
if (PRIV(read_op)->clean_loc_used)
if (PRIV(read_op)->clean_loc_used == UINT64_MAX)
{
auto & uo = used_clean_objects[clean_loc];
uo.refs++;
if (dsk.csum_block_size && flusher->is_mutated(clean_loc))
uo.was_changed = true;
PRIV(read_op)->clean_loc_used = clean_loc;
PRIV(read_op)->clean_loc_used = clean_loc + 1;
}
return true;
}
@@ -725,12 +757,18 @@ bool blockstore_impl_t::verify_padded_checksums(uint8_t *clean_entry_bitmap, uin
while (pos < iov[i].iov_len)
{
uint32_t start = pos;
uint8_t bit = (clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1;
while (pos < iov[i].iov_len && ((clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1) == bit)
uint8_t bit = 1;
if (clean_entry_bitmap)
{
pos += dsk.bitmap_granularity;
bmp_pos++;
bit = (clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1;
while (pos < iov[i].iov_len && ((clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1) == bit)
{
pos += dsk.bitmap_granularity;
bmp_pos++;
}
}
else
pos = iov[i].iov_len;
uint32_t len = pos-start;
auto buf = (uint8_t*)iov[i].iov_base+start;
while (block_done+len >= dsk.csum_block_size)
@@ -807,7 +845,7 @@ bool blockstore_impl_t::verify_clean_padded_checksums(blockstore_op_t *op, uint6
{
uint32_t offset = clean_loc % dsk.data_block_size;
if (from_journal)
return verify_padded_checksums(dyn_data, dyn_data + dsk.clean_entry_bitmap_size, offset, iov, n_iov, bad_block_cb);
return verify_padded_checksums(NULL, dyn_data + dsk.clean_entry_bitmap_size, offset, iov, n_iov, bad_block_cb);
clean_loc = (clean_loc / dsk.data_block_size) * dsk.data_block_size;
if (!dyn_data)
{
@@ -835,7 +873,7 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
void *meta_block = NULL;
if (dsk.csum_block_size > dsk.bitmap_granularity)
{
for (int i = rv.size()-1; i >= 0 && (rv[i].copy_flags & COPY_BUF_CSUM_FILL); i--)
for (int i = 0; i < rv.size(); i++)
{
if (rv[i].copy_flags & COPY_BUF_META_BLOCK)
{
@@ -845,8 +883,36 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
rv[i].buf = NULL;
continue;
}
struct iovec *iov = (struct iovec*)((uint8_t*)rv[i].buf + (rv[i].len & 0xFFFFFFFF));
int n_iov = rv[i].len >> 32;
if (rv[i].copy_flags & COPY_BUF_COALESCED)
{
// Sub-block shared with another read. Skip
continue;
}
if ((rv[i].copy_flags & COPY_BUF_JOURNAL) && journal.inmemory)
{
// Do not check journal checksums in-memory
continue;
}
iovec single_iov = {};
iovec *iov = NULL;
int n_iov = 0;
if (rv[i].copy_flags & COPY_BUF_CSUM_FILL)
{
// Padded, buffer list passed using a 'creepy way'
iov = (struct iovec*)((uint8_t*)rv[i].buf + (rv[i].len & 0xFFFFFFFF));
n_iov = rv[i].len >> 32;
}
else
{
// Not padded, buffer is fully within the input buffer
assert(op->buf);
assert(rv[i].csum_buf);
iov = &single_iov;
n_iov = 1;
assert(rv[i].offset >= op->offset);
assert(rv[i].offset + rv[i].len <= op->offset + op->len);
single_iov = { .iov_base = op->buf + rv[i].offset - op->offset, .iov_len = rv[i].len };
}
bool ok = true;
if (rv[i].copy_flags & COPY_BUF_JOURNAL)
{
@@ -944,23 +1010,7 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
meta_block = NULL;
}
}
if (PRIV(op)->clean_loc_used)
{
// Release clean data block
auto uo_it = used_clean_objects.find(PRIV(op)->clean_loc_used);
if (uo_it != used_clean_objects.end())
{
uo_it->second.refs--;
if (uo_it->second.refs <= 0)
{
if (uo_it->second.was_freed)
{
data_alloc->set(PRIV(op)->clean_loc_used / dsk.data_block_size, false);
}
used_clean_objects.erase(uo_it);
}
}
}
release_clean(op);
if (!journal.inmemory)
{
// Release journal sector usage