Files
codebase-memory-mcp/internal/cbm/result_spill.h
Martin Vogel de1819e73e fix(python): preserve ordered namespace metadata
Capture ordered Python namespace facts and retain them through compact, spill,
and versioned LSP surfaces. Reject unavailable metadata before incremental
reuse and rebuild on Python namespace changes or deletion.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-12 22:25:56 +02:00

88 lines
4.4 KiB
C

/*
* result_spill.h — per-file extraction results parked on disk when the
* memory budget is hit.
*
* A compacted CBMFileResult is one exact-size arena block plus a header whose
* pointers all point into that block (cbm_result_compact guarantees it). That
* makes a result a relocatable blob: written as header + block, read back at
* any address by shifting every pointer by the base delta. The store is one
* append-only file per writer thread under the cache directory; loads use
* pread and are safe from any thread. Nothing here decides WHEN to spill --
* the pipeline does, on the budget signal (see cbm_pipeline_ctx_t.spill).
*/
#ifndef CBM_RESULT_SPILL_H
#define CBM_RESULT_SPILL_H
#include "cbm.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct cbm_result_spill cbm_result_spill_t;
/* One store for one pipeline run: `writers` append-only files under
* <dir>/spill-<pid>-<w>.bin, `slots` result slots (one per input file).
* NULL on failure (no directory, no space): the caller keeps results in
* memory as before. */
cbm_result_spill_t *cbm_result_spill_open(const char *dir, int writers, int slots);
/* Park a compacted result in slot `slot` via writer `writer`, then free the
* in-memory result (a retained parse tree goes with it; a loaded result has
* none). Returns false (and leaves the result untouched) when the result is
* not a single-block compaction, owns sub-results, or the write fails. */
bool cbm_result_spill_park(cbm_result_spill_t *sp, int writer, int slot, CBMFileResult *result);
/* True when slot `slot` holds a parked result. */
bool cbm_result_spill_has(const cbm_result_spill_t *sp, int slot);
/* Load slot `slot` back into memory: a fresh CBMFileResult the caller owns
* (cbm_free_result). NULL when the slot is empty or the read fails. Safe from
* several threads for different slots. */
CBMFileResult *cbm_result_spill_load(const cbm_result_spill_t *sp, int slot);
/* Read only the parked header of slot `slot` into *out: every count in it
* is valid, every pointer meaningless. False when the slot is empty or the
* read fails. Lets a consumer skip the load when there is nothing to read.
* Python namespace version/language/status/count are scalar evidence only:
* zero facts with NOT_CAPTURED/INCOMPLETE is not a known-empty namespace, and
* event pointers require load. Failed peek/load provides no namespace evidence. */
bool cbm_result_spill_peek_header(const cbm_result_spill_t *sp, int slot, CBMFileResult *out);
/* Read only the parked header of slot `slot`: how many defs and impl
* relations it holds (the collector sizes its array before loading). */
void cbm_result_spill_peek_counts(const cbm_result_spill_t *sp, int slot, int *defs, int *impls);
/* The namespace/package a parked file declares, or NULL. Kept in MEMORY while
* the result itself is on disk, because import resolution builds its namespace
* map from EVERY file before any parked one is read back. Without it a spilled
* file contributed nothing to that map, so imports resolved differently
* depending on memory pressure -- php measured 57,182 edges in memory against
* 59,379 while spilling, the same binary, 2026-09-18. */
const char *cbm_result_spill_namespace(const cbm_result_spill_t *sp, int slot);
/* Counters for the log: results parked, bytes on disk, loads served. */
void cbm_result_spill_stats(const cbm_result_spill_t *sp, int64_t *parked, int64_t *bytes,
int64_t *loads);
/* Close and delete the files. */
void cbm_result_spill_close(cbm_result_spill_t *sp);
/* Relocation primitive used by the loader: every pointer inside `result`
* that points into [old_base, old_base + len) is shifted to the block now at
* `new_base`. Exposed for tests. */
void cbm_result_relocate(CBMFileResult *result, const char *old_base, size_t len, char *new_base);
#ifdef CBM_ENABLE_TEST_SEAMS
/* Test builds only: open's free-space reading (and so its low-disk refusal and
* its disk-share cap) answers `bytes` instead of asking the disk; 0 asks the
* disk again. Returns the previous pin. A test's verdict must not depend on how
* full the machine's disk happens to be, so the test runner pins ample space
* for the whole process and the refusal test pins a low value on purpose. */
size_t cbm_result_spill_pin_free_bytes_for_tests(size_t bytes);
/* The production floor: below this much free space, open refuses. */
size_t cbm_result_spill_free_floor_bytes_for_tests(void);
#endif
#endif /* CBM_RESULT_SPILL_H */