1#define _POSIX_C_SOURCE 200809L
7#include <ttak/mem/epoch.h>
30#define BDR_BUCKETS 1024
32#define BDR_JANITOR_PERIOD 64
33#define BDR_DEFAULT_MAX_BYTES CWIST_MIB(32)
34#define BDR_DEFAULT_ENTRY_TTL 300
35#define BDR_DEFAULT_REVALIDATE_HITS 100000
40static const uint8_t BDR_KEY[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
41 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
53static void bdr_blob_free_cb(
void *ptr) {
59 void *mem = blob->
mem;
78static bdr_blob_t *bdr_blob_learn(
const void *data,
size_t len) {
80 if (!blob)
return NULL;
84 blob->
data = (
const unsigned char *)(blob + 1);
95static bdr_blob_t *bdr_blob_wrap(
void *mem,
size_t len,
void (*free_fn)(
void *)) {
97 if (!blob)
return NULL;
101 blob->
data = (
const unsigned char *)mem;
122 size_t next =
cur >= len ?
cur - len : 0;
141 if (blob) bdr_bytes_add(
bdr, blob->
len);
143 bdr_bytes_sub(
bdr,
old->len);
144 bdr_blob_retire(
old);
156static uint64_t bdr_hash(
const char *method,
const char *path) {
169static uint64_t bdr_hash_n(
const char *method,
const char *path,
size_t path_len) {
181static uint64_t bdr_hash_data(
const void *data,
size_t len) {
198 if (!
bdr || !entry)
return false;
200 if (
bdr->max_entry_age_sec > 0 &&
created > 0) {
247 if (found)
return found;
250 if (!entry)
return NULL;
301 bdr_entry_publish(
bdr, entry,
NULL);
303 bdr->retired_entries = entry;
315 if (!
bdr ||
bdr->bucket_count == 0 ||
steps == 0)
return;
317 for (
size_t i = 0;
i <
steps; ++
i) {
318 size_t idx =
bdr->gc_cursor %
bdr->bucket_count;
319 bdr->gc_cursor = (
bdr->gc_cursor + 1) %
bdr->bucket_count;
351 for (
size_t i = 0;
i <
bdr->bucket_count; ++
i) {
370 if (!
victim)
return false;
387#if defined(__EMSCRIPTEN__) || !defined(CWIST_DB)
396 printf(
"[BDR] Low RAM. Switching to Disk Cache.\n");
400 "CREATE TABLE IF NOT EXISTS bdr (hash INTEGER PRIMARY KEY, blob BLOB);",
405 for (
size_t i = 0;
i <
bdr->bucket_count;
i++) {
415 "INSERT INTO bdr (hash, blob) VALUES (?, ?);", -1,
426 curr->retire_next =
bdr->retired_entries;
456 if (
bdr->max_bytes > 0) {
459 if (!bdr_trim_oldest(
bdr,
now))
break;
489 bdr->latency_threshold_ms = 10;
514 for (
size_t i = 0;
i <
bdr->bucket_count;
i++) {
525 while (
bdr->retired_entries) {
529 bdr->retired_entries = next;
532#if !defined(__EMSCRIPTEN__) && defined(CWIST_DB)
535 remove(
"cwist_bdr_fallback.db");
568 if (!entry) entry = bdr_find(
bdr,
req_h);
586 bdr_entry_publish(
bdr, entry,
nb);
598 if (out_len) *out_len = blob->
len;
622 return bdr_serve_hit(
bdr, bdr_hash(method, path),
NULL, out_len,
out_pin);
657 req_h = bdr_hash_n(method, path, path_len);
672 cursor->path_len = path_len;
732 if (data && len > 0) {
736 if (out_len) *out_len = len;
786 if (!
bdr || !method || !path || !data || len == 0)
return;
787 if (
strcmp(method,
"GET") != 0)
return;
805 bdr_entry_publish(
bdr, entry,
NULL);
820 bdr_entry_publish(
bdr, entry, blob);
832 bdr_janitor_tick(
bdr);
850 if (!
bdr || !method || !path || !data || len == 0)
return;
851 if (
strcmp(method,
"GET") != 0)
return;
868 bdr_entry_publish(
bdr, entry, blob);
873 bdr_janitor_tick(
bdr);
897 if (!
bdr || !method || !path || !data || len == 0 || !fn)
return;
898 if (
strcmp(method,
"GET") != 0)
return;
921 bdr_entry_publish(
bdr, entry, blob);
926 bdr_janitor_tick(
bdr);
944 if (max_bytes > 0)
bdr->max_bytes = max_bytes;
945 if (max_entry_age_sec > 0)
bdr->max_entry_age_sec = max_entry_age_sec;
946 if (revalidate_hits > 0)
bdr->revalidate_hits = revalidate_hits;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
void cwist_bdr_set_limits(cwist_bdr_t *bdr, size_t max_bytes, time_t max_entry_age_sec, uint64_t revalidate_hits)
Update cache limits; zero arguments leave the corresponding limit unchanged.
#define BDR_DEFAULT_MAX_BYTES
void cwist_bdr_put_fixed(cwist_bdr_t *bdr, const char *method, const char *path, const void *data, size_t len)
Publish a reply as immediately stable, bypassing the learning phase.
void cwist_bdr_unpin(bdr_blob_t *pin)
Release a pin obtained from a cwist_bdr_get_pinned*() call.
void cwist_bdr_put_revalidatable(cwist_bdr_t *bdr, const char *method, const char *path, const void *data, size_t len, cwist_bdr_revalidate_fn fn, void *arg)
Publish a stable reply together with a hit-time revalidation hook.
#define BDR_DEFAULT_ENTRY_TTL
#define BDR_JANITOR_PERIOD
void cwist_bdr_put(cwist_bdr_t *bdr, const char *method, const char *path, const void *data, size_t len)
Write a reply into the SQLite fallback database (disk mode).
void * cwist_bdr_copy_get(cwist_bdr_t *bdr, const char *method, const char *path, size_t *out_len)
Look up a cached GET reply and return an owned copy of its bytes.
void cwist_bdr_destroy(cwist_bdr_t *bdr)
Destroy a cache instance and release all its resources.
const void * cwist_bdr_get_pinned(cwist_bdr_t *bdr, const char *method, const char *path, size_t *out_len, bdr_blob_t **out_pin)
Look up a cached GET reply, returning epoch-pinned bytes.
#define BDR_DEFAULT_REVALIDATE_HITS
const void * cwist_bdr_get(cwist_bdr_t *bdr, const char *method, const char *path, size_t *out_len)
Look up a cached GET reply and return a borrowed pointer to its bytes.
const void * cwist_bdr_get_pinned_cursor(cwist_bdr_t *bdr, const char *method, const char *path, size_t path_len, size_t *out_len, bdr_blob_t **out_pin, cwist_bdr_cursor_t *cursor)
Cursor-accelerated variant of cwist_bdr_get_pinned() for non-NUL-terminated paths.
cwist_bdr_t * cwist_bdr_create(void)
Create a reply cache instance with default limits.
Auto-Caching Layer for repetitive requests.
#define CWIST_BDR_CURSOR_PATH_MAX
Per-connection cursor that turns repeated hits of the same route into a content-compare instead of a ...
bool(* cwist_bdr_revalidate_fn)(void *arg, void **out_data, size_t *out_len, void(**out_free)(void *))
Revalidation callback attached to a cache entry.
Convenience size-conversion and utility macros shared across CWIST.
#define CWIST_MIB(x)
Convert binary mebibytes to bytes.
uint64_t siphash24(const void *src, size_t len, const uint8_t key[16])
Compute SipHash-2-4 over a byte range using a 16-byte secret key.
Refcount-free cached response blob, reclaimed via libttak EBR.
const unsigned char * data
Serialized HTTP response (headers + body).
void(* free_fn)(void *)
Releaser for mem.
void * mem
Backing allocation (this object for learned blobs).
size_t len
Length of data in bytes.
Big Dumb Reply Entry. Holds an atomically swappable cached response blob.
_Atomic uint64_t hits
Hit count (approximate, relaxed)
uint64_t request_hash
Key: SipHash(Method + Path)
_Atomic bool is_stable
True once the response proved stable.
void * revalidate_arg
Opaque argument for the hook.
_Atomic uint64_t response_hash
Hash of the response content (stability check)
_Atomic bool retired
Unlinked by the janitor; walkers skip it.
cwist_bdr_revalidate_fn revalidate
Optional hit-time refresh hook.
_Atomic int64_t created_at
Creation timestamp (time_t)
struct bdr_entry_t * retire_next
Janitor retired-entry list link.
Big Dumb Reply Context. Lock-free read/learn paths; the mutex only serializes janitor work (sweep/tri...
System Resource Monitoring Utilities.
bool cwist_is_ram_critical(uint64_t threshold_bytes)
Check if system RAM is critically low.