Chez concurrency pt.1: real OS-thread futures + blocking promises (shared heap)

future/future-call run the body on a native thread (fork-thread) over the SAME
heap — JVM semantics, not Janet's isolated-heap snapshot. deref blocks on a
mutex+condition latch; timed (deref f ms val) uses an absolute deadline.
promise is a real blocking promise (deref parks until deliver), replacing the
Janet non-blocking atom shim. future?/future-done?/future-cancelled?/future-cancel
/realized? are native (the overlay versions read Janet map keys); re-asserted in
post-prelude over the overlay. pmap/pcalls/pvalues (overlay, over future) light
up for free.

Thread-safety this forces:
- atoms get a per-atom mutex; swap!/swap-vals! are a JVM-style CAS loop (f runs
  outside the lock, so a watch/validator can deref the same atom); reset!/
  compare-and-set! are atomic.
- the dynamic binding stack becomes a Chez thread-parameter, so each future/thread
  has its own; Chez inherits it at fork, giving binding conveyance (the shim also
  installs an explicit snapshot).
- Thread/sleep really sleeps now (a worker sleeping doesn't block the parent).

Re-minted the seed: future-call now resolves at compile time, so pmap compiles to
a var-deref instead of the host-static-call fallback that crashed. image.ss
unchanged.

Corpus: the 2 snapshot cases now match the JVM (shared) not Janet (isolated) —
allowlisted on both Chez gates; the two racy future-cancel cases allowlisted;
"promise undelivered" (blocks on JVM/Chez, profile :bucket :timeout) skipped like
:throws. Zero-Janet corpus 2544 -> 2569, 0 new divergences, floor raised. Full
Janet gate + JVM cert green.

jolt-byjr
This commit is contained in:
Yogthos 2026-06-20 13:11:31 -04:00
parent a23095b502
commit ec30c9e405
10 changed files with 330 additions and 42 deletions

View file

@ -15,11 +15,15 @@
;; the stack before falling back to the cell root. Loaded LAST (after vars.ss and
;; ns.ss) so it chains the fully-extended jolt-var-get and overrides rt.ss var-deref.
(define dyn-binding-stack '())
;; THREAD-LOCAL (jolt-byjr): a Chez thread parameter, so each OS thread (a future
;; / go block) has its own binding stack. Chez initializes a new thread's parameter
;; to the spawning thread's value at fork time, giving Clojure binding conveyance
;; for free (the future shim also installs an explicit snapshot, belt-and-suspenders).
(define dyn-binding-stack (make-thread-parameter '()))
;; find the innermost (cell . value) pair binding CELL, or #f.
(define (dyn-find-binding cell)
(let loop ((frames dyn-binding-stack))
(let loop ((frames (dyn-binding-stack)))
(and (pair? frames)
(or (assq cell (car frames))
(loop (cdr frames))))))
@ -28,7 +32,7 @@
;; value happens to be jolt-nil.
(define dyn-no-binding (list 'no-binding))
(define (dyn-binding-value cell)
(if (pair? dyn-binding-stack)
(if (pair? (dyn-binding-stack))
(let ((p (dyn-find-binding cell)))
(if p
(let ((val (cdr p)))
@ -39,21 +43,21 @@
;; push-thread-bindings: frame is a jolt map of var-cell -> value. Fold it into an
;; identity-keyed alist of mutable pairs and push.
(define (jolt-push-thread-bindings frame)
(set! dyn-binding-stack
(cons (pmap-fold frame (lambda (k v acc) (cons (cons k v) acc)) '())
dyn-binding-stack))
(dyn-binding-stack
(cons (pmap-fold frame (lambda (k v acc) (cons (cons k v) acc)) '())
(dyn-binding-stack)))
jolt-nil)
(define (jolt-pop-thread-bindings)
(when (pair? dyn-binding-stack)
(set! dyn-binding-stack (cdr dyn-binding-stack)))
(when (pair? (dyn-binding-stack))
(dyn-binding-stack (cdr (dyn-binding-stack))))
jolt-nil)
;; get-thread-bindings: a jolt map of every currently-bound cell -> value,
;; innermost wins. Merge oldest-frame-first (the stack head is innermost). The
;; result can be re-pushed by with-bindings* / bound-fn*.
(define (jolt-get-thread-bindings)
(let loop ((frames (reverse dyn-binding-stack)) (m (jolt-hash-map)))
(let loop ((frames (reverse (dyn-binding-stack))) (m (jolt-hash-map)))
(if (null? frames)
m
(loop (cdr frames)