ols
ols

Reputation: 163

How to start a singleton task in an ada package

I am doing some Ada this Sunday .. ;-)

I have write a small Log package:

log.ads:

package Log is
    procedure log (text: String);
end Log;

log.adb:

with Ada.Text_IO;

package body Log is

    procedure log (text: String) is
    begin
        Ada.Text_IO.Put (text);
    end log;

end Log;

I can use it like this: test.adb:

with Log;

procedure Test is
begin
    Log.log ("bla bla");
end Test;

Now, I would like to "improve" this package. I would like the log procedure to "push" the text to a task. It is the task that does the "Ada.Text_IO.Put (text)". The task can be:

task Logger_Task is
    entry log (text : String);
end Logger_Task;

task body Logger_Task is
begin
    loop
        accept log (text: String) do
            Ada.Text_IO.Put (text);
        end log;
    end loop;
end Logger_Task;

I would like that the Log clients are not aware of this task, so it should be hidden somewhere into the Log package. I do not know how and where to instantiate the task ...

This task must also remain active throughout the duration of the application.

Thanks for your help.

Upvotes: 4

Views: 356

Answers (3)

Jere
Jere

Reputation: 3641

A method using rendezvous was shown by another user, but if the means of handling that message could block, then even if you do that outside the accept statement, the caller could still potentially block waiting on the logger task to get back around to the accept statement. Text_IO should not be much of a problem, but for other types of outputs that potentially could block longer, consider an alternative:

Use an unbounded synchronized queue paired with a standalone task that monitors the queue and retrieves messages from it as they are added and it has the time. Even though it is an entry, the Enqueue operation will not block the calling task. This way, if the output method for your logger ends up blocking for relatively long periods of time occasionally, your other tasks will just pop messages on a queue and move on and the monitoring task will eventually output them when it catches up.

logging.ads

package Logging is

   procedure Put_Line(Message : String);

end Logging;

logging.adb

with Ada.Containers.Synchronized_Queue_Interfaces;
with Ada.Containers.Unbounded_Synchronized_Queues;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
use Ada.Containers;
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;

package body Logging is

   -- Create the queue
   package Interfaces is new Synchronized_Queue_Interfaces(Unbounded_String);
   package Queues is new Unbounded_Synchronized_Queues(Interfaces);
   Messages : Queues.Queue;

   -- This task will monitor the queue until the program ends
   task Logger;

   task body Logger is
      Message : Unbounded_String := Null_Unbounded_String;
   begin

      -- Stop looping once both main ends and there are no messages left
      -- The Environment_Task refers to the task that runs Main
      while Is_Callable(Environment_Task) or Messages.Current_Use > 0 loop

         -- Check the queue for a message but time out if not.  We
         -- need the timeout because Dequeue can block if the queue 
         -- is empty.
         select
            Messages.Dequeue(Message);
            Ada.Text_IO.Unbounded_IO.Put_Line(Message);
         or
            -- Pick a timeout that makes sense.  Longer timeouts 
            -- give better CPU usage during idle times, but also means
            -- the program might not end immediately. 10ms or 1 second
            -- might be fine for general use
            delay 1.0;
         end select;

      end loop;

      Ada.Text_IO.Put_Line("Main task closed, closing Logger");

   end Logger;

   procedure Put_Line(Message : String) is 
   begin
      Messages.Enqueue(To_Unbounded_String(Message));
   end Put_Line;

end Logging;

Upvotes: 1

flyx
flyx

Reputation: 39808

If you define the task in the body of the package, you can keep the procedure log interface:

with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded;

package body Log is

   task Logger_Task is
      entry log (text : String);
   end Logger_Task;

   task body Logger_Task is
      Cache: Ada.Strings.Unbounded.Unbounded_String;
   begin
      loop
         select
            accept log (text: String) do
               Cache := Ada.Strings.Unbounded.To_Unbounded_String (text);
            end log;
            Ada.Text_IO.Unbounded_IO.Put (Cache);
         or
            terminate;
         end select;
      end loop;
   end Logger_Task;

   procedure log (text: String) is
   begin
      Logger_Task.log (text);
   end log;
end Log;

select … or terminate; is vital to end the task when the main task ends (this alternative will only be taken when the main task has reached its end).

Cache is also important because it allows the calling task to continue after the text parameter has been received by the accept block. Directly calling Put in the accept block will make the calling task wait for it to finish, since it only continues after the accept block has been left.

Upvotes: 5

egilhh
egilhh

Reputation: 6430

You already instantiated it.

task Logger_Task is
    entry log (text : String);
end Logger_Task;

is the same as creating an instance of an anonymous task type:

task type Anonymous is
    entry log (text : String);
end Anonymous;
Logger_Task : Anonymous;

Upvotes: 5

Related Questions