erlang: send + selective receive via shift/reset (+13 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled

This commit is contained in:
2026-04-24 20:27:59 +00:00
parent 266693a2f6
commit d191f7cd9e
4 changed files with 224 additions and 29 deletions

View File

@@ -88,6 +88,26 @@
(range h (len items)))
out)))
;; Read the i'th entry (relative to head) without popping.
(define
er-q-nth
(fn (q i) (nth (get q :items) (+ (get q :head-idx) i))))
;; Remove entry at logical index i, shift tail in.
(define
er-q-delete-at!
(fn
(q i)
(let
((h (get q :head-idx)) (items (get q :items)) (new (list)))
(for-each
(fn
(j)
(when (not (= j (+ h i))) (append! new (nth items j))))
(range h (len items)))
(dict-set! q :items new)
(dict-set! q :head-idx 0))))
;; ── pids ─────────────────────────────────────────────────────────
(define er-mk-pid (fn (id) {:id id :tag "pid"}))
(define er-pid? (fn (v) (er-is-tagged? v "pid")))
@@ -251,25 +271,57 @@
(get proc :pid)))))
;; ── scheduler loop ──────────────────────────────────────────────
;; Drain all runnable processes to completion. Synchronous — each
;; spawned process runs its :initial-fun front-to-back with no yielding.
;; receive-driven suspension arrives in the next roadmap step.
;; Each process's entry runs inside a `reset`; `receive` uses `shift`
;; to suspend (saving a continuation on the proc record). When a `!`
;; delivers a message to a waiting process we re-enqueue it — the
;; scheduler step invokes the saved continuation, which retries the
;; receive against the updated mailbox.
(define er-suspend-marker {:tag "er-suspend-marker"})
(define
er-sched-drain!
er-suspended?
(fn
(v)
(and
(= (type-of v) "dict")
(= (get v :tag) "er-suspend-marker"))))
(define
er-sched-run-all!
(fn
()
(let
((pid (er-sched-next-runnable!)))
(when
(not (= pid nil))
(er-sched-set-current! pid)
(er-proc-set! pid :state "running")
(let
((fv (er-proc-field pid :initial-fun)))
(when
(not (= fv nil))
(er-apply-fun fv (list))))
(er-proc-set! pid :state "dead")
(er-proc-set! pid :exit-reason (er-mk-atom "normal"))
(er-sched-set-current! nil)
(er-sched-drain!)))))
(er-sched-step! pid)
(er-sched-run-all!)))))
(define
er-sched-step!
(fn
(pid)
(er-sched-set-current! pid)
(er-proc-set! pid :state "running")
(let
((prev-k (er-proc-field pid :continuation))
(result-ref (list nil)))
(if
(= prev-k nil)
(set-nth!
result-ref
0
(reset (er-apply-fun (er-proc-field pid :initial-fun) (list))))
(do
(er-proc-set! pid :continuation nil)
(set-nth! result-ref 0 (prev-k nil))))
(let
((r (nth result-ref 0)))
(cond
(er-suspended? r) nil
:else (do
(er-proc-set! pid :state "dead")
(er-proc-set! pid :exit-reason (er-mk-atom "normal"))
(er-proc-set! pid :exit-result r)
(er-proc-set! pid :continuation nil)))))
(er-sched-set-current! nil)))