ada

Difference between statements inside and outside accept block


Suppose the following (server) task specification:

task type Server_Task is
      entry E1(I: Integer);
      entry E2;
      entry E3;
end Server_Task;

with a (dummy) implementation of:

task body Server_Task is      
   begin
      loop
         select
            accept E1(I: Integer) do
               -- statements inside E1 using I
               -- statements inside E1 using I
               -- statements inside E1 using I
               null;
            end;                       
         or
            accept E2 do
               null;               
            end;
         or
            accept E3 do
               null;
            end; 
         end select;         
      end loop;     
end Server_Task;

Based on my understanding, if a client task makes an entry call for (say) E1 then all statements inside the E1 accept block will be executed before the server task loops over again and is ready to accept another entry call. The same is true if there are further statements following the end of the accept block so that again all these will need to run before the task can randevouz with a calling task again. If that assumption is correct, I'm wondering what the behavioural difference is between the above implementation and the one below:

task body Server_Task is
      Temp: Integer;
   begin
      loop
         select
            accept E1(I: Integer) do
               Temp := I;       
            end;
            -- statements outside E1 using Temp
            -- statements outside E1 using Temp
            -- statements outside E1 using Temp
         or
            accept E2 do
               null;               
            end;
         or
            accept E3 do
               null;
            end; 
         end select;         
      end loop;     
end Server_Task;

Will there be a difference if the statements outside E1 make a blocking call and hence the server task is suspended and therefore these statements will then have to somehow compete with any other entry calls made by the task's clients? (though this doesn't make much sense if the task is implemented using just one "thread"?)

For the sake of argument suppose the client code is along the lines of:

ST: Server_Task;   
   task body Client_Task is
   begin
      select
         ST.E2;
      else
         -- do something else
         null;
      end select;
      null;
   end Client_Task;

Is this behaviour detailed somewhere in the ARM? - Thanks


Solution

  • Ok, so there's several language features in play here... perhaps the best way to explain how things are working is with evolving a small "protocol" -- first let's have a system that accepts one A, two Bs, and a C:

    Task Example_1 is
       Entry A;
       Entry B;
       Entry C;
    End Example_1;
    
    Task Body Example_1 is
    Begin
       -- Protocol accept one A, followed by 2 B, finished with a C.
       Accept A;
       Accept B;
       Accept B;
       Accept C;
    End Example_1;
    

    Excellent, there we have a task that receives messages A, B, B, C, in order and terminates. Now, complexifying the protocol a bit, let's make the protocol A, followed by either a B or a C, then terminated with an A.

    Task Example_2 is
       Entry A;
       Entry B;
       Entry C;
    End Example_2;
    
    Task Body Example_2 is
    Begin
       -- Protocol: A, (B or C), A.
       Accept A;
       Select
          Accept B;
       or
          Accept C;
       End Select;
       Accept A;
    End Example_2;
    

    Now to complexify things a bit more: let's say that the task is a parser and A is submission of text, B is retrieval of token(s) and the length of unused text, and C is returning unused text, all in a loop until Finished:

    Task Example_3 is
       Entry A(Text : in     String);
       Entry B(List :    out Token_List; Remainder :    out Natural);
       Entry C(Text :    out String);
       Entry Finish;
    End Example_3;
    
    Task Body Example_3 is
      Temp     : String_Holder.Holder:= String_Holder.Empty_Holder;
      Result   : Token_List:= Empty_List;
      Finished : Boolean:= False;
    Begin
      Parser_Loop:
      Loop
       -- Protocol: Loop of (A, B, C) or finished.
       Select
         Accept A(Text : in     String) do
           Temp:= To_Holder( Text )
         End A;
         -- Processing TEMP here.
         Accept B(List :    out Token_List; Remainder :    Out Natural) do
            List := Result;
            Remainder:= Temp.Element'Length;
         End B;
         Accept C(Text :    out String) do
            Text:= Temp.Element;
         End C;
       or
         Accept Finish do
           Finished:= True;
         End Finish;
       End Select;
       Exit Parser_Loop when Finished;
     End loop Parser_Loop;
    End Example_3;
    

    And there to have most of the basics in understanding entry, accept and select -- the only other important thing to remember is that the things in an ACCEPT between DO and END are the rendezvous, and thus both the caller and callee are synchronized... if you want the parallelism increases, be sure to do your actual processing outside the rendezvous.