Refactor phase 4b: share the ctx-image load/save dance (jolt-q5ql)
init-cached (core image) and the deps-image (main.janet) each hand-wrote the same fork->slurp->validate-ctx?->rewire load and the snapshot->tmp->atomic-rename save. Extract load-ctx-image / save-ctx-image (in api.janet, beside snapshot/fork): the two callers now differ only in the validity predicate they pass — none for the core image (its source fingerprint is already in the path), a deps-manifest mtime check for the deps image. The per-process print-method-cb rewiring an image restore must replay is threaded as a callback so the helpers don't depend on core. Kept in api.janet rather than a new ctx_image.janet module: Janet's `use` doesn't transitively re-export, and snapshot/fork already live in api and are consumed via (use ./api) by main and the test harnesses — a separate module would force every caller to import it directly. (load-image/save-image collide with Janet builtins, hence the ctx- prefix.) Full gate green; ctx-image-test cold/warm + deps tests pass.
This commit is contained in:
parent
dd64fd4e74
commit
21721473a5
2 changed files with 44 additions and 33 deletions
|
|
@ -245,7 +245,7 @@
|
|||
(and (os/getenv "JOLT_WHOLE_PROGRAM") (get (ctx :env) :direct-linking?)))
|
||||
ctx))
|
||||
|
||||
# --- Context snapshot/fork (cheap isolated copies) --------------------------
|
||||
# --- Context snapshot/fork + disk image (AOT context image) ------------------
|
||||
#
|
||||
# init is expensive (~50 ms interpreted, ~900 ms compiled: tier loading, analyzer
|
||||
# build, macro recompilation). For workloads that need MANY isolated contexts —
|
||||
|
|
@ -254,6 +254,12 @@
|
|||
# shares nothing mutable with the original: defs, protocol extensions, hierarchy
|
||||
# changes, atom states in one fork are invisible to the others.
|
||||
#
|
||||
# load-ctx-image/save-ctx-image add the disk layer (fork->validate->rewire load,
|
||||
# atomic save) shared by init-cached and main's deps-image cache (jolt-q5ql) —
|
||||
# the callers differ only in the validity predicate. They live here (not a
|
||||
# separate module) because Janet's `use` doesn't transitively re-export, and the
|
||||
# snapshot/fork they build on are already api's and consumed via (use ./api).
|
||||
#
|
||||
# The reverse-lookup dicts must be built from root-env (cfunctions and abstract
|
||||
# values from the Janet runtime marshal by reference through them) BEFORE any ctx
|
||||
# values exist in scope — module load time here, so user code can't leak into it.
|
||||
|
|
@ -273,8 +279,30 @@
|
|||
[snap]
|
||||
(unmarshal snap image-load-dict))
|
||||
|
||||
# --- Disk-cached init (AOT context image) ------------------------------------
|
||||
#
|
||||
(defn load-ctx-image
|
||||
"Load a marshaled ctx image from `path`: fork it, verify it really is a ctx (a
|
||||
corrupt image can unmarshal to a non-ctx value), run the optional `valid?`
|
||||
predicate (e.g. a source-mtime check), and replay the per-PROCESS module-state
|
||||
wiring an image restore skips by calling `(rewire! ctx)` (the print-method hook
|
||||
lives in module state, not the marshaled ctx). Returns the ctx, or nil if the
|
||||
file is absent, unreadable, not a ctx, or fails `valid?`."
|
||||
[path rewire! &opt valid?]
|
||||
(when (os/stat path)
|
||||
(def r (protect (fork (slurp path))))
|
||||
(when (and (r 0) (ctx? (r 1)) (or (nil? valid?) (valid? (r 1))))
|
||||
(def c (r 1))
|
||||
(when rewire! (rewire! c))
|
||||
c)))
|
||||
|
||||
(defn save-ctx-image
|
||||
"Atomically write ctx's marshaled image to `path` (write a pid-tagged tmp file
|
||||
then rename) so concurrent cold starts never observe a torn image. Best-effort:
|
||||
a failed write/rename is swallowed (the next run just rebuilds)."
|
||||
[ctx path]
|
||||
(def tmp (string path "." (os/getpid) ".tmp"))
|
||||
(when (protect (spit tmp (snapshot ctx)))
|
||||
(protect (os/rename tmp path))))
|
||||
|
||||
# init in compile mode is ~2.4 s (tier loading, analyzer self-compile, macro
|
||||
# recompilation) — paid by every PROCESS that builds a ctx from source, e.g.
|
||||
# each `jpm test` file. init-cached pays it once: the built ctx is snapshotted
|
||||
|
|
@ -334,23 +362,12 @@
|
|||
(default opts {})
|
||||
(if (or (= "1" (os/getenv "JOLT_NO_IMAGE_CACHE")) (nil? (src-dir)))
|
||||
(init opts)
|
||||
(let [path (image-cache-path opts)
|
||||
loaded (when (os/stat path)
|
||||
(let [r (protect (fork (slurp path)))]
|
||||
# unmarshal of a corrupt image can also "succeed" with a
|
||||
# non-ctx value, so check the shape, not just the throw.
|
||||
(when (and (r 0) (ctx? (r 1)))
|
||||
(r 1))))]
|
||||
(or (when loaded
|
||||
# per-PROCESS wiring an image restore skips: the renderer's
|
||||
# print-method hook lives in module state, not the marshaled ctx
|
||||
(install-print-method-cb! loaded)
|
||||
loaded)
|
||||
(let [ctx (init opts)
|
||||
tmp (string path "." (os/getpid) ".tmp")]
|
||||
# Atomic publish so concurrent cold starts never see a torn image.
|
||||
(when (protect (spit tmp (snapshot ctx)))
|
||||
(protect (os/rename tmp path)))
|
||||
(let [path (image-cache-path opts)]
|
||||
# The source-fingerprint is already in the path, so any cached image at
|
||||
# this path is valid — no extra predicate, just the per-process rewiring.
|
||||
(or (load-ctx-image path install-print-method-cb!)
|
||||
(let [ctx (init opts)]
|
||||
(save-ctx-image ctx path)
|
||||
ctx)))))
|
||||
|
||||
(defn eval-one
|
||||
|
|
|
|||
|
|
@ -461,23 +461,17 @@
|
|||
ok)
|
||||
|
||||
(defn- try-load-deps-image [path]
|
||||
(when (and (not (os/getenv "JOLT_NO_DEPS_CACHE")) (os/stat path))
|
||||
(def r (protect (fork (slurp path))))
|
||||
(when (and (r 0) (ctx? (r 1)))
|
||||
(def c (r 1))
|
||||
(def manifest (get (c :env) :deps-manifest))
|
||||
(when (and manifest (manifest-current? manifest))
|
||||
# per-process wiring an image restore must redo (module state, not in the
|
||||
# marshaled ctx) — same as init-cached does for the core image.
|
||||
(jcore/install-print-method-cb! c)
|
||||
c))))
|
||||
(unless (os/getenv "JOLT_NO_DEPS_CACHE")
|
||||
# load-ctx-image forks + validates + rewires (shared with init-cached, jolt-q5ql);
|
||||
# the deps-specific validity check is the source-mtime manifest.
|
||||
(load-ctx-image path jcore/install-print-method-cb!
|
||||
(fn [c] (let [m (get (c :env) :deps-manifest)]
|
||||
(and m (manifest-current? m)))))))
|
||||
|
||||
(defn- save-deps-image [c path]
|
||||
(unless (os/getenv "JOLT_NO_DEPS_CACHE")
|
||||
(put (c :env) :deps-manifest (manifest-of (or (get (c :env) :loaded-files) @[])))
|
||||
(def tmp (string path "." (os/getpid) ".tmp"))
|
||||
(when (protect (spit tmp (snapshot c)))
|
||||
(protect (os/rename tmp path)))))
|
||||
(save-ctx-image c path)))
|
||||
|
||||
(defn- run-main [ns-name argv]
|
||||
(when (nil? ns-name) (eprint "Error: -m/--main requires a namespace") (os/exit 1))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue