Reputation: 33
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
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