aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-19 09:58:47 +0100
committereunhhu <[email protected]>2026-10-19 09:58:47 +0100
commitb8e6a2e5b8fc64ec47adfbe583b8a27200cc5a2e (patch)
treed3a2a5041e42e4ca2fa4a7ec8e5cd10147c9030b /core
parent31ef28609c6135058ffa0645fed9edd75c1086c7 (diff)
downloadpure-b8e6a2e5b8fc64ec47adfbe583b8a27200cc5a2e.tar.gz
pure-b8e6a2e5b8fc64ec47adfbe583b8a27200cc5a2e.zip
The modules an app ran are freed (research 115's next step): every app's module a Loaded with holds (each snapshot of it, each request read with it); one replaced is retired and freed on the apps thread once no app runs it, every connection thread has begun a pass since (woken for it), and nothing holds it; who keeps a module to ask later whether it is the same asks its serial (core: PureModule.serial, a reader's module_serial, pure_snapshot_module, pure_snapshot_release says it freed; serve: routes, groups, links, bindings, queries, the published snapshot), as an address may come back; a link binds under its receiver's lock, a link naming an input under binding_lock; 030's memory.py: 4,000 changes 0 bytes a change (was 19,284), 4,100 freed; stress.py: readers, view workers, push streams and a body across changes, 586 of 600 loads at a freed module's address, every answer right, ASan 1,000 changes clean; a push group that looked while its app resumed missed the new snapshot (published before the app took requests) and waited for its next save: woken again once it takes them (server_view_workers had failed once under load)
Diffstat (limited to 'core')
-rw-r--r--core/module.c13
-rw-r--r--core/module.h9
2 files changed, 18 insertions, 4 deletions
diff --git a/core/module.c b/core/module.c
index f970daf..4b20e25 100644
--- a/core/module.c
+++ b/core/module.c
@@ -2284,6 +2284,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
}
out->runner = out->calls || out->nesting > PURE_MODULE_NESTING ? 2 : out->batches ? 1 : 0;
out->jit = pure_jit_make(out); /* none: the executor runs it all */
+ static uint64_t loads;
+ out->serial = __atomic_add_fetch(&loads, 1, __ATOMIC_RELAXED);
return PURE_OK;
refused:
if (error_size) snprintf(error, error_size, "%s (%u)", why, bad);
@@ -5171,8 +5173,10 @@ const PureList *pure_snapshot_list(const PureSnapshot *snapshot, uint32_t state)
return state >= PURE_LIST_HEARD ? snapshot->runtime[state - PURE_LIST_HEARD] : snapshot->lists[state];
}
-void pure_snapshot_release(PureSnapshot *snapshot) {
- if (!snapshot || __atomic_sub_fetch(&snapshot->refs, 1, __ATOMIC_ACQ_REL)) return;
+const PureModule *pure_snapshot_module(const PureSnapshot *snapshot) { return snapshot->module; }
+
+bool pure_snapshot_release(PureSnapshot *snapshot) {
+ if (!snapshot || __atomic_sub_fetch(&snapshot->refs, 1, __ATOMIC_ACQ_REL)) return false;
Register *all = snapshot->all;
if (all) { /* out of the links: what the one before may read goes to it (still linked, so still there) */
lock_register(all);
@@ -5193,6 +5197,7 @@ void pure_snapshot_release(PureSnapshot *snapshot) {
pure_retired_free(&all->returned); /* the app is gone: no list takes it back */
free(all);
}
+ return true;
}
static void let_own_go(PureApp *app);
@@ -6058,13 +6063,15 @@ static void let_own_go(PureApp *app) {
}
bool pure_app_read(PureApp *reader, const PureSnapshot *snapshot) {
- if (reader->module != snapshot->module) drop_passes(reader), let_own_go(reader); /* laid out for its module */
+ if (reader->module_serial != snapshot->module->serial) /* laid out for its module (by serial: freed, another may */
+ drop_passes(reader), let_own_go(reader); /* come to its address) */
if (!reader->states || reader->laid != snapshot->states) { /* its arrays for as many states (027's 4f-2) */
void *was = reader->states;
if (!lay_app(reader, snapshot->states)) return false;
free(was);
}
reader->module = snapshot->module;
+ reader->module_serial = snapshot->module->serial;
reader->reading = true;
reader->changes = snapshot->changes; /* what the row cache is fresh for */
memcpy(reader->values, snapshot->values, snapshot->states * sizeof(*reader->values));
diff --git a/core/module.h b/core/module.h
index 43d9ccb..aff6240 100644
--- a/core/module.h
+++ b/core/module.h
@@ -453,6 +453,9 @@ typedef struct {
uint16_t *signature_record;
bool calls; /* it has functions: its blocks run in the executor that calls (module.c) */
uint32_t nesting; /* its loops' deepest nesting (set at load) */
+ /* Which load this is, of the process's: never 0, never the same twice. Who keeps a module's address to ask later
+ * whether it is the same module asks this instead, since another may come to that address once one is freed. */
+ uint64_t serial;
} PureModule;
/* Whether a state of m is transient (PURE_STATE_TRANSIENT). */
@@ -533,6 +536,7 @@ typedef struct { unsigned char *bytes; size_t used, capacity; } PureArena;
enum { PURE_LIST_HEARD = 0x10000, PURE_LIST_OUTBOX, PURE_LIST_RUNTIME = 2 };
typedef struct {
const PureModule *module;
+ uint64_t module_serial; /* a reader's: its module's serial when it read (the module may since be freed) */
PureData data;
/* The arrays of each state (027's 4f-2), sized to the module in one block (states) for the states it was laid out
* for (laid). */
@@ -792,7 +796,10 @@ void pure_app_checkpointed(PureApp *, PureCheckpoint *, PureResult);
PureSnapshot *pure_app_snapshot(PureApp *);
void pure_snapshot_hold(PureSnapshot *);
/* On any thread; the last reference frees it. */
-void pure_snapshot_release(PureSnapshot *);
+/* Let a reference go: true when it was the last, the snapshot freed. */
+bool pure_snapshot_release(PureSnapshot *);
+/* The module the snapshot's app ran when it was taken. */
+const PureModule *pure_snapshot_module(const PureSnapshot *);
/* Show a snapshot's states in reader, a zeroed app or a reader before: its
* views read them while the snapshot is held, with the reader's own arena and
* row cache (one thread at a time). It applies no events. pure_app_stop frees