123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <QCoreApplication>
- #include <QElapsedTimer>
- #include <AzCore/Settings/CommandLine.h>
- #include <AzCore/Settings/SettingsRegistry.h>
- #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
- #include <AzCore/std/parallel/binary_semaphore.h>
- #include <AzCore/Utils/Utils.h>
- #include <utilities/Builder.h>
- #include <utilities/AssetBuilderInfo.h>
- namespace AssetProcessor
- {
- //! Amount of time in milliseconds to wait between checking the status of the AssetBuilder process and pumping the stdout/err pipes
- //! Should be kept fairly low to avoid the process stalling due to a full pipe but not too low to avoid wasting CPU time
- constexpr int MaximumSleepTimeMs = 10;
- constexpr int MillisecondsInASecond = 1000;
- constexpr const char* BuildersFolderName = "Builders";
- bool Builder::IsConnected() const
- {
- return m_connectionId > 0;
- }
- AZ::Outcome<void, AZStd::string> Builder::WaitForConnection()
- {
- if (m_startupWaitTimeS == 0)
- {
- const auto* settingsRegistry = AZ::SettingsRegistry::Get();
- if (settingsRegistry)
- {
- settingsRegistry->Get(m_startupWaitTimeS, "/Amazon/AssetProcessor/Settings/BuilderManager/StartupTimeoutSeconds");
- }
- }
- if (m_connectionId == 0)
- {
- bool result = false;
- QElapsedTimer ticker;
- ticker.start();
- while (!result)
- {
- result = m_connectionEvent.try_acquire_for(AZStd::chrono::milliseconds(MaximumSleepTimeMs));
- PumpCommunicator();
- if (ticker.elapsed() > m_startupWaitTimeS * MillisecondsInASecond || m_quitListener.WasQuitRequested() || !IsRunning())
- {
- break;
- }
- }
- PumpCommunicator();
- FlushCommunicator();
- if (result)
- {
- return AZ::Success();
- }
- AZ::u32 exitCode;
- if (m_quitListener.WasQuitRequested())
- {
- AZ_TracePrintf(AssetProcessor::DebugChannel, "Aborting waiting for builder, quit requested\n");
- }
- else if (!IsRunning(&exitCode))
- {
- AZ_Error("Builder", false, "AssetBuilder terminated during start up with exit code %d", exitCode);
- }
- else
- {
- AZ_Error("Builder", false, "AssetBuilder failed to connect within %d seconds", m_startupWaitTimeS);
- }
- return AZ::Failure(AZStd::string::format("Connection failed to builder %.*s", AZ_STRING_ARG(UuidString())));
- }
- return AZ::Success();
- }
- void Builder::SetConnection(AZ::u32 connId)
- {
- m_connectionId = connId;
- m_connectionEvent.release();
- }
- AZ::u32 Builder::GetConnectionId() const
- {
- return m_connectionId;
- }
- AZ::Uuid Builder::GetUuid() const
- {
- return m_uuid;
- }
- AZStd::string Builder::UuidString() const
- {
- return m_uuid.ToString<AZStd::string>(false, false);
- }
- void Builder::PumpCommunicator() const
- {
- if (m_tracePrinter)
- {
- m_tracePrinter->Pump();
- }
- }
- void Builder::FlushCommunicator() const
- {
- if (m_tracePrinter)
- {
- m_tracePrinter->Flush();
- }
- }
- void Builder::TerminateProcess(AZ::u32 exitCode) const
- {
- if (m_processWatcher)
- {
- m_processWatcher->TerminateProcess(exitCode);
- }
- }
- AZ::Outcome<void, AZStd::string> Builder::Start(BuilderPurpose purpose)
- {
- // Get the current BinXXX folder based on the current running AP
- QString applicationDir = QCoreApplication::instance()->applicationDirPath();
- // Construct the Builders subfolder path
- AZStd::string buildersFolder;
- AzFramework::StringFunc::Path::Join(applicationDir.toUtf8().constData(), BuildersFolderName, buildersFolder);
- // Construct the full exe for the builder.exe
- const AZStd::string fullExePathString =
- QDir(applicationDir).absoluteFilePath(AssetProcessor::s_assetBuilderRelativePath).toUtf8().constData();
- if (m_quitListener.WasQuitRequested())
- {
- return AZ::Failure(AZStd::string("Cannot start builder, quit was requested"));
- }
- const AZStd::vector<AZStd::string> params = BuildParams("resident", buildersFolder.c_str(), UuidString(), "", "", purpose);
- m_processWatcher = LaunchProcess(fullExePathString.c_str(), params);
- if (!m_processWatcher)
- {
- return AZ::Failure(AZStd::string::format("Failed to start process watcher for Builder %.*s.", AZ_STRING_ARG(UuidString())));
- }
- // Currently, this uses polling for managing the trace printing output because the job log redirections rely on thread local
- // storage to route different jobs to different logs. If the trace printing spins up a new thread for printing, it won't
- // redirect to the correct job logs.
- m_tracePrinter = AZStd::make_unique<ProcessCommunicatorTracePrinter>(
- m_processWatcher->GetCommunicator(), "AssetBuilder", ProcessCommunicatorTracePrinter::TraceProcessing::Poll);
- return WaitForConnection();
- }
- bool Builder::IsValid() const
- {
- return m_connectionId != 0 && IsRunning();
- }
- bool Builder::IsRunning(AZ::u32* exitCode) const
- {
- return !m_processWatcher || (m_processWatcher && m_processWatcher->IsProcessRunning(exitCode));
- }
- AZStd::vector<AZStd::string> Builder::BuildParams(
- const char* task,
- const char* moduleFilePath,
- const AZStd::string& builderGuid,
- const AZStd::string& jobDescriptionFile,
- const AZStd::string& jobResponseFile,
- BuilderPurpose purpose) const
- {
- QDir projectCacheRoot;
- AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
- AZ::SettingsRegistryInterface::FixedValueString projectName = AZ::Utils::GetProjectName();
- AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
- AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
- int portNumber = 0;
- ApplicationServerBus::BroadcastResult(portNumber, &ApplicationServerBus::Events::GetServerListeningPort);
- AZStd::vector<AZStd::string> params;
- params.emplace_back(AZStd::string::format(R"(-task="%s")", task));
- params.emplace_back(AZStd::string::format(R"(-id="%s")", builderGuid.c_str()));
- params.emplace_back(AZStd::string::format(R"(-project-name="%s")", projectName.c_str()));
- params.emplace_back(AZStd::string::format(R"(-project-cache-path="%s")", projectCacheRoot.absolutePath().toUtf8().constData()));
- params.emplace_back(AZStd::string::format(R"(-project-path="%s")", projectPath.c_str()));
- params.emplace_back(AZStd::string::format(R"(-engine-path="%s")", enginePath.c_str()));
- params.emplace_back(AZStd::string::format("-port=%d", portNumber));
- if (purpose == BuilderPurpose::Registration)
- {
- params.emplace_back("--register");
- }
- if (moduleFilePath && moduleFilePath[0])
- {
- params.emplace_back(AZStd::string::format(R"(-module="%s")", moduleFilePath));
- }
- if (!jobDescriptionFile.empty() && !jobResponseFile.empty())
- {
- params.emplace_back(AZStd::string::format(R"(-input="%s")", jobDescriptionFile.c_str()));
- params.emplace_back(AZStd::string::format(R"(-output="%s")", jobResponseFile.c_str()));
- }
- auto settingsRegistry = AZ::SettingsRegistry::Get();
- AZ::CommandLine commandLine;
- AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(*settingsRegistry, commandLine);
- AZStd::fixed_vector optionKeys{ "regset", "regremove" };
- for (auto&& optionKey : optionKeys)
- {
- size_t commandOptionCount = commandLine.GetNumSwitchValues(optionKey);
- for (size_t optionIndex = 0; optionIndex < commandOptionCount; ++optionIndex)
- {
- const AZStd::string& optionValue = commandLine.GetSwitchValue(optionKey, optionIndex);
- params.emplace_back(AZStd::string::format(R"(--%s="%s")", optionKey, optionValue.c_str()));
- }
- }
- return params;
- }
- AZStd::unique_ptr<AzFramework::ProcessWatcher> Builder::LaunchProcess(
- const char* fullExePath, const AZStd::vector<AZStd::string>& params) const
- {
- AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
- processLaunchInfo.m_processExecutableString = fullExePath;
- AZStd::vector<AZStd::string> commandLineArray{ fullExePath };
- commandLineArray.insert(commandLineArray.end(), params.begin(), params.end());
- processLaunchInfo.m_commandlineParameters = AZStd::move(commandLineArray);
- processLaunchInfo.m_showWindow = false;
- processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_IDLE;
- AZ_TracePrintf(
- AssetProcessor::DebugChannel,
- "Executing AssetBuilder with parameters: %s\n",
- processLaunchInfo.GetCommandLineParametersAsString().c_str());
- auto processWatcher = AZStd::unique_ptr<AzFramework::ProcessWatcher>(AzFramework::ProcessWatcher::LaunchProcess(
- processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT));
- AZ_Error(AssetProcessor::ConsoleChannel, processWatcher, "Failed to start %s", fullExePath);
- return processWatcher;
- }
- BuilderRunJobOutcome Builder::WaitForBuilderResponse(
- AssetBuilderSDK::JobCancelListener* jobCancelListener,
- AZ::u32 processTimeoutLimitInSeconds,
- AZStd::binary_semaphore* waitEvent) const
- {
- AZ::u32 exitCode = 0;
- bool finishedOK = false;
- QElapsedTimer ticker;
- AZ_Assert(waitEvent, "WaitEvent must not be null");
- ticker.start();
- while (!finishedOK)
- {
- finishedOK = waitEvent->try_acquire_for(AZStd::chrono::milliseconds(MaximumSleepTimeMs));
- PumpCommunicator();
- if (!IsValid() || ticker.elapsed() > processTimeoutLimitInSeconds * MillisecondsInASecond ||
- (jobCancelListener && jobCancelListener->IsCancelled()))
- {
- break;
- }
- }
- PumpCommunicator();
- FlushCommunicator();
- if (finishedOK)
- {
- return BuilderRunJobOutcome::Ok;
- }
- else if (!IsConnected())
- {
- AZ_Error("Builder", false, "Lost connection to asset builder %s", UuidString().c_str());
- return BuilderRunJobOutcome::LostConnection;
- }
- else if (!IsRunning(&exitCode))
- {
- // these are written to the debug channel because other messages are given for when asset builders die
- // that are more appropriate
- AZ_Error("Builder", false, "AssetBuilder %s terminated with exit code %d", UuidString().c_str(), exitCode);
- return BuilderRunJobOutcome::ProcessTerminated;
- }
- else if (jobCancelListener && jobCancelListener->IsCancelled())
- {
- AZ_Error("Builder", false, "Job request was canceled");
- TerminateProcess(
- AZ::u32(-1)); // Terminate the builder. Even if it isn't deadlocked, we can't put it back in the pool while it's busy.
- return BuilderRunJobOutcome::JobCancelled;
- }
- else
- {
- AZ_Error("Builder", false, "AssetBuilder %s failed to respond within %d seconds", UuidString().c_str(), processTimeoutLimitInSeconds);
- TerminateProcess(
- AZ::u32(-1)); // Terminate the builder. Even if it isn't deadlocked, we can't put it back in the pool while it's busy.
- return BuilderRunJobOutcome::ResponseFailure;
- }
- }
- //////////////////////////////////////////////////////////////////////////////////////////
- BuilderRef::BuilderRef(const AZStd::shared_ptr<Builder>& builder)
- : m_builder(builder)
- {
- if (m_builder)
- {
- m_builder->m_busy = true;
- }
- }
- BuilderRef::BuilderRef(BuilderRef&& rhs)
- : m_builder(AZStd::move(rhs.m_builder))
- {
- }
- BuilderRef& BuilderRef::operator=(BuilderRef&& rhs)
- {
- m_builder = AZStd::move(rhs.m_builder);
- return *this;
- }
- BuilderRef::~BuilderRef()
- {
- release();
- }
- const Builder* BuilderRef::operator->() const
- {
- return m_builder.get();
- }
- BuilderRef::operator bool() const
- {
- return m_builder != nullptr;
- }
- void BuilderRef::release()
- {
- if (m_builder)
- {
- AZ_Warning("BuilderRef", m_builder->m_busy, "Builder reference is valid but is already set to not busy");
- m_builder->m_busy = false;
- m_builder = nullptr;
- }
- }
- }
|