feat: core.async Phase 2 — dynamic var binding conveyance
Jolt's dynamic-var binding stack was a single global array, so concurrent go blocks interleaved each other's bindings and a go block didn't see the bindings in effect when it was spawned. Move the binding stack into Janet's fiber-local dyn (:jolt/binding-stack): each fiber (go block) lazily gets its own array, so bindings can't interleave. Janet ev/go fibers inherit the parent's dyn, but go-spawn now snapshots the binding stack at spawn time and installs a private copy in the new fiber — Clojure binding conveyance. A go block's own (binding ...) shadows the conveyed frame. types.janet: cur-binding-stack / snapshot-bindings / install-bindings; var-get/ var-set/push/pop use the fiber-local stack. async.janet: go-spawn conveys. spec: core.async/binding-conveyance (4 cases — conveyance, isolation, no leak to root, inner shadowing). jpm test green.
This commit is contained in:
parent
7d1e1f42e1
commit
e8cb4605ac
4 changed files with 52 additions and 9 deletions
|
|
@ -55,9 +55,15 @@
|
|||
# Run thunk (a jolt 0-arg closure, directly callable) in a fiber; return a
|
||||
# buffered(1) channel that conveys its value once, then closes. A nil result
|
||||
# just closes. Buffered(1) so a fire-and-forget go leaves no parked fiber.
|
||||
#
|
||||
# The dynamic-var bindings in effect at spawn time are conveyed into the fiber
|
||||
# (Clojure binding conveyance): we snapshot them here (on the spawning fiber)
|
||||
# and install a private copy inside the new fiber before running the body.
|
||||
(defn async-go-spawn [thunk]
|
||||
(def snap (snapshot-bindings))
|
||||
(def w (async-chan 1))
|
||||
(ev/go (fn []
|
||||
(install-bindings snap)
|
||||
(def res (protect (thunk)))
|
||||
(when (and (in res 0) (not (nil? (in res 1))))
|
||||
(async-give w (in res 1)))
|
||||
|
|
|
|||
|
|
@ -40,17 +40,36 @@
|
|||
# Var
|
||||
# ============================================================
|
||||
|
||||
(def- binding-stack @[]) # stack of {var → value} tables for thread-local bindings
|
||||
# Dynamic-var binding stack. Stored fiber-locally (via Janet's dyn), so that
|
||||
# concurrent go blocks — each a Janet fiber — don't interleave each other's
|
||||
# dynamic bindings, and a go block conveys the bindings in effect when it was
|
||||
# spawned (see snapshot-bindings/install-bindings). Each fiber lazily gets its
|
||||
# own array on first use.
|
||||
(defn cur-binding-stack []
|
||||
(or (dyn :jolt/binding-stack)
|
||||
(let [s @[]] (setdyn :jolt/binding-stack s) s)))
|
||||
|
||||
(defn push-thread-bindings
|
||||
"Push a frame of dynamic var bindings. Takes a struct of var→value."
|
||||
[bindings]
|
||||
(array/push binding-stack bindings))
|
||||
(array/push (cur-binding-stack) bindings))
|
||||
|
||||
(defn pop-thread-bindings
|
||||
"Pop the most recent frame of dynamic var bindings."
|
||||
[]
|
||||
(array/pop binding-stack))
|
||||
(array/pop (cur-binding-stack)))
|
||||
|
||||
(defn snapshot-bindings
|
||||
"Shallow copy of the current binding stack (frames are immutable value maps).
|
||||
Captured by a go block at spawn time for binding conveyance."
|
||||
[]
|
||||
(array/slice (cur-binding-stack)))
|
||||
|
||||
(defn install-bindings
|
||||
"Install a snapshot as this fiber's binding stack (a fresh copy, so the
|
||||
fiber's own push/pop/var-set don't mutate the snapshot's frames array)."
|
||||
[snap]
|
||||
(setdyn :jolt/binding-stack (array/slice snap)))
|
||||
|
||||
(defn make-var
|
||||
"Create a new Jolt Var.
|
||||
|
|
@ -107,10 +126,11 @@
|
|||
Otherwise return the root binding."
|
||||
[v]
|
||||
# walk binding stack top-down for this var
|
||||
(def bs (cur-binding-stack))
|
||||
(var result nil)
|
||||
(var i (dec (length binding-stack)))
|
||||
(var i (dec (length bs)))
|
||||
(while (>= i 0)
|
||||
(let [frame (in binding-stack i)
|
||||
(let [frame (in bs i)
|
||||
val (get frame v)]
|
||||
(if (not (nil? val))
|
||||
(do
|
||||
|
|
@ -124,12 +144,13 @@
|
|||
the innermost frame that binds it (matching Clojure, where var-set targets the
|
||||
current binding); otherwise set the root."
|
||||
[v val]
|
||||
(var i (dec (length binding-stack)))
|
||||
(def bs (cur-binding-stack))
|
||||
(var i (dec (length bs)))
|
||||
(var done false)
|
||||
(while (and (not done) (>= i 0))
|
||||
(let [frame (in binding-stack i)]
|
||||
(let [frame (in bs i)]
|
||||
(if (not (nil? (get frame v)))
|
||||
(do (put binding-stack i (merge frame {v val})) (set done true))
|
||||
(do (put bs i (merge frame {v val})) (set done true))
|
||||
(-- i))))
|
||||
(unless done (put v :root val))
|
||||
val)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue