From b53bf52e0dc0a2af6886b35478a9c65eb64e03ba Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Wed, 15 Sep 2021 09:48:08 -0700 Subject: [PATCH] Perform global deinitialization when exiting the game launcher (#4131) * Fix code that deregisters the Atom Scene subsystem from the AzFramework Scene The AzFramework Scene subsystem API is a generic container based on the type of argument that is passed to it. It maintains a vector of typeids, and only one object of any type is stored at a time. The Bootstrap system component registers the Atom scene as a `ScenePtr` (aka `AZStd::shared_ptr`) with the AzFramework Scene's generic subsystem. However, the component was previously deregistering the type by value, `RPI::Scene`. Since no subsystem for the type `RPI::Scene` was set, unsetting this type did nothing. The result was that the `RPI::Scene` object would still be around by the time that all the Atom `InstanceDatabse`s were being destroyed, resulting in a large number of errors reported about leaked instances during global shutdown. This fixes the above issue by passing the `m_defaultScene` as a parameter to `AzFramework::Scene::UnsetSubsystem`, the same value that is passed to `SetSubsystem`. This is better, because instead of providing explicit template arguments (which were specifying the incorrect type), this now allows the compiler to deduce the correct type, and the syntax is symmetric with the call to `SetSubsystem`. Signed-off-by: Chris Burel * Correctly release the AWS API from the `HttpRequestManager` module This code was incorrectly assuming that `AWSNativeSDKInit::InitializationManager::Shutdown()` would be called automatically by the `InitializationManager` itself. However, all that `InitAwsApi()` does is create an `AZ::EnvironmentVariable`, which is a ref-counted type, and stores it in a global static. That global static is defined in a static library (namely `AWSNativeSDKInit`), which is linked in to the `HttpRequestManager` dynamic lib. Because it is a global static, it has to be explicitly cleared with the call to `Shutdown()`. Otherwise the destructor of the EnvironmentVariable doesn't happen until global destruction, by which time the allocator that is supplied to the AWS SDK has already been destroyed, and the shutdown of the AWS SDK attempts to use the already-destroyed allocator. Signed-off-by: Chris Burel * Avoid blocking the remote console server thread if there are no connections The Remote console server runs in a separate thread. Previously, it would directly call `AzSock::Accept()` and block the server thread until some client connected to it. However, if no client connected, the thread would continue to be blocked, even if the game launcher tried to exit. This adds a check to see if there's a client on the socket before calling `Accept()`, to avoid the deadlock on launcher exit. Signed-off-by: Chris Burel * Fix a log message to print one message per line Signed-off-by: Chris Burel * Allow pumping the event loop to close the launcher window Events from the OS are handled in the game's main loop. The general loop looks like this: * Read events from the OS * Tick the game application One of the events that can come from the OS is that the window hosting the game is closed. When this event happens, many resources provided by the renderer are freed, and the game application's `shouldExit` bit is set. However, when the game's `Tick()` is called, there is lots of code that assumes the renderer is still there. To avoid crashing in the `Tick()` call, check if the game should exit after pumping the system events. Signed-off-by: Chris Burel * Unload the level when exiting the launcher This ensures that any resources held onto by the level are freed before the launcher exits. Signed-off-by: Chris Burel * Add an explicit bus `Disconnect()` call to `AZCoreLogSink` This is necessary because this bus has virtual functions and can be called from multiple threads. Signed-off-by: Chris Burel * Allow normal cleanup to take place when exiting the game launcher Previously, global cleanup was side-stepped by calling `TerminateProcess` or `exit`, when quitting the game launcher. This is in contrast to the call to `_exit` on Linux and Mac when exiting the Editor. That leading `_` makes a big difference: the former runs object destruction, the latter does not. Instead of making the launcher exit with `_exit` on Linux, instead, remove that call and actually run all the atexit code. This does not modify the Editor's behavior however. It still uses `_exit` and `TerminateProcess`. Signed-off-by: Chris Burel --- .../AzFramework/Archive/Archive.cpp | 2 +- Code/LauncherUnified/Launcher.cpp | 11 +++++---- Code/Legacy/CrySystem/AZCoreLogSink.h | 5 ++++ .../CrySystem/LevelSystem/LevelSystem.cpp | 1 + Code/Legacy/CrySystem/System.cpp | 23 ------------------- .../RemoteConsole/Core/RemoteConsoleCore.cpp | 5 ++++ .../Code/Source/BootstrapSystemComponent.cpp | 2 +- .../Code/Source/HttpRequestManager.cpp | 3 +-- 8 files changed, 20 insertions(+), 32 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp index 012d713cf5..1891c50d10 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp @@ -2185,7 +2185,7 @@ namespace AZ::IO AZStd::unique_lock lock(m_archiveMutex); if (pArchive) { - AZ_TracePrintf("Archive", "Closing Archive file: %s", pArchive->GetFullPath()); + AZ_TracePrintf("Archive", "Closing Archive file: %s\n", pArchive->GetFullPath()); } ArchiveArray::iterator it; if (m_arrArchives.size() < 16) diff --git a/Code/LauncherUnified/Launcher.cpp b/Code/LauncherUnified/Launcher.cpp index de4c496b2a..27aab074ef 100644 --- a/Code/LauncherUnified/Launcher.cpp +++ b/Code/LauncherUnified/Launcher.cpp @@ -232,13 +232,17 @@ namespace // our frame time to be managed by AzGameFramework::GameApplication // instead, which probably isn't going to happen anytime soon given // how many things depend on the ITimer interface). - bool continueRunning = true; ISystem* system = gEnv ? gEnv->pSystem : nullptr; - while (continueRunning) + while (!gameApplication.WasExitMainLoopRequested()) { // Pump the system event loop gameApplication.PumpSystemEventLoopUntilEmpty(); + if (gameApplication.WasExitMainLoopRequested()) + { + break; + } + // Update the AzFramework system tick bus gameApplication.TickSystem(); @@ -256,9 +260,6 @@ namespace { system->UpdatePostTickBus(); } - - // Check for quit requests - continueRunning = !gameApplication.WasExitMainLoopRequested() && continueRunning; } } } diff --git a/Code/Legacy/CrySystem/AZCoreLogSink.h b/Code/Legacy/CrySystem/AZCoreLogSink.h index e824c4c172..a54a78157c 100644 --- a/Code/Legacy/CrySystem/AZCoreLogSink.h +++ b/Code/Legacy/CrySystem/AZCoreLogSink.h @@ -31,6 +31,11 @@ class AZCoreLogSink : public AZ::Debug::TraceMessageBus::Handler { public: + ~AZCoreLogSink() + { + Disconnect(); + } + inline static void Connect() { GetInstance().m_ignoredAsserts = new IgnoredAssertMap(); diff --git a/Code/Legacy/CrySystem/LevelSystem/LevelSystem.cpp b/Code/Legacy/CrySystem/LevelSystem/LevelSystem.cpp index a0338941b5..c5d364a025 100644 --- a/Code/Legacy/CrySystem/LevelSystem/LevelSystem.cpp +++ b/Code/Legacy/CrySystem/LevelSystem/LevelSystem.cpp @@ -233,6 +233,7 @@ CLevelSystem::CLevelSystem(ISystem* pSystem, const char* levelsFolder) //------------------------------------------------------------------------ CLevelSystem::~CLevelSystem() { + UnloadLevel(); } //------------------------------------------------------------------------ diff --git a/Code/Legacy/CrySystem/System.cpp b/Code/Legacy/CrySystem/System.cpp index 4a9d9ece5f..2e18e13841 100644 --- a/Code/Legacy/CrySystem/System.cpp +++ b/Code/Legacy/CrySystem/System.cpp @@ -548,29 +548,6 @@ void CSystem::Quit() logger->Flush(); } - /* - * TODO: This call to _exit, _Exit, TerminateProcess etc. needs to - * eventually be removed. This causes an extremely early exit before we - * actually perform cleanup. When this gets called most managers are - * simply never deleted and we leave it to the OS to clean up our mess - * which is just really bad practice. However there are LOTS of issues - * with shutdown at the moment. Removing this will simply cause - * a crash when either the Editor or Launcher initiate shutdown. Both - * applications crash differently too. Bugs will be logged about those - * issues. - */ -#if defined(AZ_RESTRICTED_PLATFORM) -#define AZ_RESTRICTED_SECTION SYSTEM_CPP_SECTION_4 -#include AZ_RESTRICTED_FILE(System_cpp) -#endif -#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED) -#undef AZ_RESTRICTED_SECTION_IMPLEMENTED -#elif defined(WIN32) || defined(WIN64) - TerminateProcess(GetCurrentProcess(), m_env.retCode); -#else - exit(m_env.retCode); -#endif - #ifdef WIN32 //Post a WM_QUIT message to the Win32 api which causes the message loop to END //This is not the same as handling a WM_DESTROY event which destroys a window diff --git a/Code/Tools/RemoteConsole/Core/RemoteConsoleCore.cpp b/Code/Tools/RemoteConsole/Core/RemoteConsoleCore.cpp index d0e9e6a760..0a8d9d8eb7 100644 --- a/Code/Tools/RemoteConsole/Core/RemoteConsoleCore.cpp +++ b/Code/Tools/RemoteConsole/Core/RemoteConsoleCore.cpp @@ -239,6 +239,11 @@ void SRemoteServer::Run() while (m_bAcceptClients) { + AZTIMEVAL timeout { 1, 0 }; + if (!AZ::AzSock::IsRecvPending(m_socket, &timeout)) + { + continue; + } AZ::AzSock::AzSocketAddress clientAddress; sClient = AZ::AzSock::Accept(m_socket, clientAddress); if (!m_bAcceptClients || !AZ::AzSock::IsAzSocketValid(sClient)) diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index 97faa7380f..ddd88a1f52 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -386,7 +386,7 @@ namespace AZ // Unbind m_defaultScene to the GameEntityContext's AzFramework::Scene if (m_defaultFrameworkScene) { - m_defaultFrameworkScene->UnsetSubsystem(); + m_defaultFrameworkScene->UnsetSubsystem(m_defaultScene); } m_defaultScene = nullptr; diff --git a/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp b/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp index 8ed40c1599..523d84f0f5 100644 --- a/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp +++ b/Gems/HttpRequestor/Code/Source/HttpRequestManager.cpp @@ -35,7 +35,6 @@ namespace HttpRequestor desc.m_name = s_loggingName; desc.m_cpuId = AFFINITY_MASK_USERTHREADS; m_runThread = true; - // Shutdown will be handled by the InitializationManager - no need to call in the destructor AWSNativeSDKInit::InitializationManager::InitAwsApi(); auto function = AZStd::bind(&Manager::ThreadFunction, this); m_thread = AZStd::thread(function, &desc); @@ -43,7 +42,7 @@ namespace HttpRequestor Manager::~Manager() { - // NativeSDK Shutdown does not need to be called here - will be taken care of by the InitializationManager + AWSNativeSDKInit::InitializationManager::Shutdown(); m_runThread = false; m_requestConditionVar.notify_all(); if (m_thread.joinable())