www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Please help with GC exception!

reply Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
I have a structure:

private struct Block
{
	this(size_t n) { /* allocate n bytes with GC.malloc */ }
	this(this) { /* deep-copy the bytes */ }
	~this() { /* deallocate them with GC.free */ }
}

And a class:

final class Region
{
	private Block _block;
	alias _block this;
}


This setup allows me to have memory regions, reallocation of which
will never invalidate pointers, because thanks to Region class no-one
holds a direct pointer to the memory.
The problem is, that I get a
core.exception.InvalidMemoryOperationError when my program ends.

shared static ~this() { import core.thread;
Thread.sleep(dur!`seconds`(1)); } // The error message still showed up
after a delay, so it had to be at the process termination
When I delete the Region object with "clear", the error diappears.
Disabling the GC or forcing a collection before the 1-second sleep
doesn't do anything: I still get the error after the 1-second sleep.
The only way to stop the error, besides manually deleting the object
is the remove the deallocation from the Block's destructor.

Can somebody please help me fix this problem?

-- 
Bye,
Gor Gyolchanyan.
May 09 2012
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 09 May 2012 11:28:30 -0400, Gor Gyolchanyan  
<gor.f.gyolchanyan gmail.com> wrote:

 I have a structure:

 private struct Block
 {
 	this(size_t n) { /* allocate n bytes with GC.malloc */ }
 	this(this) { /* deep-copy the bytes */ }
 	~this() { /* deallocate them with GC.free */ }
 }

 And a class:

 final class Region
 {
 	private Block _block;
 	alias _block this;
 }


 This setup allows me to have memory regions, reallocation of which
 will never invalidate pointers, because thanks to Region class no-one
 holds a direct pointer to the memory.
 The problem is, that I get a
 core.exception.InvalidMemoryOperationError when my program ends.

 shared static ~this() { import core.thread;
 Thread.sleep(dur!`seconds`(1)); } // The error message still showed up
 after a delay, so it had to be at the process termination
 When I delete the Region object with "clear", the error diappears.
 Disabling the GC or forcing a collection before the 1-second sleep
 doesn't do anything: I still get the error after the 1-second sleep.
 The only way to stop the error, besides manually deleting the object
 is the remove the deallocation from the Block's destructor.

 Can somebody please help me fix this problem?

Yes, you cannot use GC.delete on a member of a class. Ever. The reason is, the memory you are attempting to delete may already be gone, there is no guaranteed order of destruction in a collection cycle. -Steve
May 09 2012
prev sibling next sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
I'm not deleting the member. I'm deleting memory, allocated by the member.
If GC deleted it before the object, the same error would appear when I
forced a GC collection cycle.
Also, docs clearly say, that I'm free to delete the memory myself.
This means, that I shouldn't care if a collection cycle went before my
deletion!

On Wed, May 9, 2012 at 8:07 PM, Steven Schveighoffer
<schveiguy yahoo.com> wrote:
 On Wed, 09 May 2012 11:28:30 -0400, Gor Gyolchanyan
 <gor.f.gyolchanyan gmail.com> wrote:

 I have a structure:

 private struct Block
 {
 =C2=A0 =C2=A0 =C2=A0 =C2=A0this(size_t n) { /* allocate n bytes with GC.=


 =C2=A0 =C2=A0 =C2=A0 =C2=A0this(this) { /* deep-copy the bytes */ }
 =C2=A0 =C2=A0 =C2=A0 =C2=A0~this() { /* deallocate them with GC.free */ =


 }

 And a class:

 final class Region
 {
 =C2=A0 =C2=A0 =C2=A0 =C2=A0private Block _block;
 =C2=A0 =C2=A0 =C2=A0 =C2=A0alias _block this;
 }


 This setup allows me to have memory regions, reallocation of which
 will never invalidate pointers, because thanks to Region class no-one
 holds a direct pointer to the memory.
 The problem is, that I get a
 core.exception.InvalidMemoryOperationError when my program ends.

 shared static ~this() { import core.thread;
 Thread.sleep(dur!`seconds`(1)); } // The error message still showed up
 after a delay, so it had to be at the process termination
 When I delete the Region object with "clear", the error diappears.
 Disabling the GC or forcing a collection before the 1-second sleep
 doesn't do anything: I still get the error after the 1-second sleep.
 The only way to stop the error, besides manually deleting the object
 is the remove the deallocation from the Block's destructor.

 Can somebody please help me fix this problem?

Yes, you cannot use GC.delete on a member of a class. =C2=A0Ever. =C2=A0T=

 the memory you are attempting to delete may already be gone, there is no
 guaranteed order of destruction in a collection cycle.

 -Steve

--=20 Bye, Gor Gyolchanyan.
May 09 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 09 May 2012 12:12:55 -0400, Gor Gyolchanyan  
<gor.f.gyolchanyan gmail.com> wrote:

 I'm not deleting the member. I'm deleting memory, allocated by the  
 member.

The member is a struct, so it's fully contained within the class's block. Therefore, the memory block is a member, even if indirectly so.
 If GC deleted it before the object, the same error would appear when I
 forced a GC collection cycle.
 Also, docs clearly say, that I'm free to delete the memory myself.
 This means, that I shouldn't care if a collection cycle went before my
 deletion!

Here is how it works. This is not your example, it's an even simpler one. Memory block A has a pointer to memory block B. Both are GC allocated. A is an object with a dtor that calls GC.delete(B). Since A has the only pointer to B, B is not removed by the GC as long as A is pointed at. Now, A is no longer pointed at, and a GC collection runs. The GC marks all memory, does not mark A, and therefore, does not mark B, so now A and B are scheduled for deletion. After the mark period, the GC cycles through all deletable (unmarked) memory blocks, and finds *B first*. Since B has no dtor, it's simply deallocated. However, A has a destructor. So first, the runtime runs A.__dtor(), which *again* deletes B. This is where the failure occurs. For this reason, you cannot have a destructor which deletes GC-allocated memory. This is even in the spec. -Steve
May 09 2012
prev sibling next sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
I got your point. Thanks for the reply!
Wouldn't it make more sense for GC to ignore second deallocation?
If this was the case, data, which is know to become garbage would be
deallocated right away.
On the other hand, I might as well use std.c.stdlib.realloc for these cases=
.

On Wed, May 9, 2012 at 8:43 PM, Steven Schveighoffer
<schveiguy yahoo.com> wrote:
 On Wed, 09 May 2012 12:12:55 -0400, Gor Gyolchanyan
 <gor.f.gyolchanyan gmail.com> wrote:

 I'm not deleting the member. I'm deleting memory, allocated by the membe=


 The member is a struct, so it's fully contained within the class's block.
 =C2=A0Therefore, the memory block is a member, even if indirectly so.


 If GC deleted it before the object, the same error would appear when I
 forced a GC collection cycle.
 Also, docs clearly say, that I'm free to delete the memory myself.
 This means, that I shouldn't care if a collection cycle went before my
 deletion!

Here is how it works. =C2=A0This is not your example, it's an even simple=

 Memory block A has a pointer to memory block B. =C2=A0Both are GC allocat=

 is an object with a dtor that calls GC.delete(B). =C2=A0Since A has the o=

 pointer to B, B is not removed by the GC as long as A is pointed at.

 Now, A is no longer pointed at, and a GC collection runs.

 The GC marks all memory, does not mark A, and therefore, does not mark B,=

 now A and B are scheduled for deletion.

 After the mark period, the GC cycles through all deletable (unmarked) mem=

 blocks, and finds *B first*. =C2=A0Since B has no dtor, it's simply deall=

 However, A has a destructor. =C2=A0So first, the runtime runs A.__dtor(),=

 *again* deletes B. =C2=A0This is where the failure occurs.

 For this reason, you cannot have a destructor which deletes GC-allocated
 memory. =C2=A0This is even in the spec.

 -Steve

--=20 Bye, Gor Gyolchanyan.
May 09 2012
prev sibling next sibling parent Kevin Cox <kevincox.ca gmail.com> writes:
--000e0cdff71424ac5a04bf9d6a0a
Content-Type: text/plain; charset=UTF-8

On May 9, 2012 12:53 PM, "Gor Gyolchanyan" <gor.f.gyolchanyan gmail.com>
wrote:
 Wouldn't it make more sense for GC to ignore second deallocation?
 If this was the case, data, which is know to become garbage would be
 deallocated right away.
 On the other hand, I might as well use std.c.stdlib.realloc for these

No, because then the GC could never give out that same location in memory again. --000e0cdff71424ac5a04bf9d6a0a Content-Type: text/html; charset=UTF-8 <p><br> On May 9, 2012 12:53 PM, &quot;Gor Gyolchanyan&quot; &lt;<a href="mailto:gor.f.gyolchanyan gmail.com">gor.f.gyolchanyan gmail.com</a>&gt; wrote:<br> &gt; Wouldn&#39;t it make more sense for GC to ignore second deallocation?<br> &gt; If this was the case, data, which is know to become garbage would be<br> &gt; deallocated right away.<br> &gt; On the other hand, I might as well use std.c.stdlib.realloc for these cases.</p> <p>No, because then the GC could never give out that same location in memory again.</p> --000e0cdff71424ac5a04bf9d6a0a--
May 09 2012
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 09 May 2012 12:52:43 -0400, Gor Gyolchanyan  
<gor.f.gyolchanyan gmail.com> wrote:

 I got your point. Thanks for the reply!
 Wouldn't it make more sense for GC to ignore second deallocation?

The memory may have already been reallocated elsewhere!
 If this was the case, data, which is know to become garbage would be
 deallocated right away.
 On the other hand, I might as well use std.c.stdlib.realloc for these  
 cases.

Do not call C's realloc on GC allocated memory. The best option is to avoid the GC whatsoever for memory that you want to manage that tightly. Use c's malloc and free, which can be called from the dtor, no problem. If the block has pointers to GC memory, remember to addRoot it (and removeRoot it in destructor). -Steve
May 09 2012