BGParseManager.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "ParserPch.h"
  6. #include "Memory/AutoPtr.h"
  7. #include "Common/Event.h"
  8. #include "Base/ThreadContextTlsEntry.h"
  9. #include "Base/ThreadBoundThreadContextManager.h"
  10. #include "Base/Utf8SourceInfo.h"
  11. #include "BGParseManager.h"
  12. #include "Base/ScriptContext.h"
  13. #include "ByteCodeSerializer.h"
  14. #define BGPARSE_FLAGS (fscrGlobalCode | fscrWillDeferFncParse | fscrCanDeferFncParse | fscrCreateParserState)
  15. // Global, process singleton
  16. BGParseManager* BGParseManager::s_BGParseManager = nullptr;
  17. DWORD BGParseManager::s_lastCookie = 0;
  18. DWORD BGParseManager::s_completed = 0;
  19. DWORD BGParseManager::s_failed = 0;
  20. CriticalSection BGParseManager::s_staticMemberLock;
  21. // Static member management
  22. BGParseManager* BGParseManager::GetBGParseManager()
  23. {
  24. AutoCriticalSection lock(&s_staticMemberLock);
  25. if (s_BGParseManager == nullptr)
  26. {
  27. AUTO_NESTED_HANDLED_EXCEPTION_TYPE(ExceptionType_DisableCheck);
  28. s_BGParseManager = HeapNew(BGParseManager);
  29. s_BGParseManager->Processor()->AddManager(s_BGParseManager);
  30. }
  31. return s_BGParseManager;
  32. }
  33. void BGParseManager::DeleteBGParseManager()
  34. {
  35. AutoCriticalSection lock(&s_staticMemberLock);
  36. if (s_BGParseManager != nullptr)
  37. {
  38. BGParseManager* manager = s_BGParseManager;
  39. s_BGParseManager = nullptr;
  40. HeapDelete(manager);
  41. }
  42. }
  43. DWORD BGParseManager::GetNextCookie()
  44. {
  45. AutoCriticalSection lock(&s_staticMemberLock);
  46. return ++s_lastCookie;
  47. }
  48. DWORD BGParseManager::IncCompleted()
  49. {
  50. AutoCriticalSection lock(&s_staticMemberLock);
  51. return ++s_completed;
  52. }
  53. DWORD BGParseManager::IncFailed()
  54. {
  55. AutoCriticalSection lock(&s_staticMemberLock);
  56. return ++s_failed;
  57. }
  58. // Note: runs on any thread
  59. BGParseManager::BGParseManager()
  60. : JsUtil::WaitableJobManager(ThreadBoundThreadContextManager::GetSharedJobProcessor())
  61. {
  62. }
  63. // Note: runs on any thread
  64. BGParseManager::~BGParseManager()
  65. {
  66. // First, remove the manager from the JobProcessor so that any remaining jobs
  67. // are moved back into this manager's processed workitem list
  68. Processor()->RemoveManager(this);
  69. Assert(this->workitemsProcessing.IsEmpty());
  70. // Now, free all remaining processed jobs
  71. BGParseWorkItem *item = (BGParseWorkItem*)this->workitemsProcessed.Head();
  72. while (item != nullptr)
  73. {
  74. // Get the next item first so that the current item
  75. // can be safely removed and freed
  76. BGParseWorkItem* temp = item;
  77. item = (BGParseWorkItem*)item->Next();
  78. this->workitemsProcessed.Unlink(temp);
  79. HeapDelete(temp);
  80. }
  81. }
  82. // Returns the BGParseWorkItem that matches the provided cookie. Parameters have the following impact:
  83. // - waitForResults: creates an event on the returned WorkItem that the caller can wait for
  84. // - removeJob: removes the job from the list that contained it, and marks it for discard if it is processing
  85. // Note: runs on any thread
  86. BGParseWorkItem* BGParseManager::FindJob(DWORD dwCookie, bool waitForResults, bool removeJob)
  87. {
  88. Assert(dwCookie != 0);
  89. Assert(!waitForResults || !removeJob);
  90. AutoOptionalCriticalSection autoLock(Processor()->GetCriticalSection());
  91. BGParseWorkItem* matchedWorkitem = nullptr;
  92. // First, look among processed jobs
  93. for (BGParseWorkItem *item = this->workitemsProcessed.Head(); item != nullptr && matchedWorkitem == nullptr; item = (BGParseWorkItem*)item->Next())
  94. {
  95. if (item->GetCookie() == dwCookie)
  96. {
  97. matchedWorkitem = item;
  98. if (removeJob)
  99. {
  100. this->workitemsProcessed.Unlink(matchedWorkitem);
  101. }
  102. }
  103. }
  104. if (matchedWorkitem == nullptr)
  105. {
  106. // Then, look among processing jobs
  107. for (BGParseWorkItem *item = this->workitemsProcessing.Head(); item != nullptr && matchedWorkitem == nullptr; item = (BGParseWorkItem*)item->Next())
  108. {
  109. if (item->GetCookie() == dwCookie)
  110. {
  111. matchedWorkitem = item;
  112. if (removeJob)
  113. {
  114. this->workitemsProcessing.Unlink(matchedWorkitem);
  115. // Since the job is still processing, it cannot be freed immediately. Mark it as discarded so
  116. // that it can be freed later.
  117. matchedWorkitem->Discard();
  118. }
  119. }
  120. }
  121. // Lastly, look among queued jobs
  122. if (matchedWorkitem == nullptr)
  123. {
  124. Processor()->ForEachJob([&](JsUtil::Job * job) {
  125. if (job->Manager() == this)
  126. {
  127. BGParseWorkItem* workitem = (BGParseWorkItem*)job;
  128. if (workitem->GetCookie() == dwCookie)
  129. {
  130. matchedWorkitem = workitem;
  131. return false;
  132. }
  133. }
  134. return true;
  135. });
  136. if (removeJob && matchedWorkitem != nullptr)
  137. {
  138. Processor()->RemoveJob(matchedWorkitem);
  139. }
  140. }
  141. // Since this job isn't already processed and the caller needs the results, create an event
  142. // that the caller can wait on for results to complete
  143. if (waitForResults && matchedWorkitem != nullptr)
  144. {
  145. // TODO: Is it possible for one event to be shared to reduce the number of heap allocations?
  146. matchedWorkitem->CreateCompletionEvent();
  147. }
  148. }
  149. return matchedWorkitem;
  150. }
  151. // Creates a new job to parse the provided script on a background thread
  152. // Note: runs on any thread
  153. HRESULT BGParseManager::QueueBackgroundParse(LPCUTF8 pszSrc, size_t cbLength, char16 *fullPath, DWORD* dwBgParseCookie)
  154. {
  155. HRESULT hr = S_OK;
  156. if (cbLength > 0)
  157. {
  158. BGParseWorkItem* workitem;
  159. {
  160. AUTO_NESTED_HANDLED_EXCEPTION_TYPE(ExceptionType_DisableCheck);
  161. workitem = HeapNew(BGParseWorkItem, this, (const byte *)pszSrc, cbLength, fullPath);
  162. }
  163. // Add the job to the processor
  164. {
  165. AutoOptionalCriticalSection autoLock(Processor()->GetCriticalSection());
  166. Processor()->AddJob(workitem, false /*prioritize*/);
  167. }
  168. (*dwBgParseCookie) = workitem->GetCookie();
  169. if (PHASE_TRACE1(Js::BgParsePhase))
  170. {
  171. Js::Tick now = Js::Tick::Now();
  172. Output::Print(
  173. _u("[BgParse: Start -- cookie: %04d on thread 0x%X at %.2f ms -- %s]\n"),
  174. workitem->GetCookie(),
  175. ::GetCurrentThreadId(),
  176. now.ToMilliseconds(),
  177. fullPath
  178. );
  179. }
  180. }
  181. else
  182. {
  183. hr = E_INVALIDARG;
  184. }
  185. return hr;
  186. }
  187. // Returns the data provided when the parse was queued
  188. // Note: runs on any thread, but the buffer lifetimes are not guaranteed after parse results are returned
  189. HRESULT BGParseManager::GetInputFromCookie(DWORD cookie, LPCUTF8* ppszSrc, size_t* pcbLength)
  190. {
  191. HRESULT hr = E_FAIL;
  192. // Find the job associated with this cookie
  193. BGParseWorkItem* workitem = FindJob(cookie, false /*waitForResults*/, false /*removeJob*/);
  194. if (workitem != nullptr)
  195. {
  196. (*ppszSrc) = workitem->GetScriptSrc();
  197. (*pcbLength) = workitem->GetScriptLength();
  198. hr = S_OK;
  199. }
  200. return hr;
  201. }
  202. // Deserializes the background parse results into this thread
  203. // Note: *must* run on a UI/Execution thread with an available ScriptContext
  204. HRESULT BGParseManager::GetParseResults(
  205. Js::ScriptContext* scriptContextUI,
  206. DWORD cookie,
  207. LPCUTF8 pszSrc,
  208. SRCINFO const * pSrcInfo,
  209. Js::ParseableFunctionInfo** ppFunc,
  210. CompileScriptException* pse,
  211. size_t& srcLength,
  212. Js::Utf8SourceInfo* utf8SourceInfo,
  213. uint& sourceIndex)
  214. {
  215. // TODO: Is there a way to cache the environment from which serialization begins to
  216. // determine whether or not deserialization will succeed? Specifically, being able
  217. // to assert/compare the flags used during background parse with the flags expected
  218. // from the UI thread?
  219. HRESULT hr = E_FAIL;
  220. // Find the job associated with this cookie
  221. BGParseWorkItem* workitem = FindJob(cookie, true /*waitForResults*/, false /*removeJob*/);
  222. if (workitem != nullptr)
  223. {
  224. // Synchronously wait for the job to complete
  225. workitem->WaitForCompletion();
  226. Js::FunctionBody* functionBody = nullptr;
  227. hr = workitem->DeserializeParseResults(scriptContextUI, pszSrc, pSrcInfo, utf8SourceInfo, &functionBody, srcLength, sourceIndex);
  228. (*ppFunc) = functionBody;
  229. workitem->TransferCSE(pse);
  230. if (hr == S_OK)
  231. {
  232. BGParseManager::IncCompleted();
  233. }
  234. else
  235. {
  236. BGParseManager::IncFailed();
  237. }
  238. }
  239. if (PHASE_TRACE1(Js::BgParsePhase))
  240. {
  241. Js::Tick now = Js::Tick::Now();
  242. Output::Print(
  243. _u("[BgParse: End -- cookie: %04d on thread 0x%X at %.2f ms -- hr: 0x%X]\n"),
  244. workitem != nullptr ? workitem->GetCookie() : -1,
  245. ::GetCurrentThreadId(),
  246. now.ToMilliseconds(),
  247. hr
  248. );
  249. }
  250. return hr;
  251. }
  252. // Finds and removes the workitem associated with the provided cookie. If the workitem is processed
  253. // or not yet processed, the workitem is simply removed and freed. If the workitem is being processed,
  254. // it is removed from the list and will be freed after the job is processed (with its script source
  255. // buffer).
  256. // Returns true when the caller should free the script source buffer. Otherwise, when false is returned,
  257. // the workitem is responsible for freeing the script source buffer.
  258. bool BGParseManager::DiscardParseResults(DWORD cookie, void* buffer)
  259. {
  260. BGParseWorkItem* workitem = FindJob(cookie, false /*waitForResults*/, true /*removeJob*/);
  261. bool callerOwnsSourceBuffer = true;
  262. if (workitem != nullptr)
  263. {
  264. Assert(buffer == workitem->GetScriptSrc());
  265. if (!workitem->IsDiscarded())
  266. {
  267. HeapDelete(workitem);
  268. }
  269. else
  270. {
  271. callerOwnsSourceBuffer = false;
  272. }
  273. }
  274. if (PHASE_TRACE1(Js::BgParsePhase))
  275. {
  276. Js::Tick now = Js::Tick::Now();
  277. Output::Print(
  278. _u("[BgParse: Discard -- cookie: %04d on thread 0x%X at %.2f ms, workitem: 0x%p, workitem owns buffer: %u]\n"),
  279. cookie,
  280. ::GetCurrentThreadId(),
  281. now.ToMilliseconds(),
  282. workitem,
  283. !callerOwnsSourceBuffer
  284. );
  285. }
  286. return callerOwnsSourceBuffer;
  287. }
  288. // Overloaded function called by JobProcessor to do work
  289. // Note: runs on background thread
  290. bool BGParseManager::Process(JsUtil::Job *const job, JsUtil::ParallelThreadData *threadData)
  291. {
  292. #if ENABLE_BACKGROUND_JOB_PROCESSOR
  293. Assert(job->Manager() == this);
  294. // Create script context on this thread
  295. ThreadContext* threadContext = ThreadBoundThreadContextManager::EnsureContextForCurrentThread();
  296. // If there is no script context created for this thread yet, create it now
  297. if (threadData->scriptContextBG == nullptr)
  298. {
  299. threadData->scriptContextBG = Js::ScriptContext::New(threadContext);
  300. threadData->scriptContextBG->Initialize();
  301. threadData->canDecommit = true;
  302. }
  303. // Parse the workitem's data
  304. BGParseWorkItem* workItem = (BGParseWorkItem*)job;
  305. workItem->ParseUTF8Core(threadData->scriptContextBG);
  306. return true;
  307. #else
  308. Assert(!"BGParseManager does not work without ThreadContext");
  309. return false;
  310. #endif
  311. }
  312. // Callback before the provided job will be processed
  313. // Note: runs on any thread
  314. void BGParseManager::JobProcessing(JsUtil::Job* job)
  315. {
  316. Assert(job->Manager() == this);
  317. Assert(Processor()->GetCriticalSection()->IsLocked());
  318. this->workitemsProcessing.LinkToEnd((BGParseWorkItem*)job);
  319. }
  320. // Callback after the provided job was processed. succeeded is true if the job
  321. // was executed as well.
  322. // Note: runs on any thread
  323. void BGParseManager::JobProcessed(JsUtil::Job *const job, const bool succeeded)
  324. {
  325. Assert(job->Manager() == this);
  326. Assert(Processor()->GetCriticalSection()->IsLocked());
  327. BGParseWorkItem* workItem = (BGParseWorkItem*)job;
  328. if (succeeded)
  329. {
  330. Assert(!this->workitemsProcessed.Contains(workItem));
  331. if (this->workitemsProcessing.Contains(workItem))
  332. {
  333. // Move this processed workitem from the processing list to
  334. // the processed list
  335. this->workitemsProcessing.Unlink(workItem);
  336. this->workitemsProcessed.LinkToEnd(workItem);
  337. }
  338. else
  339. {
  340. // If this workitem isn't in the processing queue, it should
  341. // already be discarded
  342. Assert(workItem->IsDiscarded());
  343. }
  344. }
  345. else
  346. {
  347. // When the manager is shutting down, workitems are processed through the JobProcessor
  348. // without executing (i.e., !succeeded). So, mark it for discard so that it can be freed.
  349. workItem->Discard();
  350. }
  351. workItem->JobProcessed(succeeded);
  352. }
  353. // Define needed for jobs.inl
  354. // Note: Runs on any thread
  355. BGParseWorkItem* BGParseManager::GetJob(BGParseWorkItem* workitem)
  356. {
  357. Assert(!"BGParseManager::WasAddedToJobProcessor");
  358. return nullptr;
  359. }
  360. // Define needed for jobs.inl
  361. // Note: Runs on any thread
  362. bool BGParseManager::WasAddedToJobProcessor(JsUtil::Job *const job) const
  363. {
  364. Assert(!"BGParseManager::WasAddedToJobProcessor");
  365. return true;
  366. }
  367. // Note: runs on any thread
  368. BGParseWorkItem::BGParseWorkItem(
  369. BGParseManager* manager,
  370. const byte* pszScript,
  371. size_t cbScript,
  372. char16 *fullPath
  373. )
  374. : JsUtil::Job(manager),
  375. script(pszScript),
  376. cb(cbScript),
  377. path(nullptr),
  378. parseHR(S_OK),
  379. parseSourceLength(0),
  380. bufferReturn(nullptr),
  381. bufferReturnBytes(0),
  382. complete(nullptr),
  383. discarded(false)
  384. {
  385. this->cookie = BGParseManager::GetNextCookie();
  386. Assert(fullPath != nullptr);
  387. this->path = SysAllocString(fullPath);
  388. }
  389. BGParseWorkItem::~BGParseWorkItem()
  390. {
  391. SysFreeString(this->path);
  392. if (this->complete != nullptr)
  393. {
  394. HeapDelete(this->complete);
  395. }
  396. if (this->bufferReturn != nullptr)
  397. {
  398. ::CoTaskMemFree(this->bufferReturn);
  399. }
  400. if (this->discarded)
  401. {
  402. // When this workitem has been discarded, this is the last reference
  403. // to the script source, so free it now during destruction.
  404. ::HeapFree(GetProcessHeap(), 0, (void*)this->script);
  405. }
  406. }
  407. void BGParseWorkItem::TransferCSE(CompileScriptException* pse)
  408. {
  409. this->cse.CopyInto(pse);
  410. }
  411. // This function parses the input data cached in BGParseWorkItem and caches the
  412. // seralized bytecode
  413. // Note: runs on BackgroundJobProcessor thread
  414. // Note: All exceptions are caught by BackgroundJobProcessor
  415. void BGParseWorkItem::ParseUTF8Core(Js::ScriptContext* scriptContext)
  416. {
  417. if (PHASE_TRACE1(Js::BgParsePhase))
  418. {
  419. Js::Tick now = Js::Tick::Now();
  420. Output::Print(
  421. _u("[BgParse: Parse -- cookie: %04d on thread 0x%X at %.2f ms]\n"),
  422. GetCookie(),
  423. ::GetCurrentThreadId(),
  424. now.ToMilliseconds()
  425. );
  426. }
  427. Js::AutoDynamicCodeReference dynamicFunctionReference(scriptContext);
  428. SourceContextInfo* sourceContextInfo = scriptContext->GetSourceContextInfo(this->cookie, nullptr);
  429. if (sourceContextInfo == nullptr)
  430. {
  431. sourceContextInfo = scriptContext->CreateSourceContextInfo(this->cookie, this->path, wcslen(this->path), nullptr);
  432. }
  433. SRCINFO si = {
  434. sourceContextInfo,
  435. 0, // dlnHost
  436. 0, // ulColumnHost
  437. 0, // lnMinHost
  438. 0, // ichMinHost
  439. static_cast<ULONG>(cb / sizeof(utf8char_t)), // ichLimHost
  440. 0, // ulCharOffset
  441. kmodGlobal, // mod
  442. 0 // grfsi
  443. };
  444. ENTER_PINNED_SCOPE(Js::Utf8SourceInfo, sourceInfo);
  445. sourceInfo = Js::Utf8SourceInfo::NewWithNoCopy(scriptContext, (LPUTF8)this->script, (int32)this->cb, static_cast<int32>(this->cb), &si, false);
  446. charcount_t cchLength = 0;
  447. uint sourceIndex = 0;
  448. Js::ParseableFunctionInfo * func = nullptr;
  449. Parser ps(scriptContext);
  450. this->parseHR = scriptContext->CompileUTF8Core(
  451. ps,
  452. sourceInfo,
  453. &si,
  454. true, // fOriginalUtf8Code
  455. this->script,
  456. this->cb,
  457. BGPARSE_FLAGS,
  458. &this->cse,
  459. cchLength,
  460. this->parseSourceLength,
  461. sourceIndex,
  462. &func,
  463. nullptr // pDataCache
  464. );
  465. if (this->parseHR == S_OK)
  466. {
  467. BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("BGParseWorkItem"));
  468. Js::FunctionBody *functionBody = func->GetFunctionBody();
  469. this->parseHR = Js::ByteCodeSerializer::SerializeToBuffer(
  470. scriptContext,
  471. tempAllocator,
  472. (DWORD)this->cb,
  473. this->script,
  474. functionBody,
  475. functionBody->GetHostSrcInfo(),
  476. &this->bufferReturn,
  477. &this->bufferReturnBytes,
  478. GENERATE_BYTE_CODE_PARSER_STATE | GENERATE_BYTE_CODE_COTASKMEMALLOC
  479. );
  480. END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
  481. Assert(this->parseHR == S_OK);
  482. }
  483. else
  484. {
  485. Assert(this->cse.ei.bstrSource != nullptr);
  486. Assert(func == nullptr);
  487. }
  488. LEAVE_PINNED_SCOPE();
  489. }
  490. // Deserializes the background parse results into this thread
  491. // Note: *must* run on a UI/Execution thread with an available ScriptContext
  492. HRESULT BGParseWorkItem::DeserializeParseResults(
  493. Js::ScriptContext* scriptContextUI,
  494. LPCUTF8 pszSrc,
  495. SRCINFO const * pSrcInfo,
  496. Js::Utf8SourceInfo* utf8SourceInfo,
  497. Js::FunctionBody** functionBodyReturn,
  498. size_t& srcLength,
  499. uint& sourceIndex
  500. )
  501. {
  502. HRESULT hr = this->parseHR;
  503. if (hr == S_OK)
  504. {
  505. srcLength = this->parseSourceLength;
  506. sourceIndex = scriptContextUI->SaveSourceNoCopy(utf8SourceInfo, (int)srcLength, false /*isCesu8*/);
  507. Assert(sourceIndex != Js::Constants::InvalidSourceIndex);
  508. Field(Js::FunctionBody*) functionBody = nullptr;
  509. hr = Js::ByteCodeSerializer::DeserializeFromBuffer(
  510. scriptContextUI,
  511. BGPARSE_FLAGS,
  512. (const byte *)pszSrc,
  513. pSrcInfo,
  514. this->bufferReturn,
  515. nullptr, // nativeModule
  516. &functionBody,
  517. sourceIndex
  518. );
  519. if (hr == S_OK)
  520. {
  521. // The buffer is now owned by the output of DeserializeFromBuffer
  522. (*functionBodyReturn) = functionBody;
  523. this->bufferReturn = nullptr;
  524. this->bufferReturnBytes = 0;
  525. }
  526. }
  527. return hr;
  528. }
  529. void BGParseWorkItem::CreateCompletionEvent()
  530. {
  531. Assert(this->complete == nullptr);
  532. this->complete = HeapNew(Event, false);
  533. }
  534. // Upon notification of job processed, set the event for those waiting for this job to complete
  535. void BGParseWorkItem::JobProcessed(const bool succeeded)
  536. {
  537. Assert(Manager()->Processor()->GetCriticalSection()->IsLocked());
  538. if (IsDiscarded())
  539. {
  540. Js::Tick now = Js::Tick::Now();
  541. Output::Print(
  542. _u("[BgParse: Discard Before GetResults -- cookie: %04d on thread 0x%X at %.2f ms]\n"),
  543. GetCookie(),
  544. ::GetCurrentThreadId(),
  545. now.ToMilliseconds()
  546. );
  547. // When a workitem has been discarded while processing, there are now other
  548. // references to it, so free it now
  549. Assert((this->Next() == nullptr && this->Previous() == nullptr) || !succeeded);
  550. HeapDelete(this);
  551. }
  552. else if (this->complete != nullptr)
  553. {
  554. this->complete->Set();
  555. }
  556. }
  557. // Wait for this job to finish processing
  558. void BGParseWorkItem::WaitForCompletion()
  559. {
  560. if (this->complete != nullptr)
  561. {
  562. if (PHASE_TRACE1(Js::BgParsePhase))
  563. {
  564. Js::Tick now = Js::Tick::Now();
  565. Output::Print(
  566. _u("[BgParse: Wait -- cookie: %04d on thread 0x%X at %.2f ms]\n"),
  567. GetCookie(),
  568. ::GetCurrentThreadId(),
  569. now.ToMilliseconds()
  570. );
  571. }
  572. this->complete->Wait();
  573. }
  574. }