TLA+
Paradigm | Action |
---|---|
Designed by | Leslie Lamport |
First appeared | April 23, 1999[1] |
Stable release |
TLA+2
/ January 15, 2014[2] |
Implementation language | Java |
OS | Cross-platform (multi-platform) |
License | MIT License[3] |
Filename extensions | .tla |
Website |
research |
TLA+ (pronounced as tee ell a plus, /ˈtiː ɛl eɪ plʌs/) is a formal specification language developed by Leslie Lamport. It is used to design, model, document, and verify concurrent systems. TLA+ has been described as exhaustively-testable pseudocode[4] and blueprints for software systems.[5]
For design and documentation, TLA+ fulfills the same purpose as informal technical specifications. However, TLA+ specifications are written in a formal language of logic and mathematics, and the precision of specifications written in this language is intended to uncover design flaws before system implementation is underway.[6]
Since TLA+ specifications are written in a formal language, they are amenable to finite model checking. The model checker finds all possible system behaviours up to some number of execution steps, and examines them for violations of desired invariance properties such as safety and liveness. TLA+ specifications use basic set theory to define safety (bad things won't happen) and temporal logic to define liveness (good things eventually happen).
TLA+ is also used to write machine-checked proofs of correctness both for algorithms and mathematical theorems. The proofs are written in a declarative, hierarchical style independent of any single theorem prover backend. Both formal and informal structured mathematical proofs can be written in TLA+; the language is similar to LaTeX, and tools exist to translate TLA+ specifications to LaTeX documents.[7]
TLA+ was introduced in 1999, following several decades of research into a verification method for concurrent systems. A toolchain has since developed, including an IDE and distributed model checker. The pseudocode-like language PlusCal was created in 2009; it transpiles to TLA+ and is useful for specifying sequential algorithms. TLA+2 was announced in 2014, expanding language support for proof constructs. The current TLA+ reference is The TLA+ Hyperbook by Leslie Lamport.
History
Modern temporal logic was developed by Arthur Prior in 1957, then called tense logic. Although Amir Pnueli was the first to seriously study the applications of temporal logic to computer science, Prior speculated on its use a decade earlier in 1967:
- "The usefulness of systems of this sort [on discrete time] does not depend on any serious metaphysical assumption that time is discrete; they are applicable in limited fields of discourse in which we are concerned only with what happens next in a sequence of discrete states, e.g. in the working of a digital computer."
Pnueli researched the use of temporal logic in specifying and reasoning about computer programs, introducing linear temporal logic in 1977. LTL became an important tool for analysis of concurrent programs, easily expressing properties such as mutual exclusion and freedom from deadlock.[8]
Concurrent with Pnueli's work on LTL, academics were working to generalize Hoare logic for verification of multiprocess programs. Leslie Lamport became interested in the problem after peer review found an error in a paper he submitted on mutual exclusion. Ed Ashcroft introduced invariance in his 1975 paper "Proving Assertions About Parallel Programs", which Lamport used to generalize Floyd's method in his 1977 paper "Proving Correctness of Multiprocess Programs". Lamport's paper also introduced safety and liveness as generalizations of partial correctness and termination, respectively.[9] This method was used to verify the first concurrent garbage collection algorithm in a 1978 paper with Edsger Dijkstra.[10]
Lamport first encountered Pnueli's LTL during a 1978 seminar at Stanford organized by Susan Owicki. According to Lamport, "I was sure that temporal logic was some kind of abstract nonsense that would never have any practical application, but it seemed like fun, so I attended." In 1980 he published "'Sometime' is Sometimes 'Not Never'", which became one of the most frequently-cited papers in the temporal logic literature.[11] Lamport worked on writing temporal logic specifications during his time at SRI, but found the approach to be impractical:
- "However, I became disillusioned with temporal logic when I saw how Schwartz, Melliar-Smith, and Fritz Vogt were spending days trying to specify a simple FIFO queue - arguing over whether the properties they listed were sufficient. I realized that, despite its aesthetic appeal, writing a specification as a conjunction of temporal properties just didn't work in practice."
His search for a practical method of specification resulted in the 1983 paper "Specifying Concurrent Programming Modules", which introduced the idea of describing state transitions as boolean-valued functions of primed and unprimed variables.[12] Work continued throughout the 1980s, and Lamport began publishing papers on the temporal logic of actions in 1990; however, it was not formally introduced until "The Temporal Logic of Actions" was published in 1994. TLA enabled the use of actions in temporal formulas, which according to Lamport "provides an elegant way to formalize and systematize all the reasoning used in concurrent system verification."[13]
TLA specifications mostly consisted of ordinary non-temporal mathematics, which Lamport found less cumbersome than a purely temporal specification. TLA provided a mathematical foundation to the specification language TLA+, introduced with the paper "Specifying Concurrent Systems with TLA+" in 1999.[1] Later that same year, Yuan Yu wrote the TLC model checker for TLA+ specifications; TLC was used to find errors in the cache coherence protocol for a Compaq multiprocessor.[14]
Lamport published a full textbook on TLA+ in 2002, titled "Specifying Systems: The TLA+ Language and Tools for Software Engineers".[15] PlusCal was introduced in 2009,[16] and the TLA+ proof system (TLAPS) in 2012.[17] TLA+2 was announced in 2014, adding some additional language constructs as well as greatly increasing in-language support for the proof system.[2] Lamport is engaged in creating an updated TLA+ reference, "The TLA+ Hyperbook". The incomplete work is available from his official website.
Language
TLA+ specifications are organized into modules. Modules can extend (import) other modules to use their functionality. Although the TLA+ standard is specified in typeset mathematical symbols, existing TLA+ tools use LaTeX-like symbol definitions in ASCII. TLA+ uses several terms which require definition:
- State - an assignment of values to variables
- Behaviour - a sequence of states
- Step - a pair of successive states in a behavior
- Stuttering step - a step during which variables are unchanged
- Next-state relation - a relation describing how variables can change in any step
- State function - an expression containing variables and constants that is not a next-state relation
- State predicate - a Boolean-valued state function
- Invariant - a state predicate true in all reachable states
- Temporal formula - an expression containing statements in temporal logic
Safety
TLA+ concerns itself with defining the set of all correct system behaviours. For example, a one-bit clock ticking endlessly between 0 and 1 could be specified as follows:
VARIABLE clock
Init == clock \in {0, 1}
Tick == IF clock = 0 THEN clock' = 1 ELSE clock' = 0
Spec == Init /\ [][Tick]_<<clock>>
The next-state relation Tick sets clock′ (the value of clock in the next state) to 1 if clock is 0, and 0 if clock is 1. The state predicate Init is true if the value of clock is either 0 or 1. Spec is a temporal formula asserting all behaviours of one-bit clock must initially satisfy Init and have all steps either match Tick or be stuttering steps. Two such behaviours are:
0 -> 1 -> 0 -> 1 -> 0 -> ...
1 -> 0 -> 1 -> 0 -> 1 -> ...
The safety properties of the one-bit clock - the set of reachable system states - are adequately described by the spec.
Liveness
The above spec disallows strange states for the one-bit clock, but does not say the clock will ever tick. For example, the following perpetually-stuttering behaviours are accepted:
0 -> 0 -> 0 -> 0 -> 0 -> ...
1 -> 1 -> 1 -> 1 -> 1 -> ...
A clock which does not tick is not useful, so these behaviours should be disallowed. One solution is to disable stuttering, but TLA+ requires stuttering always be enabled; a stuttering step represents a change to some part of the system not described in the spec, and is useful for refinement. To ensure the clock must eventually tick, weak fairness is asserted for Tick:
Spec == Init /\ [][Tick]_<<clock>> /\ WF_<<clock>>(Tick)
Weak fairness over an action means if that action is continuously enabled, it must eventually be taken. With weak fairness on Tick only a finite number of stuttering steps are permitted between ticks. This temporal logical statement about Tick is called a liveness assertion. In general, a liveness assertion should be machine-closed: it shouldn't constrain the set of reachable states, only the set of possible behaviours.[18]
Most specifications do not require assertion of liveness properties. Safety properties suffice both for model checking and guidance in system implementation.[19]
Operators
TLA+ is based on ZFC, so operations on variables involve set manipulation. The language includes set membership, union, intersection, difference, powerset, and subset operators. First-order logic operators such as , , , , , are also included, as well as universal and existential quantifiers and . Hilbert's is provided as the CHOOSE operator, which uniquely selects an arbitrary set element. Arithmetic operators over reals, integers, and natural numbers are available from the standard modules.
Temporal logic operators are built into TLA+. Temporal formulas use to mean P is always true, and to mean P is eventually true. The operators are combined into to mean P is true infinitely often, or to mean eventually P will always be true. Other temporal operators include weak fairness WFe(A), which means if action A is continuously enabled, it must eventually be taken; and strong fairness SFe(A), which means if action A is continually enabled, it must eventually be taken. Temporal existential and universal quantification are included in TLA+, although without support from the tools.
User-defined operators are similar to macros. Operators differ from functions in that their domain need not be a set: for example, the set membership operator has the category of sets as its domain, which is not a valid set in ZFC (since its existence leads to Russell's paradox). Recursive and anonymous user-defined operators were added in TLA+2.
Data structures
The foundational data structure of TLA+ is the set. Sets are either explicitly enumerated or constructed from other sets using operators or with {x \in S : p}
where p is some condition on x, or {e : x \in S}
where e is some function of x. The unique empty set is represented as {}
.
Functions in TLA+ assign a value to each element in their domain, a set. [S -> T]
is the set of all functions with f[x] in T, for each x in the domain set S. For example, the TLA+ function Double[x \in Nat] == x*2
is an element of the set [Nat -> Nat]
so Double \in [Nat -> Nat]
is a true statement in TLA+. Functions are also defined with [x \in S |-> e]
for some expression e, or by modifying an existing function [f EXCEPT ![v1] = v2]
.
Records are a type of function in TLA+. The record [name |-> "John", age |-> 35]
is a record with fields name and age, accessed with r.name
and r.age
, and belonging to the set of records [name : String, age : Nat]
.
Tuples are included in TLA+. They are explicitly defined with <<e1,e2,e3>>
or constructed with operators from the standard Sequences module. Sets of tuples are defined by Cartesian product; for example, the set of all pairs of natural numbers is defined Nat \X Nat
.
Standard modules
TLA+ has a set of standard modules containing common operators. They are distributed with the syntactic analyzer. The TLC model checker uses Java implementations for improved performance.
- FiniteSets: Module for working with finite sets. Provides IsFiniteSet(S) and Cardinality(S) operators.
- Sequences: Defines operators on tuples such as Len(S), Head(S), Tail(S), Append(S), concatenation, and filter.
- Bags: Module for working with multisets. Provides primitive set operation analogues and duplicate counting.
- Naturals: Defines the Natural numbers along with inequality and arithmetic operators.
- Integers: Defines the Integers.
- Reals: Defines the Real numbers along with division and infinity.
- RealTime: Provides definitions useful in real-time system specifications.
- TLC: Provides utility functions for model-checked specifications, such as logging and assertions.
Standard modules are imported with the EXTENDS
or INSTANCE
statements.
Tools
IDE
An integrated development environment is implemented on top of Eclipse. It includes an editor with error and syntax highlighting, plus a GUI front-end to several other TLA+ tools:
- The SANY syntactic analyzer, which parses and checks the spec for syntax errors.
- The LaTeX translator, to generate pretty-printed specs.
- The PlusCal translator.
- The TLC model checker.
- The TLAPS proof system.
The IDE is distributed in The TLA Toolbox.
Model checker
The TLC model checker builds a finite state model of TLA+ specifications for checking invariance properties. TLC generates a set of initial states satisfying the spec, then performs a breadth-first search over all defined state transitions. Execution stops when all state transitions lead to states which have already been discovered. If TLC discovers a state which violates a system invariant, it halts and provides a state trace path to the offending state. TLC provides a method of declaring model symmetries to defend against combinatorial explosion.[14] It also parallelizes the state exploration step, and can run in distributed mode to spread the workload across a large number of computers.[20]
As an alternative to exhaustive breadth-first search, TLC can use depth-first search or generate random behaviours. TLC operates on a subset of TLA+; the model must be finite and enumerable, and some temporal operators are not supported. In distributed mode TLC cannot check liveness properties, nor check random or depth-first behaviours. TLC is available as a command line tool or bundled with the TLA toolbox.
Proof system
The TLA+ Proof System, or TLAPS, mechanically checks proofs written in TLA+. It was developed at the Microsoft Research-INRIA Joint Centre to prove correctness of concurrent and distributed algorithms. The proof language is designed to be independent of any particular theorem prover; proofs are written in a declarative style, and transformed into individual obligations which are sent to back-end provers. The primary back-end provers are Isabelle and Zenon, with fallback to SMT solvers CVC3, Yices, and Z3. TLAPS proofs are hierarchically structured, easing refactoring and enabling non-linear development: work can begin on later steps before all prior steps are verified, and difficult steps are decomposed into smaller sub-steps. TLAPS works well with TLC, as the model checker quickly finds small errors before verification is begun. In turn, TLAPS can prove system properties which are beyond the capabilities of finite model checking.[17]
TLAPS does not currently support reasoning with real numbers, nor most temporal operators. Isabelle and Zenon generally cannot prove arithmetic proof obligations, requiring use of the SMT solvers.[21] TLAPS has been used to prove correctness of Byzantine Paxos, the Memoir security architecture, and components of the Pastry distributed hash table.[17] It is distributed separately from the rest of the TLA+ tools. TLA+2 greatly expanded language support for proof constructs.
Industry use
At Microsoft, a critical bug was discovered in the Xbox 360 memory module during the process of writing a specification in TLA+.[22] TLA+ was used to write formal proofs of correctness for Byzantine Paxos and components of the Pastry distributed hash table.[17]
Amazon Web Services has used TLA+ since 2011. TLA+ model checking uncovered bugs in DynamoDB, S3, EBS, and an internal distributed lock manager; some bugs required state traces of 35 steps. Model checking was also used to verify aggressive optimizations. In addition, TLA+ specifications were found to hold value as documentation and design aids.[4][23]
Examples
--------------------------- MODULE KeyValueStore ---------------------------
CONSTANTS Key, \* The set of all keys.
Val, \* The set of all values.
TxId \* The set of all transaction IDs.
VARIABLES store, \* A data store mapping keys to values.
tx, \* The set of open snapshot transactions.
snapshotStore, \* Snapshots of the store for each transaction.
written, \* A log of writes performed within each transaction.
missed \* The set of writes invisible to each transaction.
----------------------------------------------------------------------------
NoVal == \* Choose something to represent the absence of a value.
CHOOSE v : v \notin Val
Store == \* The set of all key-value stores.
[Key -> Val \cup {NoVal}]
Init == \* The initial predicate.
/\ store = [k \in Key |-> NoVal] \* All store values are initially NoVal.
/\ tx = {} \* The set of open transactions is initially empty.
/\ snapshotStore = \* All snapshotStore values are initially NoVal.
[t \in TxId |-> [k \in Key |-> NoVal]]
/\ written = [t \in TxId |-> {}] \* All write logs are initially empty.
/\ missed = [t \in TxId |-> {}] \* All missed writes are initially empty.
TypeInvariant == \* The type invariant.
/\ store \in Store
/\ tx \subseteq TxId
/\ snapshotStore \in [TxId -> Store]
/\ written \in [TxId -> SUBSET Key]
/\ missed \in [TxId -> SUBSET Key]
TxLifecycle ==
/\ \A t \in tx : \* If store != snapshot & we haven't written it, we must have missed a write.
\A k \in Key : (store[k] /= snapshotStore[t][k] /\ k \notin written[t]) => k \in missed[t]
/\ \A t \in TxId \ tx : \* Checks transactions are cleaned up after disposal.
/\ \A k \in Key : snapshotStore[t][k] = NoVal
/\ written[t] = {}
/\ missed[t] = {}
OpenTx(t) == \* Open a new transaction.
/\ t \notin tx
/\ tx' = tx \cup {t}
/\ snapshotStore' = [snapshotStore EXCEPT ![t] = store]
/\ UNCHANGED <<written, missed, store>>
Add(t, k, v) == \* Using transaction t, add value v to the store under key k.
/\ t \in tx
/\ snapshotStore[t][k] = NoVal
/\ snapshotStore' = [snapshotStore EXCEPT ![t][k] = v]
/\ written' = [written EXCEPT ![t] = @ \cup {k}]
/\ UNCHANGED <<tx, missed, store>>
Update(t, k, v) == \* Using transaction t, update the value associated with key k to v.
/\ t \in tx
/\ snapshotStore[t][k] \notin {NoVal, v}
/\ snapshotStore' = [snapshotStore EXCEPT ![t][k] = v]
/\ written' = [written EXCEPT ![t] = @ \cup {k}]
/\ UNCHANGED <<tx, missed, store>>
Remove(t, k) == \* Using transaction t, remove key k from the store.
/\ t \in tx
/\ snapshotStore[t][k] /= NoVal
/\ snapshotStore' = [snapshotStore EXCEPT ![t][k] = NoVal]
/\ written' = [written EXCEPT ![t] = @ \cup {k}]
/\ UNCHANGED <<tx, missed, store>>
RollbackTx(t) == \* Close the transaction without merging writes into store.
/\ t \in tx
/\ tx' = tx \ {t}
/\ snapshotStore' = [snapshotStore EXCEPT ![t] = [k \in Key |-> NoVal]]
/\ written' = [written EXCEPT ![t] = {}]
/\ missed' = [missed EXCEPT ![t] = {}]
/\ UNCHANGED store
CloseTx(t) == \* Close transaction t, merging writes into store.
/\ t \in tx
/\ missed[t] \cap written[t] = {} \* Detection of write-write conflicts.
/\ store' = \* Merge snapshotStore writes into store.
[k \in Key |-> IF k \in written[t] THEN snapshotStore[t][k] ELSE store[k]]
/\ tx' = tx \ {t}
/\ missed' = \* Update the missed writes for other open transactions.
[otherTx \in TxId |-> IF otherTx \in tx' THEN missed[otherTx] \cup written[t] ELSE {}]
/\ snapshotStore' = [snapshotStore EXCEPT ![t] = [k \in Key |-> NoVal]]
/\ written' = [written EXCEPT ![t] = {}]
Next == \* The next-state relation.
\/ \E t \in TxId : OpenTx(t)
\/ \E t \in tx : \E k \in Key : \E v \in Val : Add(t, k, v)
\/ \E t \in tx : \E k \in Key : \E v \in Val : Update(t, k, v)
\/ \E t \in tx : \E k \in Key : Remove(t, k)
\/ \E t \in tx : RollbackTx(t)
\/ \E t \in tx : CloseTx(t)
Spec == \* Initialize state with Init and transition with Next.
Init /\ [][Next]_<<store, tx, snapshotStore, written, missed>>
----------------------------------------------------------------------------
THEOREM Spec => [](TypeInvariant /\ TxLifecycle)
=============================================================================
------------------------------ MODULE Firewall ------------------------------
EXTENDS Integers
CONSTANTS Address, \* The set of all addresses
Port, \* The set of all ports
Protocol \* The set of all protocols
AddressRange == \* The set of all address ranges
{r \in Address \X Address : r[1] <= r[2]}
InAddressRange[r \in AddressRange, a \in Address] ==
/\ r[1] <= a
/\ a <= r[2]
PortRange == \* The set of all port ranges
{r \in Port \X Port : r[1] <= r[2]}
InPortRange[r \in PortRange, p \in Port] ==
/\ r[1] <= p
/\ p <= r[2]
Packet == \* The set of all packets
[sourceAddress : Address,
sourcePort : Port,
destAddress : Address,
destPort : Port,
protocol : Protocol]
Firewall == \* The set of all firewalls
[Packet -> BOOLEAN]
Rule == \* The set of all firewall rules
[remoteAddress : AddressRange,
remotePort : PortRange,
localAddress : AddressRange,
localPort : PortRange,
protocol : SUBSET Protocol,
allow : BOOLEAN]
Ruleset == \* The set of all firewall rulesets
SUBSET Rule
Allowed[rset \in Ruleset, p \in Packet] == \* Whether the ruleset allows the packet
LET matches == {rule \in rset :
/\ InAddressRange[rule.remoteAddress, p.sourceAddress]
/\ InPortRange[rule.remotePort, p.sourcePort]
/\ InAddressRange[rule.localAddress, p.destAddress]
/\ InPortRange[rule.localPort, p.destPort]
/\ p.protocol \in rule.protocol}
IN /\ matches /= {}
/\ \A rule \in matches : rule.allow
=============================================================================
------------------------------ MODULE Elevator ------------------------------
(***************************************************************************)
(* This spec describes a simple multi-car elevator system. The actions in *)
(* this spec are unsurprising and common to all such systems except for *)
(* DispatchElevator, which contains the logic to determine which elevator *)
(* ought to service which call. The algorithm used is very simple and does *)
(* not optimize for global throughput or average wait time. The *)
(* TemporalInvariant definition ensures this specification provides *)
(* capabilities expected of any elevator system, such as people eventually *)
(* reaching their destination floor. *)
(***************************************************************************)
EXTENDS Integers
CONSTANTS Person, \* The set of all people using the elevator system
Elevator, \* The set of all elevators
FloorCount \* The number of floors serviced by the elevator system
VARIABLES PersonState, \* The state of each person
ActiveElevatorCalls, \* The set of all active elevator calls
ElevatorState \* The state of each elevator
Vars == \* Tuple of all specification variables
<<PersonState, ActiveElevatorCalls, ElevatorState>>
Floor == \* The set of all floors
1 .. FloorCount
Direction == \* Directions available to this elevator system
{"Up", "Down"}
ElevatorCall == \* The set of all elevator calls
[floor : Floor, direction : Direction]
ElevatorDirectionState == \* Elevator movement state; it is either moving in a direction or stationary
Direction \cup {"Stationary"}
GetDistance[f1, f2 \in Floor] == \* The distance between two floors
IF f1 > f2 THEN f1 - f2 ELSE f2 - f1
GetDirection[current, destination \in Floor] == \* Direction of travel required to move between current and destination floors
IF destination > current THEN "Up" ELSE "Down"
CanServiceCall[e \in Elevator, c \in ElevatorCall] == \* Whether elevator is in position to immediately service call
LET eState == ElevatorState[e] IN
/\ c.floor = eState.floor
/\ c.direction = eState.direction
PeopleWaiting[f \in Floor, d \in Direction] == \* The set of all people waiting on an elevator call
{p \in Person :
/\ PersonState[p].location = f
/\ PersonState[p].waiting
/\ GetDirection[PersonState[p].location, PersonState[p].destination] = d}
TypeInvariant == \* Statements about the variables which we expect to hold in every system state
/\ PersonState \in [Person -> [location : Floor \cup Elevator, destination : Floor, waiting : BOOLEAN]]
/\ ActiveElevatorCalls \subseteq ElevatorCall
/\ ElevatorState \in [Elevator -> [floor : Floor, direction : ElevatorDirectionState, doorsOpen : BOOLEAN, buttonsPressed : SUBSET Floor]]
SafetyInvariant == \* Some more comprehensive checks beyond the type invariant
/\ \A e \in Elevator : \* An elevator has a floor button pressed only if a person in that elevator is going to that floor
/\ \A f \in ElevatorState[e].buttonsPressed :
/\ \E p \in Person :
/\ PersonState[p].location = e
/\ PersonState[p].destination = f
/\ \A p \in Person : \* A person is in an elevator only if the elevator is moving toward their destination floor
/\ \A e \in Elevator :
/\ (PersonState[p].location = e /\ ElevatorState[e].floor /= PersonState[p].destination) =>
/\ ElevatorState[e].direction = GetDirection[ElevatorState[e].floor, PersonState[p].destination]
/\ \A c \in ActiveElevatorCalls : PeopleWaiting[c.floor, c.direction] /= {} \* No ghost calls
TemporalInvariant == \* Expectations about elevator system capabilities
/\ \A c \in ElevatorCall : \* Every call is eventually serviced by an elevator
/\ c \in ActiveElevatorCalls ~> \E e \in Elevator : CanServiceCall[e, c]
/\ \A p \in Person : \* If a person waits for their elevator, they'll eventually arrive at their floor
/\ PersonState[p].waiting ~> PersonState[p].location = PersonState[p].destination
PickNewDestination(p) == \* Person decides they need to go to a different floor
LET pState == PersonState[p] IN
/\ ~pState.waiting
/\ pState.location \in Floor
/\ \E f \in Floor :
/\ f /= pState.location
/\ PersonState' = [PersonState EXCEPT ![p] = [@ EXCEPT !.destination = f]]
/\ UNCHANGED <<ActiveElevatorCalls, ElevatorState>>
CallElevator(p) == \* Person calls the elevator to go in a certain direction from their floor
LET pState == PersonState[p] IN
LET call == [floor |-> pState.location, direction |-> GetDirection[pState.location, pState.destination]] IN
/\ ~pState.waiting
/\ pState.location /= pState.destination
/\ ActiveElevatorCalls' =
IF \E e \in Elevator :
/\ CanServiceCall[e, call]
/\ ElevatorState[e].doorsOpen
THEN ActiveElevatorCalls
ELSE ActiveElevatorCalls \cup {call}
/\ PersonState' = [PersonState EXCEPT ![p] = [@ EXCEPT !.waiting = TRUE]]
/\ UNCHANGED <<ElevatorState>>
OpenElevatorDoors(e) == \* Open the elevator doors if there is a call on this floor or the button for this floor was pressed.
LET eState == ElevatorState[e] IN
/\ ~eState.doorsOpen
/\ \/ \E call \in ActiveElevatorCalls : CanServiceCall[e, call]
\/ eState.floor \in eState.buttonsPressed
/\ ElevatorState' = [ElevatorState EXCEPT ![e] = [@ EXCEPT !.doorsOpen = TRUE, !.buttonsPressed = @ \ {eState.floor}]]
/\ ActiveElevatorCalls' = ActiveElevatorCalls \ {[floor |-> eState.floor, direction |-> eState.direction]}
/\ UNCHANGED <<PersonState>>
EnterElevator(e) == \* All people on this floor who are waiting for the elevator and travelling the same direction enter the elevator.
LET eState == ElevatorState[e] IN
LET gettingOn == PeopleWaiting[eState.floor, eState.direction] IN
LET destinations == {PersonState[p].destination : p \in gettingOn} IN
/\ eState.doorsOpen
/\ eState.direction /= "Stationary"
/\ gettingOn /= {}
/\ PersonState' = [p \in Person |->
IF p \in gettingOn
THEN [PersonState[p] EXCEPT !.location = e]
ELSE PersonState[p]]
/\ ElevatorState' = [ElevatorState EXCEPT ![e] = [@ EXCEPT !.buttonsPressed = @ \cup destinations]]
/\ UNCHANGED <<ActiveElevatorCalls>>
ExitElevator(e) == \* All people whose destination is this floor exit the elevator.
LET eState == ElevatorState[e] IN
LET gettingOff == {p \in Person : PersonState[p].location = e /\ PersonState[p].destination = eState.floor} IN
/\ eState.doorsOpen
/\ gettingOff /= {}
/\ PersonState' = [p \in Person |->
IF p \in gettingOff
THEN [PersonState[p] EXCEPT !.location = eState.floor, !.waiting = FALSE]
ELSE PersonState[p]]
/\ UNCHANGED <<ActiveElevatorCalls, ElevatorState>>
CloseElevatorDoors(e) == \* Close the elevator doors once all people have entered and exited the elevator on this floor.
LET eState == ElevatorState[e] IN
/\ ~ENABLED EnterElevator(e)
/\ ~ENABLED ExitElevator(e)
/\ eState.doorsOpen
/\ ElevatorState' = [ElevatorState EXCEPT ![e] = [@ EXCEPT !.doorsOpen = FALSE]]
/\ UNCHANGED <<PersonState, ActiveElevatorCalls>>
MoveElevator(e) == \* Move the elevator to the next floor unless we have to open the doors here.
LET eState == ElevatorState[e] IN
LET nextFloor == IF eState.direction = "Up" THEN eState.floor + 1 ELSE eState.floor - 1 IN
/\ eState.direction /= "Stationary"
/\ ~eState.doorsOpen
/\ eState.floor \notin eState.buttonsPressed
/\ \A call \in ActiveElevatorCalls : \* Can move only if other elevator servicing call
/\ CanServiceCall[e, call] =>
/\ \E e2 \in Elevator :
/\ e /= e2
/\ CanServiceCall[e2, call]
/\ nextFloor \in Floor
/\ ElevatorState' = [ElevatorState EXCEPT ![e] = [@ EXCEPT !.floor = nextFloor]]
/\ UNCHANGED <<PersonState, ActiveElevatorCalls>>
StopElevator(e) == \* Stops the elevator if it's moved as far as it can in one direction
LET eState == ElevatorState[e] IN
LET nextFloor == IF eState.direction = "Up" THEN eState.floor + 1 ELSE eState.floor - 1 IN
/\ ~ENABLED OpenElevatorDoors(e)
/\ ~eState.doorsOpen
/\ nextFloor \notin Floor
/\ ElevatorState' = [ElevatorState EXCEPT ![e] = [@ EXCEPT !.direction = "Stationary"]]
/\ UNCHANGED <<PersonState, ActiveElevatorCalls>>
(***************************************************************************)
(* This action chooses an elevator to service the call. The simple *)
(* algorithm picks the closest elevator which is either stationary or *)
(* already moving toward the call floor in the same direction as the call. *)
(* The system keeps no record of assigning an elevator to service a call. *)
(* It is possible no elevator is able to service a call, but we are *)
(* guaranteed an elevator will eventually become available. *)
(***************************************************************************)
DispatchElevator(c) ==
LET stationary == {e \in Elevator : ElevatorState[e].direction = "Stationary"} IN
LET approaching == {e \in Elevator :
/\ ElevatorState[e].direction = c.direction
/\ \/ ElevatorState[e].floor = c.floor
\/ GetDirection[ElevatorState[e].floor, c.floor] = c.direction } IN
/\ c \in ActiveElevatorCalls
/\ stationary \cup approaching /= {}
/\ ElevatorState' =
LET closest == CHOOSE e \in stationary \cup approaching :
/\ \A e2 \in stationary \cup approaching :
/\ GetDistance[ElevatorState[e].floor, c.floor] <= GetDistance[ElevatorState[e2].floor, c.floor] IN
IF closest \in stationary
THEN [ElevatorState EXCEPT ![closest] = [@ EXCEPT !.floor = c.floor, !.direction = c.direction]]
ELSE ElevatorState
/\ UNCHANGED <<PersonState, ActiveElevatorCalls>>
Init == \* Initializes people and elevators to arbitrary floors
/\ PersonState \in [Person -> [location : Floor, destination : Floor, waiting : {FALSE}]]
/\ ActiveElevatorCalls = {}
/\ ElevatorState \in [Elevator -> [floor : Floor, direction : {"Stationary"}, doorsOpen : {FALSE}, buttonsPressed : {{}}]]
Next == \* The next-state relation
\/ \E p \in Person : PickNewDestination(p)
\/ \E p \in Person : CallElevator(p)
\/ \E e \in Elevator : OpenElevatorDoors(e)
\/ \E e \in Elevator : EnterElevator(e)
\/ \E e \in Elevator : ExitElevator(e)
\/ \E e \in Elevator : CloseElevatorDoors(e)
\/ \E e \in Elevator : MoveElevator(e)
\/ \E e \in Elevator : StopElevator(e)
\/ \E c \in ElevatorCall : DispatchElevator(c)
TemporalAssumptions == \* Assumptions about how elevators and people will behave
/\ \A p \in Person : WF_Vars(CallElevator(p))
/\ \A e \in Elevator : WF_Vars(OpenElevatorDoors(e))
/\ \A e \in Elevator : WF_Vars(EnterElevator(e))
/\ \A e \in Elevator : WF_Vars(ExitElevator(e))
/\ \A e \in Elevator : SF_Vars(CloseElevatorDoors(e))
/\ \A e \in Elevator : SF_Vars(MoveElevator(e))
/\ \A e \in Elevator : WF_Vars(StopElevator(e))
/\ \A c \in ElevatorCall : SF_Vars(DispatchElevator(c))
Spec == \* Initialize state with Init and transition with Next, subject to TemporalAssumptions
/\ Init
/\ [][Next]_Vars
/\ TemporalAssumptions
THEOREM Spec => [](TypeInvariant /\ SafetyInvariant /\ TemporalInvariant)
=============================================================================
See also
- Alloy (specification language)
- B-Method
- Computation tree logic
- PlusCal
- Temporal logic
- Temporal logic of actions
- Z notation
References
- 1 2 Lamport, Leslie (January 2000). "Specifying Concurrent Systems with TLA+" (PDF). NATO Science Series, III: Computer and Systems Sciences. IOS Press, Amsterdam. 173 (Calculational System Design): 183–247. ISBN 978-90-5199-459-9. Retrieved 22 May 2015.
- 1 2 Lamport, Leslie (15 January 2014). "TLA+2: A Preliminary Guide" (PDF). Retrieved 2 May 2015.
- ↑ "Tlaplus Tools - License". CodePlex. Microsoft, Compaq. 8 April 2013. Retrieved 10 May 2015. https://tlaplus.codeplex.com/license
- 1 2 Newcombe, Chris; Rath, Tim; Zhang, Fan; Munteanu, Bogdan; Brooker, Marc; Deardeuff, Michael (29 September 2014). "Use of Formal Methods at Amazon Web Services" (PDF). Amazon. Retrieved 8 May 2015.
- ↑ Lamport, Leslie (25 January 2013). "Why We Should Build Software Like We Build Houses". Wired. Retrieved 7 May 2015.
- ↑ Lamport, Leslie (18 June 2002). "7.1 Why Specify". Specifying Systems: The TLA+ Language and Tools for Hardware and Software Engineers. Addison-Wesley. p. 75. ISBN 0-321-14306-X.
Having to describe a design precisely often reveals problems - subtle interactions and "corner cases" that are easily overlooked.
- ↑ Lamport, Leslie (2012). "How to Write a 21st Century Proof" (PDF). Journal of Fixed Point Theory and Applications. Springer Verlag. 11: 43–63. doi:10.1007/s11784-012-0071-6. ISSN 1661-7738. Retrieved 23 May 2015.
- ↑ Øhrstrøm, Peter; Hasle, Per (1995). "3.7 Temporal Logic and Computer Science". Temporal Logic: From Ancient Ideas to Artificial Intelligence. Studies in Linguistics and Philosophy. 57. Springer Netherlands. pp. 344–365. doi:10.1007/978-0-585-37463-5.
- ↑ Lamport, Leslie. "The Writings of Leslie Lamport: Proving the Correctness of Multiprocess Programs". Retrieved 22 May 2015.
- ↑ Lamport, Leslie. "The Writings of Leslie Lamport: On-the-fly Garbage Collection: an Exercise in Cooperation". Retrieved 22 May 2015.
- ↑ Lamport, Leslie. "The Writings of Leslie Lamport: 'Sometime' is Sometimes 'Not Never'". Retrieved 22 May 2015.
- ↑ Lamport, Leslie. "The Writings of Leslie Lamport: Specifying Concurrent Programming Modules". Retrieved 22 May 2015.
- ↑ Lamport, Leslie. "The Writings of Leslie Lamport: The Temporal Logic of Actions". Retrieved 22 May 2015.
- 1 2 Yu, Yuan; Manolios, Panagiotis; Lamport, Leslie (1999). "Model checking TLA+ specifications" (PDF). Correct Hardware Design and Verification Methods. Springer-Verlag: 54–66. doi:10.1007/3-540-48153-2_6. Retrieved 14 May 2015.
- ↑ Lamport, Leslie (18 June 2002). Specifying Systems: The TLA+ Language and Tools for Hardware and Software Engineers. Addison-Wesley. ISBN 0-321-14306-X.
- ↑ Lamport, Leslie (2 January 2009). "The PlusCal Algorithm Language" (PDF). Lecture Notes in Computer Science. Springer Berlin Heidelberg. 5684 (Theoretical Aspects of Computing - ICTAC 2009): 36–60. doi:10.1007/978-3-642-03466-4_2. Retrieved 10 May 2015.
- 1 2 3 4 Cousineau, Denis; Doligez, Damien; Lamport, Leslie; Merz, Stephan; Ricketts, Daniel; Vanzetto, Hernán (1 January 2012). "TLA+ Proofs" (PDF). FM 2012: Formal Methods. Springer Berlin Heidelberg. 7436: 147–154. doi:10.1007/978-3-642-32759-9_14. Retrieved 14 May 2015.
- ↑ Lamport, Leslie (18 June 2002). "8.9.2 Machine Closure". Specifying Systems: The TLA+ Language and Tools for Hardware and Software Engineers. Addison-Wesley. p. 112. ISBN 0-321-14306-X.
We seldom want to write a specification that isn't machine closed. If we do write one, it's usually by mistake.
- ↑ Lamport, Leslie (18 June 2002). "8.9.6 Temporal Logic Considered Confusing". Specifying Systems: The TLA+ Language and Tools for Hardware and Software Engineers. Addison-Wesley. p. 116. ISBN 0-321-14306-X.
Indeed, [most engineers] can get along quite well with specifications of the form (8.38) that express only safety properties and don't hide any variables.
- ↑ Markus A. Kuppe (3 June 2014). Distributed TLC (Recording of technical talk). TLA+ Community Event 2014, Toulouse, France.
- ↑ "Unsupported TLAPS features". TLA+ Proof System. Microsoft Research - INRIA Joint Centre. Retrieved 14 May 2015.
- ↑ Leslie Lamport (3 April 2014). Thinking for Programmers (at 21m46s) (Recording of technical talk). San Francisco: Microsoft. Retrieved 14 May 2015.
- ↑ Chris, Newcombe (2014). "Why Amazon Chose TLA+". Lecture Notes in Computer Science. Springer Berlin Heidelberg. 8477 (Abstract State Machines, Alloy, B, TLA, VDM, and Z): 25–39. doi:10.1007/978-3-662-43652-3_3.
External links
- The TLA Home Page, Leslie Lamport's webpage linking to the TLA+ tools and resources
- The TLA+ Hyperbook, a TLA+ textbook by Leslie Lamport
- How Amazon Web Services Uses Formal Methods, an article in the April 2015 Communications of the ACM
- Thinking for Programmers, a talk by Leslie Lamport at Build 2014
- Thinking Above the Code, a talk by Leslie Lamport at the 2014 Microsoft Research faculty summit
- Who Builds a Skyscraper without Drawing Blueprints?, a talk by Leslie Lamport at React San Francisco 2014
- Programming Should Be More than Coding, a 2015 talk at Stanford by Leslie Lamport
- Euclid Writes an Algorithm: A Fairytale, a TLA+ introduction by Leslie Lamport included in a festschrift for Manfred Broy
- The TLA+ Google Group