Jump to page: 1 2
Thread overview
GC BUG: Referenced object destroyed before released
Mar 16, 2008
Koroskin Denis
Mar 16, 2008
Vladimir Panteleev
Mar 16, 2008
Koroskin Denis
Mar 16, 2008
Christopher Wright
Mar 16, 2008
Vladimir Panteleev
Mar 17, 2008
Christopher Wright
Mar 16, 2008
Graham St Jack
Mar 16, 2008
Koroskin Denis
Mar 18, 2008
Graham St Jack
Mar 17, 2008
Bill Baxter
Mar 17, 2008
Graham St Jack
Mar 17, 2008
Frits van Bommel
Mar 17, 2008
Graham St Jack
Mar 18, 2008
Graham St Jack
Mar 17, 2008
Vladimir Panteleev
Mar 17, 2008
Koroskin Denis
March 16, 2008
Hello, community!
Consider the following code:

// For those who prefer alternative reality ;)
version(Tango) extern(C) void printf(char[] s, ...);

class Resource
{
    private char[] data;
    private bool released;

    this()
    {
        this.data = null;
        this.released = false;
    }

    ~this()
    {
        printf("Resource destroyed\n");
    }

    void release()
    {
        released = true;
    }
}

class Owner
{
    private Resource res;

    this(Resource res) {
        this.res = res;
    }

    ~this()
    {
        res.release();
        printf("Owner destroyed\n");
    }
}

int main()
{
    Resource res = new Resource();
    Owner owner = new Owner(res);

    printf("Main exit\n");
    return 0;
}

In this code we don't explicitly destroy anything, so GC chooses the order of object destruction. And it chooses wrong.

"Phobos hack" shows the following stack trace upon exit:

Main exit
Resource destroyed
Unhandled win32 exception!
Error: Access Violation (object.Exception)
backtrace:
 00402087 void crash.Owner._dtor() (+f) crash.d:35
 0040262a _d_callfinalizer (+46)
 004025dd void std.gc.new_finalizer(void*, bool) (+9)
 004066c3 void gcx.GC.fullCollectNoStack() (+3b)
 0040226f gc_term (+b)
 00415b6d mainCRTStartup (+a9)
 7c816d4f ???

or without it:

Main exit
Resource destroyed
Error: Access Violation

or with Tango:

object.Exception: Access Violation
tango.core.Exception.FinalizeException: An exception was thrown while finalizing an instance of class crash.Resource

object.Exception: Access Violation

Was there any bug report filled on this bug? It's very annoying, since it makes your program not reliable in runtime. At least, if it was a compilation bug, and I managed to bypass it, I would be assured, that the resulted program whould behave predictable. Yet, it is not...
March 16, 2008
On Sun, 16 Mar 2008 15:45:14 +0200, Koroskin Denis <2korden@gmail.com> wrote:

> In this code we don't explicitly destroy anything, so GC chooses the order of object destruction. And it chooses wrong.

Quoting from http://www.digitalmars.com/d/1.0/class.html#destructors :

> Furthermore, the order in which the garbage collector calls destructors for unreference objects is not specified.

So, it's not a bug :) You can't rely on the order of which objects will be destroyed.

-- 
Best regards,
 Vladimir                          mailto:thecybershadow@gmail.com
March 16, 2008
On Sun, 16 Mar 2008 17:15:36 +0300, Vladimir Panteleev <thecybershadow@gmail.com> wrote:

> Quoting from http://www.digitalmars.com/d/1.0/class.html#destructors :
>
>> Furthermore, the order in which the garbage collector calls destructors for unreference objects is not specified.
>
> So, it's not a bug :) You can't rely on the order of which objects will be destroyed.
>

Yes:
> order in which the garbage collector calls destructors for /unreferenced/ objects is not specified.

However, in this particular example Resource _is_ a referenced object, unlike Owner, which is not.
In any case, is this code wrong? If not, why does it cause acess violation?
March 16, 2008
Koroskin Denis wrote:
> On Sun, 16 Mar 2008 17:15:36 +0300, Vladimir Panteleev <thecybershadow@gmail.com> wrote:
> 
>> Quoting from http://www.digitalmars.com/d/1.0/class.html#destructors :
>>
>>> Furthermore, the order in which the garbage collector calls destructors for unreference objects is not specified.
>>
>> So, it's not a bug :) You can't rely on the order of which objects will be destroyed.
>>
> 
> Yes:
>> order in which the garbage collector calls destructors for /unreferenced/ objects is not specified.
> 
> However, in this particular example Resource _is_ a referenced object, unlike Owner, which is not.
> In any case, is this code wrong? If not, why does it cause acess violation?

Resource is not referenced, from the garbage collector's point of view.

An object is referenced if there is a path to it from the GC's roots (the stack, registers, and anything else you care to add with std.gc.addRoot or some such). When main exits, there's no reference to Owner, which means there is no way to get to Resource, so it's not referenced.

Getting around this would not be extremely difficult, I think. You could just call all destructors and then collect the memory, which might leave some things in a bad state. Still, if you were careful, you could reference other objects in destructors without segfaulting.

Or, you could build a reference graph from the memory you're collecting, then call destructors based on that graph. This would be pretty slow; I'd support it as the standard, but people would certainly want a way to disable it, since it's unnecessary in many situations.

If you want deterministic destruction, you can do something like this:

interface IFinalizable
{
   void finalize();
}

class MyClass : IFinalizable
{
   // You would use a mixin for this...
   IFinalizable[] finalizeParents, finalizeChildren;
   public void finalize()
   {
      if (finalized) return;
      finalized = true;
      foreach (o; finalizeParents)
      {
         o.finalize;
      }

      destroyThis();

      foreach (o; finalizeChildren)
      {
         o.finalize;
      }
   }

   // And for this.
   ~this()
   {
      finalize();
   }

   // And this would be defined per-class, of course.
   private void destroyThis()
   {
      // Do whatever you need to do.
      // It's safe to use any IFinalizable, but not anything else.
   }
}

The runtime for this is quadratic in the worst case, plus it adds 16 bytes to each object. On the other hand, memory is cheap these days, and in the average case, the runtime's going to be linear.

If you have a cycle of IFinalizables, you get nondeterministic destruction, but no infinite loops, which is the best you could ask for.
March 16, 2008
On Sun, 16 Mar 2008 17:58:40 +0200, Christopher Wright <dhasenan@gmail.com> wrote:

> Getting around this would not be extremely difficult, I think.

You could also write custom class allocators and use non-managed memory for manual memory management:

http://www.digitalmars.com/d/1.0/class.html#allocators

-- 
Best regards,
 Vladimir                          mailto:thecybershadow@gmail.com
March 16, 2008
On Sun, 16 Mar 2008 16:45:14 +0300, Koroskin Denis wrote:

> Hello, community!
> Consider the following code:
> 
> // For those who prefer alternative reality ;) version(Tango) extern(C)
> void printf(char[] s, ...);
> 
> class Resource
> {
>      private char[] data;
>      private bool released;
> 
>      this()
>      {
>          this.data = null;
>          this.released = false;
>      }
> 
>      ~this()
>      {
>          printf("Resource destroyed\n");
>      }
> 
>      void release()
>      {
>          released = true;
>      }
> }
> 
> class Owner
> {
>      private Resource res;
> 
>      this(Resource res) {
>          this.res = res;
>      }
> 
>      ~this()
>      {
>          res.release();
>          printf("Owner destroyed\n");
>      }
> }
> 
> int main()
> {
>      Resource res = new Resource();
>      Owner owner = new Owner(res);
> 
>      printf("Main exit\n");
>      return 0;
> }
> 
> In this code we don't explicitly destroy anything, so GC chooses the order of object destruction. And it chooses wrong.
> 
> "Phobos hack" shows the following stack trace upon exit:
> 
> Main exit
> Resource destroyed
> Unhandled win32 exception!
> Error: Access Violation (object.Exception) backtrace:
>   00402087 void crash.Owner._dtor() (+f) crash.d:35 0040262a
>   _d_callfinalizer (+46)
>   004025dd void std.gc.new_finalizer(void*, bool) (+9) 004066c3 void
>   gcx.GC.fullCollectNoStack() (+3b) 0040226f gc_term (+b)
>   00415b6d mainCRTStartup (+a9)
>   7c816d4f ???
> 
> or without it:
> 
> Main exit
> Resource destroyed
> Error: Access Violation
> 
> or with Tango:
> 
> object.Exception: Access Violation tango.core.Exception.FinalizeException: An exception was thrown while finalizing an instance of class crash.Resource
> 
> object.Exception: Access Violation
> 
> Was there any bug report filled on this bug? It's very annoying, since it makes your program not reliable in runtime. At least, if it was a compilation bug, and I managed to bypass it, I would be assured, that the resulted program whould behave predictable. Yet, it is not...

I agree that this is a problem. Who cares if the two objects in question aren't referenced and more - their destructors should be called in the right order if it is possible to do so.

I guess the work-around (and maybe permanent) is to not reference any garbage-collected objects in your destructors.

March 16, 2008
On Mon, 17 Mar 2008 01:28:04 +0300, Graham St Jack <grahams@acres.com.au> wrote:
> I agree that this is a problem. Who cares if the two objects in question
> aren't referenced and more - their destructors should be called in the
> right order if it is possible to do so.

Yep.

> I guess the work-around (and maybe permanent) is to not reference any
> garbage-collected objects in your destructors.
>

Nope. This is against RAII pattern.
March 17, 2008
Graham St Jack wrote:

> I guess the work-around (and maybe permanent) is to not reference any garbage-collected objects in your destructors. 

Err, that's more or less what the documentation says.

"""
When the garbage collector calls a destructor for an object of a class that has members that are references to garbage collected objects, those references are no longer valid. This means that destructors cannot reference sub objects.
"""
-- http://www.digitalmars.com/d/1.0/class.html#destructors

Maybe it would be more precise to say "This means destructors cannot reference garbage collected objects".

--bb
March 17, 2008
On Mon, 17 Mar 2008 00:28:04 +0200, Graham St Jack <grahams@acres.com.au> wrote:

> I agree that this is a problem. Who cares if the two objects in question aren't referenced and more - their destructors should be called in the right order if it is possible to do so.

This is impossible without an exact garbage collector.

Also: circular references.

-- 
Best regards,
 Vladimir                          mailto:thecybershadow@gmail.com
March 17, 2008
On Mon, 17 Mar 2008 09:45:30 +0900, Bill Baxter wrote:

> Graham St Jack wrote:
> 
>> I guess the work-around (and maybe permanent) is to not reference any garbage-collected objects in your destructors.
> 
> Err, that's more or less what the documentation says.
> 
> """
> When the garbage collector calls a destructor for an object of a class
> that has members that are references to garbage collected objects, those
> references are no longer valid. This means that destructors cannot
> reference sub objects.
> """
> -- http://www.digitalmars.com/d/1.0/class.html#destructors
> 
> Maybe it would be more precise to say "This means destructors cannot reference garbage collected objects".
> 
> --bb

Thanks for the clarification - it clears it up nicely for me.

Another question along these lines - I am having trouble with a multi- threaded program (linux, gdc, phobos). It works fine until main() returns, and then I get a SIGUSR1 that causes the program to crash. Any ideas what might be happening?

My investigations so far show that SIGUSR1 and SIGUSR2 are used in thread.d as part of the implementation of pause() and resume(). I was under the impression that the thread that is interrupted to handle a signal is chosen at random, and therefore that it is a bad idea to try to use signals in a threaded program, so I am confused.

Also, the garbage collector seems to kill off all my threads without waiting for their run methods to return. I assume this means that the threads aren't being treated as root objects by the garbage collector.

Does all this mean that I have to explicitly take action so that the run methods will return, then wait() on them before returning from main()?
« First   ‹ Prev
1 2