I'm writing a specification of a simple client-server interaction to learn TLA+:
-------------------------------- MODULE Bar --------------------------------
VARIABLES
sessionOK, \* if session is OK or expired
msg \* message currently on the wire
vars == <<msg, sessionOK>>
TypeOK ==
/\ sessionOK \in {TRUE, FALSE}
/\ msg \in {
"Query",
"OK",
"Unauthorized",
"null"
}
Init ==
/\ msg = "null"
/\ sessionOK = FALSE
Query ==
/\ msg \in {"null", "OK"}
/\ msg' = "Query"
/\ UNCHANGED <<sessionOK>>
OK ==
/\ msg = "Query"
/\ sessionOK = TRUE
/\ msg' = "OK"
/\ UNCHANGED <<sessionOK>>
Unauthorized ==
/\ msg = "Query"
/\ sessionOK = FALSE
/\ msg' = "Unauthorized"
/\ UNCHANGED <<sessionOK>>
Authorize ==
/\ msg = "Unauthorized"
/\ msg' = "null"
/\ sessionOK' = TRUE
Expire ==
/\ sessionOK = TRUE
/\ sessionOK' = FALSE
/\ UNCHANGED <<msg>>
Next ==
\/ Query
\/ Unauthorized
\/ OK
\/ Authorize
\/ Expire
Spec == Init /\ [][Next]_vars /\ WF_vars(Next)
=============================================================================
As you can hopefully see the client can run some query against a server, and if the session is OK, it will get a result, otherwise it gets a message that it needs to authorize and then retry. The session can expire at any time.
I want to ensure that the client eventually will get a result, so I added this line to properties:
(msg = "Query") ~> (msg = "OK")
Upon model checking, I'm confronted with a counter-example going like this:
Init -> (Query -> Unauthorized -> Authorize -> Expire),
with the part in parentheses repeating indefinitely. My first thought was to make a strong fairness requirement on OK
step. However the problem is in this scenario OK
step is never enabled.
I could add things like []<><<OK>>_vars
(which reads "it's always the case that eventually OK
occurs"), but this feels like cheating, and from what I gather specifying liveness using arbitrary temporal formulas -- and not weak or strong fairness -- is frowned upon.
How can I use weak or strong fairness to guarantee that a query will eventually get a response? Or maybe my model is bad? I'm out of ideas...
I'm answering my own question. If anyone thinks they have a better answer, please share.
I found this aptly-named thread How to test a process eventually succeeds after indefinite number of retries?, where Leslie Lamport himself points out we can just assert fairness on a conjuction of an action and some other formula. In our scenario Spec
then looks like this:
Spec ==
/\ Init
/\ [][Next]_vars
/\ WF_vars(Next)
/\ SF_vars(OK)
/\ SF_vars(Query /\ (sessionOK = TRUE))
Now, what do each of these formulas in conjuction mean? The first three are quite obvious and were included in my attempt at defining Spec
.
Init
is true on the first state of the behaviour.Next
is true for every step of the behaviour (with stuttering allowed).Next
will continue to occur if it continues to be enabled indefinitely, so a system doesn't come to a stop in the middle of message exchange.Fourth one was my intuition: it fixes the situation where the query is made, and then the session expires.
OK
is repeatedly enabled (possible to happen) then it will eventually happen.And fifth is what was missing: it fixes the situation where OK
was never enabled in the first place, because the session expired before the Query
could happen.
Query
can happen and the session is valid at the same time, it will eventually happen.