Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support threads in the script debugger #76582

Merged
merged 1 commit into from
Jul 26, 2023

Conversation

reduz
Copy link
Member

@reduz reduz commented Apr 29, 2023

  • This implementation adds threads on the side of the client (script debugger).
  • Some functions of the debugger are optimized.
  • The profile is also now thread safe using atomics.
  • The editor has been upgraded to be able to edit multiple threads.

This PR adds threaded support for the script language debugger. Every thread has its own thread local data and it will connect to the debugger using multiple thread IDs. This means that, now, the editor can receive multiple threads entering debug mode at the same time.

Obligatory screenshot:
thread_debug

@reduz reduz marked this pull request as ready for review April 29, 2023 15:24
@reduz reduz requested review from a team as code owners April 29, 2023 15:24
@adamscott
Copy link
Member

I tried your PR. I paused my instance, while 2 threads were running in the background, but they didn't show up in the debugger. Is there something more to do?

I ran that code on my main scene:

extends Node

var thread_hello: Thread
var thread_world: Thread

func _ready() -> void:
	thread_hello = Thread.new()
	thread_world = Thread.new()

	thread_hello.start(_thread_func.bind("hello", 500), Thread.PRIORITY_NORMAL)
	thread_world.start(_thread_func.bind("world", 1000), Thread.PRIORITY_NORMAL)

func _thread_func(str: String, delay: int) -> void:
	while true:
		prints(str)
		OS.delay_msec(delay)

@reduz reduz force-pushed the threaded-debugger branch 2 times, most recently from f907f7d to 114ebea Compare April 30, 2023 14:36
@reduz
Copy link
Member Author

reduz commented Apr 30, 2023

@adamscott Threads only show up in the debugger once they break with an error, otherwise they are not tracked. There is not much of a point to doing so, otherwise since the engine itself does not track the threads. If you want to know what they are doing, that will probably be possible with the threaded profiler.

@Chaosus
Copy link
Member

Chaosus commented May 1, 2023

Is it possible to assign a name for the Thread? Otherwise, it may be hard to detect which thread emit an error by only number.

Calinou
Calinou previously requested changes May 1, 2023
Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to remove this line from the class reference:

[b]Note:[/b] Breakpoints won't break on code if it's running in a thread. This is a current limitation of the GDScript debugger.

@vnen
Copy link
Member

vnen commented May 2, 2023

Is it possible to assign a name for the Thread? Otherwise, it may be hard to detect which thread emit an error by only number.

Since we already have a Thread wrapper, it would be nice to add a name property to it. I agree that it helps debugging.

@vnen
Copy link
Member

vnen commented May 2, 2023

Overall looks good to me. It is not that much of changes in the end. The editor side does need polish though, it is difficult to use as the pointer does not update correctly when it breaks from multiple threads.

@reduz reduz force-pushed the threaded-debugger branch from 114ebea to 6b17667 Compare May 4, 2023 10:49
@reduz reduz requested a review from a team as a code owner May 4, 2023 10:49
@reduz
Copy link
Member Author

reduz commented May 4, 2023

Remember to remove this line from the class reference:

[b]Note:[/b] Breakpoints won't break on code if it's running in a thread. This is a current limitation of the GDScript debugger.

Done

Copy link
Member

@adamscott adamscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@akien-mga akien-mga requested review from Calinou and vnen May 9, 2023 06:12
@akien-mga akien-mga added the bug label May 9, 2023
@akien-mga akien-mga added this to the 4.1 milestone May 9, 2023
@rohanrhu

This comment was marked as off-topic.

@AThousandShips
Copy link
Member

There's still reviews that need to be done before this can be merged

@rohanrhu
Copy link
Contributor

rohanrhu commented Jun 11, 2023

Aren't we able to use breakpoints in threads with this PR?

@Calinou
Copy link
Member

Calinou commented Jun 11, 2023

Aren't we able to use breakpoints in threads with this PR?

Yes, but 4.1 is in feature freeze now. While this PR fixes a bug, it still has a large scope and may be postponed to 4.2.

@akien-mga akien-mga requested review from Faless and RandomShaper June 12, 2023 09:33
@akien-mga akien-mga modified the milestones: 4.1, 4.2 Jun 19, 2023
@adamscott adamscott requested a review from dalexeev July 8, 2023 14:40
@dalexeev dalexeev removed their request for review July 8, 2023 18:12
Copy link
Collaborator

@Faless Faless left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the overall debugger changes are good (see my comment about locking in _get_message).

One thing I noticed, is that breaking in a thread will spam the console with errors like:

This function in this node (/root/Control) can only be accessed from either the main thread or a thread group. Use call_deferred() instead.

As far as I can tell, this is due to the Remote Inspector trying to get the Node properties if a local reference to a node is present in the thread stack.

This is the test I used:

extends Control

var t = Thread.new()


func _tfunc():
	while true:
		var v1 := 1
		var v2 := "Test"
		print("Test")
		OS.delay_msec(1000)


func _ready():
	t.start(_tfunc)


func _process(delta):
	var v1 := "Test"
	print("Delta")

I think that the best solution here would be to change all the scene: messages (the ones handled by SceneDebugger) to always be sent to the main thread.

EDIT: Well, it seems like it's already the case that scene: messages are sent to the main thread, so it might be a different message that generate the object spam... I'll try to dig deeper.

EDIT2: The error seems to be coming from the stack variables serialization. We are passing full objects (and not encoded ID), and the remote inspector uses the received values directly (instead of the 2-steps process which we use when inspecting remote tree). This might require some changes to how we handle those.

core/debugger/remote_debugger.cpp Show resolved Hide resolved
@Faless
Copy link
Collaborator

Faless commented Jul 22, 2023

This patch should fix the error spam, while still allow for remote inspection:

diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp
index 591b44869f..3e6b7501c7 100644
--- a/core/debugger/debugger_marshalls.cpp
+++ b/core/debugger/debugger_marshalls.cpp
@@ -67,6 +67,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
 	Array arr;
 	arr.push_back(name);
 	arr.push_back(type);
+	arr.push_back(value.get_type());
 
 	Variant var = value;
 	if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) {
@@ -74,7 +75,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
 	}
 
 	int len = 0;
-	Error err = encode_variant(var, nullptr, len, true);
+	Error err = encode_variant(var, nullptr, len, false);
 	if (err != OK) {
 		ERR_PRINT("Failed to encode variant.");
 	}
@@ -88,11 +89,12 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
 }
 
 bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) {
-	CHECK_SIZE(p_arr, 3, "ScriptStackVariable");
+	CHECK_SIZE(p_arr, 4, "ScriptStackVariable");
 	name = p_arr[0];
 	type = p_arr[1];
-	value = p_arr[2];
-	CHECK_END(p_arr, 3, "ScriptStackVariable");
+	var_type = p_arr[2];
+	value = p_arr[3];
+	CHECK_END(p_arr, 4, "ScriptStackVariable");
 	return true;
 }
 
diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h
index 8ba93c3092..1b81623688 100644
--- a/core/debugger/debugger_marshalls.h
+++ b/core/debugger/debugger_marshalls.h
@@ -38,6 +38,7 @@ struct DebuggerMarshalls {
 		String name;
 		Variant value;
 		int type = -1;
+		int var_type = -1;
 
 		Array serialize(int max_size = 1 << 20); // 1 MiB default.
 		bool deserialize(const Array &p_arr);
diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp
index e083e1746d..1e9b7c2c60 100644
--- a/editor/debugger/editor_debugger_inspector.cpp
+++ b/editor/debugger/editor_debugger_inspector.cpp
@@ -232,7 +232,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
 	PropertyHint h = PROPERTY_HINT_NONE;
 	String hs;
 
-	if (v.get_type() == Variant::OBJECT) {
+	if (var.var_type == Variant::OBJECT) {
 		v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();
 		h = PROPERTY_HINT_OBJECT_ID;
 		hs = "Object";

* This implementation adds threads on the side of the client (script debugger).
* Some functions of the debugger are optimized.
* The profile is also now thread safe using atomics.
* The editor can switch between multiple threads when debugging.

This PR adds threaded support for the script language debugger. Every thread has its own thread local data and it will connect to the debugger using multiple thread IDs.
This means that, now, the editor can receive multiple threads entering debug mode at the same time.
@reduz reduz force-pushed the threaded-debugger branch from 6b17667 to 5e512b7 Compare July 26, 2023 10:07
@reduz
Copy link
Member Author

reduz commented Jul 26, 2023

@Faless @AThousandShips Thanks lots!

Copy link
Member

@RandomShaper RandomShaper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked this, not as deeply as I would have wanted due to my current limited bandwitdth. I spotted no issues. In case some bugs arise, I may be able to be of greater help in fixing them.

Copy link
Member

@vnen vnen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. I tested for a bit and seems to work well.

@YuriSizov YuriSizov merged commit c4e5822 into godotengine:master Jul 26, 2023
@YuriSizov
Copy link
Contributor

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Threads do not output errors to debugger Breakpoints do not work in Thread