diff options
| author | EPICGAMES\thierry.begin <[email protected]> | 2024-04-08 10:43:16 -0400 |
|---|---|---|
| committer | EPICGAMES\thierry.begin <[email protected]> | 2024-04-08 10:43:16 -0400 |
| commit | b35e1258a043cab06950b2453f434861d99b918a (patch) | |
| tree | 695737774fa08ebaa0e32a9f95cb0247c34b3dc3 /src/zencore | |
| parent | Add docker support (diff) | |
| parent | Merge pull request #41 from ue-foundation/zs/import-oplog-clean (diff) | |
| download | zen-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.cpp | 221 | ||||
| -rw-r--r-- | src/zencore/compress.cpp | 138 | ||||
| -rw-r--r-- | src/zencore/filesystem.cpp | 2 | ||||
| -rw-r--r-- | src/zencore/include/zencore/callstack.h | 37 | ||||
| -rw-r--r-- | src/zencore/include/zencore/scopeguard.h | 6 | ||||
| -rw-r--r-- | src/zencore/include/zencore/zencore.h | 47 | ||||
| -rw-r--r-- | src/zencore/jobqueue.cpp | 17 | ||||
| -rw-r--r-- | src/zencore/workthreadpool.cpp | 16 | ||||
| -rw-r--r-- | src/zencore/xmake.lua | 1 | ||||
| -rw-r--r-- | src/zencore/zencore.cpp | 111 |
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 |