Does not understand point about requeuing and preemption

Hi, I am working to finish Ben Ari’s Ada for Software Engineers.
Anything related to concurrency is already hard enough to visualize, but the author isn’t doing much to help, to be honest.

I don’t know what is relevant so there isn’t much in the code that I can cut out.

pragma Queuing_Policy(Priority_Queuing);
 with Ada.Text_IO; use Ada.Text_IO;
 procedure CEOT is
   type Departments is (Engineering, Finance, Marketing);
   type ID_Numbers is range 0..10;
   Group_Size: constant array(Departments) of 
     ID_Numbers range 2..ID_Numbers'Last := (
       Engineering => 5, Finance => 3, Marketing => 2);
   task CEO is
     entry Wake(Departments);
     entry Invite_In(Departments)(ID: ID_Numbers);
     entry Show_Out(Departments)(ID:  ID_Numbers);
   end CEO;
   type Door_State is (Open, Closed);
   protected type Room(Department: Departments; Size: ID_Numbers) is
     entry Register;
     procedure Open_Door;
   private
     entry Wait_for_Last_Member;
     Waiting:   ID_Numbers := 0;
     Entrance:  Door_State := Open;
     Exit_Door: Door_State := Closed;
   end Room;
   protected body Room is
     entry Register when Entrance = Open is
     begin
       if Size = 1 then
         Entrance := Closed;
         requeue CEO.Wake(Department) with abort;
       end if;
       Waiting := Waiting + 1;
       if Waiting < Size then
         requeue Wait_for_Last_Member with abort;
       else
         Waiting := Waiting - 1;
         Entrance  := Closed;
         Exit_Door := Open;
       end if;
     end Register;
 
     entry Wait_for_Last_Member when Exit_Door = Open is
     begin
       Waiting := Waiting - 1;
       if Waiting = 0 then
         Exit_Door := Closed;
         requeue CEO.Wake(Department) with abort;
       end if;
     end Wait_for_Last_Member;
 
     procedure Open_Door is
     begin
       Entrance := Open;
     end Open_Door;
   end Room;
   Engineering_Room: Room(Engineering, Group_Size(Engineering));
   Finance_Room:     Room(Finance,     Group_Size(Finance));
   Marketing_Room:   Room(Marketing,   Group_Size(Marketing));
   task body CEO is
     I: ID_Numbers;
   begin
     loop
       select
         accept Wake(Finance);
         for N in 1..Group_Size(Finance) loop
           accept Invite_In(Finance)(ID: ID_Numbers) do
             I := ID;
           end Invite_In;
         end loop;
         for N in 1..Group_Size(Finance) loop
           accept Show_Out(Finance)(ID: ID_Numbers) do
             I := ID;
           end Show_Out;
         end loop;
         Finance_Room.Open_Door;
       or
         when Wake(Finance)'Count = 0 =>
         accept Wake(Marketing);
         for N in 1..Group_Size(Marketing) loop
           accept Invite_In(Marketing)(ID: ID_Numbers) do
             I := ID;
           end Invite_In;
         end loop;
         for N in 1..Group_Size(Marketing) loop
           accept Show_Out(Marketing)(ID: ID_Numbers) do
             I := ID;
           end Show_Out;
         end loop;
         Marketing_Room.Open_Door;
       or
         when Wake(Finance)'Count = 0 and
              Wake(Marketing)'Count = 0 =>
         accept Wake(Engineering);
         for N in 1..Group_Size(Engineering) loop
           accept Invite_In(Engineering)(ID: ID_Numbers) do
             I := ID;
           end Invite_In;
         end loop;
         for N in 1..Group_Size(Engineering) loop
           accept Show_Out(Engineering)(ID: ID_Numbers) do
             I := ID;
           end Show_Out;
         end loop;
         Engineering_Room.Open_Door;
       or
         terminate;
       end select;
     end loop;
   end CEO;
   task type Engineer_Task(ID: ID_Numbers);
   task body Engineer_Task is
   begin
     loop
       delay 1.0;
       Engineering_Room.Register;
       CEO.Invite_In(Engineering)(ID);
       CEO.Show_Out(Engineering)(ID);
     end loop;
   end Engineer_Task;
 ds
   task type Finance_Task(ID: ID_Numbers);
   task body Finance_Task is
   begin
     loop
       delay 4.0;
       Finance_Room.Register;
       CEO.Invite_In(Finance)(ID);
       CEO.Show_Out(Finance)(ID);
     end loop;
   end Finance_Task;
   task type Marketing_Task(ID: ID_Numbers);
   task body Marketing_Task is
   begin
     loop
       delay 2.0;
       Marketing_Room.Register;
       CEO.Invite_In(Marketing)(ID);
       CEO.Show_Out(Marketing)(ID);
     end loop;
   end Marketing_Task;
   type Engineer_Ptr is access Engineer_Task;
   type Finance_Ptr is access Finance_Task;
   type Marketing_Ptr is access Marketing_Task;
   Engineers:    array(1..7) of Engineer_Ptr;
   Accountants:  array(1..5) of Finance_Ptr;
   Salespersons: array(1..8) of Marketing_Ptr;
 begin
   for I in Engineers'Range loop
     Engineers(I) := new Engineer_Task(ID_Numbers(I));
   end loop;
   for I in Accountants'Range loop
     Accountants(I) := new Finance_Task(ID_Numbers(I))
   end loop;
   for I in Salespersons'Range loop
     Salespersons(I) := new Marketing_Task(ID_Numbers(I));
   end loop;
   delay 15.0; 
   for I in Engineers'Range loop
     abort Engineers(I).all;
   end loop;
   for I in Accountants'Range loop
     abort Accountants(I).all;
   end loop;
   for I in Salespersons'Range loop
     abort Salespersons(I).all;
   end loop;
 end CEOT;

this is how he explains about the necessity for requeuing rather than letting the last accountant checking in to call the CEO task on its own after the PO’s completion:

Closing the entrance door ‡115 closes the barrier Entrance=Open of entry Register and prevents other employees of the same type from overtaking the group that has been formed.
Requeue is also essential to avoid overtaking when awakening the CEO. Suppose that a group of accountants has been formed, that is, the last task of the group has completed Wait_for_Last_Member. Without requeue, the last accountant task would have to complete the protected action and then call the entry CEO.Wake(Finance) from within the sequence of statements of its task body:

Finance_Room.Register;
if I_Am_Last_Member then
CEO.Wake(Finance);
end if;

This task could be preempted—and a group of engineers could be formed and enqueued on CEO.Wake(Engineering)—after the protected action Finance_Room.Register completes, but before the call to CEO.Wake(Finance) is issued. With requeue, the protected action of the last accountant task is not completed until the task entry call has been made and immediately selected or enqueued. If it is enqueued, the guards on the accept statements of the selective accept prevent overtaking.

I don’t get it. The point is to avoid the CEO’s guard to be re-evaluated between the completition of the PA (protected action) and the call to its Wake entry, and a lower priority group be received before Finance, right.
But, if we accept that the normal entry call and requeue would happen at the exact same time (no issue of delay or context switch or whatnot), and the atomicity of both statements, how would the PA stop the CEO from re-evaluating its guard at any point ? CEO isn’t blocked or waiting on either the PO or the last accountant’s task. I do not understand why the window of opportunity for a preemption would stop any sooner with call to CEO inside a PO or not.

I hope I made myself clear :person_shrugging:

The explanation why you cannot call Wake after completing the action:

Finance_Room.Register;
if I_Am_Last_Member then
   CEO.Wake(Finance);
end if;

tells about the race condition. When Finance_Room.Register finishes on the last accountant, the entry count of Wake(Finance) is still zero. If the CEO has a core it could accept Wake(Engineering) before Wake(Finance) under the if statement, which would violate the corporate subordinance (and open the Gates of Hell :grinning:).

CEO sitting on select reevaluates the guards on queueing. If the last engineer queues on Wake(Engineering) the select would accept it.

The example is not quite convincing. There are far better cases for requeue. And there are probably issues with with abort. But I am too lazy to analyse them.

When Finance_Room.Register finishes on the last accountant, the entry count of Wake(Finance) is still zero. If the CEO has a core it could accept Wake(Engineering)

Look. Say you have the two cases running concurrenty on two (identical) machines, all the states and registries of your cores are equal before the last statement of Wait_for_Last_Member. And both CEO.Wake(Finance) and requeue CEO.Wake(Finance) with abortexecute at the exact same moment.
What, prior to this, stops CEO from evaluating its guard. And prior to that, there should be no difference whatsoever. How is being in a PO or outside it when the call is made, changing anything to CEO’s behavior ?
Or am I particularly dense :fearful:?

Register → Wake sequence is made atomic, so the CEO cannot accept any Wake for a lower priority room inbetween from the point of view the accountants. For the CEO there is no difference.

Not at all. The point could be to show how to work around the limitation that the CEO cannot non-busily check multiple protected objects (rooms) and how to impose ordering on entries which are selected “randomly.” Making one protected object for the whole building would resolve the issue too (there are entry families for that).

Disclaimer I did not read this book.