How it works
The console on the previous page is three programs talking to each other, and all of their source is here. Each links into the exact lines where things happen. Text in "double quotes" in the code's comments is quoted from N. G. Leveson and C. S. Turner, "An Investigation of the Therac-25 Accidents" (IEEE Computer, July 1993). Where the paper is silent and the code had to guess, the comment says ASSUMPTION.
- The Therac-25 software: a model of the AECL program on the PDP-11/23, written in Haskell because its tasks share memory the way the original's did. That sharing is where both accidents came from.
- The console program: the part of that program the operator saw: the data-entry screen of the paper's Figure A, the keyboard handler and the screen processor.
- The VT100: the terminal on the operator's desk, rebuilt from DEC's manuals, drawing its letters from the VT100's own character ROM, 9600 baud away.
Tasks that share variables
The software "has four major components: stored data, a scheduler, a set of critical and noncritical tasks, and interrupt services." Three of the tasks matter here. They run at the same time and communicate only through shared variables:
treatis the treatment monitor. Every 0.1 s it runs one of eight phase subroutines, chosen byTphase: data entry (datent), set-up test (setupTest), treatment (zapTheSpecimen) and so on.- The keyboard handler (
keyboardHandler) turns what the operator types into those shared variables, most importantly the mode/energy offsetMEOS(editMEOS). - The housekeeper (
housekeeper) moves the turntable (hand) and checks limits (lmtchk).
In the simulator, each shared variable is read and written in its own small transaction, just as the PDP-11 tasks did. Nothing makes a sequence of those steps atomic, which is the point.
Tyler, 1986: Malfunction 54
When the operator enters the mode, the keyboard handler puts it in MEOS.
datent sees it, sets the machine up for that beam, and calls magnet to set the
bending magnets, which takes about 8 seconds. While each magnet settles, pTime is
supposed to watch for edits (the paper's Figure 3):
Ptime:
repeat
if bending magnet flag is set then
if editing taking place then
if mode/energy has changed then exit
until hysteresis delay has expired
Clear bending magnet flag
return
The last line is the bug. "Since Ptime clears it during its first execution, any edits
performed during each succeeding pass through Ptime will not be recognized." Look for it at the
end of pTime. An edit made after the first magnet but before the last goes into
MEOS and onto the screen, and nobody reads it. If the cursor has already been to the
command line once (cursorToCommandLine: "the data-entry completion variable only
indicates that the cursor has been down to the command line, not that it is still there"),
Datent moves on to set-up with the old beam.
Meanwhile the housekeeper has turned the turntable for the new mode. beamPhysics
decides what the hardware does with X-ray current and no target in the beam, and
zapTheSpecimen records the dose. The ion chamber saturates, so the console shows
"Malfunction 54" with 6 monitor units (poll_machine).
At the keyboard, that is accept (RETURN, including the quick
series that "would thus complete data entry"), cursor_up (the cursor
up key) and send_meos. The test that types it:
tyler_with_returns.
Yakima, 1987: Class3
setupTest runs ten times a second while the machine is set up, and each pass adds one
to Class3, a single byte. On every 256th pass it rolls over to zero.
lmtchk only calls chkcol, the check that the turntable (the "upper collimator")
is where the treatment needs it, when Class3 is not zero (the paper's Figure 4):
Lmtchk: If Class3=0 then do not enter Chkcol If Class3 is not 0 then enter Chkcol Chkcol: If upper collimator position inconsistent with treatment then set bit 9 of F$mal
The operator had turned the turntable to the field-light position (fieldLight). When
the set button was pressed "at the precise moment that Class3 rolled over to zero", the check
was skipped, set-up test saw no malfunction, and the beam fired with nothing to spread or measure
it. The console showed no dose, then "flatness". The test: yakima.
AECL's fix: "the Class3 variable is set to some fixed nonzero value each time through Set-Up Test instead of being incremented."
Why nobody at the console could tell
- The software never compares what it set the beam up for with where the turntable is:
"The software appears to include no checks to detect such an incompatibility"
(
beamPhysics). - Malfunctions were routine, and one key started the machine again. See the made-up ones in
spuriousMessage, and P, which works up to five times (zapTheSpecimen). - The dose monitor shows what the saturated ion chamber measured, not what the patient got. The console has no way to see the real dose; the page's "What the operator couldn't see" panel cheats.
The console and the terminal
The console program never draws anything itself. It sends characters and VT100 control
sequences down a 9600-baud line (serial_get). Its screen processor
(refresh) rewrites only what changed, and a full screen still takes
about a second. The terminal parses that stream (vt_receive) and draws
each character from the ROM, dot by dot, through the dot stretcher
(vt_render, glyph_dots). Keys go the other
way (vt_key), repeating as a VT100's did.
Where the model guesses
The paper is the most detailed public account, and it "leaves some unanswered questions". These are the places where the code fills a gap:
src-lib/Therac25.hs:242which is only after the magnets if they are being set (ASSUMPTION, see README).src-lib/Therac25.hs:280(ASSUMPTION, see README).src-lib/Therac25.hs:312between exposures; going back to Set-Up Test from a pause is an ASSUMPTION.src-lib/Therac25.hs:541(ASSUMPTION, see README).src-lib/Therac25.hs:571"The machine paused again, this time displaying 'flatness'" (ASSUMPTION, see README).vt100/src/console.c:5does not say, the choice is marked ASSUMPTION and listed in vt100/README.md. */vt100/src/console.c:15columns below are scaled from it onto the VT100's 80 columns (ASSUMPTION); the labels arevt100/src/console.c:50The room set-up in Figure A. ASSUMPTION: the patient is already set up when the page opens. */vt100/src/console.c:54the Tyler narrative. ASSUMPTION: RETURN on an empty dose field copies it, as it copies thevt100/src/console.c:12414.3 against 14.2 and 359 against 359.2, so there is some tolerance (ASSUMPTION: 1 degree,vt100/src/console.c:284RETURN is dropped (ASSUMPTION). */vt100/src/console.c:412others are the Figure 2 subroutine names (ASSUMPTION). */vt100/src/console.c:593ASSUMPTION: the treatment mode is filled in, as in Figure A; the operator still types thevt100/src/vt100.c:64t->decawm = false; /* ASSUMPTION: SET-UP B "auto wrap" off */vt100/src/vt100.c:81t->block_cursor = true; /* ASSUMPTION: SET-UP "cursor" set to blinking block */
All the source
Free software under the GNU Affero General Public License, version 3
or later. Download all of it as source.tar.gz; it builds with
vt100/build-web.sh and GHC's WebAssembly toolchain.
The Therac-25 software
the simulator: Treat and its phases, the keyboard handler, the housekeeper
Its interface
what every front end links against
The console program
the operator's screen and keyboard on the PDP-11
The VT100
the terminal, from DEC's manuals
The wiring
the serial line, and how the pieces are put together
- vt100/src/serial.h
- vt100/src/serial.c
- vt100/src/session.h
- vt100/src/session.c
- vt100/src/web_main.c
- vt100/hs/WebMain.hs
- vt100/src/native_tty.c
- vt100/hs/NativeMain.hs
The tests
both accidents, step by step