87 lines
3.1 KiB
C
87 lines
3.1 KiB
C
/* 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 <pthread.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#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();
|
||
|
|
}
|