libtlafmt 0.4.1

A formatter library for TLA+ specs, core of tlafmt
Documentation
---
source: libtlafmt/tests/format.rs
expression: output
input_file: libtlafmt/tests/corpus/KeyValueStore.tla
---
----------------------------- MODULE KeyValueStore -----------------------------
(**************************************************************************)
(* A simple key-value store exhibiting snapshot isolation. If two         *)
(* concurrent transactions write to the same key, the one merging later   *)
(* will be rejected. If they write different keys both will succeed. For  *)
(* a more-detailed specification of snapshot isolation, look at the       *)
(* specifications/SnapshotIsolation specs in the tlaplus/examples repo.   *)
(**************************************************************************)

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 \union {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 \union {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] = @ \union {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] = @ \union {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] = @ \union {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] \intersect 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] \union 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)
================================================================================