diff options
| author | eunhhu <[email protected]> | 2026-09-26 22:56:32 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-26 22:56:32 +0100 |
| commit | 42021a500d19dbe091075338d1a621498a81f167 (patch) | |
| tree | ee3e2df5f0d1ba20ef5d681bf67a148d8c896b03 /prototypes/connections/module.c | |
| parent | d2adae5deacc1545cd5dbdfcc5b80697c91dac09 (diff) | |
| download | pure-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.c | 82 |
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, |
