//===--- TaskQueue.inc ------------------------------------------*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// /// /// \file /// This file contains an implementation of TaskQueue for Windows platform. /// //===----------------------------------------------------------------------===// #include "swift/Basic/LLVM.h" #include "swift/Basic/TaskQueue.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Signals.h" #define NOMINMAX #include #include #include "Default/Task.inc" using namespace llvm::sys; namespace { class TaskMonitor { std::queue> &TasksToBeExecuted; llvm::SmallVector, 32> TasksBeingExecuted; const unsigned MaxNumberOfParallelTasks; public: struct Callbacks { const TaskQueue::TaskBeganCallback TaskBegan; const TaskQueue::TaskFinishedCallback TaskFinished; const TaskQueue::TaskSignalledCallback TaskSignalled; }; private: Callbacks Cbs; bool startUpSomeTasks() { while (!TasksToBeExecuted.empty() && TasksBeingExecuted.size() < MaxNumberOfParallelTasks) { std::unique_ptr T(std::move(TasksToBeExecuted.front())); TasksToBeExecuted.pop(); if (T->execute()) return true; if (Cbs.TaskBegan) Cbs.TaskBegan(T->PI.Pid, T->Context); TasksBeingExecuted.push_back(std::move(T)); } return false; } bool waitForFinishedTask() { if (TasksBeingExecuted.empty()) return false; llvm::SmallVector Handles; for (auto &TBE : TasksBeingExecuted) Handles.push_back((HANDLE)TBE->PI.Process); DWORD Finished = WaitForMultipleObjects(Handles.size(), Handles.data(), FALSE, INFINITE); if (Finished < WAIT_OBJECT_0 || Finished >= WAIT_OBJECT_0 + Handles.size()) return true; size_t Index = Finished - WAIT_OBJECT_0; if (Index + 1 != TasksBeingExecuted.size()) std::swap(TasksBeingExecuted[Index], TasksBeingExecuted.back()); std::unique_ptr T(std::move(TasksBeingExecuted.back())); TasksBeingExecuted.pop_back(); DWORD Status = 0; BOOL RC = GetExitCodeProcess(T->PI.Process, &Status); DWORD Err = GetLastError(); if (Err != ERROR_INVALID_HANDLE) CloseHandle(T->PI.Process); if (!RC) { SetLastError(Err); // -2 indicates a crash or timeout as opposed to failure to execute. // See the comments on llvm::sys::Wait for more details. T->PI.ReturnCode = -2; } // Convert the return code in the same way as llvm::sys::Wait does. if (Status != 0) { // Pass 10(Warning) and 11(Error) to the callee as negative value. // For more information on status codes, see // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/87fba13e-bf06-450e-83b1-9241dc81e781 if ((Status & 0xBFFF0000U) == 0x80000000U) T->PI.ReturnCode = static_cast(Status); else if (Status & 0xFF) T->PI.ReturnCode = Status & 0x7FFFFFFF; else T->PI.ReturnCode = 1; } auto StdoutBuffer = llvm::MemoryBuffer::getFile(T->StdoutPath); StringRef StdoutContents = StdoutBuffer.get()->getBuffer(); StringRef StderrContents; if (T->SeparateErrors) { auto StderrBuffer = llvm::MemoryBuffer::getFile(T->StderrPath); StderrContents = StderrBuffer.get()->getBuffer(); } bool Crashed = T->PI.ReturnCode & 0xC0000000; FILETIME CreationTime; FILETIME ExitTime; FILETIME UtimeTicks; FILETIME StimeTicks; PROCESS_MEMORY_COUNTERS Counters = {}; GetProcessTimes(T->PI.Process, &CreationTime, &ExitTime, &StimeTicks, &UtimeTicks); // Each tick is 100ns uint64_t UTime = ((uint64_t)UtimeTicks.dwHighDateTime << 32 | UtimeTicks.dwLowDateTime) / 10; uint64_t STime = ((uint64_t)StimeTicks.dwHighDateTime << 32 | StimeTicks.dwLowDateTime) / 10; GetProcessMemoryInfo(T->PI.Process, &Counters, sizeof(Counters)); TaskProcessInformation TPI(T->PI.Pid, UTime, STime, Counters.PeakWorkingSetSize); bool ErrorOccurred = false; if (Crashed) { if (Cbs.TaskSignalled) { TaskFinishedResponse Response = Cbs.TaskSignalled(T->PI.Pid, "", StdoutContents, StderrContents, T->Context, T->PI.ReturnCode, TPI); ErrorOccurred = (Response == TaskFinishedResponse::StopExecution); } else { // If we don't have a Signalled callback, unconditionally stop. ErrorOccurred = true; } } else { // Wait() returned a normal return code, so just indicate that the task // finished. if (Cbs.TaskFinished) { TaskFinishedResponse Response = Cbs.TaskFinished(T->PI.Pid, T->PI.ReturnCode, StdoutContents, StderrContents, TPI, T->Context); ErrorOccurred = (Response == TaskFinishedResponse::StopExecution); } else if (Crashed) { ErrorOccurred = true; } } llvm::sys::fs::remove(T->StdoutPath); if (T->SeparateErrors) llvm::sys::fs::remove(T->StderrPath); return ErrorOccurred; } public: TaskMonitor(std::queue> &TasksToBeExecuted, unsigned NumberOfParallelTasks, Callbacks Cbs) : TasksToBeExecuted(TasksToBeExecuted), MaxNumberOfParallelTasks(std::max(NumberOfParallelTasks, 1u)), Cbs(std::move(Cbs)) {} /// Run the tasks to be executed. /// \return true on error. bool executeTasks() { bool ErrorOccurred = false; while ((!ErrorOccurred && !TasksToBeExecuted.empty()) || !TasksBeingExecuted.empty()) { if (!ErrorOccurred) ErrorOccurred |= startUpSomeTasks(); ErrorOccurred |= waitForFinishedTask(); } return ErrorOccurred; } }; } // end namespace bool TaskQueue::supportsBufferingOutput() { return true; } bool TaskQueue::supportsParallelExecution() { return true; } unsigned TaskQueue::getNumberOfParallelTasks() const { return NumberOfParallelTasks; } void TaskQueue::addTask(const char *ExecPath, ArrayRef Args, ArrayRef Env, void *Context, bool SeparateErrors) { auto T = std::make_unique(ExecPath, Args, Env, Context, SeparateErrors); QueuedTasks.push(std::move(T)); } bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished, TaskSignalledCallback Signalled) { TaskMonitor::Callbacks Cbs{std::move(Began), std::move(Finished), std::move(Signalled)}; TaskMonitor TM(QueuedTasks, NumberOfParallelTasks, std::move(Cbs)); return TM.executeTasks(); }