godotengine/godot

Code after the await keyword is executed in the main thread instead of the original thread.

HungryProton opened this issue · 5 comments

Godot version

4.1+

System information

Arch Linux - Vulkan (Forward+)

Issue description

When using await in a thread, the code after the await is executed in the main thread, instead of the original thread.

This bug only happens in 4.1 and above. It works fine 4.0.3 - stable.
(Apparently I got my builds mixed up, this also does not work in 4.0.3)

Steps to reproduce

Using this minimal function running in a thread as an example:

func run_in_thread() -> void:
	print("Caller thread before await: ", OS.get_thread_caller_id())

	await get_tree().create_timer(1.0).timeout

	print("Caller thread after await: ", OS.get_thread_caller_id())

In theory, this should print the same thread ID twice, but that's not the case, the second time it prints the main thread ID.

Also, it's not just a reporting issue, the main thread does freeze if you put a heavy workload after the await keyword.

Minimal reproduction project

extends Node


var _thread: Thread


func _ready():
	print("Main thread ID: ", OS.get_thread_caller_id())
	_thread = Thread.new()
	_thread.start(_run_in_thread)


func _exit_tree():
	_thread.wait_to_finish()


func _run_in_thread() -> void:
	print("Caller thread before: ", OS.get_thread_caller_id())

	await get_tree().create_timer(1.0).timeout

	print("Caller thread After: ", OS.get_thread_caller_id())

	# Code from here will run on the main thread instead of the thread
  • Open the project.
  • Press F5 (or run the minimal_scene.tscn).
  • Check the console output, the last thread ID matches the main thread ID.

In 4.0.1 I get a different seemingly wrong result.

--- Debugging process started ---
Godot Engine v4.0.1.stable.official.cacf49999 - https://godotengine.org
Vulkan API 1.3.230 - Forward+ - Using Vulkan Device #0: AMD - AMD Radeon RX 580 Series (RADV POLARIS10)

Caller thread before: -6881540454091642859
Caller thread After: 5364107225503294706
--- Debugging process stopped ---

In 4.0, the main thread ID is not necessarily 1, it's the same issue.

I think my problem is related (Godot v4.1.stable.official [9704596] on Windows 11 Pro, Version 22H2, Build 22621.1778):

func _ready():
	var semaphore = Semaphore.new()
	semaphore.post()

	var outside_index = 0

	var task = func(index):
		semaphore.wait()
		print("%s" % index)
		await get_tree().process_frame
		semaphore.post()

	var task_id = WorkerThreadPool.add_group_task(task, 2)
	WorkerThreadPool.wait_for_group_task_completion(task_id)
	get_tree().quit()

In the code above, the await even seems to prevent the semaphore.post(). With the await get_tree().process_frame statement, the program just hangs - most likely the semaphore.post() is not working. When I comment await get_tree().process_frame out, then I get the expected output

0
1

Is there any idea on how to work around this issue for the moment being?

Edit: Ok, I have a solution for my specific use case with await and signals, but it's rather messy and would be much more readable with a simple Semaphore. If anyone's interested ask and I provide it somewhere.

Kinda related: #66947.

BTW @Calinou, the "regression" label doesn't fit here.

4d49 commented

If anyone is facing the same problem, here is a possible solution to the problem.

var _thread: Thread
var _semaphore : Semaphore


func _ready():
	_semaphore = Semaphore.new()
	
	_thread = Thread.new()
	_thread.start(_run_in_thread)


func _exit_tree():
	_thread.wait_to_finish()


func _run_in_thread() -> void:
	print("Caller thread before: ", OS.get_thread_caller_id())
	
	get_tree().create_timer(1.0).timeout.connect(_semaphore.post)
	_semaphore.wait()

	print("Caller thread After: ", OS.get_thread_caller_id())