A parked thunk's output, its empty globals section, and a better silence
Three gaps in the first pass. The park flushes after the poll, which is the only thing that gets a printing expression's line out of a fully buffered pipe before the next run — now pinned by its position in the transcript. The globals section under a parked break answers with everything skipped, which is a different reply from the timeout a job nobody polled would give, so it is asked for rather than assumed. And the render thunk's five-second sentence no longer asks a parked program whether it is calling agent/poll.
This commit is contained in:
parent
b206e9a9a4
commit
9b877a2835
22
lib/dev.ml
22
lib/dev.ml
@ -1335,10 +1335,26 @@ let run_render_thunk ?(stopped_only = false) t ~tag ~(c : Session.change)
|
||||
match resumed () with
|
||||
| Some why -> Error why
|
||||
| None ->
|
||||
(* Two sentences for the same silence, because there are two
|
||||
causes and each names a different thing to go and look at.
|
||||
The frame-boundary one is about a game loop that is not
|
||||
polling; said about a parked program it would be the sentence
|
||||
this whole verb's parked refusal used to be, which is the one
|
||||
thing not to say here. A parked thread has nothing competing
|
||||
with it, so what it is doing is holding a break — the thunk
|
||||
asked for a stopped stack and the stop has been let go of. *)
|
||||
let gave_up =
|
||||
Error
|
||||
"the program did not reach a frame boundary; is it calling \
|
||||
(agent/poll)?"
|
||||
if liveness t = Parked then
|
||||
Error
|
||||
"the inspection produced nothing in five seconds. The \
|
||||
program is parked, so nothing is competing with the thunk: \
|
||||
the break it was built against has most likely been \
|
||||
resumed since, and a stopped-only job is dropped rather \
|
||||
than run against a resumed program"
|
||||
else
|
||||
Error
|
||||
"the program did not reach a frame boundary; is it calling \
|
||||
(agent/poll)?"
|
||||
in
|
||||
if ms <= 0 then gave_up
|
||||
else begin
|
||||
|
||||
@ -564,6 +564,25 @@ let () =
|
||||
fail "a global the finished run left: %s"
|
||||
(Option.value ~default:(status r) (Wire.string_field r "message"));
|
||||
|
||||
(* What the thunk *printed*, which needs one more thing than the value
|
||||
does. In the merged build fd 1 is a fully buffered pipe into the
|
||||
daemon, and a run flushes at its own pace while the park flushes once
|
||||
on the way in — neither covers a thunk that printed after both, so
|
||||
without a flush beside the poll this line would sit in the FILE buffer
|
||||
until the next park, which is to say until after the next run. The
|
||||
value arriving and the output not is exactly the shape that would go
|
||||
unnoticed, so it is asked for by name. *)
|
||||
let before = Buffer.length output in
|
||||
let r =
|
||||
request c
|
||||
"(:op \"eval-expr\" :code \"(do (println \\\"pk\\\") 9)\" :file \"/tmp/buf.flan\")"
|
||||
in
|
||||
if Wire.string_field r "value" <> Some "9" then
|
||||
fail "a printing expression against the park: %s"
|
||||
(Option.value ~default:(status r) (Wire.string_field r "message"));
|
||||
if not (contains_sub (Buffer.sub output before (Buffer.length output - before)) "pk")
|
||||
then fail "a parked thunk's output never reached the daemon";
|
||||
|
||||
(* ── And a thunk that stops, on a thread with no run under it ──── *)
|
||||
|
||||
(* Parked and stopped at once, which is a pair of states that could not
|
||||
@ -595,6 +614,18 @@ let () =
|
||||
fail "a backtrace of a thunk stopped in the park: %s"
|
||||
(Option.value ~default:(status r) (Wire.string_field r "message"));
|
||||
|
||||
(* And the section that walks that stack, which is the one op here whose
|
||||
answer is *empty* and has to arrive anyway. Every frame a paused thunk
|
||||
has is an eval frame, so nothing is attributed and everything lands in
|
||||
[:skipped] — but a render thunk is still built, delivered and run, and
|
||||
"ok with nothing in it" is a different reply from the five-second
|
||||
timeout that a job nobody polled would give. That is what this
|
||||
distinguishes. *)
|
||||
let r = request c "(:op \"globals\")" in
|
||||
if status r <> "ok" then
|
||||
fail "the globals of a thunk stopped in the park: %s"
|
||||
(Option.value ~default:(status r) (Wire.string_field r "message"));
|
||||
|
||||
(* The way out, and the reason the restart ops had to stop refusing the
|
||||
state: nothing else resumes this, and a re-run asked for meanwhile
|
||||
would be taken and then wait on the same resume. *)
|
||||
@ -656,8 +687,12 @@ let () =
|
||||
106 rather than 1, because [extra] is a global of a process that never
|
||||
died and the second run reads what the first left in it. Nothing is
|
||||
zeroed between runs, deliberately: a clean slate is one evaluation
|
||||
away, and cannot be had back once a re-run has wiped something. *)
|
||||
if not (settle 6) then fail "the program did not run again";
|
||||
away, and cannot be had back once a re-run has wiped something.
|
||||
|
||||
Seven and not six, because [settle] counts every line the daemon has
|
||||
handed over and the printing expression above contributed one that no
|
||||
run printed. Six would be satisfied by the first of these two. *)
|
||||
if not (settle 7) then fail "the program did not run again";
|
||||
|
||||
(* And a re-run while it is running is refused rather than queued: two
|
||||
mains in one process would be writing the same globals at once. *)
|
||||
@ -686,10 +721,16 @@ let () =
|
||||
again, from the body the first run ended with, and 106 from the one
|
||||
delivered while it was parked. 106 and not 1 is the line that says
|
||||
the globals are the finished run's — the process never died, so
|
||||
[extra] is where the first run left it. *)
|
||||
[extra] is where the first run left it.
|
||||
|
||||
And [pk] between the two, which is a line no run printed: it is the
|
||||
thunk evaluated against the park, on the parked thread, flushed there
|
||||
rather than waiting for a run to flush it. Its position in the
|
||||
transcript is the claim — after everything the first run printed and
|
||||
before anything the second did. *)
|
||||
ignore (Unix.waitpid [] pid);
|
||||
let text = Buffer.contents output in
|
||||
let wanted = "1\n5\n105\n777\n777\n106\n" in
|
||||
let wanted = "1\n5\n105\n777\npk\n777\n106\n" in
|
||||
if text <> wanted then
|
||||
fail "program transcript\n got: %S\n wanted: %S" text wanted
|
||||
end;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user