Let me illustrate this question with code first:
with Ada.Text_IO;
procedure Test
is
task type Serving_Task is
entry Start;
end Serving_Task;
task body Serving_Task is begin
accept Start;
Ada.Text_IO.Put_Line ("Started");
loop
null; -- Blocking operation, eg. accepting on a socket.
null; -- Processing on blocking operation's result.
end loop;
Ada.Text_IO.Put_Line ("Stopped");
end Serving_Task;
type Server is tagged limited record
Serving : Serving_Task;
end record;
procedure Start
(S : in out Server)
is begin
Ada.Text_IO.Put_Line ("Starting");
S.Serving.Start;
end Start;
procedure Stop
(S : in out Server)
is begin
Ada.Text_IO.Put_Line ("Stopping");
-- TODO To implement.
end Stop;
Main_Server : Server;
begin
Ada.Text_IO.Put_Line ("Calling Start");
Start (Main_Server);
delay 5.0;
Ada.Text_IO.Put_Line ("Calling Stop");
Stop (Main_Server);
end Test;
This is typical server construct – there’s a server task with a loop accepting incoming connections. My question is – what is the best way to implement Stop procedure for Server type.
I’d like it to wait until the serving task blocks on accepting a socket (or just before the blocking call, so every accepted request if fully handled before terminating the task) and exit the loop, so the task can clean up before terminating.
First thing that comes to mind is adding
select
accept Stop;
or
delay 0.1;
exit;
end select;
at the end of the loop, but that’s 0.1 second wasted on each iteration. Seems significant.
I’ve looked at some examples in RM, Lovelace tutorial and Wikibook, but nothing really seemed appropriate.
What is the best practice for such (fairly common I believe) problem in Ada?
And the answer is…
Example solution based on Marc C‘s answer: https://gist.github.com/3413017
The way I typically set this up is first by using AdaCore’s GNAT.Sockets package, rather than directly programming the sockets. Since I’ll be using the (socket) select() function (which is wrapped as Check_Selector) to be notified when data is available on the socket, GNAT.Sockets provides an Abort_Selector() procedure that can be invoked from elsewhere. With the task blocked on Check_Selector(), I just wait for data to arrive (Status = Completed) or the flag that it’s time to exit (Status = Aborted).
See the start of the Monitor_Connections procedure (lines 397-416ish) in the TOMI_4_Ada package TCP_Connect. Monitor_Connections is invoked from task Connection_Monitoring (lines 469-495).