digitalmars.D.learn - Use of mutex in destructors
- Rene Zwanenburg (33/33) Apr 27 2012 Since an OpenGL context is owned by one thread, any OpenGL calls
- Dmitry Olshansky (6/9) Apr 27 2012 How about using structs for GL objects? It's not like you have a
- Simen Kjaeraas (5/13) Apr 27 2012 http://d.puremagic.com/issues/show_bug.cgi?id=2834
- Dmitry Olshansky (6/21) Apr 27 2012 Boom! Just LOL...
- Rene Zwanenburg (5/9) Apr 27 2012 Yeah, I already have a few different types of smart pointer which
- Rene Zwanenburg (3/20) Apr 27 2012 Good to know, that's quite a serious bug and is open for three
- Steven Schveighoffer (15/37) Apr 27 2012 Yes it's hard. The GC has no access to compile-time type information. ...
- Rene Zwanenburg (6/21) Apr 27 2012 I see, that makes sense. Thanks.
- Simen Kjaeraas (11/33) Apr 27 2012 It's not necessarily hard to fix, but the simple fixes are costly in oth...
- Rene Zwanenburg (6/16) Apr 27 2012 True, but what would using structs do to fix the problem?
- Dmitry Olshansky (16/21) Apr 27 2012 Don't. If I still worth anything in GL texture is bound to an opaque
- Rene Zwanenburg (26/40) Apr 27 2012 That's correct. The problem is that if I copy the handle, I don't
- Steven Schveighoffer (8/13) Apr 27 2012 AFAIK, this is how it works (world is resumed during sweep). Otherwise,...
- Rene Zwanenburg (7/23) Apr 27 2012 Great, thanks. That'll save me a lot a trouble. I've run a few
- Steven Schveighoffer (5/9) Apr 27 2012 I think it's a current GC limitation, but I'm not sure it will get fixed...
- Simen Kjaeraas (64/66) Apr 27 2012 You wouldn't really need to. Only the texture struct would need that.
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
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:How about using structs for GL objects? It's not like you have a hierarchy or interfaces in there. [snip] -- Dmitry Olshansky
Apr 27 2012
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:http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.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:How about using structs for GL objects? It's not like you have a hierarchy or interfaces in there. [snip]
Apr 27 2012
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: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 OlshanskyOn 27.04.2012 15:15, Rene Zwanenburg wrote:http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.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:How about using structs for GL objects? It's not like you have a hierarchy or interfaces in there. [snip]
Apr 27 2012
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
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:Good to know, that's quite a serious bug and is open for three years now. Is it that hard to fix?On 27.04.2012 15:15, Rene Zwanenburg wrote:http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.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:How about using structs for GL objects? It's not like you have a hierarchy or interfaces in there. [snip]
Apr 27 2012
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: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 ;) -SteveOn Fri, 27 Apr 2012 13:23:00 +0200, Dmitry Olshansky <dmitry.olsh gmail.com> wrote:Good to know, that's quite a serious bug and is open for three years now. Is it that hard to fix?On 27.04.2012 15:15, Rene Zwanenburg wrote:http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.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:How about using structs for GL objects? It's not like you have a hierarchy or interfaces in there. [snip]
Apr 27 2012
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 ;) -SteveI 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
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: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.On Fri, 27 Apr 2012 13:23:00 +0200, Dmitry Olshansky <dmitry.olsh gmail.com> wrote:Good to know, that's quite a serious bug and is open for three years now. Is it that hard to fix?On 27.04.2012 15:15, Rene Zwanenburg wrote:http://d.puremagic.com/issues/show_bug.cgi?id=2834 Heap-allocated structs don't have their destructors called when they're collected.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:How about using structs for GL objects? It's not like you have a hierarchy or interfaces in there. [snip]
Apr 27 2012
On Friday, 27 April 2012 at 11:23:06 UTC, Dmitry Olshansky wrote:On 27.04.2012 15:15, Rene Zwanenburg wrote: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.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:How about using structs for GL objects? It's not like you have a hierarchy or interfaces in there. [snip]
Apr 27 2012
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 tostruct.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
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
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
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: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?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
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
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