A value that traps or faults while the inspector reads it shows as #<trapped: NAME> with no break pushed, a restart already accepted is taken before queued reads run, and a fault in the inspector's read names the read.
This commit is contained in:
parent
bd70dfa913
commit
d77d541360
@ -2628,7 +2628,11 @@ they say a thunk renders it, `Inspect` now does.
|
|||||||
**What the program is asked** is six agent verbs, all refused unless stopped: `slot F I`, `cond-at` and `global SYM`
|
**What the program is asked** is six agent verbs, all refused unless stopped: `slot F I`, `cond-at` and `global SYM`
|
||||||
for where a root is; `peek ADDR LEN` for bytes; `ptr ADDR` for the registry's live/dead/unknown and the epitaph
|
for where a root is; `peek ADDR LEN` for bytes; `ptr ADDR` for the registry's live/dead/unknown and the epitaph
|
||||||
(`flan_dev_reg_epitaph`, which `flan_dev_reg_emit` now writes through, so the sentence has one author); and `dyn WORD`,
|
(`flan_dev_reg_epitaph`, which `flan_dev_reg_emit` now writes through, so the sentence has one author); and `dyn WORD`,
|
||||||
because a dyn value's tag is the runtime's to read and `flan_dyn_emit_to` renders it. They go through `Dev.request`,
|
because a dyn value's tag is the runtime's to read. `dyn` queues a job the break loop runs on the program's thread, since the
|
||||||
|
dyn printer follows pointers: a trap or fault there is the reader's, so `break_loop_at` writes the value as
|
||||||
|
`#<trapped: NAME>` and unwinds through the evaluation escape without pushing a break, and the fault report says the
|
||||||
|
inspector's read touched the address. A restart already accepted is taken before the ring is drained, and a job
|
||||||
|
that needs the stop is refused after one is. They go through `Dev.request`,
|
||||||
so the two-process daemon answers them over the socket and the merged one by a call. `peek` reads through
|
so the two-process daemon answers them over the socket and the merged one by a call. `peek` reads through
|
||||||
`process_vm_readv` on its own process, so an unmapped address is a refusal and not a fault that takes the program —
|
`process_vm_readv` on its own process, so an unmapped address is a refusal and not a fault that takes the program —
|
||||||
and in one process, the daemon — down with it.
|
and in one process, the daemon — down with it.
|
||||||
|
|||||||
@ -2475,9 +2475,9 @@ let agent_mem t : Inspect.mem =
|
|||||||
| Ok l when starts l "dead" -> Inspect.Dead (after l "dead")
|
| Ok l when starts l "dead" -> Inspect.Dead (after l "dead")
|
||||||
| _ -> Inspect.Unknown);
|
| _ -> Inspect.Unknown);
|
||||||
(* Rendered by the program, on its own thread, as an evaluation is: the
|
(* Rendered by the program, on its own thread, as an evaluation is: the
|
||||||
dyn printer follows pointers and can trap, and a trap there is a nested
|
dyn printer follows pointers and can trap or fault, and there that is
|
||||||
break rather than a daemon that never answers again. See the agent's
|
caught and the value reads [#<trapped: NAME>], rather than a daemon
|
||||||
[dyn] verb. *)
|
that never answers again. See the agent's [dyn] verb. *)
|
||||||
dyn =
|
dyn =
|
||||||
(fun w ->
|
(fun w ->
|
||||||
let mark = job_mark ~at_stop:1 t in
|
let mark = job_mark ~at_stop:1 t in
|
||||||
|
|||||||
@ -2545,6 +2545,11 @@ static void crash_hex(uintptr_t x) {
|
|||||||
crash_puts(b + i, sizeof b - i);
|
crash_puts(b + i, sizeof b - i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Non-NULL while the agent renders a value for the inspector, naming it. A
|
||||||
|
* fault then is the reader's, and the report says so instead of blaming the
|
||||||
|
* frame that happens to be on top — the program was stopped, not running. */
|
||||||
|
const char *volatile flan_dev_crash_reading;
|
||||||
|
|
||||||
static void crash_handler(int sig, siginfo_t *si, void *uc) {
|
static void crash_handler(int sig, siginfo_t *si, void *uc) {
|
||||||
/* Not the program's thread: this is the daemon's own fault to deal with,
|
/* Not the program's thread: this is the daemon's own fault to deal with,
|
||||||
* and OCaml's handler is the one that knows how. See the header. */
|
* and OCaml's handler is the one that knows how. See the header. */
|
||||||
@ -2555,12 +2560,19 @@ static void crash_handler(int sig, siginfo_t *si, void *uc) {
|
|||||||
if (flan_crash_entered++) goto die;
|
if (flan_crash_entered++) goto die;
|
||||||
crash_puts("\nflan: ", 7);
|
crash_puts("\nflan: ", 7);
|
||||||
if (sig == SIGBUS) crash_puts("SIGBUS", 6); else crash_puts("SIGSEGV", 7);
|
if (sig == SIGBUS) crash_puts("SIGBUS", 6); else crash_puts("SIGSEGV", 7);
|
||||||
{
|
if (flan_dev_crash_reading != NULL) {
|
||||||
|
static const char reading[] = " \xe2\x80\x94 reading ";
|
||||||
|
static const char tail[] = " for the inspector touched ";
|
||||||
|
crash_puts(reading, sizeof reading - 1);
|
||||||
|
crash_puts(flan_dev_crash_reading, strlen(flan_dev_crash_reading));
|
||||||
|
crash_puts(tail, sizeof tail - 1);
|
||||||
|
} else {
|
||||||
static const char touched[] = " \xe2\x80\x94 the program touched ";
|
static const char touched[] = " \xe2\x80\x94 the program touched ";
|
||||||
crash_puts(touched, sizeof touched - 1);
|
crash_puts(touched, sizeof touched - 1);
|
||||||
}
|
}
|
||||||
crash_hex((uintptr_t)si->si_addr);
|
crash_hex((uintptr_t)si->si_addr);
|
||||||
if (flan_frame_head != NULL && flan_frame_head->info != NULL) {
|
if (flan_dev_crash_reading == NULL
|
||||||
|
&& flan_frame_head != NULL && flan_frame_head->info != NULL) {
|
||||||
const flan_fninfo *fi = flan_frame_head->info;
|
const flan_fninfo *fi = flan_frame_head->info;
|
||||||
crash_puts(" in ", 4);
|
crash_puts(" in ", 4);
|
||||||
crash_puts(fi->name, (size_t)fi->namelen);
|
crash_puts(fi->name, (size_t)fi->namelen);
|
||||||
|
|||||||
@ -23,6 +23,8 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
void flan_rt_init(int32_t argc, char **argv);
|
void flan_rt_init(int32_t argc, char **argv);
|
||||||
int32_t flan_agent_start(const uint8_t *path, int64_t len);
|
int32_t flan_agent_start(const uint8_t *path, int64_t len);
|
||||||
@ -190,6 +192,59 @@ static int stale(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── A restart accepted with a read queued behind it ────────────────
|
||||||
|
*
|
||||||
|
* The inspector's [dyn] read is a job for the stopped thread, named for the
|
||||||
|
* stop. One queued before a restart is accepted, and one asked for after,
|
||||||
|
* must neither run in the break being left: the first stays in the ring and
|
||||||
|
* is dropped at the next poll, the second is refused at the door. The break
|
||||||
|
* takes the restart without draining the ring first. */
|
||||||
|
extern uint64_t flan_dynword(void *xfer) __asm__("flan.dynword");
|
||||||
|
int32_t flan_agent_poll(void);
|
||||||
|
static int resuming_done;
|
||||||
|
static char r_queued[128], r_take[128], r_late[128];
|
||||||
|
static pthread_t resumer;
|
||||||
|
|
||||||
|
/* The listener's side of it, from another thread: the break loop is asleep
|
||||||
|
* between turns when these land, as a request from the editor would be. */
|
||||||
|
static void *resume_from_outside(void *arg) {
|
||||||
|
const char *line = arg;
|
||||||
|
struct timespec pause = { 0, 500000 };
|
||||||
|
nanosleep(&pause, NULL);
|
||||||
|
snprintf(r_queued, sizeof r_queued, "%s", ask(line));
|
||||||
|
snprintf(r_take, sizeof r_take, "%s", ask("restart-at 0 retry"));
|
||||||
|
snprintf(r_late, sizeof r_late, "%s", ask(line));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resuming_hook(void) {
|
||||||
|
static char line[64];
|
||||||
|
void *x = NULL;
|
||||||
|
if (resuming_done) return;
|
||||||
|
resuming_done = 1;
|
||||||
|
snprintf(line, sizeof line, "dyn %llu",
|
||||||
|
(unsigned long long)flan_dynword(&x));
|
||||||
|
pthread_create(&resumer, NULL, resume_from_outside, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long refusal_count(void) { return strtol(ask("refusals"), NULL, 10); }
|
||||||
|
|
||||||
|
static int resuming(void) {
|
||||||
|
void *xfer = NULL;
|
||||||
|
int32_t v;
|
||||||
|
long before;
|
||||||
|
flan_agent_break_poll_hook = resuming_hook;
|
||||||
|
v = flan_deep(0, &xfer);
|
||||||
|
flan_agent_break_poll_hook = NULL;
|
||||||
|
pthread_join(resumer, NULL);
|
||||||
|
printf("queued %stake %slate %s", r_queued, r_take, r_late);
|
||||||
|
printf("returned %d\n", v);
|
||||||
|
before = refusal_count();
|
||||||
|
flan_agent_poll();
|
||||||
|
printf("dropped %ld\n", refusal_count() - before);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
flan_rt_init(argc, argv);
|
flan_rt_init(argc, argv);
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
@ -204,6 +259,7 @@ int main(int argc, char **argv) {
|
|||||||
if (strcmp(argv[1], "snapmax") == 0) return snapmax();
|
if (strcmp(argv[1], "snapmax") == 0) return snapmax();
|
||||||
if (strcmp(argv[1], "snapnames") == 0) return snapnames();
|
if (strcmp(argv[1], "snapnames") == 0) return snapnames();
|
||||||
if (strcmp(argv[1], "stale") == 0) return stale();
|
if (strcmp(argv[1], "stale") == 0) return stale();
|
||||||
|
if (strcmp(argv[1], "resuming") == 0) return resuming();
|
||||||
fprintf(stderr, "unknown mode %s\n", argv[1]);
|
fprintf(stderr, "unknown mode %s\n", argv[1]);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,3 +21,7 @@
|
|||||||
(restart-case
|
(restart-case
|
||||||
(if (= n 0) (do (error (Deep {.n n})) 0) (wide (- n 1)))
|
(if (= n 0) (do (error (Deep {.n n})) 0) (wide (- n 1)))
|
||||||
(retry-with-a-name-long-enough-that-twenty-of-them-fill-the-four-kilobytes-a-break-loop-keeps-for-the-names-of-its-restarts-and-the-twenty-first-does-not-fit-anywhere-in-the-buffer-at-all-xxx-and-so-on [] n)))
|
(retry-with-a-name-long-enough-that-twenty-of-them-fill-the-four-kilobytes-a-break-loop-keeps-for-the-names-of-its-restarts-and-the-twenty-first-does-not-fit-anywhere-in-the-buffer-at-all-xxx-and-so-on [] n)))
|
||||||
|
|
||||||
|
;;; A dyn keyword, so the dyn runtime is linked and the agent's [dyn] verb has
|
||||||
|
;;; something to render. agent_hooks.c's "resuming" mode reads one.
|
||||||
|
(defn dynword [] dyn :k)
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
;;;; whose arena was freed, which traps DynRange when printed; the first
|
;;;; whose arena was freed, which traps DynRange when printed; the first
|
||||||
;;;; element of [s] is a word that reads as a boxed pointer to address 0x10,
|
;;;; element of [s] is a word that reads as a boxed pointer to address 0x10,
|
||||||
;;;; which faults when printed as a dyn. The inspector hands each to the
|
;;;; which faults when printed as a dyn. The inspector hands each to the
|
||||||
;;;; program's own thread to render, so both become a nested break and the
|
;;;; program's own thread to render, which catches the trap: each reads as
|
||||||
;;;; daemon keeps answering.
|
;;;; #<trapped: NAME>, no break is pushed, and the daemon keeps answering.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defstruct Boom [why i32])
|
(defstruct Boom [why i32])
|
||||||
|
|||||||
@ -956,6 +956,18 @@ let () =
|
|||||||
if code <> 0 || out <> want then
|
if code <> 0 || out <> want then
|
||||||
fail "a choice addressed to an outer break, met by a nested one\n got: %S (exit %d, err %S)\n wanted: %S"
|
fail "a choice addressed to an outer break, met by a nested one\n got: %S (exit %d, err %S)\n wanted: %S"
|
||||||
out code err want;
|
out code err want;
|
||||||
|
(* A restart accepted with an inspector read queued behind it, and a
|
||||||
|
second asked for after. Neither runs in the break being left: the
|
||||||
|
break takes the restart without draining the ring, the queued job is
|
||||||
|
dropped at the next poll, and the late one is refused at the door. *)
|
||||||
|
let code, out, err = hook_mode "resuming" in
|
||||||
|
let want =
|
||||||
|
"queued ok\ntake ok\nlate err the program is resuming: a restart was taken\n\
|
||||||
|
returned 0\ndropped 1\n"
|
||||||
|
in
|
||||||
|
if code <> 0 || out <> want then
|
||||||
|
fail "a restart accepted with a read queued behind it\n got: %S (exit %d, err %S)\n wanted: %S"
|
||||||
|
out code err want;
|
||||||
(try Sys.remove hexe with Sys_error _ -> ());
|
(try Sys.remove hexe with Sys_error _ -> ());
|
||||||
|
|
||||||
Test_support.report ~label:"agent" ()
|
Test_support.report ~label:"agent" ()
|
||||||
|
|||||||
140
test/test_dev.ml
140
test/test_dev.ml
@ -6759,59 +6759,95 @@ let () =
|
|||||||
|
|
||||||
(* The reader hands a dyn word to the program's own thread to render,
|
(* The reader hands a dyn word to the program's own thread to render,
|
||||||
because the dyn printer follows pointers: a view of a freed arena
|
because the dyn printer follows pointers: a view of a freed arena
|
||||||
traps, and a scribbled word faults. Rendered on the agent's thread
|
traps, and a scribbled word faults. Either is the reader's trouble and
|
||||||
either one hung the daemon for good or killed it. Here each must come
|
not the program's, so it pushes no break: the value shows as the trap's
|
||||||
back as a refusal, and the daemon must go on answering. *)
|
name, the rest of the section renders, and the stop on top is still
|
||||||
let dsock = tmp "dyntrap.sock" and dout = tmp "dyntrap.out" in
|
the program's own after any number of refreshes. A fault says it was
|
||||||
(try Sys.remove dsock with Sys_error _ -> ());
|
the inspector's read that touched the address, not the stopped frame.
|
||||||
let dfd = Unix.openfile dout [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC ] 0o600 in
|
Both backends. *)
|
||||||
let dpid =
|
List.iter
|
||||||
Unix.create_process flan
|
(fun backend ->
|
||||||
[| flan; "dev"; "programs/dev-dyn-trap.flan"; "-s"; dsock; "--llvm" |]
|
let dsock = tmp ("dyntrap" ^ backend ^ ".sock")
|
||||||
Unix.stdin dfd Unix.stderr
|
and dout = tmp ("dyntrap" ^ backend ^ ".out") in
|
||||||
in
|
(try Sys.remove dsock with Sys_error _ -> ());
|
||||||
Unix.close dfd;
|
let dfd =
|
||||||
if not (listening ~pid:dpid dsock) then begin
|
Unix.openfile dout [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC ] 0o600
|
||||||
fail "the dyn-trap daemon %s" !listen_why;
|
in
|
||||||
(try Unix.kill dpid Sys.sigkill with Unix.Unix_error _ -> ())
|
let dpid =
|
||||||
end
|
Unix.create_process flan
|
||||||
else begin
|
[| flan; "dev"; "programs/dev-dyn-trap.flan"; "-s"; dsock; backend |]
|
||||||
let c = connect dsock in
|
Unix.stdin dfd dfd
|
||||||
let stopped r =
|
in
|
||||||
match Wire.field r "stopped" with
|
Unix.close dfd;
|
||||||
| Some { Form.v = Form.Sym "t"; _ } -> true
|
if not (listening ~pid:dpid dsock) then begin
|
||||||
| _ -> false
|
fail "the %s dyn-trap daemon %s" backend !listen_why;
|
||||||
in
|
(try Unix.kill dpid Sys.sigkill with Unix.Unix_error _ -> ())
|
||||||
let answers what =
|
end
|
||||||
let r = request c "(:op \"eval-expr\" :code \"n\")" in
|
else begin
|
||||||
if Wire.string_field r "value" <> Some "5" then
|
let c = connect dsock in
|
||||||
fail "after %s the daemon no longer answers: %s" what
|
let stopped r =
|
||||||
(Option.value ~default:(status r) (Wire.string_field r "message"))
|
match Wire.field r "stopped" with
|
||||||
in
|
| Some { Form.v = Form.Sym "t"; _ } -> true
|
||||||
if not (await (fun () -> stopped (request c "(:op \"describe\")"))) then
|
| _ -> false
|
||||||
fail "the dyn-trap program never stopped"
|
|
||||||
else begin
|
|
||||||
let r = request c "(:op \"inspect\" :frame 0 :slot 0 :path (0))" in
|
|
||||||
(match Wire.field r "addr" with
|
|
||||||
| Some { Form.v = Form.Int a; _ } ->
|
|
||||||
let r =
|
|
||||||
request c (Printf.sprintf "(:op \"at\" :addr %Ld :type \"dyn\")" a)
|
|
||||||
in
|
in
|
||||||
if status r <> "error" then
|
let still_boom what =
|
||||||
fail "a dyn word pointing at 0x10 rendered: %s"
|
let r = request c "(:op \"describe\")" in
|
||||||
(Option.value ~default:"" (Wire.string_field r "value"));
|
if Wire.string_field r "condition" <> Some "Boom" then
|
||||||
answers "a dyn word that faults"
|
fail "%s: after %s the program is stopped on %s, not its own Boom"
|
||||||
| _ -> fail "inspecting the slice element gave no :addr");
|
backend what
|
||||||
let r = request c "(:op \"globals\")" in
|
(Option.value ~default:"nothing" (Wire.string_field r "condition"))
|
||||||
if status r <> "error" then
|
in
|
||||||
fail "a dyn view of a freed arena rendered in the globals section";
|
if not (await (fun () -> stopped (request c "(:op \"describe\")"))) then
|
||||||
answers "a dyn value that traps"
|
fail "the %s dyn-trap program never stopped" backend
|
||||||
end;
|
else begin
|
||||||
ignore (request c "(:op \"close\")");
|
let r = request c "(:op \"inspect\" :frame 0 :slot 0 :path (0))" in
|
||||||
(try Unix.close c with Unix.Unix_error _ -> ());
|
(match Wire.field r "addr" with
|
||||||
(try ignore (Unix.waitpid [] dpid) with Unix.Unix_error _ -> ())
|
| Some { Form.v = Form.Int a; _ } ->
|
||||||
end;
|
let r =
|
||||||
List.iter (fun f -> try Sys.remove f with Sys_error _ -> ()) [ dsock; dout ];
|
request c
|
||||||
|
(Printf.sprintf "(:op \"at\" :addr %Ld :type \"dyn\")" a)
|
||||||
|
in
|
||||||
|
if Wire.string_field r "value" <> Some "<ptr #<trapped: SegFault>>"
|
||||||
|
then
|
||||||
|
fail "%s: a dyn word pointing at 0x10 read as %s" backend
|
||||||
|
(Option.value ~default:(status r) (Wire.string_field r "value"));
|
||||||
|
still_boom "a dyn word that faults"
|
||||||
|
| _ -> fail "%s: inspecting the slice element gave no :addr" backend);
|
||||||
|
for i = 1 to 10 do
|
||||||
|
let r = request c "(:op \"globals\")" in
|
||||||
|
let rows =
|
||||||
|
match Wire.field r "globals" with
|
||||||
|
| Some { Form.v = Form.List l; _ } ->
|
||||||
|
List.filter_map
|
||||||
|
(fun (e : Form.t) ->
|
||||||
|
match e.Form.v with
|
||||||
|
| Form.List ({ Form.v = Form.Str n; _ } :: _
|
||||||
|
:: { Form.v = Form.Str v; _ } :: _) ->
|
||||||
|
Some (n, v)
|
||||||
|
| _ -> None)
|
||||||
|
l
|
||||||
|
| _ -> []
|
||||||
|
in
|
||||||
|
if List.assoc_opt "dv" rows <> Some "#<trapped: DynRange>"
|
||||||
|
|| List.assoc_opt "n" rows <> Some "5"
|
||||||
|
then
|
||||||
|
fail "%s: globals refresh %d with a trapping dyn: %s" backend i
|
||||||
|
(String.concat ", " (List.map (fun (n, v) -> n ^ "=" ^ v) rows))
|
||||||
|
done;
|
||||||
|
still_boom "ten refreshes of a section holding a trapping dyn"
|
||||||
|
end;
|
||||||
|
ignore (request c "(:op \"close\")");
|
||||||
|
(try Unix.close c with Unix.Unix_error _ -> ());
|
||||||
|
(try ignore (Unix.waitpid [] dpid) with Unix.Unix_error _ -> ());
|
||||||
|
let said = In_channel.with_open_bin dout In_channel.input_all in
|
||||||
|
if not (contains_sub said "reading a dyn value for the inspector touched")
|
||||||
|
|| contains_sub said "the program touched"
|
||||||
|
then
|
||||||
|
fail "%s: the fault in the inspector's read was reported as: %s"
|
||||||
|
backend said
|
||||||
|
end;
|
||||||
|
List.iter (fun f -> try Sys.remove f with Sys_error _ -> ()) [ dsock; dout ])
|
||||||
|
[ "--llvm"; "--x86" ];
|
||||||
|
|
||||||
(* ── What a half-finished assignment looks like from the break ────── *)
|
(* ── What a half-finished assignment looks like from the break ────── *)
|
||||||
|
|
||||||
|
|||||||
59
vendor/agent/flan_agent.c
vendored
59
vendor/agent/flan_agent.c
vendored
@ -465,6 +465,13 @@ static const uint8_t abandon_report[] =
|
|||||||
* frames jumped over do not run. NULL when no evaluation is in progress;
|
* frames jumped over do not run. NULL when no evaluation is in progress;
|
||||||
* saved and restored around the call like [eval_boundary]. */
|
* saved and restored around the call like [eval_boundary]. */
|
||||||
static sigjmp_buf *eval_escape;
|
static sigjmp_buf *eval_escape;
|
||||||
|
/* Set while [dyn_job] renders a value for the inspector: a trap in there is
|
||||||
|
* the reader's, not the program's. See [break_loop_at]. Game thread only. */
|
||||||
|
static int inspector_reading;
|
||||||
|
/* flan_dev.c's: what the fault report names when the fault is a read the
|
||||||
|
* inspector asked for, rather than the program's own. */
|
||||||
|
extern const char *volatile flan_dev_crash_reading;
|
||||||
|
void flan_dev_emit(const uint8_t *bytes, int64_t len);
|
||||||
|
|
||||||
/* Whether the game thread is inside an evaluated thunk's call, at any depth,
|
/* Whether the game thread is inside an evaluated thunk's call, at any depth,
|
||||||
* rather than in the program's own code. A break records it, and it is what
|
* rather than in the program's own code. A break records it, and it is what
|
||||||
@ -1078,6 +1085,27 @@ void (*flan_agent_break_poll_hook)(void);
|
|||||||
static void break_loop_at(const uint8_t *name, int64_t namelen, void *condition,
|
static void break_loop_at(const uint8_t *name, int64_t namelen, void *condition,
|
||||||
void *xfer, int resumable) {
|
void *xfer, int resumable) {
|
||||||
struct timespec step = { 0, 2000000 }; /* 2ms */
|
struct timespec step = { 0, 2000000 }; /* 2ms */
|
||||||
|
/* A trap or fault inside the inspector's own read of a value — the dyn
|
||||||
|
* printer following a pointer that is no longer good. It is not the
|
||||||
|
* program's error and no break is pushed for it: the value is written as
|
||||||
|
* the trap's name, the evaluation escape unwinds the job exactly as an
|
||||||
|
* abandon would, and the stop the reader was looking at stays the one on
|
||||||
|
* top. A break here would leave the program one level deeper on every
|
||||||
|
* refresh of a section that holds the value. */
|
||||||
|
if (inspector_reading && eval_escape != NULL) {
|
||||||
|
inspector_reading = 0;
|
||||||
|
flan_dev_crash_reading = NULL;
|
||||||
|
fprintf(stderr, "flan: reading a value for the inspector stopped on "
|
||||||
|
"%.*s; it is shown as #<trapped: %.*s>\n",
|
||||||
|
(int)namelen, (const char *)name, (int)namelen, (const char *)name);
|
||||||
|
fflush(stderr);
|
||||||
|
flan_dev_result_begin();
|
||||||
|
flan_dev_emit((const uint8_t *)"#<trapped: ", 11);
|
||||||
|
flan_dev_emit(name, namelen);
|
||||||
|
flan_dev_emit((const uint8_t *)">", 1);
|
||||||
|
flan_dev_result_end();
|
||||||
|
siglongjmp(*eval_escape, 1);
|
||||||
|
}
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
fprintf(stderr, "\nflan: unhandled %.*s — stopped, not dead.\n",
|
fprintf(stderr, "\nflan: unhandled %.*s — stopped, not dead.\n",
|
||||||
(int)namelen, (const char *)name);
|
(int)namelen, (const char *)name);
|
||||||
@ -1164,9 +1192,16 @@ static void break_loop_at(const uint8_t *name, int64_t namelen, void *condition,
|
|||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
die_now();
|
die_now();
|
||||||
}
|
}
|
||||||
|
/* The ring is not drained while a choice for this break is waiting. A job
|
||||||
|
* queued after a restart was accepted belongs to the stop being left; run
|
||||||
|
* first, it would run in that stop and could push a break of its own that
|
||||||
|
* takes the restart meant for this one. Left in the ring, it meets the gate
|
||||||
|
* in [flan_agent_poll] at the next boundary and is dropped. */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
flan_agent_poll();
|
if (!(atomic_load(&chosen_ready) && atomic_load(&chosen_gen) == my_gen)) {
|
||||||
if (flan_agent_break_poll_hook != NULL) flan_agent_break_poll_hook();
|
flan_agent_poll();
|
||||||
|
if (flan_agent_break_poll_hook != NULL) flan_agent_break_poll_hook();
|
||||||
|
}
|
||||||
if (atomic_load(&aborting)) {
|
if (atomic_load(&aborting)) {
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
fprintf(stderr, "flan: aborted at the break loop\n");
|
fprintf(stderr, "flan: aborted at the break loop\n");
|
||||||
@ -1529,7 +1564,11 @@ static pthread_mutex_t request_lock = PTHREAD_MUTEX_INITIALIZER;
|
|||||||
static uint64_t dyn_word;
|
static uint64_t dyn_word;
|
||||||
static void dyn_job(void) {
|
static void dyn_job(void) {
|
||||||
flan_dev_result_begin();
|
flan_dev_result_begin();
|
||||||
|
inspector_reading = 1;
|
||||||
|
flan_dev_crash_reading = "a dyn value";
|
||||||
flan_dyn_emit_dev(dyn_word);
|
flan_dyn_emit_dev(dyn_word);
|
||||||
|
inspector_reading = 0;
|
||||||
|
flan_dev_crash_reading = NULL;
|
||||||
flan_dev_result_end();
|
flan_dev_result_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2111,8 +2150,9 @@ static void handle_line(char *line, sink *o) {
|
|||||||
* trap — a view of a Vec whose arena was freed — or fault on a scribbled
|
* trap — a view of a Vec whose arena was freed — or fault on a scribbled
|
||||||
* word, and on this thread either one is a break loop that holds
|
* word, and on this thread either one is a break loop that holds
|
||||||
* [request_lock] for ever, or the daemon's own death. On the game thread it
|
* [request_lock] for ever, or the daemon's own death. On the game thread it
|
||||||
* runs as an evaluation does, inside [flan_agent_poll]'s escape, so it
|
* runs as an evaluation does, inside [flan_agent_poll]'s escape, and a trap
|
||||||
* becomes a nested break the break buffer shows. */
|
* there is caught by [break_loop_at] and written as the value's text
|
||||||
|
* without pushing a break. */
|
||||||
if (strncmp(line, "dyn ", 4) == 0) {
|
if (strncmp(line, "dyn ", 4) == 0) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
unsigned long long w = strtoull(line + 4, &end, 0);
|
unsigned long long w = strtoull(line + 4, &end, 0);
|
||||||
@ -2124,6 +2164,10 @@ static void handle_line(char *line, sink *o) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (s == NULL) { reply(o, "err no snapshot\n"); return; }
|
if (s == NULL) { reply(o, "err no snapshot\n"); return; }
|
||||||
|
if (atomic_load(&chosen_ready)) {
|
||||||
|
reply(o, "err the program is resuming: a restart was taken\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!queue_room()) { reply(o, "err the install queue is full\n"); return; }
|
if (!queue_room()) { reply(o, "err the install queue is full\n"); return; }
|
||||||
dyn_word = (uint64_t)w;
|
dyn_word = (uint64_t)w;
|
||||||
j.call = dyn_job;
|
j.call = dyn_job;
|
||||||
@ -2355,6 +2399,13 @@ static void handle_line(char *line, sink *o) {
|
|||||||
* one there is no point relocating, and refusing here means no handle is
|
* one there is no point relocating, and refusing here means no handle is
|
||||||
* taken for it at all. Only one producer runs at a time, so room seen now is
|
* taken for it at all. Only one producer runs at a time, so room seen now is
|
||||||
* room still there at [publish] below. */
|
* room still there at [publish] below. */
|
||||||
|
/* A job that needs the stop, arriving after a restart was accepted and
|
||||||
|
* before the break loop has taken it: the stop it was built for is being
|
||||||
|
* left, so it is refused here rather than run in a break that is ending. */
|
||||||
|
if ((stopped_only || at_stop != 0) && atomic_load(&chosen_ready)) {
|
||||||
|
reply(o, "err the program is resuming: a restart was taken\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!queue_room()) {
|
if (!queue_room()) {
|
||||||
reply(o, "err reload queue full; the program is not calling "
|
reply(o, "err reload queue full; the program is not calling "
|
||||||
"agent/poll\n");
|
"agent/poll\n");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user