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
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 B
s, 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.