erlang: link/unlink/monitor/demonitor + refs (+17 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
This commit is contained in:
@@ -117,6 +117,24 @@
|
||||
er-pid-equal?
|
||||
(fn (a b) (and (er-pid? a) (er-pid? b) (= (er-pid-id a) (er-pid-id b)))))
|
||||
|
||||
;; ── refs ─────────────────────────────────────────────────────────
|
||||
(define er-mk-ref (fn (id) {:id id :tag "ref"}))
|
||||
(define er-ref? (fn (v) (er-is-tagged? v "ref")))
|
||||
(define
|
||||
er-ref-equal?
|
||||
(fn (a b) (and (er-ref? a) (er-ref? b) (= (get a :id) (get b :id)))))
|
||||
|
||||
(define
|
||||
er-ref-new!
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((s (er-sched)))
|
||||
(let
|
||||
((n (get s :next-ref)))
|
||||
(dict-set! s :next-ref (+ n 1))
|
||||
(er-mk-ref n)))))
|
||||
|
||||
;; ── scheduler state ──────────────────────────────────────────────
|
||||
(define er-scheduler (list nil))
|
||||
|
||||
@@ -128,6 +146,7 @@
|
||||
er-scheduler
|
||||
0
|
||||
{:next-pid 0
|
||||
:next-ref 0
|
||||
:current nil
|
||||
:processes {}
|
||||
:runnable (er-q-new)})))
|
||||
@@ -190,6 +209,7 @@
|
||||
:mailbox (er-q-new)
|
||||
:state "runnable"
|
||||
:monitors (list)
|
||||
:monitored-by (list)
|
||||
:continuation nil
|
||||
:receive-pats nil
|
||||
:trap-exit false
|
||||
@@ -296,9 +316,165 @@
|
||||
(= (len vs) 1) (raise (er-mk-exit-marker (nth vs 0)))
|
||||
(= (len vs) 2)
|
||||
(error
|
||||
"Erlang: exit/2 (signal another process) deferred to Phase 4 (links)")
|
||||
"Erlang: exit/2 (signal another process) deferred to next Phase 4 step (signal propagation)")
|
||||
:else (error "Erlang: exit: wrong arity"))))
|
||||
|
||||
;; ── links / monitors / refs ─────────────────────────────────────
|
||||
(define
|
||||
er-bif-is-reference
|
||||
(fn (vs) (er-bool (er-ref? (er-bif-arg1 vs "is_reference")))))
|
||||
|
||||
(define
|
||||
er-bif-make-ref
|
||||
(fn
|
||||
(vs)
|
||||
(if
|
||||
(not (= (len vs) 0))
|
||||
(error "Erlang: make_ref/0: arity")
|
||||
(er-ref-new!))))
|
||||
|
||||
;; Add `target` to `pid`'s :links list if not already there.
|
||||
(define
|
||||
er-link-add-one!
|
||||
(fn
|
||||
(pid target)
|
||||
(let
|
||||
((links (er-proc-field pid :links)))
|
||||
(when
|
||||
(not (er-link-has? links target))
|
||||
(append! links target)))))
|
||||
|
||||
(define
|
||||
er-link-has?
|
||||
(fn
|
||||
(links target)
|
||||
(cond
|
||||
(= (len links) 0) false
|
||||
(er-pid-equal? (nth links 0) target) true
|
||||
:else (er-link-has? (er-slice-list links 1) target))))
|
||||
|
||||
(define
|
||||
er-link-remove-one!
|
||||
(fn
|
||||
(pid target)
|
||||
(let
|
||||
((old (er-proc-field pid :links)) (out (list)))
|
||||
(for-each
|
||||
(fn
|
||||
(i)
|
||||
(let
|
||||
((p (nth old i)))
|
||||
(when (not (er-pid-equal? p target)) (append! out p))))
|
||||
(range 0 (len old)))
|
||||
(er-proc-set! pid :links out))))
|
||||
|
||||
(define
|
||||
er-bif-link
|
||||
(fn
|
||||
(vs)
|
||||
(let
|
||||
((target (er-bif-arg1 vs "link")) (me (er-sched-current-pid)))
|
||||
(cond
|
||||
(not (er-pid? target)) (error "Erlang: link: not a pid")
|
||||
(er-pid-equal? target me) (er-mk-atom "true")
|
||||
(not (er-proc-exists? target))
|
||||
(raise (er-mk-exit-marker (er-mk-atom "noproc")))
|
||||
:else (do
|
||||
(er-link-add-one! me target)
|
||||
(er-link-add-one! target me)
|
||||
(er-mk-atom "true"))))))
|
||||
|
||||
(define
|
||||
er-bif-unlink
|
||||
(fn
|
||||
(vs)
|
||||
(let
|
||||
((target (er-bif-arg1 vs "unlink")) (me (er-sched-current-pid)))
|
||||
(cond
|
||||
(not (er-pid? target)) (error "Erlang: unlink: not a pid")
|
||||
:else (do
|
||||
(er-link-remove-one! me target)
|
||||
(when
|
||||
(er-proc-exists? target)
|
||||
(er-link-remove-one! target me))
|
||||
(er-mk-atom "true"))))))
|
||||
|
||||
(define
|
||||
er-bif-monitor
|
||||
(fn
|
||||
(vs)
|
||||
(if
|
||||
(not (= (len vs) 2))
|
||||
(error "Erlang: monitor/2: arity")
|
||||
(let
|
||||
((kind (nth vs 0))
|
||||
(target (nth vs 1))
|
||||
(me (er-sched-current-pid)))
|
||||
(cond
|
||||
(not (and (er-atom? kind) (= (get kind :name) "process")))
|
||||
(error "Erlang: monitor: only 'process' supported")
|
||||
(not (er-pid? target)) (error "Erlang: monitor: not a pid")
|
||||
:else (let
|
||||
((ref (er-ref-new!)))
|
||||
(append!
|
||||
(er-proc-field me :monitors)
|
||||
{:ref ref :pid target})
|
||||
(when
|
||||
(er-proc-exists? target)
|
||||
(append!
|
||||
(er-proc-field target :monitored-by)
|
||||
{:from me :ref ref}))
|
||||
ref))))))
|
||||
|
||||
(define
|
||||
er-bif-demonitor
|
||||
(fn
|
||||
(vs)
|
||||
(let
|
||||
((ref (er-bif-arg1 vs "demonitor")) (me (er-sched-current-pid)))
|
||||
(if
|
||||
(not (er-ref? ref))
|
||||
(error "Erlang: demonitor: not a reference")
|
||||
(do
|
||||
(er-demonitor-purge! me ref)
|
||||
(er-mk-atom "true"))))))
|
||||
|
||||
(define
|
||||
er-demonitor-purge!
|
||||
(fn
|
||||
(me ref)
|
||||
(let
|
||||
((old (er-proc-field me :monitors)) (out (list)) (target-ref (list nil)))
|
||||
(for-each
|
||||
(fn
|
||||
(i)
|
||||
(let
|
||||
((m (nth old i)))
|
||||
(if
|
||||
(er-ref-equal? (get m :ref) ref)
|
||||
(set-nth! target-ref 0 (get m :pid))
|
||||
(append! out m))))
|
||||
(range 0 (len old)))
|
||||
(er-proc-set! me :monitors out)
|
||||
(when
|
||||
(and
|
||||
(not (= (nth target-ref 0) nil))
|
||||
(er-proc-exists? (nth target-ref 0)))
|
||||
(let
|
||||
((target (nth target-ref 0))
|
||||
(oldby (er-proc-field (nth target-ref 0) :monitored-by))
|
||||
(out2 (list)))
|
||||
(for-each
|
||||
(fn
|
||||
(i)
|
||||
(let
|
||||
((m (nth oldby i)))
|
||||
(when
|
||||
(not (er-ref-equal? (get m :ref) ref))
|
||||
(append! out2 m))))
|
||||
(range 0 (len oldby)))
|
||||
(er-proc-set! target :monitored-by out2))))))
|
||||
|
||||
;; ── scheduler loop ──────────────────────────────────────────────
|
||||
;; Each scheduler step wraps the process body in `guard`. `receive`
|
||||
;; with no match captures a `call/cc` continuation onto the proc
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"language": "erlang",
|
||||
"total_pass": 358,
|
||||
"total": 358,
|
||||
"total_pass": 375,
|
||||
"total": 375,
|
||||
"suites": [
|
||||
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
|
||||
{"name":"parse","pass":52,"total":52,"status":"ok"},
|
||||
{"name":"eval","pass":174,"total":174,"status":"ok"},
|
||||
{"name":"eval","pass":191,"total":191,"status":"ok"},
|
||||
{"name":"runtime","pass":39,"total":39,"status":"ok"},
|
||||
{"name":"ring","pass":4,"total":4,"status":"ok"},
|
||||
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Erlang-on-SX Scoreboard
|
||||
|
||||
**Total: 358 / 358 tests passing**
|
||||
**Total: 375 / 375 tests passing**
|
||||
|
||||
| | Suite | Pass | Total |
|
||||
|---|---|---|---|
|
||||
| ✅ | tokenize | 62 | 62 |
|
||||
| ✅ | parse | 52 | 52 |
|
||||
| ✅ | eval | 174 | 174 |
|
||||
| ✅ | eval | 191 | 191 |
|
||||
| ✅ | runtime | 39 | 39 |
|
||||
| ✅ | ring | 4 | 4 |
|
||||
| ✅ | ping-pong | 4 | 4 |
|
||||
|
||||
@@ -432,6 +432,76 @@
|
||||
(nm (er-last-main-exit-reason)))
|
||||
"from_fn")
|
||||
|
||||
;; ── refs / link / monitor ──────────────────────────────────────
|
||||
(er-eval-test "make_ref tag"
|
||||
(get (ev "make_ref()") :tag) "ref")
|
||||
(er-eval-test "is_reference fresh"
|
||||
(nm (ev "R = make_ref(), is_reference(R)")) "true")
|
||||
(er-eval-test "is_reference pid"
|
||||
(nm (ev "is_reference(self())")) "false")
|
||||
(er-eval-test "is_reference number"
|
||||
(nm (ev "is_reference(42)")) "false")
|
||||
(er-eval-test "make_ref distinct"
|
||||
(nm (ev "R1 = make_ref(), R2 = make_ref(), R1 =:= R2")) "false")
|
||||
(er-eval-test "make_ref same id eq"
|
||||
(nm (ev "R = make_ref(), R =:= R")) "true")
|
||||
|
||||
(er-eval-test "link returns true"
|
||||
(nm (ev "P = spawn(fun () -> ok end), link(P)")) "true")
|
||||
(er-eval-test "self link returns true"
|
||||
(nm (ev "link(self())")) "true")
|
||||
(er-eval-test "unlink returns true"
|
||||
(nm (ev "P = spawn(fun () -> ok end), link(P), unlink(P)")) "true")
|
||||
(er-eval-test "unlink without link"
|
||||
(nm (ev "P = spawn(fun () -> ok end), unlink(P)")) "true")
|
||||
|
||||
(er-eval-test "monitor returns ref"
|
||||
(get (ev "P = spawn(fun () -> ok end), monitor(process, P)") :tag)
|
||||
"ref")
|
||||
(er-eval-test "monitor refs distinct"
|
||||
(nm (ev "P = spawn(fun () -> ok end), R1 = monitor(process, P), R2 = monitor(process, P), R1 =:= R2"))
|
||||
"false")
|
||||
(er-eval-test "demonitor returns true"
|
||||
(nm (ev "P = spawn(fun () -> ok end), R = monitor(process, P), demonitor(R)"))
|
||||
"true")
|
||||
|
||||
;; Bidirectional link recorded on both sides.
|
||||
(er-eval-test "link bidirectional"
|
||||
(do
|
||||
(ev "P = spawn(fun () -> receive forever -> ok end end), link(P)")
|
||||
;; After eval, check links on main + child via accessors.
|
||||
(and
|
||||
(= (len (er-proc-field (er-mk-pid 0) :links)) 1)
|
||||
(= (len (er-proc-field (er-mk-pid 1) :links)) 1)))
|
||||
true)
|
||||
|
||||
;; unlink clears both sides.
|
||||
(er-eval-test "unlink clears both"
|
||||
(do
|
||||
(ev "P = spawn(fun () -> receive forever -> ok end end), link(P), unlink(P)")
|
||||
(and
|
||||
(= (len (er-proc-field (er-mk-pid 0) :links)) 0)
|
||||
(= (len (er-proc-field (er-mk-pid 1) :links)) 0)))
|
||||
true)
|
||||
|
||||
;; monitor adds entries to both lists.
|
||||
(er-eval-test "monitor records both sides"
|
||||
(do
|
||||
(ev "P = spawn(fun () -> receive forever -> ok end end), monitor(process, P)")
|
||||
(and
|
||||
(= (len (er-proc-field (er-mk-pid 0) :monitors)) 1)
|
||||
(= (len (er-proc-field (er-mk-pid 1) :monitored-by)) 1)))
|
||||
true)
|
||||
|
||||
;; demonitor clears both lists.
|
||||
(er-eval-test "demonitor clears both"
|
||||
(do
|
||||
(ev "P = spawn(fun () -> receive forever -> ok end end), R = monitor(process, P), demonitor(R)")
|
||||
(and
|
||||
(= (len (er-proc-field (er-mk-pid 0) :monitors)) 0)
|
||||
(= (len (er-proc-field (er-mk-pid 1) :monitored-by)) 0)))
|
||||
true)
|
||||
|
||||
(define
|
||||
er-eval-test-summary
|
||||
(str "eval " er-eval-test-pass "/" er-eval-test-count))
|
||||
|
||||
@@ -372,6 +372,7 @@
|
||||
(range 0 (len ea)))))
|
||||
(and (= (type-of a) "string") (= (type-of b) "string")) (= a b)
|
||||
(and (er-pid? a) (er-pid? b)) (= (get a :id) (get b :id))
|
||||
(and (er-ref? a) (er-ref? b)) (= (get a :id) (get b :id))
|
||||
:else false)))
|
||||
|
||||
;; Exact equality: 1 =/= 1.0 in Erlang.
|
||||
@@ -562,9 +563,15 @@
|
||||
(= name "atom_to_list") (er-bif-atom-to-list vs)
|
||||
(= name "list_to_atom") (er-bif-list-to-atom vs)
|
||||
(= name "is_pid") (er-bif-is-pid vs)
|
||||
(= name "is_reference") (er-bif-is-reference vs)
|
||||
(= name "self") (er-bif-self vs)
|
||||
(= name "spawn") (er-bif-spawn vs)
|
||||
(= name "exit") (er-bif-exit vs)
|
||||
(= name "make_ref") (er-bif-make-ref vs)
|
||||
(= name "link") (er-bif-link vs)
|
||||
(= name "unlink") (er-bif-unlink vs)
|
||||
(= name "monitor") (er-bif-monitor vs)
|
||||
(= name "demonitor") (er-bif-demonitor vs)
|
||||
:else (error
|
||||
(str "Erlang: undefined function '" name "/" (len vs) "'")))))
|
||||
|
||||
@@ -894,6 +901,7 @@
|
||||
(er-tuple? v) (str "{" (er-format-tuple-elems (get v :elements)) "}")
|
||||
(er-fun? v) "#Fun"
|
||||
(er-pid? v) (str "<pid:" (get v :id) ">")
|
||||
(er-ref? v) (str "#Ref<" (get v :id) ">")
|
||||
:else (str v))))
|
||||
|
||||
(define
|
||||
|
||||
Reference in New Issue
Block a user