Tag: v-TP3-INTERRUPT-RESTORE
Commit: (the commit that adds this file)
Fixes: ff7b2dd "tests: nonvacuity restores Compiler.mod when the run is
interrupted" and 5e0312e "tests: the nonvacuity trap also restores the
mod=11 probe sources" — the two commits immediately before this one
Covers: the nonvacuity harness's interrupt safety
v-TP3-STRCAT (tag v-TP3-STRCAT, commit 1bed3bb) is the string
concatenation milestone this session began with; its write-up is
v-TP3-STRCAT.md. This document covers the test-harness repair that finishing
that milestone turned up, which was small in code and turned out to be two
problems deep.
shell/tests/nonvacuity.sh mutates sources on purpose and has to put them back.
It had an EXIT trap that restored Runtime.mod, Exec86.mod, Shell.mod,
tests/comimage.py and tests/comtest.py — but not Compiler.mod, the file it
mutates more than any other, because SAVED_C was defined at the BP-displacement
section halfway down the file, well after the trap was set at line 153. An
interrupted run therefore left Compiler.mod half-mutated, and git status
showed a source that no case had finished with — the exact confusion that two
agents colliding on one tree produces.
Moving SAVED_C above the trap was the obvious half. Testing it disproved the
comment next to it:
bash -c 'trap "echo EXIT-TRAP" EXIT; sleep 30' &
kill -TERM $! # prints nothing
An unhandled signal does not run an EXIT trap at all — bash dies on the
spot. So trap restore_all EXIT had only ever protected against an exit
inside the script; Runtime.mod and the rest were never protected against
interruption either, despite the comment promising "an interrupted run cannot
leave the layout half-written".
The fix is that a handler which only exits is enough, because exit from a
trap does fire the EXIT trap — and it has to exit, or the run would go on
using a tree it has just put back:
trap restore_all EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
Measured on bash 5.2.37, not assumed:
| signal | does the handler fire? |
|---|---|
TERM |
yes — restores everything |
HUP |
yes — restores everything |
INT |
usually never arrives |
For INT the rule is: a foreground child that dies of SIGINT makes bash treat
the signal as the child's and continue the script instead. So Ctrl-C
does not abort nonvacuity.sh at all — it kills the current case's child and
the next case starts. SIGTERM is what actually stops it. The first version of
the comment claimed INT worked; the interrupt test disproved it, and the
comment now records what was measured.
tests/probe/modrm11.{py,s}The last file group, committed separately. The mod=11 table section mutates the
probe's own sources and puts them back after each case, so a run that died
between the two left them edited. SAVED_PY / SAVED_S moved to the top with
the other six copies.
restore_all copies them directly rather than calling restore_probe, which
is defined halfway down the file: a trap firing before that definition would
abort the rest of the restore with command not found, putting back the copies
before it and silently leaving the ones after it. Same failure mode the whole
exercise is about.
The trap now covers all eight:
Runtime.mod Exec86.mod Shell.mod Compiler.mod
tests/comimage.py tests/comtest.py
tests/probe/modrm11.py tests/probe/modrm11.s
Each claim came from killing a live run at the moment a given file was dirty:
| file live | before | after |
|---|---|---|
Compiler.mod |
DIRTY, byte-different |
clean, byte-identical |
tests/probe/modrm11.py |
DIRTY |
both probe sources byte-identical |
The healthy path is unchanged: non-vacuity: 78 ok, 0 failed, and
run_all.sh reports OVERALL: ALL PASS across all 15 suites.
cd shell && tests/nonvacuity.sh # expect: non-vacuity: 78 ok, 0 failed
cd shell && tests/run_all.sh # expect: OVERALL: ALL PASS
To reproduce the interrupt evidence — kill a run while a mutation is live and check the file comes back byte-identical:
cd shell
cp Compiler.mod /tmp/before
./tests/nonvacuity.sh > /dev/null 2>&1 &
PID=$!
# wait until Compiler.mod differs, then:
kill -TERM $PID
wait $PID 2>/dev/null
cmp /tmp/before Compiler.mod && echo restored
Note that Compiler.mod is restored by the trap but is a .mod source, so the
trap's trailing make also rebuilds Compiler.o and tpshell from it — a
restore that left a binary built from the mutation would still be reporting on
the mutation.
Item 2 (the large one) is untouched: nested procedures / recursion, var
parameters (the SEG:OFF push, RESUME-TP3.md 3.11), range/index checks
(TU_RANGE_CHECK, TU_INDEX_CHECK), typed constants (3.14), array at its
point of use (t14), case with subrange labels.
Still ENoLib as unknown identifiers: the string standard functions length,
copy, pos, concat, delete, insert, str. + and the six comparisons
are done (v-TP3-STRCAT, v-TP3-STRCMP).
Also still open:
program p(1;)) with a BOOLEAN flag — not EXIT, which ICEs gm2.freedos.qcow2, FD14-LiveCD) is still untried; it is the only
third opinion on the INT 21h shim.SAVED_C2/SAVED_R2 (the 8086-opcodes section) and SAVED_R3 are per-case
snapshots taken mid-file. They are fine as they are: the files they
guard (Compiler.mod, Runtime.mod) are already trap-covered, so an
interruption there is restored by restore_all regardless of which
per-case copy was live.