Unintentionally killing active threads

Godot Version

godot-stable-4.3

Question

So, I have a thread-based chunk generation system for my game. All of the threads are stored in an array. For some reason, even though it is programmed to only kill threads after they are done running, the debugger keeps saying that threads are being destroyed without completion being realized. Any idea why this might be happening? Here is my code:

func add_chunk(x, z):
	for i in generation_thread:
		if not i.is_started() or not i.is_alive():
			generation_thread.remove_at(generation_thread.find(i)) # <------This line will remove the thread if it's no longer running.
	if len(generation_thread) < 5:
		generation_thread.append(Thread.new())
		generation_thread[len(generation_thread) - 1].start(find_or_make_chunk_contents.bind(x, z))
		chunks_started.append([x,z])

Use Thread’s wait_to_finish() to join the thread, you must join finished threads for proper cleanup.

Thanks! I never really understood before, but it seems that it should go right before the line to remove the thread from the array? When I do that, I get no more errors.

Yes, though I suppose the thread reference would still exist if you did i.wait_to_finish() after removing it. It won’t halt the main thread if the thread is_alive is false, which is what you are checking before removing it.

Actually, I’m getting a crash now. Where do I find the log for the Godot editor?

For some reason, it crashes 50% of the time, saying: 'error(-1): no debug info in PE/COFF executable'. The other half of the time it runs, but says that threads are closing without completion being realized.

Can anyone help me? I’m drowning in error messages right now.

Ugh, you’re altering the array you’re iterating over. Don’t do that.

Sorry, can you explain a little more? I’m new to this.

You shouldn’t be messing with threads then. Start with something simpler.

If you’re looping over array elements and delete elements of that array in the body of that loop - you’ll mess up the loop, because removing an element shifts the indexes. Depending on what you delete - you could end up iterating over the same element twice or skip an element.

I guess, I should clarify. I’ve been programming since age 7, but now is the first time I’m using threads. I guess I can use a range and go through the elements of the array backwards?

No, don’t alter the array while iterating. Put the deletion candidates in another array and delete them afterwards by value. Or put non-candidates into a fresh array and swap the arrays.

This may not even be the main cause of your problems (although it could be) but it’s almost guaranteed to result in bugs. Just don’t alter the array while iterating over it. It’s a really bad habit.

You can iterate backwards to prevent such issues, I’d be surprised if that was causing hard crashes of the engine/editor. Without much more code, or a better error message(s), it’s hard to say

Just to clarify, It’s the program, not the engine, that is crashing.

I’m seeing some others with the same error, but it seems quite random. Some mention changing rendering mode fixes it. Does it crash if you comment out the line for remove_at?

Well, no, but also that means only a few chunks generate. It’s hard to know for sure if it’s no crashing or just the edge case that causes it to crash isn’t coming up.

You have two obvious problems - the loop and improper thread cleanup. So fix those first and then see if the crash/error persists.

Instead of removing threads you could check for not started threads and start on those. Save you a lot of logistical headaches as we have here.

Assuming your generation_thread array has five Thread.new() entries.

func add_chunk(x, z):
	for i in generation_thread:
		if i.is_started() and not i.is_alive():
			i.wait_to_finish() # join finished threads

	for i in generation_thread:
		# Start an idle thread
		if not i.is_started():
			i.start(find_or_make_chunk_contents.bind(x, z))
			chunks_started.append([x,z])
			break

Or just use WorkerThreadPool

func add_chunk(x, z):
	WorkerThreadPool.add_task(find_or_make_chunk_contents.bind(x, z))

If it performs well, you’re done.

So, like, how do I use this exactly? Looking up the documentation, I can’t figure out how to actually execute the task, and nothing seems to happen just by setting it.