Builder.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <QCoreApplication>
  9. #include <QElapsedTimer>
  10. #include <AzCore/Settings/CommandLine.h>
  11. #include <AzCore/Settings/SettingsRegistry.h>
  12. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  13. #include <AzCore/std/parallel/binary_semaphore.h>
  14. #include <AzCore/Utils/Utils.h>
  15. #include <utilities/Builder.h>
  16. #include <utilities/AssetBuilderInfo.h>
  17. namespace AssetProcessor
  18. {
  19. //! Amount of time in milliseconds to wait between checking the status of the AssetBuilder process and pumping the stdout/err pipes
  20. //! Should be kept fairly low to avoid the process stalling due to a full pipe but not too low to avoid wasting CPU time
  21. constexpr int MaximumSleepTimeMs = 10;
  22. constexpr int MillisecondsInASecond = 1000;
  23. constexpr const char* BuildersFolderName = "Builders";
  24. bool Builder::IsConnected() const
  25. {
  26. return m_connectionId > 0;
  27. }
  28. AZ::Outcome<void, AZStd::string> Builder::WaitForConnection()
  29. {
  30. if (m_startupWaitTimeS == 0)
  31. {
  32. const auto* settingsRegistry = AZ::SettingsRegistry::Get();
  33. if (settingsRegistry)
  34. {
  35. settingsRegistry->Get(m_startupWaitTimeS, "/Amazon/AssetProcessor/Settings/BuilderManager/StartupTimeoutSeconds");
  36. }
  37. }
  38. if (m_connectionId == 0)
  39. {
  40. bool result = false;
  41. QElapsedTimer ticker;
  42. ticker.start();
  43. while (!result)
  44. {
  45. result = m_connectionEvent.try_acquire_for(AZStd::chrono::milliseconds(MaximumSleepTimeMs));
  46. PumpCommunicator();
  47. if (ticker.elapsed() > m_startupWaitTimeS * MillisecondsInASecond || m_quitListener.WasQuitRequested() || !IsRunning())
  48. {
  49. break;
  50. }
  51. }
  52. PumpCommunicator();
  53. FlushCommunicator();
  54. if (result)
  55. {
  56. return AZ::Success();
  57. }
  58. AZ::u32 exitCode;
  59. if (m_quitListener.WasQuitRequested())
  60. {
  61. AZ_TracePrintf(AssetProcessor::DebugChannel, "Aborting waiting for builder, quit requested\n");
  62. }
  63. else if (!IsRunning(&exitCode))
  64. {
  65. AZ_Error("Builder", false, "AssetBuilder terminated during start up with exit code %d", exitCode);
  66. }
  67. else
  68. {
  69. AZ_Error("Builder", false, "AssetBuilder failed to connect within %d seconds", m_startupWaitTimeS);
  70. }
  71. return AZ::Failure(AZStd::string::format("Connection failed to builder %.*s", AZ_STRING_ARG(UuidString())));
  72. }
  73. return AZ::Success();
  74. }
  75. void Builder::SetConnection(AZ::u32 connId)
  76. {
  77. m_connectionId = connId;
  78. m_connectionEvent.release();
  79. }
  80. AZ::u32 Builder::GetConnectionId() const
  81. {
  82. return m_connectionId;
  83. }
  84. AZ::Uuid Builder::GetUuid() const
  85. {
  86. return m_uuid;
  87. }
  88. AZStd::string Builder::UuidString() const
  89. {
  90. return m_uuid.ToString<AZStd::string>(false, false);
  91. }
  92. void Builder::PumpCommunicator() const
  93. {
  94. if (m_tracePrinter)
  95. {
  96. m_tracePrinter->Pump();
  97. }
  98. }
  99. void Builder::FlushCommunicator() const
  100. {
  101. if (m_tracePrinter)
  102. {
  103. m_tracePrinter->Flush();
  104. }
  105. }
  106. void Builder::TerminateProcess(AZ::u32 exitCode) const
  107. {
  108. if (m_processWatcher)
  109. {
  110. m_processWatcher->TerminateProcess(exitCode);
  111. }
  112. }
  113. AZ::Outcome<void, AZStd::string> Builder::Start(BuilderPurpose purpose)
  114. {
  115. // Get the current BinXXX folder based on the current running AP
  116. QString applicationDir = QCoreApplication::instance()->applicationDirPath();
  117. // Construct the Builders subfolder path
  118. AZStd::string buildersFolder;
  119. AzFramework::StringFunc::Path::Join(applicationDir.toUtf8().constData(), BuildersFolderName, buildersFolder);
  120. // Construct the full exe for the builder.exe
  121. const AZStd::string fullExePathString =
  122. QDir(applicationDir).absoluteFilePath(AssetProcessor::s_assetBuilderRelativePath).toUtf8().constData();
  123. if (m_quitListener.WasQuitRequested())
  124. {
  125. return AZ::Failure(AZStd::string("Cannot start builder, quit was requested"));
  126. }
  127. const AZStd::vector<AZStd::string> params = BuildParams("resident", buildersFolder.c_str(), UuidString(), "", "", purpose);
  128. m_processWatcher = LaunchProcess(fullExePathString.c_str(), params);
  129. if (!m_processWatcher)
  130. {
  131. return AZ::Failure(AZStd::string::format("Failed to start process watcher for Builder %.*s.", AZ_STRING_ARG(UuidString())));
  132. }
  133. // Currently, this uses polling for managing the trace printing output because the job log redirections rely on thread local
  134. // storage to route different jobs to different logs. If the trace printing spins up a new thread for printing, it won't
  135. // redirect to the correct job logs.
  136. m_tracePrinter = AZStd::make_unique<ProcessCommunicatorTracePrinter>(
  137. m_processWatcher->GetCommunicator(), "AssetBuilder", ProcessCommunicatorTracePrinter::TraceProcessing::Poll);
  138. return WaitForConnection();
  139. }
  140. bool Builder::IsValid() const
  141. {
  142. return m_connectionId != 0 && IsRunning();
  143. }
  144. bool Builder::IsRunning(AZ::u32* exitCode) const
  145. {
  146. return !m_processWatcher || (m_processWatcher && m_processWatcher->IsProcessRunning(exitCode));
  147. }
  148. AZStd::vector<AZStd::string> Builder::BuildParams(
  149. const char* task,
  150. const char* moduleFilePath,
  151. const AZStd::string& builderGuid,
  152. const AZStd::string& jobDescriptionFile,
  153. const AZStd::string& jobResponseFile,
  154. BuilderPurpose purpose) const
  155. {
  156. QDir projectCacheRoot;
  157. AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
  158. AZ::SettingsRegistryInterface::FixedValueString projectName = AZ::Utils::GetProjectName();
  159. AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
  160. AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
  161. int portNumber = 0;
  162. ApplicationServerBus::BroadcastResult(portNumber, &ApplicationServerBus::Events::GetServerListeningPort);
  163. AZStd::vector<AZStd::string> params;
  164. params.emplace_back(AZStd::string::format(R"(-task="%s")", task));
  165. params.emplace_back(AZStd::string::format(R"(-id="%s")", builderGuid.c_str()));
  166. params.emplace_back(AZStd::string::format(R"(-project-name="%s")", projectName.c_str()));
  167. params.emplace_back(AZStd::string::format(R"(-project-cache-path="%s")", projectCacheRoot.absolutePath().toUtf8().constData()));
  168. params.emplace_back(AZStd::string::format(R"(-project-path="%s")", projectPath.c_str()));
  169. params.emplace_back(AZStd::string::format(R"(-engine-path="%s")", enginePath.c_str()));
  170. params.emplace_back(AZStd::string::format("-port=%d", portNumber));
  171. if (purpose == BuilderPurpose::Registration)
  172. {
  173. params.emplace_back("--register");
  174. }
  175. if (moduleFilePath && moduleFilePath[0])
  176. {
  177. params.emplace_back(AZStd::string::format(R"(-module="%s")", moduleFilePath));
  178. }
  179. if (!jobDescriptionFile.empty() && !jobResponseFile.empty())
  180. {
  181. params.emplace_back(AZStd::string::format(R"(-input="%s")", jobDescriptionFile.c_str()));
  182. params.emplace_back(AZStd::string::format(R"(-output="%s")", jobResponseFile.c_str()));
  183. }
  184. auto settingsRegistry = AZ::SettingsRegistry::Get();
  185. AZ::CommandLine commandLine;
  186. AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(*settingsRegistry, commandLine);
  187. AZStd::fixed_vector optionKeys{ "regset", "regremove" };
  188. for (auto&& optionKey : optionKeys)
  189. {
  190. size_t commandOptionCount = commandLine.GetNumSwitchValues(optionKey);
  191. for (size_t optionIndex = 0; optionIndex < commandOptionCount; ++optionIndex)
  192. {
  193. const AZStd::string& optionValue = commandLine.GetSwitchValue(optionKey, optionIndex);
  194. params.emplace_back(AZStd::string::format(R"(--%s="%s")", optionKey, optionValue.c_str()));
  195. }
  196. }
  197. return params;
  198. }
  199. AZStd::unique_ptr<AzFramework::ProcessWatcher> Builder::LaunchProcess(
  200. const char* fullExePath, const AZStd::vector<AZStd::string>& params) const
  201. {
  202. AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
  203. processLaunchInfo.m_processExecutableString = fullExePath;
  204. AZStd::vector<AZStd::string> commandLineArray{ fullExePath };
  205. commandLineArray.insert(commandLineArray.end(), params.begin(), params.end());
  206. processLaunchInfo.m_commandlineParameters = AZStd::move(commandLineArray);
  207. processLaunchInfo.m_showWindow = false;
  208. processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_IDLE;
  209. AZ_TracePrintf(
  210. AssetProcessor::DebugChannel,
  211. "Executing AssetBuilder with parameters: %s\n",
  212. processLaunchInfo.GetCommandLineParametersAsString().c_str());
  213. auto processWatcher = AZStd::unique_ptr<AzFramework::ProcessWatcher>(AzFramework::ProcessWatcher::LaunchProcess(
  214. processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT));
  215. AZ_Error(AssetProcessor::ConsoleChannel, processWatcher, "Failed to start %s", fullExePath);
  216. return processWatcher;
  217. }
  218. BuilderRunJobOutcome Builder::WaitForBuilderResponse(
  219. AssetBuilderSDK::JobCancelListener* jobCancelListener,
  220. AZ::u32 processTimeoutLimitInSeconds,
  221. AZStd::binary_semaphore* waitEvent) const
  222. {
  223. AZ::u32 exitCode = 0;
  224. bool finishedOK = false;
  225. QElapsedTimer ticker;
  226. AZ_Assert(waitEvent, "WaitEvent must not be null");
  227. ticker.start();
  228. while (!finishedOK)
  229. {
  230. finishedOK = waitEvent->try_acquire_for(AZStd::chrono::milliseconds(MaximumSleepTimeMs));
  231. PumpCommunicator();
  232. if (!IsValid() || ticker.elapsed() > processTimeoutLimitInSeconds * MillisecondsInASecond ||
  233. (jobCancelListener && jobCancelListener->IsCancelled()))
  234. {
  235. break;
  236. }
  237. }
  238. PumpCommunicator();
  239. FlushCommunicator();
  240. if (finishedOK)
  241. {
  242. return BuilderRunJobOutcome::Ok;
  243. }
  244. else if (!IsConnected())
  245. {
  246. AZ_Error("Builder", false, "Lost connection to asset builder %s", UuidString().c_str());
  247. return BuilderRunJobOutcome::LostConnection;
  248. }
  249. else if (!IsRunning(&exitCode))
  250. {
  251. // these are written to the debug channel because other messages are given for when asset builders die
  252. // that are more appropriate
  253. AZ_Error("Builder", false, "AssetBuilder %s terminated with exit code %d", UuidString().c_str(), exitCode);
  254. return BuilderRunJobOutcome::ProcessTerminated;
  255. }
  256. else if (jobCancelListener && jobCancelListener->IsCancelled())
  257. {
  258. AZ_Error("Builder", false, "Job request was canceled");
  259. TerminateProcess(
  260. 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.
  261. return BuilderRunJobOutcome::JobCancelled;
  262. }
  263. else
  264. {
  265. AZ_Error("Builder", false, "AssetBuilder %s failed to respond within %d seconds", UuidString().c_str(), processTimeoutLimitInSeconds);
  266. TerminateProcess(
  267. 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.
  268. return BuilderRunJobOutcome::ResponseFailure;
  269. }
  270. }
  271. //////////////////////////////////////////////////////////////////////////////////////////
  272. BuilderRef::BuilderRef(const AZStd::shared_ptr<Builder>& builder)
  273. : m_builder(builder)
  274. {
  275. if (m_builder)
  276. {
  277. m_builder->m_busy = true;
  278. }
  279. }
  280. BuilderRef::BuilderRef(BuilderRef&& rhs)
  281. : m_builder(AZStd::move(rhs.m_builder))
  282. {
  283. }
  284. BuilderRef& BuilderRef::operator=(BuilderRef&& rhs)
  285. {
  286. m_builder = AZStd::move(rhs.m_builder);
  287. return *this;
  288. }
  289. BuilderRef::~BuilderRef()
  290. {
  291. release();
  292. }
  293. const Builder* BuilderRef::operator->() const
  294. {
  295. return m_builder.get();
  296. }
  297. BuilderRef::operator bool() const
  298. {
  299. return m_builder != nullptr;
  300. }
  301. void BuilderRef::release()
  302. {
  303. if (m_builder)
  304. {
  305. AZ_Warning("BuilderRef", m_builder->m_busy, "Builder reference is valid but is already set to not busy");
  306. m_builder->m_busy = false;
  307. m_builder = nullptr;
  308. }
  309. }
  310. }