hectoruhalte
hectoruhalte

Reputation: 33

What is the best way to put logs on standard output generated by an infinite loop?

I am working on a (multi) client - server environment using PolyORB (DSA personality) by Adacore.

I have one shared passive partition which basically contains a protected type (array) which gets updated by the server with info coming from the clients every second, on a non-stop basis.

I have created a very simple main unit with an infinite loop just to monitor the content of that array.

As expected, printing out the rows of the array every second on the standard output is consuming the memory of the system.

I would like to avoid, if possible, the solution of printing out the info on a file, and run a tail -f command. I would not like to mess around with the disk, the number of clients now is limited but it is expected to grow and performance might get compromised.

On the other hand, I have tried through scripting to kill the monitor and launch it again, but somehow it does seem to work only once. I killed it, and then launched again (OK), then kill it again and the launched (not OK).

Is there a good way to print out a lot of info on the standard output using and infinite loop and not running out of memory?

I have checked there is no other issue causing the memory leak, if no info is printed out on the terminal, there is no problem with the memory.

This is the code of that monitor main unit

with Ada.Text_IO;
with Shared_Table;
with GNAT.OS_Lib;

procedure monitor is

 -- Variables to run linux command clear
 ARGUMENTS : GNAT.OS_Lib.Argument_List := (1 => new String'(""));
 RESULT : Boolean := False;

begin

   loop

         -- Clearing the terminal after writing the content of the table.

         GNAT.OS_Lib.Spawn (Program_Name => "/usr/bin/clear",
                            Args         => ARGUMENTS,
                            Success      => RESULT);

         -- Printing out the content of the table on the standard output.

         Shared_Table.Visualize_Table (Shared_Table.Obtain_Table);

         delay 1.0;

   end loop;  

end monitor;

Thanks!

Here it is the code of the procudure to print out the rows of the array which is likely to be causing the memory leak.

procedure Visualize_Table (Shared_Table : in Shared_Table_Type) is
      
      -- Very big varibale containing adaptation data in a simple linked list.
      Adaptation_Data : Adaptation.Track_List.List;
      
      -- Segment type is a record. One of the components being a simple linked list of items.
      Segment_Aux : Segment.Segment_Type;

      -- Track Type is a record. One of the components being a simple linked list of segments.
      Track_Aux : Track.Track_Type;
      
      Number_of_items : Natural range 1 .. 3 := 1;
   
   begin
      
      -- Load adaptation data.
      Adaptation.Generate_Track_List (Adaptation_Data);
      
      -- Visualize header.
      Ada.Text_Io.Put_Line (" +-----------+---------------------------------+---------+---------------------------------+-------------+");
      Ada.Text_Io.Put_Line (" | xxxxxxxxx |          xxxxxxxxxxxx           | # Items |          xxxxxxxxxxxxxxx        |  xxxxxxxxx  |");
      Ada.Text_Io.Put_Line (" +-----------+---------------------------------+---------+---------------------------------+-------------+");
      
      for I in 1 .. Number_of_clients loop
            
         -- Visualize first two columns.
         Ada.Text_Io.Put (" | " & Shared_Table(I).First_Row & "  | ");
         Ada.Text_Io.Put (Shared_Table(I).Second_Row & "| ");
            
         -- Initialize auxiliar variables..
         Track.Initialize_Track (Track_Aux);
         Segment.Initizalize (Segment_Aux);

         Track.Set_Id (Id    => Shared_Table(I).Track_Id,
                       Track => Track_Aux);
         
         Segment.Set_Position (Position => Shared_Table(I).Position,
                               Segment  => Segment_Aux);
            
         Number_of_items := 
           Segment.List_of_items.Obtain_Number 
             (Segment.Obtain_List_of_items 
                (Track.List_of_segments.Find_Element 
                   (Element => Segment_Aux,
                    List    => Track.Obtain_Segment_List (Adaptation.Track_List.Find_Element (Element => Track_Aux,
                                                                                              List    => Adaptation_Data)))));
            
         Ada.Text_Io.Put (" |");

         Ada.Text_Io.Put ("    X : ");
         Ada.Integer_Text_Io.Put (Item => Shared_Table(I).Position_X,
                                  Width => 7);
         Ada.Text_Io.Put ("  Y : ");
         Ada.Integer_Text_Io.Put (Item => Shared_Table(I).Position_Y,
                                  Width => 7);
         Ada.Text_Io.Put ("     |");

         Ada.Integer_Text_Io.Put (Item => Shared_Table(I).Last_Row,
                                  Width => 3);
         Ada.Text_Io.Put_Line ("          |");
            
         Ada.Text_Io.Put_Line 
           (" +-----------+---------------------------------+---------+---------------------------------+-------------+");
         
      end loop;
      
   end Visualize_Table;

My understanding is that the variables life should end when the procedure finishes printing out the rows of the array, and the memory used by them should then be free, but given some of the variables are linked list (using access types), that may not be the case, is that possible?

This is part of the log obtained by usgin valgrind

==17190== 61,799,120 (7,774,208 direct, 54,024,912 indirect) bytes in 60,736 blocks are definitely lost in loss record 816 of 816
==17190==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17190==    by 0x68D12E: __gnat_malloc (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x691C7C: system__pool_global__allocate (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x4CFD16: adaptation__track_list__insert_element (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x4D6028: adaptation__generate_track_list (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x4D991C: shared_table__visualize_table (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x4DB9FE: _ada_monitor (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x47926F: partition___elabb (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x47BA56: adainit (in /home/hector/ITS/distributed/src/monitor)
==17190==    by 0x47BAC1: main (in /home/hector/ITS/distributed/src/monitor)

I can see some other memory leaks all of the ultimately pointing to the instantiation of the procedure to insert new elements in a simple linked list.

This is the code of the generic package.

   procedure Insert_Element (Element : in Type_Element;
                             List : in out Type_List) is
    
      Pointer_to_Cell : Type_Pointer_to_Cell := new Type_Cell'(Record_Element => Element,
                                                               Record_Pointer => List.Record_Initial_Cell);
      
   begin
      
      if List.Record_Number_of_elements <= Max_Number_of_elements then
         
         List.Record_Initial_Cell := Pointer_to_Cell;
         List.Record_Number_of_elements := List.Record_Number_of_elements + 1;
         
      else
         
         raise List_Full_Exception;
         
      end if;

   end Insert_Element;

How can I deallocate the memory used by Adaptation_Data once I do not need it anymore?

Upvotes: 2

Views: 405

Answers (1)

Jim Rogers
Jim Rogers

Reputation: 5021

A generic protected object using a circular buffer can look like:

-----------------------------------------------------------------------
-- Generic Bounded Protected Queue
-----------------------------------------------------------------------

generic
   type Element_Type is private;
   type Queue_Index is mod <>;
package Generic_Protected_Bounded_Queue is
   type Buffer is array (Queue_Index) of Element_Type;

   protected type Queue is
      function Capacity return Natural;
      function Is_Full return Boolean;
      function Is_Empty return Boolean;
      entry Enqueue (Value : in Element_Type);
      entry Dequeue (Value : out Element_Type);
      procedure Clear;
   private
      Buf   : Buffer;
      Head  : Queue_Index := 0;
      Tail  : Queue_Index := 0;
      Count : Natural     := 0;
   end Queue;

end Generic_Protected_Bounded_Queue;

The corresponding body is:

package body Generic_Protected_Bounded_Queue is

   -----------
   -- Queue --
   -----------

   protected body Queue is

      --------------
      -- Capacity --
      --------------

      function Capacity return Natural is
      begin
         return Buf'Length;
      end Capacity;

      -------------
      -- Is_Full --
      -------------

      function Is_Full return Boolean is
      begin
         return Buf'Length = Count;
      end Is_Full;

      --------------
      -- Is_Empty --
      --------------

      function Is_Empty return Boolean is
      begin
         return Count = 0;
      end Is_Empty;

      -------------
      -- Enqueue --
      -------------

      entry Enqueue (Value : in Element_Type) when Count < Buf'Length is
      begin
         Buf (Tail) := Value;
         Tail       := Tail + 1;
         Count      := Count + 1;
      end Enqueue;

      -------------
      -- Dequeue --
      -------------

      entry Dequeue (Value : out Element_Type) when Count > 0 is
      begin
         Value := Buf (Head);
         Head  := Head + 1;
         Count := Count - 1;
      end Dequeue;

      -----------
      -- Clear --
      -----------

      procedure Clear is
      begin
         Head  := 0;
         Tail  := 0;
         Count := 0;
      end Clear;

   end Queue;

end Generic_Protected_Bounded_Queue;

When using this approach the consuming task does not need a one second timer to output the data. It only needs to print a line of text as soon as it arrives in the protected object buffer. The printing of data then becomes an event-based activity and not a timer-based activity.

Hi Jim, this the code for the task. I hope it helps

task Visualize_Data;
   
   task body Visualize_Data is
      
      Arguments : GNAT.OS_Lib.Argument_List := (1 => new String'(""));
      Result  : Boolean := False;

   begin
      
      loop
         
         if Shared_Table.Obtain_Rows > 0 then
            
            Shared_Table.Visualize_Header;

            for I in 1 .. Shared_Table.Obtain_Rows loop
            
               declare
           
                  Row : Shared_Table.Row;
         
               begin
         
                  Bounded_Buffer.Queue.Dequeue (Row);
                  Shared_Table.Visualize_Row (Row);
                  
               end;

            end loop;
                  
            
            GNAT.OS_Lib.Spawn (Program_Name => "/usr/bin/clear",
                               Args                  => Argument,
                               Success             => Result);
            
         end if;
         
      end loop;
      
   end Visualize_Data;

Of course, the task uses a lot of CPU resources because of the outer loop. That loop performs a polling operation rather than waiting for data to be available. The shared table should be implemented as a bounded buffer. The reading task will then suspend when the buffer is empty and read data only when it is available.

See the following example using the bounded queue package:

with Ada.Text_IO; use Ada.Text_IO;
with generic_protected_bounded_queue;

procedure Main is
   type Index_T is mod 10;
   package int_queue is new generic_protected_bounded_queue(Element_Type => Integer,
                                                            Queue_Index  => Index_T);
   use int_queue;

   The_Queue : Queue;

   task producer;
   task body producer is
   begin
      Put_Line("Producer started.");
      for Num in 1..20 loop
         The_Queue.Enqueue(Num);
         Put_Line("Enqueued" & Num'Image);
      end loop;
      Put_Line("Producer terminating.");
   end Producer;

   task consumer;
   task body consumer is
      Num : Integer;
   begin
      Put_Line("Consumer started.");
      for Val in 11..30 loop
         The_Queue.Dequeue(Num);
         Put_Line("Dequeued" & Num'Image);
      end loop;
      Put_Line("Consumer terminating.");
   end Consumer;

begin
   null;
end Main;

Upvotes: 1

Related Questions