fix(v4.0.0): a waiting node goes on passing on and paying; KILL is not held by a stuck node

Found in the five-node test, each with a check that failed first:
- A node with answers it could not give to two stuck senders began no
  text at all.  It now holds back only text from a sender whose own
  answer is still with it.
- KILL waited to tell a stuck node, and a typed line that broke it left
  the nodes after it untold.  GONE is put with the messages waiting, as
  how text ended is, and goes when it can.
- So that 'tell, then wait' still works, AWAIT does what (IDLE) does but
  for beginning text: it looks through the messages waiting, offers what
  is to be passed on and what is owed, and sleeps in the wait.  A waiting
  node no longer keeps back what passes through it.
- A node in the wait with an offer on a port that turns out to have
  nothing on it is woken and told so (which port: 2 * V4_PORTS + k); no
  error is raised on it.  (GATE) makes it error 18; (IDLE) and AWAIT let
  the message go.  Before, removing a node raised 'No one on that port'
  on the unrelated text of a neighbour that had something queued for it.

make -C v4 test and sanitize at both widths, hosted-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-08 01:00:33 -04:00
co-authored by Claude Opus 5.5
parent 353783726d
commit ec817a8264
9 changed files with 130 additions and 99 deletions
+4 -4
View File
@@ -547,16 +547,16 @@ int main(void)
/* a port with nothing on it */
fresh();
a = loaded(0); offer(5, 7); wait_into(OUT); done_mark(); wait_for_ever();
CHECK(v4_asm_ok(&as), "a node that offers on a port nothing is wired to");
CHECK(v4_asm_ok(&as) && v4_fabric_wire_device(&f, a, 7, &sink), "a node that offers on a port nothing is wired to (the port it ends by reading has a device that gives nothing)");
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
v4_fabric_gone_error(&f, 18);
for (i = 0; i < 50; i++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&ND(a)->n) && ND(a)->n.faults == 0, "a bare node waits, though the fabric has an error for it");
v4_node_error_attach(&ND(a)->n, OUT + 20);
v4_node_fault_attach(&ND(a)->n, 900);
(void)v4_fabric_step(&f);
CHECK(!v4_node_in_wait(&ND(a)->n) && ND(a)->n.fault_kind == V4_FAULT_RAISED && ND(a)->n.fault_addr == 18 && ND(a)->n.offers == 0,
"one that can take an error has it raised, and its offer is withdrawn");
for (i = 0; i < 100 && ND(a)->n.mem[OUT + 2] == 0; i++) (void)v4_fabric_step(&f); /* until it has noted what it was told */
CHECK(!v4_node_in_wait(&ND(a)->n) && ND(a)->n.faults == 0 && ND(a)->n.offers == 0 && ND(a)->n.mem[OUT] == 0 && ND(a)->n.mem[OUT + 1] == 2 * (v4_cell)V4_PORTS + 5 && ND(a)->n.mem[OUT + 2] == 1,
"one that can take an error is woken and told there is nothing on port 5: no error is raised, and its offer is withdrawn: %ld", (long)ND(a)->n.mem[OUT + 1]);
/* a host with one node, whose devices always take */
fresh();
+9 -8
View File
@@ -306,12 +306,12 @@ int main(void)
/* no room: a node that is waiting keeps what it is sent, and has room for only so much */
{
v4_cell lost = pool[1].n.mem[LOST], refused = pool[0].n.mem[REFUSED], got, not_taken;
v4_cell lost = pool[1].n.mem[LOST], refused = pool[0].n.mem[REFUSED], got, not_taken, told_of;
CHECK(is_empty(tell(11, "0 GOT !")) && is_empty(tell(11, "12 AWAIT .")) && ended == 0 && pool[1].n.mem[AWAIT_FROM] == 12, "node 11 waits for an answer from 12, which is not going to send one");
CHECK(is_empty(tell(10, ": F 49 0 DO S\" HIT\" 11 SEND LOOP ; F")) && ended == 1 && ended_from == 10, "node 10 sends it 49 messages meanwhile");
CHECK(pool[1].n.mem[MQ_COUNT] > 300 && pool[1].n.mem[MQ_COUNT] <= MQ_CELLS - 36, "node 11 has kept what it has room for, and left the room that is kept for refusals: %ld cells", (long)pool[1].n.mem[MQ_COUNT]);
CHECK(pool[1].n.mem[OWED_COUNT] == 8, "it owes a refusal for each of the rest, as many as it can owe: %ld", (long)pool[1].n.mem[OWED_COUNT]);
CHECK(pool[0].n.mem[REFUSED] == refused, "and has sent none yet: it is still waiting");
CHECK(pool[1].n.mem[OWED_COUNT] == 0 && pool[0].n.mem[REFUSED] >= refused + 8, "it owed a refusal for each of the rest, as many as it could owe at once, and paid them while it waited: node 10 was told of %ld",
(long)(pool[0].n.mem[REFUSED] - refused));
/* one from its centre ends the wait */
CHECK(is_empty(tell(10, "12 NO-ROUTE")), "node 10 forgets the way to 12");
@@ -320,9 +320,10 @@ int main(void)
got = strtol(tell(11, "GOT @ ."), NULL, 10);
not_taken = 49 - got;
CHECK(got >= 30 && not_taken > 8, "it then did every message it had kept: %ld of the 49", (long)got);
CHECK(pool[0].n.mem[REFUSED] == refused + 8, "and sent the eight refusals it owed: node 10 was told of %ld", (long)(pool[0].n.mem[REFUSED] - refused));
CHECK(pool[1].n.mem[LOST] == lost + not_taken + (not_taken - 8), "it counted each message it did not take, and each refusal it could not owe: %ld for %ld not taken",
(long)(pool[1].n.mem[LOST] - lost), (long)not_taken);
told_of = pool[0].n.mem[REFUSED] - refused;
CHECK(told_of >= 8 && told_of <= not_taken, "node 10 was told of %ld of them", (long)told_of);
CHECK(pool[1].n.mem[LOST] == lost + not_taken + (not_taken - told_of), "it counted each message it did not take, and each refusal it could not owe: %ld for %ld not taken, %ld told",
(long)(pool[1].n.mem[LOST] - lost), (long)not_taken, (long)told_of);
/* the way to 12 is forgotten: what is sent there now is refused */
refused = pool[1].n.mem[REFUSED];
CHECK(is_empty(tell(11, ": P12 S\" 1 DROP\" 12 SEND ; P12")) && pool[1].n.mem[REFUSED] == refused + 1, "having forgotten the way to 12, what node 11 sends there goes toward its centre, which has no way either, and is refused");
@@ -330,11 +331,11 @@ int main(void)
}
/* a line from the console breaks a wait (MESH.md 7b.6) */
CHECK(is_empty(tell(10, "12 AWAIT .")) && ended == 0 && pool[0].n.mem[AWAIT_FROM] == 12 && waiting(hera), "node 10 waits for an answer from 12, which is not going to send one");
CHECK(is_empty(tell(11, "20 22 + .")) && ended == 0 && pool[0].n.mem[AWAIT_FROM] == 12, "text from the console for node 11 comes to node 10 on its way: node 10 keeps it, and goes on waiting");
CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && ended == 1 && pool[0].n.mem[AWAIT_FROM] == 12, "text from the console for node 11 comes to node 10 on its way: node 10 passes it on, and goes on waiting: \"%s\"", printed);
{
const char *r = tell(10, "65 EMIT");
CHECK(strstr(r, "Interrupted\n") != NULL && pool[0].n.mem[AWAIT_FROM] == 0, "text from the console for node 10 itself ends its wait, with an error that says so: \"%s\"", r);
CHECK(strstr(r, "A") != NULL && strstr(r, "42 ") != NULL && ended == 3, "the line is then done as usual, and so is what was kept for node 11: \"%s\", %d ended", r, ended);
CHECK(strstr(r, "A") != NULL && ended == 2, "the line is then done as usual: \"%s\", %d ended", r, ended);
CHECK(strstr(r, "Interrupted\n") < strstr(r, "A"), "the wait ends before the line that ended it is done");
}
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && waiting(hera) && waiting(mid) && waiting(far), "and all three go on as before");
+30 -22
View File
@@ -276,6 +276,8 @@ static const char *tell(v4_cell node, const char *text)
if (steps >= step_limit) { stuck(); return "(still running)"; }
return printed;
}
/* The same, answering with what has been printed: for when a node is spinning and the fabric never rests. */
static const char *said(v4_cell node, const char *text) { (void)tell(node, text); return printed; }
static int told(v4_cell node, const char *text) { return tell(node, text)[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
/* POST's host, for the node numbered 10 (v4/include/v4/post.h): a line is
@@ -525,30 +527,34 @@ int main(void)
CHECK(strstr(tell(10, ": H14 S\" 1\" 14 SEND ; H14"), "Argument out of range") != NULL, "Hera herself has no way to it");
}
/* a node waits on one that is stuck and is not its neighbour; Hera kills the stuck one */
/* two nodes each send a third text and are then stuck: the third has an answer for each that it
* cannot give, and goes on doing what others send it (MESH.md 7c). While a node spins the fabric
* never rests: said() answers with what was printed. */
step_limit = 3000000ul;
CHECK(strcmp(tell(13, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 13 is stuck in a loop");
CHECK(strcmp(tell(12, ": W13 13 AWAIT . ; W13"), "(still running)") == 0 && node_numbered(12)->mem[AWAIT_FROM] == 13 && waiting(12),
"node 12, which is not wired to it, waits for its answer");
/* while it is stuck it holds up no one (MESH.md 7c): text for it waits with Hera, who goes on */
(void)tell(13, "1 DROP");
CHECK(ended == 0 && v4_node_in_wait(node_numbered(10)) && !node_numbered(10)->asking && node_numbered(10)->mem[MQ_COUNT] > 0,
"text typed for the stuck node waits with Hera, offered: she sleeps in the wait, not blocked writing");
(void)tell(11, "11 100 * .");
CHECK(strcmp(printed, "1100 ") == 0 && ended == 1, "text typed for another node is passed on and done meanwhile: \"%s\"", printed);
(void)tell(10, "1 2 + .");
CHECK(strcmp(printed, "3 ") == 0 && ended == 1, "and so is text for Hera herself: \"%s\"", printed);
(void)tell(10, "13 KILL");
CHECK(node_numbered(10)->mem[MQ_COUNT] == 0, "killing it, she lets go what she kept for it");
CHECK(node_numbered(13) == NULL && strstr(printed, "Node gone") != NULL && node_numbered(12)->mem[AWAIT_FROM] == 0 && waiting(12),
"Hera kills node 13: node 12's wait ends, with an error that says the node is gone: \"%s\"", printed);
step_limit = 30000000ul;
CHECK(strcmp(tell(12, "12 100 * ."), "1200 ") == 0 && strcmp(tell(11, "11 100 * ."), "1100 ") == 0, "and the others go on");
CHECK(strcmp(tell(12, ": S11 S\" 1 DROP\" 11 SEND BEGIN 0 UNTIL ; S11"), "(still running)") == 0 &&
strcmp(tell(13, ": S11 S\" 1 DROP\" 11 SEND BEGIN 0 UNTIL ; S11"), "(still running)") == 0, "nodes 12 and 13, either side of node 11, each send it text and are then stuck in a loop");
CHECK(node_numbered(11)->mem[MQ_COUNT] == 18 && v4_node_in_wait(node_numbered(11)), "node 11 keeps its answer to each, offered, and sleeps: %ld cells", (long)node_numbered(11)->mem[MQ_COUNT]);
CHECK(strcmp(said(11, "11 100 * ."), "1100 ") == 0 && ended == 1 && node_numbered(11)->mem[MQ_COUNT] == 18, "text typed for node 11 is done all the same: \"%s\"", printed);
/* a node waits on one that is stuck; text for the stuck one waits with Hera, who goes on; Hera kills it */
CHECK(said(11, ": W13 13 AWAIT . ; W13")[0] == 0 && ended == 0 && node_numbered(11)->mem[AWAIT_FROM] == 13 && waiting(11), "node 11 waits for node 13's answer");
{
v4_cell kept = node_numbered(10)->mem[MQ_COUNT];
(void)tell(13, "1 DROP");
CHECK(ended == 0 && v4_node_in_wait(node_numbered(10)) && !node_numbered(10)->asking && node_numbered(10)->mem[MQ_COUNT] > kept,
"text typed for the stuck node waits with Hera, offered: she sleeps in the wait, not blocked writing");
}
CHECK(strcmp(said(10, "1 2 + ."), "3 ") == 0 && ended == 1, "and text for Hera herself is done meanwhile: \"%s\"", printed);
(void)tell(10, "13 KILL 65 EMIT");
CHECK(node_numbered(13) == NULL && strstr(printed, "Node gone") != NULL && node_numbered(11)->mem[AWAIT_FROM] == 0,
"Hera kills node 13: node 11's wait ends, with an error that says the node is gone: \"%s\"", printed);
CHECK(strstr(printed, "A") != NULL && ended == 2 && node_numbered(10)->mem[MQ_COUNT] == 9 && v4_node_in_wait(node_numbered(10)),
"her line is not held by node 12, which is stuck and has to be told too: that message waits with her, offered, and what she kept for node 13 is let go: %ld cells",
(long)node_numbered(10)->mem[MQ_COUNT]);
CHECK(strcmp(said(11, "11 100 * ."), "1100 ") == 0 && strcmp(said(10, "1 2 + ."), "3 ") == 0, "and the others go on");
/* Hera's own wait, on a node that is stuck: a line from the console ends it */
step_limit = 3000000ul;
CHECK(strcmp(tell(12, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 12 is stuck in a loop");
CHECK(strcmp(tell(10, "12 AWAIT ."), "(still running)") == 0 && node_numbered(10)->mem[AWAIT_FROM] == 12 && waiting(10), "Hera waits for its answer, which will not come");
CHECK(said(10, "12 AWAIT .")[0] == 0 && ended == 0 && node_numbered(10)->mem[AWAIT_FROM] == 12 && waiting(10), "Hera waits for node 12's answer, which will not come");
{
const char *r = tell(10, "12 KILL 66 EMIT");
CHECK(strstr(r, "Interrupted\n") != NULL && strstr(r, "B") != NULL && strstr(r, "Interrupted\n") < strstr(r, "B") && ended == 2,
@@ -556,8 +562,10 @@ int main(void)
CHECK(node_numbered(12) == NULL && node_numbered(10)->mem[AWAIT_FROM] == 0, "the line killed the stuck node");
}
step_limit = 30000000ul;
CHECK(strcmp(tell(11, "11 100 * ."), "1100 ") == 0 && strcmp(tell(10, "1 2 + ."), "3 ") == 0 && node_numbered(11)->mem[MQ_COUNT] == 0 && node_numbered(10)->mem[MQ_COUNT] == 0 && waiting(10) && waiting(11),
"the two that are left go on, and keep nothing for the nodes that are gone");
/* Hera blocked writing to a node that is stuck: a line from the console lets her go (MESH.md 7b.7) */
/* Hera's line must begin a message to a node that is stuck: it waits, and a line from the console ends it (MESH.md 7c.3) */
step_limit = 3000000ul;
CHECK(strcmp(tell(11, ": SPIN BEGIN 0 UNTIL ; SPIN"), "(still running)") == 0, "node 11 is stuck in a loop");
CHECK(strcmp(tell(10, ": T11 S\" 1 DROP\" 11 SEND 67 EMIT ; T11"), "(still running)") == 0 && v4_node_in_wait(node_numbered(10)) && !node_numbered(10)->asking,