aboutsummaryrefslogtreecommitdiff
path: root/src/zencore
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-12-11 13:09:03 +0100
committerStefan Boberg <[email protected]>2023-12-11 13:09:03 +0100
commit93afeddbc7a5b5df390a29407f5515acd5a70fc1 (patch)
tree6f85ee551aabe20dece64a750c0b2d5d2c5d2d5d /src/zencore
parentremoved unnecessary SHA1 references (diff)
parentMake sure that PathFromHandle don't hide true error when throwing exceptions ... (diff)
downloadzen-93afeddbc7a5b5df390a29407f5515acd5a70fc1.tar.xz
zen-93afeddbc7a5b5df390a29407f5515acd5a70fc1.zip
Merge branch 'main' of https://github.com/EpicGames/zen
Diffstat (limited to 'src/zencore')
-rw-r--r--src/zencore/compactbinary.cpp35
-rw-r--r--src/zencore/compactbinarybuilder.cpp75
-rw-r--r--src/zencore/filesystem.cpp284
-rw-r--r--src/zencore/include/zencore/assertfmt.h48
-rw-r--r--src/zencore/include/zencore/blockingqueue.h40
-rw-r--r--src/zencore/include/zencore/compactbinarybuilder.h18
-rw-r--r--src/zencore/include/zencore/filesystem.h9
-rw-r--r--src/zencore/include/zencore/iobuffer.h3
-rw-r--r--src/zencore/include/zencore/logbase.h3
-rw-r--r--src/zencore/include/zencore/logging.h12
-rw-r--r--src/zencore/include/zencore/process.h98
-rw-r--r--src/zencore/include/zencore/string.h13
-rw-r--r--src/zencore/include/zencore/thread.h84
-rw-r--r--src/zencore/include/zencore/trace.h5
-rw-r--r--src/zencore/include/zencore/windows.h1
-rw-r--r--src/zencore/logging.cpp95
-rw-r--r--src/zencore/process.cpp765
-rw-r--r--src/zencore/testing.cpp52
-rw-r--r--src/zencore/thread.cpp735
-rw-r--r--src/zencore/trace.cpp16
-rw-r--r--src/zencore/workthreadpool.cpp20
-rw-r--r--src/zencore/zencore.cpp29
22 files changed, 1574 insertions, 866 deletions
diff --git a/src/zencore/compactbinary.cpp b/src/zencore/compactbinary.cpp
index 5e8ce22ed..9152a8bfc 100644
--- a/src/zencore/compactbinary.cpp
+++ b/src/zencore/compactbinary.cpp
@@ -2,6 +2,7 @@
#include "zencore/compactbinary.h"
+#include <zencore/assertfmt.h>
#include <zencore/base64.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinaryvalidation.h>
@@ -855,10 +856,10 @@ void
CbFieldView::CopyTo(MutableMemoryView Buffer) const
{
const MemoryView Source = GetViewNoType();
- ZEN_ASSERT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize());
- // TEXT("A buffer of %" UINT64_FMT " bytes was provided when %" UINT64_FMT " bytes are required"),
- // Buffer.GetSize(),
- // sizeof(CbFieldType) + Source.GetSize());
+ ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(),
+ "A buffer of {} bytes was provided when {} bytes are required",
+ Buffer.GetSize(),
+ sizeof(CbFieldType) + Source.GetSize());
*static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetSerializedType(Type);
Buffer.RightChopInline(sizeof(CbFieldType));
memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
@@ -963,10 +964,10 @@ void
CbArrayView::CopyTo(MutableMemoryView Buffer) const
{
const MemoryView Source = GetPayloadView();
- ZEN_ASSERT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize());
- // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."),
- // Buffer.GetSize(),
- // sizeof(CbFieldType) + Source.GetSize());
+ ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(),
+ "Buffer is {} bytes but {} is required.",
+ Buffer.GetSize(),
+ sizeof(CbFieldType) + Source.GetSize());
*static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType());
Buffer.RightChopInline(sizeof(CbFieldType));
@@ -1077,10 +1078,10 @@ void
CbObjectView::CopyTo(MutableMemoryView Buffer) const
{
const MemoryView Source = GetPayloadView();
- ZEN_ASSERT(Buffer.GetSize() == (sizeof(CbFieldType) + Source.GetSize()));
- // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."),
- // Buffer.GetSize(),
- // sizeof(CbFieldType) + Source.GetSize());
+ ZEN_ASSERT_FORMAT(Buffer.GetSize() == (sizeof(CbFieldType) + Source.GetSize()),
+ "Buffer is {} bytes but {} is required.",
+ Buffer.GetSize(),
+ sizeof(CbFieldType) + Source.GetSize());
*static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType());
Buffer.RightChopInline(sizeof(CbFieldType));
memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
@@ -1151,10 +1152,10 @@ TCbFieldIterator<FieldType>::CopyRangeTo(MutableMemoryView InBuffer) const
MemoryView Source;
if (TryGetSerializedRangeView(Source))
{
- ZEN_ASSERT(InBuffer.GetSize() == Source.GetSize());
- // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."),
- // InBuffer.GetSize(),
- // Source.GetSize());
+ ZEN_ASSERT_FORMAT(InBuffer.GetSize() == Source.GetSize(),
+ "Buffer is {} bytes but {} is required.",
+ InBuffer.GetSize(),
+ Source.GetSize());
memcpy(InBuffer.GetData(), Source.GetData(), Source.GetSize());
}
else
@@ -1654,7 +1655,7 @@ public:
break;
}
default:
- ZEN_ASSERT(false);
+ ZEN_ASSERT_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType()));
break;
}
diff --git a/src/zencore/compactbinarybuilder.cpp b/src/zencore/compactbinarybuilder.cpp
index d4ccd434d..5c08d2e6e 100644
--- a/src/zencore/compactbinarybuilder.cpp
+++ b/src/zencore/compactbinarybuilder.cpp
@@ -2,6 +2,7 @@
#include "zencore/compactbinarybuilder.h"
+#include <zencore/assertfmt.h>
#include <zencore/compactbinarypackage.h>
#include <zencore/compactbinaryvalidation.h>
#include <zencore/endian.h>
@@ -128,13 +129,10 @@ CbWriter::Save()
CbFieldViewIterator
CbWriter::Save(const MutableMemoryView Buffer)
{
- ZEN_ASSERT(States.size() == 1 && States.back().Flags == StateFlags::None);
- // TEXT("It is invalid to save while there are incomplete write operations."));
- ZEN_ASSERT(Data.size() > 0); // TEXT("It is invalid to save when nothing has been written."));
- ZEN_ASSERT(Buffer.GetSize() == Data.size());
- // TEXT("Buffer is %" UINT64_FMT " bytes but %" INT64_FMT " is required."),
- // Buffer.GetSize(),
- // Data.Num());
+ ZEN_ASSERT_FORMAT(States.size() == 1 && States.back().Flags == StateFlags::None,
+ "It is invalid to save while there are incomplete write operations.");
+ ZEN_ASSERT_FORMAT(Data.size() > 0, "It is invalid to save when nothing has been written.");
+ ZEN_ASSERT_FORMAT(Buffer.GetSize() == Data.size(), "Buffer is {} bytes but {} is required.", Buffer.GetSize(), Data.size());
memcpy(Buffer.GetData(), Data.data(), Data.size());
return CbFieldViewIterator::MakeRange(Buffer);
}
@@ -142,9 +140,9 @@ CbWriter::Save(const MutableMemoryView Buffer)
void
CbWriter::Save(BinaryWriter& Writer)
{
- ZEN_ASSERT(States.size() == 1 && States.back().Flags == StateFlags::None);
- // TEXT("It is invalid to save while there are incomplete write operations."));
- ZEN_ASSERT(Data.size() > 0); // TEXT("It is invalid to save when nothing has been written."));
+ ZEN_ASSERT_FORMAT(States.size() == 1 && States.back().Flags == StateFlags::None,
+ "It is invalid to save while there are incomplete write operations.");
+ ZEN_ASSERT_FORMAT(Data.size() > 0, "It is invalid to save when nothing has been written.");
Writer.Write(Data.data(), Data.size());
}
@@ -166,10 +164,9 @@ CbWriter::BeginField()
}
else
{
- ZEN_ASSERT((State.Flags & StateFlags::Name) == StateFlags::Name);
- // TEXT("A new field cannot be written until the previous field '%.*hs' is finished."),
- // GetActiveName().Len(),
- // GetActiveName().GetData());
+ ZEN_ASSERT_FORMAT((State.Flags & StateFlags::Name) == StateFlags::Name,
+ "A new field cannot be written until the previous field '{}' is finished.",
+ GetActiveName());
}
}
@@ -184,8 +181,8 @@ CbWriter::EndField(CbFieldType Type)
}
else
{
- ZEN_ASSERT((State.Flags & StateFlags::Object) == StateFlags::None);
- // TEXT("It is invalid to write an object field without a unique non-empty name."));
+ ZEN_ASSERT((State.Flags & StateFlags::Object) == StateFlags::None,
+ "It is invalid to write an object field without a unique non-empty name.");
}
if (State.Count == 0)
@@ -207,21 +204,18 @@ CbWriter&
CbWriter::SetName(const std::string_view Name)
{
WriterState& State = States.back();
- ZEN_ASSERT((State.Flags & StateFlags::Array) != StateFlags::Array);
- // TEXT("It is invalid to write a name for an array field. Name '%.*hs'"),
- // Name.Len(),
- // Name.GetData());
- ZEN_ASSERT(!Name.empty());
- // TEXT("%s"),
- //(State.Flags & EStateFlags::Object) == EStateFlags::Object
- // ? TEXT("It is invalid to write an empty name for an object field. Specify a unique non-empty name.")
- // : TEXT("It is invalid to write an empty name for a top-level field. Specify a name or avoid this call."));
- ZEN_ASSERT((State.Flags & (StateFlags::Name | StateFlags::Field)) == StateFlags::None);
- // TEXT("A new field '%.*hs' cannot be written until the previous field '%.*hs' is finished."),
- // Name.Len(),
- // Name.GetData(),
- // GetActiveName().Len(),
- // GetActiveName().GetData());
+ ZEN_ASSERT_FORMAT((State.Flags & StateFlags::Array) != StateFlags::Array,
+ "It is invalid to write a name for an array field. Name '{}'",
+ Name);
+ ZEN_ASSERT_FORMAT(!Name.empty(),
+ "{}",
+ (State.Flags & StateFlags::Object) == StateFlags::Object
+ ? "It is invalid to write an empty name for an object field. Specify a unique non-empty name."
+ : "It is invalid to write an empty name for a top-level field. Specify a name or avoid this call.");
+ ZEN_ASSERT_FORMAT((State.Flags & (StateFlags::Name | StateFlags::Field)) == StateFlags::None,
+ "A new field '{}' cannot be written until the previous field '{}' is finished.",
+ Name,
+ GetActiveName());
BeginField();
State.Flags |= StateFlags::Name;
@@ -296,7 +290,7 @@ CbWriter::MakeFieldsUniform(const int64_t FieldBeginOffset, const int64_t FieldE
void
CbWriter::AddField(const CbFieldView& Value)
{
- ZEN_ASSERT(Value.HasValue()); // , TEXT("It is invalid to write a field with no value."));
+ ZEN_ASSERT_FORMAT(Value.HasValue(), "It is invalid to write a field with no value.");
BeginField();
EndField(AppendCompactBinary(Value, Data));
}
@@ -318,11 +312,10 @@ CbWriter::BeginObject()
void
CbWriter::EndObject()
{
- ZEN_ASSERT(States.size() > 1 && (States.back().Flags & StateFlags::Object) == StateFlags::Object);
-
- // TEXT("It is invalid to end an object when an object is not at the top of the stack."));
- ZEN_ASSERT((States.back().Flags & StateFlags::Field) == StateFlags::None);
- // TEXT("It is invalid to end an object until the previous field is finished."));
+ ZEN_ASSERT_FORMAT(States.size() > 1 && (States.back().Flags & StateFlags::Object) == StateFlags::Object,
+ "It is invalid to end an object when an object is not at the top of the stack.");
+ ZEN_ASSERT_FORMAT((States.back().Flags & StateFlags::Field) == StateFlags::None,
+ "It is invalid to end an object until the previous field is finished.");
const bool bUniform = IsUniformType(States.back().UniformType);
const uint64_t Count = States.back().Count;
@@ -378,10 +371,10 @@ CbWriter::BeginArray()
void
CbWriter::EndArray()
{
- ZEN_ASSERT(States.size() > 1 && (States.back().Flags & StateFlags::Array) == StateFlags::Array);
- // TEXT("Invalid attempt to end an array when an array is not at the top of the stack."));
- ZEN_ASSERT((States.back().Flags & StateFlags::Field) == StateFlags::None);
- // TEXT("It is invalid to end an array until the previous field is finished."));
+ ZEN_ASSERT_FORMAT(States.size() > 1 && (States.back().Flags & StateFlags::Array) == StateFlags::Array,
+ "Invalid attempt to end an array when an array is not at the top of the stack.");
+ ZEN_ASSERT_FORMAT((States.back().Flags & StateFlags::Field) == StateFlags::None,
+ "It is invalid to end an array until the previous field is finished.");
const bool bUniform = IsUniformType(States.back().UniformType);
const uint64_t Count = States.back().Count;
States.pop_back();
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index 06cda7382..29ec14e0c 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -7,6 +7,7 @@
#include <zencore/fmtutils.h>
#include <zencore/iobuffer.h>
#include <zencore/logging.h>
+#include <zencore/process.h>
#include <zencore/stream.h>
#include <zencore/string.h>
#include <zencore/testing.h>
@@ -333,11 +334,17 @@ SupportsBlockRefCounting(std::filesystem::path Path)
#endif // ZEN_PLATFORM_WINDOWS
}
-bool
+static bool
CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
{
#if ZEN_PLATFORM_WINDOWS
- windows::Handle FromFile(CreateFileW(FromPath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr));
+ windows::Handle FromFile(CreateFileW(FromPath.c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ 0,
+ nullptr));
if (FromFile == INVALID_HANDLE_VALUE)
{
FromFile.Detach();
@@ -401,8 +408,10 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
FILE_DISPOSITION_INFO FileDisposition = {TRUE};
if (!SetFileInformationByHandle(TargetFile, FileDispositionInfo, &FileDisposition, sizeof FileDisposition))
{
+ const DWORD ErrorCode = ::GetLastError();
TargetFile.Close();
DeleteFileW(ToPath.c_str());
+ SetLastError(ErrorCode);
return false;
}
@@ -531,6 +540,19 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
#endif // ZEN_PLATFORM_WINDOWS
}
+void
+CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options, std::error_code& OutErrorCode)
+{
+ OutErrorCode.clear();
+
+ bool Success = CopyFile(FromPath, ToPath, Options);
+
+ if (!Success)
+ {
+ OutErrorCode = MakeErrorCodeFromLastError();
+ }
+}
+
bool
CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options)
{
@@ -613,6 +635,133 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
}
void
+CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options)
+{
+ // Validate arguments
+
+ if (FromPath.empty() || !std::filesystem::is_directory(FromPath))
+ throw std::runtime_error("invalid CopyTree source directory specified");
+
+ if (ToPath.empty())
+ throw std::runtime_error("no CopyTree target specified");
+
+ if (Options.MustClone && !SupportsBlockRefCounting(FromPath))
+ throw std::runtime_error(fmt::format("cloning not possible from '{}'", FromPath));
+
+ if (std::filesystem::exists(ToPath))
+ {
+ if (!std::filesystem::is_directory(ToPath))
+ {
+ throw std::runtime_error(fmt::format("specified CopyTree target '{}' is not a directory", ToPath));
+ }
+ }
+ else
+ {
+ std::filesystem::create_directories(ToPath);
+ }
+
+ if (Options.MustClone && !SupportsBlockRefCounting(ToPath))
+ throw std::runtime_error(fmt::format("cloning not possible from '{}'", ToPath));
+
+ // Verify source/target relationships
+
+ std::error_code Ec;
+ std::filesystem::path FromCanonical = std::filesystem::canonical(FromPath, Ec);
+
+ if (!Ec)
+ {
+ std::filesystem::path ToCanonical = std::filesystem::canonical(ToPath, Ec);
+
+ if (!Ec)
+ {
+ if (FromCanonical == ToCanonical)
+ {
+ throw std::runtime_error("Target and source must be distinct files or directories");
+ }
+
+ if (ToCanonical.generic_string().starts_with(FromCanonical.generic_string()) ||
+ FromCanonical.generic_string().starts_with(ToCanonical.generic_string()))
+ {
+ throw std::runtime_error("Invalid parent/child relationship for source/target directories");
+ }
+ }
+ }
+
+ struct CopyVisitor : public FileSystemTraversal::TreeVisitor
+ {
+ CopyVisitor(std::filesystem::path InBasePath, zen::CopyFileOptions InCopyOptions) : BasePath(InBasePath), CopyOptions(InCopyOptions)
+ {
+ }
+
+ virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize) override
+ {
+ std::error_code Ec;
+ const std::filesystem::path Relative = std::filesystem::relative(Parent, BasePath, Ec);
+
+ if (Ec)
+ {
+ FailedFileCount++;
+ }
+ else
+ {
+ const std::filesystem::path FromPath = Parent / File;
+ std::filesystem::path ToPath;
+
+ if (Relative.compare("."))
+ {
+ zen::CreateDirectories(TargetPath / Relative);
+
+ ToPath = TargetPath / Relative / File;
+ }
+ else
+ {
+ ToPath = TargetPath / File;
+ }
+
+ try
+ {
+ if (zen::CopyFile(FromPath, ToPath, CopyOptions))
+ {
+ ++FileCount;
+ ByteCount += FileSize;
+ }
+ else
+ {
+ throw std::runtime_error("CopyFile failed in an unexpected way");
+ }
+ }
+ catch (std::exception& Ex)
+ {
+ ++FailedFileCount;
+
+ throw std::runtime_error(fmt::format("failed to copy '{}' to '{}': '{}'", FromPath, ToPath, Ex.what()));
+ }
+ }
+ }
+
+ virtual bool VisitDirectory(const std::filesystem::path&, const path_view&) override { return true; }
+
+ std::filesystem::path BasePath;
+ std::filesystem::path TargetPath;
+ zen::CopyFileOptions CopyOptions;
+ int FileCount = 0;
+ uint64_t ByteCount = 0;
+ int FailedFileCount = 0;
+ };
+
+ CopyVisitor Visitor{FromPath, Options};
+ Visitor.TargetPath = ToPath;
+
+ FileSystemTraversal Traversal;
+ Traversal.TraverseFileSystem(FromPath, Visitor);
+
+ if (Visitor.FailedFileCount)
+ {
+ throw std::runtime_error(fmt::format("{} file copy operations FAILED", Visitor.FailedFileCount));
+ }
+}
+
+void
WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t BufferCount)
{
#if ZEN_PLATFORM_WINDOWS
@@ -1139,12 +1288,12 @@ PathFromHandle(void* NativeHandle, std::error_code& Ec)
{
if (NativeHandle == nullptr)
{
- return std::filesystem::path();
+ return "<error handle 'nullptr'>";
}
#if ZEN_PLATFORM_WINDOWS
if (NativeHandle == INVALID_HANDLE_VALUE)
{
- return std::filesystem::path();
+ return "<error handle 'invalid handle'>";
}
auto GetFinalPathNameByHandleWRetry =
@@ -1181,7 +1330,7 @@ PathFromHandle(void* NativeHandle, std::error_code& Ec)
if (Error != ERROR_SUCCESS)
{
Ec = MakeErrorCodeFromLastError();
- return std::filesystem::path();
+ return fmt::format("<error handle '{}'>", Ec.message());
}
if (RequiredLengthIncludingNul < PathDataSize)
@@ -1198,7 +1347,7 @@ PathFromHandle(void* NativeHandle, std::error_code& Ec)
if (Error != ERROR_SUCCESS)
{
Ec = MakeErrorCodeFromLastError();
- return std::filesystem::path();
+ return fmt::format("<error handle '{}'>", Ec.message());
}
ZEN_UNUSED(FinalLength);
return FullPath;
@@ -1212,7 +1361,7 @@ PathFromHandle(void* NativeHandle, std::error_code& Ec)
if (BytesRead <= 0)
{
Ec = MakeErrorCodeFromLastError();
- return {};
+ return fmt::format("<error handle '{}'>", Ec.message());
}
Link[BytesRead] = '\0';
@@ -1223,7 +1372,7 @@ PathFromHandle(void* NativeHandle, std::error_code& Ec)
if (fcntl(Fd, F_GETPATH, Path) < 0)
{
Ec = MakeErrorCodeFromLastError();
- return {};
+ return fmt::format("<error handle '{}'>", Ec.message());
}
return Path;
@@ -1460,6 +1609,76 @@ RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles)
return Result;
}
+std::error_code
+RotateDirectories(const std::filesystem::path& DirectoryName, std::size_t MaxDirectories)
+{
+ const std::filesystem::path BasePath(DirectoryName.parent_path());
+ const std::string Stem(DirectoryName.stem().string());
+
+ auto GetPathForIndex = [&](size_t Index) -> std::filesystem::path {
+ if (Index == 0)
+ {
+ return BasePath / Stem;
+ }
+ return BasePath / fmt::format("{}.{}", Stem, Index);
+ };
+
+ auto IsEmpty = [](const std::filesystem::path& Path, std::error_code& Ec) -> bool { return std::filesystem::is_empty(Path, Ec); };
+
+ std::error_code Result;
+ const bool BaseIsEmpty = IsEmpty(GetPathForIndex(0), Result);
+ if (Result)
+ {
+ return Result;
+ }
+
+ if (BaseIsEmpty)
+ return Result;
+
+ for (std::size_t i = MaxDirectories; i > 0; i--)
+ {
+ const std::filesystem::path SourcePath = GetPathForIndex(i - 1);
+
+ if (std::filesystem::exists(SourcePath))
+ {
+ std::filesystem::path TargetPath = GetPathForIndex(i);
+
+ std::error_code DummyEc;
+ if (std::filesystem::exists(TargetPath, DummyEc))
+ {
+ std::filesystem::remove_all(TargetPath, DummyEc);
+ }
+ std::filesystem::rename(SourcePath, TargetPath, DummyEc);
+ }
+ }
+
+ return Result;
+}
+
+std::filesystem::path
+SearchPathForExecutable(std::string_view ExecutableName)
+{
+#if ZEN_PLATFORM_WINDOWS
+ std::wstring Executable(Utf8ToWide(ExecutableName));
+
+ DWORD Result = SearchPathW(nullptr, Executable.c_str(), L".exe", 0, nullptr, nullptr);
+
+ if (!Result)
+ return ExecutableName;
+
+ auto PathBuffer = std::make_unique_for_overwrite<WCHAR[]>(Result);
+
+ Result = SearchPathW(nullptr, Executable.c_str(), L".exe", Result, PathBuffer.get(), nullptr);
+
+ if (!Result)
+ return ExecutableName;
+
+ return PathBuffer.get();
+#else
+ return ExecutableName;
+#endif
+}
+
//////////////////////////////////////////////////////////////////////////
//
// Testing related code follows...
@@ -1619,6 +1838,55 @@ TEST_CASE("PathBuilder")
# endif
}
+TEST_CASE("RotateDirectories")
+{
+ std::filesystem::path TestBaseDir = GetRunningExecutablePath().parent_path() / ".test";
+ CleanDirectory(TestBaseDir);
+ std::filesystem::path RotateDir = TestBaseDir / "rotate_dir" / "dir_to_rotate";
+ IoBuffer DummyFileData = IoBufferBuilder::MakeCloneFromMemory("blubb", 5);
+
+ auto NewDir = [&] {
+ CreateDirectories(RotateDir);
+ WriteFile(RotateDir / ".placeholder", DummyFileData);
+ };
+
+ auto DirWithSuffix = [&](int Index) -> std::filesystem::path { return RotateDir.generic_string().append(fmt::format(".{}", Index)); };
+
+ const int RotateMax = 10;
+
+ NewDir();
+ CHECK(std::filesystem::exists(RotateDir));
+ RotateDirectories(RotateDir, RotateMax);
+ CHECK(!std::filesystem::exists(RotateDir));
+ CHECK(std::filesystem::exists(DirWithSuffix(1)));
+ NewDir();
+ CHECK(std::filesystem::exists(RotateDir));
+ RotateDirectories(RotateDir, RotateMax);
+ CHECK(!std::filesystem::exists(RotateDir));
+ CHECK(std::filesystem::exists(DirWithSuffix(1)));
+ CHECK(std::filesystem::exists(DirWithSuffix(2)));
+
+ for (int i = 0; i < RotateMax; ++i)
+ {
+ NewDir();
+ std::error_code Ec = RotateDirectories(RotateDir, 10);
+ const bool IsError = !!Ec;
+ CHECK_EQ(IsError, false);
+ }
+
+ CHECK(!std::filesystem::exists(RotateDir));
+
+ for (int i = 0; i < RotateMax; ++i)
+ {
+ CHECK(std::filesystem::exists(DirWithSuffix(i + 1)));
+ }
+
+ for (int i = RotateMax; i < RotateMax + 5; ++i)
+ {
+ CHECK(!std::filesystem::exists(DirWithSuffix(RotateMax + i + 1)));
+ }
+}
+
#endif
} // namespace zen
diff --git a/src/zencore/include/zencore/assertfmt.h b/src/zencore/include/zencore/assertfmt.h
new file mode 100644
index 000000000..56383ffd9
--- /dev/null
+++ b/src/zencore/include/zencore/assertfmt.h
@@ -0,0 +1,48 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/zencore.h>
+
+#include <fmt/args.h>
+#include <string_view>
+
+namespace zen {
+
+namespace assert {
+ template<typename... T>
+ auto AssertCaptureArguments(T&&... Args)
+ {
+ return fmt::make_format_args(Args...);
+ }
+
+ void ExecAssertFmt
+ [[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, std::string_view Format, fmt::format_args Args);
+
+ // MSVC (v19.00.24215.1 at time of writing) ignores no-inline attributes on
+ // lambdas. This can be worked around by calling the lambda from inside this
+ // templated (and correctly non-inlined) function.
+ template<typename RetType = void, class InnerType, typename... ArgTypes>
+ RetType ZEN_FORCENOINLINE ZEN_DEBUG_SECTION CallColdNoInline(InnerType&& Inner, ArgTypes const&... Args)
+ {
+ return Inner(Args...);
+ }
+
+} // namespace assert
+
+#define ZEN_ASSERT_FORMAT(x, fmt, ...) \
+ do \
+ { \
+ using namespace std::literals; \
+ if (x) [[likely]] \
+ break; \
+ zen::assert::CallColdNoInline([&]() ZEN_FORCEINLINE { \
+ zen::assert::ExecAssertFmt(__FILE__, \
+ __LINE__, \
+ __FUNCTION__, \
+ "assert(" #x ") failed: " fmt ""sv, \
+ zen::assert::AssertCaptureArguments(__VA_ARGS__)); \
+ }); \
+ } while (false)
+
+} // namespace zen
diff --git a/src/zencore/include/zencore/blockingqueue.h b/src/zencore/include/zencore/blockingqueue.h
index f92df5a54..e91fdc659 100644
--- a/src/zencore/include/zencore/blockingqueue.h
+++ b/src/zencore/include/zencore/blockingqueue.h
@@ -22,7 +22,6 @@ public:
{
std::lock_guard Lock(m_Lock);
m_Queue.emplace_back(std::move(Item));
- m_Size++;
}
m_NewItemSignal.notify_one();
@@ -30,31 +29,33 @@ public:
bool WaitAndDequeue(T& Item)
{
- if (m_CompleteAdding.load())
- {
- return false;
- }
-
std::unique_lock Lock(m_Lock);
- m_NewItemSignal.wait(Lock, [this]() { return !m_Queue.empty() || m_CompleteAdding.load(); });
-
- if (!m_Queue.empty())
+ if (m_Queue.empty())
{
- Item = std::move(m_Queue.front());
- m_Queue.pop_front();
- m_Size--;
-
- return true;
+ if (m_CompleteAdding)
+ {
+ return false;
+ }
+ m_NewItemSignal.wait(Lock, [this]() { return !m_Queue.empty() || m_CompleteAdding; });
+ if (m_Queue.empty())
+ {
+ ZEN_ASSERT(m_CompleteAdding);
+ return false;
+ }
}
-
- return false;
+ Item = std::move(m_Queue.front());
+ m_Queue.pop_front();
+ return true;
}
void CompleteAdding()
{
- if (!m_CompleteAdding.load())
+ std::unique_lock Lock(m_Lock);
+ if (!m_CompleteAdding)
{
- m_CompleteAdding.store(true);
+ m_CompleteAdding = true;
+
+ Lock.unlock();
m_NewItemSignal.notify_all();
}
}
@@ -69,8 +70,7 @@ private:
mutable std::mutex m_Lock;
std::condition_variable m_NewItemSignal;
std::deque<T> m_Queue;
- std::atomic_bool m_CompleteAdding{false};
- std::atomic_uint32_t m_Size;
+ bool m_CompleteAdding = false;
};
} // namespace zen
diff --git a/src/zencore/include/zencore/compactbinarybuilder.h b/src/zencore/include/zencore/compactbinarybuilder.h
index 89f69c1ab..9c81cf490 100644
--- a/src/zencore/include/zencore/compactbinarybuilder.h
+++ b/src/zencore/include/zencore/compactbinarybuilder.h
@@ -654,6 +654,24 @@ operator<<(CbWriter& Writer, const Oid& Value)
ZENCORE_API CbWriter& operator<<(CbWriter& Writer, DateTime Value);
ZENCORE_API CbWriter& operator<<(CbWriter& Writer, TimeSpan Value);
+ZENCORE_API inline TimeSpan
+ToTimeSpan(std::chrono::seconds Secs)
+{
+ return TimeSpan(0, 0, gsl::narrow<int>(Secs.count()));
+};
+ZENCORE_API inline TimeSpan
+ToTimeSpan(std::chrono::milliseconds MS)
+{
+ return TimeSpan(MS.count() * TimeSpan::TicksPerMillisecond);
+}
+ZENCORE_API inline DateTime
+ToDateTime(std::chrono::system_clock::time_point TimePoint)
+{
+ time_t Time = std::chrono::system_clock::to_time_t(TimePoint);
+ tm UTCTime = *gmtime(&Time);
+ return DateTime(1900 + UTCTime.tm_year, UTCTime.tm_mon, UTCTime.tm_mday, UTCTime.tm_hour, UTCTime.tm_min, UTCTime.tm_sec);
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void usonbuilder_forcelink(); // internal
diff --git a/src/zencore/include/zencore/filesystem.h b/src/zencore/include/zencore/filesystem.h
index 22eb40e45..233941479 100644
--- a/src/zencore/include/zencore/filesystem.h
+++ b/src/zencore/include/zencore/filesystem.h
@@ -1,5 +1,4 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
@@ -87,6 +86,11 @@ struct CopyFileOptions
};
ZENCORE_API bool CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
+ZENCORE_API void CopyFile(std::filesystem::path FromPath,
+ std::filesystem::path ToPath,
+ const CopyFileOptions& Options,
+ std::error_code& OutError);
+ZENCORE_API void CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
ZENCORE_API bool SupportsBlockRefCounting(std::filesystem::path Path);
ZENCORE_API void PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out);
@@ -211,7 +215,10 @@ void GetDirectoryContent(const std::filesystem::path& RootDir, uint8_t Flags, Di
std::string GetEnvVariable(std::string_view VariableName);
+std::filesystem::path SearchPathForExecutable(std::string_view ExecutableName);
+
std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles);
+std::error_code RotateDirectories(const std::filesystem::path& DirectoryName, std::size_t MaxDirectories);
//////////////////////////////////////////////////////////////////////////
diff --git a/src/zencore/include/zencore/iobuffer.h b/src/zencore/include/zencore/iobuffer.h
index 7accce41c..d891ed55b 100644
--- a/src/zencore/include/zencore/iobuffer.h
+++ b/src/zencore/include/zencore/iobuffer.h
@@ -31,6 +31,7 @@ enum class ZenContentType : uint8_t
kCSS = 11,
kPNG = 12,
kIcon = 13,
+ kXML = 14,
kCOUNT
};
@@ -70,6 +71,8 @@ ToString(ZenContentType ContentType)
return "png"sv;
case ZenContentType::kIcon:
return "icon"sv;
+ case ZenContentType::kXML:
+ return "xml"sv;
}
}
diff --git a/src/zencore/include/zencore/logbase.h b/src/zencore/include/zencore/logbase.h
index ad873aa51..00af68b0a 100644
--- a/src/zencore/include/zencore/logbase.h
+++ b/src/zencore/include/zencore/logbase.h
@@ -90,6 +90,9 @@ struct LoggerRef
bool ShouldLog(int Level) const;
inline operator bool() const { return SpdLogger != nullptr; }
+ void SetLogLevel(logging::level::LogLevel NewLogLevel);
+ logging::level::LogLevel GetLogLevel();
+
spdlog::logger* SpdLogger = nullptr;
};
diff --git a/src/zencore/include/zencore/logging.h b/src/zencore/include/zencore/logging.h
index d14d1ab8d..6d44e31df 100644
--- a/src/zencore/include/zencore/logging.h
+++ b/src/zencore/include/zencore/logging.h
@@ -35,6 +35,10 @@ LoggerRef ErrorLog();
void SetErrorLog(std::string_view LoggerId);
LoggerRef Get(std::string_view Name);
+void ConfigureLogLevels(level::LogLevel Level, std::string_view Loggers);
+void RefreshLogLevels();
+void RefreshLogLevels(level::LogLevel DefaultLevel);
+
struct LogCategory
{
inline LogCategory(std::string_view InCategory) : CategoryName(InCategory) {}
@@ -235,6 +239,14 @@ std::string_view EmitActivitiesForLogging(StringBuilderBase& OutString);
#define ZEN_LOG_SCOPE(...) ScopedLazyActivity $Activity##__LINE__([&](StringBuilderBase& Out) { Out << fmt::format(__VA_ARGS__); })
+#define ZEN_SCOPED_WARN(fmtstr, ...) \
+ do \
+ { \
+ ExtendableStringBuilder<256> ScopeString; \
+ const std::string_view Scopes = EmitActivitiesForLogging(ScopeString); \
+ ZEN_LOG(Log(), zen::logging::level::Warn, fmtstr "{}", ##__VA_ARGS__, Scopes); \
+ } while (false)
+
#define ZEN_SCOPED_ERROR(fmtstr, ...) \
do \
{ \
diff --git a/src/zencore/include/zencore/process.h b/src/zencore/include/zencore/process.h
new file mode 100644
index 000000000..d90a32301
--- /dev/null
+++ b/src/zencore/include/zencore/process.h
@@ -0,0 +1,98 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/thread.h>
+#include <zencore/zencore.h>
+
+#include <filesystem>
+
+namespace zen {
+
+/** Basic process abstraction
+ */
+class ProcessHandle
+{
+public:
+ ZENCORE_API ProcessHandle();
+
+ ProcessHandle(const ProcessHandle&) = delete;
+ ProcessHandle& operator=(const ProcessHandle&) = delete;
+
+ ZENCORE_API ~ProcessHandle();
+
+ ZENCORE_API void Initialize(int Pid);
+ ZENCORE_API void Initialize(void* ProcessHandle); /// Initialize with an existing handle - takes ownership of the handle
+ ZENCORE_API [[nodiscard]] bool IsRunning() const;
+ ZENCORE_API [[nodiscard]] bool IsValid() const;
+ ZENCORE_API bool Wait(int TimeoutMs = -1);
+ ZENCORE_API int WaitExitCode();
+ ZENCORE_API void Terminate(int ExitCode);
+ ZENCORE_API void Reset();
+ [[nodiscard]] inline int Pid() const { return m_Pid; }
+
+private:
+ void* m_ProcessHandle = nullptr;
+ int m_Pid = 0;
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ int m_ExitCode = -1;
+#endif
+};
+
+/** Basic process creation
+ */
+struct CreateProcOptions
+{
+ enum
+ {
+ Flag_NewConsole = 1 << 0,
+ Flag_Elevated = 1 << 1,
+ Flag_Unelevated = 1 << 2,
+ };
+
+ const std::filesystem::path* WorkingDirectory = nullptr;
+ uint32_t Flags = 0;
+ std::filesystem::path StdoutFile;
+};
+
+#if ZEN_PLATFORM_WINDOWS
+using CreateProcResult = void*; // handle to the process
+#else
+using CreateProcResult = int32_t; // pid
+#endif
+
+ZENCORE_API CreateProcResult CreateProc(const std::filesystem::path& Executable,
+ std::string_view CommandLine, // should also include arg[0] (executable name)
+ const CreateProcOptions& Options = {});
+
+/** Process monitor - monitors a list of running processes via polling
+
+ Intended to be used to monitor a set of "sponsor" processes, where
+ we need to determine when none of them remain alive
+
+ */
+
+class ProcessMonitor
+{
+public:
+ ProcessMonitor();
+ ~ProcessMonitor();
+
+ ZENCORE_API bool IsRunning();
+ ZENCORE_API void AddPid(int Pid);
+ ZENCORE_API bool IsActive() const;
+
+private:
+ using HandleType = void*;
+
+ mutable RwLock m_Lock;
+ std::vector<HandleType> m_ProcessHandles;
+};
+
+ZENCORE_API bool IsProcessRunning(int pid);
+ZENCORE_API int GetCurrentProcessId();
+int GetProcessId(CreateProcResult ProcId);
+
+void process_forcelink(); // internal
+
+} // namespace zen
diff --git a/src/zencore/include/zencore/string.h b/src/zencore/include/zencore/string.h
index e3de2224c..3aec1647d 100644
--- a/src/zencore/include/zencore/string.h
+++ b/src/zencore/include/zencore/string.h
@@ -809,6 +809,19 @@ ForEachStrTok(const std::string_view& Str, char Delim, Fn&& Func)
//////////////////////////////////////////////////////////////////////////
+inline std::string_view
+ToString(bool Value)
+{
+ using namespace std::literals;
+ if (Value)
+ {
+ return "true"sv;
+ }
+ return "false"sv;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
inline int32_t
StrCaseCompare(const char* Lhs, const char* Rhs, int64_t Length = -1)
{
diff --git a/src/zencore/include/zencore/thread.h b/src/zencore/include/zencore/thread.h
index 9f2671610..2d0ef7396 100644
--- a/src/zencore/include/zencore/thread.h
+++ b/src/zencore/include/zencore/thread.h
@@ -10,6 +10,8 @@
#include <string_view>
#include <vector>
+#define ZEN_USE_WINDOWS_EVENTS ZEN_PLATFORM_WINDOWS
+
namespace zen {
void SetCurrentThreadName(std::string_view ThreadName);
@@ -107,7 +109,7 @@ public:
ZENCORE_API bool Wait(int TimeoutMs = -1);
ZENCORE_API void Close();
-#if ZEN_PLATFORM_WINDOWS
+#if ZEN_USE_WINDOWS_EVENTS
inline void* GetWindowsHandle() { return m_EventHandle; }
#endif
@@ -204,87 +206,7 @@ private:
Event Complete;
};
-/** Basic process abstraction
- */
-class ProcessHandle
-{
-public:
- ZENCORE_API ProcessHandle();
-
- ProcessHandle(const ProcessHandle&) = delete;
- ProcessHandle& operator=(const ProcessHandle&) = delete;
-
- ZENCORE_API ~ProcessHandle();
-
- ZENCORE_API void Initialize(int Pid);
- ZENCORE_API void Initialize(void* ProcessHandle); /// Initialize with an existing handle - takes ownership of the handle
- ZENCORE_API [[nodiscard]] bool IsRunning() const;
- ZENCORE_API [[nodiscard]] bool IsValid() const;
- ZENCORE_API bool Wait(int TimeoutMs = -1);
- ZENCORE_API int WaitExitCode();
- ZENCORE_API void Terminate(int ExitCode);
- ZENCORE_API void Reset();
- [[nodiscard]] inline int Pid() const { return m_Pid; }
-
-private:
- void* m_ProcessHandle = nullptr;
- int m_Pid = 0;
-};
-
-/** Basic process creation
- */
-struct CreateProcOptions
-{
- enum
- {
- Flag_NewConsole = 1 << 0,
- Flag_Elevated = 1 << 1,
- Flag_Unelevated = 1 << 2,
- };
-
- const std::filesystem::path* WorkingDirectory = nullptr;
- uint32_t Flags = 0;
-};
-
-#if ZEN_PLATFORM_WINDOWS
-using CreateProcResult = void*; // handle to the process
-#else
-using CreateProcResult = int32_t; // pid
-#endif
-
-ZENCORE_API CreateProcResult CreateProc(const std::filesystem::path& Executable,
- std::string_view CommandLine, // should also include arg[0] (executable name)
- const CreateProcOptions& Options = {});
-
-/** Process monitor - monitors a list of running processes via polling
-
- Intended to be used to monitor a set of "sponsor" processes, where
- we need to determine when none of them remain alive
-
- */
-
-class ProcessMonitor
-{
-public:
- ProcessMonitor();
- ~ProcessMonitor();
-
- ZENCORE_API bool IsRunning();
- ZENCORE_API void AddPid(int Pid);
- ZENCORE_API bool IsActive() const;
-
-private:
- using HandleType = void*;
-
- mutable RwLock m_Lock;
- std::vector<HandleType> m_ProcessHandles;
-};
-
-ZENCORE_API bool IsProcessRunning(int pid);
-ZENCORE_API int GetCurrentProcessId();
ZENCORE_API int GetCurrentThreadId();
-int GetProcessId(CreateProcResult ProcId);
-
ZENCORE_API void Sleep(int ms);
void thread_forcelink(); // internal
diff --git a/src/zencore/include/zencore/trace.h b/src/zencore/include/zencore/trace.h
index 665df5808..2d4c1e610 100644
--- a/src/zencore/include/zencore/trace.h
+++ b/src/zencore/include/zencore/trace.h
@@ -17,6 +17,7 @@ ZEN_THIRD_PARTY_INCLUDES_START
ZEN_THIRD_PARTY_INCLUDES_END
#define ZEN_TRACE_CPU(x) TRACE_CPU_SCOPE(x)
+#define ZEN_TRACE_CPU_FLUSH(x) TRACE_CPU_SCOPE(x, trace::CpuScopeFlags::CpuFlush)
enum class TraceType
{
@@ -25,10 +26,10 @@ enum class TraceType
None
};
-void TraceInit();
+void TraceInit(std::string_view ProgramName);
void TraceShutdown();
bool IsTracing();
-void TraceStart(const char* HostOrPath, TraceType Type);
+void TraceStart(std::string_view ProgramName, const char* HostOrPath, TraceType Type);
bool TraceStop();
#else
diff --git a/src/zencore/include/zencore/windows.h b/src/zencore/include/zencore/windows.h
index 0943a85ea..14026fef1 100644
--- a/src/zencore/include/zencore/windows.h
+++ b/src/zencore/include/zencore/windows.h
@@ -24,6 +24,7 @@ struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax erro
# include <windows.h>
# undef GetObject
# undef SendMessage
+# undef CopyFile
ZEN_THIRD_PARTY_INCLUDES_END
diff --git a/src/zencore/logging.cpp b/src/zencore/logging.cpp
index 0d34372a9..90f4e2428 100644
--- a/src/zencore/logging.cpp
+++ b/src/zencore/logging.cpp
@@ -4,10 +4,14 @@
#include <zencore/string.h>
#include <zencore/testing.h>
+#include <zencore/thread.h>
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <spdlog/details/registry.h>
#include <spdlog/sinks/null_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
+ZEN_THIRD_PARTY_INCLUDES_END
#if ZEN_PLATFORM_WINDOWS
# pragma section(".zlog$a", read)
@@ -46,6 +50,8 @@ LoggingContext::~LoggingContext()
{
}
+//////////////////////////////////////////////////////////////////////////
+
static inline bool
IsErrorLevel(int LogLevel)
{
@@ -176,8 +182,77 @@ ToStringView(level::LogLevel Level)
} // namespace zen::logging::level
+//////////////////////////////////////////////////////////////////////////
+
namespace zen::logging {
+RwLock LogLevelsLock;
+std::string LogLevels[level::LogLevelCount];
+
+void
+ConfigureLogLevels(level::LogLevel Level, std::string_view Loggers)
+{
+ RwLock::ExclusiveLockScope _(LogLevelsLock);
+ LogLevels[Level] = Loggers;
+}
+
+void
+RefreshLogLevels(level::LogLevel* DefaultLevel)
+{
+ spdlog::details::registry::log_levels Levels;
+
+ {
+ RwLock::SharedLockScope _(LogLevelsLock);
+
+ for (int i = 0; i < level::LogLevelCount; ++i)
+ {
+ level::LogLevel CurrentLevel{i};
+
+ std::string_view Spec = LogLevels[i];
+
+ while (!Spec.empty())
+ {
+ std::string LoggerName;
+
+ if (auto CommaPos = Spec.find_first_of(','); CommaPos != std::string_view::npos)
+ {
+ LoggerName = Spec.substr(CommaPos + 1);
+ Spec.remove_prefix(CommaPos + 1);
+ }
+ else
+ {
+ LoggerName = Spec;
+ Spec = {};
+ }
+
+ Levels[LoggerName] = to_spdlog_level(CurrentLevel);
+ }
+ }
+ }
+
+ if (DefaultLevel)
+ {
+ spdlog::level::level_enum SpdDefaultLevel = to_spdlog_level(*DefaultLevel);
+ spdlog::details::registry::instance().set_levels(Levels, &SpdDefaultLevel);
+ }
+ else
+ {
+ spdlog::details::registry::instance().set_levels(Levels, nullptr);
+ }
+}
+
+void
+RefreshLogLevels(level::LogLevel DefaultLevel)
+{
+ RefreshLogLevels(&DefaultLevel);
+}
+
+void
+RefreshLogLevels()
+{
+ RefreshLogLevels(nullptr);
+}
+
void
SetLogLevel(level::LogLevel NewLogLevel)
{
@@ -240,6 +315,7 @@ Get(std::string_view Name)
if (!Logger)
{
Logger = Default().SpdLogger->clone(std::string(Name));
+ spdlog::apply_logger_env_levels(Logger);
spdlog::register_logger(Logger);
}
@@ -252,6 +328,11 @@ std::shared_ptr<spdlog::logger> ConLogger;
void
SuppressConsoleLog()
{
+ if (ConLogger)
+ {
+ spdlog::drop("console");
+ ConLogger = {};
+ }
ConLogger = spdlog::null_logger_mt("console");
}
@@ -262,6 +343,7 @@ ConsoleLog()
if (!ConLogger)
{
ConLogger = spdlog::stdout_color_mt("console");
+ spdlog::apply_logger_env_levels(ConLogger);
ConLogger->set_pattern("%v");
}
@@ -279,7 +361,6 @@ InitializeLogging()
void
ShutdownLogging()
{
- spdlog::drop_all();
spdlog::shutdown();
TheDefaultLogger = {};
}
@@ -321,6 +402,18 @@ LoggerRef::ShouldLog(int Level) const
return SpdLogger->should_log(static_cast<spdlog::level::level_enum>(Level));
}
+void
+LoggerRef::SetLogLevel(logging::level::LogLevel NewLogLevel)
+{
+ SpdLogger->set_level(to_spdlog_level(NewLogLevel));
+}
+
+logging::level::LogLevel
+LoggerRef::GetLogLevel()
+{
+ return logging::level::to_logging_level(SpdLogger->level());
+}
+
thread_local ScopedActivityBase* t_ScopeStack = nullptr;
ScopedActivityBase*
diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp
new file mode 100644
index 000000000..2d0ec2de6
--- /dev/null
+++ b/src/zencore/process.cpp
@@ -0,0 +1,765 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/process.h>
+
+#include <zencore/except.h>
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zencore/scopeguard.h>
+#include <zencore/string.h>
+#include <zencore/testing.h>
+
+#include <thread>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <shellapi.h>
+# include <Shlobj.h>
+# include <zencore/windows.h>
+#else
+# include <fcntl.h>
+# include <pthread.h>
+# include <signal.h>
+# include <sys/file.h>
+# include <sys/sem.h>
+# include <sys/stat.h>
+# include <sys/syscall.h>
+# include <sys/wait.h>
+# include <time.h>
+# include <unistd.h>
+#endif
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <fmt/format.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+#if ZEN_PLATFORM_LINUX
+const bool bNoZombieChildren = []() {
+ // When a child process exits it is put into a zombie state until the parent
+ // collects its result. This doesn't fit the Windows-like model that Zen uses
+ // where there is a less strict familial model and no zombification. Ignoring
+ // SIGCHLD signals removes the need to call wait() on zombies. Another option
+ // would be for the child to call setsid() but that would detatch the child
+ // from the terminal.
+ struct sigaction Action = {};
+ sigemptyset(&Action.sa_mask);
+ Action.sa_handler = SIG_IGN;
+ sigaction(SIGCHLD, &Action, nullptr);
+ return true;
+}();
+#endif
+
+ProcessHandle::ProcessHandle() = default;
+
+#if ZEN_PLATFORM_WINDOWS
+void
+ProcessHandle::Initialize(void* ProcessHandle)
+{
+ ZEN_ASSERT(m_ProcessHandle == nullptr);
+
+ if (ProcessHandle == INVALID_HANDLE_VALUE)
+ {
+ ProcessHandle = nullptr;
+ }
+
+ // TODO: perform some debug verification here to verify it's a valid handle?
+ m_ProcessHandle = ProcessHandle;
+ m_Pid = GetProcessId(m_ProcessHandle);
+}
+#endif // ZEN_PLATFORM_WINDOWS
+
+ProcessHandle::~ProcessHandle()
+{
+ Reset();
+}
+
+void
+ProcessHandle::Initialize(int Pid)
+{
+ ZEN_ASSERT(m_ProcessHandle == nullptr);
+
+#if ZEN_PLATFORM_WINDOWS
+ m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ if (Pid > 0)
+ {
+ m_ProcessHandle = (void*)(intptr_t(Pid));
+ }
+#endif
+
+ if (!m_ProcessHandle)
+ {
+ ThrowLastError(fmt::format("ProcessHandle::Initialize(pid: {}) failed", Pid));
+ }
+
+ m_Pid = Pid;
+}
+
+bool
+ProcessHandle::IsRunning() const
+{
+ bool bActive = false;
+
+#if ZEN_PLATFORM_WINDOWS
+ DWORD ExitCode = 0;
+ GetExitCodeProcess(m_ProcessHandle, &ExitCode);
+ bActive = (ExitCode == STILL_ACTIVE);
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ bActive = (kill(pid_t(m_Pid), 0) == 0);
+#endif
+
+ return bActive;
+}
+
+bool
+ProcessHandle::IsValid() const
+{
+ return (m_ProcessHandle != nullptr);
+}
+
+void
+ProcessHandle::Terminate(int ExitCode)
+{
+ if (!IsRunning())
+ {
+ return;
+ }
+
+ bool bSuccess = false;
+
+#if ZEN_PLATFORM_WINDOWS
+ TerminateProcess(m_ProcessHandle, ExitCode);
+ DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE);
+ bSuccess = (WaitResult != WAIT_OBJECT_0);
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ ZEN_UNUSED(ExitCode);
+ bSuccess = (kill(m_Pid, SIGKILL) == 0);
+#endif
+
+ if (!bSuccess)
+ {
+ // What might go wrong here, and what is meaningful to act on?
+ }
+}
+
+void
+ProcessHandle::Reset()
+{
+ if (IsValid())
+ {
+#if ZEN_PLATFORM_WINDOWS
+ CloseHandle(m_ProcessHandle);
+#endif
+ m_ProcessHandle = nullptr;
+ m_Pid = 0;
+ }
+}
+
+bool
+ProcessHandle::Wait(int TimeoutMs)
+{
+ using namespace std::literals;
+
+#if ZEN_PLATFORM_WINDOWS
+ const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
+
+ const DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, Timeout);
+
+ switch (WaitResult)
+ {
+ case WAIT_OBJECT_0:
+ return true;
+
+ case WAIT_TIMEOUT:
+ return false;
+
+ case WAIT_FAILED:
+ break;
+ }
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ const int SleepMs = 20;
+ timespec SleepTime = {0, SleepMs * 1000 * 1000};
+ for (int SleepedTimeMS = 0;; SleepedTimeMS += SleepMs)
+ {
+ int WaitState = 0;
+ waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED);
+
+ if (WIFEXITED(WaitState))
+ {
+ m_ExitCode = WEXITSTATUS(WaitState);
+ }
+
+ if (kill(m_Pid, 0) < 0)
+ {
+ int32_t LastError = zen::GetLastError();
+ if (LastError == ESRCH)
+ {
+ return true;
+ }
+ ThrowSystemError(static_cast<uint32_t>(LastError), "Process::Wait kill failed"sv);
+ }
+
+ if (TimeoutMs >= 0 && SleepedTimeMS >= TimeoutMs)
+ {
+ return false;
+ }
+
+ nanosleep(&SleepTime, nullptr);
+ }
+#endif
+
+ // What might go wrong here, and what is meaningful to act on?
+ ThrowLastError("Process::Wait failed"sv);
+}
+
+int
+ProcessHandle::WaitExitCode()
+{
+ Wait(-1);
+
+#if ZEN_PLATFORM_WINDOWS
+ DWORD ExitCode = 0;
+ GetExitCodeProcess(m_ProcessHandle, &ExitCode);
+
+ ZEN_ASSERT(ExitCode != STILL_ACTIVE);
+
+ return ExitCode;
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ return m_ExitCode;
+#else
+ ZEN_NOT_IMPLEMENTED();
+
+ return 0;
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+#if !ZEN_PLATFORM_WINDOWS || ZEN_WITH_TESTS
+static void
+BuildArgV(std::vector<char*>& Out, char* CommandLine)
+{
+ char* Cursor = CommandLine;
+ while (true)
+ {
+ // Skip leading whitespace
+ for (; *Cursor == ' '; ++Cursor)
+ ;
+
+ // Check for nullp terminator
+ if (*Cursor == '\0')
+ {
+ break;
+ }
+
+ Out.push_back(Cursor);
+
+ // Extract word
+ int QuoteCount = 0;
+ do
+ {
+ QuoteCount += (*Cursor == '\"');
+ if (*Cursor == ' ' && !(QuoteCount & 1))
+ {
+ break;
+ }
+ ++Cursor;
+ } while (*Cursor != '\0');
+
+ if (*Cursor == '\0')
+ {
+ break;
+ }
+
+ *Cursor = '\0';
+ ++Cursor;
+ }
+}
+#endif // !WINDOWS || TESTS
+
+#if ZEN_PLATFORM_WINDOWS
+static CreateProcResult
+CreateProcNormal(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
+ PROCESS_INFORMATION ProcessInfo{};
+ STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)};
+
+ bool InheritHandles = false;
+ void* Environment = nullptr;
+ LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr;
+ LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr;
+
+ DWORD CreationFlags = 0;
+ if (Options.Flags & CreateProcOptions::Flag_NewConsole)
+ {
+ CreationFlags |= CREATE_NEW_CONSOLE;
+ }
+
+ const wchar_t* WorkingDir = nullptr;
+ if (Options.WorkingDirectory != nullptr)
+ {
+ WorkingDir = Options.WorkingDirectory->c_str();
+ }
+
+ ExtendableWideStringBuilder<256> CommandLineZ;
+ CommandLineZ << CommandLine;
+
+ if (!Options.StdoutFile.empty())
+ {
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof sa;
+ sa.lpSecurityDescriptor = nullptr;
+ sa.bInheritHandle = TRUE;
+
+ StartupInfo.hStdInput = nullptr;
+ StartupInfo.hStdOutput = CreateFileW(Options.StdoutFile.c_str(),
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ,
+ &sa,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr);
+
+ const BOOL Success = DuplicateHandle(GetCurrentProcess(),
+ StartupInfo.hStdOutput,
+ GetCurrentProcess(),
+ &StartupInfo.hStdError,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS);
+
+ if (Success)
+ {
+ StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+ InheritHandles = true;
+ }
+ else
+ {
+ CloseHandle(StartupInfo.hStdOutput);
+ StartupInfo.hStdOutput = 0;
+ }
+ }
+
+ BOOL Success = CreateProcessW(Executable.c_str(),
+ CommandLineZ.Data(),
+ ProcessAttributes,
+ ThreadAttributes,
+ InheritHandles,
+ CreationFlags,
+ Environment,
+ WorkingDir,
+ &StartupInfo,
+ &ProcessInfo);
+
+ if (StartupInfo.dwFlags & STARTF_USESTDHANDLES)
+ {
+ CloseHandle(StartupInfo.hStdError);
+ CloseHandle(StartupInfo.hStdOutput);
+ }
+
+ if (!Success)
+ {
+ return nullptr;
+ }
+
+ CloseHandle(ProcessInfo.hThread);
+ return ProcessInfo.hProcess;
+}
+
+static CreateProcResult
+CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
+ /* Launches a binary with the shell as its parent. The shell (such as
+ Explorer) should be an unelevated process. */
+
+ // No sense in using this route if we are not elevated in the first place
+ if (IsUserAnAdmin() == FALSE)
+ {
+ return CreateProcNormal(Executable, CommandLine, Options);
+ }
+
+ // Get the users' shell process and open it for process creation
+ HWND ShellWnd = GetShellWindow();
+ if (ShellWnd == nullptr)
+ {
+ return nullptr;
+ }
+
+ DWORD ShellPid;
+ GetWindowThreadProcessId(ShellWnd, &ShellPid);
+
+ HANDLE Process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ShellPid);
+ if (Process == nullptr)
+ {
+ return nullptr;
+ }
+ auto $0 = MakeGuard([&] { CloseHandle(Process); });
+
+ // Creating a process as a child of another process is done by setting a
+ // thread-attribute list on the startup info passed to CreateProcess()
+ SIZE_T AttrListSize;
+ InitializeProcThreadAttributeList(nullptr, 1, 0, &AttrListSize);
+
+ auto AttrList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(AttrListSize);
+ auto $1 = MakeGuard([&] { free(AttrList); });
+
+ if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &AttrListSize))
+ {
+ return nullptr;
+ }
+
+ BOOL bOk =
+ UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, (HANDLE*)&Process, sizeof(Process), nullptr, nullptr);
+ if (!bOk)
+ {
+ return nullptr;
+ }
+
+ // By this point we know we are an elevated process. It is not allowed to
+ // create a process as a child of another unelevated process that share our
+ // elevated console window if we have one. So we'll need to create a new one.
+ uint32_t CreateProcFlags = EXTENDED_STARTUPINFO_PRESENT;
+ if (GetConsoleWindow() != nullptr)
+ {
+ CreateProcFlags |= CREATE_NEW_CONSOLE;
+ }
+ else
+ {
+ CreateProcFlags |= DETACHED_PROCESS;
+ }
+
+ // Everything is set up now so we can proceed and launch the process
+ STARTUPINFOEXW StartupInfo = {
+ .StartupInfo = {.cb = sizeof(STARTUPINFOEXW)},
+ .lpAttributeList = AttrList,
+ };
+ PROCESS_INFORMATION ProcessInfo = {};
+
+ if (Options.Flags & CreateProcOptions::Flag_NewConsole)
+ {
+ CreateProcFlags |= CREATE_NEW_CONSOLE;
+ }
+
+ ExtendableWideStringBuilder<256> CommandLineZ;
+ CommandLineZ << CommandLine;
+
+ ExtendableWideStringBuilder<256> CurrentDirZ;
+ LPCWSTR WorkingDirectoryPtr = nullptr;
+ if (Options.WorkingDirectory)
+ {
+ CurrentDirZ << Options.WorkingDirectory->native();
+ WorkingDirectoryPtr = CurrentDirZ.c_str();
+ }
+
+ bOk = CreateProcessW(Executable.c_str(),
+ CommandLineZ.Data(),
+ nullptr,
+ nullptr,
+ FALSE,
+ CreateProcFlags,
+ nullptr,
+ WorkingDirectoryPtr,
+ &StartupInfo.StartupInfo,
+ &ProcessInfo);
+ if (bOk == FALSE)
+ {
+ return nullptr;
+ }
+
+ CloseHandle(ProcessInfo.hThread);
+ return ProcessInfo.hProcess;
+}
+
+static CreateProcResult
+CreateProcElevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
+ ExtendableWideStringBuilder<256> CommandLineZ;
+ CommandLineZ << CommandLine;
+
+ SHELLEXECUTEINFO ShellExecuteInfo;
+ ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo));
+ ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo);
+ ShellExecuteInfo.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS;
+ ShellExecuteInfo.lpFile = Executable.c_str();
+ ShellExecuteInfo.lpVerb = TEXT("runas");
+ ShellExecuteInfo.nShow = SW_SHOW;
+ ShellExecuteInfo.lpParameters = CommandLineZ.c_str();
+
+ if (Options.WorkingDirectory != nullptr)
+ {
+ ShellExecuteInfo.lpDirectory = Options.WorkingDirectory->c_str();
+ }
+
+ if (::ShellExecuteEx(&ShellExecuteInfo))
+ {
+ return ShellExecuteInfo.hProcess;
+ }
+
+ return nullptr;
+}
+#endif // ZEN_PLATFORM_WINDOWS
+
+CreateProcResult
+CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
+#if ZEN_PLATFORM_WINDOWS
+ if (Options.Flags & CreateProcOptions::Flag_Unelevated)
+ {
+ return CreateProcUnelevated(Executable, CommandLine, Options);
+ }
+
+ if (Options.Flags & CreateProcOptions::Flag_Elevated)
+ {
+ return CreateProcElevated(Executable, CommandLine, Options);
+ }
+
+ return CreateProcNormal(Executable, CommandLine, Options);
+#else
+ std::vector<char*> ArgV;
+ std::string CommandLineZ(CommandLine);
+ BuildArgV(ArgV, CommandLineZ.data());
+ ArgV.push_back(nullptr);
+
+ int ChildPid = fork();
+ if (ChildPid < 0)
+ {
+ ThrowLastError("Failed to fork a new child process");
+ }
+ else if (ChildPid == 0)
+ {
+ if (Options.WorkingDirectory != nullptr)
+ {
+ int Result = chdir(Options.WorkingDirectory->c_str());
+ ZEN_UNUSED(Result);
+ }
+
+ if (execv(Executable.c_str(), ArgV.data()) < 0)
+ {
+ ThrowLastError("Failed to exec() a new process image");
+ }
+ }
+
+ return ChildPid;
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+ProcessMonitor::ProcessMonitor()
+{
+}
+
+ProcessMonitor::~ProcessMonitor()
+{
+ RwLock::ExclusiveLockScope _(m_Lock);
+
+ for (HandleType& Proc : m_ProcessHandles)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ CloseHandle(Proc);
+#endif
+ Proc = 0;
+ }
+}
+
+bool
+ProcessMonitor::IsRunning()
+{
+ RwLock::ExclusiveLockScope _(m_Lock);
+
+ bool FoundOne = false;
+
+ for (HandleType& Proc : m_ProcessHandles)
+ {
+ bool ProcIsActive;
+
+#if ZEN_PLATFORM_WINDOWS
+ DWORD ExitCode = 0;
+ GetExitCodeProcess(Proc, &ExitCode);
+
+ ProcIsActive = (ExitCode == STILL_ACTIVE);
+ if (!ProcIsActive)
+ {
+ CloseHandle(Proc);
+ }
+#else
+ int Pid = int(intptr_t(Proc));
+ ProcIsActive = IsProcessRunning(Pid);
+#endif
+
+ if (!ProcIsActive)
+ {
+ Proc = 0;
+ }
+
+ // Still alive
+ FoundOne |= ProcIsActive;
+ }
+
+ std::erase_if(m_ProcessHandles, [](HandleType Handle) { return Handle == 0; });
+
+ return FoundOne;
+}
+
+void
+ProcessMonitor::AddPid(int Pid)
+{
+ HandleType ProcessHandle;
+
+#if ZEN_PLATFORM_WINDOWS
+ ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
+#else
+ ProcessHandle = HandleType(intptr_t(Pid));
+#endif
+
+ if (ProcessHandle)
+ {
+ RwLock::ExclusiveLockScope _(m_Lock);
+ m_ProcessHandles.push_back(ProcessHandle);
+ }
+}
+
+bool
+ProcessMonitor::IsActive() const
+{
+ RwLock::SharedLockScope _(m_Lock);
+ return m_ProcessHandles.empty() == false;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+bool
+IsProcessRunning(int pid)
+{
+ // This function is arguably not super useful, a pid can be re-used
+ // by the OS so holding on to a pid and polling it over some time
+ // period will not necessarily tell you what you probably want to know.
+
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
+
+ if (!hProc)
+ {
+ DWORD Error = zen::GetLastError();
+
+ if (Error == ERROR_INVALID_PARAMETER)
+ {
+ return false;
+ }
+
+ ThrowSystemError(Error, fmt::format("failed to open process with pid {}", pid));
+ }
+
+ bool bStillActive = true;
+ DWORD ExitCode = 0;
+ if (0 != GetExitCodeProcess(hProc, &ExitCode))
+ {
+ bStillActive = ExitCode == STILL_ACTIVE;
+ }
+ else
+ {
+ ZEN_WARN("Unable to get exit code from handle for process '{}', treating the process as active", pid);
+ }
+
+ CloseHandle(hProc);
+
+ return bStillActive;
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ return (kill(pid_t(pid), 0) == 0);
+#endif
+}
+
+int
+GetCurrentProcessId()
+{
+#if ZEN_PLATFORM_WINDOWS
+ return ::GetCurrentProcessId();
+#else
+ return int(getpid());
+#endif
+}
+
+int
+GetProcessId(CreateProcResult ProcId)
+{
+#if ZEN_PLATFORM_WINDOWS
+ return static_cast<int>(::GetProcessId(ProcId));
+#else
+ return ProcId;
+#endif
+}
+
+#if ZEN_WITH_TESTS
+
+void
+process_forcelink()
+{
+}
+
+TEST_SUITE_BEGIN("core.process");
+
+TEST_CASE("Process")
+{
+ int Pid = GetCurrentProcessId();
+ CHECK(Pid > 0);
+ CHECK(IsProcessRunning(Pid));
+}
+
+TEST_CASE("BuildArgV")
+{
+ const char* Words[] = {"one", "two", "three", "four", "five"};
+ struct
+ {
+ int WordCount;
+ const char* Input;
+ } Cases[] = {
+ {0, ""},
+ {0, " "},
+ {1, "one"},
+ {1, " one"},
+ {1, "one "},
+ {2, "one two"},
+ {2, " one two"},
+ {2, "one two "},
+ {2, " one two"},
+ {2, "one two "},
+ {2, "one two "},
+ {3, "one two three"},
+ {3, "\"one\" two \"three\""},
+ {5, "one two three four five"},
+ };
+
+ for (const auto& Case : Cases)
+ {
+ std::vector<char*> OutArgs;
+ StringBuilder<64> Mutable;
+ Mutable << Case.Input;
+ BuildArgV(OutArgs, Mutable.Data());
+
+ CHECK_EQ(OutArgs.size(), Case.WordCount);
+
+ for (int i = 0, n = int(OutArgs.size()); i < n; ++i)
+ {
+ const char* Truth = Words[i];
+ size_t TruthLen = strlen(Truth);
+
+ const char* Candidate = OutArgs[i];
+ bool bQuoted = (Candidate[0] == '\"');
+ Candidate += bQuoted;
+
+ CHECK(strncmp(Truth, Candidate, TruthLen) == 0);
+
+ if (bQuoted)
+ {
+ CHECK_EQ(Candidate[TruthLen], '\"');
+ }
+ }
+ }
+}
+
+TEST_SUITE_END(/* core.process */);
+
+#endif
+
+} // namespace zen
diff --git a/src/zencore/testing.cpp b/src/zencore/testing.cpp
index 54d89ded2..936424e0f 100644
--- a/src/zencore/testing.cpp
+++ b/src/zencore/testing.cpp
@@ -5,12 +5,64 @@
#if ZEN_WITH_TESTS
+# include <doctest/doctest.h>
+
namespace zen::testing {
using namespace std::literals;
+struct TestListener : public doctest::IReporter
+{
+ const std::string_view ColorYellow = "\033[0;33m"sv;
+ const std::string_view ColorNone = "\033[0m"sv;
+
+ // constructor has to accept the ContextOptions by ref as a single argument
+ TestListener(const doctest::ContextOptions&) {}
+
+ void report_query(const doctest::QueryData& /*in*/) override {}
+
+ void test_run_start() override {}
+
+ void test_run_end(const doctest::TestRunStats& /*in*/) override {}
+
+ void test_case_start(const doctest::TestCaseData& in) override
+ {
+ Current = &in;
+ ZEN_CONSOLE("{}======== TEST_CASE: {:<50} ========{}", ColorYellow, Current->m_name, ColorNone);
+ }
+
+ // called when a test case is reentered because of unfinished subcases
+ void test_case_reenter(const doctest::TestCaseData& /*in*/) override
+ {
+ ZEN_CONSOLE("{}-------------------------------------------------------------------------------{}", ColorYellow, ColorNone);
+ }
+
+ void test_case_end(const doctest::CurrentTestCaseStats& /*in*/) override { Current = nullptr; }
+
+ void test_case_exception(const doctest::TestCaseException& /*in*/) override {}
+
+ void subcase_start(const doctest::SubcaseSignature& in) override
+ {
+ ZEN_CONSOLE("{}-------- SUBCASE: {:<50} --------{}",
+ ColorYellow,
+ fmt::format("{}/{}", Current->m_name, in.m_name.c_str()),
+ ColorNone);
+ }
+
+ void subcase_end() override {}
+
+ void log_assert(const doctest::AssertData& /*in*/) override {}
+
+ void log_message(const doctest::MessageData& /*in*/) override {}
+
+ void test_case_skipped(const doctest::TestCaseData& /*in*/) override {}
+
+ const doctest::TestCaseData* Current = nullptr;
+};
+
struct TestRunner::Impl
{
+ Impl() { REGISTER_LISTENER("ZenTestListener", 1, TestListener); }
doctest::Context Session;
};
diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp
index 1f1b1b8f5..149a0d781 100644
--- a/src/zencore/thread.cpp
+++ b/src/zencore/thread.cpp
@@ -8,6 +8,9 @@
#include <zencore/scopeguard.h>
#include <zencore/string.h>
#include <zencore/testing.h>
+#include <zencore/trace.h>
+
+#include <thread>
#if ZEN_PLATFORM_LINUX
# if !defined(_GNU_SOURCE)
@@ -15,15 +18,15 @@
# endif
#endif
-#if ZEN_PLATFORM_WINDOWS
-# include <shellapi.h>
-# include <Shlobj.h>
-# include <zencore/windows.h>
-#else
+#if !ZEN_USE_WINDOWS_EVENTS
# include <chrono>
# include <condition_variable>
# include <mutex>
+#endif
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#else
# include <fcntl.h>
# include <pthread.h>
# include <signal.h>
@@ -36,10 +39,6 @@
# include <unistd.h>
#endif
-#include <zencore/trace.h>
-
-#include <thread>
-
ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/format.h>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -78,22 +77,6 @@ SetNameInternal(DWORD thread_id, const char* name)
}
#endif
-#if ZEN_PLATFORM_LINUX
-const bool bNoZombieChildren = []() {
- // When a child process exits it is put into a zombie state until the parent
- // collects its result. This doesn't fit the Windows-like model that Zen uses
- // where there is a less strict familial model and no zombification. Ignoring
- // SIGCHLD siganals removes the need to call wait() on zombies. Another option
- // would be for the child to call setsid() but that would detatch the child
- // from the terminal.
- struct sigaction Action = {};
- sigemptyset(&Action.sa_mask);
- Action.sa_handler = SIG_IGN;
- sigaction(SIGCHLD, &Action, nullptr);
- return true;
-}();
-#endif
-
void
SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName)
{
@@ -152,12 +135,12 @@ RwLock::ReleaseExclusive() noexcept
//////////////////////////////////////////////////////////////////////////
-#if !ZEN_PLATFORM_WINDOWS
+#if !ZEN_USE_WINDOWS_EVENTS
struct EventInner
{
std::mutex Mutex;
std::condition_variable CondVar;
- bool volatile bSet = false;
+ std::atomic_bool bSet{false};
};
#endif // !ZEN_PLATFORM_WINDOWS
@@ -166,7 +149,7 @@ Event::Event()
bool bManualReset = true;
bool bInitialState = false;
-#if ZEN_PLATFORM_WINDOWS
+#if ZEN_USE_WINDOWS_EVENTS
m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr);
#else
ZEN_UNUSED(bManualReset);
@@ -184,13 +167,13 @@ Event::~Event()
void
Event::Set()
{
-#if ZEN_PLATFORM_WINDOWS
+#if ZEN_USE_WINDOWS_EVENTS
SetEvent(m_EventHandle);
#else
auto* Inner = (EventInner*)m_EventHandle;
{
std::unique_lock Lock(Inner->Mutex);
- Inner->bSet = true;
+ Inner->bSet.store(true);
}
Inner->CondVar.notify_all();
#endif
@@ -199,13 +182,13 @@ Event::Set()
void
Event::Reset()
{
-#if ZEN_PLATFORM_WINDOWS
+#if ZEN_USE_WINDOWS_EVENTS
ResetEvent(m_EventHandle);
#else
auto* Inner = (EventInner*)m_EventHandle;
{
std::unique_lock Lock(Inner->Mutex);
- Inner->bSet = false;
+ Inner->bSet.store(false);
}
#endif
}
@@ -213,10 +196,14 @@ Event::Reset()
void
Event::Close()
{
-#if ZEN_PLATFORM_WINDOWS
+#if ZEN_USE_WINDOWS_EVENTS
CloseHandle(m_EventHandle);
#else
auto* Inner = (EventInner*)m_EventHandle;
+ {
+ std::unique_lock Lock(Inner->Mutex);
+ Inner->bSet.store(true);
+ }
delete Inner;
#endif
m_EventHandle = nullptr;
@@ -225,7 +212,7 @@ Event::Close()
bool
Event::Wait(int TimeoutMs)
{
-#if ZEN_PLATFORM_WINDOWS
+#if ZEN_USE_WINDOWS_EVENTS
using namespace std::literals;
const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
@@ -239,25 +226,34 @@ Event::Wait(int TimeoutMs)
return (Result == WAIT_OBJECT_0);
#else
- auto* Inner = (EventInner*)m_EventHandle;
+ auto* Inner = reinterpret_cast<EventInner*>(m_EventHandle);
+
+ if (Inner->bSet.load())
+ {
+ return true;
+ }
if (TimeoutMs >= 0)
{
std::unique_lock Lock(Inner->Mutex);
- if (Inner->bSet)
+ if (Inner->bSet.load())
{
return true;
}
- return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet; });
+ return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet.load(); });
}
+ // Infinite wait. This does not actually call the wait() function to work around
+ // an apparent issue in the underlying implementation.
+
std::unique_lock Lock(Inner->Mutex);
- if (!Inner->bSet)
+ if (!Inner->bSet.load())
{
- Inner->CondVar.wait(Lock, [&] { return Inner->bSet; });
+ while (!Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(1000), [&] { return Inner->bSet.load(); }))
+ ;
}
return true;
@@ -398,9 +394,10 @@ NamedEvent::Wait(int TimeoutMs)
}
# if defined(_GNU_SOURCE)
+ const int TimeoutSec = TimeoutMs / 1000;
struct timespec TimeoutValue = {
- .tv_sec = TimeoutMs >> 10,
- .tv_nsec = (TimeoutMs & 0x3ff) << 20,
+ .tv_sec = TimeoutSec,
+ .tv_nsec = (TimeoutMs - (TimeoutSec * 1000)) * 1000000,
};
Result = semtimedop(Sem, &SemOp, 1, &TimeoutValue);
# else
@@ -418,7 +415,6 @@ NamedEvent::Wait(int TimeoutMs)
TimeoutMs -= SleepTimeMs;
} while (TimeoutMs > 0);
# endif // _GNU_SOURCE
-
return Result == 0;
#endif
}
@@ -520,582 +516,6 @@ NamedMutex::Exists(std::string_view MutexName)
#endif // ZEN_PLATFORM_WINDOWS
}
-//////////////////////////////////////////////////////////////////////////
-
-ProcessHandle::ProcessHandle() = default;
-
-#if ZEN_PLATFORM_WINDOWS
-void
-ProcessHandle::Initialize(void* ProcessHandle)
-{
- ZEN_ASSERT(m_ProcessHandle == nullptr);
-
- if (ProcessHandle == INVALID_HANDLE_VALUE)
- {
- ProcessHandle = nullptr;
- }
-
- // TODO: perform some debug verification here to verify it's a valid handle?
- m_ProcessHandle = ProcessHandle;
- m_Pid = GetProcessId(m_ProcessHandle);
-}
-#endif // ZEN_PLATFORM_WINDOWS
-
-ProcessHandle::~ProcessHandle()
-{
- Reset();
-}
-
-void
-ProcessHandle::Initialize(int Pid)
-{
- ZEN_ASSERT(m_ProcessHandle == nullptr);
-
-#if ZEN_PLATFORM_WINDOWS
- m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
-#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- if (Pid > 0)
- {
- m_ProcessHandle = (void*)(intptr_t(Pid));
- }
-#endif
-
- if (!m_ProcessHandle)
- {
- ThrowLastError(fmt::format("ProcessHandle::Initialize(pid: {}) failed", Pid));
- }
-
- m_Pid = Pid;
-}
-
-bool
-ProcessHandle::IsRunning() const
-{
- bool bActive = false;
-
-#if ZEN_PLATFORM_WINDOWS
- DWORD ExitCode = 0;
- GetExitCodeProcess(m_ProcessHandle, &ExitCode);
- bActive = (ExitCode == STILL_ACTIVE);
-#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- bActive = (kill(pid_t(m_Pid), 0) == 0);
-#endif
-
- return bActive;
-}
-
-bool
-ProcessHandle::IsValid() const
-{
- return (m_ProcessHandle != nullptr);
-}
-
-void
-ProcessHandle::Terminate(int ExitCode)
-{
- if (!IsRunning())
- {
- return;
- }
-
- bool bSuccess = false;
-
-#if ZEN_PLATFORM_WINDOWS
- TerminateProcess(m_ProcessHandle, ExitCode);
- DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE);
- bSuccess = (WaitResult != WAIT_OBJECT_0);
-#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- ZEN_UNUSED(ExitCode);
- bSuccess = (kill(m_Pid, SIGKILL) == 0);
-#endif
-
- if (!bSuccess)
- {
- // What might go wrong here, and what is meaningful to act on?
- }
-}
-
-void
-ProcessHandle::Reset()
-{
- if (IsValid())
- {
-#if ZEN_PLATFORM_WINDOWS
- CloseHandle(m_ProcessHandle);
-#endif
- m_ProcessHandle = nullptr;
- m_Pid = 0;
- }
-}
-
-bool
-ProcessHandle::Wait(int TimeoutMs)
-{
- using namespace std::literals;
-
-#if ZEN_PLATFORM_WINDOWS
- const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
-
- const DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, Timeout);
-
- switch (WaitResult)
- {
- case WAIT_OBJECT_0:
- return true;
-
- case WAIT_TIMEOUT:
- return false;
-
- case WAIT_FAILED:
- break;
- }
-#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- const int SleepMs = 20;
- timespec SleepTime = {0, SleepMs * 1000 * 1000};
- for (int i = 0;; i += SleepMs)
- {
-# if ZEN_PLATFORM_MAC
- int WaitState = 0;
- waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED);
-# endif
-
- if (kill(m_Pid, 0) < 0)
- {
- if (zen::GetLastError() == ESRCH)
- {
- return true;
- }
- break;
- }
-
- if (TimeoutMs >= 0 && i >= TimeoutMs)
- {
- return false;
- }
-
- nanosleep(&SleepTime, nullptr);
- }
-#endif
-
- // What might go wrong here, and what is meaningful to act on?
- ThrowLastError("Process::Wait failed"sv);
-}
-
-int
-ProcessHandle::WaitExitCode()
-{
- Wait(-1);
-
-#if ZEN_PLATFORM_WINDOWS
- DWORD ExitCode = 0;
- GetExitCodeProcess(m_ProcessHandle, &ExitCode);
-
- ZEN_ASSERT(ExitCode != STILL_ACTIVE);
-
- return ExitCode;
-#else
- ZEN_NOT_IMPLEMENTED();
-
- return 0;
-#endif
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-#if !ZEN_PLATFORM_WINDOWS || ZEN_WITH_TESTS
-static void
-BuildArgV(std::vector<char*>& Out, char* CommandLine)
-{
- char* Cursor = CommandLine;
- while (true)
- {
- // Skip leading whitespace
- for (; *Cursor == ' '; ++Cursor)
- ;
-
- // Check for nullp terminator
- if (*Cursor == '\0')
- {
- break;
- }
-
- Out.push_back(Cursor);
-
- // Extract word
- int QuoteCount = 0;
- do
- {
- QuoteCount += (*Cursor == '\"');
- if (*Cursor == ' ' && !(QuoteCount & 1))
- {
- break;
- }
- ++Cursor;
- } while (*Cursor != '\0');
-
- if (*Cursor == '\0')
- {
- break;
- }
-
- *Cursor = '\0';
- ++Cursor;
- }
-}
-#endif // !WINDOWS || TESTS
-
-#if ZEN_PLATFORM_WINDOWS
-static CreateProcResult
-CreateProcNormal(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
-{
- PROCESS_INFORMATION ProcessInfo{};
- STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)};
-
- const bool InheritHandles = false;
- void* Environment = nullptr;
- LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr;
- LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr;
-
- DWORD CreationFlags = 0;
- if (Options.Flags & CreateProcOptions::Flag_NewConsole)
- {
- CreationFlags |= CREATE_NEW_CONSOLE;
- }
-
- const wchar_t* WorkingDir = nullptr;
- if (Options.WorkingDirectory != nullptr)
- {
- WorkingDir = Options.WorkingDirectory->c_str();
- }
-
- ExtendableWideStringBuilder<256> CommandLineZ;
- CommandLineZ << CommandLine;
-
- BOOL Success = CreateProcessW(Executable.c_str(),
- CommandLineZ.Data(),
- ProcessAttributes,
- ThreadAttributes,
- InheritHandles,
- CreationFlags,
- Environment,
- WorkingDir,
- &StartupInfo,
- &ProcessInfo);
-
- if (!Success)
- {
- return nullptr;
- }
-
- CloseHandle(ProcessInfo.hThread);
- return ProcessInfo.hProcess;
-}
-
-static CreateProcResult
-CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
-{
- /* Launches a binary with the shell as its parent. The shell (such as
- Explorer) should be an unelevated process. */
-
- // No sense in using this route if we are not elevated in the first place
- if (IsUserAnAdmin() == FALSE)
- {
- return CreateProcNormal(Executable, CommandLine, Options);
- }
-
- // Get the users' shell process and open it for process creation
- HWND ShellWnd = GetShellWindow();
- if (ShellWnd == nullptr)
- {
- return nullptr;
- }
-
- DWORD ShellPid;
- GetWindowThreadProcessId(ShellWnd, &ShellPid);
-
- HANDLE Process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ShellPid);
- if (Process == nullptr)
- {
- return nullptr;
- }
- auto $0 = MakeGuard([&] { CloseHandle(Process); });
-
- // Creating a process as a child of another process is done by setting a
- // thread-attribute list on the startup info passed to CreateProcess()
- SIZE_T AttrListSize;
- InitializeProcThreadAttributeList(nullptr, 1, 0, &AttrListSize);
-
- auto AttrList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(AttrListSize);
- auto $1 = MakeGuard([&] { free(AttrList); });
-
- if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &AttrListSize))
- {
- return nullptr;
- }
-
- BOOL bOk =
- UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, (HANDLE*)&Process, sizeof(Process), nullptr, nullptr);
- if (!bOk)
- {
- return nullptr;
- }
-
- // By this point we know we are an elevated process. It is not allowed to
- // create a process as a child of another unelevated process that share our
- // elevated console window if we have one. So we'll need to create a new one.
- uint32_t CreateProcFlags = EXTENDED_STARTUPINFO_PRESENT;
- if (GetConsoleWindow() != nullptr)
- {
- CreateProcFlags |= CREATE_NEW_CONSOLE;
- }
- else
- {
- CreateProcFlags |= DETACHED_PROCESS;
- }
-
- // Everything is set up now so we can proceed and launch the process
- STARTUPINFOEXW StartupInfo = {
- .StartupInfo = {.cb = sizeof(STARTUPINFOEXW)},
- .lpAttributeList = AttrList,
- };
- PROCESS_INFORMATION ProcessInfo = {};
-
- if (Options.Flags & CreateProcOptions::Flag_NewConsole)
- {
- CreateProcFlags |= CREATE_NEW_CONSOLE;
- }
-
- ExtendableWideStringBuilder<256> CommandLineZ;
- CommandLineZ << CommandLine;
-
- bOk = CreateProcessW(Executable.c_str(),
- CommandLineZ.Data(),
- nullptr,
- nullptr,
- FALSE,
- CreateProcFlags,
- nullptr,
- nullptr,
- &StartupInfo.StartupInfo,
- &ProcessInfo);
- if (bOk == FALSE)
- {
- return nullptr;
- }
-
- CloseHandle(ProcessInfo.hThread);
- return ProcessInfo.hProcess;
-}
-
-static CreateProcResult
-CreateProcElevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
-{
- ExtendableWideStringBuilder<256> CommandLineZ;
- CommandLineZ << CommandLine;
-
- SHELLEXECUTEINFO ShellExecuteInfo;
- ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo));
- ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo);
- ShellExecuteInfo.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS;
- ShellExecuteInfo.lpFile = Executable.c_str();
- ShellExecuteInfo.lpVerb = TEXT("runas");
- ShellExecuteInfo.nShow = SW_SHOW;
- ShellExecuteInfo.lpParameters = CommandLineZ.c_str();
-
- if (Options.WorkingDirectory != nullptr)
- {
- ShellExecuteInfo.lpDirectory = Options.WorkingDirectory->c_str();
- }
-
- if (::ShellExecuteEx(&ShellExecuteInfo))
- {
- return ShellExecuteInfo.hProcess;
- }
-
- return nullptr;
-}
-#endif // ZEN_PLATFORM_WINDOWS
-
-CreateProcResult
-CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
-{
-#if ZEN_PLATFORM_WINDOWS
- if (Options.Flags & CreateProcOptions::Flag_Unelevated)
- {
- return CreateProcUnelevated(Executable, CommandLine, Options);
- }
-
- if (Options.Flags & CreateProcOptions::Flag_Elevated)
- {
- return CreateProcElevated(Executable, CommandLine, Options);
- }
-
- return CreateProcNormal(Executable, CommandLine, Options);
-#else
- std::vector<char*> ArgV;
- std::string CommandLineZ(CommandLine);
- BuildArgV(ArgV, CommandLineZ.data());
- ArgV.push_back(nullptr);
-
- int ChildPid = fork();
- if (ChildPid < 0)
- {
- ThrowLastError("Failed to fork a new child process");
- }
- else if (ChildPid == 0)
- {
- if (Options.WorkingDirectory != nullptr)
- {
- int Result = chdir(Options.WorkingDirectory->c_str());
- ZEN_UNUSED(Result);
- }
-
- if (execv(Executable.c_str(), ArgV.data()) < 0)
- {
- ThrowLastError("Failed to exec() a new process image");
- }
- }
-
- return ChildPid;
-#endif
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-ProcessMonitor::ProcessMonitor()
-{
-}
-
-ProcessMonitor::~ProcessMonitor()
-{
- RwLock::ExclusiveLockScope _(m_Lock);
-
- for (HandleType& Proc : m_ProcessHandles)
- {
-#if ZEN_PLATFORM_WINDOWS
- CloseHandle(Proc);
-#endif
- Proc = 0;
- }
-}
-
-bool
-ProcessMonitor::IsRunning()
-{
- RwLock::ExclusiveLockScope _(m_Lock);
-
- bool FoundOne = false;
-
- for (HandleType& Proc : m_ProcessHandles)
- {
- bool ProcIsActive;
-
-#if ZEN_PLATFORM_WINDOWS
- DWORD ExitCode = 0;
- GetExitCodeProcess(Proc, &ExitCode);
-
- ProcIsActive = (ExitCode == STILL_ACTIVE);
- if (!ProcIsActive)
- {
- CloseHandle(Proc);
- }
-#else
- int Pid = int(intptr_t(Proc));
- ProcIsActive = IsProcessRunning(Pid);
-#endif
-
- if (!ProcIsActive)
- {
- Proc = 0;
- }
-
- // Still alive
- FoundOne |= ProcIsActive;
- }
-
- std::erase_if(m_ProcessHandles, [](HandleType Handle) { return Handle == 0; });
-
- return FoundOne;
-}
-
-void
-ProcessMonitor::AddPid(int Pid)
-{
- HandleType ProcessHandle;
-
-#if ZEN_PLATFORM_WINDOWS
- ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
-#else
- ProcessHandle = HandleType(intptr_t(Pid));
-#endif
-
- if (ProcessHandle)
- {
- RwLock::ExclusiveLockScope _(m_Lock);
- m_ProcessHandles.push_back(ProcessHandle);
- }
-}
-
-bool
-ProcessMonitor::IsActive() const
-{
- RwLock::SharedLockScope _(m_Lock);
- return m_ProcessHandles.empty() == false;
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-bool
-IsProcessRunning(int pid)
-{
- // This function is arguably not super useful, a pid can be re-used
- // by the OS so holding on to a pid and polling it over some time
- // period will not necessarily tell you what you probably want to know.
-
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
-
- if (!hProc)
- {
- DWORD Error = zen::GetLastError();
-
- if (Error == ERROR_INVALID_PARAMETER)
- {
- return false;
- }
-
- ThrowSystemError(Error, fmt::format("failed to open process with pid {}", pid));
- }
-
- bool bStillActive = true;
- DWORD ExitCode = 0;
- if (0 != GetExitCodeProcess(hProc, &ExitCode))
- {
- bStillActive = ExitCode == STILL_ACTIVE;
- }
- else
- {
- ZEN_WARN("Unable to get exit code from handle for process '{}', treating the process as active", pid);
- }
-
- CloseHandle(hProc);
-
- return bStillActive;
-#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- return (kill(pid_t(pid), 0) == 0);
-#endif
-}
-
-int
-GetCurrentProcessId()
-{
-#if ZEN_PLATFORM_WINDOWS
- return ::GetCurrentProcessId();
-#else
- return int(getpid());
-#endif
-}
-
int
GetCurrentThreadId()
{
@@ -1108,16 +528,6 @@ GetCurrentThreadId()
#endif
}
-int
-GetProcessId(CreateProcResult ProcId)
-{
-#if ZEN_PLATFORM_WINDOWS
- return static_cast<int>(::GetProcessId(ProcId));
-#else
- return ProcId;
-#endif
-}
-
void
Sleep(int ms)
{
@@ -1140,65 +550,11 @@ thread_forcelink()
{
}
-TEST_CASE("Thread")
-{
- int Pid = GetCurrentProcessId();
- CHECK(Pid > 0);
- CHECK(IsProcessRunning(Pid));
-
- CHECK_FALSE(GetCurrentThreadId() == 0);
-}
+TEST_SUITE_BEGIN("core.thread");
-TEST_CASE("BuildArgV")
+TEST_CASE("GetCurrentThreadId")
{
- const char* Words[] = {"one", "two", "three", "four", "five"};
- struct
- {
- int WordCount;
- const char* Input;
- } Cases[] = {
- {0, ""},
- {0, " "},
- {1, "one"},
- {1, " one"},
- {1, "one "},
- {2, "one two"},
- {2, " one two"},
- {2, "one two "},
- {2, " one two"},
- {2, "one two "},
- {2, "one two "},
- {3, "one two three"},
- {3, "\"one\" two \"three\""},
- {5, "one two three four five"},
- };
-
- for (const auto& Case : Cases)
- {
- std::vector<char*> OutArgs;
- StringBuilder<64> Mutable;
- Mutable << Case.Input;
- BuildArgV(OutArgs, Mutable.Data());
-
- CHECK_EQ(OutArgs.size(), Case.WordCount);
-
- for (int i = 0, n = int(OutArgs.size()); i < n; ++i)
- {
- const char* Truth = Words[i];
- size_t TruthLen = strlen(Truth);
-
- const char* Candidate = OutArgs[i];
- bool bQuoted = (Candidate[0] == '\"');
- Candidate += bQuoted;
-
- CHECK(strncmp(Truth, Candidate, TruthLen) == 0);
-
- if (bQuoted)
- {
- CHECK_EQ(Candidate[TruthLen], '\"');
- }
- }
- }
+ CHECK_FALSE(GetCurrentThreadId() == 0);
}
TEST_CASE("NamedEvent")
@@ -1213,19 +569,20 @@ TEST_CASE("NamedEvent")
CHECK(!bEventSet);
}
+ NamedEvent ReadyEvent(Name + "_ready");
+
// Thread check
std::thread Waiter = std::thread([Name]() {
NamedEvent ReadyEvent(Name + "_ready");
ReadyEvent.Set();
NamedEvent TestEvent(Name);
- TestEvent.Wait(100);
+ TestEvent.Wait(1000);
});
- NamedEvent ReadyEvent(Name + "_ready");
ReadyEvent.Wait();
- zen::Sleep(50);
+ zen::Sleep(100);
TestEvent.Set();
Waiter.join();
@@ -1253,6 +610,8 @@ TEST_CASE("NamedMutex")
CHECK(!NamedMutex::Exists(Name));
}
+TEST_SUITE_END();
+
#endif // ZEN_WITH_TESTS
} // namespace zen
diff --git a/src/zencore/trace.cpp b/src/zencore/trace.cpp
index d71ca0984..f7e4c4b68 100644
--- a/src/zencore/trace.cpp
+++ b/src/zencore/trace.cpp
@@ -9,7 +9,7 @@
# include <zencore/trace.h>
void
-TraceInit()
+TraceInit(std::string_view ProgramName)
{
static std::atomic_bool gInited = false;
bool Expected = false;
@@ -23,14 +23,20 @@ TraceInit()
};
trace::Initialize(Desc);
+# if ZEN_PLATFORM_WINDOWS
+ const char* CommandLineString = GetCommandLineA();
+# else
+ const char* CommandLineString = "";
+# endif
+
trace::ThreadRegister("main", /* system id */ 0, /* sort id */ 0);
- trace::DescribeSession("zenserver",
+ trace::DescribeSession(ProgramName,
# if ZEN_BUILD_DEBUG
trace::Build::Debug,
# else
trace::Build::Development,
# endif
- "",
+ CommandLineString,
ZEN_CFG_VERSION_BUILD_STRING);
}
@@ -48,9 +54,9 @@ IsTracing()
}
void
-TraceStart(const char* HostOrPath, TraceType Type)
+TraceStart(std::string_view ProgramName, const char* HostOrPath, TraceType Type)
{
- TraceInit();
+ TraceInit(ProgramName);
switch (Type)
{
case TraceType::Network:
diff --git a/src/zencore/workthreadpool.cpp b/src/zencore/workthreadpool.cpp
index 3a4b1e6a1..6ff6463dd 100644
--- a/src/zencore/workthreadpool.cpp
+++ b/src/zencore/workthreadpool.cpp
@@ -74,6 +74,7 @@ struct WorkerThreadPool::Impl
{
WaitForThreadpoolWorkCallbacks(m_Work, /* CancelPendingCallbacks */ TRUE);
CloseThreadpoolWork(m_Work);
+ CloseThreadpool(m_ThreadPool);
}
void ScheduleWork(Ref<IWork> Work)
@@ -109,6 +110,7 @@ struct WorkerThreadPool::Impl
m_WorkQueue.pop_front();
}
+ ZEN_TRACE_CPU_FLUSH("AsyncWork");
WorkFromQueue->Execute();
}
};
@@ -150,7 +152,10 @@ struct WorkerThreadPool::Impl
for (std::thread& Thread : m_WorkerThreads)
{
- Thread.join();
+ if (Thread.joinable())
+ {
+ Thread.join();
+ }
}
m_WorkerThreads.clear();
@@ -174,6 +179,7 @@ WorkerThreadPool::Impl::WorkerThreadFunction(ThreadStartInfo Info)
{
try
{
+ ZEN_TRACE_CPU_FLUSH("AsyncWork");
Work->Execute();
}
catch (std::exception& e)
@@ -219,7 +225,17 @@ WorkerThreadPool::ScheduleWork(Ref<IWork> Work)
}
else
{
- Work->Execute();
+ try
+ {
+ ZEN_TRACE_CPU_FLUSH("SyncWork");
+ Work->Execute();
+ }
+ catch (std::exception& e)
+ {
+ Work->m_Exception = std::current_exception();
+
+ ZEN_WARN("Caught exception when executing worker synchronously: {}", e.what());
+ }
}
}
diff --git a/src/zencore/zencore.cpp b/src/zencore/zencore.cpp
index 9377a733b..eed903f54 100644
--- a/src/zencore/zencore.cpp
+++ b/src/zencore/zencore.cpp
@@ -10,6 +10,7 @@
# include <pthread.h>
#endif
+#include <zencore/assertfmt.h>
#include <zencore/blake3.h>
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
@@ -24,6 +25,7 @@
#include <zencore/logging.h>
#include <zencore/memory.h>
#include <zencore/mpscqueue.h>
+#include <zencore/process.h>
#include <zencore/sha1.h>
#include <zencore/stats.h>
#include <zencore/stream.h>
@@ -33,6 +35,22 @@
#include <zencore/uid.h>
#include <zencore/workthreadpool.h>
+#include <fmt/format.h>
+
+namespace zen::assert {
+
+void
+ExecAssertFmt(const char* Filename, int LineNumber, const char* FunctionName, std::string_view Format, fmt::format_args Args)
+{
+ fmt::basic_memory_buffer<char, 1024> Message;
+ fmt::vformat_to(fmt::appender(Message), Format, Args);
+ Message.push_back('\0');
+
+ AssertImpl::ExecAssert(Filename, LineNumber, FunctionName, Message.data());
+}
+
+} // namespace zen::assert
+
namespace zen {
void refcount_forcelink();
@@ -123,6 +141,7 @@ zencore_forcelinktests()
zen::logging_forcelink();
zen::memory_forcelink();
zen::mpscqueue_forcelink();
+ zen::process_forcelink();
zen::refcount_forcelink();
zen::sha1_forcelink();
zen::stats_forcelink();
@@ -142,6 +161,8 @@ zencore_forcelinktests()
namespace zen {
+TEST_SUITE_BEGIN("core.assert");
+
TEST_CASE("Assert.Default")
{
bool A = true;
@@ -149,6 +170,13 @@ TEST_CASE("Assert.Default")
CHECK_THROWS_WITH(ZEN_ASSERT(A == B), "A == B");
}
+TEST_CASE("Assert.Format")
+{
+ bool A = true;
+ bool B = false;
+ CHECK_THROWS_WITH(ZEN_ASSERT_FORMAT(A == B, "{} == {}", A, B), "assert(A == B) failed: true == false");
+}
+
TEST_CASE("Assert.Custom")
{
struct MyAssertImpl : AssertImpl
@@ -183,6 +211,7 @@ TEST_CASE("Assert.Custom")
CHECK(strcmp(MyAssert.Message, "A == B") == 0);
}
+TEST_SUITE_END();
#endif
} // namespace zen