diff options
| author | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
| commit | b1ce1927022d374198441afafeb27199fc8a052a (patch) | |
| tree | 6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/connections/test-paged-store.c | |
| parent | d34f9426d5317a1948f12f229439946728bc342a (diff) | |
| download | pure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz pure-b1ce1927022d374198441afafeb27199fc8a052a.zip | |
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/connections/test-paged-store.c')
| -rw-r--r-- | prototypes/connections/test-paged-store.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/prototypes/connections/test-paged-store.c b/prototypes/connections/test-paged-store.c new file mode 100644 index 0000000..aed0539 --- /dev/null +++ b/prototypes/connections/test-paged-store.c @@ -0,0 +1,270 @@ +#define _POSIX_C_SOURCE 200809L +#include "store.h" +#include <assert.h> +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +enum { PAGE = 16384, SIZE = 3 * PAGE + 23 }; +static int fail_stage = -1; +static int crash; +int pure_store_test_fault(PureStoreStage stage) { + if ((int)stage != fail_stage) return 0; + if (crash) _exit(77); + return 1; +} + +static void expect(const char *path, int64_t revision, const unsigned char *bytes) { + PureStoredView view; + assert(pure_store_get(path, "large", SIZE, &view) == PURE_OK); + assert(view.revision == revision && view.body.size == SIZE && + !memcmp(view.body.data, bytes, SIZE)); + pure_store_release(&view); +} + +static uint32_t random_value(uint32_t *state) { + *state ^= *state << 13; + *state ^= *state >> 17; + *state ^= *state << 5; + return *state; +} + +static void expect_root(const char *path, const char *key, + const char magic[8], off_t size) { + char filename[320]; + snprintf(filename, sizeof(filename), "%s/obj-%s", path, key); + struct stat info; + assert(stat(filename, &info) == 0 && info.st_size == size); + int fd = open(filename, O_RDONLY); + assert(fd >= 0); + char actual[8]; + assert(read(fd, actual, sizeof(actual)) == sizeof(actual)); + assert(!memcmp(actual, magic, sizeof(actual))); + assert(close(fd) == 0); +} + +static void format_transitions(const char *path) { + unsigned char bytes[PAGE + 1]; + memset(bytes, 'A', sizeof(bytes)); + int64_t revision; + assert(pure_store_put(path, "boundary", 0, (PureSpan){bytes, PAGE}, + &revision) == PURE_OK); + expect_root(path, "boundary", "PUREB001", 24 + PAGE); + PureStoredView old; + assert(pure_store_get(path, "boundary", PAGE + 1, &old) == PURE_OK); + assert(pure_store_splice(path, "boundary", revision, PAGE, 0, + (PureSpan){bytes, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + expect_root(path, "boundary", "PUREP001", 48 + 2 * 8); + assert(old.body.size == PAGE && old.body.data[PAGE - 1] == 'A'); + pure_store_release(&old); + PureStoredView large; + assert(pure_store_get(path, "boundary", PAGE + 1, &large) == PURE_OK); + assert(large.body.size == PAGE + 1 && large.body.data[PAGE] == 'A'); + assert(pure_store_splice(path, "boundary", revision, PAGE, 1, + (PureSpan){NULL, 0}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + expect_root(path, "boundary", "PUREB001", 24 + PAGE); + assert(large.body.size == PAGE + 1 && large.body.data[PAGE] == 'A'); + pure_store_release(&large); + PureStoredView current; + assert(pure_store_get(path, "boundary", PAGE, ¤t) == PURE_OK); + assert(current.revision == 3 && current.body.size == PAGE && + !memcmp(current.body.data, bytes, PAGE)); + pure_store_release(¤t); + + const unsigned char one = 'X', two = 'Y'; + assert(pure_store_put(path, "tiny", 0, (PureSpan){&one, 1}, &revision) == PURE_OK); + expect_root(path, "tiny", "PUREB001", 25); + assert(pure_store_splice(path, "tiny", revision, 1, 0, + (PureSpan){&two, 1}, 1, &revision) == PURE_LIMIT); + assert(revision == 1); + PureStoredView policy_view; + assert(pure_store_get(path, "tiny", 1, &policy_view) == PURE_OK); + assert(policy_view.revision == 1 && policy_view.body.size == 1 && policy_view.body.data[0] == one); + pure_store_release(&policy_view); + fail_stage = PURE_BEFORE_RENAME; + assert(pure_store_splice(path, "tiny", revision, 0, 1, + (PureSpan){&two, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_STORE_ERROR); + fail_stage = -1; + assert(pure_store_get(path, "tiny", 1, ¤t) == PURE_OK); + assert(current.revision == 1 && current.body.data[0] == one); + pure_store_release(¤t); + fail_stage = PURE_BEFORE_DIR_SYNC; + assert(pure_store_splice(path, "tiny", revision, 0, 1, + (PureSpan){&two, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_UNCERTAIN); + fail_stage = -1; + assert(pure_store_get(path, "tiny", 1, ¤t) == PURE_OK); + assert(current.revision == 2 && current.body.data[0] == two); + pure_store_release(¤t); + + /* An existing flat-store object has no data segment. Its first large + * edit must read the old inline body before publishing a page root. */ + char legacy_file[320]; + snprintf(legacy_file, sizeof(legacy_file), "%s/obj-legacy", path); + int fd = open(legacy_file, O_WRONLY | O_CREAT | O_EXCL, 0600); + assert(fd >= 0); + unsigned char header[24] = "PUREB001"; + header[8] = 1; + header[16] = 1; + header[17] = 0x40; /* 0x4001 = 16 KiB + 1 */ + assert(write(fd, header, sizeof(header)) == sizeof(header)); + assert(write(fd, bytes, sizeof(bytes)) == sizeof(bytes)); + assert(close(fd) == 0); + assert(pure_store_get(path, "legacy", PAGE + 1, ¤t) == PURE_OK); + assert(current.revision == 1 && current.body.size == PAGE + 1); + pure_store_release(¤t); + assert(pure_store_splice(path, "legacy", 1, PAGE, 1, + (PureSpan){&two, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + expect_root(path, "legacy", "PUREP001", 48 + 2 * 8); + assert(pure_store_get(path, "legacy", PAGE + 1, ¤t) == PURE_OK); + assert(current.revision == 2 && current.body.size == PAGE + 1 && + current.body.data[PAGE] == two); + pure_store_release(¤t); +} + +static void mixed_edits(const char *path) { + enum { CAPACITY = 6 * PAGE }; + unsigned char *oracle = malloc(CAPACITY), *before = malloc(CAPACITY); + unsigned char insert[4096]; + assert(oracle && before); + size_t length = 2 * PAGE + 17; + uint32_t state = 0xA17E532Du; + for (size_t i = 0; i < length; ++i) oracle[i] = (unsigned char)random_value(&state); + int64_t revision; + assert(pure_store_put(path, "mixed", 0, (PureSpan){oracle, length}, &revision) == PURE_OK); + for (unsigned step = 0; step < 128; ++step) { + size_t offset = random_value(&state) % (length + 1); + size_t remove = random_value(&state) % 4097; + if (remove > length - offset) remove = length - offset; + size_t count = step % 3 ? random_value(&state) % 4097 : remove; + if (count > CAPACITY - (length - remove)) count = CAPACITY - (length - remove); + for (size_t i = 0; i < count; ++i) insert[i] = (unsigned char)random_value(&state); + PureStoredView old; + assert(pure_store_get(path, "mixed", CAPACITY, &old) == PURE_OK); + if (length) memcpy(before, old.body.data, length); + assert(pure_store_splice(path, "mixed", revision, offset, remove, + (PureSpan){insert, count}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + assert(old.body.size == length && + (!length || !memcmp(old.body.data, before, length))); + pure_store_release(&old); + memmove(oracle + offset + count, oracle + offset + remove, length - offset - remove); + memcpy(oracle + offset, insert, count); + length = length - remove + count; + PureStoredView current; + assert(pure_store_get(path, "mixed", CAPACITY, ¤t) == PURE_OK); + assert(current.revision == (int64_t)step + 2 && current.body.size == length && + (!length || !memcmp(current.body.data, oracle, length))); + pure_store_release(¤t); + } + free(before); + free(oracle); +} + +int main(void) { + assert(sysconf(_SC_PAGESIZE) == PAGE); + char base[] = "/tmp/pure-page-store-XXXXXX"; + assert(mkdtemp(base)); + char path[256]; + snprintf(path, sizeof(path), "%s/store", base); + assert(pure_store_init(path) == PURE_OK); + unsigned char expected[SIZE]; + for (size_t i = 0; i < SIZE; ++i) expected[i] = (unsigned char)(i * 17u + i / PAGE); + int64_t revision; + assert(pure_store_put(path, "large", 0, (PureSpan){expected, SIZE}, &revision) == PURE_OK); + assert(revision == 1); + PureStoredView old; + assert(pure_store_get(path, "large", SIZE, &old) == PURE_OK); + unsigned char original = old.body.data[PAGE - 1]; + const unsigned char replacement = 0xC7; + assert(pure_store_splice(path, "large", revision, PAGE - 1, 1, + (PureSpan){&replacement, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + expected[PAGE - 1] = replacement; + assert(revision == 2 && old.body.data[PAGE - 1] == original); + expect(path, revision, expected); + PureStoredView range; + assert(pure_store_get_range(path, "large", PAGE - 3, 9, 9, &range) == PURE_OK); + assert(!memcmp(range.body.data, expected + PAGE - 3, 9)); + pure_store_release(&range); + char file[320]; + struct stat info; + snprintf(file, sizeof(file), "%s/dat-large-%016x", path, 1); + assert(stat(file, &info) == 0 && info.st_size == (off_t)(5 * PAGE)); + + /* The bounded append log compacts into a new generation. The old view + * remains valid even after the old generation has been unlinked. */ + for (unsigned i = 0; i < 3; ++i) { + unsigned char value = (unsigned char)(0xA0 + i); + assert(pure_store_splice(path, "large", revision, SIZE - 1, 1, + (PureSpan){&value, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + expected[SIZE - 1] = value; + expect(path, revision, expected); + } + assert(access(file, F_OK) != 0 && old.body.data[PAGE - 1] == original); + pure_store_release(&old); + + /* Unpublished crashes may leave an orphan generation. They never change + * the visible root; the next writer must skip the collided generation. */ + pid_t child = fork(); + assert(child >= 0); + if (!child) { + fail_stage = PURE_BEFORE_RENAME; + crash = 1; + pure_store_put(path, "large", revision, (PureSpan){expected, SIZE}, &revision); + _exit(78); + } + int status; + assert(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 77); + assert(pure_store_init(path) == PURE_OK); + expect(path, revision, expected); + unsigned char value = 0x5A; + assert(pure_store_splice(path, "large", revision, 0, 1, + (PureSpan){&value, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + expected[0] = value; + expect(path, revision, expected); + + fail_stage = PURE_BEFORE_DIR_SYNC; + value = 0x36; + assert(pure_store_splice(path, "large", revision, PAGE + 4, 1, + (PureSpan){&value, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_UNCERTAIN); + fail_stage = -1; + expected[PAGE + 4] = value; + expect(path, revision, expected); + + /* A whole replacement must skip the generation orphaned by the crash. */ + assert(pure_store_put(path, "large", revision, + (PureSpan){expected, SIZE}, &revision) == PURE_OK); + expect(path, revision, expected); + DIR *segments = opendir(path); + assert(segments); + unsigned generations = 0; + struct dirent *segment; + while ((segment = readdir(segments))) { + assert(strncmp(segment->d_name, ".pending-", 9)); + if (!strncmp(segment->d_name, "dat-large-", 10)) ++generations; + } + assert(closedir(segments) == 0 && generations == 1); + const unsigned char initial[] = "abcde", inserted[] = "XYZ"; + assert(pure_store_put(path, "variable", 0, + (PureSpan){initial, 5}, &revision) == PURE_OK); + assert(pure_store_splice(path, "variable", revision, 1, 2, + (PureSpan){inserted, 3}, PURE_OBJECT_LIMIT, &revision) == PURE_OK); + PureStoredView variable; + assert(pure_store_get(path, "variable", 6, &variable) == PURE_OK); + assert(variable.body.size == 6 && !memcmp(variable.body.data, "aXYZde", 6)); + pure_store_release(&variable); + format_transitions(path); + mixed_edits(path); + DIR *directory = opendir(path); + assert(directory); + struct dirent *entry; + while ((entry = readdir(directory))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + assert(unlinkat(dirfd(directory), entry->d_name, 0) == 0); + } + assert(closedir(directory) == 0 && rmdir(path) == 0 && rmdir(base) == 0); + puts("{\"hybrid_storage\":\"snapshot-patch-range-compaction-crash-conflict-uncertain-inline-transitions\",\"mixed_edits\":128}"); +} |
