comp.lang.ada
 help / color / mirror / Atom feed
From: Adam Beneschan <adam@irvine.com>
Subject: Re: Allocators and memory reclamation
Date: Mon, 28 Jan 2008 15:54:21 -0800 (PST)
Date: 2008-01-28T15:54:21-08:00	[thread overview]
Message-ID: <eac39007-57c9-4133-b0bd-16df9158752f@l1g2000hsa.googlegroups.com> (raw)
In-Reply-To: 5f1fb537-239c-4700-8280-560287ce5677@d21g2000prf.googlegroups.com

On Jan 28, 2:27 pm, Maciej Sobczak <see.my.homep...@gmail.com> wrote:

> I understand that I can create my own pool with any behaviour I need.
> Let's try:
>
> with Ada.Text_IO;
> with Ada.Finalization;
> with System.Storage_Pools;
> with System.Storage_Elements;
>
> procedure Main is
>
>    use System.Storage_Pools;
>
>    -- used to acquire reference to the standard storage pool
>    type Integer_Ptr is access Integer;
>
>    procedure Test is
>
>       type My_Type is new Ada.Finalization.Controlled with null
> record;
>
>       procedure Finalize (T : in out My_Type) is
>       begin
>          Ada.Text_IO.Put_Line ("Finalize");
>       end Finalize;
>
>       use System;
>
>       type My_Pool_Type is new Root_Storage_Pool with null record;
>
>       procedure Allocate (Pool : in out My_Pool_Type;
>                           Storage_Address : out Address;
>                           Size_In_Storage_Elements : in
> Storage_Elements.Storage_Count;
>                           Alignment : in
> Storage_Elements.Storage_Count);
>       procedure Deallocate (Pool : in out My_Pool_Type;
>                             Storage_Address : in Address;
>                             Size_In_Storage_Elements : in
> Storage_Elements.Storage_Count;
>                             Alignment : in
> Storage_Elements.Storage_Count);
>       function Storage_Size (Pool : in My_Pool_Type) return
> Storage_Elements.Storage_Count;
>
>       procedure Allocate (Pool : in out My_Pool_Type;
>                           Storage_Address : out Address;
>                           Size_In_Storage_Elements : in
> Storage_Elements.Storage_Count;
>                           Alignment : in
> Storage_Elements.Storage_Count) is
>       begin
>          Allocate (Integer_Ptr'Storage_Pool, Storage_Address,
> Size_In_Storage_Elements, Alignment);
>          Ada.Text_IO.Put_Line ("allocating " &
>                                Storage_Elements.Storage_Count'Image
> (Size_In_Storage_Elements));
>       end Allocate;
>
>       procedure Deallocate (Pool : in out My_Pool_Type;
>                             Storage_Address : in Address;
>                             Size_In_Storage_Elements : in
> Storage_Elements.Storage_Count;
>                             Alignment : in
> Storage_Elements.Storage_Count) is
>       begin
>          Deallocate (Integer_Ptr'Storage_Pool, Storage_Address,
> Size_In_Storage_Elements, Alignment);
>          Ada.Text_IO.Put_Line ("deallocating");
>       end Deallocate;
>
>       function Storage_Size (Pool : in My_Pool_Type) return
> Storage_Elements.Storage_Count is
>       begin
>          return Storage_Size (Integer_Ptr'Storage_Pool);
>       end Storage_Size;
>
>       My_Pool : My_Pool_Type;
>
>       type My_Type_Ptr is access My_Type;
>       for My_Type_Ptr'Storage_Pool use My_Pool;
>
>       P : My_Type_Ptr;
>
>    begin
>       Ada.Text_IO.Put_Line ("starting to allocate objects");
>       P := new My_Type;
>       P := new My_Type;
>       Ada.Text_IO.Put_Line ("finished with objects and leaving the
> scope");
>    end Test;
>
> begin
>    Ada.Text_IO.Put_Line ("Running test");
>    Test;
>    Ada.Text_IO.Put_Line ("Test finished");
> end Main;
>
> (the code has lengthy lines and can get broken in your readers)
>
> In the above example I tried to create my own pool that is backed up
> by the standard pool acquired from some dummy access type. The new
> pool just forwards all primitive operations to the standard pool and
> prints some messages to see what's going on.
> It looks like I got it correctly, because it compiles and even works:
>
> Running Test
> Starting To Allocate Objects
> Allocating  12
> Allocating  12
> Finished With Objects And Leaving The Scope
> Finalize
> Finalize
> Test Finished
>
> I understand (now) that two objects are allocated with the help of my
> own pool. Since nobody deallocated explicitly (no
> Unchecked_Deallocation was used), the Deallocate procedure was not
> called and the memory is effectively leaked. I could have remembered
> the allocated addresses and deallocate the dangling blocks in the
> finalizer of my own pool (it is controlled after all) and this way the
> pool would guard the memory and prevent the leak. It didn't, so
> effectively there is a leak.
> More precisely, the allocated blocks come from the standard pool and
> they will be deallocated when the whole program will finish.
>
> The interesting part is that finalizers of My_Type were called and
> this is even before the pool gets finalized itself (I've checked it by
> overriding Finalize for My_Pool_Type). This is good, because after
> that only raw memory remained to be reclaimed.
> The question is - who called the finalizers of allocated objects? Any
> AARM paragraphs for this would be welcome.

As Randy pointed out, 7.6.1(11) is the key; it discusses when objects
created by an <allocator> are finalized.  It also says that those
objects are finalized in an arbitrary order.

>
> > > Another question relates to the order of finalizing objects. If the
> > > storage pool is torn down when the access type goes out of scope, is
> > > the order of finalizing objects guaranteed?
>
> > AFAIK, it is not. Why should it be?
>
> Why not? :-)
>
> If not, then there is additional (automatic) question: how can I make
> it guaranteed? Since finalizers seem to be called by some magic, then
> I don't think there is any way for me to influence the behaviour here
> - at least not by providing my own pool, which does not even get a
> chance to have anything to say on this subject.

I'm not completely clear on what you are trying to accomplish.  Are
you trying to ensure that if you allocate a number of objects of a
controlled type, then when those objects are finalized, you want them
to be finalized in a particular order, for example in the reverse
order in which you allocated them?

If this is what you want, you'll need to invent your own solution, but
I think it's doable.  I can envision an implementation where all the
allocated records of a type are linked together in a list; then when
one of these is finalized according to 7.6.1(11), the Finalize routine
recognizes this and finalizes *all* of the objects in the linked list,
perhaps setting a flag in the records to indicate that the
finalization took place.  Then when the other objects are finalized
according to 7.6.1(11), the Finalize routine would recognize that the
finalization has already been done, and do nothing.  If you do this,
you can certainly control the order in which allocated objects are
finalized.  But I think something like this has to be done.  There's
nothing in the language that I  know of to assert any control over the
order in which allocated objects are finalized.

>
> There is a related problem which I've found while playing with the
> above code example: I cannot have a pointer to the standard pool.
> In other words, this:
>
> type Pool_Ptr is access Root_Storage_Pool'Class;
> PP : Pool_Ptr := Integer'Storage_Pool'Access;
>
> is not possible, because the standard storage pool is not aliased.
> How can I get a reference to the standard pool so that I don't have to
> repeat the "Integer'Storage_Pool" million times?
> What about this:
>
> P : Root_Storage_Pool'Class := Integer_Ptr'Storage_Pool;
>
> I expect that P will be a reference to the standard pool, not its copy
> (it cannot be a copy, because Root_Storage_Pool is limited). Is this
> reasoning correct?

No.  Declaring an object of a limited type does *not* create a
reference; it creates an object.  Here, the compiler will think you're
trying to make a copy of a limited object, and it will complain.  Try
"renames".


> Last but not least: does AARM 13.11/20 mean that the above program is
> incorrect? How to implement it correctly, then?

You're probably OK in this particular case, since you're simply
passing through the arguments that the compiler is passing to another
Deallocate.  But the potential for trouble is that when the
implementation generates code for an <allocator> or for a call to an
Unchecked_Deallocation instance, it may do other things besides call
Allocate or Deallocate, and a direct call to Allocate or Deallocate
doesn't do those things.  It seems unlikely to me that, for an
implementation, those "other things" would do anything that involves
the storage pool, which means that this pass-through usage should be
fine.  But I could be wrong.

                                  -- Adam



  reply	other threads:[~2008-01-28 23:54 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-01-28 13:49 Allocators and memory reclamation Maciej Sobczak
2008-01-28 14:52 ` gpriv
2008-01-28 14:53 ` Lucretia
2008-01-28 16:00   ` gpriv
2008-01-28 22:46     ` Alex R. Mosteo
2008-01-28 15:15 ` Dmitry A. Kazakov
2008-01-28 22:27   ` Maciej Sobczak
2008-01-28 23:54     ` Adam Beneschan [this message]
2008-01-29  9:38     ` Dmitry A. Kazakov
2008-01-28 23:07   ` Randy Brukardt
2008-01-28 22:00 ` Aurele
2008-01-29  0:41 ` Robert A Duff
2008-01-29 11:12   ` Maciej Sobczak
  -- strict thread matches above, loose matches on Subject: below --
2008-01-29 11:06 Grein, Christoph (Fa. ESG)
2008-01-29 12:50 ` Dmitry A. Kazakov
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox