Files
tromcho.net/src/test/test_heap.cpp
T

2730 lines
102 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(blockstore_heap_t & heap, heap_entry_t *obj)
{
int n = 0;
for (auto wr = obj; wr; wr = heap.prev(wr))
{
n++;
}
return n;
}
#define BS_HEAP_FREE_SPACE 0xAB8F
#define GARBAGE_BIT ((uint64_t)1 << 63)
bool check_used_space(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint32_t block_num)
{
uint8_t *buf = (uint8_t*)malloc_or_die(dsk.meta_block_size);
heap.get_meta_block(block_num, buf);
uint8_t *data = buf;
uint8_t *end = data+dsk.meta_block_size;
uint32_t used = 0;
while (data <= end-4)
{
heap_entry_t *wr = ((heap_entry_t*)data);
if (wr->entry_type == BS_HEAP_FREE_SPACE)
{
break;
}
if (!wr->is_garbage())
{
used += wr->size;
}
if (!wr->size)
{
break;
}
data += wr->size;
}
free(buf);
return used == heap.get_meta_block_used_space(block_num);
}
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, uint32_t offset, uint32_t len, uint8_t *data, uint32_t *mblock = NULL)
{
if (!offset && !len)
len = dsk.data_block_size;
object_id oid = { .inode = INODE_WITH_POOL(1, inode), .stripe = stripe };
heap_entry_t *obj = heap.read_entry(oid);
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0xff, dsk.clean_entry_bitmap_size);
return heap.add_big_write(oid, obj, stable, version, offset, len, location, ext_bitmap, data, mblock);
}
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, uint32_t offset, uint32_t len, uint8_t *data, uint32_t expected_mblock = 0)
{
heap.use_data(INODE_WITH_POOL(1, inode), location); // blocks are allocated before write and outside the heap_t
uint32_t mblock = 999999;
int res = _test_do_big_write(heap, dsk, inode, stripe, version, location, stable, offset, len, data, &mblock);
assert(res == 0);
assert(heap.is_data_used(location));
assert(mblock == expected_mblock || expected_mblock == UINT32_MAX);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
}
void _test_big_intent(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint64_t inode, uint64_t stripe, uint64_t version,
bool stable, uint32_t offset, uint32_t len, uint8_t *data, uint32_t expected_mblock = 0)
{
uint32_t mblock = 999999;
object_id oid = { .inode = INODE_WITH_POOL(1, inode), .stripe = stripe };
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0x8e, dsk.clean_entry_bitmap_size);
heap_entry_t *obj = heap.read_entry(oid);
int res = heap.add_big_intent(oid, &obj, version, offset, len, ext_bitmap, data, NULL, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
heap.complete_lsn_write(obj->lsn);
}
void _test_redirect_intent(blockstore_heap_t & heap, blockstore_disk_t & dsk, uint64_t inode, uint64_t stripe, uint64_t version, uint64_t location,
bool stable, uint32_t offset, uint32_t len, uint8_t *data, uint32_t expected_mblock = 0)
{
heap.use_data(INODE_WITH_POOL(1, inode), location); // blocks are allocated before write and outside the heap_t
uint32_t mblock = 999999;
object_id oid = { .inode = INODE_WITH_POOL(1, inode), .stripe = stripe };
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0x8e, dsk.clean_entry_bitmap_size);
heap_entry_t *obj = heap.read_entry(oid);
int res = heap.add_redirect_intent(oid, &obj, version, offset, len, location, ext_bitmap, data, &mblock);
assert(res == 0);
assert(mblock == expected_mblock || expected_mblock == UINT32_MAX);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
}
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, uint8_t *data, bool is_intent = false, uint32_t *mblock = NULL, heap_entry_t **obj = NULL)
{
object_id oid = { .inode = INODE_WITH_POOL(1, inode), .stripe = stripe };
heap_entry_t *local_obj;
if (!obj)
obj = &local_obj;
*obj = heap.read_entry(oid);
uint16_t type = (is_intent ? BS_HEAP_INTENT_WRITE : BS_HEAP_SMALL_WRITE) | (stable ? BS_HEAP_STABLE : 0);
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0xff, dsk.clean_entry_bitmap_size);
return heap.add_small_write(oid, obj, type, version, offset, len, location, ext_bitmap, data, mblock);
}
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, uint8_t *data, bool is_intent = false,
uint32_t expected_mblock = 0)
{
if (!is_intent)
heap.use_buffer_area(INODE_WITH_POOL(1, inode), location, len); // blocks are allocated before write and outside the heap_t
uint32_t mblock = 999999;
heap_entry_t *obj = NULL;
int res = _test_do_small_write(heap, dsk, inode, stripe, version, offset, len, location, stable, data, is_intent, &mblock, &obj);
assert(res == 0);
if (!is_intent)
assert(!heap.is_buffer_area_free(location, len));
assert(mblock == expected_mblock || expected_mblock == UINT32_MAX);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
heap.complete_lsn_write(obj->lsn);
}
void _test_init(blockstore_disk_t & dsk, bool csum, std::function<void(std::map<std::string, std::string> &)> cfg_cb = NULL)
{
std::map<std::string, std::string> config;
if (csum)
config["data_csum_type"] = "crc32c";
if (cfg_cb)
cfg_cb(config);
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(true);
}
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_recheck();
// write, read, modify, check basic mvcc
{
assert(_test_do_small_write(heap, dsk, 1, 0, 1, 0, 4096, 0, true, buffer_area.data()) == EINVAL);
assert(heap.find_free_data() == 0);
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 0, buffer_area.data());
assert(heap.get_meta_block_used_space(0) == heap.get_big_entry_size());
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 };
heap_entry_t *obj = heap.lock_and_read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->lsn == 1);
assert(obj->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(obj->version == 1);
assert(obj->big_location(&heap) == 0);
uint64_t old_size = obj->size;
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true, buffer_area.data()+16384, false);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 2);
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == old_size + obj->size);
assert(_test_do_small_write(heap, dsk, 1, 0, 1, 0, 4096, 0, true, buffer_area.data()) == EINVAL);
_test_big_write(heap, dsk, 1, 0, 3, 128*1024, true, 0, 0, buffer_area.data());
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 3);
assert(obj->lsn == 3);
assert(obj->version == 3);
assert(obj->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(count_writes(heap, heap.read_entry(oid)) == 3); // MVCC prevents GC of old entries
assert(heap.unlock_entry(oid));
assert(count_writes(heap, heap.read_entry(oid)) == 3); // Now we unlock it and old entries are GCed, but left in the list
}
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_recheck();
{
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 0, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true, buffer_area.data()+16384, false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
assert(count_writes(heap, heap.read_entry(oid)) == 2);
}
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_recheck();
{
// Add 1:0 and 1:20000
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x40000, true, 0, 0, buffer_area.data());
auto & space = heap.get_inode_space_stats();
assert(space.at(INODE_WITH_POOL(1, 1)) == 0x40000);
assert(heap.get_data_used_space() == 0x40000);
// Delete 1:0
uint32_t mblock = 999999;
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
assert(obj);
int res = heap.add_delete(obj, &mblock);
assert(mblock == 0);
assert(res == 0);
heap.start_block_write(mblock);
assert(space.at(INODE_WITH_POOL(1, 1)) == 0x40000);
assert(heap.get_data_used_space() == 0x40000);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
assert(obj->entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE));
assert(space.at(INODE_WITH_POOL(1, 1)) == 0x20000);
assert(heap.get_data_used_space() == 0x20000);
// Write version 1 over delete again
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
assert(obj->entry_type == (BS_HEAP_BIG_WRITE|BS_HEAP_STABLE));
// Delete it again...
res = heap.add_delete(obj, &mblock);
assert(mblock == 0);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(obj);
assert(heap.get_meta_block_used_space(0) == heap.get_big_entry_size() + heap.get_simple_entry_size());
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
obj = heap.read_entry(oid);
res = heap.add_delete(obj, &mblock);
assert(mblock == 0);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
// Now the trickiest part - check that the delete entry itself disappears
// when all previous entries disappear from the disk too. It happens only
// during block defragmentation so we fill the block 0 to 100%
assert(heap.get_meta_block_used_space(0) == heap.get_simple_entry_size());
int i = 0;
while (dsk.meta_block_size-heap.get_meta_block_used_space(0) >= heap.get_big_entry_size())
{
_test_big_write(heap, dsk, 2, 0x40000+0x20000*i, 1, 0x60000+0x20000*i, true, 0, 0, buffer_area.data());
i++;
}
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
obj = heap.read_entry(oid);
assert(!obj);
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
obj = heap.read_entry(oid);
assert(!obj);
// Check that inode 1 is removed from statistics
assert(space.find(INODE_WITH_POOL(1, 1)) == space.end());
}
printf("OK test_delete %s\n", csum ? "csum" : "no_csum");
}
void test_defrag_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_recheck();
uint32_t big_write_size = heap.get_big_entry_size();
uint32_t small_write_size = heap.get_small_entry_size(0, 4096);
assert(big_write_size == 180);
assert(small_write_size == 64);
uint32_t nwr = 0;
bool add = false;
if ((dsk.meta_block_size % (big_write_size+small_write_size)) >= big_write_size)
{
nwr = (dsk.meta_block_size / (big_write_size+small_write_size)) +
(dsk.meta_block_size-small_write_size) / (big_write_size+small_write_size);
add = (dsk.meta_block_size - small_write_size -
(dsk.meta_block_size-small_write_size) % (big_write_size+small_write_size)) >= big_write_size;
}
else
{
nwr = dsk.meta_block_size/(big_write_size+small_write_size)*2-1;
}
{
uint32_t used = 0;
uint32_t expected_block = 0;
for (uint32_t i = 0; i < nwr; i++)
{
_test_big_write(heap, dsk, 1, i*0x20000, 1, i*0x20000, true, 0, 0, buffer_area.data(), expected_block);
used += big_write_size;
if (dsk.meta_block_size-used < small_write_size)
{
used = 0;
expected_block++;
}
_test_small_write(heap, dsk, 1, i*0x20000, 2, 0, 4096, i*4096, true, buffer_area.data()+i*4096, false, expected_block);
used += small_write_size;
if (dsk.meta_block_size-used < big_write_size)
{
used = 0;
expected_block++;
}
}
if (add)
{
_test_big_write(heap, dsk, 1, nwr*0x20000, 1, nwr*0x20000, true, 0, 0, buffer_area.data(), 1);
used += big_write_size;
}
// The next write should be rejected because allowing it would block compaction
assert(_test_do_big_write(heap, dsk, 1, (nwr+1)*0x20000, 1, (nwr+1)*0x20000, true, 0, 0, buffer_area.data()) == ENOSPC);
// Compact all small writes
uint8_t bitmap[dsk.clean_entry_bitmap_size];
memset(bitmap, 0xFF, dsk.clean_entry_bitmap_size);
uint32_t mblock = 999999;
for (uint32_t i = 0; i < nwr; i++)
{
auto obj = heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = i*0x20000 });
assert(obj);
assert(heap.prev(obj)->entry_type == (BS_HEAP_BIG_WRITE|BS_HEAP_STABLE));
int res = heap.add_compact(obj, obj->version, obj->lsn, heap.prev(obj)->big_location(&heap),
false, &mblock, bitmap, bitmap, NULL);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
}
}
printf("OK test_defrag_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_recheck();
memset(buffer_area.data(), 0x19, 4096);
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 4096, buffer_area.data());
// write unstable - stabilize - compact
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->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_int_bitmap(&heap), ref_int_bitmap, dsk.clean_entry_bitmap_size));
uint64_t old_size = obj->size;
memset(buffer_area.data()+8192, 0xAA, 4096);
_test_small_write(heap, dsk, 1, 0, 3, 8192, 4096, 16384, stable, buffer_area.data()+8192, false);
obj = heap.read_entry(oid);
old_size += obj->get_size(&heap);
assert(obj->lsn == 2);
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == old_size);
object_id oid2 = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
_test_big_write(heap, dsk, 2, 0, 1, 0x40000, true, 0, 4096, buffer_area.data());
uint32_t mblock = 999999;
object_id compact_oid = {};
if (!stable)
{
assert(heap.get_compact_queue_size() == 0);
res = heap.get_next_compact(compact_oid);
assert(res == ENOENT);
auto obj2 = heap.read_entry(oid2);
res = heap.add_commit(obj2, 3, NULL);
assert(res == ENOENT);
res = heap.add_commit(obj2, 5, NULL);
assert(res == ENOENT);
auto obj = heap.read_entry(oid);
res = heap.add_commit(obj, 1, &mblock);
assert(res == 0); // already stable
res = heap.add_commit(obj, 3, &mblock);
assert(res == 0);
assert(mblock == 0);
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == old_size + heap.get_big_entry_size() + heap.get_simple_entry_size());
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
}
assert(heap.get_compacted_count() == 2);
assert(heap.get_to_compact_count() == 1);
assert(heap.get_compact_queue_size() == 1);
res = heap.get_next_compact(compact_oid);
assert(heap.get_compact_queue_size() == 0);
assert(res == 0);
assert(oid == compact_oid);
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == (stable ? 2 : 3));
int small_writes = 0;
heap_entry_t *small_wr = NULL;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_wr = wr;
small_writes++;
});
assert(compact_info.compact_lsn == (stable ? 2 : 4));
assert(compact_info.compact_version == 3);
assert(compact_info.clean_wr->lsn == 1);
assert(small_writes == 1);
assert(small_wr->lsn == 2);
bitmap_set(ref_int_bitmap, 8192, 4096, 4096);
{
size_t csum_count = dsk.data_block_size/(dsk.csum_block_size ? dsk.csum_block_size : 4096);
std::vector<uint32_t> csums(csum_count);
csums[0] = crc32c(0, buffer_area.data(), 4096);
csums[2] = crc32c(0, buffer_area.data()+8192, 4096);
res = heap.add_compact(obj, compact_info.compact_version, compact_info.compact_lsn,
compact_info.clean_wr->big_location(&heap), compact_info.do_delete,
&mblock, ref_int_bitmap, ref_int_bitmap, (uint8_t*)csums.data());
assert(res == 0);
}
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
assert(heap.get_to_compact_count() == 0);
assert(heap.get_compacted_count() == 3);
assert(check_used_space(heap, dsk, 0));
assert(heap.get_meta_block_used_space(0) == 2*heap.get_big_entry_size());
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == (stable ? 3 : 4));
assert(obj->version == 3);
assert(!memcmp(obj->get_int_bitmap(&heap), ref_int_bitmap, dsk.clean_entry_bitmap_size));
if (csum)
{
assert(heap.calc_checksums(obj, buffer_area.data(), false, 0, dsk.data_block_size));
size_t csum_count = dsk.data_block_size/(dsk.csum_block_size ? dsk.csum_block_size : 4096);
std::vector<uint32_t> csums(csum_count);
csums[0] = crc32c(0, buffer_area.data(), 4096);
csums[2] = crc32c(0, buffer_area.data()+8192, 4096);
assert(!memcmp(obj->get_checksums(&heap), csums.data(), dsk.data_block_size/dsk.csum_block_size*4));
}
obj = heap.read_entry({ .inode = INODE_WITH_POOL(1, 2), .stripe = 0 });
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->version == 1);
printf("OK test_compact %s %s\n", stable ? "stable" : "unstable", csum ? "csum" : "no_csum");
}
void test_iterate_compaction()
{
int res;
blockstore_disk_t dsk;
_test_init(dsk, false);
std::vector<uint8_t> tmp;
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// Case: BIG_STABLE(v1 l1) SMALL(v2 l2) SMALL(v3 l3) SMALL(v4 l4) COMMIT(v2 l5) SMALL(v5 l6) COMMIT(v5 l7)
uint32_t mblock = 0;
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 0, 4096, 0, false, buffer_area.data(), false);
_test_small_write(heap, dsk, 1, 0, 3, 4*1024, 4096, 4096, false, buffer_area.data(), false);
_test_small_write(heap, dsk, 1, 0, 4, 8*1024, 4096, 8*1024, false, buffer_area.data(), false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
res = heap.add_commit(obj, 2, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
_test_small_write(heap, dsk, 1, 0, 5, 12*1024, 4096, 12*1024, false, buffer_area.data(), false);
obj = heap.read_entry(oid);
res = heap.add_commit(obj, 5, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
assert(heap.get_fsynced_lsn() == 7);
int small_writes = 0;
obj = heap.read_entry(oid);
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == 7);
assert(compact_info.compact_version == 5);
assert(compact_info.clean_wr->lsn == 1);
assert(small_writes == 4);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
{
// reload heap and check that object state isn't changed and validation passes
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == 7);
assert(compact_info.compact_version == 5);
assert(compact_info.clean_wr->lsn == 1);
assert(small_writes == 4);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// Case: BIG_STABLE(v1 l1) SMALL(v2 l2) COMMIT(v2 l3) SMALL(v3 l4) COMMIT(v3 l5) unfinished
uint32_t mblock = 0;
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 0, 4096, 0, false, buffer_area.data(), false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
res = heap.add_commit(obj, 2, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
_test_small_write(heap, dsk, 1, 0, 3, 4*1024, 4096, 4096, false, buffer_area.data(), false);
obj = heap.read_entry(oid);
res = heap.add_commit(obj, 3, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
assert(heap.get_fsynced_lsn() == 4);
int small_writes = 0;
obj = heap.read_entry(oid);
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == 3);
assert(compact_info.compact_version == 2);
assert(compact_info.clean_wr->lsn == 1);
assert(small_writes == 1);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
{
// reload heap and check that object state isn't changed and validation passes
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == 5);
assert(compact_info.compact_version == 3);
assert(compact_info.clean_wr->lsn == 1);
assert(small_writes == 2);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// Case: BIG_STABLE(v1 l1) SMALL(v2 l2) SMALL(v3 l3) SMALL(v4 l4) ROLLBACK(v3 l5) COMMIT(v2 l6)
// -> compact by adding BIG_STABLE(v2 l2)
uint32_t mblock = 0;
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 0, 4096, 0, false, buffer_area.data(), false);
_test_small_write(heap, dsk, 1, 0, 3, 4096, 4096, 4096, false, buffer_area.data(), false);
_test_small_write(heap, dsk, 1, 0, 4, 8192, 4096, 8192, false, buffer_area.data(), false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
res = heap.add_rollback(obj, 3, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 5);
assert(obj->entry_type == BS_HEAP_ROLLBACK);
assert(obj->version == 3);
assert(obj->lsn == 5);
res = heap.add_commit(obj, 2, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
assert(heap.get_fsynced_lsn() == 6);
int small_writes = 0;
obj = heap.read_entry(oid);
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
assert(wr->lsn == 2);
small_writes++;
});
assert(compact_info.compact_lsn == 2);
assert(compact_info.compact_version == 2);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
assert(small_writes == 1);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
{
// reload heap and check that object state isn't changed and validation passes
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
assert(wr->lsn == 2);
small_writes++;
});
assert(compact_info.compact_lsn == 2);
assert(compact_info.compact_version == 2);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
assert(small_writes == 1);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// Case: BIG_STABLE(v1 l1) DELETE(l2) BIG_UNSTABLE(v1 l3) ROLLBACK(v0 l4)
// -> compact by adding DELETE(l4)
uint32_t mblock = 0;
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 4096, buffer_area.data());
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
res = heap.add_delete(obj, &mblock);
assert(mblock == 0);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
_test_big_write(heap, dsk, 1, 0, 1, 0, false, 0, 4096, buffer_area.data());
obj = heap.read_entry(oid);
res = heap.add_rollback(obj, 0, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 2);
assert(obj->entry_type == BS_HEAP_ROLLBACK);
assert(obj->version == 0);
uint64_t rollback_lsn = obj->lsn;
assert(heap.get_fsynced_lsn() == rollback_lsn);
int small_writes = 0;
obj = heap.read_entry(oid);
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == rollback_lsn);
assert(compact_info.compact_version == 0);
assert(compact_info.clean_wr == NULL);
assert(compact_info.do_delete);
assert(small_writes == 0);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
{
// reload heap and check that object state isn't changed and validation passes
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == 6);
assert(compact_info.compact_version == 0);
assert(compact_info.clean_wr == NULL);
assert(compact_info.do_delete);
assert(small_writes == 0);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// Case: BIG_STABLE(v1 l1) SMALL(v2 l2) SMALL(v3 l3) ROLLBACK(v2 l4) SMALL(v3 l5) COMMIT(v3 l6)
// -> compact by adding BIG_STABLE(v3 l6) and skip l3
uint32_t mblock = 0;
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 0, 4096, 0, false, buffer_area.data(), false);
_test_small_write(heap, dsk, 1, 0, 3, 4096, 4096, 4096, false, buffer_area.data(), false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
assert(obj->lsn == 3);
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 4);
_test_small_write(heap, dsk, 1, 0, 3, 8192, 4096, 8192, false, buffer_area.data(), false);
obj = heap.read_entry(oid);
assert(obj->lsn == 5);
res = heap.add_commit(obj, 3, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
assert(heap.get_fsynced_lsn() == 6);
int small_writes = 0;
obj = heap.read_entry(oid);
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
assert(wr->lsn == 2 || wr->lsn == 5);
small_writes++;
});
assert(compact_info.compact_lsn == 6);
assert(compact_info.compact_version == 3);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
assert(small_writes == 2);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
{
// reload heap and check that object state isn't changed and validation passes
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
assert(wr->lsn == 2 || wr->lsn == 5);
small_writes++;
});
assert(compact_info.compact_lsn == 6);
assert(compact_info.compact_version == 3);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
assert(small_writes == 2);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// Case: BIG_STABLE(v1 l1) SMALL_STABLE(v2 l2) BIG_UNSTABLE(v3 l3)
// -> skip compaction of l2 into l1 if not under pressure
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 0, 4096, 0, true, buffer_area.data(), false);
_test_big_write(heap, dsk, 1, 0, 3, 128*1024, false, 4096, 4096, buffer_area.data());
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == 0);
assert(compact_info.compact_version == 0);
assert(!compact_info.clean_wr);
assert(!compact_info.do_delete);
assert(small_writes == 0);
compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), true, [&](heap_entry_t *wr)
{
assert(wr->lsn == 2);
small_writes++;
});
assert(compact_info.compact_lsn == 2);
assert(compact_info.compact_version == 2);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
assert(small_writes == 1);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
{
// reload heap and check that object state isn't changed and validation passes
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), true, [&](heap_entry_t *wr)
{
assert(wr->lsn == 2);
small_writes++;
});
assert(compact_info.compact_lsn == 2);
assert(compact_info.compact_version == 2);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
assert(small_writes == 1);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// Case: BIG_STABLE(v1 l1) SMALL(v2 l2) SMALL(v3 l3) ROLLBACK(v2 l4) ROLLBACK(v1 l5)
// -> compact by adding BIG_STABLE(v1 l6) and skip l2 and l3
uint32_t mblock = 0;
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 0, 4096, 0, false, buffer_area.data(), false);
_test_small_write(heap, dsk, 1, 0, 3, 4096, 4096, 4096, false, buffer_area.data(), false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
assert(obj->lsn == 3);
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
res = heap.add_rollback(obj, 1, &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 5);
assert(heap.get_fsynced_lsn() == 5);
int small_writes = 0;
obj = heap.read_entry(oid);
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), false, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(small_writes == 0);
assert(compact_info.compact_lsn == 5);
assert(compact_info.compact_version == 1);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
{
// reload heap and check that object state isn't changed and validation passes
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
auto obj = heap.read_entry(oid);
int small_writes = 0;
auto compact_info = heap.iterate_compaction(obj, heap.get_fsynced_lsn(), true, [&](heap_entry_t *wr)
{
small_writes++;
});
assert(compact_info.compact_lsn == 5);
assert(compact_info.compact_version == 1);
assert(compact_info.clean_wr->lsn == 1);
assert(!compact_info.do_delete);
assert(small_writes == 0);
}
printf("OK test_iterate_compaction\n");
}
void test_modify_bitmap()
{
blockstore_disk_t dsk;
_test_init(dsk, true, [&](std::map<std::string, std::string> & config) { config["csum_block_size"] = "32k"; });
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
memset(buffer_area.data(), 0x19, 8192);
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 8192, buffer_area.data());
memset(buffer_area.data()+8192, 0xAA, 4096);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 8192, true, buffer_area.data()+8192, false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
uint8_t new_bmp[dsk.clean_entry_bitmap_size];
memcpy(new_bmp, heap.prev(obj)->get_int_bitmap(&heap), dsk.clean_entry_bitmap_size);
bitmap_clear(new_bmp, 4096, 32768-4096, dsk.bitmap_granularity);
uint8_t new_csums[dsk.data_block_size/32768*4];
memset(new_csums, 0, dsk.data_block_size/32768*4);
new_csums[0] = crc32c(0, buffer_area.data(), 4096);
uint32_t mblock = 999999;
int res = heap.punch_holes(heap.prev(obj), new_bmp, new_csums, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
assert(memcmp(heap.prev(obj)->get_int_bitmap(&heap), new_bmp, dsk.clean_entry_bitmap_size) == 0);
assert(memcmp(heap.prev(obj)->get_checksums(&heap), new_csums, dsk.data_block_size/32768*4) == 0);
printf("OK test_modify_bitmap\n");
}
void test_recheck(bool async, bool csum)
{
printf("test_recheck %s %s\n", async ? "async" : "sync", csum ? "csum" : "no_csum");
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);
// write
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// object 1 - two intent writes, both valid
_test_big_write(heap, dsk, 1, 0, 1, 0, true, 0, 8192, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 4*1024, 8*1024, 0, true, buffer_area.data(), true);
_test_small_write(heap, dsk, 1, 0, 3, 8*1024, 8*1024, 0, true, buffer_area.data(), true);
// object 2 - two intent writes, second invalid
_test_big_write(heap, dsk, 2, 0, 1, 0x20000, true, 0, 8192, buffer_area.data());
_test_small_write(heap, dsk, 2, 0, 2, 4*1024, 8*1024, 0, true, buffer_area.data(), true);
_test_small_write(heap, dsk, 2, 0, 3, 8*1024, 8*1024, 0, true, buffer_area.data(), true);
// object 3 - 2 valid small writes
_test_big_write(heap, dsk, 3, 0, 1, 0x40000, true, 0, 8192, buffer_area.data());
memset(buffer_area.data()+12*1024, 0xab, 8*1024);
_test_small_write(heap, dsk, 3, 0, 2, 4*1024, 8*1024, 12*1024, true, buffer_area.data());
memset(buffer_area.data()+20*1024, 0xab, 8*1024);
_test_small_write(heap, dsk, 3, 0, 3, 8*1024, 8*1024, 20*1024, true, buffer_area.data());
// object 4 - first valid and second invalid small write
_test_big_write(heap, dsk, 4, 0, 1, 0x60000, true, 0, 8192, buffer_area.data());
memset(buffer_area.data()+28*1024, 0xab, 8*1024);
_test_small_write(heap, dsk, 4, 0, 2, 4*1024, 8*1024, 28*1024, true, buffer_area.data());
memset(buffer_area.data()+36*1024, 0xab, 8*1024);
memset(buffer_area.data()+36*1024+4096+40, 0xcc, 40);
_test_small_write(heap, dsk, 4, 0, 3, 8*1024, 8*1024, 36*1024, true, buffer_area.data());
// object 5 - first invalid and second valid small write
_test_big_write(heap, dsk, 5, 0, 1, 0x80000, true, 0, 8192, buffer_area.data());
memset(buffer_area.data()+44*1024, 0xab, 8*1024);
memset(buffer_area.data()+44*1024+4096+40, 0xcc, 40);
_test_small_write(heap, dsk, 5, 0, 2, 4*1024, 8*1024, 44*1024, true, buffer_area.data());
memset(buffer_area.data()+52*1024, 0xab, 8*1024);
_test_small_write(heap, dsk, 5, 0, 3, 8*1024, 8*1024, 52*1024, true, buffer_area.data());
// object 6 - single big_intent write, valid
_test_redirect_intent(heap, dsk, 6, 0, 1, 0xA0000, true, 16384, 8192, buffer_area.data());
// object 7 - single big_intent write, invalid
_test_redirect_intent(heap, dsk, 7, 0, 1, 0xC0000, true, 16384, 8192, buffer_area.data());
// object 8 - big_write + big_intent write, valid
_test_big_write(heap, dsk, 8, 0, 1, 0xE0000, true, 0, 8192, buffer_area.data());
_test_big_intent(heap, dsk, 8, 0, 2, true, 16384, 8192, buffer_area.data());
// object 9 - big_write + big_intent write, invalid
_test_big_write(heap, dsk, 9, 0, 1, 0x100000, true, 0, 8192, buffer_area.data());
_test_big_intent(heap, dsk, 9, 0, 2, true, 16384, 8192, buffer_area.data());
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
// reload heap
{
memset(buffer_area.data()+16*1024, 0xab, 8*1024); // valid data for object 1
memset(buffer_area.data()+24*1024, 0xab, 12*1024); // valid data for object 1 write 1
memset(buffer_area.data()+36*1024, 0xab, 4*1024); // valid data for object 1 write 2
memset(buffer_area.data()+36*1024+64, 0xcc, 4); // invalid data for object 1 write 2
blockstore_heap_t heap(&dsk, async ? NULL : buffer_area.data(), 10);
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
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)
{
assert(len == 8*1024);
if (is_data)
{
// intent writes
if (offset == 8*1024) // valid
memcpy(buf, buffer_area.data(), len);
else if (offset == 0x20000+8*1024) // invalid
memset(buf, 0xcc, len);
else if (offset == 0xA0000+16*1024) // valid
memcpy(buf, buffer_area.data(), len);
else if (offset == 0xC0000+16*1024) // invalid
memset(buf, 0xcc, len);
else if (offset == 0xE0000+16*1024) // valid
memcpy(buf, buffer_area.data(), len);
else if (offset == 0x100000+16*1024) // invalid
memset(buf, 0xcc, len);
else
assert(0);
}
else
{
assert(offset == 12*1024 || offset == 20*1024 || offset == 28*1024 || offset == 36*1024 ||
offset == 44*1024 || offset == 52*1024);
memcpy(buf, buffer_area.data()+offset, len);
}
assert(cb);
cb();
}
}, 1);
assert(done);
assert(calls == (async ? 13 : 7));
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 1);
assert(mod[0] == 0);
// check objects
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 3);
assert(obj->lsn == 3);
assert(obj->entry_type == BS_HEAP_INTENT_WRITE|BS_HEAP_STABLE);
assert(obj->version == 3);
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
assert(obj->lsn == 5);
assert(obj->entry_type == BS_HEAP_INTENT_WRITE|BS_HEAP_STABLE);
assert(obj->version == 2);
oid = { .inode = INODE_WITH_POOL(1, 3), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 3);
assert(obj->lsn == 9);
assert(obj->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
assert(obj->version == 3);
assert(obj->small().offset == 8*1024);
assert(obj->small().len == 8*1024);
assert(obj->small().location == 20*1024);
oid = { .inode = INODE_WITH_POOL(1, 4), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
assert(obj->lsn == 11);
assert(obj->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
assert(obj->version == 2);
assert(obj->small().offset == 4*1024);
assert(obj->small().len == 8*1024);
assert(obj->small().location == 28*1024);
oid = { .inode = INODE_WITH_POOL(1, 5), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->lsn == 13);
assert(obj->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(obj->version == 1);
oid = { .inode = INODE_WITH_POOL(1, 6), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->lsn == 16);
assert(obj->entry_type == BS_HEAP_BIG_INTENT|BS_HEAP_STABLE);
assert(obj->version == 1);
oid = { .inode = INODE_WITH_POOL(1, 7), .stripe = 0 };
obj = heap.read_entry(oid);
assert(!obj);
oid = { .inode = INODE_WITH_POOL(1, 8), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->lsn == 19);
assert(obj->entry_type == BS_HEAP_BIG_INTENT|BS_HEAP_STABLE);
assert(obj->version == 2);
oid = { .inode = INODE_WITH_POOL(1, 9), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->lsn == 21); // lsn 20 is inserted for compaction
assert(obj->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(obj->version == 1);
// check space
assert(check_used_space(heap, dsk, 0));
}
printf("...OK\n");
}
void test_corruption()
{
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_recheck();
// big_write
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
// big_write object 2
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x40000, true, 0, 0, buffer_area.data());
// big_write object 3
_test_big_write(heap, dsk, 1, 0x40000, 1, 0x60000, true, 0, 0, buffer_area.data());
// 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);
heap.get_meta_block(0, tmp.data());
}
// reload heap with corruption
{
blockstore_heap_t heap(&dsk, buffer_area.data());
tmp.data()[10]++; // corrupt the first object
uint64_t entries_loaded;
assert(heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded) == EDOM);
}
// reload heap with bad entry size
{
blockstore_heap_t heap(&dsk, buffer_area.data());
auto entry = ((heap_entry_t*)tmp.data());
entry->size++;
entry->crc32c = entry->calc_crc32c();
uint64_t entries_loaded;
assert(heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded) == EDOM);
}
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_recheck();
// big_write
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
// read it to test mvcc
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.lock_and_read_entry(oid);
assert(obj);
// small_write
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true, buffer_area.data());
// big_write again
_test_big_write(heap, dsk, 1, 0, 3, 0x40000, stable, 16384, 4096, buffer_area.data());
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
// free mvcc
heap.unlock_entry(oid);
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, buffer_area.data());
assert(res == EINVAL);
}
_test_small_write(heap, dsk, 1, 0, 4, 20480, 4096, 20480, stable, buffer_area.data());
if (!stable)
{
auto obj = heap.read_entry(oid);
uint32_t mblock = 999999;
res = heap.add_commit(obj, 4, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
}
// read object
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == (stable ? 2 : 5));
assert(obj->version == 4);
assert((stable ? obj : heap.prev(obj))->type() == BS_HEAP_SMALL_WRITE);
assert((stable ? obj : heap.prev(obj))->small().location == 20480);
auto wr = stable ? heap.prev(obj) : heap.prev(heap.prev(obj));
assert(wr->version == 3);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(wr->big_location(&heap) == 0x40000);
if (!stable)
{
// old data block will be freed only after compaction on unstable overwrite
// it COULD be fixed but it complicates the logic and it seems we don't need it
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));
uint8_t bitmap[dsk.clean_entry_bitmap_size];
memset(bitmap, 0xFF, dsk.clean_entry_bitmap_size);
uint32_t mblock = 999999;
res = heap.add_compact(obj, obj->version, obj->lsn, wr->big_location(&heap),
false, &mblock, bitmap, bitmap, NULL);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
assert(heap.get_to_compact_count() == 0);
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));
}
else
{
// 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_recheck();
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x40000, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 1, 0x40000, 1, 0, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 2, 0x60000, 1, 0x60000, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 2, 0x60000, 2, 0x80000, false, 0, 0, buffer_area.data());
_test_small_write(heap, dsk, 2, 0x60000, 3, 4096, 4096, 0, false, buffer_area.data()+4096, 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;
void *st = heap.reshard_start(1, 2, 0x20000, 0);
assert(st == NULL);
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = 0 }));
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 }));
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 1), .stripe = 0x40000 }));
assert(heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 2), .stripe = 0x60000 }));
assert(!heap.read_entry((object_id){ .inode = INODE_WITH_POOL(1, 2), .stripe = 0x80000 }));
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_reshard_chunked()
{
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_recheck();
for (int i = 0; i < 30; i++)
_test_big_write(heap, dsk, 1, i*0x20000, 1, i*0x20000, true, 0, 0, buffer_area.data());
obj_ver_id *listing = NULL;
size_t stable_count = 0, unstable_count = 0;
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 == 30);
assert(unstable_count == 0);
free(listing);
listing = NULL;
assert(!heap.reshard_check(1, 2, 0x20000));
void *st = heap.reshard_start(1, 2, 0x20000, 10);
assert(st != NULL);
assert(!heap.reshard_check(1, 2, 0x20000));
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 == 0);
assert(unstable_count == 0);
free(listing);
listing = NULL;
bool done = heap.reshard_continue(st, 10);
assert(!done);
assert(!heap.reshard_check(1, 2, 0x20000));
done = heap.reshard_continue(st, 10);
assert(done);
assert(heap.reshard_check(1, 2, 0x20000));
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 == 15);
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, 1), .stripe = UINT64_MAX }, &listing, &stable_count, &unstable_count);
assert(res == 0);
assert(stable_count == 15);
assert(unstable_count == 0);
free(listing);
listing = NULL;
}
printf("OK test_reshard_chunked\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_recheck();
// some writes
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
// read it to test mvcc
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.lock_and_read_entry(oid);
assert(obj);
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true, buffer_area.data()+16384, false);
}
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_recheck();
// some writes
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, true, buffer_area.data()+16384, false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
uint32_t mblock = 0;
// already rolled back to 2
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
// can't be rolled back to 1
res = heap.add_rollback(obj, 1, NULL);
assert(res == EBUSY);
// unstable writes
_test_big_write(heap, dsk, 1, 0, 3, 0x40000, false, 16384, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 4, 20480, 4096, 20480, false, buffer_area.data()+16384, false);
obj = heap.read_entry(oid);
assert(obj);
// 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.add_rollback(obj, 5, NULL);
assert(res == 0);
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
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);
assert(obj);
assert(count_writes(heap, obj) == 5);
assert(obj->entry_type == BS_HEAP_ROLLBACK);
assert(obj->lsn == 5);
auto wr = heap.prev(obj);
assert(wr->version == 4);
assert(!(wr->entry_type & BS_HEAP_STABLE));
wr = heap.prev(wr);
assert(wr->version == 3);
assert(!(wr->entry_type & BS_HEAP_STABLE));
wr = heap.prev(wr);
assert(wr->version == 2);
assert(wr->lsn == 2);
assert(wr->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
assert(wr->small().location == 16384);
assert(wr->small().len == 4096);
wr = heap.prev(wr);
assert(wr->version == 1);
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(wr->big_location(&heap) == 0x20000);
// compact without rollback (can we do it at all?)
uint8_t bitmap[dsk.clean_entry_bitmap_size];
memset(bitmap, 0xFF, dsk.clean_entry_bitmap_size);
res = heap.add_compact(obj, 2, 2, wr->big_location(&heap),
false, &mblock, bitmap, bitmap, NULL);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 6);
assert(heap.get_to_compact_count() == 1);
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_recheck();
// Remove a big write at all
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
_test_big_write(heap, dsk, 1, 0x20000, 1, 0x20000, false, 0, 0, buffer_area.data());
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
uint32_t mblock = 999999;
res = heap.add_rollback(obj, 0, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
// Check that it's not present
int count = 0;
obj = heap.read_entry(oid);
heap.iterate_with_stable(obj, obj->lsn, [&](heap_entry_t *wr, bool stable)
{
count++;
return true;
});
assert(count == 0);
// But the data is still in place, removed only on compaction
assert(heap.is_data_used(0x20000));
res = heap.add_compact(obj, 0, 2, 0, true, &mblock, NULL, NULL, NULL);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
assert(!heap.is_data_used(0x20000));
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// v1 unstable -> v2 unstable -> v3 unstable -> rollback v2 -> rollback v1
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, false, 0, 0, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, false, buffer_area.data()+16384, false);
_test_small_write(heap, dsk, 1, 0, 3, 12*1024, 4096, 20480, false, buffer_area.data()+20480, false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
uint32_t mblock = 0;
// rollback to 2
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 4);
// rollback to 1
res = heap.add_rollback(obj, 1, &mblock);
assert(res == 0);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 5);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// v1 unstable -> v2 unstable -> v3 unstable -> rollback v2 -> rollback v1
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, false, 0, 0, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, false, buffer_area.data()+16384, false);
_test_small_write(heap, dsk, 1, 0, 3, 12*1024, 4096, 20480, false, buffer_area.data()+20480, false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
uint32_t mblock = 0;
// rollback to 2
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 4);
// rollback to 2 again (?!)
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 4);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// v1 unstable -> v2 unstable -> v3 unstable -> commit v1 -> commit v2 -> rollback v1
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, false, 0, 0, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 16384, false, buffer_area.data()+16384, false);
_test_small_write(heap, dsk, 1, 0, 3, 12*1024, 4096, 20480, false, buffer_area.data()+20480, false);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
uint32_t mblock = 0;
// commit 1
res = heap.add_commit(obj, 1, &mblock);
assert(res == 0);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 4);
// check stable writes
int count = 0;
heap.iterate_with_stable(obj, obj->lsn, [&](heap_entry_t *wr, bool stable)
{
assert(wr->lsn == 1 || wr->lsn == 2 || wr->lsn == 3);
assert(stable == (wr->lsn == 1));
count++;
return true;
});
assert(count == 3);
// commit 2
res = heap.add_commit(obj, 2, &mblock);
assert(res == 0);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 5);
// check stable writes
count = 0;
heap.iterate_with_stable(obj, obj->lsn, [&](heap_entry_t *wr, bool stable)
{
assert(wr->lsn == 2 || wr->lsn == 3);
assert(stable == (wr->lsn <= 2));
count++;
return (wr->lsn > 2);
});
assert(count == 2);
// rollback to 1 (should fail)
res = heap.add_rollback(obj, 1, &mblock);
assert(res == EBUSY);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
// v1 stable -> rollback v2
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
uint32_t mblock = 0;
// rollback to 2
res = heap.add_rollback(obj, 2, &mblock);
assert(res == 0);
}
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_recheck();
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 = 64*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(true);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
assert(heap.get_meta_total_space() == 4*4096);
uint32_t big_write_size = heap.get_big_entry_size();
uint32_t small_write_size = heap.get_small_entry_size(0, 4096);
assert(big_write_size == 180);
assert(small_write_size == 64);
uint32_t epb = dsk.meta_block_size/big_write_size;
for (int j = 0; j < 4; j++)
{
assert(heap.get_meta_nearfull_blocks() == j);
for (int i = j*epb; i < j*epb+epb-(j == 3); i++)
{
_test_big_write(heap, dsk, 1, i*0x20000, 1, i*0x20000, true, 0, 0, buffer_area.data(), j);
assert(heap.get_meta_block_used_space(0) == (i < epb ? i+1 : epb)*big_write_size);
assert(heap.get_meta_block_used_space(1) == (i < epb ? 0 : (i < 2*epb ? i+1-epb : epb)*big_write_size));
assert(heap.get_meta_block_used_space(2) == (i < 2*epb ? 0 : (i < 3*epb ? i+1-2*epb : epb)*big_write_size));
assert(heap.get_meta_block_used_space(3) == (i < 3*epb ? 0 : (i < 4*epb ? i+1-3*epb : epb)*big_write_size));
}
}
// New writes are prevented if it may block compaction i.e. if all blocks will have less than <big_entry_size> free space
assert(ENOSPC == _test_do_big_write(heap, dsk, 1, epb*4*0x20000, 1, epb*4*0x20000, true, 0, 0, buffer_area.data(), 0));
// We can still do some more overwrites into 3 of 4 nearfull blocks
int rest_fit = (big_write_size + dsk.meta_block_size % big_write_size)/small_write_size +
(dsk.meta_block_size % big_write_size)/small_write_size * 2;
for (int i = 0; i < rest_fit; i++)
{
_test_small_write(heap, dsk, 1, 1*0x20000, 5+i, 8192, 4096, (4*epb-1)*16384+3*4096+i*4096, true, buffer_area.data(), false, UINT32_MAX /*any block*/);
}
assert(ENOSPC == _test_do_small_write(heap, dsk, 1, 1*0x20000, 5+rest_fit, 8192, 4096, (4*epb-1)*16384+3*4096+rest_fit*4096, true, buffer_area.data(), false, 0));
printf("OK test_full_alloc\n");
}
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_recheck();
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 4096, buffer_area.data());
_test_small_write(heap, dsk, 1, 0, 2, 8192, 4096, 0, true, buffer_area.data(), true);
_test_small_write(heap, dsk, 1, 0, 3, 16384, 4096, 0, true, buffer_area.data(), true);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 3);
assert(obj->lsn == 3);
assert(heap.prev(heap.prev(obj))->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(check_used_space(heap, dsk, 0));
}
printf("OK test_intent_write %s\n", csum ? "csum" : "no_csum");
}
void test_big_intent_csums()
{
blockstore_disk_t dsk;
_test_init(dsk, true /*csum*/);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
memset(buffer_area.data(), 0xab, 4096);
memset(buffer_area.data()+4096, 0xac, 4096);
std::vector<uint8_t> tmp;
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 4096, buffer_area.data());
uint32_t mblock = 999999;
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0x8e, dsk.clean_entry_bitmap_size);
heap_entry_t *obj = heap.read_entry(oid);
int res = heap.add_big_intent(oid, &obj, 2, 32768, 4096, ext_bitmap, buffer_area.data()+4096, NULL, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
heap.complete_lsn_write(obj->lsn);
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
assert(heap.prev(obj)->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(obj->lsn == 2);
// verify csums
uint32_t ref_csums[dsk.data_block_size/4096];
memset(ref_csums, 0, dsk.data_block_size/4096*4);
ref_csums[0] = crc32c(0, buffer_area.data(), 4096);
ref_csums[8] = crc32c(0, buffer_area.data()+4096, 4096);
assert(!memcmp(obj->get_checksums(&heap), ref_csums, dsk.data_block_size/dsk.csum_block_size*4));
// verify bitmap
uint8_t ref_bmp[dsk.clean_entry_bitmap_size];
memset(ref_bmp, 0, dsk.clean_entry_bitmap_size);
bitmap_set(ref_bmp, 0, 4096, 4096);
bitmap_set(ref_bmp, 32768, 4096, 4096);
assert(!memcmp(obj->get_int_bitmap(&heap), ref_bmp, dsk.clean_entry_bitmap_size));
assert(!memcmp(obj->get_ext_bitmap(&heap), ext_bitmap, dsk.clean_entry_bitmap_size));
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
// reload heap to check that the write is still here
{
blockstore_heap_t heap(&dsk, buffer_area.data(), 10);
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
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)
{
assert(is_data);
assert(offset == 0x20000+32768 && len == 4096);
memcpy(buf, buffer_area.data()+4096, len);
assert(cb);
cb();
}
}, 1);
assert(done);
assert(calls == 2);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 1);
assert(mod[0] == 0);
// read object 1 - big_intent should be there
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->lsn == 2);
assert(obj->entry_type == BS_HEAP_BIG_INTENT|BS_HEAP_STABLE);
assert(obj->version == 2);
assert(obj->big_location(&heap) == 0x20000);
}
printf("OK test_big_intent_csums\n");
}
void test_recalc_stats()
{
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_recheck();
{
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 2, 0, 1, 0x40000, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 3, 0, 1, 0x60000, true, 0, 0, buffer_area.data());
uint32_t mblock = 999999;
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0x8e, dsk.clean_entry_bitmap_size);
heap_entry_t *obj = heap.read_entry(oid);
int res = heap.add_big_intent(oid, &obj, 2, 32768, 4096, ext_bitmap, buffer_area.data()+4096, NULL, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
heap.complete_lsn_write(obj->lsn);
auto & space = heap.get_inode_space_stats();
assert(space.size() == 3);
assert(heap.get_data_used_space() == 0x60000);
heap.set_no_inode_stats({1});
assert(space.size() == 1);
assert(space.at(INODE_WITH_POOL(1, 0)) == 0x60000);
heap.set_no_inode_stats({});
assert(space.size() == 3);
assert(space.at(INODE_WITH_POOL(1, 1)) == 0x20000);
assert(space.at(INODE_WITH_POOL(1, 2)) == 0x20000);
assert(space.at(INODE_WITH_POOL(1, 3)) == 0x20000);
}
printf("OK test_recalc_stats\n");
}
void test_redirect_intent_csums()
{
blockstore_disk_t dsk;
_test_init(dsk, true /*csum*/);
dsk.disable_journal_fsync = dsk.disable_meta_fsync = false;
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
memset(buffer_area.data(), 0xab, 4096);
memset(buffer_area.data()+4096, 0xac, 4096);
std::vector<uint8_t> tmp;
{
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 4096, buffer_area.data());
uint32_t mblock = 999999;
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0x8e, dsk.clean_entry_bitmap_size);
heap_entry_t *obj = heap.read_entry(oid);
int res = heap.add_redirect_intent(oid, &obj, 2, 32768, 4096, 0x40000, ext_bitmap, buffer_area.data()+4096, &mblock);
assert(res == 0);
assert(mblock == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
// persist
assert(heap.get_meta_block_used_space(0) > 0);
tmp.resize(dsk.meta_block_size);
heap.get_meta_block(0, tmp.data());
}
// reload heap to check that the write is still here
{
dsk.gc_on_start = false;
blockstore_heap_t heap(&dsk, buffer_area.data(), 10);
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
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)
{
assert(is_data);
assert(offset == 0x40000+32768 && len == 4096);
memcpy(buf, buffer_area.data()+4096, len);
assert(cb);
cb();
}
}, 1);
assert(done);
assert(calls == 2);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
// read object 1 - big_intent should be there
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 2);
assert(obj->lsn == 2);
assert(obj->entry_type == BS_HEAP_BIG_INTENT|BS_HEAP_STABLE);
assert(obj->version == 2);
assert(obj->big_location(&heap) == 0x40000);
// verify csums
uint32_t ref_csums[dsk.data_block_size/4096];
memset(ref_csums, 0, dsk.data_block_size/4096*4);
ref_csums[8] = crc32c(0, buffer_area.data()+4096, 4096);
assert(!memcmp(obj->get_checksums(&heap), ref_csums, dsk.data_block_size/dsk.csum_block_size*4));
// verify bitmap
uint8_t ref_bmp[dsk.clean_entry_bitmap_size];
memset(ref_bmp, 0, dsk.clean_entry_bitmap_size);
bitmap_set(ref_bmp, 32768, 4096, 4096);
assert(!memcmp(obj->get_int_bitmap(&heap), ref_bmp, dsk.clean_entry_bitmap_size));
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0x8e, dsk.clean_entry_bitmap_size);
assert(!memcmp(obj->get_ext_bitmap(&heap), ext_bitmap, dsk.clean_entry_bitmap_size));
}
// reload heap to check that the write is removed if data is invalid
{
blockstore_heap_t heap(&dsk, buffer_area.data(), 10);
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
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)
{
memset(buf, 0xaa, len);
assert(cb);
cb();
}
}, 1);
assert(done);
assert(calls == 2);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 1);
assert(mod[0] == 0);
// read object 1 - big_intent should be absent
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 1);
assert(obj->lsn == 1);
assert(obj->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
assert(obj->version == 1);
assert(obj->big_location(&heap) == 0x20000);
}
printf("OK test_redirect_intent_csums\n");
}
void test_explicit_complete()
{
blockstore_disk_t dsk;
_test_init(dsk, false);
dsk.disable_journal_fsync = dsk.disable_meta_fsync = false;
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
blockstore_heap_t heap(&dsk, buffer_area.data());
heap.finish_recheck();
{
_test_big_write(heap, dsk, 1, 0, 1, 0x20000, true, 0, 0, buffer_area.data());
assert(heap.get_completed_lsn() == 1);
_test_big_write(heap, dsk, 1, 0, 2, 0x40000, true, 0, 0, buffer_area.data());
_test_big_write(heap, dsk, 1, 0, 3, 0x60000, true, 0, 0, buffer_area.data());
assert(heap.get_completed_lsn() == 3);
assert(heap.is_lsn_completed(3));
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 3);
assert(heap.get_fsynced_lsn() == 0);
heap.mark_lsn_fsynced(3);
assert(heap.get_fsynced_lsn() == 3);
_test_big_write(heap, dsk, 2, 0, 1, 0x80000, true, 0, 0, buffer_area.data());
assert(heap.get_completed_lsn() == 6);
assert(heap.is_lsn_completed(6));
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 3);
// now fsync GC
heap.mark_lsn_fsynced(6);
obj = heap.read_entry(oid);
assert(count_writes(heap, obj) == 1);
uint32_t mblock = 999999;
uint8_t ext_bitmap[dsk.clean_entry_bitmap_size];
memset(ext_bitmap, 0xff, dsk.clean_entry_bitmap_size);
int res = heap.add_small_write(oid, &obj, BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE, 4, 0, 4096, 4096, ext_bitmap, buffer_area.data(), &mblock);
assert(res == 0);
// test explicit_complete - do not complete_lsn_write()
assert(heap.get_completed_lsn() == 6);
oid = { .inode = INODE_WITH_POOL(1, 2), .stripe = 0 };
heap_entry_t *obj2 = heap.read_entry(oid);
res = heap.add_small_write(oid, &obj2, BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE, 5, 0, 4096, 8192, ext_bitmap, buffer_area.data(), &mblock);
assert(res == 0);
heap.start_block_write(mblock);
heap.complete_block_write(mblock);
heap.complete_lsn_write(8);
assert(heap.get_completed_lsn() == 6);
assert(heap.is_lsn_completed(8));
heap.complete_lsn_write(7);
assert(heap.get_completed_lsn() == 8);
assert(heap.is_lsn_completed(7));
}
printf("OK test_explicit_complete\n");
}
void test_skip_double_claim()
{
blockstore_disk_t dsk;
_test_init(dsk, false);
dsk.skip_double_claim = true;
std::vector<uint8_t> tmp(dsk.meta_block_size);
std::vector<uint8_t> out(dsk.meta_block_size*3);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
heap_entry_t *wr1 = NULL, *wr2 = NULL, *wr3 = NULL, *wr4 = NULL;
uint32_t total_size = 0;
{
blockstore_heap_t heap(&dsk, buffer_area.data());
wr1 = (heap_entry_t*)(tmp.data() + total_size);
wr1->size = heap.get_big_entry_size();
wr1->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
wr1->lsn = 1;
wr1->inode = INODE_WITH_POOL(1, 1);
wr1->stripe = 0;
wr1->version = 1;
wr1->set_big_location(&heap, 0x40000); // <-- overwritten
wr1->crc32c = wr1->calc_crc32c();
total_size += wr1->size;
wr2 = (heap_entry_t*)(tmp.data() + total_size);
wr2->size = heap.get_big_entry_size();
wr2->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
wr2->lsn = 2;
wr2->inode = INODE_WITH_POOL(1, 1);
wr2->stripe = 0;
wr2->version = 2;
wr2->set_big_location(&heap, 0); // <-- double claimed
wr2->crc32c = wr2->calc_crc32c();
total_size += wr2->size;
wr3 = (heap_entry_t*)(tmp.data() + total_size);
wr3->size = heap.get_big_entry_size();
wr3->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
wr3->lsn = 3;
wr3->inode = INODE_WITH_POOL(1, 1);
wr3->stripe = 0x20000;
wr3->version = 1;
wr3->set_big_location(&heap, 0); // <-- double claimed
wr3->crc32c = wr3->calc_crc32c();
total_size += wr3->size;
wr4 = (heap_entry_t*)(tmp.data() + total_size);
wr4->size = heap.get_big_entry_size();
wr4->entry_type = BS_HEAP_BIG_WRITE; // <-- unstable
wr4->lsn = 4;
wr4->inode = INODE_WITH_POOL(1, 1);
wr4->stripe = 0x20000;
wr4->version = 2;
wr4->set_big_location(&heap, 0x20000);
wr4->crc32c = wr4->calc_crc32c();
total_size += wr4->size;
*(uint16_t*)(tmp.data() + total_size) = dsk.meta_block_size - total_size;
*(uint16_t*)(tmp.data() + total_size + 2) = BS_HEAP_FREE_SPACE;
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 1);
assert(mod[0] == 0);
// [1 2] [3 4] - should erase first
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(!obj);
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
obj = heap.read_entry(oid);
assert(obj);
assert(heap.is_data_used(0));
assert(heap.is_data_used(0x20000));
assert(!heap.is_data_used(0x40000));
assert(check_used_space(heap, dsk, 0));
heap.get_meta_block(0, out.data());
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
wr1->lsn = 1;
wr1->crc32c = wr1->calc_crc32c();
wr2->lsn = 3;
wr2->crc32c = wr2->calc_crc32c();
wr3->lsn = 2;
wr3->crc32c = wr3->calc_crc32c();
wr4->lsn = 4;
wr4->crc32c = wr4->calc_crc32c();
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 1);
assert(mod[0] == 0);
// [1 [2 3] 4] - intersect - should erase both
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(!obj);
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
obj = heap.read_entry(oid);
assert(!obj);
assert(!heap.is_data_used(0));
assert(!heap.is_data_used(0x20000));
assert(!heap.is_data_used(0x40000));
assert(check_used_space(heap, dsk, 0));
heap.get_meta_block(0, out.data()+dsk.meta_block_size);
}
{
blockstore_heap_t heap(&dsk, buffer_area.data());
// [3 4] [1 2] - should erase second
wr1->lsn = 3;
wr1->crc32c = wr1->calc_crc32c();
wr2->lsn = 4;
wr2->crc32c = wr2->calc_crc32c();
wr3->lsn = 1;
wr3->crc32c = wr3->calc_crc32c();
wr4->lsn = 2;
wr4->crc32c = wr4->calc_crc32c();
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 1);
assert(mod[0] == 0);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0x20000 };
obj = heap.read_entry(oid);
assert(!obj);
assert(heap.is_data_used(0));
assert(!heap.is_data_used(0x20000));
assert(!heap.is_data_used(0x40000));
assert(check_used_space(heap, dsk, 0));
heap.get_meta_block(0, out.data()+dsk.meta_block_size*2);
}
// Validate persisted variants
for (int i = 0; i < 3; i++)
{
blockstore_heap_t heap(&dsk, buffer_area.data());
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, out.data() + dsk.meta_block_size*i, false, entries_loaded);
heap.finish_load();
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 0);
}
}
void test_postpone_load()
{
blockstore_disk_t dsk;
// FIXME dsk.readonly = true;
_test_init(dsk, false);
std::vector<uint8_t> tmp(dsk.meta_block_size*10);
std::vector<uint8_t> buffer_area(dsk.journal_device_size);
{
blockstore_heap_t heap(&dsk, buffer_area.data(), 10);
size_t total_size = 0;
auto wr1 = (heap_entry_t*)(tmp.data() + total_size);
wr1->size = heap.get_big_entry_size();
wr1->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
wr1->lsn = 1;
wr1->inode = INODE_WITH_POOL(1, 1);
wr1->stripe = 0;
wr1->version = 1;
wr1->set_big_location(&heap, 0x20000);
memset(wr1->get_ext_bitmap(&heap), 0xff, dsk.clean_entry_bitmap_size);
wr1->crc32c = wr1->calc_crc32c();
total_size += wr1->size;
assert(total_size+heap.get_big_entry_size() <= dsk.meta_block_size);
wr1 = (heap_entry_t*)(tmp.data() + total_size);
wr1->size = heap.get_big_entry_size();
wr1->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
wr1->lsn = 20; // 20 but compacted - newest entry
wr1->inode = INODE_WITH_POOL(1, 1);
wr1->stripe = 0;
wr1->version = 1;
wr1->set_big_location(&heap, 0x20000);
memset(wr1->get_ext_bitmap(&heap), 0xff, dsk.clean_entry_bitmap_size);
wr1->crc32c = wr1->calc_crc32c();
total_size += wr1->size;
uint32_t small_size = heap.get_small_entry_size(0, 4096);
auto add_small = [&](uint64_t lsn)
{
assert(total_size+small_size <= dsk.meta_block_size);
auto wr2 = (heap_entry_t*)(tmp.data() + total_size);
wr2->size = small_size;
wr2->entry_type = BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE;
wr2->lsn = lsn;
wr2->inode = INODE_WITH_POOL(1, 1);
wr2->stripe = 0;
wr2->version = lsn;
wr2->small().offset = (lsn % 32)*4096;
wr2->small().len = 4096;
wr2->small().location = lsn*4096;
memset(wr2->get_ext_bitmap(&heap), 0xff, dsk.clean_entry_bitmap_size);
*((uint32_t*)wr2->get_checksum(&heap)) = crc32c(0, buffer_area.data()+wr2->small().location, 4096);
wr2->crc32c = wr2->calc_crc32c();
total_size += small_size;
};
for (int i = 0; i < 10; i++)
add_small(2 + 2*i); // 2..20
for (int i = 0; i < 10; i++)
add_small(30 - i); // 21..30
for (int i = 0; i < 9; i++)
add_small(3 + 2*i); // 3..19
assert(total_size+heap.get_big_entry_size() <= dsk.meta_block_size);
wr1 = (heap_entry_t*)(tmp.data() + total_size);
wr1->size = heap.get_big_entry_size();
wr1->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
wr1->lsn = 15; // 15 but also compacted
wr1->inode = INODE_WITH_POOL(1, 1);
wr1->stripe = 0;
wr1->version = 1;
wr1->set_big_location(&heap, 0x20000);
memset(wr1->get_ext_bitmap(&heap), 0xff, dsk.clean_entry_bitmap_size);
wr1->crc32c = wr1->calc_crc32c();
total_size += wr1->size;
*(uint16_t*)(tmp.data() + total_size) = dsk.meta_block_size - total_size;
*(uint16_t*)(tmp.data() + total_size + 2) = BS_HEAP_FREE_SPACE;
uint64_t entries_loaded;
heap.load_blocks(0, dsk.meta_block_size, tmp.data(), false, entries_loaded);
object_id oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
heap_entry_t *obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 22);
heap.finish_load();
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 32);
uint64_t clsn = 30;
bool stable = true;
for (auto wr = obj; wr; wr = heap.prev(wr))
{
assert(wr->lsn == clsn);
if (clsn == 20 || clsn == 15)
{
assert(wr->entry_type == (stable ? BS_HEAP_BIG_WRITE|BS_HEAP_STABLE : BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE));
if (stable)
stable = false;
else
{
clsn--;
stable = true;
}
}
else if (clsn == 1)
{
assert(wr->entry_type == BS_HEAP_BIG_WRITE|BS_HEAP_STABLE);
}
else
{
assert(wr->entry_type == BS_HEAP_SMALL_WRITE|BS_HEAP_STABLE);
clsn--;
}
}
bool done = heap.recheck_small_writes([&](bool, uint64_t, uint64_t, uint8_t*, std::function<void()> cb) {}, 1);
assert(done);
heap.finish_recheck();
auto mod = heap.get_recheck_modified_blocks();
assert(mod.size() == 1);
oid = { .inode = INODE_WITH_POOL(1, 1), .stripe = 0 };
obj = heap.read_entry(oid);
assert(obj);
assert(count_writes(heap, obj) == 11);
}
}
// FIXME: Add a test for big_intent, incl. explicit_complete with big_intent over big_write over deletion over big_write :)
int main(int narg, char *args[])
{
test_mvcc(false);
test_mvcc(true);
test_update(true);
test_update(false);
test_delete(true);
test_delete(false);
test_defrag_block();
test_compact(true, true);
test_compact(true, false);
test_compact(false, true);
test_compact(false, false);
test_iterate_compaction();
test_modify_bitmap();
test_recheck(false, true);
test_recheck(false, false);
test_recheck(true, true);
test_recheck(true, false);
test_corruption();
test_full_overwrite(true);
test_full_overwrite(false);
test_reshard_list();
test_reshard_chunked();
test_destructor_mvcc();
test_rollback();
test_alloc_buffer();
test_full_alloc();
test_intent_write(true);
test_intent_write(false);
test_big_intent_csums();
test_recalc_stats();
test_redirect_intent_csums();
test_explicit_complete();
test_skip_double_claim();
test_postpone_load();
return 0;
}