www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Use of mutex in destructors

reply "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
Since an OpenGL context is owned by one thread, any OpenGL calls 
made from other threads will fail. I've wrapped OpenGL 'objects' 
in D classes to automate destruction of the underlying object:

this() {
     // Init texture
}

~this() {
     glDeleteTextures(1, &_handle);
}

This will blow up if the GC runs in it's own thread. As a 
solution, I was planning for each wrapper instance to have a 
reference to an OpenGL command queue unique to the context which 
owns the object. This queue will be emptied once per frame. The 
texture wrapper will change to

this(CommandQueue comQueue) {
     this.comQueue = comQueue;
     comQueue ~= { /* Init texture */ }
}

~this() {
     this.comQueue ~= { /* Delete texture */ }
}

Putting the initialization in a command allows for asynchronous 
loading of resources, which is a nice bonus. However, access to 
this queue should be synchronized. I haven't used synchronized in 
D yet so I'd like to know what happens in the following situation:

One thread is processing the command queue. Due to some 
allocation by code in a command or on another thread, the GC 
runs. The processing thread still holds the lock on the queue 
mutex when the GC blocks all threads. The GC runs the destructor 
of a resource, so it waits for the lock on the queue to be 
released and we have a deadlock.

Does the GC somehow avoid this? If not, what's the best solution 
to this problem?
Apr 27 2012
next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 27.04.2012 15:15, Rene Zwanenburg wrote:
 Since an OpenGL context is owned by one thread, any OpenGL calls made
 from other threads will fail. I've wrapped OpenGL 'objects' in D classes
 to automate destruction of the underlying object:

hierarchy or interfaces in there. [snip] -- Dmitry Olshansky
Apr 27 2012
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 27.04.2012 15:53, Simen Kjaeraas wrote:
 On Fri, 27 Apr 2012 13:23:00 +0200, Dmitry Olshansky
 <dmitry.olsh gmail.com> wrote:

 On 27.04.2012 15:15, Rene Zwanenburg wrote:
 Since an OpenGL context is owned by one thread, any OpenGL calls made
 from other threads will fail. I've wrapped OpenGL 'objects' in D classes
 to automate destruction of the underlying object:

hierarchy or interfaces in there. [snip]

http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.

Boom! Just LOL... Use manual memory management or ref-counting. If std.container.Array was not so bogus I'd recommend it for arrays. -- Dmitry Olshansky
Apr 27 2012
prev sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 27.04.2012 16:54, Rene Zwanenburg wrote:
[snip]
 True, but what would using structs do to fix the problem?

 The reason I'm using (final) classes is because a resource can be used
 by multiple objects. If two meshes use the same texture, I don't want to
 load that texture twice.

Don't. If I still worth anything in GL texture is bound to an opaque uint of sorts. There is no problem copying this number around. There is no point in holding multiple references to 42 ;) Just copy the number. Reference to class is cleaner than pointer to
 struct.

Use struct as reference-like type with some alias this magic you may also gain interesting properties. This way you can hide inside a delay-loading scheme of sorts. Say counting usage rates then once resource usage hits a margin you go through the whole list or resources releasing those that are rarely used. Also let us not forget that each class instance holds monitor object. Even though it's lazying initialized it still occupies extra space. -- Dmitry Olshansky
Apr 27 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Fri, 27 Apr 2012 13:23:00 +0200, Dmitry Olshansky  
<dmitry.olsh gmail.com> wrote:

 On 27.04.2012 15:15, Rene Zwanenburg wrote:
 Since an OpenGL context is owned by one thread, any OpenGL calls made
 from other threads will fail. I've wrapped OpenGL 'objects' in D classes
 to automate destruction of the underlying object:

hierarchy or interfaces in there. [snip]

http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.
Apr 27 2012
prev sibling next sibling parent "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
On Friday, 27 April 2012 at 11:23:06 UTC, Dmitry Olshansky wrote:
 On 27.04.2012 15:15, Rene Zwanenburg wrote:
 Since an OpenGL context is owned by one thread, any OpenGL 
 calls made
 from other threads will fail. I've wrapped OpenGL 'objects' in 
 D classes
 to automate destruction of the underlying object:

a hierarchy or interfaces in there. [snip]

True, but what would using structs do to fix the problem? The reason I'm using (final) classes is because a resource can be used by multiple objects. If two meshes use the same texture, I don't want to load that texture twice. Reference to class is cleaner than pointer to struct.
Apr 27 2012
prev sibling next sibling parent "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
On Friday, 27 April 2012 at 11:53:37 UTC, Simen Kjaeraas wrote:
 On Fri, 27 Apr 2012 13:23:00 +0200, Dmitry Olshansky 
 <dmitry.olsh gmail.com> wrote:

 On 27.04.2012 15:15, Rene Zwanenburg wrote:
 Since an OpenGL context is owned by one thread, any OpenGL 
 calls made
 from other threads will fail. I've wrapped OpenGL 'objects' 
 in D classes
 to automate destruction of the underlying object:

a hierarchy or interfaces in there. [snip]

http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.

Good to know, that's quite a serious bug and is open for three years now. Is it that hard to fix?
Apr 27 2012
prev sibling next sibling parent "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
On Friday, 27 April 2012 at 12:03:18 UTC, Dmitry Olshansky wrote:
 Boom! Just LOL...

 Use manual memory management or ref-counting. If 
 std.container.Array was not so bogus I'd recommend it for 
 arrays.

Yeah, I already have a few different types of smart pointer which can work with custom allocators. The main reason I don't use them right now is that there are a few bugs with storing structs in AA's, breaking reference counting.
Apr 27 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 27 Apr 2012 08:57:52 -0400, Rene Zwanenburg  
<renezwanenburg gmail.com> wrote:

 On Friday, 27 April 2012 at 11:53:37 UTC, Simen Kjaeraas wrote:
 On Fri, 27 Apr 2012 13:23:00 +0200, Dmitry Olshansky  
 <dmitry.olsh gmail.com> wrote:

 On 27.04.2012 15:15, Rene Zwanenburg wrote:
 Since an OpenGL context is owned by one thread, any OpenGL calls made
 from other threads will fail. I've wrapped OpenGL 'objects' in D  
 classes
 to automate destruction of the underlying object:

hierarchy or interfaces in there. [snip]

http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.

Good to know, that's quite a serious bug and is open for three years now. Is it that hard to fix?

Yes it's hard. The GC has no access to compile-time type information. It relies on runtime information. The GC is able to call the dtor for classes because classes store a pointer to their typeinfo in the class instance itself (needed for virtual functions). But since structs do not have virtual functions, and many times they are POD, this is not feasible. The allocator could technically store the type info in the memory block, but it doesn't. Strides recently have been made to make the GC more precise, and in that effort, a path to solving this problem has been opened up. I suspect with precise GC work, this problem will be solved as a side-effect. Maybe 6 months off, depending on how fervently someone tries to add precise scanning ;) -Steve
Apr 27 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Fri, 27 Apr 2012 14:57:52 +0200, Rene Zwanenburg  
<renezwanenburg gmail.com> wrote:

 On Friday, 27 April 2012 at 11:53:37 UTC, Simen Kjaeraas wrote:
 On Fri, 27 Apr 2012 13:23:00 +0200, Dmitry Olshansky  
 <dmitry.olsh gmail.com> wrote:

 On 27.04.2012 15:15, Rene Zwanenburg wrote:
 Since an OpenGL context is owned by one thread, any OpenGL calls made
 from other threads will fail. I've wrapped OpenGL 'objects' in D  
 classes
 to automate destruction of the underlying object:

hierarchy or interfaces in there. [snip]

http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.

Good to know, that's quite a serious bug and is open for three years now. Is it that hard to fix?

It's not necessarily hard to fix, but the simple fixes are costly in other ways - allocate a tag block for each alloation or adjust some data structure in the GC to point to the destructor - each of these makes allocation slower, which is unacceptable to some (I'm just allocating POD struct! Why do I have to pay so that guy over there can have his data destroyed in an orderly manner?) But as Steve said, there is work on a precise GC that will likely fix the problem.
Apr 27 2012
prev sibling next sibling parent "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
On Friday, 27 April 2012 at 13:13:20 UTC, Steven Schveighoffer 
wrote:
 Yes it's hard.  The GC has no access to compile-time type 
 information.  It relies on runtime information.  The GC is able 
 to call the dtor for classes because classes store a pointer to 
 their typeinfo in the class instance itself (needed for virtual 
 functions).

 But since structs do not have virtual functions, and many times 
 they are POD, this is not feasible.  The allocator could 
 technically store the type info in the memory block, but it 
 doesn't.  Strides recently have been made to make the GC more 
 precise, and in that effort, a path to solving this problem has 
 been opened up.

 I suspect with precise GC work, this problem will be solved as 
 a side-effect.  Maybe 6 months off, depending on how fervently 
 someone tries to add precise scanning ;)

 -Steve

I see, that makes sense. Thanks. I'm storing pointers to heap allocated structs in an AA to work around issue 6178. I'll wrap them in a class instead, that should work.
Apr 27 2012
prev sibling next sibling parent "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
On Friday, 27 April 2012 at 13:04:32 UTC, Dmitry Olshansky wrote:
 Don't. If I still worth anything in GL texture is bound to an 
 opaque uint of sorts. There is no problem copying this number 
 around.
 There is no point in holding multiple references to 42 ;) Just 
 copy the number.

That's correct. The problem is that if I copy the handle, I don't know when to release the texture. If the application was level based I could keep a list of all used resources for that level, and release them all when the level is destroyed. But an environment is usually quite large, so I need to support streaming. Handling destruction in the destructor end let the GC figure out the rest seemed like a nice solution :P.
 Use struct as reference-like type with some alias this magic 
 you may also gain interesting properties. This way you can hide 
 inside a delay-loading scheme of sorts.
 Say counting usage rates then once resource usage hits a margin 
 you go through the whole list or resources releasing those that 
 are rarely used.

I'm using something commonly known as an entity-component system. There are a lot of dependencies between entities and components, components and components owned by the same or another entity, and who knows what else ;). Using the GC there simplifies things a lot. Components containing reference counted OpenGL resources would still call the destructor on the GC thread. I _could_ modify the system to use ref counting everywhere, but I'm reluctant to do that. Which reminds me, does the GC actually block all threads while calling the destructors on garbage? I'm far from an expert on GC's, but I believe the mark needs to stop the world, but the sweep can be done concurrently. If the GC thread calls destructors while the other threads aren't waiting, there shouldn't be a problem.
 Also let us not forget that each class instance holds monitor 
 object. Even though it's lazying initialized it still occupies 
 extra space.

That's good to keep in mind, but I'm not concerned about memory usage in this case. The size of a texture, vertex buffer or whatever dwarfs the size of the monitor. Total overhead should only be a few kilobytes.
Apr 27 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 27 Apr 2012 11:55:02 -0400, Rene Zwanenburg  
<renezwanenburg gmail.com> wrote:

 Which reminds me, does the GC actually block all threads while calling  
 the destructors on garbage? I'm far from an expert on GC's, but I  
 believe the mark needs to stop the world, but the sweep can be done  
 concurrently. If the GC thread calls destructors while the other threads  
 aren't waiting, there shouldn't be a problem.

AFAIK, this is how it works (world is resumed during sweep). Otherwise, you would have deadlocks all over the place where you used mutexes in dtors! Given that arbitrary C library calls may lock something somewhere, there is no way to avoid this. The one thing you *can't* do is allocate GC memory inside a dtor. -Steve
Apr 27 2012
prev sibling next sibling parent "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
On Friday, 27 April 2012 at 15:59:50 UTC, Steven Schveighoffer 
wrote:
 On Fri, 27 Apr 2012 11:55:02 -0400, Rene Zwanenburg 
 <renezwanenburg gmail.com> wrote:

 Which reminds me, does the GC actually block all threads while 
 calling the destructors on garbage? I'm far from an expert on 
 GC's, but I believe the mark needs to stop the world, but the 
 sweep can be done concurrently. If the GC thread calls 
 destructors while the other threads aren't waiting, there 
 shouldn't be a problem.

AFAIK, this is how it works (world is resumed during sweep). Otherwise, you would have deadlocks all over the place where you used mutexes in dtors! Given that arbitrary C library calls may lock something somewhere, there is no way to avoid this. The one thing you *can't* do is allocate GC memory inside a dtor. -Steve

Great, thanks. That'll save me a lot a trouble. I've run a few times in the 'dtor allocating memory' problem, but it's usually easy enough to work around. One more question: Is that a limitation of the current GC implementation, or something intrinsic to garbage collection in general?
Apr 27 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Fri, 27 Apr 2012 17:55:02 +0200, Rene Zwanenburg  
<renezwanenburg gmail.com> wrote:

 I _could_ modify the system to use ref counting everywhere, but I'm
 reluctant to do that.

You wouldn't really need to. Only the texture struct would need that. Look to std.typecons's RefCounted[1] for inspiration. One could easily imagine and RAII version of that, which would take as a template parameter a function that would run in the destructor. Simple implementation (I'm lazy): import std.typecons : Tuple; import std.traits : hasIndirections; import std.algorithm : swap, move; import core.stdc.stdlib : malloc, free; import std.conv : emplace; struct RefCountedDestructor(T, alias destructor = {}) if (!is(T == class)) { Tuple!(T, "_payload", size_t, "_count")* _store; this(A...)(A args) { auto sz = (*_store).sizeof; auto p = malloc(sz)[0..sz]; static if (hasIndirections!T) { GC.addRange(p.ptr, sz); } emplace(cast(T*)p.ptr, args); _store = cast(typeof(_store))p.ptr; _store._count = 1; } this(this) { _store._count++; } ~this() { assert(_store._count > 0); if (--_store._count) { return; } destructor(); clear(_store._payload); static if (hasIndirections!T) { GC.removeRange(_store); } _store = null; } void opAssign(typeof(this) rhs) { swap(_store, rhs._store); } void opAssign(T rhs) { _store._payload = move(rhs); } property ref T payload( ) { return _store._payload; } property const ref const(T) payload( ) { return _store._payload; } alias payload this; } Usage: void main() { auto a = RefCountedDestructor!(int, {writeln("Goodbye, cruel world")})(4); }
Apr 27 2012
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 27 Apr 2012 12:16:46 -0400, Rene Zwanenburg  
<renezwanenburg gmail.com> wrote:

 I've run a few times in the 'dtor allocating memory' problem, but it's  
 usually easy enough to work around. One more question: Is that a  
 limitation of the current GC implementation, or something intrinsic to  
 garbage collection in general?

I think it's a current GC limitation, but I'm not sure it will get fixed any time soon. There are so many better ways we can improve the GC :) -Steve
Apr 27 2012