aboutsummaryrefslogtreecommitdiff
path: root/src/zencore
diff options
context:
space:
mode:
authorEPICGAMES\thierry.begin <[email protected]>2024-04-08 10:43:16 -0400
committerEPICGAMES\thierry.begin <[email protected]>2024-04-08 10:43:16 -0400
commitb35e1258a043cab06950b2453f434861d99b918a (patch)
tree695737774fa08ebaa0e32a9f95cb0247c34b3dc3 /src/zencore
parentAdd docker support (diff)
parentMerge pull request #41 from ue-foundation/zs/import-oplog-clean (diff)
downloadzen-tb/docker.tar.xz
zen-tb/docker.zip
Merge branch 'main' of https://github.ol.epicgames.net/ue-foundation/zen into tb/dockertb/docker
Diffstat (limited to 'src/zencore')
-rw-r--r--src/zencore/callstack.cpp221
-rw-r--r--src/zencore/compress.cpp138
-rw-r--r--src/zencore/filesystem.cpp2
-rw-r--r--src/zencore/include/zencore/callstack.h37
-rw-r--r--src/zencore/include/zencore/scopeguard.h6
-rw-r--r--src/zencore/include/zencore/zencore.h47
-rw-r--r--src/zencore/jobqueue.cpp17
-rw-r--r--src/zencore/workthreadpool.cpp16
-rw-r--r--src/zencore/xmake.lua1
-rw-r--r--src/zencore/zencore.cpp111
10 files changed, 513 insertions, 83 deletions
diff --git a/src/zencore/callstack.cpp b/src/zencore/callstack.cpp
new file mode 100644
index 000000000..905ab3d9e
--- /dev/null
+++ b/src/zencore/callstack.cpp
@@ -0,0 +1,221 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/callstack.h>
+#include <zencore/thread.h>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+# include <Dbghelp.h>
+#endif
+
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+# include <execinfo.h>
+#endif
+
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+#endif
+
+#include <fmt/format.h>
+
+namespace zen {
+#if ZEN_PLATFORM_WINDOWS
+
+class WinSymbolInit
+{
+public:
+ WinSymbolInit() {}
+ ~WinSymbolInit()
+ {
+ m_CallstackLock.WithExclusiveLock([this]() {
+ if (m_Initialized)
+ {
+ SymCleanup(m_CurrentProcess);
+ }
+ });
+ }
+
+ bool GetSymbol(void* Frame, SYMBOL_INFO* OutSymbolInfo, DWORD64& OutDisplacement)
+ {
+ bool Result = false;
+ m_CallstackLock.WithExclusiveLock([&]() {
+ if (!m_Initialized)
+ {
+ m_CurrentProcess = GetCurrentProcess();
+ if (SymInitialize(m_CurrentProcess, NULL, TRUE) == TRUE)
+ {
+ m_Initialized = true;
+ }
+ }
+ if (m_Initialized)
+ {
+ if (SymFromAddr(m_CurrentProcess, (DWORD64)Frame, &OutDisplacement, OutSymbolInfo) == TRUE)
+ {
+ Result = true;
+ }
+ }
+ });
+ return Result;
+ }
+
+private:
+ HANDLE m_CurrentProcess = NULL;
+ BOOL m_Initialized = FALSE;
+ RwLock m_CallstackLock;
+};
+
+static WinSymbolInit WinSymbols;
+
+#endif
+
+CallstackFrames*
+CreateCallstack(uint32_t FrameCount, void** Frames) noexcept
+{
+ if (FrameCount == 0)
+ {
+ return nullptr;
+ }
+ CallstackFrames* Callstack = (CallstackFrames*)malloc(sizeof(CallstackFrames) + sizeof(void*) * FrameCount);
+ if (Callstack != nullptr)
+ {
+ Callstack->FrameCount = FrameCount;
+ if (FrameCount == 0)
+ {
+ Callstack->Frames = nullptr;
+ }
+ else
+ {
+ Callstack->Frames = (void**)&Callstack[1];
+ memcpy(Callstack->Frames, Frames, sizeof(void*) * FrameCount);
+ }
+ }
+ return Callstack;
+}
+
+CallstackFrames*
+CloneCallstack(const CallstackFrames* Callstack) noexcept
+{
+ if (Callstack == nullptr)
+ {
+ return nullptr;
+ }
+ return CreateCallstack(Callstack->FrameCount, Callstack->Frames);
+}
+
+void
+FreeCallstack(CallstackFrames* Callstack) noexcept
+{
+ if (Callstack != nullptr)
+ {
+ free(Callstack);
+ }
+}
+
+uint32_t
+GetCallstack(int FramesToSkip, int FramesToCapture, void* OutAddresses[])
+{
+#if ZEN_PLATFORM_WINDOWS
+ return (uint32_t)CaptureStackBackTrace(FramesToSkip, FramesToCapture, OutAddresses, 0);
+#endif
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ void* Frames[FramesToSkip + FramesToCapture];
+ int FrameCount = backtrace(Frames, FramesToSkip + FramesToCapture);
+ if (FrameCount > FramesToSkip)
+ {
+ for (int Index = FramesToSkip; Index < FrameCount; Index++)
+ {
+ OutAddresses[Index - FramesToSkip] = Frames[Index];
+ }
+ return (uint32_t)(FrameCount - FramesToSkip);
+ }
+ else
+ {
+ return 0;
+ }
+#endif
+}
+
+std::vector<std::string>
+GetFrameSymbols(uint32_t FrameCount, void** Frames)
+{
+ std::vector<std::string> FrameSymbols;
+ if (FrameCount > 0)
+ {
+ FrameSymbols.resize(FrameCount);
+#if ZEN_PLATFORM_WINDOWS
+ char SymbolBuffer[sizeof(SYMBOL_INFO) + 1024];
+ SYMBOL_INFO* SymbolInfo = (SYMBOL_INFO*)SymbolBuffer;
+ SymbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO);
+ SymbolInfo->MaxNameLen = 1023;
+ DWORD64 Displacement = 0;
+ for (uint32_t FrameIndex = 0; FrameIndex < FrameCount; FrameIndex++)
+ {
+ if (WinSymbols.GetSymbol(Frames[FrameIndex], SymbolInfo, Displacement))
+ {
+ FrameSymbols[FrameIndex] = fmt::format("{}+{:#x} [{:#x}]", SymbolInfo->Name, Displacement, (uintptr_t)Frames[FrameIndex]);
+ }
+ }
+#endif
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ char** messages = backtrace_symbols(Frames, (int)FrameCount);
+ if (messages)
+ {
+ for (uint32_t FrameIndex = 0; FrameIndex < FrameCount; FrameIndex++)
+ {
+ FrameSymbols[FrameIndex] = messages[FrameIndex];
+ }
+ free(messages);
+ }
+#endif
+ }
+ return FrameSymbols;
+}
+
+void
+FormatCallstack(const CallstackFrames* Callstack, StringBuilderBase& SB)
+{
+ bool First = true;
+ for (const std::string& Symbol : GetFrameSymbols(Callstack))
+ {
+ if (!First)
+ {
+ SB.Append("\n");
+ }
+ else
+ {
+ First = false;
+ }
+ SB.Append(Symbol);
+ }
+}
+
+std::string
+CallstackToString(const CallstackFrames* Callstack)
+{
+ StringBuilder<2048> SB;
+ FormatCallstack(Callstack, SB);
+ return SB.ToString();
+}
+
+#if ZEN_WITH_TESTS
+
+TEST_CASE("Callstack.Basic")
+{
+ void* Addresses[4];
+ uint32_t FrameCount = GetCallstack(1, 4, Addresses);
+ CHECK(FrameCount > 0);
+ std::vector<std::string> Symbols = GetFrameSymbols(FrameCount, Addresses);
+ for (const std::string& Symbol : Symbols)
+ {
+ CHECK(!Symbol.empty());
+ }
+}
+
+void
+callstack_forcelink()
+{
+}
+
+#endif
+
+} // namespace zen
diff --git a/src/zencore/compress.cpp b/src/zencore/compress.cpp
index 58be65f13..143317e65 100644
--- a/src/zencore/compress.cpp
+++ b/src/zencore/compress.cpp
@@ -863,7 +863,7 @@ GetDecoder(CompressionMethod Method)
//////////////////////////////////////////////////////////////////////////
bool
-BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
+ReadHeader(const CompositeBuffer& CompressedData, BufferHeader& OutHeader, UniqueBuffer* OutHeaderData)
{
const uint64_t CompressedDataSize = CompressedData.GetSize();
if (CompressedDataSize < sizeof(BufferHeader))
@@ -871,61 +871,89 @@ BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash,
return false;
}
- const size_t StackBufferSize = 256;
- uint8_t StackBuffer[StackBufferSize];
- uint64_t ReadSize = Min(CompressedDataSize, StackBufferSize);
- BufferHeader* Header = reinterpret_cast<BufferHeader*>(StackBuffer);
+ const size_t HeaderBufferSize = 1024;
+ uint8_t HeaderBuffer[HeaderBufferSize];
+ uint64_t ReadSize = Min(CompressedDataSize, HeaderBufferSize);
+ uint64_t FirstSegmentSize = CompressedData.GetSegments()[0].GetSize();
+ if (FirstSegmentSize >= sizeof(BufferHeader))
{
- CompositeBuffer::Iterator It;
- CompressedData.CopyTo(MutableMemoryView(StackBuffer, StackBuffer + StackBufferSize), It);
+ // Keep first read inside first segment if possible
+ ReadSize = Min(ReadSize, FirstSegmentSize);
}
- Header->ByteSwap();
- if (Header->Magic != BufferHeader::ExpectedMagic)
+
+ MutableMemoryView HeaderMemory(HeaderBuffer, &HeaderBuffer[ReadSize]);
+ CompositeBuffer::Iterator It = CompressedData.GetIterator(0);
+ CompressedData.CopyTo(HeaderMemory, It);
+
+ OutHeader = *reinterpret_cast<BufferHeader*>(HeaderMemory.GetData());
+ OutHeader.ByteSwap();
+ if (OutHeader.Magic != BufferHeader::ExpectedMagic)
{
return false;
}
-
- const BaseDecoder* const Decoder = GetDecoder(Header->Method);
+ if (OutHeader.TotalCompressedSize > CompressedDataSize)
+ {
+ return false;
+ }
+ const BaseDecoder* const Decoder = GetDecoder(OutHeader.Method);
if (!Decoder)
{
return false;
}
-
- uint32_t Crc32 = Header->Crc32;
- OutRawHash = IoHash::FromBLAKE3(Header->RawHash);
- OutRawSize = Header->TotalRawSize;
- uint64_t HeaderSize = Decoder->GetHeaderSize(*Header);
-
- if (Header->TotalCompressedSize > CompressedDataSize)
+ uint64_t FullHeaderSize = Decoder->GetHeaderSize(OutHeader);
+ if (FullHeaderSize > CompressedDataSize)
{
return false;
}
-
- Header->ByteSwap();
-
- if (HeaderSize > ReadSize)
+ if (OutHeaderData)
{
- UniqueBuffer HeaderCopy = UniqueBuffer::Alloc(HeaderSize);
- CompositeBuffer::Iterator It;
- CompressedData.CopyTo(HeaderCopy.GetMutableView(), It);
- const MemoryView HeaderView = HeaderCopy.GetView();
- if (Crc32 != BufferHeader::CalculateCrc32(HeaderView))
+ *OutHeaderData = UniqueBuffer::Alloc(FullHeaderSize);
+ MutableMemoryView RemainingHeaderView = OutHeaderData->GetMutableView().CopyFrom(HeaderMemory.Mid(0, FullHeaderSize));
+ if (!RemainingHeaderView.IsEmpty())
+ {
+ CompressedData.CopyTo(RemainingHeaderView, It);
+ }
+ if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(OutHeaderData->GetView()))
+ {
+ return false;
+ }
+ }
+ else if (FullHeaderSize < ReadSize)
+ {
+ if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(HeaderMemory.Mid(0, FullHeaderSize)))
{
return false;
}
}
else
{
- MemoryView FullHeaderView(StackBuffer, StackBuffer + HeaderSize);
- if (Crc32 != BufferHeader::CalculateCrc32(FullHeaderView))
+ UniqueBuffer HeaderData = UniqueBuffer::Alloc(FullHeaderSize);
+ MutableMemoryView RemainingHeaderView = HeaderData.GetMutableView().CopyFrom(HeaderMemory.Mid(0, FullHeaderSize));
+ if (!RemainingHeaderView.IsEmpty())
+ {
+ CompressedData.CopyTo(RemainingHeaderView, It);
+ }
+ if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(HeaderData.GetView()))
{
return false;
}
}
-
return true;
}
+bool
+BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
+{
+ detail::BufferHeader Header;
+ if (ReadHeader(CompressedData, Header, nullptr))
+ {
+ OutRawHash = IoHash::FromBLAKE3(Header.RawHash);
+ OutRawSize = Header.TotalRawSize;
+ return true;
+ }
+ return false;
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static bool
@@ -1097,7 +1125,11 @@ ValidBufferOrEmpty(BufferType&& CompressedData, IoHash& OutRawHash, uint64_t& Ou
}
CompositeBuffer
-GetCompressedRange(const BufferHeader& Header, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize)
+GetCompressedRange(const BufferHeader& Header,
+ MemoryView HeaderRawData,
+ const CompositeBuffer& CompressedData,
+ uint64_t RawOffset,
+ uint64_t RawSize)
{
if (Header.TotalRawSize < RawOffset + RawSize)
{
@@ -1118,9 +1150,7 @@ GetCompressedRange(const BufferHeader& Header, const CompositeBuffer& Compressed
}
else
{
- UniqueBuffer BlockSizeBuffer;
- MemoryView BlockSizeView =
- CompressedData.ViewOrCopyRange(sizeof(BufferHeader), Header.BlockCount * sizeof(uint32_t), BlockSizeBuffer);
+ MemoryView BlockSizeView = HeaderRawData.Mid(sizeof(Header), Header.BlockCount * sizeof(uint32_t));
std::span<uint32_t const> CompressedBlockSizes(reinterpret_cast<const uint32_t*>(BlockSizeView.GetData()), Header.BlockCount);
const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent;
@@ -1179,7 +1209,11 @@ GetCompressedRange(const BufferHeader& Header, const CompositeBuffer& Compressed
}
CompositeBuffer
-CopyCompressedRange(const BufferHeader& Header, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize)
+CopyCompressedRange(const BufferHeader& Header,
+ MemoryView HeaderRawData,
+ const CompositeBuffer& CompressedData,
+ uint64_t RawOffset,
+ uint64_t RawSize)
{
if (Header.TotalRawSize < RawOffset + RawSize)
{
@@ -1204,9 +1238,7 @@ CopyCompressedRange(const BufferHeader& Header, const CompositeBuffer& Compresse
}
else
{
- UniqueBuffer BlockSizeBuffer;
- MemoryView BlockSizeView =
- CompressedData.ViewOrCopyRange(sizeof(BufferHeader), Header.BlockCount * sizeof(uint32_t), BlockSizeBuffer);
+ MemoryView BlockSizeView = HeaderRawData.Mid(sizeof(Header), Header.BlockCount * sizeof(uint32_t));
std::span<uint32_t const> CompressedBlockSizes(reinterpret_cast<const uint32_t*>(BlockSizeView.GetData()), Header.BlockCount);
const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent;
@@ -1410,26 +1442,28 @@ CompressedBuffer::DecodeRawHash() const
CompressedBuffer
CompressedBuffer::CopyRange(uint64_t RawOffset, uint64_t RawSize) const
{
- using namespace detail;
- const BufferHeader Header = BufferHeader::Read(CompressedData);
- const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset;
-
- CompressedBuffer Range;
- Range.CompressedData = CopyCompressedRange(Header, CompressedData, RawOffset, TotalRawSize);
-
+ CompressedBuffer Range;
+ detail::BufferHeader Header;
+ UniqueBuffer RawHeaderData;
+ if (ReadHeader(CompressedData, Header, &RawHeaderData))
+ {
+ const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset;
+ Range.CompressedData = CopyCompressedRange(Header, RawHeaderData.GetView(), CompressedData, RawOffset, TotalRawSize);
+ }
return Range;
}
CompressedBuffer
CompressedBuffer::GetRange(uint64_t RawOffset, uint64_t RawSize) const
{
- using namespace detail;
- const BufferHeader Header = BufferHeader::Read(CompressedData);
- const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset;
-
- CompressedBuffer Range;
- Range.CompressedData = GetCompressedRange(Header, CompressedData, RawOffset, TotalRawSize);
-
+ CompressedBuffer Range;
+ detail::BufferHeader Header;
+ UniqueBuffer RawHeaderData;
+ if (ReadHeader(CompressedData, Header, &RawHeaderData))
+ {
+ const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset;
+ Range.CompressedData = GetCompressedRange(Header, RawHeaderData.GetView(), CompressedData, RawOffset, TotalRawSize);
+ }
return Range;
}
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index 3e94b550f..ca2b3101f 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -730,7 +730,7 @@ CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
throw std::runtime_error("CopyFile failed in an unexpected way");
}
}
- catch (std::exception& Ex)
+ catch (const std::exception& Ex)
{
++FailedFileCount;
diff --git a/src/zencore/include/zencore/callstack.h b/src/zencore/include/zencore/callstack.h
new file mode 100644
index 000000000..02ba8b3c3
--- /dev/null
+++ b/src/zencore/include/zencore/callstack.h
@@ -0,0 +1,37 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/zencore.h>
+
+#include <zencore/string.h>
+
+#include <string>
+#include <vector>
+
+namespace zen {
+
+struct CallstackFrames
+{
+ uint32_t FrameCount;
+ void** Frames;
+};
+
+CallstackFrames* CreateCallstack(uint32_t FrameCount, void** Frames) noexcept;
+CallstackFrames* CloneCallstack(const CallstackFrames* Callstack) noexcept;
+void FreeCallstack(CallstackFrames* Callstack) noexcept;
+
+uint32_t GetCallstack(int FramesToSkip, int FramesToCapture, void* OutAddresses[]);
+std::vector<std::string> GetFrameSymbols(uint32_t FrameCount, void** Frames);
+inline std::vector<std::string>
+GetFrameSymbols(const CallstackFrames* Callstack)
+{
+ return GetFrameSymbols(Callstack ? Callstack->FrameCount : 0, Callstack ? Callstack->Frames : nullptr);
+}
+
+void FormatCallstack(const CallstackFrames* Callstack, StringBuilderBase& SB);
+std::string CallstackToString(const CallstackFrames* Callstack);
+
+void callstack_forcelink(); // internal
+
+} // namespace zen
diff --git a/src/zencore/include/zencore/scopeguard.h b/src/zencore/include/zencore/scopeguard.h
index d04c8ed9c..3fd0564f6 100644
--- a/src/zencore/include/zencore/scopeguard.h
+++ b/src/zencore/include/zencore/scopeguard.h
@@ -21,7 +21,11 @@ public:
{
m_guardFunc();
}
- catch (std::exception& Ex)
+ catch (const AssertException& Ex)
+ {
+ ZEN_ERROR("Assert exception in scope guard: {}", Ex.FullDescription());
+ }
+ catch (const std::exception& Ex)
{
ZEN_ERROR("scope guard threw exception: '{}'", Ex.what());
}
diff --git a/src/zencore/include/zencore/zencore.h b/src/zencore/include/zencore/zencore.h
index 1a9060e29..cd1a34c7b 100644
--- a/src/zencore/include/zencore/zencore.h
+++ b/src/zencore/include/zencore/zencore.h
@@ -24,34 +24,63 @@
#endif
namespace zen {
+
+struct CallstackFrames;
+
class AssertException : public std::logic_error
{
public:
- inline explicit AssertException(const char* Msg) : std::logic_error(Msg) {}
+ using _Mybase = std::logic_error;
+
+ virtual ~AssertException() noexcept;
+
+ inline AssertException(const char* Msg, struct CallstackFrames* Callstack) noexcept : _Mybase(Msg), _Callstack(Callstack) {}
+
+ AssertException(const AssertException& Rhs) noexcept;
+
+ AssertException(AssertException&& Rhs) noexcept;
+
+ AssertException& operator=(const AssertException& Rhs) noexcept;
+
+ std::string FullDescription() const noexcept;
+
+ struct CallstackFrames* _Callstack = nullptr;
};
struct AssertImpl
{
+ ZEN_FORCENOINLINE ZEN_DEBUG_SECTION AssertImpl() : PrevAssertImpl(CurrentAssertImpl) { CurrentAssertImpl = this; }
+ virtual ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ~AssertImpl() { CurrentAssertImpl = PrevAssertImpl; }
+
static void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ExecAssert
[[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, const char* Msg)
{
- CurrentAssertImpl->OnAssert(Filename, LineNumber, FunctionName, Msg);
- throw AssertException{Msg};
+ AssertImpl* AssertImpl = CurrentAssertImpl;
+ while (AssertImpl)
+ {
+ AssertImpl->OnAssert(Filename, LineNumber, FunctionName, Msg);
+ AssertImpl = AssertImpl->PrevAssertImpl;
+ }
+ ThrowAssertException(Filename, LineNumber, FunctionName, Msg);
}
-protected:
virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION OnAssert(const char* Filename,
int LineNumber,
const char* FunctionName,
const char* Msg)
{
- (void(Filename));
- (void(LineNumber));
- (void(FunctionName));
- (void(Msg));
+ ZEN_UNUSED(Filename);
+ ZEN_UNUSED(LineNumber);
+ ZEN_UNUSED(FunctionName);
+ ZEN_UNUSED(Msg);
}
- static AssertImpl DefaultAssertImpl;
+
+protected:
+ static void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ThrowAssertException
+ [[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, const char* Msg);
static AssertImpl* CurrentAssertImpl;
+ static AssertImpl DefaultAssertImpl;
+ AssertImpl* PrevAssertImpl = nullptr;
};
} // namespace zen
diff --git a/src/zencore/jobqueue.cpp b/src/zencore/jobqueue.cpp
index 86c08cda9..d26d0dd1e 100644
--- a/src/zencore/jobqueue.cpp
+++ b/src/zencore/jobqueue.cpp
@@ -69,7 +69,7 @@ public:
Stop();
}
}
- catch (std::exception& Ex)
+ catch (const std::exception& Ex)
{
ZEN_WARN("Failed shutting down jobqueue. Reason: '{}'", Ex.what());
}
@@ -106,7 +106,7 @@ public:
});
return {.Id = NewJobId};
}
- catch (std::exception& Ex)
+ catch (const std::exception& Ex)
{
WorkerCounter.CountDown();
QueueLock.WithExclusiveLock([&]() {
@@ -359,7 +359,18 @@ public:
CompletedJobs.insert_or_assign(CurrentJob->Id.Id, std::move(CurrentJob));
});
}
- catch (std::exception& Ex)
+ catch (const AssertException& Ex)
+ {
+ ZEN_DEBUG("Background job {}:'{}' asserted. Reason: {}", CurrentJob->Id.Id, CurrentJob->Name, Ex.FullDescription());
+ QueueLock.WithExclusiveLock([&]() {
+ CurrentJob->State.AbortReason = Ex.FullDescription();
+ CurrentJob->EndTick = JobClock::Now();
+ CurrentJob->WorkerThreadId = 0;
+ RunningJobs.erase(CurrentJob->Id.Id);
+ AbortedJobs.insert_or_assign(CurrentJob->Id.Id, std::move(CurrentJob));
+ });
+ }
+ catch (const std::exception& Ex)
{
ZEN_DEBUG("Background job {}:'{}' aborted. Reason: '{}'", CurrentJob->Id.Id, CurrentJob->Name, Ex.what());
QueueLock.WithExclusiveLock([&]() {
diff --git a/src/zencore/workthreadpool.cpp b/src/zencore/workthreadpool.cpp
index 16b2310ff..f41c13bf6 100644
--- a/src/zencore/workthreadpool.cpp
+++ b/src/zencore/workthreadpool.cpp
@@ -186,7 +186,13 @@ WorkerThreadPool::Impl::WorkerThreadFunction(ThreadStartInfo Info)
ZEN_TRACE_CPU_FLUSH("AsyncWork");
Work->Execute();
}
- catch (std::exception& e)
+ catch (const AssertException& Ex)
+ {
+ Work->m_Exception = std::current_exception();
+
+ ZEN_WARN("Assert exception in worker thread: {}", Ex.FullDescription());
+ }
+ catch (const std::exception& e)
{
Work->m_Exception = std::current_exception();
@@ -234,7 +240,13 @@ WorkerThreadPool::ScheduleWork(Ref<IWork> Work)
ZEN_TRACE_CPU_FLUSH("SyncWork");
Work->Execute();
}
- catch (std::exception& e)
+ catch (const AssertException& Ex)
+ {
+ Work->m_Exception = std::current_exception();
+
+ ZEN_WARN("Assert exception in worker thread: {}", Ex.FullDescription());
+ }
+ catch (const std::exception& e)
{
Work->m_Exception = std::current_exception();
diff --git a/src/zencore/xmake.lua b/src/zencore/xmake.lua
index e6102679d..5f2d95e16 100644
--- a/src/zencore/xmake.lua
+++ b/src/zencore/xmake.lua
@@ -53,6 +53,7 @@ target('zencore')
if is_plat("windows") then
add_syslinks("Advapi32")
+ add_syslinks("Dbghelp")
add_syslinks("Shell32")
add_syslinks("User32")
add_syslinks("crypt32")
diff --git a/src/zencore/zencore.cpp b/src/zencore/zencore.cpp
index c97f5e5ca..c4fcc89de 100644
--- a/src/zencore/zencore.cpp
+++ b/src/zencore/zencore.cpp
@@ -6,12 +6,9 @@
# include <zencore/windows.h>
#endif
-#if ZEN_PLATFORM_LINUX
-# include <pthread.h>
-#endif
-
#include <zencore/assertfmt.h>
#include <zencore/blake3.h>
+#include <zencore/callstack.h>
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
@@ -55,10 +52,59 @@ ExecAssertFmt(const char* Filename, int LineNumber, const char* FunctionName, st
namespace zen {
+AssertException::AssertException(const AssertException& Rhs) noexcept : _Mybase(Rhs), _Callstack(CloneCallstack(Rhs._Callstack))
+{
+}
+
+AssertException::AssertException(AssertException&& Rhs) noexcept : _Mybase(Rhs), _Callstack(Rhs._Callstack)
+{
+ Rhs._Callstack = nullptr;
+}
+
+AssertException::~AssertException() noexcept
+{
+ FreeCallstack(_Callstack);
+}
+
+AssertException&
+AssertException::operator=(const AssertException& Rhs) noexcept
+{
+ _Mybase::operator=(Rhs);
+
+ CallstackFrames* Callstack = CloneCallstack(Rhs._Callstack);
+ std::swap(_Callstack, Callstack);
+ FreeCallstack(Callstack);
+ return *this;
+}
+
+std::string
+AssertException::FullDescription() const noexcept
+{
+ if (_Callstack)
+ {
+ return fmt::format("'{}'\n{}", what(), CallstackToString(_Callstack));
+ }
+ return what();
+}
+
+void
+AssertImpl::ThrowAssertException(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg)
+{
+ ZEN_UNUSED(FunctionName);
+ fmt::basic_memory_buffer<char, 2048> Message;
+ auto Appender = fmt::appender(Message);
+ fmt::format_to(Appender, "{}({}): {}", Filename, LineNumber, Msg);
+ Message.push_back('\0');
+
+ void* Frames[8];
+ uint32_t FrameCount = GetCallstack(3, 8, Frames);
+ throw AssertException(Message.data(), CreateCallstack(FrameCount, Frames));
+}
+
void refcount_forcelink();
+AssertImpl* AssertImpl::CurrentAssertImpl = nullptr;
AssertImpl AssertImpl::DefaultAssertImpl;
-AssertImpl* AssertImpl::CurrentAssertImpl = &AssertImpl::DefaultAssertImpl;
//////////////////////////////////////////////////////////////////////////
@@ -138,6 +184,7 @@ void
zencore_forcelinktests()
{
zen::blake3_forcelink();
+ zen::callstack_forcelink();
zen::compositebuffer_forcelink();
zen::compress_forcelink();
zen::crypto_forcelink();
@@ -174,24 +221,24 @@ TEST_SUITE_BEGIN("core.assert");
TEST_CASE("Assert.Default")
{
- bool A = true;
- bool B = false;
- CHECK_THROWS_WITH(ZEN_ASSERT(A == B), "A == B");
+ bool A = true;
+ bool B = false;
+ std::string Expected = fmt::format("{}({}): {}", __FILE__, __LINE__ + 1, "A == B");
+ CHECK_THROWS_WITH(ZEN_ASSERT(A == B), Expected.c_str());
}
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");
+ bool A = true;
+ bool B = false;
+ std::string Expected = fmt::format("{}({}): {}", __FILE__, __LINE__ + 1, "assert(A == B) failed: true == false");
+ CHECK_THROWS_WITH(ZEN_ASSERT_FORMAT(A == B, "{} == {}", A, B), Expected.c_str());
}
TEST_CASE("Assert.Custom")
{
struct MyAssertImpl : AssertImpl
{
- ZEN_FORCENOINLINE ZEN_DEBUG_SECTION MyAssertImpl() : PrevAssertImpl(CurrentAssertImpl) { CurrentAssertImpl = this; }
- virtual ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ~MyAssertImpl() { CurrentAssertImpl = PrevAssertImpl; }
virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION OnAssert(const char* Filename,
int LineNumber,
const char* FunctionName,
@@ -202,7 +249,7 @@ TEST_CASE("Assert.Custom")
FuncName = FunctionName;
Message = Msg;
}
- AssertImpl* PrevAssertImpl;
+ AssertImpl* PrevAssertImpl = nullptr;
const char* AssertFileName = nullptr;
int Line = -1;
@@ -213,13 +260,47 @@ TEST_CASE("Assert.Custom")
MyAssertImpl MyAssert;
bool A = true;
bool B = false;
- CHECK_THROWS_WITH(ZEN_ASSERT(A == B), "A == B");
+ CHECK_THROWS_WITH(ZEN_ASSERT(A == B), std::string(fmt::format("{}({}): {}", __FILE__, __LINE__, "A == B")).c_str());
CHECK(MyAssert.AssertFileName != nullptr);
CHECK(MyAssert.Line != -1);
CHECK(MyAssert.FuncName != nullptr);
CHECK(strcmp(MyAssert.Message, "A == B") == 0);
}
+TEST_CASE("Assert.Callstack")
+{
+ try
+ {
+ ZEN_ASSERT(false);
+ }
+ catch (const AssertException& Assert)
+ {
+ ZEN_INFO("Assert failed: {}", Assert.what());
+ CHECK(Assert._Callstack->FrameCount > 0);
+ CHECK(Assert._Callstack->Frames != nullptr);
+ ZEN_INFO("Callstack:\n{}", CallstackToString(Assert._Callstack));
+ }
+
+ WorkerThreadPool Pool(1);
+ auto Task = Pool.EnqueueTask(std::packaged_task<int()>{[] {
+ ZEN_ASSERT(false);
+ return 1;
+ }});
+
+ try
+ {
+ (void)Task.get();
+ CHECK(false);
+ }
+ catch (const AssertException& Assert)
+ {
+ ZEN_INFO("Assert in future: {}", Assert.what());
+ CHECK(Assert._Callstack->FrameCount > 0);
+ CHECK(Assert._Callstack->Frames != nullptr);
+ ZEN_INFO("Callstack:\n{}", CallstackToString(Assert._Callstack));
+ }
+}
+
TEST_SUITE_END();
#endif