Thread overview
How can I signal a master object that a resource handle is no longer used, from within the resource class's destructor?
6 days ago
realhet
2 days ago
realhet
6 days ago

Hi,

I started to make a resident texture class, it just holds a TexHandle (an int).
I it's destructor, I try to notify the outer world in a thread safe way that that the handle is no longer in use.
I know that in the ~this() I have restricted options.

I tried to do this with a simple queue object:

class SafeQueue(T, bool multiSrc, bool multiDst)
{
	struct Node { T data; Node* next; }
	Node* head, tail;
	
	this()
	{ head = tail = new Node; }
	
	void put(T data)
	{
		auto node = new Node(data);
		void doit()
		{
			tail.next = node;
			tail = node;
		}
		static if(multiSrc) synchronized doit; else doit;
	}
	
	T* fetch()
	{
		T* res;
		void doit()
		{
			if(auto newHead = head.next)
			{
				res = &newHead.data;
				head = newHead;
			}
		}
		static if(multiDst) synchronized doit; else doit;
		return res;
	}
	
	auto fetchAll()
	{
		//Opt: it could be optimized into a single syncronize block.
		T[] res; /+Not the fastest but it's synchronous.+/
		while(1) if(auto a = fetch) res ~= *a; else break;
		return res;
	}
}

alias SSQueue	(T) = SafeQueue!(T, 0, 0),
MSQueue	(T) = SafeQueue!(T, 1, 0),
SMQueue	(T) = SafeQueue!(T, 0, 1),
MMQueue	(T) = SafeQueue!(T, 1, 1);

class Texture
{
	const TexHandle handle;
	
	version(/+$DIDE_REGION Tracking released texHandles+/all)
	{
		protected __gshared MSQueue!TexHandle destroyedResidentTexHandles;
		shared static this()
		{ destroyedResidentTexHandles = new typeof(destroyedResidentTexHandles); }
	}
	
	this(S)(in TexFlags flags, in TexFormat format, in S size, in void[] data=null)
	{
		TexSizeFormat fmt;
		fmt.flags 	= flags,
		fmt.format 	= format,
		fmt.size 	= size;
		fmt.resident = true;
		handle = TB.createHandleAndSetData(fmt, data);
	}
	
	this(S)(in TexFormat format, in S size, in void[] data=null, in TexFlags flags=TexFlags.init)
	{ this(flags, format, size, data); }
	
	~this()
	{
		if(handle)
		{ destroyedResidentTexHandles.put(handle); }
	}
}

This is working in a normal use case: I create a Texture class insance, I store it's pointer and later when I press a key, I release the class pointer.

But when I do not store the class pointer: new Texture(...); It just enters into a dead stall right after the first GC call.

Maybe it is because inside MSQueue.put() there is a synchronized{} clause and when the GC runs every thread is stopped?

Maybe it because there are additional memory allocation inside MSQueue.put()?

What's the best practice to do this automatic resource deallocation problem?

Manual deallocation is not a solution for this, because the meaning of this is to avoid manual deallocation, and the human errors that comes with that, in the first place. ;)

4 days ago

On Tuesday, 24 June 2025 at 08:48:16 UTC, realhet wrote:

>

This is working in a normal use case: I create a Texture class insance, I store it's pointer and later when I press a key, I release the class pointer.

But when I do not store the class pointer: new Texture(...); It just enters into a dead stall right after the first GC call.

Maybe it is because inside MSQueue.put() there is a synchronized{} clause and when the GC runs every thread is stopped?

Finalizers are run with the world resumed. But if you can learn where it's hanging, it can give more information.

But there is another problem with your synchronization. A synchronized statement needs a mutex to use for protection. When you don't give it one, it just makes one for each synchronized statement implicitly.

Therefore, a call to fetch is not synchronized with a call to put. Only calls to fetch are synchronized with each other, and calls to put are synchronized with each other.

>

Maybe it because there are additional memory allocation inside MSQueue.put()?

If you allocate inside a finalizer, the system should throw an error, not hang. But yes, you are allocating in that put function, that is not allowed. What may be happening is that the error is thrown in a non-main thread, and now the process is invalid.

What about using C malloc to allocate your link nodes? Surely the queue can be manually managed for memory.

>

What's the best practice to do this automatic resource deallocation problem?

Manual deallocation is not a solution for this, because the meaning of this is to avoid manual deallocation, and the human errors that comes with that, in the first place. ;)

Can you post the usage of the item? And what is TB.createHandleAndSetData(fmt, data);? Is this a C library function? What does it do?

You are storing an int which means there is no reference to anything that is stored on behalf of that int. In particular, I'm suspicious of the data parameter.

In a GC finalizer, you should be able to access and clean up resources allocated outside the GC. You cannot access any resources allocated with the GC (unless they are pinned).

Is there a reason you don't want to immediately clean up the resource in the destructor?

-Steve

2 days ago

On Thursday, 26 June 2025 at 16:40:10 UTC, Steven Schveighoffer wrote:

>

On Tuesday, 24 June 2025 at 08:48:16 UTC, realhet wrote:

>

Finalizers are run with the world resumed.
I'll remember this. It sounds less mystical than I thought.

Currently I'm having an 'infrastructure' that's like:
I'm managing all the parent/child object relations in the destructors automatically.
This thing is traversed and destroyed in every parents' destructor: bool[Object] _childMap;.
Child objects can also notify their parents when their destructors are manually called by object.destroy().
I use it to manage Vulkan object hierarcy, I destroy it with a single. root.destroy() call. There is no multithreading and no GC initiated destroys either. Maybe that's why it is stable.

(So at a last option I can still organize my classes in a tree and I can avoid calling multiple destroys by a single destroy.)

With this automatic thing it was much easier to represent the many vulkan classes:

class VulkanPhysicalDevice
{
	mixin SmartParent!q{
		@PARENT VulkanInstance 	instance,
		VkPhysicalDevice 	handle,
		int 	index
	};
//from here the lifecycle management is automatic: 'instance' is holding a pointer to this object. The mixin template generates the constructor and the destructor.

The mixin template is inside this module: github/realhet/hetlib/blob/master/het/Package.d
There are destructor calls and aa.remove() operations in the destructors.
It works very well, but it shouldn't because I'm heavily accessing GC managed pointers from destructors ;) But maybe because I do it in a hierarchical pattern, I avoid the crashes/freezes/exceptions.

With the texture handle class, I tried a different approach: No object hierarchy stored, I wanted that the GC initiate the destroy calls whenever it wants. The hierarchy is maintained by the GC. I don't even have to write a single .destroy(). It's full automatic. But if I don't pin the allocated pointer, it freezes. (I will try to debug that later.)

>

But there is another problem with your synchronization.
Thanks for pointing this out, but in this particular case the put and the fetch calls are synchronized (serialized) by the x86 processor's cache coherency: It's a tricky queue, its state is updated by single 8 byte memory writes, so that's where the atomicity comes from.
The Queue can be configured to single/multithreaded on both the input and output sides.

Current case: fetch is called only from the main thread regularly to fetch what TextureHandles are needed to be deallocated (those are on GPU accessable memory, so no GC conflicts, but I can't do this deallocation from another thread, because the main thread accessing/modifying that memory all the time)
put() is multithreaded: There will be multiple cameras, requiring a texture buffer to put data into. When the camera object is no longer used, the GC will notice it. It will notice that the Texture Buffer object is no longer needed. I need to send this no longer needed handle to the main thread from a destructor.

>

Can you post the usage of the item? And what is TB.createHandleAndSetData(fmt, data);? Is this a C library function? What does it do?

createHandleAndSetData, I'm having hard times naming :D But this is just searches for a free texture handle, if there is non, it reallocated the buffer on GPU. It's a lot of operations, but it's not in any destructor and only on main thread.

>

In a GC finalizer, you should be able to access and clean up resources allocated outside the GC. You cannot access any resources allocated with the GC (unless they are pinned).
Again an important sentence to remember!

Actually there are allocations in that queue.put(). Internally it is a linked list. Every item is a new allocation.

>

Is there a reason you don't want to immediately clean up the resource in the destructor?
The texture handle is indeed not a GC managed resource, BUT! I'm in a camera's thread or whatever thread, I can't have access to the manager of the texture handles.
I also don't want to mynchronize all the texture operations -> that is 95% main thread responsibility, and 5% responsibility of the external input sources (camera streams from different threads).

>

What about using C malloc to allocate your link nodes? Surely the queue can be manually managed for memory.
A good idea, thanks. I can avoid using GC allocations when I rewrite the Queue object to use malloc.

Thank You!