Files
tromcho.net/src/test/test_heap.cpp
T
Vitaliy Filippov 172b106d99 Add entry_type to heap_object_t too
This is required to:
1) later inline the last "big_write" entry into the object to slightly reduce memory usage
2) eliminate an ugly hack where entry type is determined by its size
3) make the storage scheme extensible i.e. when adding new entry types
2025-11-23 19:08:24 +03:00

1678 lines
61 KiB
C++

// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../util/malloc_or_die.h"
#include "../util/allocator.h"
#include "blockstore_heap.h"
#include "../util/crc32c.h"
static int count_writes(heap_object_t *obj)
{
int n = 0;
for (auto wr = obj->get_writes(); wr; wr = wr->next())
{
n++;
}
return n;
}
#define FREE_SPACE_BIT 0x8000
bool check_used_space(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint32_t block_num)
{
uint8_t *data = heap.get_meta_block(block_num);
uint8_t *end = data+dsk.meta_block_size;
uint32_t used = 0;
while (data < end)
{
uint16_t region_marker = *((uint16_t*)data);
if (!(region_marker & FREE_SPACE_BIT))
{
used += region_marker;
}
data += (region_marker & ~FREE_SPACE_BIT);
}
return used == heap.get_meta_block_used_space(block_num);
}
int count_free_fragments(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint32_t block_num)
{
uint8_t *data = heap.get_meta_block(block_num);
uint8_t *end = data+dsk.meta_block_size;
int fragments = 0;
bool is_free = false;
while (data < end)
{
uint16_t region_marker = *((uint16_t*)data);
if ((region_marker & FREE_SPACE_BIT) && !is_free)
{
fragments++;
}
is_free = !!(region_marker & FREE_SPACE_BIT);
data += (region_marker & ~FREE_SPACE_BIT);
}
return fragments;
}
int _test_do_big_write(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint64_t inode, uint64_t stripe, uint64_t version, uint64_t location,
bool stable = true, uint32_t offset = 0, uint32_t len = 0, uint32_t *checksums = NULL)
{
if (!offset && !len)
len = dsk.data_block_size;
object_id oid = { .inode = INODE_WITH_POOL(1, inode), .stripe = stripe };
uint8_t wr_buf[heap.get_max_write_entry_size()];
heap_write_t *wr = (heap_write_t*)wr_buf;
wr->version = version;
wr->offset = offset;
wr->len = len;
wr->location = location;
wr->entry_type = BS_HEAP_BIG_WRITE | (stable ? BS_HEAP_STABLE : 0);
assert(heap.get_max_write_entry_size() >= wr->get_size(&heap));
assert(wr->get_size(&heap) == sizeof(heap_write_t) + 2*dsk.clean_entry_bitmap_size + (dsk.csum_block_size
? dsk.data_block_size/dsk.csum_block_size*4 : 0));
memset(wr->get_ext_bitmap(&heap), 0xff, dsk.clean_entry_bitmap_size);
memset(wr->get_int_bitmap(&heap), 0, dsk.clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(&heap), offset, len, dsk.bitmap_granularity);
if (dsk.csum_block_size)
{
if (checksums)
memcpy(wr->get_checksums(&heap), checksums, dsk.data_block_size/dsk.csum_block_size*4);
else
memset(wr->get_checksums(&heap), 0xde, dsk.data_block_size/dsk.csum_block_size*4);
}
uint32_t mblock, mfblock;
return heap.post_write(oid, wr, &mblock, &mfblock);
}
void _test_big_write(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint64_t inode, uint64_t stripe, uint64_t version, uint64_t location,
bool stable = true, uint32_t offset = 0, uint32_t len = 0, uint32_t *checksums = NULL)
{
heap.use_data(INODE_WITH_POOL(1, inode), location); // blocks are allocated before write and outside the heap_t
int res = _test_do_big_write(heap, dsk, inode, stripe, version, location, stable, offset, len, checksums);
assert(res == 0);
assert(heap.is_data_used(location));
}
int _test_do_small_write(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint64_t inode, uint64_t stripe, uint64_t version,
uint32_t offset, uint32_t len, uint64_t location, bool stable = true, uint32_t *checksums = NULL, bool is_intent = false,
uint32_t *mblock = NULL, uint32_t *mfblock = NULL)
{
object_id oid = { .inode = INODE_WITH_POOL(1, inode), .stripe = stripe };
uint8_t wr_buf[heap.get_max_write_entry_size()];
heap_write_t *wr = (heap_write_t*)wr_buf;
wr->version = version;
wr->offset = offset;
wr->len = len;
wr->location = location;
wr->entry_type = (is_intent ? BS_HEAP_INTENT_WRITE : BS_HEAP_SMALL_WRITE) | (stable ? BS_HEAP_STABLE : 0);
assert(wr->get_size(&heap) == sizeof(heap_write_t) + dsk.clean_entry_bitmap_size + (dsk.csum_block_size
? ((offset+len+dsk.csum_block_size-1)/dsk.csum_block_size - offset/dsk.csum_block_size)*4 : 4));
memset(wr->get_ext_bitmap(&heap), 0xff, dsk.clean_entry_bitmap_size);
assert(!wr->get_int_bitmap(&heap));
if (checksums)
{
if (dsk.csum_block_size)
memcpy(wr->get_checksums(&heap), checksums, wr->get_csum_size(&heap));
else
*wr->get_checksum(&heap) = *checksums;
}
else if (dsk.csum_block_size)
memset(wr->get_checksums(&heap), 0xab, ((offset+len+dsk.csum_block_size-1)/dsk.csum_block_size - offset/dsk.csum_block_size)*4);
else
*wr->get_checksum(&heap) = 0xabababab;
return heap.post_write(oid, wr, mblock, mfblock);
}
void _test_small_write(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint64_t inode, uint64_t stripe, uint64_t version,
uint32_t offset, uint32_t len, uint64_t location, bool stable = true, uint32_t *checksums = NULL, bool is_intent = false,
uint32_t *mblock = NULL, uint32_t *mfblock = NULL)
{
if (!is_intent)
heap.use_buffer_area(INODE_WITH_POOL(1, inode), location, len); // blocks are allocated before write and outside the heap_t
int res = _test_do_small_write(heap, dsk, inode, stripe, version, offset, len, location, stable, checksums, is_intent, mblock, mfblock);
assert(res == 0);
if (!is_intent)
assert(!heap.is_buffer_area_free(location, len));
}
void _test_init(blockstore_disk_t & dsk, bool csum)
{
std::map<std::string, std::string> config;
if (csum)
config["data_csum_type"] = "crc32c";
dsk.parse_config(config);
dsk.data_device = "data";
dsk.meta_device = "meta";
dsk.journal_device = "journal";
dsk.data_device_size = 1*1024*1024*1024;
dsk.meta_device_size = 4*1024*1024;
dsk.journal_device_size = 4*1024*1024;
dsk.data_fd = 0;
dsk.meta_fd = 1;
dsk.journal_fd = 2;
dsk.disable_journal_fsync = dsk.disable_meta_fsync = true;
dsk.calc_lengths();
}
void test_mvcc(bool csum)
{
blockstore_disk_t dsk;
_test_init(dsk, csum);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// write, read, modify, check basic mvcc
{
assert(_test_do_small_write(heap, dsk, 1, 0, 1, 0, 4096, 0) == EINVAL);
assert(heap.find_free_data() == 0);
_test_big_write(heap, dsk, 1, 0, 1, 0);
assert(heap.get_meta_block_used_space(0) == sizeof(heap_object_t) + sizeof(heap_write_t) +
2*dsk.clean_entry_bitmap_size + (dsk.csum_block_size ? dsk.data_block_size/dsk.csum_block_size*4 : 0));
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_used_space() == heap.get_meta_block_used_space(0));
assert(heap.find_free_data() == 0x20000);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint64_t copy_id = 0;
heap_object_t *obj = heap.lock_and_read_entry(oid, copy_id);
assert(obj);
assert(copy_id == 1);
assert(count_writes(obj) == 1);
heap_write_t *wr = obj->get_writes();
assert(wr->lsn == 1);
assert(wr->version == 1);
assert(wr->offset == 0);
assert(wr->len == dsk.data_block_size);
assert(wr->location == 0);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
uint64_t old_size = obj->size + wr->size;
assert(heap.read_locked_entry(oid, copy_id) == obj);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true);
obj = heap.read_entry(oid, NULL);
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == old_size + obj->get_writes()->get_size(&heap));
assert(_test_do_small_write(heap, dsk, 1, 0, 1, 0, 4096, 0) == EINVAL);
assert(!heap.read_locked_entry(oid, UINT64_MAX));
assert(heap.read_locked_entry(oid, copy_id) == obj); // small_write isn't MVCCed
_test_big_write(heap, dsk, 1, 0, 3, 0x20000);
obj = heap.read_entry(oid, NULL);
assert(count_writes(obj) == 1);
wr = obj->get_writes();
assert(wr->lsn == 3);
assert(wr->version == 3);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(heap.read_locked_entry(oid, copy_id) != obj); // big_write is MVCCed
obj = heap.read_locked_entry(oid, copy_id);
assert(count_writes(obj) == 2);
wr = obj->get_writes();
assert(wr->lsn == 2);
assert(!heap.unlock_entry(oid, UINT64_MAX));
assert(heap.unlock_entry(oid, copy_id));
}
printf("OK test_mvcc %s\n", csum ? "csum" : "no_csum");
}
void test_update(bool csum)
{
blockstore_disk_t dsk;
_test_init(dsk, csum);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
{
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x40000);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true);
}
printf("OK test_update %s\n", csum ? "csum" : "no_csum");
}
void test_delete(bool csum)
{
blockstore_disk_t dsk;
_test_init(dsk, csum);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
{
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x40000);
auto & space = heap.get_inode_space_stats();
assert(space.at(INODE_WITH_POOL(1, 1)) == 0x40000);
assert(heap.get_data_used_space() == 0x40000);
object_id oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
int res = heap.post_delete(oid, NULL, NULL);
assert(res == ENOENT);
uint32_t mblock = 100;
uint64_t new_lsn = 0;
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
res = heap.post_delete(oid, &new_lsn, &mblock);
assert(mblock == 0);
assert(res == 0);
heap.mark_lsn_completed(new_lsn);
uint64_t lsn = 0;
heap_object_t *obj = heap.lock_and_read_entry(oid, lsn);
assert(!obj);
}
printf("OK test_delete %s\n", csum ? "csum" : "no_csum");
}
void test_compact_block()
{
blockstore_disk_t dsk;
_test_init(dsk, true);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
dsk.meta_area_size = 4096*3;
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
uint32_t big_write_size = (sizeof(heap_object_t) + sizeof(heap_write_t) + 2*dsk.clean_entry_bitmap_size + dsk.data_block_size/dsk.csum_block_size*4);
uint32_t small_write_size = (sizeof(heap_write_t) + dsk.clean_entry_bitmap_size + 4);
assert(big_write_size == 198);
assert(small_write_size == 45);
uint32_t nwr = dsk.meta_block_size/(big_write_size+small_write_size);
{
for (uint32_t i = 0; i < nwr*2; i++)
{
_test_big_write(heap, dsk, 1, i*0x20000, 1, i*0x20000);
_test_small_write(heap, dsk, 1, i*0x20000, 2, 0, 4096, i*4096, true);
}
assert(_test_do_big_write(heap, dsk, 1, (nwr*2+1)*0x20000, 1, (nwr*2+1)*0x20000) == ENOSPC);
// Compact all small writes
for (uint32_t i = 0; i < nwr*2; i++)
{
int res = heap.compact_object((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = i*0x20000 }, 1000000, NULL);
assert(res == 0);
}
// Check fragmentation
assert(count_free_fragments(heap, dsk, 0) == nwr);
assert(count_free_fragments(heap, dsk, 1) == nwr);
// Write 3 more objects
_test_big_write(heap, dsk, 1, nwr*2*0x20000, 1, nwr*2*0x20000);
_test_big_write(heap, dsk, 1, (nwr*2+1)*0x20000, 1, (nwr*2+1)*0x20000);
_test_big_write(heap, dsk, 1, (nwr*2+2)*0x20000, 1, (nwr*2+2)*0x20000);
assert(count_free_fragments(heap, dsk, 0) == nwr+1);
assert(count_free_fragments(heap, dsk, 1) == 1);
}
printf("OK test_compact_block\n");
}
void test_compact(bool csum, bool stable)
{
int res;
blockstore_disk_t dsk;
_test_init(dsk, csum);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
memset(buffer_area.data(), 0x19, 4096);
uint32_t csums[dsk.data_block_size/(dsk.csum_block_size ? dsk.csum_block_size : 4096)] = {};
csums[0] = crc32c(0, buffer_area.data(), 4096);
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 4096, csums);
// write unstable - stabilize - compact
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint64_t copy_id = 0;
heap_object_t *obj = heap.lock_and_read_entry(oid, copy_id);
assert(obj);
assert(count_writes(obj) == 1);
assert(obj->get_writes()->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
uint8_t ref_int_bitmap[dsk.clean_entry_bitmap_size];
memset(ref_int_bitmap, 0, dsk.clean_entry_bitmap_size);
bitmap_set(ref_int_bitmap, 0, 4096, 4096);
assert(!memcmp(obj->get_writes()->get_int_bitmap(&heap), ref_int_bitmap, dsk.clean_entry_bitmap_size));
uint64_t old_size = obj->size + obj->get_writes()->size;
memset(buffer_area.data()+8192, 0xAA, 4096);
csums[2] = crc32c(0, buffer_area.data()+8192, 4096);
_test_small_write(heap, dsk, 1, 0, 3, 8192, 4096, 16384, stable, &csums[2]);
obj = heap.read_entry(oid, NULL);
uint64_t wr_size = obj->get_writes()->get_size(&heap);
assert(obj->get_writes()->lsn == 2);
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == old_size + wr_size);
_test_big_write(heap, dsk, 2, 0, 1, 0x40000, true, 0, 4096);
obj = heap.read_locked_entry(oid, copy_id);
assert(obj);
assert(count_writes(obj) == 2);
heap.mark_lsn_completed(1);
heap.mark_lsn_completed(2);
heap.mark_lsn_completed(3);
uint32_t mblock;
object_id compact_oid = {};
if (!stable)
{
res = heap.get_next_compact(compact_oid);
assert(res == ENOENT);
uint64_t new_lsn = 0, new_to_lsn = 0;
res = heap.post_stabilize({ .inode = INODE_WITH_POOL(1, 2), .stripe = 0 }, 3, NULL, &new_lsn, &new_to_lsn);
assert(res == ENOENT);
res = heap.post_stabilize(oid, 5, NULL, &new_lsn, &new_to_lsn);
assert(res == ENOENT);
res = heap.post_stabilize(oid, 1, &mblock, &new_lsn, &new_to_lsn);
assert(res == 0);
assert(new_lsn == 0);
assert(new_to_lsn == 0);
res = heap.post_stabilize(oid, 3, &mblock, &new_lsn, &new_to_lsn);
assert(res == 0);
assert(mblock == 0);
assert(new_lsn == 4);
assert(new_to_lsn == 4);
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == 2*old_size + wr_size);
heap.mark_lsn_completed(4);
}
assert(heap.get_to_compact_count() == 1);
res = heap.get_next_compact(compact_oid);
assert(res == 0);
assert(oid == compact_oid);
heap_write_t *compact_begin = NULL, *compact_end = NULL;
obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 2);
heap.get_compact_range(obj, 4, &compact_begin, &compact_end);
assert(compact_begin == obj->get_writes());
assert(compact_end == obj->get_writes()->next());
heap.mark_object_compacted(obj, 4);
assert(heap.get_compacted_lsn() == (stable ? 3 : 4));
assert(heap.get_to_compact_count() == 0);
res = heap.compact_object((object_id){ .inode = INODE_WITH_POOL(1, 3), .stripe = 0 }, compact_begin->lsn, NULL);
assert(res == ENOENT);
heap.mark_lsn_trimmed((stable ? 3 : 4));
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == 2*old_size);
obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 1);
assert(obj->get_writes()->version == 3);
bitmap_set(ref_int_bitmap, 8192, 4096, 4096);
assert(!memcmp(obj->get_writes()->get_int_bitmap(&heap), ref_int_bitmap, dsk.clean_entry_bitmap_size));
if (csum)
{
assert(heap.calc_checksums(obj->get_writes(), buffer_area.data(), false));
assert(!memcmp(obj->get_writes()->get_checksums(&heap), csums, dsk.data_block_size/dsk.csum_block_size*4));
}
obj = heap.read_entry({ .inode = INODE_WITH_POOL(1, 2), .stripe = 0 }, NULL);
assert(obj);
assert(count_writes(obj) == 1);
assert(obj->get_writes()->version == 1);
int unlock_res = heap.unlock_entry(oid, copy_id);
assert(unlock_res);
printf("OK test_compact %s %s\n", stable ? "stable" : "unstable", csum ? "csum" : "no_csum");
}
void test_modify_bitmap()
{
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
uint64_t copy_id = 0;
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.lock_and_read_entry(oid, copy_id);
assert(obj);
uint32_t modified_block = 1;
obj = heap.read_entry(oid, &modified_block, true);
assert(obj);
assert(modified_block == 0);
uint8_t *bmp = obj->get_writes()->get_int_bitmap(&heap);
bitmap_clear(bmp, 4096, 16384, dsk.bitmap_granularity);
obj->crc32c = obj->calc_crc32c();
uint8_t ref_int_bitmap[dsk.clean_entry_bitmap_size];
memset(ref_int_bitmap, 0xFF, dsk.clean_entry_bitmap_size);
obj = heap.read_locked_entry(oid, copy_id);
assert(obj);
assert(!memcmp(obj->get_writes()->get_int_bitmap(&heap), ref_int_bitmap, dsk.clean_entry_bitmap_size));
obj = heap.read_entry(oid, NULL);
assert(obj);
bitmap_clear(ref_int_bitmap, 4096, 16384, dsk.bitmap_granularity);
assert(!memcmp(obj->get_writes()->get_int_bitmap(&heap), ref_int_bitmap, dsk.clean_entry_bitmap_size));
int unlock_res = heap.unlock_entry(oid, copy_id);
assert(unlock_res);
printf("OK test_modify_bitmap\n");
}
void test_recheck(bool async, bool csum, bool intent)
{
blockstore_disk_t dsk;
_test_init(dsk, csum);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
std::vector<uint8_t> tmp;
memset(buffer_area.data(), 0xab, 12288);
uint32_t csum_4096 = crc32c(0, buffer_area.data(), 4096);
uint32_t csum_8192 = crc32c(0, buffer_area.data(), 8192);
uint32_t csum_12288 = crc32c(0, buffer_area.data(), 12288);
uint32_t three_csums[3] = { csum_4096, csum_4096, csum_4096 };
// write
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// object 1
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 8*1024, 16*1024, true, csum ? three_csums : &csum_8192, intent);
// object 2
_test_big_write(heap, dsk, 2, 0, 1, 0x40000);
_test_small_write(heap, dsk, 2, 0, 2, 8192, 12*1024, 24*1024, true, csum ? three_csums : &csum_12288, intent);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
memcpy(tmp.data(), heap.get_meta_block(0), dsk.meta_block_size);
}
// reload heap
{
memset(buffer_area.data()+16*1024, 0xab, 20*1024); // valid data
memset(buffer_area.data()+20*1024+64, 0xcc, 4); // invalid data in the second block of the first write
blockstore_heap_t heap(&dsk, async ? NULL : buffer_area.data(), 10);
heap.load_blocks(0, dsk.meta_block_size, tmp.data());
int calls = 0;
bool done = heap.recheck_small_writes([&](bool is_data, uint64_t offset, uint64_t len, uint8_t *buf, std::function<void()> cb)
{
calls++;
if (len)
{
if (!intent)
{
assert(!is_data);
assert(offset == 16384 && len == 8192 || offset == 24*1024 && len == 12*1024);
memcpy(buf, buffer_area.data()+offset, len);
}
else
{
assert(is_data);
assert(offset == 0x20000+8192 && len == 8192 || 0x40000+8192 && len == 12*1024);
memcpy(buf, buffer_area.data() + (offset == 0x20000+8192 ? 16*1024 : 24*1024), len);
}
assert(cb);
cb();
}
}, 1);
assert(done);
assert(calls == (async || intent ? 3 : 1));
heap.finish_load();
// read object 1 - big_write should be there but small_write should be rechecked and removed
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 1);
heap_write_t *wr = obj->get_writes();
assert(wr->lsn == 1);
assert(wr->version == 1);
assert(wr->offset == 0);
assert(wr->len == dsk.data_block_size);
assert(wr->location == 0x20000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
// read object 2 - both writes should be present
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 2);
wr = obj->get_writes();
assert(wr->lsn == 4);
assert(wr->version == 2);
assert(wr->offset == 8192);
assert(wr->len == 12*1024);
assert(wr->location == 24*1024);
assert(wr->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
}
printf("OK test_recheck %s %s %s\n", async ? "async" : "sync", csum ? "csum" : "no_csum", intent ? "intent" : "buffered");
}
void test_corruption()
{
int res;
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
std::vector<uint8_t> tmp;
// write
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// big_write
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
// big_write object 2
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x40000);
// big_write object 3
_test_big_write(heap, dsk, 1, 0x40000, 1, 0x60000);
// tombstone object 4
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x60000 };
uint8_t wr_buf[heap.get_max_write_entry_size()];
heap_write_t *wr = (heap_write_t*)wr_buf;
wr->version = 2;
wr->offset = 0;
wr->len = 0;
wr->location = 0;
wr->entry_type = BS_HEAP_TOMBSTONE|BS_HEAP_STABLE;
assert(!wr->get_checksums(&heap));
res = heap.post_write(oid, wr, NULL, NULL);
assert(res == 0);
// try to do a small_write over a tombstone to fail
wr->version = 3;
wr->entry_type = BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE;
res = heap.post_write(oid, wr, NULL, NULL);
assert(res == EINVAL);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
assert(heap.get_meta_block_used_space(1) == 0);
tmp.resize(dsk.meta_block_size);
memcpy(tmp.data(), heap.get_meta_block(0), dsk.meta_block_size);
}
// reload heap with corruption
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.set_abort_on_corruption(true);
tmp.data()[10]++; // corrupt the first object
heap.load_blocks(0, dsk.meta_block_size, tmp.data());
heap.finish_load();
// read object - object should be not present (checksum is invalid)
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint64_t lsn = 0;
heap_object_t *obj = heap.lock_and_read_entry(oid, lsn);
assert(!obj);
// object 2 should be present
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
obj = heap.lock_and_read_entry(oid, lsn);
assert(obj);
assert(count_writes(obj) == 1);
heap_write_t *wr = obj->get_writes();
assert(wr->location == 0x40000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
// object 3 should be present
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x40000 };
obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 1);
wr = obj->get_writes();
assert(wr->location == 0x60000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
// object 4 should be a tombstone
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x60000 };
obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 1);
wr = obj->get_writes();
assert(wr->entry_type == BS_HEAP_TOMBSTONE|BS_HEAP_STABLE);
}
printf("OK test_corruption\n");
}
void test_full_overwrite(bool stable)
{
int res;
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
// write
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// big_write
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
heap.mark_lsn_completed(1);
// read it to test mvcc
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint64_t copy_id = 0;
heap_object_t *obj = heap.lock_and_read_entry(oid, copy_id);
assert(obj);
// small_write
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true);
heap.mark_lsn_completed(2);
// big_write again
_test_big_write(heap, dsk, 1, 0, 3, 0x40000, stable, 16384, 4096);
assert(!heap.is_buffer_area_free(16384, 4096)); // should not be freed because MVCC includes it
assert(heap.is_data_used(0x20000)); // should NOT be freed - still referenced by MVCC
heap.mark_lsn_completed(3);
// free mvcc
heap.unlock_entry(oid, copy_id);
if (stable)
{
assert(!heap.is_data_used(0x20000)); // should now be freed
}
// small_write again
if (!stable)
{
res = _test_do_small_write(heap, dsk, 1, 0, 4, 20480, 4096, 20480, true);
assert(res == EINVAL);
}
_test_small_write(heap, dsk, 1, 0, 4, 20480, 4096, 20480, stable);
heap.mark_lsn_completed(4);
if (!stable)
{
res = heap.post_stabilize(oid, 4, NULL, NULL, NULL);
heap.mark_lsn_completed(5);
assert(res == 0);
}
// read object
obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 2);
heap_write_t *wr = obj->get_writes();
assert(wr->version == 4);
assert(wr->location == 20480);
assert(wr->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
wr = wr->next();
assert(wr->version == 3);
assert(wr->location == 0x40000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
// check that the data block 0x20000 is freed and 0x40000 is used
assert(!heap.is_data_used(0x20000));
assert(heap.is_data_used(0x40000));
assert(heap.is_buffer_area_free(16384, 4096));
assert(!heap.is_buffer_area_free(20480, 4096));
}
printf("OK test_full_overwrite %s\n", stable ? "stable" : "unstable");
}
void test_reshard_list()
{
int res;
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
// write
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x40000);
_test_big_write(heap, dsk, 1, 0x40000, 1, 0);
_test_big_write(heap, dsk, 2, 0x60000, 1, 0x60000);
_test_big_write(heap, dsk, 2, 0x60000, 2, 0x80000, false);
_test_small_write(heap, dsk, 2, 0x60000, 3, 4096, 4096, 0, false);
obj_ver_id *listing = NULL;
size_t stable_count = 0, unstable_count = 0;
res = heap.list_objects(1, (object_id){ .inode = INODE_WITH_POOL(0, 1) },
(object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == EINVAL);
res = heap.list_objects(1, (object_id){ .inode = INODE_WITH_POOL(1, 1) },
(object_id){ .inode = INODE_WITH_POOL(2, 1), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == EINVAL);
res = heap.list_objects(2, (object_id){ .inode = INODE_WITH_POOL(1, 1) },
(object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == EINVAL);
res = heap.list_objects(1, (object_id){ .inode = INODE_WITH_POOL(1, 1) },
(object_id){ .inode = INODE_WITH_POOL(1, UINT64_MAX), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == 0);
assert(stable_count == 4);
assert(unstable_count == 2);
free(listing);
listing = NULL;
res = heap.list_objects(1, (object_id){ .inode = INODE_WITH_POOL(1, 1) },
(object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == 0);
assert(stable_count == 3);
assert(unstable_count == 0);
free(listing);
listing = NULL;
heap.reshard(1, 2, 0x20000);
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = 0 }, NULL));
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 }, NULL));
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = 0x40000 }, NULL));
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 2), .stripe = 0x60000 }, NULL));
assert(!heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 2), .stripe = 0x80000 }, NULL));
res = heap.list_objects(3, (object_id){ .inode = INODE_WITH_POOL(1, 1) },
(object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == EINVAL);
res = heap.list_objects(1, (object_id){ .inode = INODE_WITH_POOL(1, 1) },
(object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == 0);
assert(stable_count == 2);
assert(unstable_count == 0);
free(listing);
listing = NULL;
res = heap.list_objects(2, (object_id){ .inode = INODE_WITH_POOL(1, 1) },
(object_id){ .inode = INODE_WITH_POOL(1, UINT64_MAX), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == 0);
assert(stable_count == 2);
assert(unstable_count == 2);
free(listing);
listing = NULL;
}
printf("OK test_reshard_list\n");
}
void _test_invalid_data_setup(blockstore_disk_t & dsk, std::vector<uint8_t> & buffer_area, std::vector<uint8_t> & tmp)
{
tmp.clear();
tmp.resize(dsk.meta_block_size*2);
heap_object_t *obj = (heap_object_t*)tmp.data();
obj->entry_type = BS_HEAP_OBJECT;
obj->size = sizeof(heap_object_t);
obj->inode = INODE_WITH_POOL(1, 1);
obj->write_pos = sizeof(heap_object_t);
heap_write_t *wr = obj->get_writes();
wr->next_pos = 0;
wr->lsn = 1;
wr->version = 1;
wr->entry_type = BS_HEAP_TOMBSTONE;
wr->size = sizeof(heap_write_t);
obj->crc32c = obj->calc_crc32c();
obj = (heap_object_t*)((uint8_t*)wr + wr->size);
obj->entry_type = BS_HEAP_OBJECT;
obj->size = sizeof(heap_object_t);
obj->inode = INODE_WITH_POOL(1, 3);
obj->stripe = 0;
obj->write_pos = sizeof(heap_object_t);
wr = obj->get_writes();
wr->lsn = 1;
wr->version = 1;
wr->entry_type = BS_HEAP_TOMBSTONE;
wr->size = sizeof(heap_write_t);
obj->crc32c = obj->calc_crc32c();
obj = (heap_object_t*)(tmp.data() + dsk.meta_block_size);
obj->entry_type = BS_HEAP_OBJECT;
obj->size = sizeof(heap_object_t);
obj->inode = INODE_WITH_POOL(1, 2);
obj->write_pos = sizeof(heap_object_t);
wr = obj->get_writes();
wr->lsn = 2;
wr->version = 1;
wr->entry_type = BS_HEAP_TOMBSTONE;
wr->size = sizeof(heap_write_t);
obj->crc32c = obj->calc_crc32c();
}
void test_invalid_data()
{
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
std::vector<uint8_t> tmp;
// Too small object
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
heap_object_t *obj = (heap_object_t*)tmp.data();
obj->size = sizeof(heap_object_t)-2;
*((uint16_t*)(tmp.data()+sizeof(heap_object_t)-2)) = 0x8002;
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*2, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
// Too large object
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
heap_object_t *obj = (heap_object_t*)tmp.data();
obj->size = dsk.meta_block_size+1;
obj->crc32c = obj->calc_crc32c();
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*2, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
// No writes
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
heap_object_t *obj = (heap_object_t*)tmp.data();
obj->write_pos = 0;
obj->crc32c = obj->calc_crc32c();
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*2, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 3), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
// Bad crc32c
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
heap_object_t *obj = (heap_object_t*)tmp.data();
obj->write_pos = sizeof(heap_object_t);
obj->crc32c = obj->calc_crc32c()+1;
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*2, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 3), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
// Bad write size
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
heap_object_t *obj = (heap_object_t*)tmp.data();
obj->get_writes()->size--;
obj->crc32c = obj->calc_crc32c();
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*2, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
// Bad write positions:
// 1) exceeds block back
// 2) exceeds block forward
// 3) intersects with object end
// 4) intersects with object beginning
for (int i = 0; i < 4; i++)
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
tmp.resize(dsk.meta_block_size*3);
memmove(tmp.data()+dsk.meta_block_size, tmp.data(), 2*dsk.meta_block_size);
memset(tmp.data(), 0, dsk.meta_block_size);
heap_object_t *obj = (heap_object_t*)(tmp.data() + dsk.meta_block_size + sizeof(heap_object_t) + sizeof(heap_write_t));
if (i == 0)
obj->write_pos = -(int16_t)(sizeof(heap_object_t)+sizeof(heap_write_t)+1);
else if (i == 1)
obj->write_pos = dsk.meta_block_size-sizeof(heap_object_t)-2*sizeof(heap_write_t)+1;
else if (i == 2)
obj->write_pos = -1;
else if (i == 3)
obj->write_pos = 5;
obj->crc32c = obj->calc_crc32c();
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*3, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 3), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
// Object write intersects with other writes
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
// Object2 Object1 BadLength Write2
uint8_t *nb = tmp.data() + dsk.meta_block_size;
memcpy(nb, tmp.data() + sizeof(heap_object_t) + sizeof(heap_write_t), sizeof(heap_object_t));
nb += sizeof(heap_object_t);
memcpy(nb, tmp.data(), sizeof(heap_object_t));
nb += sizeof(heap_object_t);
*((uint16_t*)nb) = sizeof(heap_write_t) + 4;
nb += 2;
memcpy(nb, tmp.data() + 2*sizeof(heap_object_t) + sizeof(heap_write_t), sizeof(heap_write_t));
heap_object_t *obj = (heap_object_t*)(tmp.data() + dsk.meta_block_size);
obj->write_pos = 2*sizeof(heap_object_t) + 2;
obj->crc32c = obj->calc_crc32c();
obj = (heap_object_t*)(tmp.data() + dsk.meta_block_size + sizeof(heap_object_t));
obj->write_pos = sizeof(heap_object_t);
obj->crc32c = obj->calc_crc32c();
memset(tmp.data(), 0, dsk.meta_block_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*2, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 3), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
// Write list entry exceeds block boundaries
for (int i = 0; i < 2; i++)
{
_test_invalid_data_setup(dsk, buffer_area, tmp);
tmp.resize(dsk.meta_block_size*3);
memmove(tmp.data()+dsk.meta_block_size, tmp.data(), 2*dsk.meta_block_size);
memset(tmp.data(), 0, dsk.meta_block_size);
heap_object_t *obj = (heap_object_t*)(tmp.data() + dsk.meta_block_size);
obj->get_writes()->next_pos = (i == 0 ? -sizeof(heap_object_t)-1 : dsk.meta_block_size - sizeof(heap_object_t) - sizeof(heap_write_t) + 1);
obj->crc32c = obj->calc_crc32c();
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.load_blocks(0, dsk.meta_block_size*3, tmp.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(!heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 3), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
assert(heap.read_entry(oid, NULL));
}
printf("OK test_invalid_data\n");
}
void test_destructor_mvcc()
{
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// some writes
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
// read it to test mvcc
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint64_t copy_id = 0;
heap_object_t *obj = heap.lock_and_read_entry(oid, copy_id);
assert(obj);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true);
}
printf("OK test_destructor_mvcc\n");
}
void test_rollback()
{
int res;
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
std::vector<uint8_t> tmp;
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// some writes
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
heap.mark_lsn_completed(1);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true);
heap.mark_lsn_completed(2);
// read it to test mvcc
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint64_t copy_id = 0;
heap_object_t *obj = heap.lock_and_read_entry(oid, copy_id);
assert(obj);
// already stable
uint64_t new_lsn = 0;
uint32_t mblock = 0;
res = heap.post_rollback(oid, 2, &new_lsn, &mblock);
assert(res == 0);
res = heap.post_rollback(oid, 1, &new_lsn, NULL);
assert(res == EBUSY);
// unstable writes
_test_big_write(heap, dsk, 1, 0, 3, 0x40000, false, 16384, 4096);
heap.mark_lsn_completed(3);
_test_small_write(heap, dsk, 1, 0, 4, 20480, 4096, 20480, false);
heap.mark_lsn_completed(4);
// second read
uint64_t copy2_id = 0;
obj = heap.lock_and_read_entry(oid, copy2_id);
assert(obj);
assert(copy2_id == copy_id);
// rollback
assert(heap.is_data_used(0x20000));
assert(heap.is_data_used(0x40000));
assert(!heap.is_buffer_area_free(16384, 4096));
assert(!heap.is_buffer_area_free(20480, 4096));
res = heap.post_rollback({ .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 }, 2, NULL, NULL);
assert(res == ENOENT);
res = heap.post_rollback(oid, 5, NULL, NULL);
assert(res == ENOENT);
res = heap.post_rollback(oid, 2, &new_lsn, NULL);
assert(res == 0);
heap.mark_lsn_completed(new_lsn);
assert(heap.is_data_used(0x20000));
assert(heap.is_data_used(0x40000));
assert(!heap.is_buffer_area_free(16384, 4096));
assert(!heap.is_buffer_area_free(20480, 4096));
// free mvcc
heap.unlock_entry(oid, copy2_id);
heap.unlock_entry(oid, copy_id);
assert(heap.is_data_used(0x20000));
assert(!heap.is_data_used(0x40000));
assert(!heap.is_buffer_area_free(16384, 4096));
assert(heap.is_buffer_area_free(20480, 4096));
// check object data
obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 2);
heap_write_t *wr = obj->get_writes();
assert(wr->version == 2);
assert(wr->location == 16384);
assert(wr->len == 4096);
assert(wr->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
wr = wr->next();
assert(wr->version == 1);
assert(wr->location == 0x20000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(heap.is_data_used(0x20000));
assert(!heap.is_data_used(0x40000));
assert(!heap.is_buffer_area_free(16384, 4096));
assert(heap.is_buffer_area_free(20480, 4096));
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x20000, false);
uint64_t new_lsn = 0;
res = heap.post_rollback(oid, 0, &new_lsn, NULL);
assert(res == 0);
heap.mark_lsn_completed(new_lsn);
assert(!heap.read_entry(oid, NULL));
assert(!heap.is_data_used(0x20000));
}
printf("OK test_rollback\n");
}
void test_alloc_buffer()
{
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
{
multilist_alloc_t alloc(2048, 31);
alloc.use(1998, 1);
alloc.verify();
alloc.use(70, 1);
alloc.verify();
alloc.use(206, 1);
alloc.verify();
}
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
uint64_t pos;
for (int i = 0; i < 4096/64; i++)
{
pos = heap.find_free_buffer_area(64*1024);
assert(pos == i*64*1024);
heap.use_buffer_area(1, pos, 64*1024);
assert(heap.get_buffer_area_used_space() == (i+1)*64*1024);
assert(!heap.is_buffer_area_free(i*64*1024+4096, 4096));
assert(heap.is_buffer_area_free(i*64*1024+4096, 0)); // zero length is always free
if (i < 4096/64-1)
assert(heap.is_buffer_area_free((i+1)*64*1024, 64*1024));
}
pos = heap.find_free_buffer_area(4096);
assert(pos == UINT64_MAX);
for (int i = 0; i < 4096/64/2; i++)
{
heap.free_buffer_area(1, i*2*64*1024, 64*1024);
assert(heap.get_buffer_area_used_space() == 4096*1024-(i+1)*64*1024);
}
for (int i = 0; i < 4096/64/2*16; i++)
{
pos = heap.find_free_buffer_area(4096);
assert(pos != UINT64_MAX);
heap.use_buffer_area(1, pos, 4096);
}
assert(heap.get_buffer_area_used_space() == 4096*1024);
pos = heap.find_free_buffer_area(4096);
assert(pos == UINT64_MAX);
for (int i = 0; i < 4096/64/2*16; i++)
{
heap.free_buffer_area(1, (i/16)*2*64*1024+4096*(i%16), 4096);
}
pos = heap.find_free_buffer_area(64*1024);
assert(pos != UINT64_MAX);
printf("OK test_alloc_buffer\n");
}
void test_full_alloc()
{
blockstore_disk_t dsk;
std::map<std::string, std::string> config;
config["data_csum_type"] = "crc32c";
dsk.parse_config(config);
dsk.data_device_size = 8*1024*1024;
dsk.meta_device_size = 5*4096;
dsk.journal_device_size = 4*1024*1024;
dsk.data_device = "data";
dsk.meta_device = "meta";
dsk.journal_device = "journal";
dsk.calc_lengths();
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
assert(heap.get_meta_total_space() == 4*4096);
uint32_t big_write_size = (sizeof(heap_object_t) + sizeof(heap_write_t) + 2*dsk.clean_entry_bitmap_size + dsk.data_block_size/dsk.csum_block_size*4);
uint32_t small_write_size = (sizeof(heap_write_t) + dsk.clean_entry_bitmap_size + 4);
assert(big_write_size == 198);
assert(small_write_size == 45);
uint32_t b_4s = (big_write_size + 4*small_write_size); // 377
uint32_t epb = (4096-800+b_4s-1)/b_4s; // entries per block
for (int j = 0; j < 4; j++)
{
assert(heap.get_meta_nearfull_blocks() == j);
for (int i = j*epb; i < j*epb+epb; i++)
{
_test_big_write(heap, dsk, 1, i*0x20000, 1, i*0x20000);
_test_small_write(heap, dsk, 1, i*0x20000, 2, 8192, 4096, i*16384, true);
_test_small_write(heap, dsk, 1, i*0x20000, 3, 8192, 4096, i*16384+4096, true);
_test_small_write(heap, dsk, 1, i*0x20000, 4, 8192, 4096, i*16384+2*4096, true);
_test_small_write(heap, dsk, 1, i*0x20000, 5, 8192, 4096, i*16384+3*4096, true);
assert(heap.get_meta_block_used_space(0) == (i < epb ? i+1 : epb)*b_4s);
assert(heap.get_meta_block_used_space(1) == (i < epb ? 0 : (i < 2*epb ? i+1-epb : epb)*b_4s));
assert(heap.get_meta_block_used_space(2) == (i < 2*epb ? 0 : (i < 3*epb ? i+1-2*epb : epb)*b_4s));
assert(heap.get_meta_block_used_space(3) == (i < 3*epb ? 0 : (i < 4*epb ? i+1-3*epb : epb)*b_4s));
}
}
// After filling all blocks to (4096-800), most free blocks should start to be allocated first
for (int i = 0; i < 8; i++)
{
assert(heap.get_meta_nearfull_blocks() == 4);
_test_big_write(heap, dsk, 1, (40+i)*0x20000, 1, (40+i)*0x20000);
}
for (int i = 0; i < 4; i++)
{
assert(heap.get_meta_block_used_space(i) == (epb*b_4s + big_write_size*2));
}
// New writes are prevented if it may lead to inability to overwrite any object
// - i.e. if the block doesn't have at least <max_overwrite_size> free space as the result
assert(_test_do_big_write(heap, dsk, 1, 48*0x20000, 1, 48*0x20000) == ENOSPC);
// Overwrites are, however, allowed until the block is almost empty
for (int i = 0; i < 6; i++)
{
assert(_test_do_small_write(heap, dsk, 1, 0, 6+i, 0, 4096, epb*4*16384+i*4096) == 0);
}
assert(dsk.meta_block_size-heap.get_meta_block_used_space(0) < big_write_size);
assert(_test_do_small_write(heap, dsk, 1, 0, 12, 0, 4096, 48*16384+8*4096) == EAGAIN);
// Check that used_alloc_queue doesn't return used blocks
{
// object from block 2
uint64_t new_lsn = 0;
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 11*0x20000 };
int res = heap.post_delete(oid, &new_lsn, NULL);
assert(res == 0);
heap.mark_lsn_completed(new_lsn);
uint32_t block_num = 0;
assert(!heap.read_entry(oid, &block_num));
_test_big_write(heap, dsk, 1, 11*0x20000, 6, 48*0x20000);
assert(heap.read_entry(oid, &block_num));
assert(block_num == 1);
}
printf("OK test_full_alloc\n");
}
void test_duplicate()
{
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
std::vector<uint8_t> tmp;
tmp.resize(dsk.meta_block_size*2);
// write
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// big_write
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
memcpy(tmp.data(), heap.get_meta_block(0), dsk.meta_block_size);
// update object
_test_big_write(heap, dsk, 1, 0, 2, 0x40000);
// persist again to block 2
assert(heap.get_meta_block_used_space(0) > 0);
memcpy(tmp.data()+dsk.meta_block_size, heap.get_meta_block(0), dsk.meta_block_size);
}
// reload heap with duplicate
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.set_abort_on_corruption(true);
heap.load_blocks(0, 2*dsk.meta_block_size, tmp.data());
heap.finish_load();
// read object - version 2 should be present
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 1);
heap_write_t *wr = obj->get_writes();
assert(wr->version == 2);
assert(wr->location == 0x40000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(heap.get_meta_block_used_space(0) == 0);
assert(heap.get_meta_block_used_space(1) == obj->size+wr->size);
assert(heap.is_data_used(0x40000));
assert(!heap.is_data_used(0x20000));
}
// reload heap with duplicate in different order
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.set_abort_on_corruption(true);
heap.load_blocks(dsk.meta_block_size, dsk.meta_block_size, tmp.data()+dsk.meta_block_size);
heap.load_blocks(0, dsk.meta_block_size, tmp.data());
heap.finish_load();
// read object - version 2 should be present
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 1);
heap_write_t *wr = obj->get_writes();
assert(wr->version == 2);
assert(wr->location == 0x40000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(heap.get_meta_block_used_space(0) == 0);
assert(heap.get_meta_block_used_space(1) == obj->size+wr->size);
assert(heap.is_data_used(0x40000));
assert(!heap.is_data_used(0x20000));
}
printf("OK test_duplicate\n");
}
void test_autocompact(bool csum)
{
blockstore_disk_t dsk;
_test_init(dsk, csum);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
std::vector<uint8_t> tmp;
tmp.resize(dsk.meta_block_size);
uint32_t big_write_size = 0, small_write_size = 0;
// write
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
// some writes
uint32_t buffer_csum = crc32c(0, buffer_area.data()+4*4096, 4096);
_test_big_write(heap, dsk, 1, 0, 1, 0x20000);
_test_small_write(heap, dsk, 1, 0, 2, 4096, 4096, 4*4096, true, &buffer_csum);
_test_small_write(heap, dsk, 1, 0, 3, 3*4096, 4096, 5*4096, true, &buffer_csum);
_test_small_write(heap, dsk, 1, 0, 4, 5*4096, 4096, 6*4096, true, &buffer_csum);
_test_small_write(heap, dsk, 1, 0, 5, 7*4096, 4096, 7*4096, true, &buffer_csum);
_test_big_write(heap, dsk, 1, 0x40000, 1, 0x60000);
// check lsn
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 5);
assert(obj->get_writes()->lsn == 5);
big_write_size = obj->get_writes()->next()->next()->next()->next()->get_size(&heap);
small_write_size = obj->get_writes()->get_size(&heap);
// persist
assert(heap.get_meta_block_used_space(0) == 2*sizeof(heap_object_t) + 2*big_write_size + 4*small_write_size);
memcpy(tmp.data(), heap.get_meta_block(0), dsk.meta_block_size);
}
// reload heap with autocompaction
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.set_abort_on_corruption(true);
heap.set_compacted_lsn(3);
assert(heap.get_compacted_lsn() == 3);
heap.load_blocks(0, dsk.meta_block_size, tmp.data());
heap.finish_load();
// read object - all entries should be present first...
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 3);
heap_write_t *wr = obj->get_writes();
assert(wr->lsn == 5);
assert(wr->version == 5);
assert(wr->offset == 7*4096);
assert(wr->len == 4096);
assert(wr->location == 7*4096);
assert(wr->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
wr = wr->next();
assert(wr->lsn == 4);
assert(wr->version == 4);
assert(wr->offset == 5*4096);
assert(wr->len == 4096);
assert(wr->location == 6*4096);
assert(wr->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
wr = wr->next();
assert(wr->lsn == 3);
assert(wr->version == 3);
assert(wr->offset == 0);
assert(wr->len == dsk.data_block_size);
assert(wr->location == 0x20000);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
// check that blocks are auto-freed
assert(heap.is_data_used(0x20000));
assert(heap.is_buffer_area_free(4*4096, 4096));
assert(heap.is_buffer_area_free(5*4096, 4096));
assert(!heap.is_buffer_area_free(6*4096, 4096));
assert(!heap.is_buffer_area_free(7*4096, 4096));
assert(heap.get_meta_block_used_space(0) == 2*sizeof(heap_object_t) + 2*big_write_size + 2*small_write_size);
}
printf("OK test_autocompact %s\n", csum ? "csum" : "no_csum");
}
void test_intent_write(bool csum)
{
blockstore_disk_t dsk;
_test_init(dsk, csum);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
memset(buffer_area.data(), 0xab, 4096);
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 4096);
heap.mark_lsn_completed(1);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 0, true, NULL, true);
heap.mark_lsn_completed(2);
_test_small_write(heap, dsk, 1, 0, 3, 16384, 4096, 0, true, NULL, true);
heap.mark_lsn_completed(3);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 2); // intent overwrites previous intent
assert(obj->get_writes()->lsn == 3);
assert(obj->get_writes()->next()->offset == 0);
assert(obj->get_writes()->next()->len == 12288);
uint8_t ref_int_bitmap[dsk.clean_entry_bitmap_size];
memset(ref_int_bitmap, 0, dsk.clean_entry_bitmap_size);
bitmap_set(ref_int_bitmap, 0, 4096, 4096);
bitmap_set(ref_int_bitmap, 8192, 4096, 4096);
assert(!memcmp(obj->get_writes()->next()->get_int_bitmap(&heap), ref_int_bitmap, dsk.clean_entry_bitmap_size));
if (csum)
{
uint8_t ref_csums[dsk.data_block_size/dsk.csum_block_size*4];
memset(ref_csums, 0xde, sizeof(ref_csums));
memset(ref_csums+8, 0xab, 4);
assert(!memcmp(obj->get_writes()->next()->get_checksums(&heap), ref_csums, sizeof(ref_csums)));
}
assert(check_used_space(heap, dsk, 0));
}
printf("OK test_intent_write %s\n", csum ? "csum" : "no_csum");
}
void test_move()
{
blockstore_disk_t dsk;
std::map<std::string, std::string> config;
config["data_csum_type"] = "crc32c";
dsk.parse_config(config);
dsk.data_device_size = 8*1024*1024;
dsk.meta_device_size = 5*4096;
dsk.journal_device_size = 4*1024*1024;
dsk.data_device = "data";
dsk.meta_device = "meta";
dsk.journal_device = "journal";
dsk.calc_lengths();
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_load();
assert(heap.get_meta_total_space() == 4*4096);
uint32_t big_write_size = (sizeof(heap_object_t) + sizeof(heap_write_t) + 2*dsk.clean_entry_bitmap_size + dsk.data_block_size/dsk.csum_block_size*4);
uint32_t small_write_size = (sizeof(heap_write_t) + dsk.clean_entry_bitmap_size + 4);
assert(big_write_size == 198);
assert(small_write_size == 45);
// Fill block 1 almost completely with unstable small writes
_test_big_write(heap, dsk, 1, 0*0x20000, 1, 0*0x20000);
_test_big_write(heap, dsk, 1, 1*0x20000, 1, 1*0x20000);
int i = 0;
while (i < (dsk.meta_block_size-2*big_write_size-2)/small_write_size/2)
{
_test_small_write(heap, dsk, 1, 0*0x20000, 2+i, (i*4096) % dsk.data_block_size, 4096, 2*i*4096, false);
_test_small_write(heap, dsk, 1, 1*0x20000, 2+i, (i*4096) % dsk.data_block_size, 4096, (2*i+1)*4096, false);
i++;
}
assert(heap.get_meta_block_used_space(0) > 0);
assert(!heap.get_meta_block_used_space(1));
assert(!heap.get_meta_block_used_space(2));
assert(!heap.get_meta_block_used_space(3));
// Next small_write should auto-move an object
uint32_t mblock = UINT32_MAX, mfblock = UINT32_MAX;
_test_small_write(heap, dsk, 1, 0*0x20000, 2+i, (i*4096) % dsk.data_block_size, 4096, 2*i*4096, false, NULL, false, &mblock, &mfblock);
assert(mblock == 1 && mfblock == 0);
assert(heap.get_meta_block_used_space(0) == big_write_size+i*small_write_size);
assert(heap.get_meta_block_used_space(1) == big_write_size+(i+1)*small_write_size);
// Check that the object is still readable
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_object_t *obj = heap.read_entry(oid, NULL);
assert(obj);
assert(count_writes(obj) == 2+i);
assert(obj->get_writes()->version == 2+i);
printf("OK test_move\n");
}
int main(int narg, char *args[])
{
test_mvcc(true);
test_mvcc(false);
test_update(true);
test_update(false);
test_delete(true);
test_delete(false);
test_compact_block();
test_compact(true, true);
test_compact(true, false);
test_compact(false, true);
test_compact(false, false);
test_modify_bitmap();
test_recheck(false, true, false);
test_recheck(false, false, false);
test_recheck(true, true, false);
test_recheck(true, false, false);
test_recheck(false, true, true);
test_recheck(false, false, true);
test_recheck(true, true, true);
test_recheck(true, false, true);
test_corruption();
test_full_overwrite(true);
test_full_overwrite(false);
test_reshard_list();
test_invalid_data();
test_destructor_mvcc();
test_rollback();
test_alloc_buffer();
test_full_alloc();
test_duplicate();
test_autocompact(true);
test_autocompact(false);
test_intent_write(true);
test_intent_write(false);
test_move();
return 0;
}