GC-proof resource classes

ponce via Digitalmars-d digitalmars-d at puremagic.com
Sat Aug 29 06:14:23 PDT 2015


As a library writer I've struggled a bit with to provide easy 
resource clean-up despite using class objects.

Is there reasons to use class objects that hold resources in the 
first place vs Unique!T or RefCounted!T structs?
I think yes:
- classes have reference semantics without naming or runtime 
overhead: you read Resource and not RefCounted!Resource
- no need to disable the postblit or have a valid .init is 
another thing
That's about it from the top of my head and it may not be good 
reasons!

As you probably know GC-called destructors enjoy a variety of 
limitations:
- can't allocate,
- can't access members,
- aren't guaranteed to happen at all.

This makes GC-called destructors mostly useless for non-memory 
resource release. IIRC Andrei suggested once that destructors 
shouldn't be called at all by the GC, something that I agree with.

As such, some of us started providing a 
release()/dispose()/close() method, and have the destructor call 
it to support both scoped ownership and manual release.

But that introduce accidentally correct design when the 
destructor is called by GC, and avoids a leak. This is arguably 
worse than the initial problem.

It turns out separating calls of ~this() that are called by the 
GC from those that are called for a deterministic reason is 
enough, and support all cases I can think of: 
Unique!T/scoped!T/.destroy/RefCounted!T/manual/GC-called

It works like this:

----------------

class MyResource
{
     void* handle;

     this()
     {
         handle = create_handle();
     }

     ~this()
     {
         if (handle != null) // must support repeated calls for 
the case (called by .destroy + called by GC later)
         {
             ensureNotInGC("MyResource");
             free_handle(handle);
         }
     }
}

----------------

ensureNotInGC() is implemented like this:

----------------

void ensureNotInGC(string resourceName) nothrow
{
     debug
     {
         import core.exception;
         try
         {
             import core.memory;
             void* p = GC.malloc(1); // not ideal since it 
allocates
             return;
         }
         catch(InvalidMemoryOperationError e)
         {
             import core.stdc.stdio;
             fprintf(stderr, "Error: clean-up of %s incorrectly 
depends on destructors called by the GC.\n", resourceName.ptr);
             assert(false); // crash
         }
     }
}

--------------

Looks ugly? Yes, but it makes the GC acts as a cheap leak 
detector, giving accurate messages for still opened resources.




More information about the Digitalmars-d mailing list