From 6df7bce35e84f91c868face688587c26a3765c7e Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 16 Mar 2026 10:27:24 +0100 Subject: URI decoding, process env, compiler info, httpasio strands, regex route removal (#841) - Percent-decode URIs in ASIO HTTP server to match http.sys CookedUrl behavior, ensuring consistent decoded paths across backends - Add Environment field to CreateProcOptions for passing extra env vars to child processes (Windows: merged into Unicode environment block; Unix: setenv in fork) - Add GetCompilerName() and include it in build options startup logging - Suppress Windows CRT error dialogs in test harness for headless/CI runs - Fix mimalloc package: pass CMAKE_BUILD_TYPE, skip cfuncs test for cross-compile - Add virtual destructor to SentryAssertImpl to fix debug-mode warning - Simplify object store path handling now that URIs arrive pre-decoded - Add URI decoding test coverage for percent-encoded paths and query params - Simplify httpasio request handling by using strands (guarantees no parallel handlers per connection) - Removed deprecated regex-based route matching support - Fix full GC never triggering after cross-toolchain builds: The `gc_state` file stores `system_clock` ticks, but the tick resolution differs between toolchains (nanoseconds on GCC/standard clang, microseconds on UE clang). A nanosecond timestamp misinterpreted as microseconds appears far in the future (~year 58,000), bypassing the staleness check and preventing time-based full GC from ever running. Fixed by also resetting when the stored timestamp is in the future. - Clamp GC countdown display to configured interval: Prevents nonsensical log output (e.g. "Full GC in 492128002h") caused by the above or any other clock anomaly. The clamp applies to both the scheduler log and the status API. --- src/zencore/include/zencore/process.h | 9 ++++++ src/zencore/include/zencore/system.h | 1 + src/zencore/process.cpp | 52 ++++++++++++++++++++++++++++++++++- src/zencore/sentryintegration.cpp | 2 ++ src/zencore/system.cpp | 18 ++++++++++++ src/zencore/testing.cpp | 13 +++++++++ 6 files changed, 94 insertions(+), 1 deletion(-) (limited to 'src/zencore') diff --git a/src/zencore/include/zencore/process.h b/src/zencore/include/zencore/process.h index 809312c7b..3177f64c1 100644 --- a/src/zencore/include/zencore/process.h +++ b/src/zencore/include/zencore/process.h @@ -6,6 +6,9 @@ #include #include +#include +#include +#include namespace zen { @@ -68,6 +71,12 @@ struct CreateProcOptions const std::filesystem::path* WorkingDirectory = nullptr; uint32_t Flags = 0; std::filesystem::path StdoutFile; + + /// Additional environment variables for the child process. These are merged + /// with the parent's environment — existing variables are inherited, and + /// entries here override or add to them. + std::vector> Environment; + #if ZEN_PLATFORM_WINDOWS JobObject* AssignToJob = nullptr; // When set, the process is created suspended, assigned to the job, then resumed #endif diff --git a/src/zencore/include/zencore/system.h b/src/zencore/include/zencore/system.h index a67999e52..2e39cc660 100644 --- a/src/zencore/include/zencore/system.h +++ b/src/zencore/include/zencore/system.h @@ -17,6 +17,7 @@ std::string_view GetOperatingSystemName(); std::string GetOperatingSystemVersion(); std::string_view GetRuntimePlatformName(); // "windows", "wine", "linux", or "macos" std::string_view GetCpuName(); +std::string_view GetCompilerName(); struct SystemMetrics { diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp index f657869dc..852678ffe 100644 --- a/src/zencore/process.cpp +++ b/src/zencore/process.cpp @@ -11,6 +11,7 @@ #include #include +#include #include ZEN_THIRD_PARTY_INCLUDES_START @@ -487,13 +488,57 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)}; bool InheritHandles = false; - void* Environment = nullptr; LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr; LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr; + // Build environment block when custom environment variables are specified + ExtendableWideStringBuilder<512> EnvironmentBlock; + void* Environment = nullptr; + if (!Options.Environment.empty()) + { + // Capture current environment into a map + std::map EnvMap; + wchar_t* EnvStrings = GetEnvironmentStringsW(); + if (EnvStrings) + { + for (const wchar_t* Ptr = EnvStrings; *Ptr; Ptr += wcslen(Ptr) + 1) + { + std::wstring_view Entry(Ptr); + size_t EqPos = Entry.find(L'='); + if (EqPos != std::wstring_view::npos && EqPos > 0) + { + EnvMap[std::wstring(Entry.substr(0, EqPos))] = std::wstring(Entry.substr(EqPos + 1)); + } + } + FreeEnvironmentStringsW(EnvStrings); + } + + // Apply overrides + for (const auto& [Key, Value] : Options.Environment) + { + EnvMap[Utf8ToWide(Key)] = Utf8ToWide(Value); + } + + // Build double-null-terminated environment block + for (const auto& [Key, Value] : EnvMap) + { + EnvironmentBlock << Key; + EnvironmentBlock.Append(L'='); + EnvironmentBlock << Value; + EnvironmentBlock.Append(L'\0'); + } + EnvironmentBlock.Append(L'\0'); + + Environment = EnvironmentBlock.Data(); + } + const bool AssignToJob = Options.AssignToJob && Options.AssignToJob->IsValid(); DWORD CreationFlags = 0; + if (Environment) + { + CreationFlags |= CREATE_UNICODE_ENVIRONMENT; + } if (Options.Flags & CreateProcOptions::Flag_NewConsole) { CreationFlags |= CREATE_NEW_CONSOLE; @@ -790,6 +835,11 @@ CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine } } + for (const auto& [Key, Value] : Options.Environment) + { + setenv(Key.c_str(), Value.c_str(), 1); + } + if (execv(Executable.c_str(), ArgV.data()) < 0) { ThrowLastError("Failed to exec() a new process image"); diff --git a/src/zencore/sentryintegration.cpp b/src/zencore/sentryintegration.cpp index 58b76783a..b7d01003b 100644 --- a/src/zencore/sentryintegration.cpp +++ b/src/zencore/sentryintegration.cpp @@ -31,6 +31,8 @@ namespace { struct SentryAssertImpl : zen::AssertImpl { + ZEN_DEBUG_SECTION ~SentryAssertImpl() override = default; + virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION OnAssert(const char* Filename, int LineNumber, const char* FunctionName, diff --git a/src/zencore/system.cpp b/src/zencore/system.cpp index 141450b84..8985a8a76 100644 --- a/src/zencore/system.cpp +++ b/src/zencore/system.cpp @@ -660,6 +660,24 @@ GetCpuName() #endif } +std::string_view +GetCompilerName() +{ +#define ZEN_STRINGIFY_IMPL(x) #x +#define ZEN_STRINGIFY(x) ZEN_STRINGIFY_IMPL(x) +#if ZEN_COMPILER_CLANG + return "clang " ZEN_STRINGIFY(__clang_major__) "." ZEN_STRINGIFY(__clang_minor__) "." ZEN_STRINGIFY(__clang_patchlevel__); +#elif ZEN_COMPILER_MSC + return "MSVC " ZEN_STRINGIFY(_MSC_VER); +#elif ZEN_COMPILER_GCC + return "GCC " ZEN_STRINGIFY(__GNUC__) "." ZEN_STRINGIFY(__GNUC_MINOR__) "." ZEN_STRINGIFY(__GNUC_PATCHLEVEL__); +#else + return "unknown"; +#endif +#undef ZEN_STRINGIFY +#undef ZEN_STRINGIFY_IMPL +} + void Describe(const SystemMetrics& Metrics, CbWriter& Writer) { diff --git a/src/zencore/testing.cpp b/src/zencore/testing.cpp index d7eb3b17d..f5bc723b1 100644 --- a/src/zencore/testing.cpp +++ b/src/zencore/testing.cpp @@ -24,6 +24,8 @@ # if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC # include # include +# elif ZEN_PLATFORM_WINDOWS +# include # endif namespace zen::testing { @@ -296,6 +298,17 @@ RunTestMain(int Argc, char* Argv[], const char* ExecutableName, void (*ForceLink } # endif +# if ZEN_PLATFORM_WINDOWS + // Suppress Windows error dialogs (crash/abort/assert) so tests terminate + // immediately instead of blocking on a modal dialog in CI or headless runs. + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + _set_abort_behavior(0, _WRITE_ABORT_MSG); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +# endif + zen::logging::InitializeLogging(); zen::MaximizeOpenFileCount(); InstallCrashSignalHandlers(); -- cgit v1.2.3