update nrepl to run in a thread

This commit is contained in:
Yogthos 2026-06-28 20:02:56 -04:00
parent 28ee005855
commit 8bba526c8c
2 changed files with 93 additions and 2 deletions

View file

@ -397,4 +397,89 @@
(with-mutex (vector-ref st 1) (with-mutex (vector-ref st 1)
(let loop () (when (> (vector-ref st 0) 0) (condition-wait (vector-ref st 2) (vector-ref st 1)) (loop))))) (let loop () (when (> (vector-ref st 0) 0) (condition-wait (vector-ref st 2) (vector-ref st 1)) (loop)))))
jolt-nil)) jolt-nil))
(cons "getCount" (lambda (self) (vector-ref (jhost-state self) 0))))) (cons "getCount" (lambda (self) (vector-ref (jhost-state self) 0)))))
;; --- main-thread executor ---------------------------------------------------
;; Lets a worker thread (e.g. an nREPL eval future) run a thunk on the thread
;; that owns the GUI main loop. On macOS GTK quartz, g_application_run must run
;; on the process main thread or AppKit aborts (setMainMenu off-main → SIGABRT).
;; Under `joltc nrepl` the accept loop is backgrounded in a future and the
;; primordial thread enters jolt-run-main-pump; glimmer's run marshals its
;; startup through jolt-call-on-main-thread.
;;
;; - With no pump running (`joltc -M:run` calls run directly on the main thread),
;; call-on-main-thread runs the thunk INLINE — unchanged behaviour.
;; - A call from a thunk already executing on the pump runs inline too, so the
;; pump can't deadlock on itself.
;; - Otherwise the thunk is enqueued; the caller blocks until the pump runs it,
;; then receives the value, or the thrown condition is re-raised.
(define jolt-main-queue-mu (make-mutex))
(define jolt-main-queue-cv (make-condition))
(define jolt-main-queue '()) ; FIFO of jolt-main-job, guarded by mu
(define jolt-main-pump-active (box #f)) ; #t while run-main-pump owns this thread
(define jolt-main-pump-stop (box #f)) ; set by stop-main-pump to drain + exit
;; thread-local: this thread is the pump, mid-thunk → nested calls run inline.
(define jolt-in-main-pump? (make-thread-parameter #f))
(define-record-type jolt-main-job
(fields thunk (mutable done?) (mutable ok?) (mutable val) mu cv)
(nongenerative jolt-main-job-v1))
(define (jolt-call-on-main-thread thunk)
(cond
((jolt-in-main-pump?) ; reentrant — already on the pump
(jolt-invoke thunk))
((not (unbox jolt-main-pump-active)) ; no pump — inline, like -M:run
(jolt-invoke thunk))
(else
(let ((job (make-jolt-main-job thunk #f #f jolt-nil (make-mutex) (make-condition))))
(with-mutex jolt-main-queue-mu
(set! jolt-main-queue (append jolt-main-queue (list job)))
(condition-signal jolt-main-queue-cv))
(with-mutex (jolt-main-job-mu job)
(let wait ()
(unless (jolt-main-job-done? job)
(condition-wait (jolt-main-job-cv job) (jolt-main-job-mu job))
(wait))))
(if (jolt-main-job-ok? job)
(jolt-main-job-val job)
(raise (jolt-main-job-val job)))))))
(define (jolt-run-main-pump)
(set-box! jolt-main-pump-stop #f)
(set-box! jolt-main-pump-active #t)
(let loop ()
(let ((job (with-mutex jolt-main-queue-mu
(let wait ()
(cond
((not (null? jolt-main-queue))
(let ((j (car jolt-main-queue)))
(set! jolt-main-queue (cdr jolt-main-queue))
j))
((unbox jolt-main-pump-stop) #f) ; drain done, told to exit
(else (condition-wait jolt-main-queue-cv jolt-main-queue-mu)
(wait)))))))
(when job
(let ((r (dynamic-wind
(lambda () (jolt-in-main-pump? #t))
(lambda ()
(guard (e (#t (cons #f e)))
(cons #t (jolt-invoke (jolt-main-job-thunk job)))))
(lambda () (jolt-in-main-pump? #f)))))
(with-mutex (jolt-main-job-mu job)
(jolt-main-job-ok?-set! job (car r))
(jolt-main-job-val-set! job (cdr r))
(jolt-main-job-done?-set! job #t)
(condition-broadcast (jolt-main-job-cv job))))
(loop)))))
(define (jolt-stop-main-pump)
(with-mutex jolt-main-queue-mu
(set-box! jolt-main-pump-stop #t)
(condition-broadcast jolt-main-queue-cv))
jolt-nil)
(def-var! "jolt.host" "call-on-main-thread" jolt-call-on-main-thread)
(def-var! "jolt.host" "run-main-pump" jolt-run-main-pump)
(def-var! "jolt.host" "stop-main-pump" jolt-stop-main-pump)

View file

@ -188,7 +188,13 @@
(let [port (or (some-> (first (filter #(not (str/starts-with? % "-")) more)) parse-long) (let [port (or (some-> (first (filter #(not (str/starts-with? % "-")) more)) parse-long)
(parse-long (or (jolt.host/getenv "JOLT_NREPL_PORT") "7888")))] (parse-long (or (jolt.host/getenv "JOLT_NREPL_PORT") "7888")))]
(require 'jolt.nrepl) (require 'jolt.nrepl)
((resolve 'jolt.nrepl/start) port (:nrepl-middleware resolved))))) ;; Run the accept loop on a worker thread so the primordial (main) thread
;; stays free to own the GUI main loop. glimmer's run marshals its startup
;; onto this thread via jolt.host/call-on-main-thread — on macOS GTK quartz,
;; g_application_run must run on the main thread or AppKit aborts when it
;; sets the main menu.
(future ((resolve 'jolt.nrepl/start) port (:nrepl-middleware resolved)))
(jolt.host/run-main-pump))))
(defn- usage [] (defn- usage []
(println "usage: jolt <command> [args]") (println "usage: jolt <command> [args]")