Merge pull request #121 from jolt-lang/refactor-phase4b-ctx-image
Refactor phase 4b: share the ctx-image load/save dance (jolt-q5ql)
This commit is contained in:
commit
10482dae35
2 changed files with 44 additions and 33 deletions
|
|
@ -245,7 +245,7 @@
|
||||||
(and (os/getenv "JOLT_WHOLE_PROGRAM") (get (ctx :env) :direct-linking?)))
|
(and (os/getenv "JOLT_WHOLE_PROGRAM") (get (ctx :env) :direct-linking?)))
|
||||||
ctx))
|
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
|
# init is expensive (~50 ms interpreted, ~900 ms compiled: tier loading, analyzer
|
||||||
# build, macro recompilation). For workloads that need MANY isolated contexts —
|
# build, macro recompilation). For workloads that need MANY isolated contexts —
|
||||||
|
|
@ -254,6 +254,12 @@
|
||||||
# shares nothing mutable with the original: defs, protocol extensions, hierarchy
|
# shares nothing mutable with the original: defs, protocol extensions, hierarchy
|
||||||
# changes, atom states in one fork are invisible to the others.
|
# 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
|
# 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 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.
|
# values exist in scope — module load time here, so user code can't leak into it.
|
||||||
|
|
@ -273,8 +279,30 @@
|
||||||
[snap]
|
[snap]
|
||||||
(unmarshal snap image-load-dict))
|
(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
|
# 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.
|
# 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
|
# each `jpm test` file. init-cached pays it once: the built ctx is snapshotted
|
||||||
|
|
@ -334,23 +362,12 @@
|
||||||
(default opts {})
|
(default opts {})
|
||||||
(if (or (= "1" (os/getenv "JOLT_NO_IMAGE_CACHE")) (nil? (src-dir)))
|
(if (or (= "1" (os/getenv "JOLT_NO_IMAGE_CACHE")) (nil? (src-dir)))
|
||||||
(init opts)
|
(init opts)
|
||||||
(let [path (image-cache-path opts)
|
(let [path (image-cache-path opts)]
|
||||||
loaded (when (os/stat path)
|
# The source-fingerprint is already in the path, so any cached image at
|
||||||
(let [r (protect (fork (slurp path)))]
|
# this path is valid — no extra predicate, just the per-process rewiring.
|
||||||
# unmarshal of a corrupt image can also "succeed" with a
|
(or (load-ctx-image path install-print-method-cb!)
|
||||||
# non-ctx value, so check the shape, not just the throw.
|
(let [ctx (init opts)]
|
||||||
(when (and (r 0) (ctx? (r 1)))
|
(save-ctx-image ctx path)
|
||||||
(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)))
|
|
||||||
ctx)))))
|
ctx)))))
|
||||||
|
|
||||||
(defn eval-one
|
(defn eval-one
|
||||||
|
|
|
||||||
|
|
@ -461,23 +461,17 @@
|
||||||
ok)
|
ok)
|
||||||
|
|
||||||
(defn- try-load-deps-image [path]
|
(defn- try-load-deps-image [path]
|
||||||
(when (and (not (os/getenv "JOLT_NO_DEPS_CACHE")) (os/stat path))
|
(unless (os/getenv "JOLT_NO_DEPS_CACHE")
|
||||||
(def r (protect (fork (slurp path))))
|
# load-ctx-image forks + validates + rewires (shared with init-cached, jolt-q5ql);
|
||||||
(when (and (r 0) (ctx? (r 1)))
|
# the deps-specific validity check is the source-mtime manifest.
|
||||||
(def c (r 1))
|
(load-ctx-image path jcore/install-print-method-cb!
|
||||||
(def manifest (get (c :env) :deps-manifest))
|
(fn [c] (let [m (get (c :env) :deps-manifest)]
|
||||||
(when (and manifest (manifest-current? manifest))
|
(and m (manifest-current? m)))))))
|
||||||
# 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))))
|
|
||||||
|
|
||||||
(defn- save-deps-image [c path]
|
(defn- save-deps-image [c path]
|
||||||
(unless (os/getenv "JOLT_NO_DEPS_CACHE")
|
(unless (os/getenv "JOLT_NO_DEPS_CACHE")
|
||||||
(put (c :env) :deps-manifest (manifest-of (or (get (c :env) :loaded-files) @[])))
|
(put (c :env) :deps-manifest (manifest-of (or (get (c :env) :loaded-files) @[])))
|
||||||
(def tmp (string path "." (os/getpid) ".tmp"))
|
(save-ctx-image c path)))
|
||||||
(when (protect (spit tmp (snapshot c)))
|
|
||||||
(protect (os/rename tmp path)))))
|
|
||||||
|
|
||||||
(defn- run-main [ns-name argv]
|
(defn- run-main [ns-name argv]
|
||||||
(when (nil? ns-name) (eprint "Error: -m/--main requires a namespace") (os/exit 1))
|
(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