Thread overview
Can I remove an element from a global associative array from within a class destructor?
Aug 02, 2019
realhet
Aug 03, 2019
Jonathan M Davis
Aug 03, 2019
realhet
Aug 03, 2019
JN
August 02, 2019
Hi,

I tried to make some resource statistict for my OpenGL Buffer objects:


//here are the things that hold the statistics.
private __gshared{ size_t[int] textureSizeMap, bufferSizeMap; }

struct GLCounters{
  int programs, shaders, textures, buffers;
  size_t textureSize, bufferSize;
}
__gshared GLCounters glCounters;

...

//And this is the buffer creation.
int genBuffer(size_t size){
  int res; glGenBuffers (1, &res); glChk;
  glCounters.buffers ++;
  glCounters.bufferSize  += size;
  bufferSizeMap [res] = size;
  writefln("buffer allocated %d %d", res, size);
  return res;
}

//Finally, this is the deallocation. If it is called from the GC (when it destroys a class), it has a big chance to throw an Invalid Memory Operation exception.

void deleteBuffer (int handle){
  if(!handle) return;
  glDeleteBuffers (1, &handle); glChk;
  glCounters.buffers --;
  glCounters.bufferSize  -= bufferSizeMap [handle];
  writefln("buffer deallocated %d %d", handle, bufferSizeMap [handle]);
  bufferSizeMap.remove(handle); <- this is the problematic part.
}

--------------------------------------------
Today I read the documentation about structs, unions and classes, but I haven't find any restrictions for the ~this() destructors.

Is there some extra rules regarding the GC and what I must not do in the destructors?

I think the destructor always called in the same thread where the instance was created. This can't be the case.
But what I can guess is: The GC makes a collection and calls my destructor and inside I do something and the GC calls a collection again recursively. But it seems a bit crazy, so I think it's not the case :D

Please help me solve this!

*I'm using LDC 1.6.0 Win64


August 02, 2019
On Friday, August 2, 2019 5:13:10 PM MDT realhet via Digitalmars-d-learn wrote:
> Hi,
>
> I tried to make some resource statistict for my OpenGL Buffer objects:
>
>
> //here are the things that hold the statistics.
> private __gshared{ size_t[int] textureSizeMap, bufferSizeMap; }
>
> struct GLCounters{
>    int programs, shaders, textures, buffers;
>    size_t textureSize, bufferSize;
> }
> __gshared GLCounters glCounters;
>
> ...
>
> //And this is the buffer creation.
> int genBuffer(size_t size){
>    int res; glGenBuffers (1, &res); glChk;
>    glCounters.buffers ++;
>    glCounters.bufferSize  += size;
>    bufferSizeMap [res] = size;
>    writefln("buffer allocated %d %d", res, size);
>    return res;
> }
>
> //Finally, this is the deallocation. If it is called from the GC (when it destroys a class), it has a big chance to throw an Invalid Memory Operation exception.
>
> void deleteBuffer (int handle){
>    if(!handle) return;
>    glDeleteBuffers (1, &handle); glChk;
>    glCounters.buffers --;
>    glCounters.bufferSize  -= bufferSizeMap [handle];
>    writefln("buffer deallocated %d %d", handle, bufferSizeMap
> [handle]);
>    bufferSizeMap.remove(handle); <- this is the problematic part.
> }
>
> --------------------------------------------
> Today I read the documentation about structs, unions and classes, but I haven't find any restrictions for the ~this() destructors.
>
> Is there some extra rules regarding the GC and what I must not do in the destructors?
>
> I think the destructor always called in the same thread where the
> instance was created. This can't be the case.
> But what I can guess is: The GC makes a collection and calls my
> destructor and inside I do something and the GC calls a
> collection again recursively. But it seems a bit crazy, so I
> think it's not the case :D
>
> Please help me solve this!
>
> *I'm using LDC 1.6.0 Win64

As I understand it, you can't do much of anything that involves the GC inside a destructor that's being run as a finalizer by the GC (as is almost always the case with classes). Structs or classes on the stack (which for classes requires something like scope) don't have that problem, because they're not being run by the GC. But if a struct or class is on the GC heap, then attempting to allocate or deallocate GC resources doesn't work, because the destructor/finalizer runs when the GC is doing a collection. Even trying to use other objects that are on the GC heap from a finalizer is not a allowed, because there is no guarantee about the order that the objects are collected (otherwise, cyclical references would be a big problem), meaning that references in the finalizer could refer to objects that have already been destroyed and their memory freed.

https://dlang.org/spec/class.html#destructors

Basically, destructors/finalizers for anything on the GC heap are just for cleaning up non-GC-allocated resources.

- Jonathan M Davis



August 03, 2019
On Saturday, 3 August 2019 at 05:33:05 UTC, Jonathan M Davis wrote:
> On Friday, August 2, 2019 5:13:10 PM MDT realhet via Digitalmars-d-learn wrote:
>> Hi,
>> ...

Thank you!

Now I have 2 solutions in mind:
1. If I only want to track the count and totalBytes for a specific kind of reference, I will be able update those from the destructor.
2. If I wanna track all instances of resources, I will use 2 classes instead of one class and an associative array:
- When I create a resource, I create class1 for the statistics, and store them in an assocArray. Then I create a class2 and inside it there is a reference to class1. Class2 will be given to the user. If the user don't use it anymore, the GC will destroy class2, and in it's destructor it will mark a field in class1. And it will be periodically checked, so the actual deletion of the GLBuffer and other statistics will be done by me.
With millions of resources it will be slow, but for a few thousand it's ok. (I can also put them in a hierarchy, but the most important is not to alloc/dealloc from ~this())

I hope it will be stable :D

Thank You again!


August 03, 2019
On Friday, 2 August 2019 at 23:13:10 UTC, realhet wrote:
> Today I read the documentation about structs, unions and classes, but I haven't find any restrictions for the ~this() destructors.
>
> Is there some extra rules regarding the GC and what I must not do in the destructors?

Class destructors are similar to Java's finalizers in behavior. D doesn't give you a guarantee when the class destructor is called and it doesn't give you a guarantee if it will be called at all, so it doesn't really work for RAII-like resource management. If you want RAII-like behavior, either use structs (struct destructors are deterministic), or wrap your class inside a scoped/refCounted wrapper from std.typecons.