aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-28 11:12:37 +0100
committereunhhu <[email protected]>2026-09-28 11:12:37 +0100
commit3c8cc4e887c861c392223a54fd5be2b49db29874 (patch)
treefccb4588cf17b2adaa0fa5ff9e20e716c8489ac7 /core
parent4c4c73c30a0524cf893d813fda215199150a6934 (diff)
downloadpure-3c8cc4e887c861c392223a54fd5be2b49db29874.tar.gz
pure-3c8cc4e887c861c392223a54fd5be2b49db29874.zip
Write list images a chunk at a time, and keep the log within the lists' size
Diffstat (limited to 'core')
-rw-r--r--core/log.h5
-rw-r--r--core/module-list.c23
-rw-r--r--core/module-list.h4
-rw-r--r--core/module-save.c67
4 files changed, 78 insertions, 21 deletions
diff --git a/core/log.h b/core/log.h
index 44dbdfb..3edf4e9 100644
--- a/core/log.h
+++ b/core/log.h
@@ -37,10 +37,11 @@ uint64_t pure_log_bytes(const PureLog *);
/* A list's image: a file of directory named after the list, replaced whole
* (written beside it, synced, renamed over it): pure_image_write writes the
- * bytes of parts one after another, pure_image_read gives them back (malloc'd;
+ * chunks next gives, one after another, until an empty one (next false:
+ * ALLOCATION, nothing replaced); pure_image_read gives them back (malloc'd;
* NOT_FOUND when there is none, STORE_ERROR when damaged). The bytes of the
* image are digested: a file cut short or changed is refused. */
-PureResult pure_image_write(const char *directory, const char *name, const PureSpan *parts, size_t count);
+PureResult pure_image_write(const char *directory, const char *name, bool (*next)(void *, PureSpan *chunk), void *context);
PureResult pure_image_read(const char *directory, const char *name, unsigned char **bytes, size_t *size);
PureResult pure_image_remove(const char *directory, const char *name);
#endif
diff --git a/core/module-list.c b/core/module-list.c
index d69ad98..c2a831a 100644
--- a/core/module-list.c
+++ b/core/module-list.c
@@ -708,6 +708,29 @@ void pure_list_encode(const PureList *list, unsigned char *out) {
visit_leaves(list->root, encode_leaf, &encoding);
}
+size_t pure_list_encode_part(const PureList *list, size_t *at, unsigned char *out, size_t capacity, size_t *needed) {
+ const Layout *l = &list->layout;
+ PureRun run;
+ size_t used = 0;
+ *needed = 0;
+ if (!pure_list_run(list, *at, &run)) return 0;
+ do {
+ const Node *leaf = run.path[run.depth];
+ for (unsigned i = (unsigned)(*at - run.start); i < leaf->count; ++i) {
+ size_t size = item_bytes(l, leaf, i);
+ if (size > capacity - used) {
+ if (!used) *needed = size;
+ return used;
+ }
+ unsigned char *end = out + used;
+ for (unsigned f = 0; f < l->fields; ++f) end = encode_cell(l, f, cell_at(l, leaf, i, f), end);
+ used += size;
+ ++*at;
+ }
+ } while (pure_list_next(list, &run));
+ return used;
+}
+
/* An item's cells in stored form at *at in bytes. */
static bool decode_item(const Layout *l, const unsigned char *bytes, size_t size, size_t *at, PureCell *cells) {
for (unsigned f = 0; f < l->fields; ++f)
diff --git a/core/module-list.h b/core/module-list.h
index ec7ee10..faacf4b 100644
--- a/core/module-list.h
+++ b/core/module-list.h
@@ -96,6 +96,10 @@ PureResult pure_list_sorted(const PureList *, unsigned field, PureList **out, si
* pure_list_encode writes them to out. */
size_t pure_list_size(const PureList *);
void pure_list_encode(const PureList *, unsigned char *out);
+/* The items from position *at on, as many whole ones as capacity holds,
+ * into out: their bytes (0 at the end, or with *needed the bytes of the one
+ * item that does not fit), *at moved past them. */
+size_t pure_list_encode_part(const PureList *, size_t *at, unsigned char *out, size_t capacity, size_t *needed);
/* Append the items stored in bytes; each field is checked. */
PureResult pure_list_read(PureList *, const unsigned char *bytes, size_t size);
/* A stored list journals its changes (from pure_list_journal(list, true)
diff --git a/core/module-save.c b/core/module-save.c
index f0befb9..c05394f 100644
--- a/core/module-save.c
+++ b/core/module-save.c
@@ -32,7 +32,8 @@ static const unsigned char values_magic[4] = {'P', 'V', 'S', '1'}, image_magic[4
list_magic[4] = {'P', 'L', 'S', '2'}, list_magic_1[4] = {'P', 'L', 'S', '1'};
/* Logged changes a list keeps before its image is written again: at least
* JOURNAL_MIN, else as many bytes as its items; and whatever the lists, the
- * log is kept within LOG_KEPT by writing the image that holds it back. */
+ * log is kept within as many bytes as all their items (and LOG_KEPT) by
+ * writing the image that holds it back. */
#ifdef PURE_STORE_TEST
enum { JOURNAL_MIN = 256 };
#define LOG_KEPT (UINT64_C(64) << 10)
@@ -261,17 +262,50 @@ void pure_save_release(PureApp *app) {
pure_log_release(app->log, kept - 1);
}
-PureResult pure_save_image(PureApp *app, const PureModule *m, uint32_t state, const PureList *list) {
- size_t items = pure_list_size(list);
- unsigned char header[IMAGE_HEADER], *bytes = malloc(items ? items : 1);
- if (!bytes) return PURE_ALLOCATION;
- pure_list_encode(list, bytes);
- PureSpan parts[2] = {{header, image_header(m, m->state_ref[state], app->seq, header)}, {bytes, items}};
- PureResult result = pure_image_write(directory_of(app), m->state_names[state], parts, 2);
- free(bytes);
+/* An image's bytes as the store writes them: its header, then its items a
+ * chunk at a time (one item at least, whatever its size). */
+typedef struct {
+ const PureList *list;
+ size_t at, capacity;
+ const unsigned char *header;
+ size_t header_size;
+ bool started;
+ unsigned char *buffer;
+} Imaging;
+
+static bool image_chunk(void *context, PureSpan *chunk) {
+ Imaging *i = context;
+ if (!i->started) {
+ i->started = true;
+ *chunk = (PureSpan){i->header, i->header_size};
+ return true;
+ }
+ size_t needed, length = pure_list_encode_part(i->list, &i->at, i->buffer, i->capacity, &needed);
+ if (!length && needed) {
+ unsigned char *grown = realloc(i->buffer, needed);
+ if (!grown) return false;
+ i->buffer = grown;
+ i->capacity = needed;
+ length = pure_list_encode_part(i->list, &i->at, i->buffer, i->capacity, &needed);
+ }
+ *chunk = (PureSpan){i->buffer, length};
+ return true;
+}
+
+static PureResult write_image(const char *directory, const char *name, const unsigned char *header, size_t header_size,
+ const PureList *list) {
+ Imaging imaging = {list, 0, 256 << 10, header, header_size, false, malloc(256 << 10)};
+ PureResult result = imaging.buffer ? pure_image_write(directory, name, image_chunk, &imaging) : PURE_ALLOCATION;
+ free(imaging.buffer);
return result;
}
+PureResult pure_save_image(PureApp *app, const PureModule *m, uint32_t state, const PureList *list) {
+ unsigned char header[IMAGE_HEADER];
+ size_t header_size = image_header(m, m->state_ref[state], app->seq, header);
+ return write_image(directory_of(app), m->state_names[state], header, header_size, list);
+}
+
PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint32_t state, PureList **out,
PureDraft **legacy, PureSaved *saved) {
uint32_t record = m->state_ref[state];
@@ -399,7 +433,10 @@ PureCheckpoint *pure_app_checkpoint(PureApp *app) {
if (app->logged[i] > (items > JOURNAL_MIN ? items : JOURNAL_MIN)) due = (int)i;
else if (oldest < 0 || app->logged_since[i] < app->logged_since[oldest]) oldest = (int)i;
}
- int pick = due >= 0 ? due : pure_log_bytes(app->log) > LOG_KEPT ? oldest : -1;
+ uint64_t items = 0;
+ for (uint32_t i = 0; due < 0 && i < m->states; ++i)
+ if (m->state_type[i] == PURE_STATE_LIST && app->lists[i]) items += pure_list_size(app->lists[i]);
+ int pick = due >= 0 ? due : pure_log_bytes(app->log) > (items > LOG_KEPT ? items : LOG_KEPT) ? oldest : -1;
if (pick < 0) return NULL;
PureCheckpoint *c = calloc(1, sizeof(*c));
const char *directory = directory_of(app);
@@ -421,15 +458,7 @@ PureCheckpoint *pure_app_checkpoint(PureApp *app) {
}
PureResult pure_checkpoint_write(PureCheckpoint *c) {
- const PureList *list = pure_snapshot_list(c->snapshot, c->state);
- size_t items = pure_list_size(list);
- unsigned char *bytes = malloc(items ? items : 1);
- if (!bytes) return PURE_ALLOCATION;
- pure_list_encode(list, bytes);
- PureSpan parts[2] = {{c->header, c->header_size}, {bytes, items}};
- PureResult result = pure_image_write(c->directory, c->name, parts, 2);
- free(bytes);
- return result;
+ return write_image(c->directory, c->name, c->header, c->header_size, pure_snapshot_list(c->snapshot, c->state));
}
void pure_app_checkpointed(PureApp *app, PureCheckpoint *c, PureResult result) {