A running program's reply never says it has not called (agent/start ...), because the agent's constructor has bound the socket before main

This commit is contained in:
Joseph Ferano 2026-09-25 10:16:25 +07:00
parent ddf7ca4974
commit 3636f31cba
4 changed files with 19 additions and 48 deletions

View File

@ -1565,11 +1565,12 @@ only by an explicit call. A sentence about the shape of the gate rather than an
observed problem: only the daemon sets the variable and it never runs release
builds. The fix, if it is ever felt, is a narrower gate.
** NEXT The daemon's "has not called (agent/start ...)" note is unreachable
Decided 2026-09-25: retire the note in the same dead-code sweep.
Unreachable, not merely unexercised: the one state it was true of is closed by the
constructor. Retiring it is the author's call over a lane that merged days ago, so
it is left in place saying a true thing about a state nothing can be in.
** DONE The daemon's "has not called (agent/start ...)" note is unreachable
CLOSED: [2026-09-25]
Retired, with the matching arm of an evaluation's timeout. A running program
either links the agent, whose constructor binds the socket before =main=, or
links none and has its delivery refused; no reply names =(agent/start ...)= as
not yet called.
** DONE The allocation registry
CLOSED: [2026-09-13]

View File

@ -7024,9 +7024,8 @@ registry now holds.
**When it happens.** At the next frame boundary of a running program. A parked program — one whose `main` has
finished — drains its ring when that sleep ends, so the store lands at the top of its next run, ahead of `main`; the
run's own startup then computes the initialiser again, which is not a wart but the two events `defparameter` has: an
evaluation assigns, and a re-run re-initialises. A program that has not called `(agent/start ...)` yet installs at its
next `(agent/poll)`, and never if it has none — the reply already says so. A program stopped at a break runs it in the
break loop, like any other evaluation.
evaluation assigns, and a re-run re-initialises. A program stopped at a break runs it in the break loop, like any other
evaluation.
**A brand-new `def`** gets its initialiser run too. Its storage comes from `flan_dev_global` and nothing in the host's
`.init-globals` names it, so before this a new `(def n i64 (count-them))` came up as `calloc`'s zeroes and stayed

View File

@ -124,7 +124,7 @@ let await ?(ms = 5000) f =
(* Whether the program has bound the socket it receives modules on.
Cheap enough to ask on every reply — one [stat] — and asked rather than
Cheap enough to ask before every request — one [stat] — and asked rather than
remembered because the answer moves in one direction at a moment this side
does not get to see: [agent/start] runs on the program's own thread. *)
let agent_bound t = Sys.file_exists t.agent
@ -781,7 +781,7 @@ let refusal ~parked reply =
refusal; it is the difference between "queued" and "running", which is a
difference only the program can close and only at a moment of its choosing.
Three of them, in the order of how far the module is from being live:
Two of them, in the order of how far the module is from being live:
A PARKED program has finished [main] and is asleep in [flan_merged_park].
Its ring is drained whenever that sleep ends — for an expression to run, or
@ -796,16 +796,11 @@ let refusal ~parked reply =
not per session, because the reader of a *new* park may not be the reader
of the last one.
A RUNNING program that has not bound its agent socket has not called
[(agent/start ...)] yet — it may be about to, ahead of a window that is
still being created, or it may have no such call at all. Either way the
module is in the ring and the ring is drained by [(agent/poll)], so what
can honestly be promised is the poll and not a frame: a program with no
poll in it never installs this, and saying "at its next frame boundary"
would be the reply that made a redefinition look applied when it was not.
A RUNNING program that has bound it needs no note: the reply already says
queued, and the frame boundary is the next one it reaches. *)
A RUNNING program needs no note: the reply already says queued, and the
frame boundary is the next one it reaches. A running program whose agent
socket is not bound is not a third case: the agent package binds it in a
constructor, before [main], and a program that does not link the package
has nothing to queue a module on, so the delivery is refused instead. *)
let install_note t ~parked =
if parked then begin
let first = not t.park_noted in
@ -821,13 +816,6 @@ let install_note t ~parked =
else "queued; installs no later than the parked program's next run")
]
end
else if not (agent_bound t) then
[ ":note "
^ Wire.quote
"queued, but the program has not called (agent/start ...) yet, so \
this installs when it next reaches an (agent/poll) — and not at \
all if it never does"
]
else []
(* [pause], when given, is the position of the form to stop at — §9. It rides
@ -1253,21 +1241,6 @@ let eval_expr t ~code ~origin ~pause =
the program is stopped at an earlier break and runs \
nothing until that ends. Take a restart or abort in the \
break buffer, and evaluate this again"
(* And a third cause, which is the one a session now reaches
early enough to hit: the program has not bound its agent
socket, so it is still ahead of its own [(agent/start ...)]
— inside whatever it does first, a window being created —
and asking whether it calls [(agent/poll)] would send the
reader to look at a loop it has not got to yet. Said only
where it is a fact about *this* program: the socket is
missing, which is a stat, not a guess. *)
else if not (agent_bound t) then
error
"the program has not called (agent/start ...) yet, so \
nothing has run the expression. It is queued and will run \
at the program's first (agent/poll); this reply cannot \
carry its value, so evaluate it again once the program is \
up"
else
error
"the program did not reach a frame boundary; is it calling \

View File

@ -7,12 +7,10 @@
;;;; hand a module to and no socket to fall back on, so the daemon refuses the
;;;; delivery and says which socket it could not reach.
;;;;
;;;; That is the honest answer for it. The sentence [install_note] keeps for a
;;;; running program with no socket — queued, installs at its next
;;;; (agent/poll) — was true of a program that *links* the agent and has not
;;;; reached its (agent/start ...) yet, because there the ring is reachable
;;;; in-process while the socket is not yet bound. The package's constructor
;;;; binds before main now, so that window is gone; see TODO.org, "(agent/start) takes no argument, and binds before main".
;;;; That is the honest answer for it. A program that *links* the agent has its
;;;; socket bound by the package's constructor before main, so a running
;;;; program with no socket is always this one; see TODO.org, "(agent/start)
;;;; takes no argument, and binds before main".
;;;;
;;;; So: no (import agent ...) anywhere, and a loop that outlasts the test.
(defn step [] i64 7)