learning_tla/lamport_video/docs/lecture09-1.md
2025-02-20 16:06:12 +01:00

6.6 KiB

09: Implementation - how it works - two-bit theorem

All specs until now focused on specifying what a system MAY do. This lecture will focus on what a system MUST do.

Finite sequences aka lists

Finite sequence is another name for tuple.

<< -3, "xyz", {0,2}>> is a tuple (or sequence) of length 3.

  • << -3, "xyz", {0,2}>>[1] = -3
  • << -3, "xyz", {0,2}>>[2] = "xyz"
  • << -3, "xyz", {0,2}>>[3] = {0,2}

They can be defined in function notation, e.g. the squares sequence for 1..N: [i \in 1..N |-> i^2]

This shows how brackets, seen in programming as dereferencing, are actually applications of the sequence function to a value in its domain.

The Sequences module defines operators for sequences:

  • Tail(<<s_1, ..., s_n>>) equals <<s_2, ..., s_n>>'
  • Head(seq) == seq[1]
  • \o concatenates sequences
    • IF seq # <<>> THEN seq = <<Head(seq)>> \o Tail(seq)
  • Append(seq, e) == seq \o <<e>>
  • Len(seq) equals the length of the sequence
    • DOMAIN seq = 1..Len(seq)
    • 1..0 = {} = DOMAIN <<>>
  • Seq(S) is the set of ALL sequences with elements in S.
    • That means it is infinite: Set({3}) = {<<>>, <<3>>, <<3,3>>, <<3,3,3>>, ...}
  • Remove(i, seq) is defined only in this lecture, not the Sequences module. It removes the i^th element from seq.
    • Len(Remove(i, seq)) = Len(seq) - 1
    • Remove(i, seq) == [j \in 1..(Len(seq) - 1) |-> IF j < i THEN seq[j] ELSE seq[j+1] ]

Evaluating constant expressions

To evaluate any constant expressions in a model:

  • in the toolbox, check "no behavior" and enter the expression under "Evaluate constant expression", then input expression
  • with TLC, disable SPECIFICATION, INIT, NEXT in the CFG file, and add a line like: ASSUME PrintT(<<"Eval", Remove(3, <<1, 2, 3, 4>>)>>) in the spec. Running TLC will include a line with <<"Eval", <<1, 2, 4>>>> in its results. Command tlceval.go can help
$ tlc -tool l09_remove.tla | go run bin/tlceval.go
<<1, 2, 3>>

Cartesian product

\A S, T: S \X T = {<<a, b>>: a \in S, b \in T}

tlc -tool l09_cartesian.tla | go run bin/tlceval.go 
 {<<1, "a">>, <<1, "b">>, <<2, "a">>, <<2, "b">>, <<3, "a">>, <<3, "b">>} 

Alternating Bit (AB) protocol

Intro

Sender A sends a sequence of data items to receiver B. Let them be strings for now.

If we sent the same string repeatedly, we cannot know what was sent if we only keep the state as the current string.

Instead, let us keep it as a sequence <<theString,bit>>, where bit \in {0,1}

This allows detecting repeated values, without having to rely on a clock.

Spec

See ABSpec.

Type correctness, checked by the TypeOK invariant, doesn't mean that the spec is correct.

To find errors, we need to check that formulas which should be invariant are. In our case, if the bits are equal, then the whole values are too.

At this point, formula Spec only asserts what MAY happen.

Safety and liveness

  • A safety formula is a temporal formula which only asserts what MAY happen
    • Any behavior that violates it does so at some point
    • Nothing past that point makes any difference (so the model check can stop)
    • A typical spec like Init /\ [][Next]_vars can only be violated if the initial step does not satisfy Init on the first state of the behavior, or if a step does not satisfy [Next]_vars: the step neither satisfies Next nor leaves vars unchanged
    • Once that has happend, nothing later in the formula can cause it to become true again
  • A liveness formula is a temporal formula which only asserts what MUST happen
    • A behavior can NOT violate it at any point: the rest of the behaviour can always make it true
    • Example: x = 5 on some state of the behavior
      • At any point, it is always possible for a later state to satisfy it
      • A behavior is any infinite sequence of states
      • This is asserted by <>(x = 5)
    • The only liveness property sequential programs must satisfy is termination, noted <>Terminated
    • Concurrent systems can have a wide variety of liveness requirements
    • Example for ABSpec: if AVar = <"hi", 0> in some state, then BVAr = <"hi", 0> in the same state or a later one (AVar = <<"hi", 0>>) ~> (BVar = <<"hi", 0>>)`

More generally we would like the AB protocol to satisfy: "Any value being sent by A is eventually received by B"

\A v \in Data \X {0,1}: (AVar = v) ~> (BVar = v) 

Check: <>P is equivalent to ~[]~P

Weak fairness

Definition

An action is enabled in state s iff there is a state t such as A is true on step s -> t, usually said "s -> t is an A step".

A conjunct (e.g. AVar = BVar) with no primes is an assertion about the first state.

A == /\ AVar = BVar \* Can be taken when values are equal
     /\ \E d \in Data: AVar' = <<d, 1 - AVar[2]>> \* Switch the second element
     /\ BVar' = BVar

A is enabled only if AVar = BVar AND Data # {}, otherwise there exists no element in Data for the \E quantifier.

Weak fairness of action A asserts of a behaviour:

  • if A ever remains continuously enabled
  • then an A step must eventually occur

Or equivalently: A cannot remain enabled forever without another A step occurring.

Weak fairness of A is written as temporal formula WF_vars(A), where vars is the tuple of all the spec's variables.

WF_vars(A) is a liveness property, because it can always be made true by an A step or a state in which A is not enabled.

Adding fairness to a spec

A spec with liveness is written: Init /\ [][Next]_vars /\ Fairness where:

  • Fairness is a conjunction of WF_vars(A) and SF_vars(a) formulas
    • where each A is a subaction of Next (every A step is a Next step)

In AB, this is FairSpec == Spec /\ WF_vars(Next): it asserts that a behavior keeps taking Next steps as long as Next is enabled, meaning as long as the system is not in a deadlocked or terminated state: the safety part of this spec ensures such a state cannot be reached, meaning it wll keep sending and receiving values forever: it doesn't not deadlock or terminate.

For liveness checking, the model MUST NOT have any SYMMETRY set.

Another possible high-level spec of AB would be FairSpec == Spec /\ WF_vars(B)

  • it requires every sent value to be received
  • but it allows the sender to stop sending

TODO: explain why these are equivalent:

Init /\ [][Next]_vars /\ WF_vars(Next)
Init /\ [][Next]_vars /\ WF_vars(A) /\ WF_vars(B)

Confirm with TLC

_vars

We add this non-stuttering step because it makes no sense to require a stuttering step to occur, since there is no way to tell that it did (no clock).