%
% SAL model of Fischer's mutual exclusion protocol
%
% Each process does
%   loop
%     wait until lock = 0;
%     set lock to process id;
%     wait;
%     if lock = process id 
%        enter critical section
%   end
%
% Parameters: 
%   N = number of processes (must be at least 2)
%   delta1, delta2: delays (must be positive)
%
%   - delay between realizing that lock = 0 and 
%   setting lock to process id is less than or equal to delta1
%   - waiting time between setting lock to process id
%   and entering the critical section is greater than or equal to delta2
%
% Mutual exclusion is satisfied if delta1 < delta2
%

fischer: CONTEXT =

BEGIN

  N: NATURAL = 2;

  IDENTITY: TYPE = [1 .. N];

  LOCK_VALUE: TYPE = [0 .. N];

  TIME: TYPE = REAL;

  delta1: TIME = 2;

  delta2: TIME = 4;

  TIMEOUT_ARRAY: TYPE = ARRAY IDENTITY OF TIME;


  %-----------------------
  %  Minimum of an array
  %-----------------------

  recur_min(x: TIMEOUT_ARRAY, min_sofar: TIME, idx: [0 .. N]): TIME =
    IF idx = 0 THEN min_sofar 
       ELSE recur_min(x, min(min_sofar, x[idx]), idx-1) 
    ENDIF;

  min(x: TIMEOUT_ARRAY): TIME = recur_min(x, x[N], N-1);



  %---------------------------------------------------------
  %  Clock module: makes time elapse up to the next timeout
  %---------------------------------------------------------

  clock: MODULE = 
    BEGIN
      INPUT time_out: TIMEOUT_ARRAY
      OUTPUT time: TIME
    INITIALIZATION
      time = 0
    TRANSITION
      [ time_elapses: time < min(time_out) --> time' = min(time_out) ]
    END;



  %--------------
  %  Process[i]
  %--------------

  PC: TYPE = { sleeping, waiting, trying, critical };

  process[i: IDENTITY]: MODULE =
    BEGIN
      INPUT  time: TIME
      GLOBAL lock: LOCK_VALUE
      OUTPUT timeout: TIME
      LOCAL pc: PC
    INITIALIZATION
      pc = sleeping;
      timeout IN { x: TIME | time < x };
      lock = 0
    TRANSITION
      [ waking_up: 
        pc = sleeping AND time = timeout AND lock = 0 -->  
             pc' = waiting;
             timeout' IN { x: TIME | time < x AND x <= time + delta1 } 

     [] try_again_later:
        pc = sleeping AND time = timeout AND lock /= 0 -->
             timeout' IN { x: TIME | time < x }

     [] setting_lock:
        pc = waiting AND time = timeout -->
             pc' = trying;
             lock' = i;
             timeout' IN { x: TIME | time + delta2 <= x }

     [] entering_cs:
        pc = trying AND time = timeout AND lock = i -->
             pc' = critical;
             timeout' IN { x: TIME | time < x }

     [] lock_changed:
        pc = trying AND time = timeout AND lock /= i -->
             pc' = sleeping;
             timeout' IN { x: TIME | time < x }

     [] exiting_cs:
        pc = critical AND time = timeout -->
             pc' = sleeping;
             lock' = 0;
             timeout' IN { x: TIME | time < x }
      ]
    END;


   %----------------------------------------------------
   %  Asynchronous composition: all processes together
   %    time_out[i] = timeout variable of process[i]
   %----------------------------------------------------

   processes: MODULE = 
     WITH OUTPUT time_out: TIMEOUT_ARRAY
        ([] (i: IDENTITY): (RENAME timeout TO time_out[i] IN process[i]));
    
   system: MODULE = clock [] processes;


   %--------------
   %  Properties 
   %--------------

   time_aux1: THEOREM
      system |- G(FORALL (i: IDENTITY): time <= time_out[i]);

   time_aux2: THEOREM
      system |- G(FORALL (i: IDENTITY): pc[i] = waiting => time_out[i] - time <= delta1);

   time_aux3: THEOREM 
      system |- G(FORALL (i, j: IDENTITY): lock = i AND pc[j] = waiting => time_out[i] > time_out[j]);

   logical_aux1: THEOREM
      system |- G(FORALL (i, j: IDENTITY): pc[i] = critical => lock = i AND pc[j] /= waiting);

   mutex: THEOREM
      system |- G(FORALL (i: IDENTITY): pc[i] = critical => lock = i);

   mutual_exclusion: THEOREM
      system |- G(FORALL (i, j: IDENTITY): i /= j AND pc[i] = critical => pc[j] /= critical);

END
