BuilderManager.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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 <utilities/BuilderManager.h>
  9. #include <AzCore/std/smart_ptr/make_shared.h>
  10. #include <AzCore/Utils/Utils.h>
  11. #include <AzFramework/API/ApplicationAPI.h>
  12. #include <native/connection/connectionManager.h>
  13. #include <native/connection/connection.h>
  14. #include <QCoreApplication>
  15. #include <AssetBuilder/AssetBuilderStatic.h>
  16. namespace AssetProcessor
  17. {
  18. //! Time in milliseconds to wait after each message pump cycle
  19. constexpr int IdleBuilderPumpingDelayMs = 100;
  20. BuilderManager::BuilderManager(ConnectionManager* connectionManager)
  21. {
  22. using namespace AZStd::placeholders;
  23. connectionManager->RegisterService(AssetBuilder::BuilderHelloRequest::MessageType(), AZStd::bind(&BuilderManager::IncomingBuilderPing, this, _1, _2, _3, _4, _5));
  24. // Setup a background thread to pump the idle builders so they don't get blocked trying to output to stdout/err
  25. AZStd::thread_desc desc;
  26. desc.m_name = "BuilderManager Idle Pump";
  27. m_pollingThread = AZStd::thread(desc, [this]()
  28. {
  29. while (!m_quitListener.WasQuitRequested())
  30. {
  31. PumpIdleBuilders();
  32. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(IdleBuilderPumpingDelayMs));
  33. }
  34. });
  35. m_quitListener.BusConnect();
  36. BusConnect();
  37. }
  38. BuilderManager::~BuilderManager()
  39. {
  40. PrintDebugOutput();
  41. BusDisconnect();
  42. m_quitListener.BusDisconnect();
  43. m_quitListener.ApplicationShutdownRequested();
  44. if (m_pollingThread.joinable())
  45. {
  46. m_pollingThread.join();
  47. }
  48. }
  49. void BuilderManager::ConnectionLost(AZ::u32 connId)
  50. {
  51. AZ_Assert(connId > 0, "ConnectionId was 0");
  52. AZStd::lock_guard<AZStd::mutex> lock(m_buildersMutex);
  53. if(auto uuidString = m_builderList.RemoveByConnectionId(connId); !uuidString.empty())
  54. {
  55. AZ_TracePrintf("BuilderManager", "Lost connection to builder %s\n", uuidString.c_str());
  56. }
  57. }
  58. void BuilderManager::IncomingBuilderPing(AZ::u32 connId, AZ::u32 /*type*/, AZ::u32 serial, QByteArray payload, QString platform)
  59. {
  60. AssetBuilder::BuilderHelloRequest requestPing;
  61. AssetBuilder::BuilderHelloResponse responsePing;
  62. if (!AZ::Utils::LoadObjectFromBufferInPlace(payload.data(), payload.length(), requestPing))
  63. {
  64. AZ_Error("BuilderManager", false,
  65. "Failed to deserialize BuilderHelloRequest.\n"
  66. "Your builder(s) may need recompilation to function correctly as this kind of failure usually indicates that "
  67. "there is a disparity between the version of asset processor running and the version of builder dll files present in the "
  68. "'builders' subfolder.");
  69. }
  70. else
  71. {
  72. AZStd::lock_guard<AZStd::mutex> lock(m_buildersMutex);
  73. auto builder = m_builderList.Find(requestPing.m_uuid);
  74. if (!builder)
  75. {
  76. if (m_allowUnmanagedBuilderConnections)
  77. {
  78. AZ_TracePrintf("BuilderManager", "External builder connection accepted for ProcessJob work\n");
  79. builder = AddNewBuilder(BuilderPurpose::ProcessJob); // We only accept external connections for ProcessJob builders
  80. }
  81. else
  82. {
  83. AZ_Warning(
  84. "BuilderManager",
  85. false,
  86. "Received request ping from builder but could not match uuid %s to list of builders started by this AssetProcessor instance. "
  87. "If you intended to connect an external builder, please set BuilderManager::m_allowUnmanagedBuilderConnections to true to allow this.",
  88. requestPing.m_uuid.ToString<AZStd::string>().c_str());
  89. }
  90. }
  91. if (builder)
  92. {
  93. if (builder->IsConnected())
  94. {
  95. AZ_Error("BuilderManager", false, "Builder %s is already connected and should not be sending another ping. Something has gone wrong. There may be multiple builders with the same UUID", builder->UuidString().c_str());
  96. }
  97. else
  98. {
  99. AZ_TracePrintf("BuilderManager", "Builder %s connected, connId: %d\n", builder->UuidString().c_str(), connId);
  100. builder->SetConnection(connId);
  101. responsePing.m_accepted = true;
  102. responsePing.m_uuid = builder->GetUuid();
  103. }
  104. }
  105. }
  106. AssetProcessor::ConnectionBus::Event(connId, &AssetProcessor::ConnectionBusTraits::SendResponse, serial, responsePing);
  107. }
  108. AZStd::shared_ptr<Builder> BuilderManager::AddNewBuilder(BuilderPurpose purpose)
  109. {
  110. AZ::Uuid builderUuid;
  111. // Make sure that we don't already have a builder with the same UUID. If we do, try generating another one
  112. constexpr int MaxRetryCount = 10;
  113. int retriesRemaining = MaxRetryCount;
  114. do
  115. {
  116. builderUuid = AZ::Uuid::CreateRandom();
  117. --retriesRemaining;
  118. } while (m_builderList.Find(builderUuid) && retriesRemaining > 0);
  119. if(m_builderList.Find(builderUuid))
  120. {
  121. AZ_Error("BuilderManager", false, "Failed to generate a unique id for new builder after %d attempts. All attempted random ids were already taken.", MaxRetryCount);
  122. return {};
  123. }
  124. auto builder = AZStd::make_shared<Builder>(m_quitListener, builderUuid);
  125. m_builderList.AddBuilder(builder, purpose);
  126. return builder;
  127. }
  128. void BuilderManager::AddAssetToBuilderProcessedList(const AZ::Uuid& builderId, const AZStd::string& sourceAsset)
  129. {
  130. m_builderDebugOutput[builderId].m_assetsProcessed.push_back(sourceAsset);
  131. }
  132. BuilderRef BuilderManager::GetBuilder(BuilderPurpose purpose)
  133. {
  134. AZStd::shared_ptr<Builder> newBuilder;
  135. BuilderRef builderRef;
  136. if (m_quitListener.WasQuitRequested())
  137. {
  138. // don't hand out new builders if we're quitting.
  139. return builderRef;
  140. }
  141. // the below scope is intentional, to contain the scoped lock.
  142. {
  143. AZStd::unique_lock<AZStd::mutex> lock(m_buildersMutex);
  144. if (purpose != BuilderPurpose::Registration)
  145. {
  146. auto builder = m_builderList.GetFirst(purpose);
  147. if (builder)
  148. {
  149. return builder;
  150. }
  151. }
  152. AZ_TracePrintf("BuilderManager", "Starting new builder for job request\n");
  153. // None found, start up a new one
  154. newBuilder = AddNewBuilder(purpose);
  155. // Grab a reference so no one else can take it while we're outside the lock
  156. builderRef = BuilderRef(newBuilder);
  157. }
  158. AZ::Outcome<void, AZStd::string> builderStartResult = newBuilder->Start(purpose);
  159. if (!builderStartResult.IsSuccess())
  160. {
  161. AZ_Error("BuilderManager", false, "Builder failed to start with error %.*s", AZ_STRING_ARG(builderStartResult.GetError()));
  162. AZStd::unique_lock<AZStd::mutex> lock(m_buildersMutex);
  163. builderRef = {}; // Release after the lock to make sure no one grabs it before we can delete it
  164. m_builderList.RemoveByUuid(newBuilder->GetUuid());
  165. }
  166. else
  167. {
  168. AZ_TracePrintf("BuilderManager", "Builder started successfully\n");
  169. }
  170. return builderRef;
  171. }
  172. void BuilderManager::PumpIdleBuilders()
  173. {
  174. AZStd::lock_guard<AZStd::mutex> lock(m_buildersMutex);
  175. m_builderList.PumpIdleBuilders();
  176. }
  177. void BuilderManager::PrintDebugOutput()
  178. {
  179. // If debug output was tracked, print it on shutdown.
  180. // This prints each asset that was processed by each builder, in the order they were processed.
  181. // This is useful for tracing issues like memory leaks across assets processed by the same builder.
  182. for (auto builderInfo : m_builderDebugOutput)
  183. {
  184. AZ_TracePrintf("BuilderManager", "Builder %.*s processed these assets:\n",
  185. AZ_STRING_ARG(builderInfo.first.ToString<AZStd::string>()));
  186. for (auto asset : builderInfo.second.m_assetsProcessed)
  187. {
  188. AZ_TracePrintf(
  189. "BuilderManager", "Builder with ID %.*s processed %.*s\n",
  190. AZ_STRING_ARG(builderInfo.first.ToFixedString()),
  191. AZ_STRING_ARG(asset));
  192. }
  193. }
  194. }
  195. } // namespace AssetProcessor