Best way to manage non-memory resources in current D, ex: database handles.
Chad Joan via Digitalmars-d-learn
digitalmars-d-learn at puremagic.com
Wed Mar 8 15:54:56 PST 2017
Hello all,
I intend to write a range that iterates over a non-memory
resource.
Incase you want something more concrete:
I'll be using the Windows ODBC API and calling SQLFetch
repeatedly on a statement handle allocated with SQLAllocHandle
and deallocated with SQLFreeHandle.
But in general:
I want to call some function (ex: SQLAllocHandle) at the
beginning of my range's lifetime and call another function (ex:
SQLFreeHandle) at the end of my range's lifetime. Unlike memory
resources, reference cycles are unlikely (or impossible), and it
is highly desirable to free the resource as soon as possible
(deterministic allocation/deallocation).
What's the best way to implement such a range in current D?
It seems things have changed since back when I used D and we used
the scope attribute on a variable or struct/class declaration to
at least mostly fake it (with limitations, of course).
I'm seeing these possibilities so far:
1.) Use std.typecons's Unique or RefCounted templates.
2.) Put a deallocate() method on my range struct/class. Call it
in a scope(exit) statement.
3.) Use a struct for my range. Put a deallocate() method. Call
deallocate() from ~this().
Please let me know if there are other things in the current D
ecosystem intended to solve this problem.
On what I've considered, I'll break my thoughts into sections.
==============================================================
=== (1) Use std.typecons's Unique or RefCounted templates. ===
==============================================================
Sorry if this one's long. Skip it if you don't know how these
are intended to be used.
------ Unique ------
The documentation states about Unique:
"Encapsulates unique ownership of a resource. Resource of type T
is deleted at the end of the scope, unless it is transferred. The
transfer can be explicit, by calling release, or implicit, when
returning Unique from a function. The resource can be a
polymorphic class object, in which case Unique behaves
polymorphically too."
Source: https://dlang.org/phobos/std_typecons.html#.Unique
How does it "delete" the resource T? What method does it call to
tell T to deallocate itself? When I look at the source, it looks
like Unique calls destroy(_p), where _p is presumably the wrapped
instance of T.
The documentation from the destroy function says this:
"Destroys the given object and puts it in an invalid state. It's
used to destroy an object so that any cleanup which its
destructor or finalizer does is done and so that it no longer
references any other objects. It does not initiate a GC cycle or
free any GC memory."
Source: https://dlang.org/library/object/destroy.html
The destroy function doesn't mention what methods specifically
will be executed on the target object, other than "destructor or
finalizer". I don't know what a finalizer is in D and cursory
internet searching brings up nothing. As for destructors, the
docs say that "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 may no longer be
valid." (https://dlang.org/spec/class.html#destructors) I
originally thought that the entire class contents would be
invalid during destruction, but maybe we are lucky, maybe it is
JUST references to GC memory that become invalid.
Is the destructor of a class actually an appropriate place to
deallocate non-memory resources, at least if Unique!T is used, or
is this going to be fragile?
------ RefCounted ------
RefCounted: The documentation says:
"Defines a reference-counted object containing a T value as
payload. RefCounted keeps track of all references of an object,
and when the reference count goes down to zero, frees the
underlying store. RefCounted uses malloc and free for operation.
RefCounted is unsafe and should be used with care. No references
to the payload should be escaped outside the RefCounted object.
... some stuff about autoInit ... "
So I have similar questions about this as I did Unique: How does
it "free" the resource T? What method does it call to tell T to
deallocate itself?
The documentation suggests that it uses malloc/free, which, if I
am guessing right, would mean that the underlying T would have
absolutely no chances to free underlying resources. Its memory
would get free'd and that's EOL for the instance, no mulligans.
The source code seems to suggest that it calls destroy(...) like
Unique does.
Is RefCounted intended to be used with non-memory resources, or
is it strictly a memory manager using malloc/free and reference
counts?
I also worry about RefCounted's unconditional dependency on
mallocator functions (and possibly also the pureGcXxxx
functions). Maybe that is a feature request for another day.
==============================================================
=== (2) Put a deallocate() method; call it in scope(exit) ===
==============================================================
import std.stdio;
struct MyRange
{
static MyRange allocate() {
MyRange r;
writeln("Allocate MyRange.");
return r;
}
void deallocate() { writeln("Deallocate MyRange."); }
}
int foo(bool doThrow)
{
auto a = MyRange.allocate();
scope(exit) a.deallocate();
if ( doThrow )
throw new Exception("!");
else
return 42;
}
void main()
{
foo(false);
foo(true);
}
I'm leaning towards this methodology; it is simple and doesn't
make a lot of assumptions. For this project, my usage should
have pretty straightforward object lifetimes; defining these
lifetimes with function scopes should be sufficient.
I still wonder if there is a better way, especially if I am not
fortunate enough to get easy program requirements in the future.
==============================================================
=== (3) Put a deallocate() method; call it in ~this() ===
==============================================================
import std.stdio;
struct MyRange
{
static MyRange allocate() {
MyRange r;
writeln("Allocate MyRange.");
return r;
}
~this() { deallocate(); }
void deallocate() { writeln("Deallocate MyRange."); }
}
int foo(bool doThrow)
{
auto a = MyRange.allocate();
if ( doThrow )
throw new Exception("!");
else
return 42;
}
void main()
{
foo(false);
foo(true);
}
This seems to work. Any caveats?
I suppose this will have the similar drawbacks to (2) : if the
lifetime of the objects becomes more complicated than function
scope, then this may not work well enough.
==============================================================
Thanks!
- Chad
More information about the Digitalmars-d-learn
mailing list