Repository: godotengine/godot (language: cpp)

Issue:
Running with the `--headless` flag does not result in removing the project's recovery mode lock if present
### Tested versions

4.4.rc1

### System information

Godot v4.4.rc (d91f2cd77) - Windows 11 (build 22631) - Multi-window, 3 monitors - OpenGL 3 (Compatibility) - AMD Radeon RX 7900 XT (Advanced Micro Devices, Inc.; 32.0.11037.4004) - AMD Ryzen Threadripper 7970X 32-Cores (64 threads)

### Issue description

Running with the `--headless flag` does not result in a call to `_remove_lock_file`

### Steps to reproduce

run any command on a project with the `--headless` flag.

### Minimal reproduction project (MRP)

N/A


Discussion / hints:
Could you explain what the actual problem is? I assume there's a fault in the code but you don't link to the code you're referring to, and as an issue triager I'm not immediately intimate with what `_remove_lock_file` is, what it's supposed to do and why the absence of calling it would be a problem.

Sure, I could spend some time researching it but this is the information a bug report should convey so that contributors can understand a problem (what was expected, what was found, what is the consequence of it) and fix it.
The problem is that the lock file is left in place, which leads to a prompt the next time you open the editor. This shouldn't happen.
[This](https://github.com/godotengine/godot/blame/9f68a816599412c56a4626dd6c47244ebc65ed1f/editor/editor_node.cpp#L1219) is the code I'm referring to.
`_remove_lock_file` removes the lock file at (on windows) `%appdata%\Godot\app_userdata\PROJECT_NAME\.recovery_mode_lock`
It is used to determine if you should launch in recovery mode.
The absence of calling is a problem, because it's indicative of needing to launch in recovery mode.

What was expected:
`_remove_lock_file` is called
What was found:
`_remove_lock_file` was not called
Consequence
The lock file is not removed, causing an erroneous popup on the next run.
CC @rsubtil 
After staring at this long enough, I wonder if the other timers in this file are even successful. They aren't causing any issues for me presently, but perhaps this is indicative of a bigger issue? In general I wish these timers had more context as to why they're needed and why they have the values they have.
Note that removing the lock file should only be done when running the editor, not when running the project directly from the command line or project manager.

This means `--headless` alone should not remove the lock file, but `--headless --editor`, `--headless --import` or `--headless --export` should (`--import` and `--export` both imply `--editor`).
I've just retested it, and the behavior looks "mostly" correct to me on `--headless` mode.

When running simply with `--headless` mode, no lock file is created nor removed. This is intended, because this behavior can only occur on editor mode, as @Calinou said. And whenever I do so (`--editor/--import/--export`), the lock file is created, and then eventually deleted. I can't replicate your situation where the timer apparently fails to trigger. I tested this on Linux, so could this be a OS specific bug? Can you provide exact commands of how you're finding this issue?

However, when using `--import/--export`, it can happen that each process is finished before the timer is triggered, and the lock file persists after the engine finishes. I can replicate this quite easily on an empty project (since it doesn't have much content to import), so I'll have to account for this scenario too then 👍.

Lastly, to answer your question as to why a timer was used an not a `call_deferred`, it has to do with the order some of these components run. `@tool` scripts run on the next frame, so the first solution used a `call_deferred`. But this fails to catch some scenarios such as `queue_free`ing a editor node, which is delayed another frame. I then added a 2 frame delay, but the issue with this is that the problem just keeps going. Scripts can always be crafted in a way to cause reliable crashes that recovery mode cannot detect, so in the end I chose to instead use a time frame of 1 second instead of engine frames, which starts after the initial import process. It's not a perfect solution, and is essentially a compromise I reached.
It seems https://github.com/godotengine/godot/issues/103564 is also related to this, and also happening on Windows, so I'll need to investigate it in there. I can only get a Windows machine on a couple of days though, but once I can test it I'll share my findings.
That other issue sounds exactly like my story. I put debug prints in the engine and every time the timer is created, but the method it's supposed to call is not. I run with both `--import` and `--export` and see the same result in both cases. The lock file is created each run, but not removed. When importing I have `godot --import --headless --verbose --quit` I can try running `--import` when I get back to work without `--quit` to see if that makes a difference.
`--quit` quits after the first process iteration, so that might be why. Try using a larger number of iterations to wait before quitting with `--quit-after N` where N is the number of iterations to wait (start with `2`). Eventually, you should find a large enough value that allows the lock file to be removed.

Fix patch:
diff --git a/main/main.cpp b/main/main.cpp
index e65f894c549f..5a3a651fcd9d 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -4662,6 +4662,12 @@ void Main::cleanup(bool p_force) {
 	ResourceSaver::remove_custom_savers();
 	PropertyListHelper::clear_base_helpers();
 
+	// Remove the lock file if the engine exits successfully. Some automated processes such as
+	// --export/--import can bypass and/or finish faster than the existing check to remove the lock file.
+	if (OS::get_singleton()->get_exit_code() == EXIT_SUCCESS) {
+		OS::get_singleton()->remove_lock_file();
+	}
+
 	// Flush before uninitializing the scene, but delete the MessageQueue as late as possible.
 	message_queue->flush();
 


Test patch: