Data-Log-Shared

 view release on metacpan or  search on metacpan

log.h  view on Meta::CPAN

 * (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 )