Browse Source

Merge pull request #5018 from aws-lumberyard-dev/burelc/linuxApTetherLifetime

[Linux] Terminate AssetProcessor when spawned by the parent project process
Jeremy Ong 3 years ago
parent
commit
47b7ad1ec4

+ 74 - 39
Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp

@@ -9,19 +9,71 @@
 #include <AzCore/IO/Path/Path.h>
 #include <AzCore/IO/SystemFile.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
+#include <AzCore/Console/IConsole.h>
 #include <AzCore/std/containers/array.h>
 #include <AzCore/std/tuple.h>
 
-#include <errno.h>
+#include <cerrno>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/prctl.h>
 #include <unistd.h>
 
+AZ_CVAR(bool, ap_tether_lifetime, true, nullptr, AZ::ConsoleFunctorFlags::Null,
+    "If enabled, a parent process that launches the AP will terminate the AP on exit");
+
 namespace AzFramework::AssetSystem::Platform
 {
     void AllowAssetProcessorToForeground()
     {}
 
+    [[noreturn]] static void LaunchAssetProcessorDirectly(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
+    {
+        AZStd::fixed_vector<const char*, 5> args {
+            assetProcessorPath.c_str(),
+            "--start-hidden",
+        };
+
+        // Add the engine path to the launch command if not empty
+        AZ::IO::FixedMaxPathString engineRootArg;
+        if (!engineRoot.empty())
+        {
+            // No need to quote these paths, this code calls exec directly and
+            // does not go through shell string interpolation
+            engineRootArg = AZ::IO::FixedMaxPathString{"--engine-path="} + AZ::IO::FixedMaxPathString{engineRoot};
+            args.push_back(engineRootArg.data());
+        }
+
+        // Add the active project path to the launch command if not empty
+        AZ::IO::FixedMaxPathString projectPathArg;
+        if (!projectPath.empty())
+        {
+            projectPathArg = AZ::IO::FixedMaxPathString{"--regset=/Amazon/AzCore/Bootstrap/project_path="} + AZ::IO::FixedMaxPathString{projectPath};
+            args.push_back(projectPathArg.data());
+        }
+
+        // Make sure this is at the end
+        args.push_back(nullptr); // argv itself needs to be null-terminated
+
+        execv(args[0], const_cast<char**>(args.data()));
+
+        // exec* family of functions only return on error
+        fprintf(stderr, "Asset Processor failed with error: %s\n", strerror(errno));
+        _exit(1);
+    }
+
+    static pid_t LaunchAssetProcessorDaemonized(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
+    {
+        // detach the child from parent
+        setsid();
+        const pid_t secondChildPid = fork();
+        if (secondChildPid == 0)
+        {
+            LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
+        }
+        return secondChildPid;
+    }
+
     bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view engineRoot,
         AZStd::string_view projectPath)
     {
@@ -40,7 +92,8 @@ namespace AzFramework::AssetSystem::Platform
             }
         }
 
-        pid_t firstChildPid = fork();
+        const pid_t parentPid = getpid();
+        const pid_t firstChildPid = fork();
         if (firstChildPid == 0)
         {
             // redirect output to dev/null so it doesn't hijack an existing console window
@@ -53,51 +106,33 @@ namespace AzFramework::AssetSystem::Platform
             AZ::IO::FileDescriptorRedirector stderrRedirect(STDERR_FILENO);
             stderrRedirect.RedirectTo(devNull, mode);
 
-            // detach the child from parent
-            setsid();
-            pid_t secondChildPid = fork();
-            if (secondChildPid == 0)
+            if (ap_tether_lifetime)
             {
-                AZStd::array args {
-                    assetProcessorPath.c_str(), assetProcessorPath.c_str(), "--start-hidden", 
-                    static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), static_cast<const char*>(nullptr)
-                };
-                int optionalArgPos = 3;
-
-                // Add the engine path to the launch command if not empty
-                AZ::IO::FixedMaxPathString engineRootArg;
-                if (!engineRoot.empty())
-                {
-                    engineRootArg = AZ::IO::FixedMaxPathString::format(R"(--engine-path="%.*s")", 
-                        aznumeric_cast<int>(engineRoot.size()), engineRoot.data());
-                    args[optionalArgPos++] = engineRootArg.data();
-                }
-
-                // Add the active project path to the launch command if not empty
-                AZ::IO::FixedMaxPathString projectPathArg;
-                if (!projectPath.empty())
+                prctl(PR_SET_PDEATHSIG, SIGTERM);
+                if (getppid() != parentPid)
                 {
-                    projectPathArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%.*s")",
-                        aznumeric_cast<int>(projectPath.size()), projectPath.data());
-                    args[optionalArgPos++] = projectPathArg.data();
+                    _exit(1);
                 }
-
-                AZStd::apply(execl, args);
-
-                // exec* family of functions only exit on error
-                AZ_Error("AssetSystemComponent", false, "Asset Processor failed with error: %s", strerror(errno));
-                _exit(1);
+                LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
             }
+            else
+            {
+                const pid_t secondChildPid = LaunchAssetProcessorDaemonized(assetProcessorPath, engineRoot, projectPath);
+                stdoutRedirect.Reset();
+                stderrRedirect.Reset();
 
-            stdoutRedirect.Reset();
-            stderrRedirect.Reset();
+                // exit the transient child with proper return code
+                int ret = (secondChildPid < 0) ? 1 : 0;
+                _exit(ret);
+            }
 
-            // exit the transient child with proper return code
-            int ret = (secondChildPid < 0) ? 1 : 0;
-            _exit(ret);
         }
         else if (firstChildPid > 0)
         {
+            if (ap_tether_lifetime)
+            {
+                return true;
+            }
             // wait for first child to exit to ensure the second child was started
             int status = 0; 
             pid_t ret = waitpid(firstChildPid, &status, 0);
@@ -106,4 +141,4 @@ namespace AzFramework::AssetSystem::Platform
 
         return false;
     }
-}
+} // namespace AzFramework::AssetSystem::Platform