Theory Lock_Abstract_Queue
section ‹Abstract Queue Lock›
theory Lock_Abstract_Queue
imports
RG_Annotated_Commands
begin
text ‹We identify each thread by a natural number.›
type_synonym thread_id = nat
text ‹The state of the Abstract Queue Lock consists of one single
field, which is the list of threads.›
record queue_lock = queue :: "thread_id list"
text ‹The following abbreviation describes when an object is at the
head of a list. Note that both clauses are needed to characterise the
predicate faithfully, because the term @{term ‹x = hd xs›} (i.e.\
@{term x} is the head of @{term xs}) does not imply that @{term ‹x ∈ set xs›}.›
abbreviation at_head :: "'a ⇒ 'a list ⇒ bool" where
"at_head x xs ≡ xs ≠ [] ∧ x = hd xs"
text ‹The contract of the Abstract Queue Lock consists of two clauses.
The first states that a thread cannot be added to or removed from the
queue by its environment. The second states that the head of the queue
remains at the head after any environment-step.›
abbreviation queue_contract :: "thread_id ⇒ queue_lock rel" where
"queue_contract i ≡ ⦃
(i ∈ set ºqueue ⟷ i ∈ set ªqueue) ∧
(at_head i ºqueue ⟶ at_head i ªqueue) ⦄"
text ‹The RG sentence of the Release procedure is made into a separate
lemma below.›
lemma qlock_rel:
"rely: queue_contract t guar: for_others queue_contract t
inv: ⦃ distinct ´queue ⦄ code:
{ ⦃ at_head t ´queue ⦄ }
´queue := tl ´queue
{ ⦃ t ∉ set ´queue ⦄ }"
proof method_basic_inv
case est_guar
then show ?case
apply clarsimp
by (metis hd_Cons_tl in_set_member member_rec(1))
next
case est_post
then show ?case
apply clarsimp
by (metis distinct.simps(2) list.collapse)
qed (simp_all add: distinct_tl)
text ‹The correctness of the Abstract Queue Lock is expressed by the
following RG sentence, which describes a closed system of @{term n}
threads, each repeatedly calls Acquire and then Release in an infinite
loop. We omit the critical section between Acquire and Release, as it
does not access the lock.
The Acquire procedure consists of two steps: enqueuing and spinning.
The Release procedure consists of only the dequeuing step.
Each thread can only be in the queue at most once, so the invariant
requires the queue to be distinct.
The queue is initially empty; hence the global precondition.
Being a closed system, there is no external actor, so the rely is the
identity relation, and the guarantee is the universal relation.
The system executes continuously, as the outer infinite loop never
terminates; hence, the global postcondition is the empty set.
›
theorem qlock_global:
assumes "0 < n"
shows "annotated
global_init: ⦃ ´queue = [] ⦄ global_rely: Id
∥ i < n @
{ ⦃ i ∉ set ´queue ⦄, queue_contract i }
WHILEa True DO
{stable_guard: ⦃ i ∉ set ´queue ⦄ }
NoAnno (´queue := ´queue @ [i]) .;
{ ⦃ i ∈ set ´queue ⦄ }
NoAnno (WHILE hd ´queue ≠ i DO SKIP OD) .;
{ ⦃ at_head i ´queue ⦄}
NoAnno (´queue := tl ´queue)
OD
⫽ ⦃ distinct ´queue ⦄ { for_others queue_contract i, {} }
global_guar: UNIV global_post: {}"
apply rg_proof_expand
apply (method_basic; fastforce)
apply (method_spinloop; fastforce)
using qlock_rel apply fastforce
using assms by fastforce
end