3 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. ;)

23 hours 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