aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/module.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 22:56:32 +0100
committereunhhu <[email protected]>2026-09-26 22:56:32 +0100
commit42021a500d19dbe091075338d1a621498a81f167 (patch)
treeee3e2df5f0d1ba20ef5d681bf67a148d8c896b03 /prototypes/connections/module.c
parentd2adae5deacc1545cd5dbdfcc5b80697c91dac09 (diff)
downloadpure-42021a500d19dbe091075338d1a621498a81f167.tar.gz
pure-42021a500d19dbe091075338d1a621498a81f167.zip
Snapshot an app's saved states for readers on other threads
Right after a save, pure_app_snapshot makes a read-only snapshot of the saved states: numbers are copied, text states are frozen drafts that share the store mapping (pure_draft_freeze, no copy), and lists are copies of their item pointers that share the items. An unchanged list shares the last snapshot's copy. pure_app_read binds a reader app on another thread to a snapshot so the existing view functions read it. A shared item never changes again: a list stamps each item with its share count, copies a shared item it changes, and retires shared items it lets go instead of freeing them. Snapshots form a chain that goes oldest first, and items retired while a snapshot was the newest go with it. References are atomic and may be released on any thread. test-module checks that each snapshot equals the app, that kept snapshots stay as made through changes, removals, a module switch, the app stopping and the store closing, that a failed save's snapshot equals the one before, and that two threads read the newest snapshots while the app goes on (also under ASan, UBSan and LeakSanitizer).
Diffstat (limited to 'prototypes/connections/module.c')
-rw-r--r--prototypes/connections/module.c82
1 files changed, 81 insertions, 1 deletions
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index e4c7309..c1a71d5 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -1259,14 +1259,50 @@ bool pure_app_defer(PureApp *app, bool deferred) {
return true;
}
+/* ---- Snapshots. Snapshots of one app form a chain from oldest to newest,
+ * each holding the next, so they go oldest first. List items the app lets go
+ * while a snapshot is its newest may be read by that one and older ones, so
+ * they go with it. */
+struct PureSnapshot {
+ unsigned refs;
+ const PureModule *module;
+ uint64_t changes;
+ double values[PURE_MODULE_STATES];
+ PureDraft *texts[PURE_MODULE_STATES];
+ PureList *lists[PURE_MODULE_STATES];
+ PureRetired retired;
+ struct PureSnapshot *newer;
+};
+
+void pure_snapshot_hold(PureSnapshot *snapshot) { __atomic_add_fetch(&snapshot->refs, 1, __ATOMIC_RELAXED); }
+
+void pure_snapshot_release(PureSnapshot *snapshot) {
+ while (snapshot && !__atomic_sub_fetch(&snapshot->refs, 1, __ATOMIC_ACQ_REL)) {
+ PureSnapshot *newer = snapshot->newer;
+ for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) {
+ pure_draft_close(snapshot->texts[i]);
+ pure_list_free(snapshot->lists[i]);
+ }
+ pure_retired_free(&snapshot->retired);
+ free(snapshot);
+ snapshot = newer;
+ }
+}
+
void pure_app_stop(PureApp *app) {
pure_app_finish(app);
close_burst(app, true); /* unsaved changes are dropped with the app */
- for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) {
+ for (uint32_t i = 0; i < PURE_MODULE_STATES && !app->reading; ++i) {
pure_draft_close(app->texts[i]);
pure_list_free(app->lists[i]);
pure_draft_close(app->list_drafts[i]);
}
+ /* Items the newest snapshot may still read go when it goes. */
+ if (app->snapshot) {
+ app->snapshot->retired = app->retired;
+ pure_snapshot_release(app->snapshot);
+ } else
+ pure_retired_free(&app->retired);
pure_draft_close(app->stored_values);
free(app->pending);
pure_undo_free(&app->undo);
@@ -1580,6 +1616,50 @@ PureResult pure_app_cell_text(PureApp *app, uint32_t column, size_t row, bool (*
return result == PURE_OK ? visit_value(app, &app->view_arena, slot.bytes, visit, context) : result;
}
+PureSnapshot *pure_app_snapshot(PureApp *app) {
+ if (app->open || app->reading) return NULL;
+ const PureModule *m = app->module;
+ PureSnapshot *made = calloc(1, sizeof(*made)), *last = app->snapshot;
+ if (!made) return NULL;
+ made->refs = 1;
+ made->module = m;
+ made->changes = app->changes;
+ memcpy(made->values, app->values, sizeof(made->values));
+ bool ok = true;
+ for (uint32_t i = 0; i < m->states && ok; ++i) {
+ if (app->texts[i]) ok = (made->texts[i] = pure_draft_freeze(app->texts[i])) != NULL;
+ if (!ok || !app->lists[i]) continue;
+ if (last && last->lists[i] && pure_list_current(last->lists[i], app->lists[i])) {
+ pure_list_hold(last->lists[i]);
+ made->lists[i] = last->lists[i];
+ } else
+ ok = (made->lists[i] = pure_list_share(app->lists[i], &app->retired)) != NULL;
+ }
+ if (!ok) {
+ pure_snapshot_release(made);
+ return NULL;
+ }
+ made->refs = last ? 3 : 2; /* the app's, the caller's and the last one's */
+ if (last) {
+ last->retired = app->retired;
+ last->newer = made;
+ pure_snapshot_release(last);
+ } else
+ pure_retired_free(&app->retired); /* shared only by a snapshot never made */
+ app->retired = (PureRetired){0};
+ app->snapshot = made;
+ return made;
+}
+
+void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) {
+ reader->module = snapshot->module;
+ reader->reading = true;
+ reader->changes = snapshot->changes; /* what the row cache is fresh for */
+ memcpy(reader->values, snapshot->values, sizeof(reader->values));
+ memcpy(reader->texts, snapshot->texts, sizeof(reader->texts));
+ memcpy(reader->lists, snapshot->lists, sizeof(reader->lists));
+}
+
/* A list with record's fields: each takes the field of `from` with the same
* name and kind, or its default. */
PURE_COLD PureResult pure_list_migrate(PureList **out, const PureList *from, const char *const *names,