diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp index dcdc4a5925..6cb474f4ce 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp @@ -9,19 +9,71 @@ #include #include #include +#include #include #include -#include +#include #include #include +#include #include +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 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(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(nullptr), static_cast(nullptr), static_cast(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(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(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