/* test_concurrency.c — exercises Section 5.3's core promise: readers * never observe a torn snapshot while writers race structural changes, * and Section 5.7's buffer-growth guard doesn't crash or corrupt under * concurrent grow-and-read. Not a formal proof, but real thread * interleavings under a sanitizer-friendly build catch real races. */ #include #include #include #include #include "packfs.h" #include "test_harness.h" #define WRITER_THREADS 4 #define READER_THREADS 4 #define ITERATIONS 500 static Vfs *g_v; static void *writer_thread(void *arg) { long id = (long)arg; char path[64]; for (int i = 0; i < ITERATIONS; i++) { snprintf(path, sizeof(path), "/t%ld/f%d.txt", id, i % 8); int err = 0; VfsFile *f = vfs_open(g_v, path, VFS_O_WRONLY | VFS_O_CREAT | VFS_O_TRUNC, &err); if (!f) continue; char payload[128]; int n = snprintf(payload, sizeof(payload), "thread=%ld iter=%d %.*s", id, i, (i % 50), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); vfs_write(f, payload, (pfs_usize)n); vfs_close(f); if (i % 37 == 0) vfs_unlink(g_v, path); } return NULL; } static void *reader_thread(void *arg) { (void)arg; char path[64]; for (int i = 0; i < ITERATIONS; i++) { for (long id = 0; id < WRITER_THREADS; id++) { snprintf(path, sizeof(path), "/t%ld/f%d.txt", id, i % 8); VfsStat st; if (vfs_stat(g_v, path, &st) != VFS_OK) continue; int err = 0; VfsFile *f = vfs_open(g_v, path, VFS_O_RDONLY, &err); if (!f) continue; char buf[256]; pfs_isize r = vfs_read(f, buf, sizeof(buf) - 1); /* No CHECK on content here: a concurrent writer may legitimately * replace the file between stat and open/read (Section 5.3 * guarantees a consistent snapshot per call, not across calls). * What we're really testing is the absence of a crash, a * negative-but-uncaught read length, or a hang. */ CHECK(r >= 0); vfs_close(f); } VfsDir dir; if (vfs_readdir(g_v, "/", &dir) == VFS_OK) vfs_dir_free(&dir); } return NULL; } int main(void) { g_v = vfs_new(); Backend *mem = backend_mem_new(); CHECK_EQ_INT(vfs_mount(g_v, "/", mem), VFS_OK); pthread_t writers[WRITER_THREADS], readers[READER_THREADS]; for (long i = 0; i < WRITER_THREADS; i++) pthread_create(&writers[i], NULL, writer_thread, (void *)i); for (long i = 0; i < READER_THREADS; i++) pthread_create(&readers[i], NULL, reader_thread, (void *)i); for (int i = 0; i < WRITER_THREADS; i++) pthread_join(writers[i], NULL); for (int i = 0; i < READER_THREADS; i++) pthread_join(readers[i], NULL); /* sanity: the store is still fully consistent after the race */ VfsDir dir; CHECK_EQ_INT(vfs_readdir(g_v, "/", &dir), VFS_OK); CHECK(dir.count > 0); vfs_dir_free(&dir); vfs_unmount(g_v, "/"); backend_free(mem); vfs_free(g_v); TEST_MAIN_END(); }