MAM append использует один sync вместо четырёх, SM checkpoint — один вместо двух. Checksummed redo journals сохраняют commit-before-ACK и восстанавливают native archive/SM JSON exports после аварии. - Ограничены рост журналов, ротация и tombstone GC. Старый `.rappend` и legacy JSON импортируются. Retain/save/purge не возвращают удалённые записи. - Recovery синхронизирует валидный journal перед публикацией состояния; проверка checksum не заменяет durability barrier. EIO при adoption блокирует startup или offline handoff. Новые fault fixtures проверяют сохранность журнала и retry. - Replay SM проверяет checksum/JSON всех frames, но создаёт и валидирует полный Record только для последнего snapshot, сокращая работу с superseded queues. - `message-processing-slow` содержит времена MAM отправителя/получателя, доставки, carbons, offline/push и handled, а также число ресурсов/копий. Payload и секреты не добавляются в логи. - Добавлена offline команда `storage-checkpoint CONFIG`: durable экспорт и передача archive writer/подготовка отката на legacy Rust. Не запускайте другого writer и не удаляйте journals до успешного завершения команды. - Новые crash, partial-write, corruption, EIO, recovery, handoff и latency регрессии включены в GitHub Actions через verify-local.sh; отчёты сохраняются. Измерения, ограничения и эксплуатация: [docs/DURABLE-MESSAGE-IO.md](docs/DURABLE-MESSAGE-IO.md), [docs/VALIDATION-0.8.3.md](docs/VALIDATION-0.8.3.md).
75 lines
3.2 KiB
C
75 lines
3.2 KiB
C
/* Test-only LD_PRELOAD shim: delay, then perform the real durable operation.
|
|
* Scope is strictly the canonical fixture data root selected by the harness.
|
|
* No host mount or durability settings are changed. Log contains no file data.
|
|
* Exceptional XMPP_SYNC_FAIL_SUFFIX=.wal|.rredo injects EIO for matching
|
|
* fixture regular files. Without that explicit fault mode all syncs are real.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
static long long ns(void) {
|
|
struct timespec t;
|
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
|
|
}
|
|
static int sync_call(int fd, const char *operation) {
|
|
int (*real_sync)(int) = dlsym(RTLD_NEXT, operation);
|
|
if (!real_sync) { errno = ENOSYS; return -1; }
|
|
const char *prefix = getenv("XMPP_SLOW_SYNC_ROOT");
|
|
const char *log = getenv("XMPP_SLOW_SYNC_LOG");
|
|
char link[64], path[PATH_MAX];
|
|
snprintf(link, sizeof link, "/proc/self/fd/%d", fd);
|
|
ssize_t length = readlink(link, path, sizeof path - 1);
|
|
if (!prefix || !log || length < 0) return real_sync(fd);
|
|
path[length] = '\0';
|
|
size_t size = strlen(prefix);
|
|
if (strncmp(path, prefix, size) || (path[size] && path[size] != '/'))
|
|
return real_sync(fd);
|
|
struct stat st;
|
|
if (fstat(fd, &st) || (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)))
|
|
return real_sync(fd);
|
|
const char *delay = getenv("XMPP_SLOW_SYNC_MS");
|
|
long ms = delay ? strtol(delay, NULL, 10) : 100;
|
|
if (ms < 0 || ms > 10000) ms = 100;
|
|
long long begin = ns();
|
|
struct timespec wait = {.tv_sec = ms / 1000, .tv_nsec = (ms % 1000) * 1000000};
|
|
while (nanosleep(&wait, &wait) && errno == EINTR) {}
|
|
const char *suffix = getenv("XMPP_SYNC_FAIL_SUFFIX");
|
|
int injected = 0;
|
|
if (suffix && (!strcmp(suffix, ".wal") || !strcmp(suffix, ".rredo")) && S_ISREG(st.st_mode)) {
|
|
size_t suffix_length = strlen(suffix);
|
|
injected = (size_t)length >= suffix_length &&
|
|
!strcmp(path + length - suffix_length, suffix);
|
|
}
|
|
int result;
|
|
if (injected) { errno = EIO; result = -1; }
|
|
else result = real_sync(fd);
|
|
int saved_errno = errno;
|
|
long long end = ns();
|
|
/* One append write per operation avoids partial concurrent log records.
|
|
* Paths are not logged: only a path-derived store category. */
|
|
const char *store = strstr(path, "/rust_smacks") ? "sm" :
|
|
strstr(path, "/archive") ? "mam" :
|
|
strstr(path, "/pep") ? "pep" : "other";
|
|
char row[512];
|
|
int count = snprintf(row, sizeof row,
|
|
"{\"op\":\"%s\",\"kind\":\"%s\",\"store\":\"%s\",\"begin_ns\":%lld,\"end_ns\":%lld,\"result\":%d,\"injected\":%s}\n",
|
|
operation, S_ISDIR(st.st_mode) ? "directory" : "file", store, begin, end, result,
|
|
injected ? "true" : "false");
|
|
int output = open(log, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0600);
|
|
if (output >= 0) { ssize_t ignored = write(output, row, count); (void)ignored; close(output); }
|
|
errno = saved_errno;
|
|
return result;
|
|
}
|
|
int fsync(int fd) { return sync_call(fd, "fsync"); }
|
|
int fdatasync(int fd) { return sync_call(fd, "fdatasync"); }
|