mirror of
https://github.com/reactos/CMake.git
synced 2024-11-28 05:50:42 +00:00
Autogen: Factor out concurrency framework to cmWorkerPool class
This factors out the concurrency framework in `cmQtAutoGeneratorMocUic` to a dedicated class `cmWorkerPool` which might be reused in other places. `cmWorkerPool` supports fence jobs that require that - all other jobs before in the queue have been processed before the fence job processing gets started, - no jobs later in the queue will be processed before the fence job processing has been completed. Fence jobs are needed where the completion of all previous jobs in the queue is a requirement for further processing. E.g. in `cmQtAutoGeneratorMocUic` the generation of `mocs_compilation.cpp` requires that all previous source file parse jobs have been completed.
This commit is contained in:
parent
7f83e8033b
commit
8cb26a0a2a
@ -391,6 +391,8 @@ set(SRCS
|
||||
cmVariableWatch.h
|
||||
cmVersion.cxx
|
||||
cmVersion.h
|
||||
cmWorkerPool.cxx
|
||||
cmWorkerPool.h
|
||||
cmWorkingDirectory.cxx
|
||||
cmWorkingDirectory.h
|
||||
cmXMLParser.cxx
|
||||
|
@ -14,12 +14,6 @@
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmake.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
// -- Class methods
|
||||
|
||||
cmQtAutoGenerator::Logger::Logger()
|
||||
{
|
||||
// Initialize logger
|
||||
@ -431,232 +425,6 @@ bool cmQtAutoGenerator::FileSystem::MakeParentDirectory(
|
||||
return cmQtAutoGenerator::MakeParentDirectory(filename);
|
||||
}
|
||||
|
||||
int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::init(uv_loop_t* uv_loop,
|
||||
ReadOnlyProcessT* process)
|
||||
{
|
||||
Process_ = process;
|
||||
Target_ = nullptr;
|
||||
return UVPipe_.init(*uv_loop, 0, this);
|
||||
}
|
||||
|
||||
int cmQtAutoGenerator::ReadOnlyProcessT::PipeT::startRead(std::string* target)
|
||||
{
|
||||
Target_ = target;
|
||||
return uv_read_start(uv_stream(), &PipeT::UVAlloc, &PipeT::UVData);
|
||||
}
|
||||
|
||||
void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::reset()
|
||||
{
|
||||
Process_ = nullptr;
|
||||
Target_ = nullptr;
|
||||
UVPipe_.reset();
|
||||
Buffer_.clear();
|
||||
Buffer_.shrink_to_fit();
|
||||
}
|
||||
|
||||
void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVAlloc(uv_handle_t* handle,
|
||||
size_t suggestedSize,
|
||||
uv_buf_t* buf)
|
||||
{
|
||||
auto& pipe = *reinterpret_cast<PipeT*>(handle->data);
|
||||
pipe.Buffer_.resize(suggestedSize);
|
||||
buf->base = pipe.Buffer_.data();
|
||||
buf->len = pipe.Buffer_.size();
|
||||
}
|
||||
|
||||
void cmQtAutoGenerator::ReadOnlyProcessT::PipeT::UVData(uv_stream_t* stream,
|
||||
ssize_t nread,
|
||||
const uv_buf_t* buf)
|
||||
{
|
||||
auto& pipe = *reinterpret_cast<PipeT*>(stream->data);
|
||||
if (nread > 0) {
|
||||
// Append data to merged output
|
||||
if ((buf->base != nullptr) && (pipe.Target_ != nullptr)) {
|
||||
pipe.Target_->append(buf->base, nread);
|
||||
}
|
||||
} else if (nread < 0) {
|
||||
// EOF or error
|
||||
auto* proc = pipe.Process_;
|
||||
// Check it this an unusual error
|
||||
if (nread != UV_EOF) {
|
||||
if (!proc->Result()->error()) {
|
||||
proc->Result()->ErrorMessage =
|
||||
"libuv reading from pipe failed with error code ";
|
||||
proc->Result()->ErrorMessage += std::to_string(nread);
|
||||
}
|
||||
}
|
||||
// Clear libuv pipe handle and try to finish
|
||||
pipe.reset();
|
||||
proc->UVTryFinish();
|
||||
}
|
||||
}
|
||||
|
||||
void cmQtAutoGenerator::ProcessResultT::reset()
|
||||
{
|
||||
ExitStatus = 0;
|
||||
TermSignal = 0;
|
||||
if (!StdOut.empty()) {
|
||||
StdOut.clear();
|
||||
StdOut.shrink_to_fit();
|
||||
}
|
||||
if (!StdErr.empty()) {
|
||||
StdErr.clear();
|
||||
StdErr.shrink_to_fit();
|
||||
}
|
||||
if (!ErrorMessage.empty()) {
|
||||
ErrorMessage.clear();
|
||||
ErrorMessage.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
void cmQtAutoGenerator::ReadOnlyProcessT::setup(
|
||||
ProcessResultT* result, bool mergedOutput,
|
||||
std::vector<std::string> const& command, std::string const& workingDirectory)
|
||||
{
|
||||
Setup_.WorkingDirectory = workingDirectory;
|
||||
Setup_.Command = command;
|
||||
Setup_.Result = result;
|
||||
Setup_.MergedOutput = mergedOutput;
|
||||
}
|
||||
|
||||
static std::string getUVError(const char* prefixString, int uvErrorCode)
|
||||
{
|
||||
std::ostringstream ost;
|
||||
ost << prefixString << ": " << uv_strerror(uvErrorCode);
|
||||
return ost.str();
|
||||
}
|
||||
|
||||
bool cmQtAutoGenerator::ReadOnlyProcessT::start(
|
||||
uv_loop_t* uv_loop, std::function<void()>&& finishedCallback)
|
||||
{
|
||||
if (IsStarted() || (Result() == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset result before the start
|
||||
Result()->reset();
|
||||
|
||||
// Fill command string pointers
|
||||
if (!Setup().Command.empty()) {
|
||||
CommandPtr_.reserve(Setup().Command.size() + 1);
|
||||
for (std::string const& arg : Setup().Command) {
|
||||
CommandPtr_.push_back(arg.c_str());
|
||||
}
|
||||
CommandPtr_.push_back(nullptr);
|
||||
} else {
|
||||
Result()->ErrorMessage = "Empty command";
|
||||
}
|
||||
|
||||
if (!Result()->error()) {
|
||||
if (UVPipeOut_.init(uv_loop, this) != 0) {
|
||||
Result()->ErrorMessage = "libuv stdout pipe initialization failed";
|
||||
}
|
||||
}
|
||||
if (!Result()->error()) {
|
||||
if (UVPipeErr_.init(uv_loop, this) != 0) {
|
||||
Result()->ErrorMessage = "libuv stderr pipe initialization failed";
|
||||
}
|
||||
}
|
||||
if (!Result()->error()) {
|
||||
// -- Setup process stdio options
|
||||
// stdin
|
||||
UVOptionsStdIO_[0].flags = UV_IGNORE;
|
||||
UVOptionsStdIO_[0].data.stream = nullptr;
|
||||
// stdout
|
||||
UVOptionsStdIO_[1].flags =
|
||||
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
|
||||
UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream();
|
||||
// stderr
|
||||
UVOptionsStdIO_[2].flags =
|
||||
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
|
||||
UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream();
|
||||
|
||||
// -- Setup process options
|
||||
std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0);
|
||||
UVOptions_.exit_cb = &ReadOnlyProcessT::UVExit;
|
||||
UVOptions_.file = CommandPtr_[0];
|
||||
UVOptions_.args = const_cast<char**>(CommandPtr_.data());
|
||||
UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
|
||||
UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
|
||||
UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
|
||||
UVOptions_.stdio = UVOptionsStdIO_.data();
|
||||
|
||||
// -- Spawn process
|
||||
int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this);
|
||||
if (uvErrorCode != 0) {
|
||||
Result()->ErrorMessage =
|
||||
getUVError("libuv process spawn failed ", uvErrorCode);
|
||||
}
|
||||
}
|
||||
// -- Start reading from stdio streams
|
||||
if (!Result()->error()) {
|
||||
if (UVPipeOut_.startRead(&Result()->StdOut) != 0) {
|
||||
Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
|
||||
}
|
||||
}
|
||||
if (!Result()->error()) {
|
||||
if (UVPipeErr_.startRead(Setup_.MergedOutput ? &Result()->StdOut
|
||||
: &Result()->StdErr) != 0) {
|
||||
Result()->ErrorMessage = "libuv start reading from stderr pipe failed";
|
||||
}
|
||||
}
|
||||
|
||||
if (!Result()->error()) {
|
||||
IsStarted_ = true;
|
||||
FinishedCallback_ = std::move(finishedCallback);
|
||||
} else {
|
||||
// Clear libuv handles and finish
|
||||
UVProcess_.reset();
|
||||
UVPipeOut_.reset();
|
||||
UVPipeErr_.reset();
|
||||
CommandPtr_.clear();
|
||||
}
|
||||
|
||||
return IsStarted();
|
||||
}
|
||||
|
||||
void cmQtAutoGenerator::ReadOnlyProcessT::UVExit(uv_process_t* handle,
|
||||
int64_t exitStatus,
|
||||
int termSignal)
|
||||
{
|
||||
auto& proc = *reinterpret_cast<ReadOnlyProcessT*>(handle->data);
|
||||
if (proc.IsStarted() && !proc.IsFinished()) {
|
||||
// Set error message on demand
|
||||
proc.Result()->ExitStatus = exitStatus;
|
||||
proc.Result()->TermSignal = termSignal;
|
||||
if (!proc.Result()->error()) {
|
||||
if (termSignal != 0) {
|
||||
proc.Result()->ErrorMessage = "Process was terminated by signal ";
|
||||
proc.Result()->ErrorMessage +=
|
||||
std::to_string(proc.Result()->TermSignal);
|
||||
} else if (exitStatus != 0) {
|
||||
proc.Result()->ErrorMessage = "Process failed with return value ";
|
||||
proc.Result()->ErrorMessage +=
|
||||
std::to_string(proc.Result()->ExitStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset process handle and try to finish
|
||||
proc.UVProcess_.reset();
|
||||
proc.UVTryFinish();
|
||||
}
|
||||
}
|
||||
|
||||
void cmQtAutoGenerator::ReadOnlyProcessT::UVTryFinish()
|
||||
{
|
||||
// There still might be data in the pipes after the process has finished.
|
||||
// Therefore check if the process is finished AND all pipes are closed
|
||||
// before signaling the worker thread to continue.
|
||||
if (UVProcess_.get() == nullptr) {
|
||||
if (UVPipeOut_.uv_pipe() == nullptr) {
|
||||
if (UVPipeErr_.uv_pipe() == nullptr) {
|
||||
IsFinished_ = true;
|
||||
FinishedCallback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmQtAutoGenerator::cmQtAutoGenerator() = default;
|
||||
|
||||
cmQtAutoGenerator::~cmQtAutoGenerator() = default;
|
||||
|
@ -7,14 +7,8 @@
|
||||
|
||||
#include "cmFilePathChecksum.h"
|
||||
#include "cmQtAutoGen.h"
|
||||
#include "cmUVHandlePtr.h"
|
||||
#include "cm_uv.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -137,102 +131,6 @@ public:
|
||||
cmFilePathChecksum FilePathChecksum_;
|
||||
};
|
||||
|
||||
/// @brief Return value and output of an external process
|
||||
struct ProcessResultT
|
||||
{
|
||||
void reset();
|
||||
bool error() const
|
||||
{
|
||||
return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty();
|
||||
}
|
||||
|
||||
std::int64_t ExitStatus = 0;
|
||||
int TermSignal = 0;
|
||||
std::string StdOut;
|
||||
std::string StdErr;
|
||||
std::string ErrorMessage;
|
||||
};
|
||||
|
||||
/// @brief External process management class
|
||||
struct ReadOnlyProcessT
|
||||
{
|
||||
// -- Types
|
||||
|
||||
/// @brief libuv pipe buffer class
|
||||
class PipeT
|
||||
{
|
||||
public:
|
||||
int init(uv_loop_t* uv_loop, ReadOnlyProcessT* process);
|
||||
int startRead(std::string* target);
|
||||
void reset();
|
||||
|
||||
// -- Libuv casts
|
||||
uv_pipe_t* uv_pipe() { return UVPipe_.get(); }
|
||||
uv_stream_t* uv_stream()
|
||||
{
|
||||
return reinterpret_cast<uv_stream_t*>(uv_pipe());
|
||||
}
|
||||
uv_handle_t* uv_handle()
|
||||
{
|
||||
return reinterpret_cast<uv_handle_t*>(uv_pipe());
|
||||
}
|
||||
|
||||
// -- Libuv callbacks
|
||||
static void UVAlloc(uv_handle_t* handle, size_t suggestedSize,
|
||||
uv_buf_t* buf);
|
||||
static void UVData(uv_stream_t* stream, ssize_t nread,
|
||||
const uv_buf_t* buf);
|
||||
|
||||
private:
|
||||
ReadOnlyProcessT* Process_ = nullptr;
|
||||
std::string* Target_ = nullptr;
|
||||
std::vector<char> Buffer_;
|
||||
cm::uv_pipe_ptr UVPipe_;
|
||||
};
|
||||
|
||||
/// @brief Process settings
|
||||
struct SetupT
|
||||
{
|
||||
std::string WorkingDirectory;
|
||||
std::vector<std::string> Command;
|
||||
ProcessResultT* Result = nullptr;
|
||||
bool MergedOutput = false;
|
||||
};
|
||||
|
||||
// -- Const accessors
|
||||
const SetupT& Setup() const { return Setup_; }
|
||||
ProcessResultT* Result() const { return Setup_.Result; }
|
||||
bool IsStarted() const { return IsStarted_; }
|
||||
bool IsFinished() const { return IsFinished_; }
|
||||
|
||||
// -- Runtime
|
||||
void setup(ProcessResultT* result, bool mergedOutput,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& workingDirectory = std::string());
|
||||
bool start(uv_loop_t* uv_loop, std::function<void()>&& finishedCallback);
|
||||
|
||||
private:
|
||||
// -- Friends
|
||||
friend class PipeT;
|
||||
// -- Libuv callbacks
|
||||
static void UVExit(uv_process_t* handle, int64_t exitStatus,
|
||||
int termSignal);
|
||||
void UVTryFinish();
|
||||
|
||||
// -- Setup
|
||||
SetupT Setup_;
|
||||
// -- Runtime
|
||||
bool IsStarted_ = false;
|
||||
bool IsFinished_ = false;
|
||||
std::function<void()> FinishedCallback_;
|
||||
std::vector<const char*> CommandPtr_;
|
||||
std::array<uv_stdio_container_t, 3> UVOptionsStdIO_;
|
||||
uv_process_options_t UVOptions_;
|
||||
cm::uv_process_ptr UVProcess_;
|
||||
PipeT UVPipeOut_;
|
||||
PipeT UVPipeErr_;
|
||||
};
|
||||
|
||||
public:
|
||||
// -- Constructors
|
||||
cmQtAutoGenerator();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,20 +7,16 @@
|
||||
|
||||
#include "cmQtAutoGen.h"
|
||||
#include "cmQtAutoGenerator.h"
|
||||
#include "cmUVHandlePtr.h"
|
||||
#include "cmUVSignalHackRAII.h" // IWYU pragma: keep
|
||||
#include "cm_uv.h"
|
||||
#include "cmWorkerPool.h"
|
||||
#include "cmsys/RegularExpression.hxx"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory> // IWYU pragma: keep
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -39,7 +35,7 @@ public:
|
||||
|
||||
public:
|
||||
// -- Types
|
||||
class WorkerT;
|
||||
typedef std::multimap<std::string, std::array<std::string, 2>> IncludesMap;
|
||||
|
||||
/// @brief Search key plus regular expression pair
|
||||
///
|
||||
@ -173,31 +169,71 @@ public:
|
||||
cmsys::RegularExpression RegExpInclude;
|
||||
};
|
||||
|
||||
/// @brief Abstract job class for threaded processing
|
||||
/// @brief Abstract job class for concurrent job processing
|
||||
///
|
||||
class JobT
|
||||
class JobT : public cmWorkerPool::JobT
|
||||
{
|
||||
public:
|
||||
JobT() = default;
|
||||
virtual ~JobT() = default;
|
||||
protected:
|
||||
/**
|
||||
* @brief Protected default constructor
|
||||
*/
|
||||
JobT(bool fence = false)
|
||||
: cmWorkerPool::JobT(fence)
|
||||
{
|
||||
}
|
||||
|
||||
JobT(JobT const&) = delete;
|
||||
JobT& operator=(JobT const&) = delete;
|
||||
//! Get the generator. Only valid during Process() call!
|
||||
cmQtAutoGeneratorMocUic* Gen() const
|
||||
{
|
||||
return static_cast<cmQtAutoGeneratorMocUic*>(UserData());
|
||||
};
|
||||
|
||||
// -- Abstract processing interface
|
||||
virtual void Process(WorkerT& wrk) = 0;
|
||||
//! Get the file system interface. Only valid during Process() call!
|
||||
FileSystem& FileSys() { return Gen()->FileSys(); }
|
||||
//! Get the logger. Only valid during Process() call!
|
||||
Logger& Log() { return Gen()->Log(); }
|
||||
|
||||
// -- Error logging with automatic abort
|
||||
void LogError(GenT genType, std::string const& message) const;
|
||||
void LogFileError(GenT genType, std::string const& filename,
|
||||
std::string const& message) const;
|
||||
void LogCommandError(GenT genType, std::string const& message,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& output) const;
|
||||
|
||||
/**
|
||||
* @brief Run an external process. Use only during Process() call!
|
||||
*/
|
||||
bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
|
||||
std::vector<std::string> const& command);
|
||||
};
|
||||
|
||||
// Job management types
|
||||
typedef std::unique_ptr<JobT> JobHandleT;
|
||||
typedef std::deque<JobHandleT> JobQueueT;
|
||||
/// @brief Fence job utility class
|
||||
///
|
||||
class JobFenceT : public JobT
|
||||
{
|
||||
public:
|
||||
JobFenceT()
|
||||
: JobT(true)
|
||||
{
|
||||
}
|
||||
void Process() override{};
|
||||
};
|
||||
|
||||
/// @brief Parse source job
|
||||
/// @brief Generate moc_predefs.h
|
||||
///
|
||||
class JobMocPredefsT : public JobT
|
||||
{
|
||||
private:
|
||||
void Process() override;
|
||||
};
|
||||
|
||||
/// @brief Parses a source file
|
||||
///
|
||||
class JobParseT : public JobT
|
||||
{
|
||||
public:
|
||||
JobParseT(std::string&& fileName, bool moc, bool uic, bool header = false)
|
||||
JobParseT(std::string fileName, bool moc, bool uic, bool header = false)
|
||||
: FileName(std::move(fileName))
|
||||
, AutoMoc(moc)
|
||||
, AutoUic(uic)
|
||||
@ -213,18 +249,15 @@ public:
|
||||
std::string FileBase;
|
||||
};
|
||||
|
||||
void Process(WorkerT& wrk) override;
|
||||
bool ParseMocSource(WorkerT& wrk, MetaT const& meta);
|
||||
bool ParseMocHeader(WorkerT& wrk, MetaT const& meta);
|
||||
std::string MocStringHeaders(WorkerT& wrk,
|
||||
std::string const& fileBase) const;
|
||||
std::string MocFindIncludedHeader(WorkerT& wrk,
|
||||
std::string const& includerDir,
|
||||
void Process() override;
|
||||
bool ParseMocSource(MetaT const& meta);
|
||||
bool ParseMocHeader(MetaT const& meta);
|
||||
std::string MocStringHeaders(std::string const& fileBase) const;
|
||||
std::string MocFindIncludedHeader(std::string const& includerDir,
|
||||
std::string const& includeBase);
|
||||
bool ParseUic(WorkerT& wrk, MetaT const& meta);
|
||||
bool ParseUicInclude(WorkerT& wrk, MetaT const& meta,
|
||||
std::string&& includeString);
|
||||
std::string UicFindIncludedFile(WorkerT& wrk, MetaT const& meta,
|
||||
bool ParseUic(MetaT const& meta);
|
||||
bool ParseUicInclude(MetaT const& meta, std::string&& includeString);
|
||||
std::string UicFindIncludedFile(MetaT const& meta,
|
||||
std::string const& includeString);
|
||||
|
||||
private:
|
||||
@ -234,12 +267,20 @@ public:
|
||||
bool Header = false;
|
||||
};
|
||||
|
||||
/// @brief Generate moc_predefs
|
||||
/// @brief Generates additional jobs after all files have been parsed
|
||||
///
|
||||
class JobMocPredefsT : public JobT
|
||||
class JobPostParseT : public JobFenceT
|
||||
{
|
||||
private:
|
||||
void Process(WorkerT& wrk) override;
|
||||
void Process() override;
|
||||
};
|
||||
|
||||
/// @brief Generate mocs_compilation.cpp
|
||||
///
|
||||
class JobMocsCompilationT : public JobFenceT
|
||||
{
|
||||
private:
|
||||
void Process() override;
|
||||
};
|
||||
|
||||
/// @brief Moc a file job
|
||||
@ -247,20 +288,20 @@ public:
|
||||
class JobMocT : public JobT
|
||||
{
|
||||
public:
|
||||
JobMocT(std::string&& sourceFile, std::string includerFile,
|
||||
std::string&& includeString)
|
||||
JobMocT(std::string sourceFile, std::string includerFile,
|
||||
std::string includeString)
|
||||
: SourceFile(std::move(sourceFile))
|
||||
, IncluderFile(std::move(includerFile))
|
||||
, IncludeString(std::move(includeString))
|
||||
{
|
||||
}
|
||||
|
||||
void FindDependencies(WorkerT& wrk, std::string const& content);
|
||||
void FindDependencies(std::string const& content);
|
||||
|
||||
private:
|
||||
void Process(WorkerT& wrk) override;
|
||||
bool UpdateRequired(WorkerT& wrk);
|
||||
void GenerateMoc(WorkerT& wrk);
|
||||
void Process() override;
|
||||
bool UpdateRequired();
|
||||
void GenerateMoc();
|
||||
|
||||
public:
|
||||
std::string SourceFile;
|
||||
@ -276,8 +317,8 @@ public:
|
||||
class JobUicT : public JobT
|
||||
{
|
||||
public:
|
||||
JobUicT(std::string&& sourceFile, std::string includerFile,
|
||||
std::string&& includeString)
|
||||
JobUicT(std::string sourceFile, std::string includerFile,
|
||||
std::string includeString)
|
||||
: SourceFile(std::move(sourceFile))
|
||||
, IncluderFile(std::move(includerFile))
|
||||
, IncludeString(std::move(includeString))
|
||||
@ -285,9 +326,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void Process(WorkerT& wrk) override;
|
||||
bool UpdateRequired(WorkerT& wrk);
|
||||
void GenerateUic(WorkerT& wrk);
|
||||
void Process() override;
|
||||
bool UpdateRequired();
|
||||
void GenerateUic();
|
||||
|
||||
public:
|
||||
std::string SourceFile;
|
||||
@ -296,80 +337,12 @@ public:
|
||||
std::string BuildFile;
|
||||
};
|
||||
|
||||
/// @brief Worker Thread
|
||||
/// @brief The last job
|
||||
///
|
||||
class WorkerT
|
||||
class JobFinishT : public JobFenceT
|
||||
{
|
||||
public:
|
||||
WorkerT(cmQtAutoGeneratorMocUic* gen, uv_loop_t* uvLoop);
|
||||
~WorkerT();
|
||||
|
||||
WorkerT(WorkerT const&) = delete;
|
||||
WorkerT& operator=(WorkerT const&) = delete;
|
||||
|
||||
// -- Const accessors
|
||||
cmQtAutoGeneratorMocUic& Gen() const { return *Gen_; }
|
||||
Logger& Log() const { return Gen_->Log(); }
|
||||
FileSystem& FileSys() const { return Gen_->FileSys(); }
|
||||
const BaseSettingsT& Base() const { return Gen_->Base(); }
|
||||
const MocSettingsT& Moc() const { return Gen_->Moc(); }
|
||||
const UicSettingsT& Uic() const { return Gen_->Uic(); }
|
||||
|
||||
// -- Log info
|
||||
void LogInfo(GenT genType, std::string const& message) const;
|
||||
// -- Log warning
|
||||
void LogWarning(GenT genType, std::string const& message) const;
|
||||
void LogFileWarning(GenT genType, std::string const& filename,
|
||||
std::string const& message) const;
|
||||
// -- Log error
|
||||
void LogError(GenT genType, std::string const& message) const;
|
||||
void LogFileError(GenT genType, std::string const& filename,
|
||||
std::string const& message) const;
|
||||
void LogCommandError(GenT genType, std::string const& message,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& output) const;
|
||||
|
||||
// -- External processes
|
||||
/// @brief Verbose logging version
|
||||
bool RunProcess(GenT genType, ProcessResultT& result,
|
||||
std::vector<std::string> const& command);
|
||||
|
||||
private:
|
||||
/// @brief Thread main loop
|
||||
void Loop();
|
||||
|
||||
// -- Libuv callbacks
|
||||
static void UVProcessStart(uv_async_t* handle);
|
||||
void UVProcessFinished();
|
||||
|
||||
private:
|
||||
// -- Generator
|
||||
cmQtAutoGeneratorMocUic* Gen_;
|
||||
// -- Job handle
|
||||
JobHandleT JobHandle_;
|
||||
// -- Process management
|
||||
std::mutex ProcessMutex_;
|
||||
cm::uv_async_ptr ProcessRequest_;
|
||||
std::condition_variable ProcessCondition_;
|
||||
std::unique_ptr<ReadOnlyProcessT> Process_;
|
||||
// -- System thread
|
||||
std::thread Thread_;
|
||||
};
|
||||
|
||||
/// @brief Processing stage
|
||||
enum class StageT
|
||||
{
|
||||
SETTINGS_READ,
|
||||
CREATE_DIRECTORIES,
|
||||
PARSE_SOURCES,
|
||||
PARSE_HEADERS,
|
||||
MOC_PREDEFS,
|
||||
MOC_PROCESS,
|
||||
MOCS_COMPILATION,
|
||||
UIC_PROCESS,
|
||||
SETTINGS_WRITE,
|
||||
FINISH,
|
||||
END
|
||||
void Process() override;
|
||||
};
|
||||
|
||||
// -- Const settings interface
|
||||
@ -377,41 +350,39 @@ public:
|
||||
const MocSettingsT& Moc() const { return this->Moc_; }
|
||||
const UicSettingsT& Uic() const { return this->Uic_; }
|
||||
|
||||
// -- Worker thread interface
|
||||
void WorkerSwapJob(JobHandleT& jobHandle);
|
||||
// -- Parallel job processing interface
|
||||
void ParallelRegisterJobError();
|
||||
bool ParallelJobPushMoc(JobHandleT& jobHandle);
|
||||
bool ParallelJobPushUic(JobHandleT& jobHandle);
|
||||
bool ParallelMocIncluded(std::string const& sourceFile);
|
||||
cmWorkerPool& WorkerPool() { return WorkerPool_; }
|
||||
void AbortError() { Abort(true); }
|
||||
void AbortSuccess() { Abort(false); }
|
||||
bool ParallelJobPushMoc(cmWorkerPool::JobHandleT&& jobHandle);
|
||||
bool ParallelJobPushUic(cmWorkerPool::JobHandleT&& jobHandle);
|
||||
|
||||
// -- Mocs compilation include file updated flag
|
||||
void ParallelMocAutoUpdated() { MocAutoFileUpdated_.store(true); }
|
||||
bool MocAutoFileUpdated() const { return MocAutoFileUpdated_.load(); }
|
||||
|
||||
// -- Mocs compilation file register
|
||||
std::string ParallelMocAutoRegister(std::string const& baseName);
|
||||
void ParallelMocAutoUpdated();
|
||||
bool ParallelMocIncluded(std::string const& sourceFile);
|
||||
std::set<std::string> const& MocAutoFiles() const
|
||||
{
|
||||
return this->MocAutoFiles_;
|
||||
}
|
||||
|
||||
private:
|
||||
// -- Utility accessors
|
||||
Logger& Log() { return Logger_; }
|
||||
FileSystem& FileSys() { return FileSys_; }
|
||||
// -- libuv loop accessors
|
||||
uv_loop_t* UVLoop() { return UVLoop_.get(); }
|
||||
cm::uv_async_ptr& UVRequest() { return UVRequest_; }
|
||||
// -- Abstract processing interface
|
||||
bool Init(cmMakefile* makefile) override;
|
||||
bool Process() override;
|
||||
// -- Process stage
|
||||
static void UVPollStage(uv_async_t* handle);
|
||||
void PollStage();
|
||||
void SetStage(StageT stage);
|
||||
// -- Settings file
|
||||
void SettingsFileRead();
|
||||
void SettingsFileWrite();
|
||||
bool SettingsFileWrite();
|
||||
// -- Thread processing
|
||||
bool ThreadsStartJobs(JobQueueT& queue);
|
||||
bool ThreadsJobsDone();
|
||||
void ThreadsStop();
|
||||
void RegisterJobError();
|
||||
void Abort(bool error);
|
||||
// -- Generation
|
||||
void CreateDirectories();
|
||||
void MocGenerateCompilation();
|
||||
bool CreateDirectories();
|
||||
|
||||
private:
|
||||
// -- Utility
|
||||
@ -421,39 +392,22 @@ private:
|
||||
BaseSettingsT Base_;
|
||||
MocSettingsT Moc_;
|
||||
UicSettingsT Uic_;
|
||||
// -- libuv loop
|
||||
#ifdef CMAKE_UV_SIGNAL_HACK
|
||||
std::unique_ptr<cmUVSignalHackRAII> UVHackRAII_;
|
||||
#endif
|
||||
std::unique_ptr<uv_loop_t> UVLoop_;
|
||||
cm::uv_async_ptr UVRequest_;
|
||||
StageT Stage_ = StageT::SETTINGS_READ;
|
||||
// -- Job queues
|
||||
std::mutex JobsMutex_;
|
||||
struct
|
||||
{
|
||||
JobQueueT Sources;
|
||||
JobQueueT Headers;
|
||||
JobQueueT MocPredefs;
|
||||
JobQueueT Moc;
|
||||
JobQueueT Uic;
|
||||
} JobQueues_;
|
||||
JobQueueT JobQueue_;
|
||||
std::size_t volatile JobsRemain_ = 0;
|
||||
bool volatile JobError_ = false;
|
||||
bool volatile JobThreadsAbort_ = false;
|
||||
std::condition_variable JobsConditionRead_;
|
||||
// -- Moc meta
|
||||
std::set<std::string> MocIncludedStrings_;
|
||||
std::mutex MocMetaMutex_;
|
||||
std::set<std::string> MocIncludedFiles_;
|
||||
IncludesMap MocIncludes_;
|
||||
std::set<std::string> MocAutoFiles_;
|
||||
bool volatile MocAutoFileUpdated_ = false;
|
||||
std::atomic<bool> MocAutoFileUpdated_ = ATOMIC_VAR_INIT(false);
|
||||
// -- Uic meta
|
||||
std::mutex UicMetaMutex_;
|
||||
IncludesMap UicIncludes_;
|
||||
// -- Settings file
|
||||
std::string SettingsFile_;
|
||||
std::string SettingsStringMoc_;
|
||||
std::string SettingsStringUic_;
|
||||
// -- Threads and loops
|
||||
std::vector<std::unique_ptr<WorkerT>> Workers_;
|
||||
// -- Thread pool and job queue
|
||||
std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
|
||||
cmWorkerPool WorkerPool_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
770
Source/cmWorkerPool.cxx
Normal file
770
Source/cmWorkerPool.cxx
Normal file
@ -0,0 +1,770 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmWorkerPool.h"
|
||||
|
||||
#include "cmRange.h"
|
||||
#include "cmUVHandlePtr.h"
|
||||
#include "cmUVSignalHackRAII.h" // IWYU pragma: keep
|
||||
#include "cm_uv.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <stddef.h>
|
||||
#include <thread>
|
||||
|
||||
/**
|
||||
* @brief libuv pipe buffer class
|
||||
*/
|
||||
class cmUVPipeBuffer
|
||||
{
|
||||
public:
|
||||
typedef cmRange<char const*> DataRange;
|
||||
typedef std::function<void(DataRange)> DataFunction;
|
||||
/// On error the ssize_t argument is a non zero libuv error code
|
||||
typedef std::function<void(ssize_t)> EndFunction;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Reset to construction state
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Initializes uv_pipe(), uv_stream() and uv_handle()
|
||||
* @return true on success
|
||||
*/
|
||||
bool init(uv_loop_t* uv_loop);
|
||||
|
||||
/**
|
||||
* Start reading
|
||||
* @return true on success
|
||||
*/
|
||||
bool startRead(DataFunction dataFunction, EndFunction endFunction);
|
||||
|
||||
//! libuv pipe
|
||||
uv_pipe_t* uv_pipe() const { return UVPipe_.get(); }
|
||||
//! uv_pipe() casted to libuv stream
|
||||
uv_stream_t* uv_stream() const { return static_cast<uv_stream_t*>(UVPipe_); }
|
||||
//! uv_pipe() casted to libuv handle
|
||||
uv_handle_t* uv_handle() { return static_cast<uv_handle_t*>(UVPipe_); }
|
||||
|
||||
private:
|
||||
// -- Libuv callbacks
|
||||
static void UVAlloc(uv_handle_t* handle, size_t suggestedSize,
|
||||
uv_buf_t* buf);
|
||||
static void UVData(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
|
||||
|
||||
private:
|
||||
cm::uv_pipe_ptr UVPipe_;
|
||||
std::vector<char> Buffer_;
|
||||
DataFunction DataFunction_;
|
||||
EndFunction EndFunction_;
|
||||
};
|
||||
|
||||
void cmUVPipeBuffer::reset()
|
||||
{
|
||||
if (UVPipe_.get() != nullptr) {
|
||||
EndFunction_ = nullptr;
|
||||
DataFunction_ = nullptr;
|
||||
Buffer_.clear();
|
||||
Buffer_.shrink_to_fit();
|
||||
UVPipe_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool cmUVPipeBuffer::init(uv_loop_t* uv_loop)
|
||||
{
|
||||
reset();
|
||||
if (uv_loop == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int ret = UVPipe_.init(*uv_loop, 0, this);
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
bool cmUVPipeBuffer::startRead(DataFunction dataFunction,
|
||||
EndFunction endFunction)
|
||||
{
|
||||
if (UVPipe_.get() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!dataFunction || !endFunction) {
|
||||
return false;
|
||||
}
|
||||
DataFunction_ = std::move(dataFunction);
|
||||
EndFunction_ = std::move(endFunction);
|
||||
int ret = uv_read_start(uv_stream(), &cmUVPipeBuffer::UVAlloc,
|
||||
&cmUVPipeBuffer::UVData);
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
void cmUVPipeBuffer::UVAlloc(uv_handle_t* handle, size_t suggestedSize,
|
||||
uv_buf_t* buf)
|
||||
{
|
||||
auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(handle->data);
|
||||
pipe.Buffer_.resize(suggestedSize);
|
||||
buf->base = pipe.Buffer_.data();
|
||||
buf->len = static_cast<unsigned long>(pipe.Buffer_.size());
|
||||
}
|
||||
|
||||
void cmUVPipeBuffer::UVData(uv_stream_t* stream, ssize_t nread,
|
||||
const uv_buf_t* buf)
|
||||
{
|
||||
auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(stream->data);
|
||||
if (nread > 0) {
|
||||
if (buf->base != nullptr) {
|
||||
// Call data function
|
||||
pipe.DataFunction_(DataRange(buf->base, buf->base + nread));
|
||||
}
|
||||
} else if (nread < 0) {
|
||||
// Save the end function on the stack before resetting the pipe
|
||||
EndFunction efunc;
|
||||
efunc.swap(pipe.EndFunction_);
|
||||
// Reset pipe before calling the end function
|
||||
pipe.reset();
|
||||
// Call end function
|
||||
efunc((nread == UV_EOF) ? 0 : nread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief External process management class
|
||||
*/
|
||||
class cmUVReadOnlyProcess
|
||||
{
|
||||
public:
|
||||
// -- Types
|
||||
//! @brief Process settings
|
||||
struct SetupT
|
||||
{
|
||||
std::string WorkingDirectory;
|
||||
std::vector<std::string> Command;
|
||||
cmWorkerPool::ProcessResultT* Result = nullptr;
|
||||
bool MergedOutput = false;
|
||||
};
|
||||
|
||||
public:
|
||||
// -- Const accessors
|
||||
SetupT const& Setup() const { return Setup_; }
|
||||
cmWorkerPool::ProcessResultT* Result() const { return Setup_.Result; }
|
||||
bool IsStarted() const { return IsStarted_; }
|
||||
bool IsFinished() const { return IsFinished_; }
|
||||
|
||||
// -- Runtime
|
||||
void setup(cmWorkerPool::ProcessResultT* result, bool mergedOutput,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& workingDirectory = std::string());
|
||||
bool start(uv_loop_t* uv_loop, std::function<void()> finishedCallback);
|
||||
|
||||
private:
|
||||
// -- Libuv callbacks
|
||||
static void UVExit(uv_process_t* handle, int64_t exitStatus, int termSignal);
|
||||
void UVPipeOutData(cmUVPipeBuffer::DataRange data);
|
||||
void UVPipeOutEnd(ssize_t error);
|
||||
void UVPipeErrData(cmUVPipeBuffer::DataRange data);
|
||||
void UVPipeErrEnd(ssize_t error);
|
||||
void UVTryFinish();
|
||||
|
||||
private:
|
||||
// -- Setup
|
||||
SetupT Setup_;
|
||||
// -- Runtime
|
||||
bool IsStarted_ = false;
|
||||
bool IsFinished_ = false;
|
||||
std::function<void()> FinishedCallback_;
|
||||
std::vector<const char*> CommandPtr_;
|
||||
std::array<uv_stdio_container_t, 3> UVOptionsStdIO_;
|
||||
uv_process_options_t UVOptions_;
|
||||
cm::uv_process_ptr UVProcess_;
|
||||
cmUVPipeBuffer UVPipeOut_;
|
||||
cmUVPipeBuffer UVPipeErr_;
|
||||
};
|
||||
|
||||
void cmUVReadOnlyProcess::setup(cmWorkerPool::ProcessResultT* result,
|
||||
bool mergedOutput,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& workingDirectory)
|
||||
{
|
||||
Setup_.WorkingDirectory = workingDirectory;
|
||||
Setup_.Command = command;
|
||||
Setup_.Result = result;
|
||||
Setup_.MergedOutput = mergedOutput;
|
||||
}
|
||||
|
||||
bool cmUVReadOnlyProcess::start(uv_loop_t* uv_loop,
|
||||
std::function<void()> finishedCallback)
|
||||
{
|
||||
if (IsStarted() || (Result() == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset result before the start
|
||||
Result()->reset();
|
||||
|
||||
// Fill command string pointers
|
||||
if (!Setup().Command.empty()) {
|
||||
CommandPtr_.reserve(Setup().Command.size() + 1);
|
||||
for (std::string const& arg : Setup().Command) {
|
||||
CommandPtr_.push_back(arg.c_str());
|
||||
}
|
||||
CommandPtr_.push_back(nullptr);
|
||||
} else {
|
||||
Result()->ErrorMessage = "Empty command";
|
||||
}
|
||||
|
||||
if (!Result()->error()) {
|
||||
if (!UVPipeOut_.init(uv_loop)) {
|
||||
Result()->ErrorMessage = "libuv stdout pipe initialization failed";
|
||||
}
|
||||
}
|
||||
if (!Result()->error()) {
|
||||
if (!UVPipeErr_.init(uv_loop)) {
|
||||
Result()->ErrorMessage = "libuv stderr pipe initialization failed";
|
||||
}
|
||||
}
|
||||
if (!Result()->error()) {
|
||||
// -- Setup process stdio options
|
||||
// stdin
|
||||
UVOptionsStdIO_[0].flags = UV_IGNORE;
|
||||
UVOptionsStdIO_[0].data.stream = nullptr;
|
||||
// stdout
|
||||
UVOptionsStdIO_[1].flags =
|
||||
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
|
||||
UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream();
|
||||
// stderr
|
||||
UVOptionsStdIO_[2].flags =
|
||||
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
|
||||
UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream();
|
||||
|
||||
// -- Setup process options
|
||||
std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0);
|
||||
UVOptions_.exit_cb = &cmUVReadOnlyProcess::UVExit;
|
||||
UVOptions_.file = CommandPtr_[0];
|
||||
UVOptions_.args = const_cast<char**>(CommandPtr_.data());
|
||||
UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
|
||||
UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
|
||||
UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
|
||||
UVOptions_.stdio = UVOptionsStdIO_.data();
|
||||
|
||||
// -- Spawn process
|
||||
int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this);
|
||||
if (uvErrorCode != 0) {
|
||||
Result()->ErrorMessage = "libuv process spawn failed";
|
||||
if (const char* uvErr = uv_strerror(uvErrorCode)) {
|
||||
Result()->ErrorMessage += ": ";
|
||||
Result()->ErrorMessage += uvErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// -- Start reading from stdio streams
|
||||
if (!Result()->error()) {
|
||||
if (!UVPipeOut_.startRead(
|
||||
[this](cmUVPipeBuffer::DataRange range) {
|
||||
this->UVPipeOutData(range);
|
||||
},
|
||||
[this](ssize_t error) { this->UVPipeOutEnd(error); })) {
|
||||
Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
|
||||
}
|
||||
}
|
||||
if (!Result()->error()) {
|
||||
if (!UVPipeErr_.startRead(
|
||||
[this](cmUVPipeBuffer::DataRange range) {
|
||||
this->UVPipeErrData(range);
|
||||
},
|
||||
[this](ssize_t error) { this->UVPipeErrEnd(error); })) {
|
||||
Result()->ErrorMessage = "libuv start reading from stderr pipe failed";
|
||||
}
|
||||
}
|
||||
|
||||
if (!Result()->error()) {
|
||||
IsStarted_ = true;
|
||||
FinishedCallback_ = std::move(finishedCallback);
|
||||
} else {
|
||||
// Clear libuv handles and finish
|
||||
UVProcess_.reset();
|
||||
UVPipeOut_.reset();
|
||||
UVPipeErr_.reset();
|
||||
CommandPtr_.clear();
|
||||
}
|
||||
|
||||
return IsStarted();
|
||||
}
|
||||
|
||||
void cmUVReadOnlyProcess::UVExit(uv_process_t* handle, int64_t exitStatus,
|
||||
int termSignal)
|
||||
{
|
||||
auto& proc = *reinterpret_cast<cmUVReadOnlyProcess*>(handle->data);
|
||||
if (proc.IsStarted() && !proc.IsFinished()) {
|
||||
// Set error message on demand
|
||||
proc.Result()->ExitStatus = exitStatus;
|
||||
proc.Result()->TermSignal = termSignal;
|
||||
if (!proc.Result()->error()) {
|
||||
if (termSignal != 0) {
|
||||
proc.Result()->ErrorMessage = "Process was terminated by signal ";
|
||||
proc.Result()->ErrorMessage +=
|
||||
std::to_string(proc.Result()->TermSignal);
|
||||
} else if (exitStatus != 0) {
|
||||
proc.Result()->ErrorMessage = "Process failed with return value ";
|
||||
proc.Result()->ErrorMessage +=
|
||||
std::to_string(proc.Result()->ExitStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset process handle
|
||||
proc.UVProcess_.reset();
|
||||
// Try finish
|
||||
proc.UVTryFinish();
|
||||
}
|
||||
}
|
||||
|
||||
void cmUVReadOnlyProcess::UVPipeOutData(cmUVPipeBuffer::DataRange data)
|
||||
{
|
||||
Result()->StdOut.append(data.begin(), data.end());
|
||||
}
|
||||
|
||||
void cmUVReadOnlyProcess::UVPipeOutEnd(ssize_t error)
|
||||
{
|
||||
// Process pipe error
|
||||
if ((error != 0) && !Result()->error()) {
|
||||
Result()->ErrorMessage =
|
||||
"Reading from stdout pipe failed with libuv error code ";
|
||||
Result()->ErrorMessage += std::to_string(error);
|
||||
}
|
||||
// Try finish
|
||||
UVTryFinish();
|
||||
}
|
||||
|
||||
void cmUVReadOnlyProcess::UVPipeErrData(cmUVPipeBuffer::DataRange data)
|
||||
{
|
||||
std::string* str =
|
||||
Setup_.MergedOutput ? &Result()->StdOut : &Result()->StdErr;
|
||||
str->append(data.begin(), data.end());
|
||||
}
|
||||
|
||||
void cmUVReadOnlyProcess::UVPipeErrEnd(ssize_t error)
|
||||
{
|
||||
// Process pipe error
|
||||
if ((error != 0) && !Result()->error()) {
|
||||
Result()->ErrorMessage =
|
||||
"Reading from stderr pipe failed with libuv error code ";
|
||||
Result()->ErrorMessage += std::to_string(error);
|
||||
}
|
||||
// Try finish
|
||||
UVTryFinish();
|
||||
}
|
||||
|
||||
void cmUVReadOnlyProcess::UVTryFinish()
|
||||
{
|
||||
// There still might be data in the pipes after the process has finished.
|
||||
// Therefore check if the process is finished AND all pipes are closed
|
||||
// before signaling the worker thread to continue.
|
||||
if ((UVProcess_.get() != nullptr) || (UVPipeOut_.uv_pipe() != nullptr) ||
|
||||
(UVPipeErr_.uv_pipe() != nullptr)) {
|
||||
return;
|
||||
}
|
||||
IsFinished_ = true;
|
||||
FinishedCallback_();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Private worker pool internals
|
||||
*/
|
||||
class cmWorkerPoolInternal
|
||||
{
|
||||
public:
|
||||
// -- Types
|
||||
|
||||
/**
|
||||
* @brief Worker thread
|
||||
*/
|
||||
class WorkerT
|
||||
{
|
||||
public:
|
||||
WorkerT(unsigned int index);
|
||||
~WorkerT();
|
||||
|
||||
WorkerT(WorkerT const&) = delete;
|
||||
WorkerT& operator=(WorkerT const&) = delete;
|
||||
|
||||
/**
|
||||
* Start the thread
|
||||
*/
|
||||
void Start(cmWorkerPoolInternal* internal);
|
||||
|
||||
/**
|
||||
* @brief Run an external process
|
||||
*/
|
||||
bool RunProcess(cmWorkerPool::ProcessResultT& result,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& workingDirectory);
|
||||
|
||||
// -- Accessors
|
||||
unsigned int Index() const { return Index_; }
|
||||
cmWorkerPool::JobHandleT& JobHandle() { return JobHandle_; }
|
||||
|
||||
private:
|
||||
// -- Libuv callbacks
|
||||
static void UVProcessStart(uv_async_t* handle);
|
||||
void UVProcessFinished();
|
||||
|
||||
private:
|
||||
//! @brief Job handle
|
||||
cmWorkerPool::JobHandleT JobHandle_;
|
||||
//! @brief Worker index
|
||||
unsigned int Index_;
|
||||
// -- Process management
|
||||
struct
|
||||
{
|
||||
std::mutex Mutex;
|
||||
cm::uv_async_ptr Request;
|
||||
std::condition_variable Condition;
|
||||
std::unique_ptr<cmUVReadOnlyProcess> ROP;
|
||||
} Proc_;
|
||||
// -- System thread
|
||||
std::thread Thread_;
|
||||
};
|
||||
|
||||
public:
|
||||
// -- Constructors
|
||||
cmWorkerPoolInternal(cmWorkerPool* pool);
|
||||
~cmWorkerPoolInternal();
|
||||
|
||||
/**
|
||||
* @brief Runs the libuv loop
|
||||
*/
|
||||
bool Process();
|
||||
|
||||
/**
|
||||
* @brief Clear queue and abort threads
|
||||
*/
|
||||
void Abort();
|
||||
|
||||
/**
|
||||
* @brief Push a job to the queue and notify a worker
|
||||
*/
|
||||
bool PushJob(cmWorkerPool::JobHandleT&& jobHandle);
|
||||
|
||||
/**
|
||||
* @brief Worker thread main loop method
|
||||
*/
|
||||
void Work(WorkerT* worker);
|
||||
|
||||
// -- Request slots
|
||||
static void UVSlotBegin(uv_async_t* handle);
|
||||
static void UVSlotEnd(uv_async_t* handle);
|
||||
|
||||
public:
|
||||
// -- UV loop
|
||||
#ifdef CMAKE_UV_SIGNAL_HACK
|
||||
std::unique_ptr<cmUVSignalHackRAII> UVHackRAII;
|
||||
#endif
|
||||
std::unique_ptr<uv_loop_t> UVLoop;
|
||||
cm::uv_async_ptr UVRequestBegin;
|
||||
cm::uv_async_ptr UVRequestEnd;
|
||||
|
||||
// -- Thread pool and job queue
|
||||
std::mutex Mutex;
|
||||
bool Aborting = false;
|
||||
bool FenceProcessing = false;
|
||||
unsigned int WorkersRunning = 0;
|
||||
unsigned int WorkersIdle = 0;
|
||||
unsigned int JobsProcessing = 0;
|
||||
std::deque<cmWorkerPool::JobHandleT> Queue;
|
||||
std::condition_variable Condition;
|
||||
std::vector<std::unique_ptr<WorkerT>> Workers;
|
||||
|
||||
// -- References
|
||||
cmWorkerPool* Pool = nullptr;
|
||||
};
|
||||
|
||||
cmWorkerPoolInternal::WorkerT::WorkerT(unsigned int index)
|
||||
: Index_(index)
|
||||
{
|
||||
}
|
||||
|
||||
cmWorkerPoolInternal::WorkerT::~WorkerT()
|
||||
{
|
||||
if (Thread_.joinable()) {
|
||||
Thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void cmWorkerPoolInternal::WorkerT::Start(cmWorkerPoolInternal* internal)
|
||||
{
|
||||
Proc_.Request.init(*(internal->UVLoop), &WorkerT::UVProcessStart, this);
|
||||
Thread_ = std::thread(&cmWorkerPoolInternal::Work, internal, this);
|
||||
}
|
||||
|
||||
bool cmWorkerPoolInternal::WorkerT::RunProcess(
|
||||
cmWorkerPool::ProcessResultT& result,
|
||||
std::vector<std::string> const& command, std::string const& workingDirectory)
|
||||
{
|
||||
if (command.empty()) {
|
||||
return false;
|
||||
}
|
||||
// Create process instance
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(Proc_.Mutex);
|
||||
Proc_.ROP = cm::make_unique<cmUVReadOnlyProcess>();
|
||||
Proc_.ROP->setup(&result, true, command, workingDirectory);
|
||||
}
|
||||
// Send asynchronous process start request to libuv loop
|
||||
Proc_.Request.send();
|
||||
// Wait until the process has been finished and destroyed
|
||||
{
|
||||
std::unique_lock<std::mutex> ulock(Proc_.Mutex);
|
||||
while (Proc_.ROP) {
|
||||
Proc_.Condition.wait(ulock);
|
||||
}
|
||||
}
|
||||
return !result.error();
|
||||
}
|
||||
|
||||
void cmWorkerPoolInternal::WorkerT::UVProcessStart(uv_async_t* handle)
|
||||
{
|
||||
auto* wrk = reinterpret_cast<WorkerT*>(handle->data);
|
||||
bool startFailed = false;
|
||||
{
|
||||
auto& Proc = wrk->Proc_;
|
||||
std::lock_guard<std::mutex> lock(Proc.Mutex);
|
||||
if (Proc.ROP && !Proc.ROP->IsStarted()) {
|
||||
startFailed =
|
||||
!Proc.ROP->start(handle->loop, [wrk] { wrk->UVProcessFinished(); });
|
||||
}
|
||||
}
|
||||
// Clean up if starting of the process failed
|
||||
if (startFailed) {
|
||||
wrk->UVProcessFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void cmWorkerPoolInternal::WorkerT::UVProcessFinished()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(Proc_.Mutex);
|
||||
if (Proc_.ROP && (Proc_.ROP->IsFinished() || !Proc_.ROP->IsStarted())) {
|
||||
Proc_.ROP.reset();
|
||||
}
|
||||
}
|
||||
// Notify idling thread
|
||||
Proc_.Condition.notify_one();
|
||||
}
|
||||
|
||||
void cmWorkerPool::ProcessResultT::reset()
|
||||
{
|
||||
ExitStatus = 0;
|
||||
TermSignal = 0;
|
||||
if (!StdOut.empty()) {
|
||||
StdOut.clear();
|
||||
StdOut.shrink_to_fit();
|
||||
}
|
||||
if (!StdErr.empty()) {
|
||||
StdErr.clear();
|
||||
StdErr.shrink_to_fit();
|
||||
}
|
||||
if (!ErrorMessage.empty()) {
|
||||
ErrorMessage.clear();
|
||||
ErrorMessage.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
cmWorkerPoolInternal::cmWorkerPoolInternal(cmWorkerPool* pool)
|
||||
: Pool(pool)
|
||||
{
|
||||
// Initialize libuv loop
|
||||
uv_disable_stdio_inheritance();
|
||||
#ifdef CMAKE_UV_SIGNAL_HACK
|
||||
UVHackRAII = cm::make_unique<cmUVSignalHackRAII>();
|
||||
#endif
|
||||
UVLoop = cm::make_unique<uv_loop_t>();
|
||||
uv_loop_init(UVLoop.get());
|
||||
}
|
||||
|
||||
cmWorkerPoolInternal::~cmWorkerPoolInternal()
|
||||
{
|
||||
uv_loop_close(UVLoop.get());
|
||||
}
|
||||
|
||||
bool cmWorkerPoolInternal::Process()
|
||||
{
|
||||
// Reset state
|
||||
Aborting = false;
|
||||
// Initialize libuv asynchronous request
|
||||
UVRequestBegin.init(*UVLoop, &cmWorkerPoolInternal::UVSlotBegin, this);
|
||||
UVRequestEnd.init(*UVLoop, &cmWorkerPoolInternal::UVSlotEnd, this);
|
||||
// Send begin request
|
||||
UVRequestBegin.send();
|
||||
// Run libuv loop
|
||||
return (uv_run(UVLoop.get(), UV_RUN_DEFAULT) == 0);
|
||||
}
|
||||
|
||||
void cmWorkerPoolInternal::Abort()
|
||||
{
|
||||
bool firstCall = false;
|
||||
// Clear all jobs and set abort flag
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(Mutex);
|
||||
if (!Aborting) {
|
||||
// Register abort and clear queue
|
||||
Aborting = true;
|
||||
Queue.clear();
|
||||
firstCall = true;
|
||||
}
|
||||
}
|
||||
if (firstCall) {
|
||||
// Wake threads
|
||||
Condition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
inline bool cmWorkerPoolInternal::PushJob(cmWorkerPool::JobHandleT&& jobHandle)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(Mutex);
|
||||
if (Aborting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Append the job to the queue
|
||||
Queue.emplace_back(std::move(jobHandle));
|
||||
|
||||
// Notify an idle worker if there's one
|
||||
if (WorkersIdle != 0) {
|
||||
Condition.notify_one();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cmWorkerPoolInternal::UVSlotBegin(uv_async_t* handle)
|
||||
{
|
||||
auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
|
||||
// Create worker threads
|
||||
{
|
||||
unsigned int const num = gint.Pool->ThreadCount();
|
||||
// Create workers
|
||||
gint.Workers.reserve(num);
|
||||
for (unsigned int ii = 0; ii != num; ++ii) {
|
||||
gint.Workers.emplace_back(cm::make_unique<WorkerT>(ii));
|
||||
}
|
||||
// Start workers
|
||||
for (auto& wrk : gint.Workers) {
|
||||
wrk->Start(&gint);
|
||||
}
|
||||
}
|
||||
// Destroy begin request
|
||||
gint.UVRequestBegin.reset();
|
||||
}
|
||||
|
||||
void cmWorkerPoolInternal::UVSlotEnd(uv_async_t* handle)
|
||||
{
|
||||
auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
|
||||
// Join and destroy worker threads
|
||||
gint.Workers.clear();
|
||||
// Destroy end request
|
||||
gint.UVRequestEnd.reset();
|
||||
}
|
||||
|
||||
void cmWorkerPoolInternal::Work(WorkerT* worker)
|
||||
{
|
||||
std::unique_lock<std::mutex> uLock(Mutex);
|
||||
// Increment running workers count
|
||||
++WorkersRunning;
|
||||
// Enter worker main loop
|
||||
while (true) {
|
||||
// Abort on request
|
||||
if (Aborting) {
|
||||
break;
|
||||
}
|
||||
// Wait for new jobs
|
||||
if (Queue.empty()) {
|
||||
++WorkersIdle;
|
||||
Condition.wait(uLock);
|
||||
--WorkersIdle;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for fence jobs
|
||||
if (FenceProcessing || Queue.front()->IsFence()) {
|
||||
if (JobsProcessing != 0) {
|
||||
Condition.wait(uLock);
|
||||
continue;
|
||||
}
|
||||
// No jobs get processed. Set the fence job processing flag.
|
||||
FenceProcessing = true;
|
||||
}
|
||||
|
||||
// Pop next job from queue
|
||||
worker->JobHandle() = std::move(Queue.front());
|
||||
Queue.pop_front();
|
||||
|
||||
// Unlocked scope for job processing
|
||||
++JobsProcessing;
|
||||
{
|
||||
uLock.unlock();
|
||||
worker->JobHandle()->Work(Pool, worker->Index()); // Process job
|
||||
worker->JobHandle().reset(); // Destroy job
|
||||
uLock.lock();
|
||||
}
|
||||
--JobsProcessing;
|
||||
|
||||
// Was this a fence job?
|
||||
if (FenceProcessing) {
|
||||
FenceProcessing = false;
|
||||
Condition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement running workers count
|
||||
if (--WorkersRunning == 0) {
|
||||
// Last worker thread about to finish. Send libuv event.
|
||||
UVRequestEnd.send();
|
||||
}
|
||||
}
|
||||
|
||||
cmWorkerPool::JobT::~JobT() = default;
|
||||
|
||||
bool cmWorkerPool::JobT::RunProcess(ProcessResultT& result,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& workingDirectory)
|
||||
{
|
||||
// Get worker by index
|
||||
auto* wrk = Pool_->Int_->Workers.at(WorkerIndex_).get();
|
||||
return wrk->RunProcess(result, command, workingDirectory);
|
||||
}
|
||||
|
||||
cmWorkerPool::cmWorkerPool()
|
||||
: Int_(cm::make_unique<cmWorkerPoolInternal>(this))
|
||||
{
|
||||
}
|
||||
|
||||
cmWorkerPool::~cmWorkerPool() = default;
|
||||
|
||||
bool cmWorkerPool::Process(unsigned int threadCount, void* userData)
|
||||
{
|
||||
// Setup user data
|
||||
UserData_ = userData;
|
||||
ThreadCount_ = (threadCount > 0) ? threadCount : 1u;
|
||||
|
||||
// Run libuv loop
|
||||
bool success = Int_->Process();
|
||||
|
||||
// Clear user data
|
||||
UserData_ = nullptr;
|
||||
ThreadCount_ = 0;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool cmWorkerPool::PushJob(JobHandleT&& jobHandle)
|
||||
{
|
||||
return Int_->PushJob(std::move(jobHandle));
|
||||
}
|
||||
|
||||
void cmWorkerPool::Abort()
|
||||
{
|
||||
Int_->Abort();
|
||||
}
|
219
Source/cmWorkerPool.h
Normal file
219
Source/cmWorkerPool.h
Normal file
@ -0,0 +1,219 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#ifndef cmWorkerPool_h
|
||||
#define cmWorkerPool_h
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include "cmAlgorithms.h" // IWYU pragma: keep
|
||||
|
||||
#include <memory> // IWYU pragma: keep
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// -- Types
|
||||
class cmWorkerPoolInternal;
|
||||
|
||||
/** @class cmWorkerPool
|
||||
* @brief Thread pool with job queue
|
||||
*/
|
||||
class cmWorkerPool
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Return value and output of an external process.
|
||||
*/
|
||||
struct ProcessResultT
|
||||
{
|
||||
void reset();
|
||||
bool error() const
|
||||
{
|
||||
return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty();
|
||||
}
|
||||
|
||||
std::int64_t ExitStatus = 0;
|
||||
int TermSignal = 0;
|
||||
std::string StdOut;
|
||||
std::string StdErr;
|
||||
std::string ErrorMessage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract job class for concurrent job processing.
|
||||
*/
|
||||
class JobT
|
||||
{
|
||||
public:
|
||||
JobT(JobT const&) = delete;
|
||||
JobT& operator=(JobT const&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor.
|
||||
*/
|
||||
virtual ~JobT();
|
||||
|
||||
/**
|
||||
* @brief Fence job flag
|
||||
*
|
||||
* Fence jobs require that:
|
||||
* - all jobs before in the queue have been processed
|
||||
* - no jobs later in the queue will be processed before this job was
|
||||
* processed
|
||||
*/
|
||||
bool IsFence() const { return Fence_; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Protected default constructor
|
||||
*/
|
||||
JobT(bool fence = false)
|
||||
: Fence_(fence)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract processing interface that must be implement in derived classes.
|
||||
*/
|
||||
virtual void Process() = 0;
|
||||
|
||||
/**
|
||||
* Get the worker pool.
|
||||
* Only valid during the JobT::Process() call!
|
||||
*/
|
||||
cmWorkerPool* Pool() const { return Pool_; }
|
||||
|
||||
/**
|
||||
* Get the user data.
|
||||
* Only valid during the JobT::Process() call!
|
||||
*/
|
||||
void* UserData() const { return Pool_->UserData(); };
|
||||
|
||||
/**
|
||||
* Get the worker index.
|
||||
* This is the index of the thread processing this job and is in the range
|
||||
* [0..ThreadCount).
|
||||
* Concurrently processing jobs will never have the same WorkerIndex().
|
||||
* Only valid during the JobT::Process() call!
|
||||
*/
|
||||
unsigned int WorkerIndex() const { return WorkerIndex_; }
|
||||
|
||||
/**
|
||||
* Run an external read only process.
|
||||
* Use only during JobT::Process() call!
|
||||
*/
|
||||
bool RunProcess(ProcessResultT& result,
|
||||
std::vector<std::string> const& command,
|
||||
std::string const& workingDirectory);
|
||||
|
||||
private:
|
||||
//! Needs access to Work()
|
||||
friend class cmWorkerPoolInternal;
|
||||
//! Worker thread entry method.
|
||||
void Work(cmWorkerPool* pool, unsigned int workerIndex)
|
||||
{
|
||||
Pool_ = pool;
|
||||
WorkerIndex_ = workerIndex;
|
||||
this->Process();
|
||||
}
|
||||
|
||||
private:
|
||||
cmWorkerPool* Pool_ = nullptr;
|
||||
unsigned int WorkerIndex_ = 0;
|
||||
bool Fence_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Job handle type
|
||||
*/
|
||||
typedef std::unique_ptr<JobT> JobHandleT;
|
||||
|
||||
/**
|
||||
* @brief Fence job base class
|
||||
*/
|
||||
class JobFenceT : public JobT
|
||||
{
|
||||
public:
|
||||
JobFenceT()
|
||||
: JobT(true)
|
||||
{
|
||||
}
|
||||
//! Does nothing
|
||||
void Process() override{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Fence job that aborts the worker pool.
|
||||
* This class is useful as the last job in the job queue.
|
||||
*/
|
||||
class JobEndT : JobFenceT
|
||||
{
|
||||
public:
|
||||
//! Does nothing
|
||||
void Process() override { Pool()->Abort(); }
|
||||
};
|
||||
|
||||
public:
|
||||
// -- Methods
|
||||
cmWorkerPool();
|
||||
~cmWorkerPool();
|
||||
|
||||
/**
|
||||
* @brief Blocking function that starts threads to process all Jobs in
|
||||
* the queue.
|
||||
*
|
||||
* This method blocks until a job calls the Abort() method.
|
||||
* @arg threadCount Number of threads to process jobs.
|
||||
* @arg userData Common user data pointer available in all Jobs.
|
||||
*/
|
||||
bool Process(unsigned int threadCount, void* userData = nullptr);
|
||||
|
||||
/**
|
||||
* Number of worker threads passed to Process().
|
||||
* Only valid during Process().
|
||||
*/
|
||||
unsigned int ThreadCount() const { return ThreadCount_; }
|
||||
|
||||
/**
|
||||
* User data reference passed to Process().
|
||||
* Only valid during Process().
|
||||
*/
|
||||
void* UserData() const { return UserData_; }
|
||||
|
||||
// -- Job processing interface
|
||||
|
||||
/**
|
||||
* @brief Clears the job queue and aborts all worker threads.
|
||||
*
|
||||
* This method is thread safe and can be called from inside a job.
|
||||
*/
|
||||
void Abort();
|
||||
|
||||
/**
|
||||
* @brief Push job to the queue.
|
||||
*
|
||||
* This method is thread safe and can be called from inside a job or before
|
||||
* Process().
|
||||
*/
|
||||
bool PushJob(JobHandleT&& jobHandle);
|
||||
|
||||
/**
|
||||
* @brief Push job to the queue
|
||||
*
|
||||
* This method is thread safe and can be called from inside a job or before
|
||||
* Process().
|
||||
*/
|
||||
template <class T, typename... Args>
|
||||
bool EmplaceJob(Args&&... args)
|
||||
{
|
||||
return PushJob(cm::make_unique<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
private:
|
||||
void* UserData_ = nullptr;
|
||||
unsigned int ThreadCount_ = 0;
|
||||
std::unique_ptr<cmWorkerPoolInternal> Int_;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user