Последовательность TLA+ не обновляется вызовами Append или Tail

Эта проблема

Я играю с TLA+ и думал, что напишу следующую явно ложную спецификацию в PlusCal:

---- MODULE transfer ----
EXTENDS Naturals, TLC, Sequences

(* --algorithm transfer

\* Simple algorithm:
\*  1. Start with a shared-memory list with one element.
\*  2. A process adds arbitrary numbers of elements to the list.
\*  3. Another process removes arbitrary numbers of elements from the list, 
\*     but only if the list has more than one item in it. This check is 
\*     applied just before trying to removing an element. 
\* Is it true that the list will always have a length of 1? 
\* You would expect this to be false, since the adder process can add more elements 
\* than the remover process can consume.

variables stack = <<0>>

process Adder = 0
  begin
      AddElement:
        stack := Append(stack, Len(stack));
        either goto AddElement
        or skip
        end either;
end process;

process Remover = 1
  begin
      RemoveElement:
        \* Pop from the front of the stack
        if Len(stack) > 1 then
            stack := Tail(stack);
        end if;
        either goto RemoveElement
        or skip
        end either;
end process;

end algorithm *)

IsStackAlwaysUnitLength == Len(stack) = 1

====

После проверки IsStackAlwaysUnitLength как одно из временных свойств для отчета, я ожидал, что TLA+ пометит это свойство как невыполненное.

Однако все штаты пройдены! Почему это не терпит неудачу?

Попытки отладки

На отладке с print В заявлениях я заметил следующее странное поведение:

process Adder = 0
  begin
      AddElement:
        print stack;
        print "Adder applied!";
        stack := Append(stack, Len(stack));
        print stack;
        print "Adder task complete!";
        \* Force 
        either goto AddElement
        or skip
        end either;
end process;

process Remover = 1
  begin
      RemoveElement:
        \* Pop from the front of the stack
        print stack;
        print "Remover applied!";
        if Len(stack) > 1 then
            stack := Tail(stack);
            print stack;
            print "Remover task complete!";
        else
            print "Remover task complete!";
        end if;
        either goto RemoveElement
        or skip
        end either;
end process;

выходы в панели отладки

<<0>>
"Adder applied!"
<<0>>
"Adder task complete!"
<<0>>
<<0>>
"Remover applied!"
"Remover applied!"
"Remover task complete!"
"Remover task complete!"
<<0>>
"Adder applied!"
<<0>>
"Adder task complete!"

Я не уверен почему stack := Append(stack, Len(stack)); а также stack := Tail(stack); не обновляют глобальные stack переменная.

Создана полная спецификация TLA

---- MODULE transfer ----
EXTENDS Naturals, TLC, Sequences

(* --algorithm transfer

variables stack = <<0>>

process Adder = 0
  begin
      AddElement:
        stack := Append(stack, Len(stack));
        either goto AddElement
        or skip
        end either;
end process;

process Remover = 1
  begin
      RemoveElement:
        \* Pop from the front of the stack
        if Len(stack) > 1 then
            stack := Tail(stack);
        end if;
        either goto RemoveElement
        or skip
        end either;
end process;

end algorithm *)
\* BEGIN TRANSLATION
VARIABLES stack, pc

vars == << stack, pc >>

ProcSet == {0} \cup {1}

Init == (* Global variables *)
        /\ stack = <<0>>
        /\ pc = [self \in ProcSet |-> CASE self = 0 -> "AddElement"
                                        [] self = 1 -> "RemoveElement"]

AddElement == /\ pc[0] = "AddElement"
              /\ stack' = [stack EXCEPT ![0] = Append(stack, Len(stack))]
              /\ \/ /\ pc' = [pc EXCEPT ![0] = "AddElement"]
                 \/ /\ TRUE
                    /\ pc' = [pc EXCEPT ![0] = "Done"]

Adder == AddElement

RemoveElement == /\ pc[1] = "RemoveElement"
                 /\ IF Len(stack) > 1
                       THEN /\ stack' = [stack EXCEPT ![1] = Tail(stack)]
                       ELSE /\ TRUE
                            /\ stack' = stack
                 /\ \/ /\ pc' = [pc EXCEPT ![1] = "RemoveElement"]
                    \/ /\ TRUE
                       /\ pc' = [pc EXCEPT ![1] = "Done"]

Remover == RemoveElement

Next == Adder \/ Remover
           \/ (* Disjunct to prevent deadlock on termination *)
              ((\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars)

Spec == Init /\ [][Next]_vars

Termination == <>(\A self \in ProcSet: pc[self] = "Done")

\* END TRANSLATION

IsStackAlwaysUnitLength == Len(stack) = 1

====

1 ответ

Решение

Поздравляем, вы попали в ошибку PlusCal! И также крайний случай, который не ошибка, но все еще не интуитивный. Давайте начнем с ошибки.

Иногда при использовании PlusCal мы хотим, чтобы несколько процессов совместно использовали метки. Мы делаем это с помощью процедуры. Чтобы все это работало, переводчик PlusCal добавляет дополнительную переменную учета, которая называется stack, Обычно, если пользователь определяет переменную foo что конфликтует с сгенерированной переменной foo перевод переименовывает один в foo_, В этом случае, поскольку не было никакого конфликта, не было никакого переименования. * Ошибка состоит в том, что переводчик запутался и перевел переменную, как будто она должна была быть бухгалтерской stack, Вы можете видеть это, поскольку это превратило приложение в

stack' = [stack EXCEPT ![0] = Append(stack, Len(stack))]

Когда это должно быть просто

stack' = Append(stack, Len(stack))

Вы можете исправить это, переименовав stack в mystack , Это должно заставить спецификацию вести себя правильно. Но это все равно пройдет: это потому, что вы положили IsStackAlwaysUnitLength как свойство, а не инвариант. Как временное свойство, IsStackAlwaysUnitLength истинно, если оно истинно в исходном состоянии. Как инвариант, IsStackAlwaysUnitLength Истинно, если оно верно во всех штатах.** Вы можете заставить спецификацию не работать должным образом, изменив IsStackAlwaysUnitLength от временного свойства до инварианта на странице "что такое модель".

* На самом деле в этом случае переводчик не будет переименовывать stack если вы добавляете процедуру, она просто выдает ошибку. Но это все еще безотказно.

** Это потому, что TLC (проверка модели) обрабатывает инвариант P как временная собственность []P, Это синтаксический сахар, в основном.

Другие вопросы по тегам