Data-Log-Shared
view release on metacpan or search on metacpan
* (typically a few CPU instructions) is NOT recoverable: if a writer
* dies there, the slot's size is unknown and readers cannot skip past
* it. Callers must reset/recreate the log. This is extremely rare in
* practice given the small instruction window.
*
* Padding ensures the next slot header is 4-byte aligned â required
* for atomic load/store on strict-alignment ISAs (ARM64 LDAR/STLR
* trap on unaligned addresses with SIGBUS).
*/
#ifndef LOG_H
#define LOG_H
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <limits.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/eventfd.h>
#define LOG_MAGIC 0x4C4F4732U /* "LOG2" â v2 entry format with reserve_size */
#define LOG_VERSION 2
#define LOG_ERR_BUFLEN 256
/* Slot header: reserve_size (u32) + len (u32) = 8 bytes. */
#define LOG_ENTRY_HDR (2 * sizeof(uint32_t))
/* Default bounded-spin (microseconds) for an in-flight writer to commit
* len before a reader declares the slot abandoned. Override per call via
* log_read_ex; this affects only the rare crash-recovery path. */
#ifndef LOG_ABANDON_WAIT_US
#define LOG_ABANDON_WAIT_US 2000000 /* 2s */
#endif
/* log_read / log_read_ex return codes */
#define LOG_READ_EMPTY 0 /* no entry at offset (end / uncommitted in flight) */
#define LOG_READ_OK 1 /* valid entry â out_data, out_len, next_off set */
#define LOG_READ_ABANDONED 2 /* slot abandoned â next_off set, no data */
/* ================================================================
* Header (128 bytes)
* ================================================================ */
typedef struct {
uint32_t magic;
uint32_t version;
uint64_t data_size; /* 8: usable data region size */
uint64_t total_size; /* 16 */
uint64_t data_off; /* 24 */
uint8_t _pad0[32]; /* 32-63 */
uint64_t tail; /* 64: byte offset past last entry (CAS target) */
uint64_t count; /* 72: number of committed entries */
uint32_t waiters; /* 80: blocked tailers */
uint32_t wake_seq; /* 84: FUTEX_WAIT target (avoids 64-bit count wraparound) */
uint64_t stat_appends; /* 88 */
uint64_t stat_waits; /* 96 */
uint64_t stat_timeouts; /* 104 */
uint64_t truncation; /* 112: entries before this offset are invalid */
uint8_t _pad2[8]; /* 120-127 */
} LogHeader;
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(sizeof(LogHeader) == 128, "LogHeader must be 128 bytes");
#endif
typedef struct {
LogHeader *hdr;
uint8_t *data;
size_t mmap_size;
char *path;
int notify_fd;
int backing_fd;
} LogHandle;
/* ================================================================
* Utility
* ================================================================ */
static inline void log_make_deadline(double t, struct timespec *dl) {
clock_gettime(CLOCK_MONOTONIC, dl);
dl->tv_sec += (time_t)t;
dl->tv_nsec += (long)((t - (double)(time_t)t) * 1e9);
if (dl->tv_nsec >= 1000000000L) { dl->tv_sec++; dl->tv_nsec -= 1000000000L; }
}
static inline int log_remaining(const struct timespec *dl, struct timespec *rem) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
rem->tv_sec = dl->tv_sec - now.tv_sec;
rem->tv_nsec = dl->tv_nsec - now.tv_nsec;
if (rem->tv_nsec < 0) { rem->tv_sec--; rem->tv_nsec += 1000000000L; }
return rem->tv_sec >= 0;
}
/* ================================================================
* Append â CAS reserve space, publish reserve_size, write data,
* commit (len). reserve_size is published BEFORE data so a crashed
* writer leaves a recoverable slot boundary for readers.
* ================================================================ */
static inline int64_t log_append(LogHandle *h, const void *data, uint32_t len) {
if (len == 0) return -1; /* 0 is the uncommitted marker */
LogHeader *hdr = h->hdr;
if (len > UINT32_MAX - LOG_ENTRY_HDR - 3U) return -1;
/* Pad total slot size up to 4-byte boundary so the next slot's
* header words are naturally aligned for atomic ops on ARM64. */
uint32_t entry_size = (LOG_ENTRY_HDR + len + 3U) & ~3U;
for (;;) {
uint64_t t = __atomic_load_n(&hdr->tail, __ATOMIC_RELAXED);
if (t + entry_size > hdr->data_size) return -1;
( run in 0.567 second using v1.01-cache-2.11-cpan-e1769b4cff6 )