TLA+
TLA+ is a high-level formal specification language developed by computer scientist Leslie Lamport for modeling, designing, documenting, and verifying the correctness of concurrent and distributed systems, particularly those involving complex interactions that are difficult to debug in traditional code.[1] Based on simple mathematical foundations, it allows engineers to express system behaviors abstractly as state machines, enabling early detection of fundamental design flaws before implementation.[2]
The origins of TLA+ trace back to Lamport's work in the 1980s on temporal logic for reasoning about concurrent programs, culminating in his 1990 publication of "A Temporal Logic of Actions" (TLA), which provided a logical framework invariant under stuttering—allowing flexible descriptions of system steps without concern for low-level timing details.[3] TLA+ emerged as an extension of TLA in the early 2000s, incorporating untyped set theory, first-order predicate logic, and mathematical operators to create readable, hierarchical specifications that avoid programming-language constructs like assignments or types, emphasizing instead invariants and actions for proving correctness properties.[2] Lamport, who received the 2013 ACM Turing Award for advancing the theory of distributed computing systems, motivated TLA+'s design to address the needs of large-scale engineering projects where complete formal proofs are impractical, focusing instead on practical debugging and documentation.[4][2]
Key features of TLA+ include its support for stuttering invariance, which permits refinements of specifications without altering their temporal properties, and action formulas that model atomic state transitions directly.[2] The language is accompanied by essential tools: the TLC model checker, which exhaustively explores finite-state models to find errors like deadlocks or safety violations, and the TLAPS proof system, which facilitates interactive theorem proving for infinite-state systems using back-end provers like Isabelle or Zenon.[1] An integrated development environment, the TLA+ Toolbox, simplifies writing, parsing, and checking specifications, with extensions available for IDEs like Visual Studio Code.[1] These tools, developed primarily at Microsoft Research and now maintained by the TLA+ Foundation, enable both simulation-based validation and formal proofs.[1][5]
In industry, TLA+ has been widely adopted for specifying critical systems, with notable applications at companies such as Amazon Web Services, where it was used to verify the design of the S3 storage service, preventing costly errors in a system handling petabytes of data.[6] Microsoft employs TLA+ for protocol design and verification in products like Azure, while Intel has integrated it into hardware and software engineering processes to model concurrent behaviors.[7] Other users include CrowdStrike for cybersecurity systems and various open-source projects, demonstrating TLA+'s role in enhancing reliability across distributed computing domains.[8][7] As of 2025, the TLA+ community continues to grow, supported by educational resources and the open-source TLA+ GitHub repository.[9][1]
Introduction
Purpose and Core Concepts
TLA+, or the Temporal Logic of Actions, is a formal specification language developed by Leslie Lamport for modeling and reasoning about the behaviors of concurrent and distributed systems.[10] It represents system behaviors as mathematical objects, specifically traces consisting of infinite sequences of states, where each state encapsulates the values of system variables at a given instant.[10] This approach enables precise description of how systems evolve over time, focusing on properties rather than implementation details.[11]
Central to TLA+ are concepts such as stuttering steps, which are atomic transitions that leave observable variables unchanged, allowing specifications to remain invariant under insertions or deletions of such steps for greater abstraction.[12] The language emphasizes a clear distinction between high-level specifications, which define desired system properties abstractly, and concrete implementations, which refine those specifications into executable code.[13] All behaviors in TLA+ are modeled as infinite sequences of states to accommodate ongoing reactive systems without terminating abruptly.[14]
The motivation behind TLA+ stems from the challenges of designing concurrent systems, where subtle errors—often arising from nondeterminism and interleaving—persist despite extensive testing, as such bugs manifest infrequently.[15] By specifying systems mathematically before coding, TLA+ aims to uncover these design flaws early.[15] Its key benefits include mathematical rigor grounded in standard temporal logic, which supports both automated model checking to explore finite-state approximations and interactive theorem proving for hierarchical proofs of correctness.[10][16][17] TLA+ builds on Lamport's foundational work in temporal logic from the 1980s.[10]
TLA+ occupies a distinct position among formal specification languages by integrating temporal logic for behavioral modeling with applicative constructs for state descriptions, setting it apart from purely state-based or structural approaches. Unlike the Z notation, which is primarily state-based and relies on schemas for specifying static states and operations using typed set theory and first-order predicate logic, TLA+ adopts a model-based perspective that treats system behaviors as infinite sequences of states, enabling the specification of dynamic properties over time.[13] This temporal dimension allows TLA+ to address concurrency and liveness more naturally than Z, which lacks built-in support for such properties and requires ad hoc extensions.[13]
In comparison to Alloy, which emphasizes structural properties through first-order relational logic and excels at visualizing complex relationships like graphs, TLA+ prioritizes behavioral focus via its temporal logic framework, making it better suited for verifying sequences of events and concurrent interactions.[18] Alloy's bounded model checking limits it to finite scopes, potentially overlooking infinite or unbounded counterexamples, whereas TLA+ supports direct verification of temporal properties using tools like the TLC model checker for exhaustive state exploration.[18] Similarly, Event-B, a refinement-oriented method rooted in typed predicate logic like its predecessor B, emphasizes stepwise refinement from abstract to concrete models with strong typing and well-definedness conditions, contrasting TLA+'s untyped, flexible syntax that facilitates rapid prototyping but requires explicit type invariants.[19] Event-B's rigidity aids in formal proofs but can hinder expressiveness for complex data structures, while TLA+ handles untyped sets, sequences, and reals more fluidly.[19]
At its core, TLA+ is founded on linear-time temporal logic (LTL), which models time as a linear sequence of states, extended by the Temporal Logic of Actions (TLA) to incorporate actions as relations between consecutive states using primed variables (e.g., x' = y) for capturing atomic transitions in concurrent systems.[10] This extension enables the specification of stuttering steps and fairness conditions, such as weak fairness \text{WF}_v(A), to reason about progress in nondeterministic environments without assuming a global clock.[10]
Compared to informal methods like natural language requirements or UML diagrams, TLA+ provides precise, ambiguity-free specifications that eliminate interpretive errors and allow exhaustive property checking, as demonstrated in industrial applications where it uncovered subtle concurrency bugs in systems like Amazon S3 (two bugs in an 804-line specification) and DynamoDB (three bugs in a 939-line specification).[6] In software engineering, TLA+ bridges high-level requirements to detailed designs by supporting iterative refinement and model checking, integrating into agile workflows more readily than theorem provers like Coq or Isabelle, which demand deeper proof expertise; engineers at Amazon typically master it in 2-3 weeks, applying it across 10 complex services since 2011 to enhance documentation, scalability analysis, and bug prevention.[6]
History
Origins and Creation
Leslie Lamport, a pioneering computer scientist known for his contributions to distributed systems, developed the foundational ideas for TLA during the 1990s while working at the Systems Research Center (SRC) of Digital Equipment Corporation (DEC) in Palo Alto, California, where he had joined in 1985.[20] His efforts at DEC SRC built upon his earlier 1980s research in temporal logic, which provided a mathematical framework for reasoning about the behavior of concurrent systems over time.[21]
The origins of TLA trace back to Lamport's 1983 paper "Specifying Concurrent Program Modules," published in ACM Transactions on Programming Languages and Systems, which introduced a method for specifying the behavior of concurrent modules using transition axioms to model state changes and ensure properties like mutual exclusion. This work was motivated by persistent challenges in verifying concurrent algorithms, including subtle bugs in solutions to the mutual exclusion problem, such as those encountered in Lamport's own 1974 bakery algorithm for distributed processes.[22] The paper aimed to provide a rigorous, mathematical approach to specifying distributed systems, addressing the limitations of informal descriptions that often failed to catch concurrency errors in real-world implementations.
TLA was formally defined in Lamport's 1994 paper "The Temporal Logic of Actions," which integrated discrete transition systems with linear-time temporal logic to enable precise specifications of system behaviors and properties.[23] Building on this, TLA+ emerged as an enhanced specification language, first detailed in Lamport's 1999 chapter "Specifying Concurrent Systems with TLA+" in the book Calculational System Design, incorporating structured proof techniques and tools for practical use while extending TLA's expressiveness for complex systems.[21]
Early adoption of TLA and its extensions occurred within DEC, later acquired by Compaq in 1998, where researchers applied it to verify hardware designs, notably in projects specifying and checking cache-coherence protocols for multiprocessor systems in the late 1990s.[24] These efforts demonstrated TLA's utility in detecting flaws in distributed hardware architectures, influencing subsequent formal verification practices at the organization.[25]
Development and Standardization
The development of TLA+ progressed from an informal notation to a mature, standardized language through key releases of supporting tools and documentation in the early 2000s. In 2002, Leslie Lamport published Specifying Systems: The TLA+ Language and Tools for Hardware and Software Engineers, which introduced the initial TLA+ tools, including the TLA+ Toolbox as an integrated development environment for editing and analyzing specifications, and the TLC model checker for explicit-state verification of temporal properties.[26] These tools, distributed freely via the TLA+ web page, enabled practical application of TLA+ for debugging concurrent systems by simulating behaviors and checking invariants, liveness, and deadlock conditions.[27]
Standardization efforts crystallized with the same 2002 book, which provided the definitive reference for TLA+'s grammar, semantics, and syntax, distinguishing it from earlier informal descriptions and establishing a rigorous foundation for specifications using set theory, actions, and temporal logic.[26] Part IV of the book served as a comprehensive manual, defining operators, modules, and evaluation rules to ensure consistent interpretation across tools and users, thereby promoting TLA+ as a reliable medium for formal system design in both hardware and software engineering contexts.[28]
Subsequent milestones enhanced TLA+'s accessibility for algorithmic modeling. In 2009, Lamport introduced PlusCal, a pseudo-code-like language designed for specifying sequential and concurrent algorithms, which automatically translates into equivalent TLA+ specifications for verification with TLC.[29] This addition addressed the challenge of writing detailed behavioral models by allowing users to employ familiar programming constructs while retaining TLA+'s mathematical precision, with labels defining atomic steps and nondeterminism for concurrency. The TLA+ Proof System (TLAPS), an interactive prover for mechanically checking hierarchical proofs of TLA+ properties using backend theorem provers, saw its first public release in 2010, building on earlier prototypes to support deductive verification beyond model checking.[30]
In the 2010s, the TLA+ tools transitioned to open-source distribution via GitHub, starting with the core repository in 2014, which allowed global developers to access, modify, and extend the Java-based implementations of the Toolbox, TLC, and related components under permissive licenses.[31] This shift fostered collaborative improvements, such as enhanced error tracing and integration with external solvers, while maintaining backward compatibility with specifications from the 2002 standard.
Community engagement further solidified TLA+'s infrastructure, with informal user groups emerging in the mid-2010s to share examples, tutorials, and extensions, culminating in the formation of the TLA+ Foundation in April 2023 under the Linux Foundation.[32] The foundation coordinates open-source development, hosts repositories, and promotes education and adoption, ensuring sustained evolution of the language and its ecosystem without centralized control.
Recent Evolution
Since 2024, the TLA+ Foundation has intensified its efforts to sustain and advance the language through regular community engagement and funding initiatives. The Foundation publishes monthly development updates, detailing progress on tools and specifications, such as the March 2025 report highlighting fixes to the PlusCal command-line interface (CLI) for improved labeling options in algorithmic translations to TLA+.[33] Additionally, the 2025 grant program allocated funds to projects enhancing TLA+ tooling, aiming to improve usability and integration in practical settings.[34] A key community milestone was the inaugural TLA+ Community Event held on May 4, 2025, at McMaster University in Hamilton, Ontario, co-located with ETAPS 2025, which featured discussions on specification practices and tool advancements.[35]
Leslie Lamport contributed significantly to recent literature before his retirement. In November 2024, he released A Science of Concurrent Programs, a comprehensive book elucidating the principles of concurrent system specification using TLA+, available as a free PDF of the final draft from his website in preparation for publication by Cambridge University Press.[36] Lamport retired from Microsoft on January 3, 2025, marking the transition of TLA+ maintenance to the open-source community under the Foundation.[37] Ongoing releases, such as the TLA+ Toolbox update enabling collapsing of block comments, PlusCal code, and translations for better code navigation, ensure continued evolution without his direct involvement.[38]
Emerging integrations highlight TLA+'s adaptability to modern paradigms. In October 2025, demonstrations showed TLA+ grounding large language models (LLMs) for reliable code generation, exemplified by specifications for cellular automata visualizers that guide AI outputs toward verifiable behaviors. Complementing this, a November 2024 systematic literature review on arXiv analyzed a decade of industrial TLA+ applications, identifying trends in adoption for distributed systems verification and calling for expanded empirical studies to quantify impact.[39]
Language Fundamentals
Syntax and Operators
TLA+ specifications are organized into modules, which provide a modular structure for defining constants, variables, operators, and assumptions. A module begins with its name, followed by declarations of constants and variables using CONSTANT and VARIABLE, respectively. Modules can import definitions from other modules via EXTENDS, such as EXTENDS Naturals to include arithmetic on natural numbers. Assumptions about constants are stated with ASSUME, for example, ASSUME N \in Nat to restrict a constant to natural numbers. Operators and predicates are defined using DEFINE or simply ==, as in Max(a, b) == IF a > b THEN a ELSE b.[40][41]
Formulas in TLA+ are boolean-valued expressions that evaluate to true or false in a given state, forming the basis for specifying system properties and behaviors. State formulas, which do not involve next-state relations, can include logical combinations, quantifiers, and arithmetic or set operations. Actions, representing state transitions, are expressed as boolean formulas over unprimed (current-state) and primed (next-state) variables. Substitution in actions uses the form [E \in A] to indicate that expression E takes values from set A in the next state, such as [x \in 1..10] meaning x' is one of 1 through 10. More complex substitutions employ the EXCEPT construct, like [f EXCEPT ![i] = e] to update a function f at index i to value e while leaving other values unchanged.[40][42][41]
Logical operators in TLA+ follow standard boolean semantics with specific syntax and precedence: negation (\lnot or ~), conjunction (/\), disjunction (\/), implication (=>), and equivalence (<->). Quantifiers include universal (\A x \in S : P) for "for all x in set S, P holds" and existential (\E x \in S : P) for "there exists x in S such that P". For example, \A i \in 1..N : pc[i] \in {"init", "done"} asserts that all processes are in specified states. The non-deterministic choice operator CHOOSE x \in S : P selects an arbitrary x from S satisfying P, useful for modeling unspecified selections. Precedence is \lnot highest, then /\, then \/, then =>, with parentheses recommended for clarity.[40][42][41]
Arithmetic operators support operations on numbers, assuming modules like Integers or Reals are extended. Basic operations include addition (+), subtraction (-), multiplication (*), division (/), and modulus (%), as in hr' = (hr % 12) + 1 for incrementing a clock hour. Set operators handle collections, with membership (\in, \notin), union (\union or ∪), Cartesian product (\times), and comprehension {x \in S : P} for sets satisfying predicate P. For instance, {n \in Nat : n % 2 = 1} denotes the set of odd natural numbers. These operators apply to built-in types like booleans, numbers, and sets, with sets serving as a foundational data structure for more complex types.[40][42][41]
Action operators relate current and next states. Primed variables, denoted by a postfix ', represent next-state values, such as x' for the value of x after a transition. The UNCHANGED operator asserts that a variable or expression remains the same, written UNCHANGED x (equivalent to x' = x), often used in stuttering steps like Next == (act /\ UNCHANGED y) \/ (UNCHANGED <<x,y>>) to allow no-op transitions. The ENABLED operator evaluates whether an action is possible in the current state, as in ENABLED x' = x + 1, which is true if the action can occur without considering fairness.[40][42][41]
Equality (=) checks if two expressions have the same value, applicable to any type, such as x = y or f[i] = 0, and is a primitive with high precedence. For boolean formulas, equivalence (<->) means both directions of implication hold, i.e., P <-> Q is (P => Q) /\ (Q => P), used to assert that two predicates are logically equivalent, like TypeOK <-> (\A i \in Procs : pc[i] \in {"n1", "n2"}). In examples, equality ensures state invariants, such as hr = 12 in a clock model, while equivalence verifies property refinements.[40][42][41]
Data Structures
TLA+ supports a rich set of data types grounded in standard mathematics, enabling precise modeling of system states through sets, functions, and related structures. These types form the foundation for specifying behaviors without reference to programming language implementations, emphasizing abstract mathematical entities. TLA+ is untyped, with all values being sets. It includes a built-in BOOLEAN set {TRUE, FALSE} for booleans. The sets Nat (naturals, non-negative integers starting from 0) and Int (integers, all whole numbers, including negatives and zero) are defined in standard modules and used for counting, indexing, and signed quantities in models.[27]
Sets in TLA+ can be finite or infinite, serving as the primary building block for composite structures. Finite sets, such as {1, 2, 3}, are enumerable and essential for model checking, while infinite sets like Nat allow abstract specifications. The power set of a set S, denoted SUBSET S, includes all possible subsets of S. Common operations include union (∪), intersection (∩), set difference (), and membership testing via the ∈ operator.[27]
Functions are defined as sets of ordered pairs, mapping elements from a domain to a range, and are denoted using notation like [S → T] for the set of all functions from S to T. The domain of a function f is the set of its defined inputs, and the range consists of its output values. Function application uses square brackets, as in f, to retrieve the value associated with x in the domain. Updates to functions are performed with the EXCEPT construct, such as [f EXCEPT ! = e], which creates a new function identical to f except at x, where it takes the value e.[27]
Sequences represent ordered collections and are implemented as functions from the naturals 1..Len(s) to their elements, where Len(s) is the sequence length. The empty sequence is denoted << >>. Key operations include Head(s), which returns the first element s[43]; Tail(s), which yields the sequence without the first element; and Append(s, e), which adds e to the end of s, often written as s \o <>.[27]
Records and tuples provide structured data with fixed components. Records are functions with a finite domain of field names (typically strings), constructed as [a |-> 1, b |-> 2], where fields are accessed via dot notation like r.a or array notation r["a"]. Tuples are a special case of sequences with enumerated positions, such as <<1, 2>>, accessed by index like t[43]. Both support updates via EXCEPT and are used to bundle related values in state representations.[27]
In TLA+, state formulas are boolean-valued expressions that describe properties of individual states, expressed in terms of the system's variables. These formulas serve as predicates over the current state, enabling the definition of invariants and initial conditions. For instance, a type invariant might be specified as TypeOK == (x \in [Nat](/page/Nat)) /\ (y \in [BOOLEAN](/page/Boolean)), ensuring that variable x is a natural number and y is a boolean value. The initial state of a system is typically defined by a state formula Init, such as Init == (x = 0) /\ (y = FALSE), which constrains the starting values of variables.[27]
Action formulas extend state formulas to describe transitions between states, representing relations over pairs of states (a current state and a next state). They use primed variables (e.g., x') to denote values in the next state, allowing expressions like (x' = x + [1](/page/1)) /\ (y' = y) to model an increment operation. A complete next-state action Next often disjuncts multiple possible atomic actions, for example, Next == \E choice \in {[1](/page/1),[2](/page/2)}: IF choice = [1](/page/1) THEN x' = x + [1](/page/1) ELSE x' = x /\ y' = ~y, capturing nondeterministic choices while leaving unspecified variables unchanged by implication. This structure ensures actions are first-order logic formulas that can be evaluated over state pairs.[10][27]
Stuttering actions play a crucial role in TLA+ by permitting steps where the state remains unchanged, which supports refinement mappings and abstraction in specifications. The operator UNCHANGED <<v>> explicitly denotes that a variable v (or tuple of variables) does not change, as in (x' = x) /\ UNCHANGED <<y>>, equivalent to (x' = x) /\ (y' = y). This allows actions to include stuttering steps, such as Stutter == UNCHANGED <<x, y>>, facilitating the modeling of systems where not every step alters the observable state, thereby enabling coarser-grained specifications to refine finer ones.[27]
A behavioral specification in TLA+ combines initial states and actions into a complete model of system execution, typically as Spec == Init /\ [][Next]_vars, where vars is the tuple of all state variables and [A]_vars means A \/ UNCHANGED vars (i.e., either action A occurs or the state stutters). The temporal operator [] asserts that this holds for every step in the infinite behavior, ensuring the specification describes all possible state sequences starting from Init. This form captures safety properties implicitly through the action constraints.[10][27]
To address liveness, TLA+ specifications often include fairness conditions, such as weak fairness WF_vars(Next) \Def \Box ( \Box Enabled \langle Next \rangle _vars \implies \Diamond \langle Next \rangle _vars ), where \langle Next \rangle _vars \Def Next \land vars' \neq vars and Enabled \langle Next \rangle _vars \Def \E vars' : \langle Next \rangle _vars. This states that if the action Next is continuously enabled from some point onward, then a non-stuttering Next-step must occur eventually (allowing stuttering in between). Strong fairness variants exist for more stringent guarantees.[10][27]
Temporal Logic and Properties
TLA+ incorporates a linear-time temporal logic to express properties over infinite behaviors, which are sequences of states representing system executions. The core temporal operators include the "always" operator, denoted []F, which asserts that formula F holds in every state of the behavior; the "eventually" operator, denoted <>F, which asserts that F holds in at least one future state; the "until" operator F U G, which requires F to hold in all states up to and including the first state where G holds, with G eventually becoming true; and the "weak until" operator F W G, which is similar but allows G to never hold, in which case F must hold forever.[10] These operators enable the specification of system invariants and progress guarantees, and TLA+ formulas are stuttering invariant, meaning they remain true if finite sequences of stuttering steps—where variables remain unchanged—are inserted into the behavior.[44]
Safety properties in TLA+ capture the notion that "bad things never happen," formalized as properties violated only by finite prefixes of behaviors. A canonical example is an invariant like []TypeOK, where TypeOK is a state predicate ensuring all variables maintain valid types, and the [] operator extends it to hold invariantly across the entire execution.[45] More generally, safety can include conditional progress under fairness, such as [](cond => <>next), which ensures that if a condition cond holds, the next action eventually occurs, preventing deadlock-like scenarios in finite traces.[10] These properties focus on the absence of errors or invalid states, independent of infinite execution assumptions.
Liveness properties, in contrast, assert that "good things eventually happen," requiring consideration of the entire infinite behavior to verify, as violations occur only in infinite executions lacking the desired outcome. For instance, strong and weak fairness conditions support liveness: SF_vars(A) (strong fairness for action A on variables vars) requires that if A is enabled infinitely often, it is taken infinitely often; WF_vars(A) (weak fairness) requires that if A is continuously enabled from some point, it is taken infinitely often.[44] The leadsto operator, defined as p ~> q equivalent to [](p => <>q), combines safety and liveness by ensuring that if p holds, q will eventually hold, often under fairness assumptions.[42]
A representative liveness formula is Termination == <>\A x \in [Nat](/page/Nat) : x = 0, which specifies that all natural number variables eventually reach zero, capturing termination in algorithms like counters or processes.[10] Safety properties differ fundamentally from liveness in that the former can be falsified by a finite observation (e.g., reaching an invalid state), while the latter demands fairness to rule out pathological infinite non-progress executions, such as unfair scheduling indefinitely postponing an action.[45]
TLA+ supports compositional temporal reasoning by allowing properties to be conjoined—such as combining multiple safety invariants or liveness conditions into a single specification—while preserving modularity through action composition and behavioral refinement, enabling verification of complex systems by proving properties over subsystems.[10]
Modules and Libraries
Standard Modules
TLA+ includes a collection of standard modules that provide predefined operators for fundamental mathematical concepts and data structures, enabling specifications to leverage common operations without redefinition. These modules are part of the core TLA+ language and are documented in Leslie Lamport's book Specifying Systems. They are imported into a specification using the EXTENDS construct in the module header, such as EXTENDS FiniteSets, Naturals, which makes the operators available as if defined locally.[27]
The FiniteSets module provides two key operators for finite sets, building on TLA+'s primitive set theory and built-in operators like ∈ (membership), ∪ (union), ∩ (intersection), \ (difference), and ⊆ (subset). IsFiniteSet(S) evaluates to TRUE if the set S contains finitely many elements, distinguishing finite sets from infinite ones like the natural numbers. Cardinality(S) returns the number of elements in a finite set S, equivalent to the size of S. The power set of all subsets of S is denoted by SUBSET S. These are commonly used for modeling discrete state spaces in concurrent systems specifications, with built-in operators handling basic manipulations such as union over a set of sets via recursive definitions if needed.[27]
The Naturals module defines arithmetic operations on natural numbers, represented by the constant Nat as the set {0, 1, 2, ...}. Basic operators include addition (+), subtraction (-), multiplication (*), and exponentiation (^), all defined for elements of Nat. Division is handled by floor division (\div), and modulo by (%), which returns the remainder of m divided by n for n > 0. The greatest common divisor is given by gcd(m, n), useful for algorithms involving divisibility. Comparison operators such as <, <=, >, and >= are also included, along with the range notation m .. n for the set {m, m+1, ..., n}. These facilitate modeling countable resources or indices in temporal specifications.[27]
The Integers module extends Naturals to include negative numbers, defining Int as the set of all integers {... , -2, -1, 0, 1, 2, ...}. Unary negation (-a) is added for integers, allowing subtraction and other operations to handle signed values. Arithmetic operators from Naturals are lifted to Int where defined, supporting specifications that require signed quantities like offsets or balances.[27]
The Functions module provides utilities for working with functions, which in TLA+ are sets of ordered pairs. DOMAIN(f) returns the set of arguments (inputs) to which function f is defined, while RANGE(f) yields the set of values (outputs) in the image of f. The module also includes IsAFunction(f) to check if f is a function and supports construction via [S -> T] for the set of functions from S to T. For combining functions, the override operator @@ (from the TLC module) applies the second function where domains overlap, handy for updating function-based state representations like configurations. Checking if one function is determined by another requires a custom definition.[27]
The Sequences module supports ordered collections, treating sequences as functions from natural numbers to values up to their length. Len(s) returns the length of sequence s as a natural number. The empty sequence is denoted << >>. Append(s, e) constructs a new sequence by adding element e to the end of s. Concatenate(s, t), often written as s \o t, joins s and t into a single sequence. SubSeq(s, m, n) extracts the subsequence of s from position m to n (inclusive), with 1-based indexing. These operators are essential for modeling lists or traces in algorithmic specifications.[27]
The Bags module handles multisets, or bags, which allow multiple instances of elements unlike sets. A bag is a function from a set to Nat indicating multiplicities. IsABag(B) returns TRUE if B represents a bag. BagIn(e, B) checks if element e appears at least once in B, and CopiesIn(e, B) gives the multiplicity of e. SubBag(B1, B2) holds if every element's multiplicity in B1 is less than or equal to that in B2. EmptyBag is the empty bag, and BagCardinality(B) sums the multiplicities for the total count. Union is via BagUnion(B1, B2) or the infix ⊕, difference by \, and conversion operators like BagToSet(B) (distinct elements) and SetToBag(S) (one copy each). FiniteBags(S) denotes all finite bags over base set S, and BagOfAll({x \in S : [predicate](/page/Predicate)}, [lambda](/page/Lambda)) builds bags from filtered mappings. These are used for counting occurrences in resource allocation models. The module includes operators for both general and finite bags.[27]
Other standard modules include Booleans, which provides logical operators like /\ (and), / (or), and ~ (not) for boolean expressions, and Reals, which defines real numbers with operators for addition, multiplication, division, and comparisons, useful for specifications involving continuous values or precise arithmetic.[27]
Community and Extension Modules
The TLA+ community has developed a repository of non-standard modules and operators to extend the language's capabilities beyond the official standard library, facilitating reuse in specifications for complex systems. Hosted on GitHub, this collection includes snippets contributed by users to address common modeling needs, such as enhanced model checking support and data handling, and can be integrated into the TLA+ Toolbox or TLC by adding the library path.[46]
A key contribution is the TLC library, exemplified by the TLCExt module, which provides assertion operators and experimental features for configuring TLC model checking, including overrides for model parameters, symmetry reductions, and custom behaviors during verification runs. This allows users to tailor TLC's exploration of state spaces without modifying the core tool, such as defining specialized invariants or fairness constraints directly in the specification.
Community libraries address gaps in data modeling and interchange. The IOUtils module supports I/O operations by enabling the input and output of TLA+ values to files and spawning system commands, useful for integrating specifications with external environments during simulation or testing. For approximating real-number arithmetic, the DyadicRationals module offers operations on dyadic rational numbers, providing a finite-precision alternative to the standard Reals module for scenarios requiring numerical computations in model checking. Additionally, the Json module facilitates data interchange through JSON serialization and deserialization of TLA+ values, allowing specifications to interface with JSON-based APIs or datasets in distributed system models.[47][48][49]
Extension patterns in community modules leverage TLA+'s INSTANCE construct to parameterize reusable components, enabling generic definitions that can be instantiated with specific values for constants or variables in a parent module. For example, a generic distributed system pattern module might be instantiated multiple times with different node parameters to model a network, promoting modularity without redundancy. This approach aligns with TLA+'s hierarchical module system, where parameters are substituted via WITH clauses to adapt behaviors like communication protocols or resource allocation.[50]
As of June 2025, the TLA+ Foundation supports community efforts through its ongoing grant program and initiatives like the GenAI-Accelerated TLA+ Challenge, which encourages explorations of generative AI-assisted specification generation. These efforts contribute to the community repository, advancing TLA+ in emerging domains such as AI and distributed systems.[34][51][52]
Integrated Development Environment
The TLA+ Toolbox is an Eclipse-based integrated development environment (IDE) designed for developing and managing TLA+ specifications. It is freely downloadable from the official GitHub repository, supporting platforms including Windows, macOS, and Linux. The IDE facilitates module editing with features such as syntax highlighting for TLA+ and PlusCal code, code completion via Ctrl+Space, and visual representation of the module hierarchy to aid in understanding specification structure.[31][53]
Key features of the Toolbox include the spec explorer, which provides a tree-based view for navigating and organizing specifications, files, and dependencies. It offers real-time error checking, marking parsing and semantic errors directly in the editor with hyperlinks to detailed messages. The PlusCal translation viewer displays the automatically generated TLA+ code from PlusCal algorithms, highlighting any translation issues or warnings. Additionally, users can configure model checking runs through a dedicated editor that supports parameters like invariants, properties, and aliases for the TLC model checker.[53][54]
The Toolbox has seen incremental updates to enhance usability, such as the introduction of block comment collapsing in version 1.7.0 for better code folding and readability, along with improvements to the user interface for handling larger specifications through better error trace visualization and HiDPI support in version 1.8.0. As of 2025, while the Eclipse-based Toolbox remains the established primary IDE, active development has shifted toward a Visual Studio Code extension that offers similar editing capabilities and is increasingly recommended for new users seeking a lighter alternative.[55][56]
The typical usage workflow in the Toolbox begins with creating a new TLA+ module or PlusCal algorithm via the File menu, followed by editing the content in the integrated text editor. Upon saving, the IDE automatically parses the specification to validate syntax and semantics, flagging issues immediately. Users then define configurations for verification tasks, such as specifying behaviors and properties, and launch the integrated tools like the TLC model checker or TLAPS proof assistant directly from the interface to analyze the spec.[53][54]
Model Checking
TLC is an explicit-state model checker designed for verifying finite-state models specified in TLA+. It exhaustively explores the state space of a specification by performing a breadth-first search (BFS) to detect violations of safety properties, such as invariants, and uses depth-first search (DFS) for liveness properties.[57][58] The tool assumes the specification describes finite behaviors, typically achieved by bounding existential quantifiers and parameters in the model configuration to ensure a finite number of states.[16] TLC detects issues like deadlocks—states with no successors—by default during exploration, unless disabled via the -deadlock flag.[57]
To configure TLC, users define model parameters in a configuration file (e.g., MySpec.cfg), assigning concrete values to constants or using model values to represent distinct, unequal elements that TLC treats as atomic.[59] Invariants and properties are checked using command-line options, such as -invariant Inv to verify a specific invariant Inv across all reachable states, or -liveness for temporal properties assuming weak fairness.[57] Optimizations enhance efficiency: symmetry reduction collapses equivalent states under permutations of model values, significantly reducing the explored space for symmetric systems like multi-process protocols.[60] The cone of influence limits state representation to variables relevant to the checked properties, minimizing memory usage by excluding irrelevant variables.[61]
Despite these features, TLC's primary limitation is the state space explosion problem, where even modest models can generate billions of states, rendering exhaustive checking infeasible.[16] Mitigations include abstraction, where users simplify the specification by hiding details or bounding parameters further, and partial order reduction techniques, though TLC's implementation of the latter is limited and primarily relies on static analysis rather than dynamic reduction during search.[62] As of 2025, TLC is applied in evaluating specifications generated by generative AI tools, such as through the TLAiBench benchmark for assessing AI-assisted formal methods and the GenAI-accelerated TLA+ contest, where it validates AI-synthesized invariants.[63][64]
Proof Assistance
The TLA+ Proof System (TLAPS) provides mechanical verification of deductive proofs for TLA+ specifications, enabling the checking of properties that are challenging for model checkers due to infinite state spaces or complex liveness conditions. It supports hierarchical proof development, where users decompose theorems into lemmas and substeps, generating proof obligations that are discharged by automated backend provers. This approach allows for scalable reasoning about safety and liveness properties in concurrent systems.[17]
TLAPS architecture centers on a proof manager that parses hierarchical proofs and translates them into obligations suitable for backend provers, including the SMT solver Z3 (tried first with a 5-second timeout), the tableaux-based Zenon (10-second timeout), and Isabelle (30-second timeout with the auto tactic). If an initial backend fails, it proceeds to the next; users can specify backends explicitly via tactics like BY [SMT](/page/SMT) or BY ZenonT(30). Obligations involving arithmetic and logic are handled by these provers, while temporal aspects rely on specialized modules. Recent enhancements in 2024 improved SMT encoding safety, reducing spurious counterexamples and boosting reliability for arithmetic-heavy proofs by better preserving TLA+ semantics in SMT-LIB format. As of 2025, updates include TLAPM integration with Isabelle 2025 for enhanced proof capabilities.[65][66]
The proof language is declarative and hierarchical, using keywords like THEOREM to state a property (e.g., THEOREM Inv == TypeInvariant), followed by PROOF to begin the structure. Numbered steps such as <1> and <2> outline subproofs, justified by BY clauses referencing assumptions, definitions, or tactics (e.g., BY DEF Next). Proofs conclude with QED, triggering verification of all obligations. This format abstracts away prover-specific details, allowing proofs to be checked across backends.[65][17]
Standard proof modules include the PTL (Propositional Temporal Logic) backend for handling temporal induction and fairness reasoning, such as proving liveness properties under weak or strong fairness assumptions without manual induction steps. The PTL module implements decision procedures for linear-time temporal logic, supporting rules like temporal consequence and enabling proofs of eventualities or invariants over infinite behaviors. These modules integrate seamlessly with core TLA+ logic for arithmetic and set theory.[67][68]
TLAPS is particularly useful for verifying infinite-state systems or properties requiring fairness, such as mutual exclusion algorithms with unbounded processes, where model checking encounters state explosion. It complements model checking by providing deductive assurance for general cases. Proofs are developed and checked interactively within the TLA+ Toolbox IDE, which highlights verified steps in green and reports failures with obligation details for debugging.[17][69]
Extensions
PlusCal Notation
PlusCal is a specialized notation designed as a preprocessor for TLA+, enabling the specification of algorithms in a structured pseudocode style that translates directly into TLA+ specifications.[29] Developed by Leslie Lamport, it facilitates the modeling of sequential and concurrent systems by abstracting away much of TLA+'s low-level syntax while preserving mathematical rigor.[29] PlusCal supports two variants: p-syntax, which is verbose and explicit, and c-syntax, which is more compact and familiar to C-like programmers.[70]
The core syntax revolves around algorithm blocks declared with --algorithm Name (or --fair algorithm for fairness), enclosing declarations of global variables, procedures, and processes, and concluding with end algorithm.[70] Variables are declared in a variables section, supporting initial values or sets, such as variables x = 0; y \in 1..N.[70] Procedures are defined with procedure PName(params), including local variables and a body of labeled statements, invoked via call PName(args).[70] Processes model concurrency with process ProcName = expr (or over a set), using self to reference the process instance, and containing labeled statements executed in an interleaving manner.[70]
PlusCal translates to a TLA+ module by generating an Init action that initializes all variables and a Next action as a disjunction of atomic steps from each process or procedure.[29] Labels delimit atomic steps, with each step from one label to the next translated as an enabling condition (e.g., based on program counter pc[self]) and variable updates, ensuring atomicity.[70] Fairness is handled via translator options or declarations like fair process, producing weak fairness (WF_vars(Action)) for eventually executing enabled actions or strong fairness (SF_vars(Action)) for infinitely often enabled ones.[70]
Key features include structured control flow: while test do body end while for loops (requiring labels inside), if test then body [else body] end if for conditionals, await expr (or when) for synchronization by blocking until the condition holds, and implicit Error labels at procedure ends to detect unreachable deadends.[70] These constructs support nondeterminism through unlabeled statements and gotos for jumps.[29]
PlusCal's primary advantages lie in its accessibility for programmers, allowing sequential and concurrent pseudocode to be written intuitively without directly managing TLA+'s state formulas or temporal operators, while automatically generating a complete temporal specification suitable for verification.[29] This abstraction reduces the learning curve for specifying algorithms mathematically, enabling efficient use of tools like the TLC model checker for debugging.[29]
In January 2025, the PlusCal command-line interface fixed issues with the -labelRoot option.[71] Temporal properties can be verified on the resulting TLA+ specifications using standard tools.[29]
TLA+ specifications can be integrated with external model checkers through translation efforts, though direct exports to tools like SMV or NuSMV are not standard in the core ecosystem. Early work explored translating finite-state TLA+ models to the SMV input language for BDD-based symbolic model checking, but the primitive data types and limited expressiveness of SMV made the process tedious and incomplete, ultimately leading to the development of the native TLC model checker instead.[16] More recent symbolic verification for TLA+ relies on tools like APALACHE, which internally translates TLA+ transition relations into SMT constraints without requiring export to external formats like NuSMV.[72] These integrations enable leveraging BDD or SAT-based techniques for larger state spaces, but they highlight the challenge of preserving TLA+'s mathematical expressiveness during translation.
The TLA+ Proof System (TLAPS) facilitates interoperability with interactive theorem provers by encoding proof obligations as theorems in backend logics. TLAPS primarily uses Isabelle/HOL as a trusted backend, where TLA+ set theory is faithfully encoded as an object logic, allowing automatic verification of proof steps via Isabelle's tactics.[73] This integration supports exporting TLA+ theorems to Isabelle for higher-order reasoning, with TLAPS invoking Isabelle after initial attempts with lighter backends like SMT solvers or Zenon; if Isabelle succeeds within a timeout (e.g., 30 seconds for auto tactics), the obligation is discharged.[65] Such interoperability enhances proof reliability for temporal properties, combining TLA+'s action-based reasoning with Isabelle's deductive power.[17]
Code generation from TLA+ to imperative languages bridges formal specifications with executable implementations, often via specialized translators. Tools like the Elixir Translator convert TLA+ actions directly into Elixir code for runtime monitoring, providing a literal mapping that preserves behavioral semantics for observation but omits low-level details.[74] Similarly, PGo compiles Modular PlusCal algorithms (a TLA+ extension) into Go code, enabling deployment of verified concurrent designs in production systems.[74] Community efforts also generate tested code and unit tests from TLA+ files, using simulation traces from TLC to derive imperative implementations in languages like Java or C, though these require manual refinement to handle platform-specific constructs.[75] These approaches extend TLA+'s applicability beyond verification to software development workflows.
In 2025, integrations with large language models (LLMs) have emerged to automate code generation from TLA+ specifications, leveraging AI to interpret formal models. For instance, Claude (version 4.5 Sonnet) uses TLA+ specs as grounding prompts to generate concurrent Rust code, such as a Rule 110 cellular automaton visualizer with parallel updates, reducing errors in complex rule translations.[76] Similarly, TLA+ serves as precise contracts for Claude to implement Go-based REST APIs (e.g., task management systems), where model checking generates edge-case tests to validate the output.[77] The TLA+ Foundation launched a GenAI-accelerated TLA+ challenge in 2025, and TLAiBench provides benchmarks for assessing LLM performance on TLA+ tasks.[64][63] These developments, highlighted in community discussions, position LLMs as assistants for bidirectional translation—specs to code or code to specs—accelerating TLA+ adoption among developers.[78]
A notable 2025 application involves ZKsync's consensus protocol, ChonkyBFT, which employs TLA+ for formal specification and verification of its partially synchronous, Byzantine fault-tolerant design. The protocol's single-round voting and slot-based execution are modeled in Quint, a TLA+-inspired specification language, and verified using Apalache to check liveness and safety properties against up to one-third faulty nodes.[79] This integration demonstrates TLA+'s role in blockchain systems, where specifications guide implementation while Apalache verifies symbolic properties.[79]
Despite these advances, integrating TLA+ temporal models with imperative code faces significant challenges in semantic alignment. Refinement mismatches arise when abstract TLA+ operations (e.g., atomic assignments) map to detailed imperative sequences involving retries or error handling, potentially introducing unobserved behaviors.[74] Data structures in TLA+ (e.g., sequences) require concrete choices in code (e.g., persistent vs. in-memory), risking misalignment if assumptions like lossless networks do not hold.[74] Hidden control flows, such as asynchronous message failures omitted in high-level specs, complicate trace validation, often necessitating manual logging and incomplete coverage checks.[74] These issues underscore the need for hybrid tools that preserve temporal invariants during translation.
Applications and Impact
Industry Adoption
TLA+ has seen significant adoption in the cloud computing sector, with early pioneers including Amazon Web Services (AWS), where engineers began using it in 2011 to specify and verify distributed systems such as the S3 storage service and DynamoDB database. These efforts uncovered subtle concurrency bugs that traditional testing missed, enhancing design confidence and preventing costly post-deployment fixes; for instance, verification of a key S3 algorithm revealed a race condition that could have required weeks of debugging otherwise. Microsoft followed suit, applying TLA+ to Azure Cosmos DB for modeling consistency guarantees and replication protocols, which helped formalize five well-defined consistency levels and detect inconsistencies in documentation and implementation.[80][81]
By the mid-2010s, adoption expanded to other major players like Alibaba for PolarDB database verification and Huawei for GaussDB, focusing on fault-tolerant distributed storage. More recent cases from 2024-2025 highlight continued growth, particularly in database and cloud infrastructure; MongoDB has integrated TLA+ since 2015 for core protocols like replication and transactions, with 2025 efforts including modular verification of multi-shard transaction isolation and conformance checking to ensure code matches specifications, accelerating development while maintaining correctness.[82][83] Oracle Cloud Infrastructure (OCI) employs TLA+ for storage services, identifying bugs in drive management as noted in 2022 reports that remain relevant.[84] In blockchain, Informal Systems uses TLA+ for verifying Cosmos SDK consensus mechanisms, supporting secure decentralized applications.
A systematic literature review of industrial TLA+ practice from 2013-2023 reveals a marked upward trend in usage, with 63% of applications in cloud services and growing interest in blockchain (6%), indicating expansion into finance-adjacent domains like secure transaction protocols.[39] Benefits include early bug prevention—such as race conditions and deadlocks—and substantial ROI through reduced downtime.[39] These advantages foster greater confidence in distributed system designs, where TLA+ models clarify behavior under concurrency. However, challenges persist, including a steep learning curve for non-experts, though the TLA+ Toolbox mitigates this by providing accessible model checking and simulation tools.[84]
Academic and Research Use
TLA+ has been extensively applied in academic research for verifying complex protocols, particularly in distributed systems such as blockchain networks. For instance, researchers have used TLA+ to formally specify and model check the Lightning Network protocol, demonstrating its security properties against potential attacks like double-spending.[85] Similarly, TLA+ specifications have verified cross-chain swap protocols, ensuring atomicity and progress in decentralized ledger interactions across multiple blockchains.[86] In AI systems, TLA+ supports verification of multi-agent behaviors, where temporal logic models coordination and safety in distributed AI environments.[87] Advancements in automatic model checking with TLA+ include symbolic approaches that scale to larger state spaces for protocol analysis, such as via the Apalache model checker.[72]
Educational resources for TLA+ are integral to formal methods curricula at various universities, emphasizing its role in teaching system verification. Leslie Lamport's video lecture series provides an accessible introduction to TLA+ for programmers, covering specification writing, refinement, and model checking through practical examples.[88] Courses such as Northwestern University's COMP_ENG 356 introduce TLA+ alongside temporal logic and model checking for undergraduate students in computer engineering.[89] At Loyola University Chicago, COMP 335/488 focuses on TLA+ for model checking concurrent systems, incorporating hands-on assignments to build student proficiency.[90] NTNU's TM8103 course integrates TLA+ into network verification education, training graduate students on specifying and validating distributed protocols every other year.[91] These curricula underscore TLA+'s utility in fostering reproducible reasoning about concurrency, contrasting with ad-hoc testing methods in industry settings.
Beyond Lamport's foundational works, key community publications have advanced TLA+ research, particularly in model checking techniques. The 2005 paper on real-time model checking with TLA+ demonstrated efficient verification of timing properties in distributed algorithms using the TLC tool.[92] This approach has been extended with symbolic model checking via Apalache, which handles infinite-state specifications more effectively than enumeration-based methods.[72] TLA+ enables reproducible experiments in concurrency research by allowing precise, executable specifications that facilitate statistical analysis of system behaviors without implementation noise.[93] The TLA+ Foundation's 2024 grant program has funded academic tool development, supporting projects that enhance verification scalability and integration with emerging paradigms like AI-assisted proving.[94] These initiatives reflect a post-2020 surge in TLA+ research, with systematic reviews documenting over a decade of growing academic contributions.[39] In 2025, the TLA+ Community Event co-located with ETAPS highlighted ongoing advancements in tool development and applications.[35]
Examples
Simple System Specification
A simple system specification in TLA+ can be illustrated using a basic counter module, which models a variable that increments indefinitely from an initial value of zero. This example demonstrates core concepts such as initial states, next-state actions, behavioral specifications, safety invariants, and basic liveness properties, all without concurrency or advanced features. The specification ensures that the counter never becomes negative and, under fairness assumptions, eventually reaches a target value if starting below it.[27]
The full module code is as follows:
---- MODULE Counter ----
EXTENDS Naturals
VARIABLE x
Init ==
x = 0
Next ==
x' = x + 1
Spec ==
Init /\ [][Next]_x /\ WF_x(Next)
Invariant ==
x >= 0
Property ==
[](x < 10 => <>(x = 10))
====
---- MODULE Counter ----
EXTENDS Naturals
VARIABLE x
Init ==
x = 0
Next ==
x' = x + 1
Spec ==
Init /\ [][Next]_x /\ WF_x(Next)
Invariant ==
x >= 0
Property ==
[](x < 10 => <>(x = 10))
====
In this specification, the initial state (Init) sets the counter x to 0. The next-state action (Next) defines a single possible transition: in each step, x increments by 1, denoted by the primed variable x' representing the value in the subsequent state. The overall behavior (Spec) combines the initial state with the temporal requirement that every step either applies Next or stutters (leaves x unchanged), using the temporal operator [] for "always" and the subscript _x to indicate stuttering over x, along with weak fairness WF_x(Next) to ensure progress by prohibiting infinite stuttering when Next is enabled. The safety invariant (Invariant) asserts that x remains non-negative in all reachable states, which is preserved because increments start from 0 and add positive values. The liveness property (Property) states that if x is ever less than 10, it will eventually equal 10, capturing progress in the system under the fairness condition.[27]
To verify these properties, the specification can be checked using the TLC model checker within the TLA+ Toolbox, an integrated development environment for editing and analyzing TLA+ modules. Users load the module into the Toolbox, configure a model (e.g., by bounding the state space with an assumption like x <= 10 to ensure finiteness for exhaustive checking) and enabling fairness checking, then run TLC on the Invariant and Property. TLC explores all possible state transitions—here, a linear chain from x=0 to x=10—confirming that the invariant holds (no negative values) and the property is satisfied under fairness (progress to 10 occurs without deadlock). If a violation exists, TLC produces a counterexample trace of violating states. This process demonstrates safety and liveness through model checking, highlighting TLA+'s ability to detect errors in simple sequential behaviors early in design.[27][53]
Concurrent Algorithm Example
To illustrate the specification of concurrent algorithms in TLA+, consider the bakery algorithm, a classic solution to the mutual exclusion problem for multiple processes using only atomic reads and writes to shared variables. Developed by Leslie Lamport, the algorithm assigns each process a "ticket" number to determine entry order into the critical section, ensuring no starvation under fairness assumptions. For clarity, the example here focuses on two processes (labeled 1 and 2), specified in PlusCal and translated to TLA+, highlighting concurrency, mutual exclusion, and liveness properties such as absence of starvation.[95]
The PlusCal specification models the processes symmetrically, with shared variables choosing (a boolean array indicating if a process is selecting a ticket) and number (an array of natural numbers representing tickets, initially 0). Each process repeatedly executes a non-critical section, attempts to enter the critical section by acquiring a ticket and waiting appropriately, performs the critical section, and then releases its ticket. The key concurrency arises in the ticket selection and waiting phases, where processes non-deterministically interleave. Below is the full PlusCal code for the two-process case, using separate process definitions for readability. Assume Proc1 = 1 and Proc2 = 2 with 1 < 2 for lexicographical ordering:
---- MODULE Bakery ----
EXTENDS Integers, FiniteSets, Naturals
CONSTANTS Proc1, Proc2
ASSUME Proc1 = 1 /\ Proc2 = 2
Procs == {Proc1, Proc2}
N == Cardinality(Procs)
VARIABLES choosing, number, pc
vars == <<choosing, number, pc>>
TypeOK ==
/\ choosing \in [Procs -> BOOLEAN]
/\ number \in [Procs -> Nat]
/\ pc \in [Procs -> {"ncs", "try", "wait", "cs", "exit"}]
Init ==
/\ choosing = [p \in Procs |-> FALSE]
/\ number = [p \in Procs |-> 0]
/\ pc = [p \in Procs |-> "ncs"]
p1(self) ==
/\ pc[self] = "ncs"
/\ pc' = [pc EXCEPT ![self] = "try"]
/\ UNCHANGED <<choosing, number>>
p2(self) ==
/\ pc[self] = "try"
/\ LET other == IF self = Proc1 THEN Proc2 ELSE Proc1 IN
/\ choosing' = [choosing EXCEPT ![self] = TRUE]
/\ number' = [number EXCEPT ![self] = number[other] + 1]
/\ choosing' = [choosing EXCEPT ![self] = FALSE]
/\ pc' = [pc EXCEPT ![self] = "wait"]
/\ UNCHANGED <<choosing, number>> \* Overrides only as defined above
p3(self) ==
/\ pc[self] = "wait"
/\ LET other == IF self = Proc1 THEN Proc2 ELSE Proc1 IN
/\ LET EnterCS == ~choosing[other] /\
(number[other] = 0 \/ <<number[self], self>> <= <<number[other], other>>) IN
/\ pc' = [pc EXCEPT ![self] = IF EnterCS THEN "cs" ELSE "wait"]
/\ UNCHANGED <<choosing, number>>
p4(self) ==
/\ pc[self] = "cs"
/\ pc' = [pc EXCEPT ![self] = "exit"]
/\ UNCHANGED <<choosing, number>>
p5(self) ==
/\ pc[self] = "exit"
/\ number' = [number EXCEPT ![self] = 0]
/\ pc' = [pc EXCEPT ![self] = "ncs"]
/\ UNCHANGED choosing
p(self) ==
\/ p1(self)
\/ p2(self)
\/ p3(self)
\/ p4(self)
\/ p5(self)
Next == \E self \in Procs : p(self)
Spec == Init /\ [][Next]_vars
Fairness ==
\A self \in Procs : WF_vars( p(self) /\ (pc[self] # "ncs") ) /\
Spec
MutualExclusion ==
[](~ (pc[Proc1] = "cs" /\ pc[Proc2] = "cs"))
NoStarvation ==
\A p \in Procs : [](pc[p] = "try") => <> (pc[p] = "cs")
THEOREM Spec => MutualExclusion
THEOREM Fairness => NoStarvation
====
---- MODULE Bakery ----
EXTENDS Integers, FiniteSets, Naturals
CONSTANTS Proc1, Proc2
ASSUME Proc1 = 1 /\ Proc2 = 2
Procs == {Proc1, Proc2}
N == Cardinality(Procs)
VARIABLES choosing, number, pc
vars == <<choosing, number, pc>>
TypeOK ==
/\ choosing \in [Procs -> BOOLEAN]
/\ number \in [Procs -> Nat]
/\ pc \in [Procs -> {"ncs", "try", "wait", "cs", "exit"}]
Init ==
/\ choosing = [p \in Procs |-> FALSE]
/\ number = [p \in Procs |-> 0]
/\ pc = [p \in Procs |-> "ncs"]
p1(self) ==
/\ pc[self] = "ncs"
/\ pc' = [pc EXCEPT ![self] = "try"]
/\ UNCHANGED <<choosing, number>>
p2(self) ==
/\ pc[self] = "try"
/\ LET other == IF self = Proc1 THEN Proc2 ELSE Proc1 IN
/\ choosing' = [choosing EXCEPT ![self] = TRUE]
/\ number' = [number EXCEPT ![self] = number[other] + 1]
/\ choosing' = [choosing EXCEPT ![self] = FALSE]
/\ pc' = [pc EXCEPT ![self] = "wait"]
/\ UNCHANGED <<choosing, number>> \* Overrides only as defined above
p3(self) ==
/\ pc[self] = "wait"
/\ LET other == IF self = Proc1 THEN Proc2 ELSE Proc1 IN
/\ LET EnterCS == ~choosing[other] /\
(number[other] = 0 \/ <<number[self], self>> <= <<number[other], other>>) IN
/\ pc' = [pc EXCEPT ![self] = IF EnterCS THEN "cs" ELSE "wait"]
/\ UNCHANGED <<choosing, number>>
p4(self) ==
/\ pc[self] = "cs"
/\ pc' = [pc EXCEPT ![self] = "exit"]
/\ UNCHANGED <<choosing, number>>
p5(self) ==
/\ pc[self] = "exit"
/\ number' = [number EXCEPT ![self] = 0]
/\ pc' = [pc EXCEPT ![self] = "ncs"]
/\ UNCHANGED choosing
p(self) ==
\/ p1(self)
\/ p2(self)
\/ p3(self)
\/ p4(self)
\/ p5(self)
Next == \E self \in Procs : p(self)
Spec == Init /\ [][Next]_vars
Fairness ==
\A self \in Procs : WF_vars( p(self) /\ (pc[self] # "ncs") ) /\
Spec
MutualExclusion ==
[](~ (pc[Proc1] = "cs" /\ pc[Proc2] = "cs"))
NoStarvation ==
\A p \in Procs : [](pc[p] = "try") => <> (pc[p] = "cs")
THEOREM Spec => MutualExclusion
THEOREM Fairness => NoStarvation
====
This PlusCal code translates directly to the TLA+ temporal formula Spec, where pc[p] tracks the program counter for process p (e.g., "try" for ticket selection, "wait" for the ordering check, "cs" for critical section). The waiting condition in p3 uses lexicographical ordering on (number[p], p) pairs with <= to enforce FIFO entry, preventing starvation, and includes the check for ~choosing[other]. Fairness is enabled via weak fairness (WF_vars) on actions that advance a process from non-critical states, ensuring progress.[95]
Verification with the TLC model checker confirms the properties. Running TLC on Spec with a bound on number values (e.g., maximum ticket 10) explores all reachable states (typically thousands for two processes) and verifies MutualExclusion as an invariant, finding no violations. For liveness, checking NoStarvation under Fairness succeeds, as the algorithm guarantees eventual entry. However, omitting fairness in the TLC configuration allows unfair executions where one process is perpetually delayed (e.g., the other process repeatedly enters without yielding), detecting potential starvation violations in infinite behaviors truncated by depth limits. A brief TLAPS proof can establish termination: a lemma proves that under the invariant TypeOK /\ Before(p,q) (where Before(p,q) captures the ordering number[p]=0 \/ (number[q] # 0 /\ <<number[p],p>> <= <<number[q],q>>)), the waiting action p3 eventually enables, using induction on the loop.[96]
This example demonstrates TLA+'s strength in handling non-determinism, such as the arbitrary choice of ticket numbers (bounded in TLC for finite checking) and interleaved process steps, which model real concurrent execution. The abstract specification refines naturally to concrete code, for instance, by mapping PlusCal labels to programming language blocks (e.g., in Java or Go) while preserving liveness through the same atomic operations, enabling stepwise implementation verification.