Butler Lampson
We have
learned depressingly little in the last ten years about how to build computer
systems. But we have learned something
about how to do the job more precisely, by writing more precise specifications,
and by showing more precisely that an implementation meets its
specification. Methods for doing this
are of both intellectual and practical interest. I will explain the most useful such method and illustrate it with
two examples:
Connection
establishment: Sending a reliable message over an unreliable network.
Transactions:
Making a large atomic action out of a sequence of small ones.
Bob Taylor
Chuck Thacker Workstations: Alto, Dorado, Firefly
Networks: AN1, AN2
Charles
Simonyi Bravo wysiwyg editor
Nancy
Lynch Reliable messages
Howard
Sturgis Transactions
Martin
Abadi Security
Mike Burrows
Morrie Gasser
Andy Goldstein
Charlie Kaufman
Ted Wobber
Divide and conquer (Roman
motto)
Any
idea is better when made recursive (Randell)
state:
a set of values, usually divided into named variables
actions:
named changes in the state
Data abstractions
Concurrent
systems
Distributed
systems
You
can’t observe the actual state of the system from outside.
All you can see is the results of actions.

This interface was used in the Bravo editor.
The implementation was about 20k lines of code.
Choose it to make the
spec clear, not to match the code.
What they do to the
state
What
they return
Notation is
important; it helps you to think about what’s going on.
Invent a suitable
vocabulary.
Fewer actions
are better. Less is more.
More
non-determinism is better; it allows more implementations.

q : sequence[M] := < >
status : {OK,
lost, ?} := lost
recs/r :
Boolean := false
(short for ‘recovering’)
|
Name |
Guard |
Effect |
Name |
Guard |
Effect |
|
||||
|
**put(m) |
|
append m to q, |
*get(m) |
m first on q |
remove
head of q, |
|
||||
|
*getAck(a) |
status = a |
status := lost |
|
|
then status := OK |
|
||||
|
lose |
recs or |
delete some element
from q; |
|
|
|
|||||
every external behavior
of Y is an external behavior of X, and
Y’s liveness property
implies X’s liveness property.
This expresses
the idea that Y implements X if
you can’t tell Y apart from X by looking only at the external actions.
Define an abstraction function f from the state of Y to the state of X.
Show that Y simulates X:
1) f maps initial states of Y to initial
states of X.
2) For
each Y-action and each state y
there is a sequence of X-actions that is the same externally,
such that the diagram commutes.

This
always works!

The
implementer wants the spec as non-deterministic as possible,
to give him more freedom and make it
easier to show correctness.





cur-q = <msg> if
msg ≠ nil and (lasts =
nil or lasts Î gr)
< > otherwise
old-q = the messages in sr with i’s that are good
and not = lasts
|
q |
old-q
+ cur-q |
|
status |
? if cur-q ≠ < > OK if lasts
= lastr
≠ nil lost if lasts
Ï (gr
È {lastr})
or lasts
= nil |
|
recs/r |
recs/r |






|
G |
H |
|
gs |
the i’s with (js, i) in rs |
|
gr |
{ir} – {nil} |
|
sr and rs |
the (I, M) and (I, A) messages in sr and rs |
news/r, lasts/r, and msg are the
same in G and H
|
|
|
|
growr(i) |
receiver sets ir to an identifier from newr |
|
grows(i) |
receiver sends (js, i) |
|
shrinks(i) |
channel rs loses the last copy of (js, i) |
|
shrinkr(i) |
receiver gets
(ir,
done) |
Identifiers on messages
Sets of good
identifiers, sender’s Í receiver’s
Cleanup
The abstraction
functions reveal their secrets.
The subtlety can be
factored in a precise way.
|
|
S : State
|
Name |
Guard |
Effect |
|
|
|
|
|
do(a):Val |
|
(S, val) := a(S) |
|
|
S , s :
State
|
Name |
Guard |
Effect |
|
|
|
|
|
do(a):Val |
|
(s, val) := a(s) |
|
|
|
|
|
commit |
|
S := s |
|
crash |
|
s := S |
S , s : State
f : {nil, run} :=
nil
|
Name |
Guard |
Effect |
|
begin |
f = nil |
f := run |
|
do(a):Val |
f = run |
(s, val) := a(s) |
|
|
|
|
|
commit |
f = run |
S := s, f := nil |
|
crash |
|
s
:= S, f := nil |
Note
that we clean up the auxiliary state f.
|
|
S , s : State S = S + L
L , l :
seq Action := < > s, f = s, f
f : {nil, run} := nil
|
Name |
Guard |
Effect |
|
begin |
f = nil |
f := run |
|
do(a):Val |
f = run |
(s, val) := a(s), l +:= a |
|
|
|
|
|
commit |
f = run |
L := l, f := nil |
|
. . . |
|
|
|
|
|
|
|
|
|
|
|
crash |
|
l := L, s := S+L, f:=nil |
|
|
S , s : State S = S + L
L ,
l :
seq Action s, f = s, f
f : {nil, run}
|
Name |
Guard |
Effect |
||
|
begin, do, and commit as before |
|
|
||
|
|
|
|
||
|
|
|
|
||
|
|
|
|
||
|
apply(a) |
a = head(l) |
S := S + a, l := tail(l) |
||
|
cleanLog |
L in S |
L := < > |
||
|
|
|
|
||
|
crash |
|
l
:= L, s := S+L, f:=nil |
||