diff options
| author | Stefan Boberg <[email protected]> | 2023-10-06 10:45:48 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-10-06 10:45:48 +0200 |
| commit | fb70324d37282910d7fa3047f4ec290d0c5a94b1 (patch) | |
| tree | a1bc82fcfdb96eb5b461742b613fcbb63f816a54 /src | |
| parent | reject known bad bucket names in structured cache (#452) (diff) | |
| download | zen-fb70324d37282910d7fa3047f4ec290d0c5a94b1.tar.xz zen-fb70324d37282910d7fa3047f4ec290d0c5a94b1.zip | |
zenserver project restructuring (#442)
Diffstat (limited to 'src')
| -rw-r--r-- | src/zenserver/config.cpp | 894 | ||||
| -rw-r--r-- | src/zenserver/config.h | 4 | ||||
| -rw-r--r-- | src/zenserver/diag/logging.cpp | 8 | ||||
| -rw-r--r-- | src/zenserver/diag/logging.h | 6 | ||||
| -rw-r--r-- | src/zenserver/main.cpp | 390 | ||||
| -rw-r--r-- | src/zenserver/sentryintegration.cpp | 288 | ||||
| -rw-r--r-- | src/zenserver/sentryintegration.h | 48 | ||||
| -rw-r--r-- | src/zenserver/xmake.lua | 2 | ||||
| -rw-r--r-- | src/zenserver/zenserver.cpp | 1487 | ||||
| -rw-r--r-- | src/zenserver/zenserver.h | 147 |
10 files changed, 1741 insertions, 1533 deletions
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp index b7699900f..435b66a83 100644 --- a/src/zenserver/config.cpp +++ b/src/zenserver/config.cpp @@ -32,6 +32,8 @@ ZEN_THIRD_PARTY_INCLUDES_END # pragma comment(lib, "shell32.lib") # pragma comment(lib, "ole32.lib") +namespace zen { + std::filesystem::path PickDefaultStateDirectory() { @@ -51,8 +53,12 @@ PickDefaultStateDirectory() return L""; } +} // namespace zen + #else +namespace zen { + std::filesystem::path PickDefaultStateDirectory() { @@ -61,8 +67,12 @@ PickDefaultStateDirectory() return std::filesystem::path(Passwd->pw_dir) / ".zen"; } +} // namespace zen + #endif +namespace zen { + void ValidateOptions(ZenServerOptions& ServerOptions) { @@ -159,409 +169,385 @@ MakeSafePath(const std::string_view Path) namespace LuaConfig { -void -EscapeBackslash(std::string& InOutString) -{ - std::size_t BackslashPos = InOutString.find('\\'); - if (BackslashPos != std::string::npos) + void EscapeBackslash(std::string& InOutString) { - std::size_t Offset = 0; - zen::ExtendableStringBuilder<512> PathBuilder; - while (BackslashPos != std::string::npos) + std::size_t BackslashPos = InOutString.find('\\'); + if (BackslashPos != std::string::npos) { - PathBuilder.Append(InOutString.substr(Offset, BackslashPos + 1 - Offset)); - PathBuilder.Append('\\'); - Offset = BackslashPos + 1; - BackslashPos = InOutString.find('\\', Offset); + std::size_t Offset = 0; + zen::ExtendableStringBuilder<512> PathBuilder; + while (BackslashPos != std::string::npos) + { + PathBuilder.Append(InOutString.substr(Offset, BackslashPos + 1 - Offset)); + PathBuilder.Append('\\'); + Offset = BackslashPos + 1; + BackslashPos = InOutString.find('\\', Offset); + } + PathBuilder.Append(InOutString.substr(Offset, BackslashPos)); + InOutString = PathBuilder.ToString(); } - PathBuilder.Append(InOutString.substr(Offset, BackslashPos)); - InOutString = PathBuilder.ToString(); } -} -class OptionValue -{ -public: - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0; - virtual void Parse(sol::object Object) = 0; + class OptionValue + { + public: + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0; + virtual void Parse(sol::object Object) = 0; - virtual ~OptionValue() {} -}; + virtual ~OptionValue() {} + }; -typedef std::shared_ptr<OptionValue> TOptionValue; + typedef std::shared_ptr<OptionValue> TOptionValue; -class StringOption : public OptionValue -{ -public: - StringOption(std::string& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + class StringOption : public OptionValue { - StringBuilder.Append(fmt::format("\"{}\"", Value)); - } - virtual void Parse(sol::object Object) override { Value = Object.as<std::string>(); } - std::string& Value; -}; - -class FilePathOption : public OptionValue -{ -public: - FilePathOption(std::filesystem::path& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override - { - std::string Path = Value.string(); - EscapeBackslash(Path); - StringBuilder.Append(fmt::format("\"{}\"", Path)); - } - virtual void Parse(sol::object Object) override - { - std::string Str = Object.as<std::string>(); - if (!Str.empty()) + public: + StringOption(std::string& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { - Value = MakeSafePath(Str); + StringBuilder.Append(fmt::format("\"{}\"", Value)); } - } - std::filesystem::path& Value; -}; - -class BoolOption : public OptionValue -{ -public: - BoolOption(bool& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { StringBuilder.Append(Value ? "true" : "false"); } - virtual void Parse(sol::object Object) override { Value = Object.as<bool>(); } - bool& Value; -}; + virtual void Parse(sol::object Object) override { Value = Object.as<std::string>(); } + std::string& Value; + }; -class CachePolicyOption : public OptionValue -{ -public: - CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + class FilePathOption : public OptionValue { - switch (Value) + public: + FilePathOption(std::filesystem::path& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { - case UpstreamCachePolicy::Read: - StringBuilder.Append("readonly"); - break; - case UpstreamCachePolicy::Write: - StringBuilder.Append("writeonly"); - break; - case UpstreamCachePolicy::Disabled: - StringBuilder.Append("disabled"); - break; - case UpstreamCachePolicy::ReadWrite: - StringBuilder.Append("readwrite"); - break; - default: - ZEN_ASSERT(false); + std::string Path = Value.string(); + EscapeBackslash(Path); + StringBuilder.Append(fmt::format("\"{}\"", Path)); } - } - virtual void Parse(sol::object Object) override - { - std::string PolicyString = Object.as<std::string>(); - if (PolicyString == "readonly") + virtual void Parse(sol::object Object) override { - Value = UpstreamCachePolicy::Read; + std::string Str = Object.as<std::string>(); + if (!Str.empty()) + { + Value = MakeSafePath(Str); + } } - else if (PolicyString == "writeonly") + std::filesystem::path& Value; + }; + + class BoolOption : public OptionValue + { + public: + BoolOption(bool& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { - Value = UpstreamCachePolicy::Write; + StringBuilder.Append(Value ? "true" : "false"); } - else if (PolicyString == "disabled") + virtual void Parse(sol::object Object) override { Value = Object.as<bool>(); } + bool& Value; + }; + + class CachePolicyOption : public OptionValue + { + public: + CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { - Value = UpstreamCachePolicy::Disabled; + switch (Value) + { + case UpstreamCachePolicy::Read: + StringBuilder.Append("readonly"); + break; + case UpstreamCachePolicy::Write: + StringBuilder.Append("writeonly"); + break; + case UpstreamCachePolicy::Disabled: + StringBuilder.Append("disabled"); + break; + case UpstreamCachePolicy::ReadWrite: + StringBuilder.Append("readwrite"); + break; + default: + ZEN_ASSERT(false); + } } - else if (PolicyString == "readwrite") + virtual void Parse(sol::object Object) override { - Value = UpstreamCachePolicy::ReadWrite; + std::string PolicyString = Object.as<std::string>(); + if (PolicyString == "readonly") + { + Value = UpstreamCachePolicy::Read; + } + else if (PolicyString == "writeonly") + { + Value = UpstreamCachePolicy::Write; + } + else if (PolicyString == "disabled") + { + Value = UpstreamCachePolicy::Disabled; + } + else if (PolicyString == "readwrite") + { + Value = UpstreamCachePolicy::ReadWrite; + } } - } - UpstreamCachePolicy& Value; -}; - -template<Integral T> -class NumberOption : public OptionValue -{ -public: - NumberOption(T& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { StringBuilder.Append(fmt::format("{}", Value)); } - virtual void Parse(sol::object Object) override { Value = Object.as<T>(); } - T& Value; -}; - -class LuaContainerWriter -{ -public: - LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent) - : StringBuilder(StringBuilder) - , InitialIndent(Indent.length()) - , LocalIndent(Indent) - { - StringBuilder.Append("{\n"); - LocalIndent.push_back('\t'); - } - ~LuaContainerWriter() - { - LocalIndent.pop_back(); - StringBuilder.Append(LocalIndent); - StringBuilder.Append("}"); - } + UpstreamCachePolicy& Value; + }; - void BeginContainer(std::string_view Name) + template<Integral T> + class NumberOption : public OptionValue { - StringBuilder.Append(LocalIndent); - if (!Name.empty()) - { - StringBuilder.Append(Name); - StringBuilder.Append(" = {\n"); - } - else + public: + NumberOption(T& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { - StringBuilder.Append("{\n"); + StringBuilder.Append(fmt::format("{}", Value)); } - LocalIndent.push_back('\t'); - } - void WriteValue(std::string_view Name, std::string_view Value) + virtual void Parse(sol::object Object) override { Value = Object.as<T>(); } + T& Value; + }; + + class LuaContainerWriter { - if (Name.empty()) + public: + LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent) + : StringBuilder(StringBuilder) + , InitialIndent(Indent.length()) + , LocalIndent(Indent) { - StringBuilder.Append(fmt::format("{}\"{}\",\n", LocalIndent, Value)); + StringBuilder.Append("{\n"); + LocalIndent.push_back('\t'); } - else + ~LuaContainerWriter() { - StringBuilder.Append(fmt::format("{}{} = \"{}\",\n", LocalIndent, Name, Value)); + LocalIndent.pop_back(); + StringBuilder.Append(LocalIndent); + StringBuilder.Append("}"); } - } - void EndContainer() - { - LocalIndent.pop_back(); - StringBuilder.Append(LocalIndent); - StringBuilder.Append("}"); - StringBuilder.Append(",\n"); - } -private: - zen::StringBuilderBase& StringBuilder; - const std::size_t InitialIndent; - std::string LocalIndent; -}; - -class StringArrayOption : public OptionValue -{ -public: - StringArrayOption(std::vector<std::string>& Value) : Value(Value) {} - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override - { - if (Value.empty()) + void BeginContainer(std::string_view Name) { - StringBuilder.Append("{}"); - } - if (Value.size() == 1) - { - StringBuilder.Append(fmt::format("\"{}\"", Value[0])); + StringBuilder.Append(LocalIndent); + if (!Name.empty()) + { + StringBuilder.Append(Name); + StringBuilder.Append(" = {\n"); + } + else + { + StringBuilder.Append("{\n"); + } + LocalIndent.push_back('\t'); } - else + void WriteValue(std::string_view Name, std::string_view Value) { - LuaContainerWriter Writer(StringBuilder, Indent); - for (std::string String : Value) + if (Name.empty()) + { + StringBuilder.Append(fmt::format("{}\"{}\",\n", LocalIndent, Value)); + } + else { - Writer.WriteValue("", String); + StringBuilder.Append(fmt::format("{}{} = \"{}\",\n", LocalIndent, Name, Value)); } } - } - virtual void Parse(sol::object Object) override + void EndContainer() + { + LocalIndent.pop_back(); + StringBuilder.Append(LocalIndent); + StringBuilder.Append("}"); + StringBuilder.Append(",\n"); + } + + private: + zen::StringBuilderBase& StringBuilder; + const std::size_t InitialIndent; + std::string LocalIndent; + }; + + class StringArrayOption : public OptionValue { - if (Object.get_type() == sol::type::string) + public: + StringArrayOption(std::vector<std::string>& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override { - Value.push_back(Object.as<std::string>()); + if (Value.empty()) + { + StringBuilder.Append("{}"); + } + if (Value.size() == 1) + { + StringBuilder.Append(fmt::format("\"{}\"", Value[0])); + } + else + { + LuaContainerWriter Writer(StringBuilder, Indent); + for (std::string String : Value) + { + Writer.WriteValue("", String); + } + } } - else if (Object.get_type() == sol::type::table) + virtual void Parse(sol::object Object) override { - for (const auto& Kv : Object.as<sol::table>()) + if (Object.get_type() == sol::type::string) { - Value.push_back(Kv.second.as<std::string>()); + Value.push_back(Object.as<std::string>()); + } + else if (Object.get_type() == sol::type::table) + { + for (const auto& Kv : Object.as<sol::table>()) + { + Value.push_back(Kv.second.as<std::string>()); + } } } - } -private: - std::vector<std::string>& Value; -}; + private: + std::vector<std::string>& Value; + }; -class ZenAuthConfigOption : public OptionValue -{ -public: - ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {} - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override + class ZenAuthConfigOption : public OptionValue { - if (Value.OpenIdProviders.empty()) + public: + ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override { - StringBuilder.Append("{}"); - return; - } - LuaContainerWriter Writer(StringBuilder, Indent); - for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders) - { - Writer.BeginContainer(""); + if (Value.OpenIdProviders.empty()) { - Writer.WriteValue("name", Config.Name); - Writer.WriteValue("url", Config.Url); - Writer.WriteValue("clientid", Config.ClientId); + StringBuilder.Append("{}"); + return; + } + LuaContainerWriter Writer(StringBuilder, Indent); + for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders) + { + Writer.BeginContainer(""); + { + Writer.WriteValue("name", Config.Name); + Writer.WriteValue("url", Config.Url); + Writer.WriteValue("clientid", Config.ClientId); + } + Writer.EndContainer(); } - Writer.EndContainer(); } - } - virtual void Parse(sol::object Object) override - { - if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>()) + virtual void Parse(sol::object Object) override { - for (const auto& Kv : OpenIdProviders.value()) + if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>()) { - if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>()) + for (const auto& Kv : OpenIdProviders.value()) { - std::string Name = OpenIdProvider.value().get_or("name", std::string("Default")); - std::string Url = OpenIdProvider.value().get_or("url", std::string()); - std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string()); + if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>()) + { + std::string Name = OpenIdProvider.value().get_or("name", std::string("Default")); + std::string Url = OpenIdProvider.value().get_or("url", std::string()); + std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string()); - Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)}); + Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)}); + } } } } - } - ZenAuthConfig& Value; -}; + ZenAuthConfig& Value; + }; -class ZenObjectStoreConfigOption : public OptionValue -{ -public: - ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {} - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override + class ZenObjectStoreConfigOption : public OptionValue { - if (Value.Buckets.empty()) + public: + ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override { - StringBuilder.Append("{}"); - return; - } - LuaContainerWriter Writer(StringBuilder, Indent); - for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets) - { - Writer.BeginContainer(""); + if (Value.Buckets.empty()) { - Writer.WriteValue("name", Config.Name); - std::string Directory = Config.Directory.string(); - EscapeBackslash(Directory); - Writer.WriteValue("directory", Directory); + StringBuilder.Append("{}"); + return; + } + LuaContainerWriter Writer(StringBuilder, Indent); + for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets) + { + Writer.BeginContainer(""); + { + Writer.WriteValue("name", Config.Name); + std::string Directory = Config.Directory.string(); + EscapeBackslash(Directory); + Writer.WriteValue("directory", Directory); + } + Writer.EndContainer(); } - Writer.EndContainer(); } - } - virtual void Parse(sol::object Object) override - { - if (sol::optional<sol::table> Buckets = Object.as<sol::table>()) + virtual void Parse(sol::object Object) override { - for (const auto& Kv : Buckets.value()) + if (sol::optional<sol::table> Buckets = Object.as<sol::table>()) { - if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>()) + for (const auto& Kv : Buckets.value()) { - std::string Name = Bucket.value().get_or("name", std::string("Default")); - std::string Directory = Bucket.value().get_or("directory", std::string()); + if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>()) + { + std::string Name = Bucket.value().get_or("name", std::string("Default")); + std::string Directory = Bucket.value().get_or("directory", std::string()); - Value.Buckets.push_back({.Name = std::move(Name), .Directory = MakeSafePath(Directory)}); + Value.Buckets.push_back({.Name = std::move(Name), .Directory = MakeSafePath(Directory)}); + } } } } - } - ZenObjectStoreConfig& Value; -}; - -std::shared_ptr<OptionValue> -MakeOption(std::string& Value) -{ - return std::make_shared<StringOption>(Value); -}; + ZenObjectStoreConfig& Value; + }; -std::shared_ptr<OptionValue> -MakeOption(std::filesystem::path& Value) -{ - return std::make_shared<FilePathOption>(Value); -}; + std::shared_ptr<OptionValue> MakeOption(std::string& Value) { return std::make_shared<StringOption>(Value); }; -template<Integral T> -std::shared_ptr<OptionValue> -MakeOption(T& Value) -{ - return std::make_shared<NumberOption<T>>(Value); -}; + std::shared_ptr<OptionValue> MakeOption(std::filesystem::path& Value) { return std::make_shared<FilePathOption>(Value); }; -std::shared_ptr<OptionValue> -MakeOption(bool& Value) -{ - return std::make_shared<BoolOption>(Value); -}; + template<Integral T> + std::shared_ptr<OptionValue> MakeOption(T& Value) + { + return std::make_shared<NumberOption<T>>(Value); + }; -std::shared_ptr<OptionValue> -MakeOption(UpstreamCachePolicy& Value) -{ - return std::make_shared<CachePolicyOption>(Value); -}; + std::shared_ptr<OptionValue> MakeOption(bool& Value) { return std::make_shared<BoolOption>(Value); }; -std::shared_ptr<OptionValue> -MakeOption(std::vector<std::string>& Value) -{ - return std::make_shared<StringArrayOption>(Value); -}; + std::shared_ptr<OptionValue> MakeOption(UpstreamCachePolicy& Value) { return std::make_shared<CachePolicyOption>(Value); }; -std::shared_ptr<OptionValue> -MakeOption(ZenAuthConfig& Value) -{ - return std::make_shared<ZenAuthConfigOption>(Value); -}; + std::shared_ptr<OptionValue> MakeOption(std::vector<std::string>& Value) { return std::make_shared<StringArrayOption>(Value); }; -std::shared_ptr<OptionValue> -MakeOption(ZenObjectStoreConfig& Value) -{ - return std::make_shared<ZenObjectStoreConfigOption>(Value); -}; + std::shared_ptr<OptionValue> MakeOption(ZenAuthConfig& Value) { return std::make_shared<ZenAuthConfigOption>(Value); }; -struct Option -{ - std::string CommandLineOptionName; - TOptionValue Value; -}; + std::shared_ptr<OptionValue> MakeOption(ZenObjectStoreConfig& Value) { return std::make_shared<ZenObjectStoreConfigOption>(Value); }; -struct Options -{ -public: - template<typename T> - void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "") + struct Option { - OptionMap.insert_or_assign(std::string(Key), - Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)}); + std::string CommandLineOptionName; + TOptionValue Value; }; - void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult) + struct Options { - zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path); + public: + template<typename T> + void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "") + { + OptionMap.insert_or_assign(std::string(Key), + Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)}); + }; - if (LuaScript) + void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult) { - sol::state lua; + zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path); + + if (LuaScript) + { + sol::state lua; - lua.open_libraries(sol::lib::base); + lua.open_libraries(sol::lib::base); - lua.set_function("getenv", [&](const std::string env) -> sol::object { + lua.set_function("getenv", [&](const std::string env) -> sol::object { #if ZEN_PLATFORM_WINDOWS - std::wstring EnvVarValue; - size_t RequiredSize = 0; - std::wstring EnvWide = zen::Utf8ToWide(env); - _wgetenv_s(&RequiredSize, nullptr, 0, EnvWide.c_str()); + std::wstring EnvVarValue; + size_t RequiredSize = 0; + std::wstring EnvWide = zen::Utf8ToWide(env); + _wgetenv_s(&RequiredSize, nullptr, 0, EnvWide.c_str()); - if (RequiredSize == 0) - return sol::make_object(lua, sol::lua_nil); + if (RequiredSize == 0) + return sol::make_object(lua, sol::lua_nil); - EnvVarValue.resize(RequiredSize); - _wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str()); - return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str())); + EnvVarValue.resize(RequiredSize); + _wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str()); + return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str())); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC char* EnvVariable = getenv(env.c_str()); if (EnvVariable == nullptr) @@ -573,198 +559,198 @@ public: ZEN_UNUSED(env); return sol::make_object(lua, sol::lua_nil); #endif - }); + }); - try - { - sol::load_result config = lua.load(std::string_view((const char*)LuaScript.Data(), LuaScript.Size()), "zen_cfg"); - - if (!config.valid()) + try { - sol::error err = config; + sol::load_result config = lua.load(std::string_view((const char*)LuaScript.Data(), LuaScript.Size()), "zen_cfg"); + + if (!config.valid()) + { + sol::error err = config; + + std::string ErrorString = sol::to_string(config.status()); - std::string ErrorString = sol::to_string(config.status()); + throw std::runtime_error(fmt::format("{} error: {}", ErrorString, err.what())); + } - throw std::runtime_error(fmt::format("{} error: {}", ErrorString, err.what())); + config(); + } + catch (std::exception& e) + { + throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str()); } - config(); - } - catch (std::exception& e) - { - throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str()); + Parse(lua, CmdLineResult); } - - Parse(lua, CmdLineResult); } - } - void Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult) - { - for (auto It : LuaState) + void Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult) { - sol::object Key = It.first; - sol::type KeyType = Key.get_type(); - if (KeyType == sol::type::string) + for (auto It : LuaState) { - sol::type ValueType = It.second.get_type(); - switch (ValueType) + sol::object Key = It.first; + sol::type KeyType = Key.get_type(); + if (KeyType == sol::type::string) { - case sol::type::table: - { - std::string Name = Key.as<std::string>(); - if (Name.starts_with("_")) - { - continue; - } - if (Name == "base") + sol::type ValueType = It.second.get_type(); + switch (ValueType) + { + case sol::type::table: { - continue; + std::string Name = Key.as<std::string>(); + if (Name.starts_with("_")) + { + continue; + } + if (Name == "base") + { + continue; + } + Traverse(It.second.as<sol::table>(), Name, CmdLineResult); } - Traverse(It.second.as<sol::table>(), Name, CmdLineResult); - } - break; - default: - break; + break; + default: + break; + } } } } - } - void Touch(std::string_view Key) { UsedKeys.insert(std::string(Key)); } + void Touch(std::string_view Key) { UsedKeys.insert(std::string(Key)); } - void Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult) - { - for (auto It : OptionMap) + void Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult) { - if (CmdLineResult.count(It.second.CommandLineOptionName) != 0) + for (auto It : OptionMap) { - UsedKeys.insert(It.first); + if (CmdLineResult.count(It.second.CommandLineOptionName) != 0) + { + UsedKeys.insert(It.first); + } } - } - std::vector<std::string> SortedKeys(UsedKeys.begin(), UsedKeys.end()); - std::sort(SortedKeys.begin(), SortedKeys.end()); - auto GetTablePath = [](const std::string& Key) -> std::vector<std::string> { - std::vector<std::string> Path; - zen::ForEachStrTok(Key, '.', [&Path](std::string_view Part) { - Path.push_back(std::string(Part)); - return true; - }); - return Path; - }; - std::vector<std::string> CurrentTablePath; - std::string Indent; - auto It = SortedKeys.begin(); - for (const std::string& Key : SortedKeys) - { - std::vector<std::string> KeyPath = GetTablePath(Key); - std::string Name = KeyPath.back(); - KeyPath.pop_back(); - if (CurrentTablePath != KeyPath) + std::vector<std::string> SortedKeys(UsedKeys.begin(), UsedKeys.end()); + std::sort(SortedKeys.begin(), SortedKeys.end()); + auto GetTablePath = [](const std::string& Key) -> std::vector<std::string> { + std::vector<std::string> Path; + zen::ForEachStrTok(Key, '.', [&Path](std::string_view Part) { + Path.push_back(std::string(Part)); + return true; + }); + return Path; + }; + std::vector<std::string> CurrentTablePath; + std::string Indent; + auto It = SortedKeys.begin(); + for (const std::string& Key : SortedKeys) { - size_t EqualCount = 0; - while (EqualCount < CurrentTablePath.size() && EqualCount < KeyPath.size() && - CurrentTablePath[EqualCount] == KeyPath[EqualCount]) - { - EqualCount++; - } - while (CurrentTablePath.size() > EqualCount) + std::vector<std::string> KeyPath = GetTablePath(Key); + std::string Name = KeyPath.back(); + KeyPath.pop_back(); + if (CurrentTablePath != KeyPath) { - CurrentTablePath.pop_back(); - Indent.pop_back(); - SB.Append(Indent); - SB.Append("}"); - if (CurrentTablePath.size() == EqualCount && !Indent.empty() && KeyPath.size() >= EqualCount) + size_t EqualCount = 0; + while (EqualCount < CurrentTablePath.size() && EqualCount < KeyPath.size() && + CurrentTablePath[EqualCount] == KeyPath[EqualCount]) { - SB.Append(","); + EqualCount++; } - SB.Append("\n"); - if (Indent.empty()) + while (CurrentTablePath.size() > EqualCount) { + CurrentTablePath.pop_back(); + Indent.pop_back(); + SB.Append(Indent); + SB.Append("}"); + if (CurrentTablePath.size() == EqualCount && !Indent.empty() && KeyPath.size() >= EqualCount) + { + SB.Append(","); + } SB.Append("\n"); + if (Indent.empty()) + { + SB.Append("\n"); + } + } + while (EqualCount < KeyPath.size()) + { + SB.Append(Indent); + SB.Append(KeyPath[EqualCount]); + SB.Append(" = {\n"); + Indent.push_back('\t'); + CurrentTablePath.push_back(KeyPath[EqualCount]); + EqualCount++; } } - while (EqualCount < KeyPath.size()) - { - SB.Append(Indent); - SB.Append(KeyPath[EqualCount]); - SB.Append(" = {\n"); - Indent.push_back('\t'); - CurrentTablePath.push_back(KeyPath[EqualCount]); - EqualCount++; - } - } - SB.Append(Indent); - SB.Append(Name); - SB.Append(" = "); - OptionMap[Key].Value->Print(Indent, SB); - SB.Append(",\n"); - } - while (!CurrentTablePath.empty()) - { - Indent.pop_back(); - SB.Append(Indent); - SB.Append("}\n"); - CurrentTablePath.pop_back(); + SB.Append(Indent); + SB.Append(Name); + SB.Append(" = "); + OptionMap[Key].Value->Print(Indent, SB); + SB.Append(",\n"); + } + while (!CurrentTablePath.empty()) + { + Indent.pop_back(); + SB.Append(Indent); + SB.Append("}\n"); + CurrentTablePath.pop_back(); + } } - } -private: - void Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult) - { - for (auto It : Table) + private: + void Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult) { - sol::object Key = It.first; - sol::type KeyType = Key.get_type(); - if (KeyType == sol::type::string || KeyType == sol::type::number) + for (auto It : Table) { - sol::type ValueType = It.second.get_type(); - switch (ValueType) + sol::object Key = It.first; + sol::type KeyType = Key.get_type(); + if (KeyType == sol::type::string || KeyType == sol::type::number) { - case sol::type::table: - case sol::type::string: - case sol::type::number: - case sol::type::boolean: - { - std::string Name = Key.as<std::string>(); - if (Name.starts_with("_")) - { - continue; - } - Name = std::string(PathPrefix) + "." + Key.as<std::string>(); - auto OptionIt = OptionMap.find(Name); - if (OptionIt != OptionMap.end()) + sol::type ValueType = It.second.get_type(); + switch (ValueType) + { + case sol::type::table: + case sol::type::string: + case sol::type::number: + case sol::type::boolean: { - UsedKeys.insert(Name); - if (CmdLineResult.count(OptionIt->second.CommandLineOptionName) != 0) + std::string Name = Key.as<std::string>(); + if (Name.starts_with("_")) { continue; } - OptionIt->second.Value->Parse(It.second); - continue; - } - if (ValueType == sol::type::table) - { - if (Name == "base") + Name = std::string(PathPrefix) + "." + Key.as<std::string>(); + auto OptionIt = OptionMap.find(Name); + if (OptionIt != OptionMap.end()) { + UsedKeys.insert(Name); + if (CmdLineResult.count(OptionIt->second.CommandLineOptionName) != 0) + { + continue; + } + OptionIt->second.Value->Parse(It.second); continue; } - Traverse(It.second.as<sol::table>(), Name, CmdLineResult); + if (ValueType == sol::type::table) + { + if (Name == "base") + { + continue; + } + Traverse(It.second.as<sol::table>(), Name, CmdLineResult); + } } - } - break; - default: - break; + break; + default: + break; + } } } } - } - std::unordered_map<std::string, Option> OptionMap; - std::unordered_set<std::string> UsedKeys; -}; + std::unordered_map<std::string, Option> OptionMap; + std::unordered_set<std::string> UsedKeys; + }; } // namespace LuaConfig @@ -1508,3 +1494,5 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions) ServerOptions.AbsLogFile = ServerOptions.DataDir / "logs" / "zenserver.log"; } } + +} // namespace zen diff --git a/src/zenserver/config.h b/src/zenserver/config.h index 8e2ffe74c..3e80ba10f 100644 --- a/src/zenserver/config.h +++ b/src/zenserver/config.h @@ -8,6 +8,8 @@ #include <string> #include <vector> +namespace zen { + struct ZenUpstreamJupiterConfig { std::string Name; @@ -164,3 +166,5 @@ struct ZenServerOptions }; void ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions); + +} // namespace zen diff --git a/src/zenserver/diag/logging.cpp b/src/zenserver/diag/logging.cpp index 29c8eec4c..07e34305c 100644 --- a/src/zenserver/diag/logging.cpp +++ b/src/zenserver/diag/logging.cpp @@ -27,7 +27,7 @@ ZEN_THIRD_PARTY_INCLUDES_END // Custom logging -- test code, this should be tweaked -namespace logging { +namespace zen::logging { using namespace spdlog; using namespace spdlog::details; @@ -317,7 +317,9 @@ private: std::string m_LogId; }; -} // namespace logging +} // namespace zen::logging + +namespace zen { void InitializeLogging(const ZenServerOptions& GlobalOptions) @@ -500,3 +502,5 @@ ShutdownLogging() DefaultLogger.info("log ending at {}", zen::DateTime::Now().ToIso8601()); zen::logging::ShutdownLogging(); } + +} // namespace zen diff --git a/src/zenserver/diag/logging.h b/src/zenserver/diag/logging.h index 8df49f842..91b74a094 100644 --- a/src/zenserver/diag/logging.h +++ b/src/zenserver/diag/logging.h @@ -3,8 +3,12 @@ #pragma once #include <zencore/logging.h> + +namespace zen { + struct ZenServerOptions; void InitializeLogging(const ZenServerOptions& GlobalOptions); - void ShutdownLogging(); + +} // namespace zen diff --git a/src/zenserver/main.cpp b/src/zenserver/main.cpp new file mode 100644 index 000000000..18b907534 --- /dev/null +++ b/src/zenserver/main.cpp @@ -0,0 +1,390 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "zenserver.h" + +#include <zencore/compactbinarybuilder.h> +#include <zencore/compactbinaryvalidation.h> +#include <zencore/config.h> +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> +#include <zencore/logging.h> +#include <zencore/scopeguard.h> +#include <zencore/session.h> +#include <zencore/string.h> +#include <zencore/thread.h> +#include <zencore/timer.h> +#include <zencore/trace.h> +#include <zenhttp/httpserver.h> + +#include "config.h" +#include "diag/logging.h" +#include "sentryintegration.h" + +#if ZEN_USE_MIMALLOC +ZEN_THIRD_PARTY_INCLUDES_START +# include <mimalloc-new-delete.h> +# include <mimalloc.h> +ZEN_THIRD_PARTY_INCLUDES_END +#endif + +#if ZEN_PLATFORM_WINDOWS +# include <zencore/windows.h> +# include "windows/service.h" +#endif + +////////////////////////////////////////////////////////////////////////// +// We don't have any doctest code in this file but this is needed to bring +// in some shared code into the executable + +#if ZEN_WITH_TESTS +# define ZEN_TEST_WITH_RUNNER 1 +# include <zencore/testing.h> +#endif + +#include <memory> + +namespace zen::utils { +std::atomic_uint32_t SignalCounter[NSIG] = {0}; + +static void +SignalCallbackHandler(int SigNum) +{ + if (SigNum >= 0 && SigNum < NSIG) + { + SignalCounter[SigNum].fetch_add(1); + } +} +} // namespace zen::utils + +namespace zen { + +using namespace std::literals; + +//////////////////////////////////////////////////////////////////////////////// + +class ZenEntryPoint +{ +public: + ZenEntryPoint(ZenServerOptions& ServerOptions); + ZenEntryPoint(const ZenEntryPoint&) = delete; + ZenEntryPoint& operator=(const ZenEntryPoint&) = delete; + int Run(); + +private: + ZenServerOptions& m_ServerOptions; + LockFile m_LockFile; +}; + +ZenEntryPoint::ZenEntryPoint(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions) +{ +} + +int +ZenEntryPoint::Run() +{ +#if ZEN_USE_SENTRY + SentryIntegration Sentry; + + if (m_ServerOptions.NoSentry == false) + { + std::string SentryDatabasePath = PathToUtf8(m_ServerOptions.DataDir / ".sentry-native"); + std::string SentryAttachmentPath = PathToUtf8(m_ServerOptions.AbsLogFile); + + Sentry.Initialize(SentryDatabasePath, SentryAttachmentPath, m_ServerOptions.SentryAllowPII); + } +#endif + + try + { + // Mutual exclusion and synchronization + ZenServerState ServerState; + ServerState.Initialize(); + ServerState.Sweep(); + + ZenServerState::ZenServerEntry* Entry = ServerState.Lookup(m_ServerOptions.BasePort); + + if (Entry) + { + if (m_ServerOptions.OwnerPid) + { + ZEN_INFO( + "Looks like there is already a process listening to this port {} (pid: {}), attaching owner pid {} to running instance", + m_ServerOptions.BasePort, + Entry->Pid.load(), + m_ServerOptions.OwnerPid); + + Entry->AddSponsorProcess(m_ServerOptions.OwnerPid); + + std::exit(0); + } + else + { + ZEN_WARN("Exiting since there is already a process listening to port {} (pid: {})", + m_ServerOptions.BasePort, + Entry->Pid.load()); + std::exit(1); + } + } + + std::error_code Ec; + + std::filesystem::path LockFilePath = m_ServerOptions.DataDir / ".lock"; + + bool IsReady = false; + + auto MakeLockData = [&] { + CbObjectWriter Cbo; + Cbo << "pid" << GetCurrentProcessId() << "data" << PathToUtf8(m_ServerOptions.DataDir) << "port" << m_ServerOptions.BasePort + << "session_id" << GetSessionId() << "ready" << IsReady; + return Cbo.Save(); + }; + + m_LockFile.Create(LockFilePath, MakeLockData(), Ec); + + if (Ec) + { + ZEN_WARN("ERROR: Unable to grab lock at '{}' (error: '{}')", LockFilePath, Ec.message()); + + std::exit(99); + } + + InitializeLogging(m_ServerOptions); + +#if ZEN_USE_SENTRY + Sentry.LogStartupInformation(); +#endif + + MaximizeOpenFileCount(); + + ZEN_INFO(ZEN_APP_NAME " - using lock file at '{}'", LockFilePath); + + ZEN_INFO(ZEN_APP_NAME " - starting on port {}, version '{}'", m_ServerOptions.BasePort, ZEN_CFG_VERSION_BUILD_STRING_FULL); + + Entry = ServerState.Register(m_ServerOptions.BasePort); + + if (m_ServerOptions.OwnerPid) + { + Entry->AddSponsorProcess(m_ServerOptions.OwnerPid); + } + + ZenServer Server; + Server.SetDataRoot(m_ServerOptions.DataDir); + Server.SetContentRoot(m_ServerOptions.ContentDir); + Server.SetTestMode(m_ServerOptions.IsTest); + Server.SetDedicatedMode(m_ServerOptions.IsDedicated); + + auto ServerCleanup = MakeGuard([&Server] { Server.Cleanup(); }); + + int EffectiveBasePort = Server.Initialize(m_ServerOptions, Entry); + + Entry->EffectiveListenPort = uint16_t(EffectiveBasePort); + if (EffectiveBasePort != m_ServerOptions.BasePort) + { + ZEN_INFO(ZEN_APP_NAME " - relocated to base port {}", EffectiveBasePort); + m_ServerOptions.BasePort = EffectiveBasePort; + } + + std::unique_ptr<std::thread> ShutdownThread; + std::unique_ptr<NamedEvent> ShutdownEvent; + + ExtendableStringBuilder<64> ShutdownEventName; + ShutdownEventName << "Zen_" << m_ServerOptions.BasePort << "_Shutdown"; + ShutdownEvent.reset(new NamedEvent{ShutdownEventName}); + + // Monitor shutdown signals + + ShutdownThread.reset(new std::thread{[&] { + SetCurrentThreadName("shutdown_monitor"); + + ZEN_INFO("shutdown monitor thread waiting for shutdown signal '{}'", ShutdownEventName); + + if (ShutdownEvent->Wait()) + { + if (!IsApplicationExitRequested()) + { + ZEN_INFO("shutdown signal received"); + Server.RequestExit(0); + } + } + else + { + ZEN_INFO("shutdown signal wait() failed"); + } + }}); + + auto CleanupShutdown = MakeGuard([&ShutdownEvent, &ShutdownThread] { + if (ShutdownEvent) + { + ShutdownEvent->Set(); + } + if (ShutdownThread && ShutdownThread->joinable()) + { + ShutdownThread->join(); + } + }); + + // If we have a parent process, establish the mechanisms we need + // to be able to communicate readiness with the parent + + Server.SetIsReadyFunc([&] { + IsReady = true; + + m_LockFile.Update(MakeLockData(), Ec); + + if (!m_ServerOptions.ChildId.empty()) + { + NamedEvent ParentEvent{m_ServerOptions.ChildId}; + ParentEvent.Set(); + } + }); + + Server.Run(); + } + catch (std::exception& e) + { + SPDLOG_CRITICAL("Caught exception in main: {}", e.what()); + if (!IsApplicationExitRequested()) + { + RequestApplicationExit(1); + } + } + + ShutdownLogging(); + + return ApplicationExitCode(); +} + +////////////////////////////////////////////////////////////////////////// + +#if ZEN_PLATFORM_WINDOWS + +class ZenWindowsService : public WindowsService +{ +public: + ZenWindowsService(ZenServerOptions& ServerOptions) : m_EntryPoint(ServerOptions) {} + + ZenWindowsService(const ZenWindowsService&) = delete; + ZenWindowsService& operator=(const ZenWindowsService&) = delete; + + virtual int Run() override; + +private: + ZenEntryPoint m_EntryPoint; +}; + +int +ZenWindowsService::Run() +{ + return m_EntryPoint.Run(); +} + +#endif // ZEN_PLATFORM_WINDOWS + +} // namespace zen + +////////////////////////////////////////////////////////////////////////// + +#if ZEN_WITH_TESTS +int +test_main(int argc, char** argv) +{ + zen::zencore_forcelinktests(); + zen::zenhttp_forcelinktests(); + zen::zenstore_forcelinktests(); + zen::z$_forcelink(); + zen::z$service_forcelink(); + + zen::logging::InitializeLogging(); + spdlog::set_level(spdlog::level::debug); + + zen::MaximizeOpenFileCount(); + + return ZEN_RUN_TESTS(argc, argv); +} +#endif + +int +main(int argc, char* argv[]) +{ + using namespace zen; + +#if ZEN_USE_MIMALLOC + mi_version(); +#endif + + if (argc >= 2) + { + if (argv[1] == "test"sv) + { +#if ZEN_WITH_TESTS + return test_main(argc, argv); +#else + fprintf(stderr, "test option not available in release mode!\n"); + exit(5); +#endif + } + } + + signal(SIGINT, utils::SignalCallbackHandler); + + try + { + ZenServerOptions ServerOptions; + ParseCliOptions(argc, argv, ServerOptions); + + if (!std::filesystem::exists(ServerOptions.DataDir)) + { + ServerOptions.IsFirstRun = true; + std::filesystem::create_directories(ServerOptions.DataDir); + } + +#if ZEN_WITH_TRACE + if (ServerOptions.TraceHost.size()) + { + TraceStart(ServerOptions.TraceHost.c_str(), TraceType::Network); + } + else if (ServerOptions.TraceFile.size()) + { + TraceStart(ServerOptions.TraceFile.c_str(), TraceType::File); + } + else + { + TraceInit(); + } + atexit(TraceShutdown); +#endif // ZEN_WITH_TRACE + +#if ZEN_PLATFORM_WINDOWS + if (ServerOptions.InstallService) + { + WindowsService::Install(); + + std::exit(0); + } + + if (ServerOptions.UninstallService) + { + WindowsService::Delete(); + + std::exit(0); + } + + ZenWindowsService App(ServerOptions); + return App.ServiceMain(); +#else + if (ServerOptions.InstallService || ServerOptions.UninstallService) + { + throw std::runtime_error("Service mode is not supported on this platform"); + } + + ZenEntryPoint App(ServerOptions); + return App.Run(); +#endif + } + catch (std::exception& Ex) + { + fprintf(stderr, "ERROR: Caught exception in main: '%s'", Ex.what()); + + return 1; + } +} diff --git a/src/zenserver/sentryintegration.cpp b/src/zenserver/sentryintegration.cpp new file mode 100644 index 000000000..2c51c3b36 --- /dev/null +++ b/src/zenserver/sentryintegration.cpp @@ -0,0 +1,288 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "sentryintegration.h" + +#include <zencore/config.h> +#include <zencore/logging.h> + +#include <stdarg.h> +#include <stdio.h> + +#if ZEN_PLATFORM_LINUX +# include <pwd.h> +#endif + +#if ZEN_PLATFORM_MAC +# include <pwd.h> +#endif + +#if ZEN_USE_SENTRY +# define SENTRY_BUILD_STATIC 1 +ZEN_THIRD_PARTY_INCLUDES_START +# include <sentry.h> +# include <spdlog/sinks/base_sink.h> +ZEN_THIRD_PARTY_INCLUDES_END + +namespace sentry { + +struct SentryAssertImpl : zen::AssertImpl +{ + ZEN_FORCENOINLINE ZEN_DEBUG_SECTION SentryAssertImpl(); + virtual ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ~SentryAssertImpl(); + virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION OnAssert(const char* Filename, + int LineNumber, + const char* FunctionName, + const char* Msg) override; + AssertImpl* PrevAssertImpl; +}; + +class sentry_sink final : public spdlog::sinks::base_sink<spdlog::details::null_mutex> +{ +public: + sentry_sink(); + ~sentry_sink(); + +protected: + void sink_it_(const spdlog::details::log_msg& msg) override; + void flush_() override; +}; + +////////////////////////////////////////////////////////////////////////// + +static constexpr sentry_level_t MapToSentryLevel[spdlog::level::level_enum::n_levels] = {SENTRY_LEVEL_DEBUG, + SENTRY_LEVEL_DEBUG, + SENTRY_LEVEL_INFO, + SENTRY_LEVEL_WARNING, + SENTRY_LEVEL_ERROR, + SENTRY_LEVEL_FATAL, + SENTRY_LEVEL_DEBUG}; + +sentry_sink::sentry_sink() +{ +} +sentry_sink::~sentry_sink() +{ +} + +void +sentry_sink::sink_it_(const spdlog::details::log_msg& msg) +{ + try + { + std::string Message = fmt::format("{}\n{}({}) [{}]", msg.payload, msg.source.filename, msg.source.line, msg.source.funcname); + sentry_value_t event = sentry_value_new_message_event( + /* level */ MapToSentryLevel[msg.level], + /* logger */ nullptr, + /* message */ Message.c_str()); + sentry_event_value_add_stacktrace(event, NULL, 0); + sentry_capture_event(event); + } + catch (std::exception&) + { + // If our logging with Message formatting fails we do a non-allocating version and just post the msg.payload raw + char TmpBuffer[256]; + size_t MaxCopy = zen::Min<size_t>(msg.payload.size(), size_t(255)); + memcpy(TmpBuffer, msg.payload.data(), MaxCopy); + TmpBuffer[MaxCopy] = '\0'; + sentry_value_t event = sentry_value_new_message_event( + /* level */ SENTRY_LEVEL_ERROR, + /* logger */ nullptr, + /* message */ TmpBuffer); + sentry_event_value_add_stacktrace(event, NULL, 0); + sentry_capture_event(event); + } +} +void +sentry_sink::flush_() +{ +} + +SentryAssertImpl::SentryAssertImpl() : PrevAssertImpl(CurrentAssertImpl) +{ + CurrentAssertImpl = this; +} + +SentryAssertImpl::~SentryAssertImpl() +{ + CurrentAssertImpl = PrevAssertImpl; +} + +void +SentryAssertImpl::OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg) +{ + try + { + std::string Message = fmt::format("ASSERT {}:({}) [{}]\n\"{}\"", Filename, LineNumber, FunctionName, Msg); + sentry_value_t event = sentry_value_new_message_event( + /* level */ SENTRY_LEVEL_ERROR, + /* logger */ nullptr, + /* message */ Message.c_str()); + sentry_event_value_add_stacktrace(event, NULL, 0); + sentry_capture_event(event); + } + catch (std::exception&) + { + // If our logging with Message formatting fails we do a non-allocating version and just post the Msg raw + sentry_value_t event = sentry_value_new_message_event( + /* level */ SENTRY_LEVEL_ERROR, + /* logger */ nullptr, + /* message */ Msg); + sentry_event_value_add_stacktrace(event, NULL, 0); + sentry_capture_event(event); + } +} + +} // namespace sentry + +namespace zen { + +# if ZEN_USE_SENTRY +static void +SentryLogFunction(sentry_level_t Level, const char* Message, va_list Args, [[maybe_unused]] void* Userdata) +{ + char LogMessageBuffer[160]; + std::string LogMessage; + const char* MessagePtr = LogMessageBuffer; + + int n = vsnprintf(LogMessageBuffer, sizeof LogMessageBuffer, Message, Args); + + if (n >= int(sizeof LogMessageBuffer)) + { + LogMessage.resize(n + 1); + + n = vsnprintf(LogMessage.data(), LogMessage.size(), Message, Args); + + MessagePtr = LogMessage.c_str(); + } + + switch (Level) + { + case SENTRY_LEVEL_DEBUG: + ConsoleLog().debug("sentry: {}", MessagePtr); + break; + + case SENTRY_LEVEL_INFO: + ConsoleLog().info("sentry: {}", MessagePtr); + break; + + case SENTRY_LEVEL_WARNING: + ConsoleLog().warn("sentry: {}", MessagePtr); + break; + + case SENTRY_LEVEL_ERROR: + ConsoleLog().error("sentry: {}", MessagePtr); + break; + + case SENTRY_LEVEL_FATAL: + ConsoleLog().critical("sentry: {}", MessagePtr); + break; + } +} +# endif + +SentryIntegration::SentryIntegration() +{ +} + +SentryIntegration::~SentryIntegration() +{ + if (m_IsInitialized && m_SentryErrorCode == 0) + { + logging::SetErrorLog(std::shared_ptr<spdlog::logger>()); + m_SentryAssert.reset(); + sentry_close(); + } +} + +void +SentryIntegration::Initialize(std::string SentryDatabasePath, std::string SentryAttachmentPath, bool AllowPII) +{ + m_AllowPII = AllowPII; + + if (SentryDatabasePath.starts_with("\\\\?\\")) + { + SentryDatabasePath = SentryDatabasePath.substr(4); + } + sentry_options_t* SentryOptions = sentry_options_new(); + sentry_options_set_dsn(SentryOptions, "https://[email protected]/5919284"); + sentry_options_set_database_path(SentryOptions, SentryDatabasePath.c_str()); + sentry_options_set_logger(SentryOptions, SentryLogFunction, this); + if (SentryAttachmentPath.starts_with("\\\\?\\")) + { + SentryAttachmentPath = SentryAttachmentPath.substr(4); + } + sentry_options_add_attachment(SentryOptions, SentryAttachmentPath.c_str()); + sentry_options_set_release(SentryOptions, ZEN_CFG_VERSION); + + // sentry_options_set_debug(SentryOptions, 1); + + m_SentryErrorCode = sentry_init(SentryOptions); + + if (m_SentryErrorCode == 0) + { + if (m_AllowPII) + { +# if ZEN_PLATFORM_WINDOWS + CHAR UserNameBuffer[511 + 1]; + DWORD UserNameLength = sizeof(UserNameBuffer) / sizeof(CHAR); + BOOL OK = GetUserNameA(UserNameBuffer, &UserNameLength); + if (OK && UserNameLength) + { + m_SentryUserName = std::string(UserNameBuffer, UserNameLength - 1); + } +# endif // ZEN_PLATFORM_WINDOWS +# if (ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC) + uid_t uid = geteuid(); + struct passwd* pw = getpwuid(uid); + if (pw) + { + m_SentryUserName = std::string(pw->pw_name); + } +# endif + sentry_value_t SentryUserObject = sentry_value_new_object(); + sentry_value_set_by_key(SentryUserObject, "id", sentry_value_new_string(m_SentryUserName.c_str())); + sentry_value_set_by_key(SentryUserObject, "username", sentry_value_new_string(m_SentryUserName.c_str())); + sentry_value_set_by_key(SentryUserObject, "ip_address", sentry_value_new_string("{{auto}}")); + sentry_set_user(SentryUserObject); + } + + auto SentrySink = spdlog::create<sentry::sentry_sink>("sentry"); + logging::SetErrorLog(std::move(SentrySink)); + + m_SentryAssert = std::make_unique<sentry::SentryAssertImpl>(); + } + + m_IsInitialized = true; +} + +void +SentryIntegration::LogStartupInformation() +{ + if (m_IsInitialized) + { + if (m_SentryErrorCode == 0) + { + if (m_AllowPII) + { + ZEN_INFO("sentry initialized, username: '{}'", m_SentryUserName); + } + else + { + ZEN_INFO("sentry initialized with anonymous reports"); + } + } + else + { + ZEN_WARN("sentry_init returned failure! (error code: {})", m_SentryErrorCode); + } + } +} + +void +SentryIntegration::ClearCaches() +{ + sentry_clear_modulecache(); +} + +} // namespace zen +#endif diff --git a/src/zenserver/sentryintegration.h b/src/zenserver/sentryintegration.h new file mode 100644 index 000000000..f25cf5dce --- /dev/null +++ b/src/zenserver/sentryintegration.h @@ -0,0 +1,48 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/intmath.h> +#include <zencore/zencore.h> + +#if !defined(ZEN_USE_SENTRY) +# if ZEN_PLATFORM_MAC && ZEN_ARCH_ARM64 +// vcpkg's sentry-native port does not support Arm on Mac. +# define ZEN_USE_SENTRY 0 +# else +# define ZEN_USE_SENTRY 1 +# endif +#endif + +#if ZEN_USE_SENTRY + +# include <memory> + +namespace sentry { + +struct SentryAssertImpl; + +} // namespace sentry + +namespace zen { + +class SentryIntegration +{ +public: + SentryIntegration(); + ~SentryIntegration(); + + void Initialize(std::string SentryDatabasePath, std::string SentryAttachmentsPath, bool AllowPII); + void LogStartupInformation(); + static void ClearCaches(); + +private: + int m_SentryErrorCode = 0; + bool m_IsInitialized = false; + bool m_AllowPII = false; + std::unique_ptr<sentry::SentryAssertImpl> m_SentryAssert; + std::string m_SentryUserName; +}; + +} // namespace zen +#endif diff --git a/src/zenserver/xmake.lua b/src/zenserver/xmake.lua index 66bfe4ada..329435acb 100644 --- a/src/zenserver/xmake.lua +++ b/src/zenserver/xmake.lua @@ -22,6 +22,8 @@ target("zenserver") add_cxxflags("/bigobj") add_links("delayimp", "projectedfslib") add_ldflags("/delayload:ProjectedFSLib.dll") + + add_links("dbghelp", "winhttp", "version") -- for Sentry else remove_files("windows/**") end diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp index 0555fab79..a8273f6c9 100644 --- a/src/zenserver/zenserver.cpp +++ b/src/zenserver/zenserver.cpp @@ -1,5 +1,9 @@ // Copyright Epic Games, Inc. All Rights Reserved. +#include "zenserver.h" + +#include "sentryintegration.h" + #include <zencore/compactbinarybuilder.h> #include <zencore/compactbinaryvalidation.h> #include <zencore/config.h> @@ -34,13 +38,6 @@ # include <pwd.h> #endif -#if ZEN_USE_MIMALLOC -ZEN_THIRD_PARTY_INCLUDES_START -# include <mimalloc-new-delete.h> -# include <mimalloc.h> -ZEN_THIRD_PARTY_INCLUDES_END -#endif - ZEN_THIRD_PARTY_INCLUDES_START #include <fmt/format.h> #include <asio.hpp> @@ -48,181 +45,23 @@ ZEN_THIRD_PARTY_INCLUDES_START ZEN_THIRD_PARTY_INCLUDES_END #include <exception> -#include <list> -#include <optional> -#include <regex> #include <set> -#include <unordered_map> - -////////////////////////////////////////////////////////////////////////// -// We don't have any doctest code in this file but this is needed to bring -// in some shared code into the executable - -#if ZEN_WITH_TESTS -# define ZEN_TEST_WITH_RUNNER 1 -# include <zencore/testing.h> -#endif ////////////////////////////////////////////////////////////////////////// #include "config.h" #include "diag/logging.h" - -#if ZEN_PLATFORM_WINDOWS -# include "windows/service.h" -#endif - -////////////////////////////////////////////////////////////////////////// -// Sentry -// - -#if !defined(ZEN_USE_SENTRY) -# if ZEN_PLATFORM_MAC && ZEN_ARCH_ARM64 -// vcpkg's sentry-native port does not support Arm on Mac. -# define ZEN_USE_SENTRY 0 -# else -# define ZEN_USE_SENTRY 1 -# endif -#endif - -#if ZEN_USE_SENTRY -# define SENTRY_BUILD_STATIC 1 -ZEN_THIRD_PARTY_INCLUDES_START -# include <sentry.h> -# include <spdlog/sinks/base_sink.h> -ZEN_THIRD_PARTY_INCLUDES_END - -// Sentry currently does not automatically add all required Windows -// libraries to the linker when consumed via vcpkg - -# if ZEN_PLATFORM_WINDOWS -# pragma comment(lib, "sentry.lib") -# pragma comment(lib, "dbghelp.lib") -# pragma comment(lib, "winhttp.lib") -# pragma comment(lib, "version.lib") -# endif -#endif - -////////////////////////////////////////////////////////////////////////// -// Services -// - -#include <zenhttp/auth/authmgr.h> -#include <zenhttp/auth/authservice.h> -#include <zenhttp/diagsvcs.h> -#include <zenhttp/httpstats.h> -#include <zenhttp/httpstatus.h> -#include <zenhttp/httptest.h> -#include <zenstore/gc.h> -#include "admin/admin.h" -#include "cache/httpstructuredcache.h" -#include "cache/structuredcachestore.h" -#include "compute/function.h" -#include "frontend/frontend.h" -#include "httpcidstore.h" -#include "objectstore/objectstore.h" -#include "projectstore/httpprojectstore.h" -#include "projectstore/projectstore.h" -#include "upstream/upstream.h" -#include "vfs/vfsservice.h" - -#define ZEN_APP_NAME "Zen store" +#include "sentryintegration.h" namespace zen { -using namespace std::literals; - namespace utils { - static std::atomic_uint32_t SignalCounter[NSIG] = {0}; - - static void SignalCallbackHandler(int SigNum) - { - if (SigNum >= 0 && SigNum < NSIG) - { - SignalCounter[SigNum].fetch_add(1); - } - } - -#if ZEN_USE_SENTRY - class sentry_sink final : public spdlog::sinks::base_sink<spdlog::details::null_mutex> - { - public: - sentry_sink() {} - - protected: - static constexpr sentry_level_t MapToSentryLevel[spdlog::level::level_enum::n_levels] = {SENTRY_LEVEL_DEBUG, - SENTRY_LEVEL_DEBUG, - SENTRY_LEVEL_INFO, - SENTRY_LEVEL_WARNING, - SENTRY_LEVEL_ERROR, - SENTRY_LEVEL_FATAL, - SENTRY_LEVEL_DEBUG}; - - void sink_it_(const spdlog::details::log_msg& msg) override - { - try - { - std::string Message = - fmt::format("{}\n{}({}) [{}]", msg.payload, msg.source.filename, msg.source.line, msg.source.funcname); - sentry_value_t event = sentry_value_new_message_event( - /* level */ MapToSentryLevel[msg.level], - /* logger */ nullptr, - /* message */ Message.c_str()); - sentry_event_value_add_stacktrace(event, NULL, 0); - sentry_capture_event(event); - } - catch (std::exception&) - { - // If our logging with Message formatting fails we do a non-allocating version and just post the msg.payload raw - char TmpBuffer[256]; - size_t MaxCopy = Min<size_t>(msg.payload.size(), size_t(255)); - memcpy(TmpBuffer, msg.payload.data(), MaxCopy); - TmpBuffer[MaxCopy] = '\0'; - sentry_value_t event = sentry_value_new_message_event( - /* level */ SENTRY_LEVEL_ERROR, - /* logger */ nullptr, - /* message */ TmpBuffer); - sentry_event_value_add_stacktrace(event, NULL, 0); - sentry_capture_event(event); - } - } - void flush_() override {} - }; + extern std::atomic_uint32_t SignalCounter[NSIG]; +} - struct SentryAssertImpl : AssertImpl - { - ZEN_FORCENOINLINE ZEN_DEBUG_SECTION SentryAssertImpl() : PrevAssertImpl(CurrentAssertImpl) { CurrentAssertImpl = this; } - virtual ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ~SentryAssertImpl() { CurrentAssertImpl = PrevAssertImpl; } - virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION OnAssert(const char* Filename, - int LineNumber, - const char* FunctionName, - const char* Msg) - { - try - { - std::string Message = fmt::format("ASSERT {}:({}) [{}]\n\"{}\"", Filename, LineNumber, FunctionName, Msg); - sentry_value_t event = sentry_value_new_message_event( - /* level */ SENTRY_LEVEL_ERROR, - /* logger */ nullptr, - /* message */ Message.c_str()); - sentry_event_value_add_stacktrace(event, NULL, 0); - sentry_capture_event(event); - } - catch (std::exception&) - { - // If our logging with Message formatting fails we do a non-allocating version and just post the Msg raw - sentry_value_t event = sentry_value_new_message_event( - /* level */ SENTRY_LEVEL_ERROR, - /* logger */ nullptr, - /* message */ Msg); - sentry_event_value_add_stacktrace(event, NULL, 0); - sentry_capture_event(event); - } - } - AssertImpl* PrevAssertImpl; - }; -#endif +using namespace std::literals; +namespace utils { asio::error_code ResolveHostname(asio::io_context& Ctx, std::string_view Host, std::string_view DefaultPort, @@ -253,561 +92,232 @@ namespace utils { } } // namespace utils -class ZenServer : public IHttpStatusProvider -{ -public: - ~ZenServer() {} - - int Initialize(const ZenServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry) - { - m_UseSentry = ServerOptions.NoSentry == false; - m_ServerEntry = ServerEntry; - m_DebugOptionForcedCrash = ServerOptions.ShouldCrash; - const int ParentPid = ServerOptions.OwnerPid; - - if (ParentPid) - { - zen::ProcessHandle OwnerProcess; - OwnerProcess.Initialize(ParentPid); - - if (!OwnerProcess.IsValid()) - { - ZEN_WARN("Unable to initialize process handle for specified parent pid #{}", ParentPid); - - // If the pid is not reachable should we just shut down immediately? the intended owner process - // could have been killed or somehow crashed already - } - else - { - ZEN_INFO("Using parent pid #{} to control process lifetime", ParentPid); - } - - m_ProcessMonitor.AddPid(ParentPid); - } - - // Initialize/check mutex based on base port - - std::string MutexName = fmt::format("zen_{}", ServerOptions.BasePort); - - if (zen::NamedMutex::Exists(MutexName) || ((m_ServerMutex.Create(MutexName) == false))) - { - throw std::runtime_error(fmt::format("Failed to create mutex '{}' - is another instance already running?", MutexName).c_str()); - } - - InitializeState(ServerOptions); - - m_JobQueue = MakeJobQueue(8, "backgroundjobs"); - - m_HealthService.SetHealthInfo({.DataRoot = m_DataRoot, - .AbsLogPath = ServerOptions.AbsLogFile, - .HttpServerClass = std::string(ServerOptions.HttpServerConfig.ServerClass), - .BuildVersion = std::string(ZEN_CFG_VERSION_BUILD_STRING_FULL)}); - - // Ok so now we're configured, let's kick things off - - m_Http = zen::CreateHttpServer(ServerOptions.HttpServerConfig); - int EffectiveBasePort = m_Http->Initialize(ServerOptions.BasePort); - - // Setup authentication manager - { - std::string EncryptionKey = ServerOptions.EncryptionKey; - - if (EncryptionKey.empty()) - { - EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456"; - - ZEN_WARN("using default encryption key"); - } - - std::string EncryptionIV = ServerOptions.EncryptionIV; - - if (EncryptionIV.empty()) - { - EncryptionIV = "0123456789abcdef"; - - ZEN_WARN("using default encryption initialization vector"); - } - - m_AuthMgr = AuthMgr::Create({.RootDirectory = m_DataRoot / "auth", - .EncryptionKey = AesKey256Bit::FromString(EncryptionKey), - .EncryptionIV = AesIV128Bit::FromString(EncryptionIV)}); - - for (const ZenOpenIdProviderConfig& OpenIdProvider : ServerOptions.AuthConfig.OpenIdProviders) - { - m_AuthMgr->AddOpenIdProvider({.Name = OpenIdProvider.Name, .Url = OpenIdProvider.Url, .ClientId = OpenIdProvider.ClientId}); - } - } - - m_AuthService = std::make_unique<zen::HttpAuthService>(*m_AuthMgr); - m_Http->RegisterService(*m_AuthService); - - m_Http->RegisterService(m_HealthService); - m_Http->RegisterService(m_StatsService); - m_Http->RegisterService(m_StatusService); - m_StatusService.RegisterHandler("status", *this); +////////////////////////////////////////////////////////////////////////// - // Initialize storage and services +ZenServer::ZenServer() +{ +} - ZEN_INFO("initializing storage"); +ZenServer::~ZenServer() +{ +} - zen::CidStoreConfiguration Config; - Config.RootDirectory = m_DataRoot / "cas"; +void +ZenServer::OnReady() +{ + m_ServerEntry->SignalReady(); - m_CidStore = std::make_unique<zen::CidStore>(m_GcManager); - m_CidStore->Initialize(Config); - m_CidService.reset(new zen::HttpCidService{*m_CidStore}); + if (m_IsReadyFunc) + { + m_IsReadyFunc(); + } +} - ZEN_INFO("instantiating project service"); +int +ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry) +{ + m_UseSentry = ServerOptions.NoSentry == false; + m_ServerEntry = ServerEntry; + m_DebugOptionForcedCrash = ServerOptions.ShouldCrash; + const int ParentPid = ServerOptions.OwnerPid; - m_ProjectStore = new zen::ProjectStore(*m_CidStore, m_DataRoot / "projects", m_GcManager, *m_JobQueue); - m_HttpProjectService.reset(new zen::HttpProjectService{*m_CidStore, m_ProjectStore, m_StatsService, *m_AuthMgr}); + if (ParentPid) + { + ProcessHandle OwnerProcess; + OwnerProcess.Initialize(ParentPid); -#if ZEN_WITH_COMPUTE_SERVICES - if (ServerOptions.ComputeServiceEnabled) + if (!OwnerProcess.IsValid()) { - InitializeCompute(ServerOptions); - } - else - { - ZEN_INFO("NOT instantiating compute services"); - } -#endif // ZEN_WITH_COMPUTE_SERVICES + ZEN_WARN("Unable to initialize process handle for specified parent pid #{}", ParentPid); - if (ServerOptions.StructuredCacheConfig.Enabled) - { - InitializeStructuredCache(ServerOptions); + // If the pid is not reachable should we just shut down immediately? the intended owner process + // could have been killed or somehow crashed already } else { - ZEN_INFO("NOT instantiating structured cache service"); + ZEN_INFO("Using parent pid #{} to control process lifetime", ParentPid); } - m_Http->RegisterService(m_TestService); // NOTE: this is intentionally not limited to test mode as it's useful for diagnostics - -#if ZEN_WITH_TESTS - m_Http->RegisterService(m_TestingService); -#endif + m_ProcessMonitor.AddPid(ParentPid); + } - if (m_HttpProjectService) - { - m_Http->RegisterService(*m_HttpProjectService); - } + // Initialize/check mutex based on base port - m_Http->RegisterService(*m_CidService); + std::string MutexName = fmt::format("zen_{}", ServerOptions.BasePort); -#if ZEN_WITH_COMPUTE_SERVICES - if (ServerOptions.ComputeServiceEnabled) - { - if (m_HttpFunctionService != nullptr) - { - m_Http->RegisterService(*m_HttpFunctionService); - } - } -#endif // ZEN_WITH_COMPUTE_SERVICES + if (NamedMutex::Exists(MutexName) || ((m_ServerMutex.Create(MutexName) == false))) + { + throw std::runtime_error(fmt::format("Failed to create mutex '{}' - is another instance already running?", MutexName).c_str()); + } - m_FrontendService = std::make_unique<HttpFrontendService>(m_ContentRoot); + InitializeState(ServerOptions); - if (m_FrontendService) - { - m_Http->RegisterService(*m_FrontendService); - } + m_JobQueue = MakeJobQueue(8, "backgroundjobs"); - if (ServerOptions.ObjectStoreEnabled) - { - ObjectStoreConfig ObjCfg; - ObjCfg.RootDirectory = m_DataRoot / "obj"; - ObjCfg.ServerPort = static_cast<uint16_t>(EffectiveBasePort); + m_HealthService.SetHealthInfo({.DataRoot = m_DataRoot, + .AbsLogPath = ServerOptions.AbsLogFile, + .HttpServerClass = std::string(ServerOptions.HttpServerConfig.ServerClass), + .BuildVersion = std::string(ZEN_CFG_VERSION_BUILD_STRING_FULL)}); - for (const auto& Bucket : ServerOptions.ObjectStoreConfig.Buckets) - { - ObjectStoreConfig::BucketConfig NewBucket{.Name = Bucket.Name}; - NewBucket.Directory = Bucket.Directory.empty() ? (ObjCfg.RootDirectory / Bucket.Name) : Bucket.Directory; - ObjCfg.Buckets.push_back(std::move(NewBucket)); - } + // Ok so now we're configured, let's kick things off - m_ObjStoreService = std::make_unique<HttpObjectStoreService>(std::move(ObjCfg)); - m_Http->RegisterService(*m_ObjStoreService); - } + m_Http = CreateHttpServer(ServerOptions.HttpServerConfig); + int EffectiveBasePort = m_Http->Initialize(ServerOptions.BasePort); - m_VfsService = std::make_unique<VfsService>(); - m_VfsService->AddService(Ref<ProjectStore>(m_ProjectStore)); - m_VfsService->AddService(Ref<ZenCacheStore>(m_CacheStore)); - m_Http->RegisterService(*m_VfsService); - - ZEN_INFO("initializing GC, enabled '{}', interval {}s, lightweight interval {}s", - ServerOptions.GcConfig.Enabled, - ServerOptions.GcConfig.IntervalSeconds, - ServerOptions.GcConfig.LightweightIntervalSeconds); - zen::GcSchedulerConfig GcConfig{ - .RootDirectory = m_DataRoot / "gc", - .MonitorInterval = std::chrono::seconds(ServerOptions.GcConfig.MonitorIntervalSeconds), - .Interval = std::chrono::seconds(ServerOptions.GcConfig.IntervalSeconds), - .MaxCacheDuration = std::chrono::seconds(ServerOptions.GcConfig.Cache.MaxDurationSeconds), - .MaxProjectStoreDuration = std::chrono::seconds(ServerOptions.GcConfig.ProjectStore.MaxDurationSeconds), - .CollectSmallObjects = ServerOptions.GcConfig.CollectSmallObjects, - .Enabled = ServerOptions.GcConfig.Enabled, - .DiskReserveSize = ServerOptions.GcConfig.DiskReserveSize, - .DiskSizeSoftLimit = ServerOptions.GcConfig.DiskSizeSoftLimit, - .MinimumFreeDiskSpaceToAllowWrites = ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites, - .LightweightInterval = std::chrono::seconds(ServerOptions.GcConfig.LightweightIntervalSeconds)}; - m_GcScheduler.Initialize(GcConfig); - - // Create and register admin interface last to make sure all is properly initialized - m_AdminService = - std::make_unique<HttpAdminService>(m_GcScheduler, - *m_JobQueue, - *m_CacheStore, - HttpAdminService::LogPaths{.AbsLogPath = ServerOptions.AbsLogFile, - .HttpLogPath = ServerOptions.DataDir / "logs" / "http.log", - .CacheLogPath = ServerOptions.DataDir / "logs" / "z$.log"}); - m_Http->RegisterService(*m_AdminService); - - return EffectiveBasePort; - } - - void InitializeState(const ZenServerOptions& ServerOptions); - void InitializeStructuredCache(const ZenServerOptions& ServerOptions); - void InitializeCompute(const ZenServerOptions& ServerOptions); - - void Run() + // Setup authentication manager { - // This is disabled for now, awaiting better scheduling - // - // ScrubStorage(); + std::string EncryptionKey = ServerOptions.EncryptionKey; - if (m_ProcessMonitor.IsActive()) + if (EncryptionKey.empty()) { - EnqueueTimer(); - } + EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456"; - if (!m_TestMode) - { - ZEN_INFO("__________ _________ __ "); - ZEN_INFO("\\____ /____ ____ / _____// |_ ___________ ____ "); - ZEN_INFO(" / // __ \\ / \\ \\_____ \\\\ __\\/ _ \\_ __ \\_/ __ \\ "); - ZEN_INFO(" / /\\ ___/| | \\ / \\| | ( <_> ) | \\/\\ ___/ "); - ZEN_INFO("/_______ \\___ >___| / /_______ /|__| \\____/|__| \\___ >"); - ZEN_INFO(" \\/ \\/ \\/ \\/ \\/ "); + ZEN_WARN("using default encryption key"); } - ZEN_INFO(ZEN_APP_NAME " now running (pid: {})", zen::GetCurrentProcessId()); + std::string EncryptionIV = ServerOptions.EncryptionIV; -#if ZEN_USE_SENTRY - ZEN_INFO("sentry crash handler {}", m_UseSentry ? "ENABLED" : "DISABLED"); - if (m_UseSentry) + if (EncryptionIV.empty()) { - sentry_clear_modulecache(); - } -#endif + EncryptionIV = "0123456789abcdef"; - if (m_DebugOptionForcedCrash) - { - ZEN_DEBUG_BREAK(); + ZEN_WARN("using default encryption initialization vector"); } - const bool IsInteractiveMode = zen::IsInteractiveSession() && !m_TestMode; - - SetNewState(kRunning); - - OnReady(); - - m_Http->Run(IsInteractiveMode); - - SetNewState(kShuttingDown); - - ZEN_INFO(ZEN_APP_NAME " exiting"); + m_AuthMgr = AuthMgr::Create({.RootDirectory = m_DataRoot / "auth", + .EncryptionKey = AesKey256Bit::FromString(EncryptionKey), + .EncryptionIV = AesIV128Bit::FromString(EncryptionIV)}); - Flush(); - } - - void RequestExit(int ExitCode) - { - RequestApplicationExit(ExitCode); - if (m_Http) + for (const ZenOpenIdProviderConfig& OpenIdProvider : ServerOptions.AuthConfig.OpenIdProviders) { - m_Http->RequestExit(); + m_AuthMgr->AddOpenIdProvider({.Name = OpenIdProvider.Name, .Url = OpenIdProvider.Url, .ClientId = OpenIdProvider.ClientId}); } } - void Cleanup() - { - ZEN_INFO(ZEN_APP_NAME " cleaning up"); - try - { - m_IoContext.stop(); - if (m_IoRunner.joinable()) - { - m_IoRunner.join(); - } + m_AuthService = std::make_unique<HttpAuthService>(*m_AuthMgr); + m_Http->RegisterService(*m_AuthService); - if (m_Http) - { - m_Http->Close(); - } - if (m_JobQueue) - { - m_JobQueue->Stop(); - } + m_Http->RegisterService(m_HealthService); + m_Http->RegisterService(m_StatsService); + m_Http->RegisterService(m_StatusService); + m_StatusService.RegisterHandler("status", *this); - m_GcScheduler.Shutdown(); - m_AdminService.reset(); - m_VfsService.reset(); - m_ObjStoreService.reset(); - m_FrontendService.reset(); + // Initialize storage and services - m_StructuredCacheService.reset(); - m_UpstreamService.reset(); - m_UpstreamCache.reset(); - m_CacheStore = {}; + ZEN_INFO("initializing storage"); -#if ZEN_WITH_COMPUTE_SERVICES - m_HttpFunctionService.reset(); -#endif // ZEN_WITH_COMPUTE_SERVICES + CidStoreConfiguration Config; + Config.RootDirectory = m_DataRoot / "cas"; - m_HttpProjectService.reset(); - m_ProjectStore = {}; - m_CidService.reset(); - m_CidStore.reset(); - m_AuthService.reset(); - m_AuthMgr.reset(); - m_Http = {}; - m_JobQueue.reset(); - } - catch (std::exception& Ex) - { - ZEN_ERROR("exception thrown during Cleanup() in {}: '{}'", ZEN_APP_NAME, Ex.what()); - } - } + m_CidStore = std::make_unique<CidStore>(m_GcManager); + m_CidStore->Initialize(Config); + m_CidService.reset(new HttpCidService{*m_CidStore}); - void SetDedicatedMode(bool State) { m_IsDedicatedMode = State; } - void SetTestMode(bool State) { m_TestMode = State; } - void SetDataRoot(std::filesystem::path Root) { m_DataRoot = Root; } - void SetContentRoot(std::filesystem::path Root) { m_ContentRoot = Root; } + ZEN_INFO("instantiating project service"); - std::function<void()> m_IsReadyFunc; - void SetIsReadyFunc(std::function<void()>&& IsReadyFunc) { m_IsReadyFunc = std::move(IsReadyFunc); } - void OnReady(); + m_ProjectStore = new ProjectStore(*m_CidStore, m_DataRoot / "projects", m_GcManager, *m_JobQueue); + m_HttpProjectService.reset(new HttpProjectService{*m_CidStore, m_ProjectStore, m_StatsService, *m_AuthMgr}); - void EnsureIoRunner() +#if ZEN_WITH_COMPUTE_SERVICES + if (ServerOptions.ComputeServiceEnabled) { - if (!m_IoRunner.joinable()) - { - m_IoRunner = std::thread{[this] { - zen::SetCurrentThreadName("timer_io"); - m_IoContext.run(); - }}; - } + InitializeCompute(ServerOptions); } - - void EnqueueTimer() + else { - m_PidCheckTimer.expires_after(std::chrono::seconds(1)); - m_PidCheckTimer.async_wait([this](const asio::error_code&) { CheckOwnerPid(); }); - - EnsureIoRunner(); + ZEN_INFO("NOT instantiating compute services"); } +#endif // ZEN_WITH_COMPUTE_SERVICES - void EnqueueStateMarkerTimer() + if (ServerOptions.StructuredCacheConfig.Enabled) { - m_StateMakerTimer.expires_after(std::chrono::seconds(5)); - m_StateMakerTimer.async_wait([this](const asio::error_code&) { CheckStateMarker(); }); - EnsureIoRunner(); + InitializeStructuredCache(ServerOptions); } - - void EnqueueSigIntTimer() + else { - m_SigIntTimer.expires_after(std::chrono::milliseconds(500)); - m_SigIntTimer.async_wait([this](const asio::error_code&) { CheckSigInt(); }); - EnsureIoRunner(); + ZEN_INFO("NOT instantiating structured cache service"); } - void CheckStateMarker() - { - std::filesystem::path StateMarkerPath = m_DataRoot / "state_marker"; - try - { - if (!std::filesystem::exists(StateMarkerPath)) - { - ZEN_WARN("state marker at {} has been deleted, exiting", StateMarkerPath); - RequestExit(1); - return; - } - } - catch (std::exception& Ex) - { - ZEN_WARN("state marker at {} could not be checked, reason: '{}'", StateMarkerPath, Ex.what()); - RequestExit(1); - return; - } - EnqueueStateMarkerTimer(); - } + m_Http->RegisterService(m_TestService); // NOTE: this is intentionally not limited to test mode as it's useful for diagnostics - void CheckSigInt() - { - if (utils::SignalCounter[SIGINT] > 0) - { - ZEN_INFO("SIGINT triggered (Ctrl+C), exiting"); - RequestExit(128 + SIGINT); - return; - } - EnqueueSigIntTimer(); - } +#if ZEN_WITH_TESTS + m_Http->RegisterService(m_TestingService); +#endif - void CheckOwnerPid() + if (m_HttpProjectService) { - // Pick up any new "owner" processes - - std::set<uint32_t> AddedPids; + m_Http->RegisterService(*m_HttpProjectService); + } - for (auto& PidEntry : m_ServerEntry->SponsorPids) - { - if (uint32_t ThisPid = PidEntry.load(std::memory_order_relaxed)) - { - if (PidEntry.compare_exchange_strong(ThisPid, 0)) - { - if (AddedPids.insert(ThisPid).second) - { - m_ProcessMonitor.AddPid(ThisPid); + m_Http->RegisterService(*m_CidService); - ZEN_INFO("added process with pid #{} as a sponsor process", ThisPid); - } - } - } - } - - if (m_ProcessMonitor.IsRunning()) +#if ZEN_WITH_COMPUTE_SERVICES + if (ServerOptions.ComputeServiceEnabled) + { + if (m_HttpFunctionService != nullptr) { - EnqueueTimer(); + m_Http->RegisterService(*m_HttpFunctionService); } - else - { - ZEN_INFO(ZEN_APP_NAME " exiting since sponsor processes are all gone"); - - RequestExit(0); - } - } - - void ScrubStorage() - { - Stopwatch Timer; - ZEN_INFO("Storage validation STARTING"); - - WorkerThreadPool ThreadPool{1}; - ScrubContext Ctx{ThreadPool}; - m_CidStore->ScrubStorage(Ctx); - m_ProjectStore->ScrubStorage(Ctx); - m_StructuredCacheService->ScrubStorage(Ctx); - - const uint64_t ElapsedTimeMs = Timer.GetElapsedTimeMs(); - - ZEN_INFO("Storage validation DONE in {}, ({} in {} chunks - {})", - NiceTimeSpanMs(ElapsedTimeMs), - NiceBytes(Ctx.ScrubbedBytes()), - Ctx.ScrubbedChunks(), - NiceByteRate(Ctx.ScrubbedBytes(), ElapsedTimeMs)); } +#endif // ZEN_WITH_COMPUTE_SERVICES - void Flush() - { - if (m_CidStore) - m_CidStore->Flush(); - - if (m_StructuredCacheService) - m_StructuredCacheService->Flush(); - - if (m_ProjectStore) - m_ProjectStore->Flush(); - } + m_FrontendService = std::make_unique<HttpFrontendService>(m_ContentRoot); - virtual void HandleStatusRequest(HttpServerRequest& Request) override + if (m_FrontendService) { - CbObjectWriter Cbo; - Cbo << "ok" << true; - Cbo << "state" << ToString(m_CurrentState); - Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); + m_Http->RegisterService(*m_FrontendService); } -private: - ZenServerState::ZenServerEntry* m_ServerEntry = nullptr; - bool m_IsDedicatedMode = false; - bool m_TestMode = false; - CbObject m_RootManifest; - std::filesystem::path m_DataRoot; - std::filesystem::path m_ContentRoot; - std::thread m_IoRunner; - asio::io_context m_IoContext; - asio::steady_timer m_PidCheckTimer{m_IoContext}; - asio::steady_timer m_StateMakerTimer{m_IoContext}; - asio::steady_timer m_SigIntTimer{m_IoContext}; - zen::ProcessMonitor m_ProcessMonitor; - zen::NamedMutex m_ServerMutex; - - enum ServerState + if (ServerOptions.ObjectStoreEnabled) { - kInitializing, - kRunning, - kShuttingDown - } m_CurrentState = kInitializing; + ObjectStoreConfig ObjCfg; + ObjCfg.RootDirectory = m_DataRoot / "obj"; + ObjCfg.ServerPort = static_cast<uint16_t>(EffectiveBasePort); - inline void SetNewState(ServerState NewState) { m_CurrentState = NewState; } - - std::string_view ToString(ServerState Value) - { - switch (Value) + for (const auto& Bucket : ServerOptions.ObjectStoreConfig.Buckets) { - case kInitializing: - return "initializing"sv; - case kRunning: - return "running"sv; - case kShuttingDown: - return "shutdown"sv; - default: - return "unknown"sv; + ObjectStoreConfig::BucketConfig NewBucket{.Name = Bucket.Name}; + NewBucket.Directory = Bucket.Directory.empty() ? (ObjCfg.RootDirectory / Bucket.Name) : Bucket.Directory; + ObjCfg.Buckets.push_back(std::move(NewBucket)); } + + m_ObjStoreService = std::make_unique<HttpObjectStoreService>(std::move(ObjCfg)); + m_Http->RegisterService(*m_ObjStoreService); } - zen::Ref<zen::HttpServer> m_Http; - std::unique_ptr<zen::AuthMgr> m_AuthMgr; - std::unique_ptr<zen::HttpAuthService> m_AuthService; - zen::HttpStatusService m_StatusService; - zen::HttpStatsService m_StatsService; - zen::GcManager m_GcManager; - zen::GcScheduler m_GcScheduler{m_GcManager}; - std::unique_ptr<zen::CidStore> m_CidStore; - Ref<zen::ZenCacheStore> m_CacheStore; - zen::HttpTestService m_TestService; -#if ZEN_WITH_TESTS - zen::HttpTestingService m_TestingService; -#endif - std::unique_ptr<zen::HttpCidService> m_CidService; - zen::RefPtr<zen::ProjectStore> m_ProjectStore; - std::unique_ptr<zen::HttpProjectService> m_HttpProjectService; - std::unique_ptr<zen::UpstreamCache> m_UpstreamCache; - std::unique_ptr<zen::HttpUpstreamService> m_UpstreamService; - std::unique_ptr<zen::HttpStructuredCacheService> m_StructuredCacheService; - zen::HttpHealthService m_HealthService; -#if ZEN_WITH_COMPUTE_SERVICES - std::unique_ptr<zen::HttpFunctionService> m_HttpFunctionService; -#endif // ZEN_WITH_COMPUTE_SERVICES - std::unique_ptr<zen::HttpFrontendService> m_FrontendService; - std::unique_ptr<zen::HttpObjectStoreService> m_ObjStoreService; - std::unique_ptr<zen::VfsService> m_VfsService; - std::unique_ptr<JobQueue> m_JobQueue; - std::unique_ptr<zen::HttpAdminService> m_AdminService; + m_VfsService = std::make_unique<VfsService>(); + m_VfsService->AddService(Ref<ProjectStore>(m_ProjectStore)); + m_VfsService->AddService(Ref<ZenCacheStore>(m_CacheStore)); + m_Http->RegisterService(*m_VfsService); - bool m_DebugOptionForcedCrash = false; - bool m_UseSentry = false; -}; + ZEN_INFO("initializing GC, enabled '{}', interval {}s, lightweight interval {}s", + ServerOptions.GcConfig.Enabled, + ServerOptions.GcConfig.IntervalSeconds, + ServerOptions.GcConfig.LightweightIntervalSeconds); + GcSchedulerConfig GcConfig{.RootDirectory = m_DataRoot / "gc", + .MonitorInterval = std::chrono::seconds(ServerOptions.GcConfig.MonitorIntervalSeconds), + .Interval = std::chrono::seconds(ServerOptions.GcConfig.IntervalSeconds), + .MaxCacheDuration = std::chrono::seconds(ServerOptions.GcConfig.Cache.MaxDurationSeconds), + .MaxProjectStoreDuration = std::chrono::seconds(ServerOptions.GcConfig.ProjectStore.MaxDurationSeconds), + .CollectSmallObjects = ServerOptions.GcConfig.CollectSmallObjects, + .Enabled = ServerOptions.GcConfig.Enabled, + .DiskReserveSize = ServerOptions.GcConfig.DiskReserveSize, + .DiskSizeSoftLimit = ServerOptions.GcConfig.DiskSizeSoftLimit, + .MinimumFreeDiskSpaceToAllowWrites = ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites, + .LightweightInterval = std::chrono::seconds(ServerOptions.GcConfig.LightweightIntervalSeconds)}; + m_GcScheduler.Initialize(GcConfig); -void -ZenServer::OnReady() -{ - m_ServerEntry->SignalReady(); + // Create and register admin interface last to make sure all is properly initialized + m_AdminService = + std::make_unique<HttpAdminService>(m_GcScheduler, + *m_JobQueue, + *m_CacheStore, + HttpAdminService::LogPaths{.AbsLogPath = ServerOptions.AbsLogFile, + .HttpLogPath = ServerOptions.DataDir / "logs" / "http.log", + .CacheLogPath = ServerOptions.DataDir / "logs" / "z$.log"}); + m_Http->RegisterService(*m_AdminService); - if (m_IsReadyFunc) - { - m_IsReadyFunc(); - } + return EffectiveBasePort; } void @@ -822,7 +332,7 @@ ZenServer::InitializeState(const ZenServerOptions& ServerOptions) bool UpdateManifest = false; std::filesystem::path ManifestPath = m_DataRoot / "root_manifest"; - FileContents ManifestData = zen::ReadFile(ManifestPath); + FileContents ManifestData = ReadFile(ManifestPath); if (ManifestData.ErrorCode) { @@ -938,7 +448,7 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) const ZenUpstreamCacheConfig& UpstreamConfig = ServerOptions.UpstreamCacheConfig; - zen::UpstreamCacheOptions UpstreamOptions; + UpstreamCacheOptions UpstreamOptions; UpstreamOptions.ReadUpstream = (uint8_t(ServerOptions.UpstreamCacheConfig.CachePolicy) & uint8_t(UpstreamCachePolicy::Read)) != 0; UpstreamOptions.WriteUpstream = (uint8_t(ServerOptions.UpstreamCacheConfig.CachePolicy) & uint8_t(UpstreamCachePolicy::Write)) != 0; @@ -947,7 +457,7 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) UpstreamOptions.ThreadCount = static_cast<uint32_t>(UpstreamConfig.UpstreamThreadCount); } - m_UpstreamCache = zen::UpstreamCache::Create(UpstreamOptions, *m_CacheStore, *m_CidStore); + m_UpstreamCache = UpstreamCache::Create(UpstreamOptions, *m_CacheStore, *m_CidStore); m_UpstreamService = std::make_unique<HttpUpstreamService>(*m_UpstreamCache, *m_AuthMgr); m_UpstreamCache->Initialize(); @@ -962,7 +472,7 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) { if (!Dns.empty()) { - const asio::error_code Err = zen::utils::ResolveHostname(m_IoContext, Dns, "1337"sv, ZenUrls); + const asio::error_code Err = utils::ResolveHostname(m_IoContext, Dns, "1337"sv, ZenUrls); if (Err) { ZEN_ERROR("resolve FAILED, reason '{}'", Err.message()); @@ -977,7 +487,7 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) { const auto ZenEndpointName = UpstreamConfig.ZenConfig.Name.empty() ? "Zen"sv : UpstreamConfig.ZenConfig.Name; - std::unique_ptr<zen::UpstreamEndpoint> ZenEndpoint = zen::UpstreamEndpoint::CreateZenEndpoint( + std::unique_ptr<UpstreamEndpoint> ZenEndpoint = UpstreamEndpoint::CreateZenEndpoint( {.Name = ZenEndpointName, .Urls = ZenUrls, .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), @@ -992,22 +502,20 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions) { std::string_view EndpointName = UpstreamConfig.JupiterConfig.Name.empty() ? "Jupiter"sv : UpstreamConfig.JupiterConfig.Name; - auto Options = - zen::CloudCacheClientOptions{.Name = EndpointName, - .ServiceUrl = UpstreamConfig.JupiterConfig.Url, - .DdcNamespace = UpstreamConfig.JupiterConfig.DdcNamespace, - .BlobStoreNamespace = UpstreamConfig.JupiterConfig.Namespace, - .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), - .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)}; + auto Options = CloudCacheClientOptions{.Name = EndpointName, + .ServiceUrl = UpstreamConfig.JupiterConfig.Url, + .DdcNamespace = UpstreamConfig.JupiterConfig.DdcNamespace, + .BlobStoreNamespace = UpstreamConfig.JupiterConfig.Namespace, + .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), + .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)}; - auto AuthConfig = zen::UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.JupiterConfig.OAuthUrl, - .OAuthClientId = UpstreamConfig.JupiterConfig.OAuthClientId, - .OAuthClientSecret = UpstreamConfig.JupiterConfig.OAuthClientSecret, - .OpenIdProvider = UpstreamConfig.JupiterConfig.OpenIdProvider, - .AccessToken = UpstreamConfig.JupiterConfig.AccessToken}; + auto AuthConfig = UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.JupiterConfig.OAuthUrl, + .OAuthClientId = UpstreamConfig.JupiterConfig.OAuthClientId, + .OAuthClientSecret = UpstreamConfig.JupiterConfig.OAuthClientSecret, + .OpenIdProvider = UpstreamConfig.JupiterConfig.OpenIdProvider, + .AccessToken = UpstreamConfig.JupiterConfig.AccessToken}; - std::unique_ptr<zen::UpstreamEndpoint> JupiterEndpoint = - zen::UpstreamEndpoint::CreateJupiterEndpoint(Options, AuthConfig, *m_AuthMgr); + std::unique_ptr<UpstreamEndpoint> JupiterEndpoint = UpstreamEndpoint::CreateJupiterEndpoint(Options, AuthConfig, *m_AuthMgr); m_UpstreamCache->RegisterEndpoint(std::move(JupiterEndpoint)); } @@ -1039,37 +547,37 @@ ZenServer::InitializeCompute(const ZenServerOptions& ServerOptions) std::string_view EndpointName = UpstreamConfig.HordeConfig.Name.empty() ? "Horde"sv : UpstreamConfig.HordeConfig.Name; auto ComputeOptions = - zen::CloudCacheClientOptions{.Name = EndpointName, - .ServiceUrl = UpstreamConfig.HordeConfig.Url, - .ComputeCluster = UpstreamConfig.HordeConfig.Cluster, - .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), - .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)}; - - auto ComputeAuthConfig = zen::UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.HordeConfig.OAuthUrl, - .OAuthClientId = UpstreamConfig.HordeConfig.OAuthClientId, - .OAuthClientSecret = UpstreamConfig.HordeConfig.OAuthClientSecret, - .OpenIdProvider = UpstreamConfig.HordeConfig.OpenIdProvider, - .AccessToken = UpstreamConfig.HordeConfig.AccessToken}; + CloudCacheClientOptions{.Name = EndpointName, + .ServiceUrl = UpstreamConfig.HordeConfig.Url, + .ComputeCluster = UpstreamConfig.HordeConfig.Cluster, + .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), + .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)}; + + auto ComputeAuthConfig = UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.HordeConfig.OAuthUrl, + .OAuthClientId = UpstreamConfig.HordeConfig.OAuthClientId, + .OAuthClientSecret = UpstreamConfig.HordeConfig.OAuthClientSecret, + .OpenIdProvider = UpstreamConfig.HordeConfig.OpenIdProvider, + .AccessToken = UpstreamConfig.HordeConfig.AccessToken}; auto StorageOptions = - zen::CloudCacheClientOptions{.Name = EndpointName, - .ServiceUrl = UpstreamConfig.HordeConfig.StorageUrl, - .BlobStoreNamespace = UpstreamConfig.HordeConfig.Namespace, - .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), - .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)}; - - auto StorageAuthConfig = zen::UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.HordeConfig.StorageOAuthUrl, - .OAuthClientId = UpstreamConfig.HordeConfig.StorageOAuthClientId, - .OAuthClientSecret = UpstreamConfig.HordeConfig.StorageOAuthClientSecret, - .OpenIdProvider = UpstreamConfig.HordeConfig.StorageOpenIdProvider, - .AccessToken = UpstreamConfig.HordeConfig.StorageAccessToken}; - - m_HttpFunctionService = std::make_unique<zen::HttpFunctionService>(*m_CidStore, - ComputeOptions, - StorageOptions, - ComputeAuthConfig, - StorageAuthConfig, - *m_AuthMgr); + CloudCacheClientOptions{.Name = EndpointName, + .ServiceUrl = UpstreamConfig.HordeConfig.StorageUrl, + .BlobStoreNamespace = UpstreamConfig.HordeConfig.Namespace, + .ConnectTimeout = std::chrono::milliseconds(UpstreamConfig.ConnectTimeoutMilliseconds), + .Timeout = std::chrono::milliseconds(UpstreamConfig.TimeoutMilliseconds)}; + + auto StorageAuthConfig = UpstreamAuthConfig{.OAuthUrl = UpstreamConfig.HordeConfig.StorageOAuthUrl, + .OAuthClientId = UpstreamConfig.HordeConfig.StorageOAuthClientId, + .OAuthClientSecret = UpstreamConfig.HordeConfig.StorageOAuthClientSecret, + .OpenIdProvider = UpstreamConfig.HordeConfig.StorageOpenIdProvider, + .AccessToken = UpstreamConfig.HordeConfig.StorageAccessToken}; + + m_HttpFunctionService = std::make_unique<HttpFunctionService>(*m_CidStore, + ComputeOptions, + StorageOptions, + ComputeAuthConfig, + StorageAuthConfig, + *m_AuthMgr); } else { @@ -1078,456 +586,281 @@ ZenServer::InitializeCompute(const ZenServerOptions& ServerOptions) } #endif // ZEN_WITH_COMPUTE_SERVICES -//////////////////////////////////////////////////////////////////////////////// - -class ZenEntryPoint -{ -public: - ZenEntryPoint(ZenServerOptions& ServerOptions); - ZenEntryPoint(const ZenEntryPoint&) = delete; - ZenEntryPoint& operator=(const ZenEntryPoint&) = delete; - int Run(); - -private: - ZenServerOptions& m_ServerOptions; - zen::LockFile m_LockFile; -}; - -ZenEntryPoint::ZenEntryPoint(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions) -{ -} - -#if ZEN_USE_SENTRY -static void -SentryLogFunction(sentry_level_t Level, const char* Message, va_list Args, [[maybe_unused]] void* Userdata) +void +ZenServer::Run() { - char LogMessageBuffer[160]; - std::string LogMessage; - const char* MessagePtr = LogMessageBuffer; - - int n = vsnprintf(LogMessageBuffer, sizeof LogMessageBuffer, Message, Args); + // This is disabled for now, awaiting better scheduling + // + // ScrubStorage(); - if (n >= int(sizeof LogMessageBuffer)) + if (m_ProcessMonitor.IsActive()) { - LogMessage.resize(n + 1); - - n = vsnprintf(LogMessage.data(), LogMessage.size(), Message, Args); - - MessagePtr = LogMessage.c_str(); + EnqueueTimer(); } - switch (Level) + if (!m_TestMode) { - case SENTRY_LEVEL_DEBUG: - ConsoleLog().debug("sentry: {}", MessagePtr); - break; - - case SENTRY_LEVEL_INFO: - ConsoleLog().info("sentry: {}", MessagePtr); - break; - - case SENTRY_LEVEL_WARNING: - ConsoleLog().warn("sentry: {}", MessagePtr); - break; - - case SENTRY_LEVEL_ERROR: - ConsoleLog().error("sentry: {}", MessagePtr); - break; - - case SENTRY_LEVEL_FATAL: - ConsoleLog().critical("sentry: {}", MessagePtr); - break; + ZEN_INFO("__________ _________ __ "); + ZEN_INFO("\\____ /____ ____ / _____// |_ ___________ ____ "); + ZEN_INFO(" / // __ \\ / \\ \\_____ \\\\ __\\/ _ \\_ __ \\_/ __ \\ "); + ZEN_INFO(" / /\\ ___/| | \\ / \\| | ( <_> ) | \\/\\ ___/ "); + ZEN_INFO("/_______ \\___ >___| / /_______ /|__| \\____/|__| \\___ >"); + ZEN_INFO(" \\/ \\/ \\/ \\/ \\/ "); } -} -#endif - -int -ZenEntryPoint::Run() -{ - zen::SetCurrentThreadName("main"); + ZEN_INFO(ZEN_APP_NAME " now running (pid: {})", GetCurrentProcessId()); #if ZEN_USE_SENTRY - int SentryErrorCode = 0; - std::unique_ptr<zen::utils::SentryAssertImpl> SentryAssert; - std::string SentryUserName; - - if (m_ServerOptions.NoSentry == false) + ZEN_INFO("sentry crash handler {}", m_UseSentry ? "ENABLED" : "DISABLED"); + if (m_UseSentry) { - std::string SentryDatabasePath = PathToUtf8(m_ServerOptions.DataDir / ".sentry-native"); - if (SentryDatabasePath.starts_with("\\\\?\\")) - { - SentryDatabasePath = SentryDatabasePath.substr(4); - } - sentry_options_t* SentryOptions = sentry_options_new(); - sentry_options_set_dsn(SentryOptions, "https://[email protected]/5919284"); - sentry_options_set_database_path(SentryOptions, SentryDatabasePath.c_str()); - sentry_options_set_logger(SentryOptions, SentryLogFunction, this); - std::string SentryAttachmentPath = PathToUtf8(m_ServerOptions.AbsLogFile); - if (SentryAttachmentPath.starts_with("\\\\?\\")) - { - SentryAttachmentPath = SentryAttachmentPath.substr(4); - } - sentry_options_add_attachment(SentryOptions, SentryAttachmentPath.c_str()); - sentry_options_set_release(SentryOptions, ZEN_CFG_VERSION); - - // sentry_options_set_debug(SentryOptions, 1); - - SentryErrorCode = sentry_init(SentryOptions); - - if (SentryErrorCode == 0) - { - if (m_ServerOptions.SentryAllowPII) - { -# if ZEN_PLATFORM_WINDOWS - CHAR UserNameBuffer[511 + 1]; - DWORD UserNameLength = sizeof(UserNameBuffer) / sizeof(CHAR); - BOOL OK = GetUserNameA(UserNameBuffer, &UserNameLength); - if (OK && UserNameLength) - { - SentryUserName = std::string(UserNameBuffer, UserNameLength - 1); - } -# endif // ZEN_PLATFORM_WINDOWS -# if (ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC) - uid_t uid = geteuid(); - struct passwd* pw = getpwuid(uid); - if (pw) - { - SentryUserName = std::string(pw->pw_name); - } -# endif - sentry_value_t SentryUserObject = sentry_value_new_object(); - sentry_value_set_by_key(SentryUserObject, "id", sentry_value_new_string(SentryUserName.c_str())); - sentry_value_set_by_key(SentryUserObject, "username", sentry_value_new_string(SentryUserName.c_str())); - sentry_value_set_by_key(SentryUserObject, "ip_address", sentry_value_new_string("{{auto}}")); - sentry_set_user(SentryUserObject); - } - - auto SentrySink = spdlog::create<utils::sentry_sink>("sentry"); - zen::logging::SetErrorLog(std::move(SentrySink)); - - SentryAssert = std::make_unique<zen::utils::SentryAssertImpl>(); - } + SentryIntegration::ClearCaches(); } - - auto _ = zen::MakeGuard([&SentryAssert, SentryErrorCode] { - if (SentryErrorCode == 0) - { - zen::logging::SetErrorLog(std::shared_ptr<spdlog::logger>()); - SentryAssert.reset(); - sentry_close(); - } - }); #endif - auto& ServerOptions = m_ServerOptions; - - try + if (m_DebugOptionForcedCrash) { - // Mutual exclusion and synchronization - ZenServerState ServerState; - ServerState.Initialize(); - ServerState.Sweep(); - - ZenServerState::ZenServerEntry* Entry = ServerState.Lookup(ServerOptions.BasePort); - - if (Entry) - { - if (ServerOptions.OwnerPid) - { - ZEN_INFO( - "Looks like there is already a process listening to this port {} (pid: {}), attaching owner pid {} to running instance", - ServerOptions.BasePort, - Entry->Pid.load(), - ServerOptions.OwnerPid); - - Entry->AddSponsorProcess(ServerOptions.OwnerPid); - - std::exit(0); - } - else - { - ZEN_WARN("Exiting since there is already a process listening to port {} (pid: {})", - ServerOptions.BasePort, - Entry->Pid.load()); - std::exit(1); - } - } + ZEN_DEBUG_BREAK(); + } - std::error_code Ec; + const bool IsInteractiveMode = IsInteractiveSession() && !m_TestMode; - std::filesystem::path LockFilePath = ServerOptions.DataDir / ".lock"; + SetNewState(kRunning); - bool IsReady = false; + OnReady(); - auto MakeLockData = [&] { - CbObjectWriter Cbo; - Cbo << "pid" << zen::GetCurrentProcessId() << "data" << PathToUtf8(ServerOptions.DataDir) << "port" << ServerOptions.BasePort - << "session_id" << GetSessionId() << "ready" << IsReady; - return Cbo.Save(); - }; + m_Http->Run(IsInteractiveMode); - m_LockFile.Create(LockFilePath, MakeLockData(), Ec); + SetNewState(kShuttingDown); - if (Ec) - { - ZEN_WARN("ERROR: Unable to grab lock at '{}' (error: '{}')", LockFilePath, Ec.message()); + ZEN_INFO(ZEN_APP_NAME " exiting"); - std::exit(99); - } + Flush(); +} - InitializeLogging(ServerOptions); +void +ZenServer::RequestExit(int ExitCode) +{ + RequestApplicationExit(ExitCode); + if (m_Http) + { + m_Http->RequestExit(); + } +} -#if ZEN_USE_SENTRY - if (m_ServerOptions.NoSentry == false) +void +ZenServer::Cleanup() +{ + ZEN_INFO(ZEN_APP_NAME " cleaning up"); + try + { + m_IoContext.stop(); + if (m_IoRunner.joinable()) { - if (SentryErrorCode == 0) - { - if (m_ServerOptions.SentryAllowPII) - { - ZEN_INFO("sentry initialized, username: '{}'", SentryUserName); - } - else - { - ZEN_INFO("sentry initialized with anonymous reports"); - } - } - else - { - ZEN_WARN("sentry_init returned failure! (error code: {})", SentryErrorCode); - } + m_IoRunner.join(); } -#endif - - MaximizeOpenFileCount(); - - ZEN_INFO(ZEN_APP_NAME " - using lock file at '{}'", LockFilePath); - ZEN_INFO(ZEN_APP_NAME " - starting on port {}, version '{}'", ServerOptions.BasePort, ZEN_CFG_VERSION_BUILD_STRING_FULL); - - Entry = ServerState.Register(ServerOptions.BasePort); - - if (ServerOptions.OwnerPid) + if (m_Http) { - Entry->AddSponsorProcess(ServerOptions.OwnerPid); + m_Http->Close(); } - - ZenServer Server; - Server.SetDataRoot(ServerOptions.DataDir); - Server.SetContentRoot(ServerOptions.ContentDir); - Server.SetTestMode(ServerOptions.IsTest); - Server.SetDedicatedMode(ServerOptions.IsDedicated); - - auto ServerCleanup = zen::MakeGuard([&Server] { Server.Cleanup(); }); - - int EffectiveBasePort = Server.Initialize(ServerOptions, Entry); - - Entry->EffectiveListenPort = uint16_t(EffectiveBasePort); - if (EffectiveBasePort != ServerOptions.BasePort) + if (m_JobQueue) { - ZEN_INFO(ZEN_APP_NAME " - relocated to base port {}", EffectiveBasePort); - ServerOptions.BasePort = EffectiveBasePort; + m_JobQueue->Stop(); } - std::unique_ptr<std::thread> ShutdownThread; - std::unique_ptr<zen::NamedEvent> ShutdownEvent; - - zen::ExtendableStringBuilder<64> ShutdownEventName; - ShutdownEventName << "Zen_" << ServerOptions.BasePort << "_Shutdown"; - ShutdownEvent.reset(new zen::NamedEvent{ShutdownEventName}); - - // Monitor shutdown signals - - ShutdownThread.reset(new std::thread{[&] { - zen::SetCurrentThreadName("shutdown_monitor"); - - ZEN_INFO("shutdown monitor thread waiting for shutdown signal '{}'", ShutdownEventName); - - if (ShutdownEvent->Wait()) - { - if (!IsApplicationExitRequested()) - { - ZEN_INFO("shutdown signal received"); - Server.RequestExit(0); - } - } - else - { - ZEN_INFO("shutdown signal wait() failed"); - } - }}); - auto CleanupShutdown = zen::MakeGuard([&ShutdownEvent, &ShutdownThread] { - if (ShutdownEvent) - { - ShutdownEvent->Set(); - } - if (ShutdownThread && ShutdownThread->joinable()) - { - ShutdownThread->join(); - } - }); - - // If we have a parent process, establish the mechanisms we need - // to be able to communicate readiness with the parent - - Server.SetIsReadyFunc([&] { - IsReady = true; + m_GcScheduler.Shutdown(); + m_AdminService.reset(); + m_VfsService.reset(); + m_ObjStoreService.reset(); + m_FrontendService.reset(); - m_LockFile.Update(MakeLockData(), Ec); + m_StructuredCacheService.reset(); + m_UpstreamService.reset(); + m_UpstreamCache.reset(); + m_CacheStore = {}; - if (!ServerOptions.ChildId.empty()) - { - zen::NamedEvent ParentEvent{ServerOptions.ChildId}; - ParentEvent.Set(); - } - }); +#if ZEN_WITH_COMPUTE_SERVICES + m_HttpFunctionService.reset(); +#endif // ZEN_WITH_COMPUTE_SERVICES - Server.Run(); + m_HttpProjectService.reset(); + m_ProjectStore = {}; + m_CidService.reset(); + m_CidStore.reset(); + m_AuthService.reset(); + m_AuthMgr.reset(); + m_Http = {}; + m_JobQueue.reset(); } - catch (std::exception& e) + catch (std::exception& Ex) { - SPDLOG_CRITICAL("Caught exception in main: {}", e.what()); - if (!IsApplicationExitRequested()) - { - RequestApplicationExit(1); - } + ZEN_ERROR("exception thrown during Cleanup() in {}: '{}'", ZEN_APP_NAME, Ex.what()); } - - ShutdownLogging(); - - return ApplicationExitCode(); } -} // namespace zen - -//////////////////////////////////////////////////////////////////////////////// - -#if ZEN_PLATFORM_WINDOWS - -class ZenWindowsService : public WindowsService +void +ZenServer::EnsureIoRunner() { -public: - ZenWindowsService(ZenServerOptions& ServerOptions) : m_EntryPoint(ServerOptions) {} - - ZenWindowsService(const ZenWindowsService&) = delete; - ZenWindowsService& operator=(const ZenWindowsService&) = delete; + if (!m_IoRunner.joinable()) + { + m_IoRunner = std::thread{[this] { + SetCurrentThreadName("timer_io"); + m_IoContext.run(); + }}; + } +} - virtual int Run() override; +void +ZenServer::EnqueueTimer() +{ + m_PidCheckTimer.expires_after(std::chrono::seconds(1)); + m_PidCheckTimer.async_wait([this](const asio::error_code&) { CheckOwnerPid(); }); -private: - zen::ZenEntryPoint m_EntryPoint; -}; + EnsureIoRunner(); +} -int -ZenWindowsService::Run() +void +ZenServer::EnqueueStateMarkerTimer() { - return m_EntryPoint.Run(); + m_StateMakerTimer.expires_after(std::chrono::seconds(5)); + m_StateMakerTimer.async_wait([this](const asio::error_code&) { CheckStateMarker(); }); + EnsureIoRunner(); } -#endif // ZEN_PLATFORM_WINDOWS - -//////////////////////////////////////////////////////////////////////////////// - -#if ZEN_WITH_TESTS -int -test_main(int argc, char** argv) +void +ZenServer::EnqueueSigIntTimer() { - zen::zencore_forcelinktests(); - zen::zenhttp_forcelinktests(); - zen::zenstore_forcelinktests(); - zen::z$_forcelink(); - zen::z$service_forcelink(); - - zen::logging::InitializeLogging(); - spdlog::set_level(spdlog::level::debug); + m_SigIntTimer.expires_after(std::chrono::milliseconds(500)); + m_SigIntTimer.async_wait([this](const asio::error_code&) { CheckSigInt(); }); + EnsureIoRunner(); +} - zen::MaximizeOpenFileCount(); +void +ZenServer::CheckStateMarker() +{ + std::filesystem::path StateMarkerPath = m_DataRoot / "state_marker"; + try + { + if (!std::filesystem::exists(StateMarkerPath)) + { + ZEN_WARN("state marker at {} has been deleted, exiting", StateMarkerPath); + RequestExit(1); + return; + } + } + catch (std::exception& Ex) + { + ZEN_WARN("state marker at {} could not be checked, reason: '{}'", StateMarkerPath, Ex.what()); + RequestExit(1); + return; + } + EnqueueStateMarkerTimer(); +} - return ZEN_RUN_TESTS(argc, argv); +void +ZenServer::CheckSigInt() +{ + if (utils::SignalCounter[SIGINT] > 0) + { + ZEN_INFO("SIGINT triggered (Ctrl+C), exiting"); + RequestExit(128 + SIGINT); + return; + } + EnqueueSigIntTimer(); } -#endif -int -main(int argc, char* argv[]) +void +ZenServer::CheckOwnerPid() { - using namespace zen; + // Pick up any new "owner" processes -#if ZEN_USE_MIMALLOC - mi_version(); -#endif + std::set<uint32_t> AddedPids; - if (argc >= 2) + for (auto& PidEntry : m_ServerEntry->SponsorPids) { - if (argv[1] == "test"sv) + if (uint32_t ThisPid = PidEntry.load(std::memory_order_relaxed)) { -#if ZEN_WITH_TESTS - return test_main(argc, argv); -#else - fprintf(stderr, "test option not available in release mode!\n"); - exit(5); -#endif + if (PidEntry.compare_exchange_strong(ThisPid, 0)) + { + if (AddedPids.insert(ThisPid).second) + { + m_ProcessMonitor.AddPid(ThisPid); + + ZEN_INFO("added process with pid #{} as a sponsor process", ThisPid); + } + } } } - signal(SIGINT, utils::SignalCallbackHandler); - - try + if (m_ProcessMonitor.IsRunning()) { - ZenServerOptions ServerOptions; - ParseCliOptions(argc, argv, ServerOptions); - - if (!std::filesystem::exists(ServerOptions.DataDir)) - { - ServerOptions.IsFirstRun = true; - std::filesystem::create_directories(ServerOptions.DataDir); - } + EnqueueTimer(); + } + else + { + ZEN_INFO(ZEN_APP_NAME " exiting since sponsor processes are all gone"); -#if ZEN_WITH_TRACE - if (ServerOptions.TraceHost.size()) - { - TraceStart(ServerOptions.TraceHost.c_str(), TraceType::Network); - } - else if (ServerOptions.TraceFile.size()) - { - TraceStart(ServerOptions.TraceFile.c_str(), TraceType::File); - } - else - { - TraceInit(); - } - atexit(TraceShutdown); -#endif // ZEN_WITH_TRACE + RequestExit(0); + } +} -#if ZEN_PLATFORM_WINDOWS - if (ServerOptions.InstallService) - { - WindowsService::Install(); +void +ZenServer::ScrubStorage() +{ + Stopwatch Timer; + ZEN_INFO("Storage validation STARTING"); + + WorkerThreadPool ThreadPool{1}; + ScrubContext Ctx{ThreadPool}; + m_CidStore->ScrubStorage(Ctx); + m_ProjectStore->ScrubStorage(Ctx); + m_StructuredCacheService->ScrubStorage(Ctx); + + const uint64_t ElapsedTimeMs = Timer.GetElapsedTimeMs(); + + ZEN_INFO("Storage validation DONE in {}, ({} in {} chunks - {})", + NiceTimeSpanMs(ElapsedTimeMs), + NiceBytes(Ctx.ScrubbedBytes()), + Ctx.ScrubbedChunks(), + NiceByteRate(Ctx.ScrubbedBytes(), ElapsedTimeMs)); +} - std::exit(0); - } +void +ZenServer::Flush() +{ + if (m_CidStore) + m_CidStore->Flush(); - if (ServerOptions.UninstallService) - { - WindowsService::Delete(); + if (m_StructuredCacheService) + m_StructuredCacheService->Flush(); - std::exit(0); - } + if (m_ProjectStore) + m_ProjectStore->Flush(); +} - ZenWindowsService App(ServerOptions); - return App.ServiceMain(); -#else - if (ServerOptions.InstallService || ServerOptions.UninstallService) - { - throw std::runtime_error("Service mode is not supported on this platform"); - } +void +ZenServer::HandleStatusRequest(HttpServerRequest& Request) +{ + CbObjectWriter Cbo; + Cbo << "ok" << true; + Cbo << "state" << ToString(m_CurrentState); + Request.WriteResponse(HttpResponseCode::OK, Cbo.Save()); +} - ZenEntryPoint App(ServerOptions); - return App.Run(); -#endif // ZEN_PLATFORM_WINDOWS - } - catch (std::exception& Ex) +std::string_view +ZenServer::ToString(ServerState Value) +{ + switch (Value) { - fprintf(stderr, "ERROR: Caught exception in main: '%s'", Ex.what()); - - return 1; + case kInitializing: + return "initializing"sv; + case kRunning: + return "running"sv; + case kShuttingDown: + return "shutdown"sv; + default: + return "unknown"sv; } } + +} // namespace zen diff --git a/src/zenserver/zenserver.h b/src/zenserver/zenserver.h new file mode 100644 index 000000000..2b1ae3842 --- /dev/null +++ b/src/zenserver/zenserver.h @@ -0,0 +1,147 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zenhttp/httpserver.h> +#include <zenhttp/httpstatus.h> +#include <zenutil/zenserverprocess.h> + +#include <memory> +#include <string_view> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <fmt/format.h> +#include <asio.hpp> +#include <lua.hpp> +ZEN_THIRD_PARTY_INCLUDES_END + +////////////////////////////////////////////////////////////////////////// +// Services +// + +#include <zenhttp/auth/authmgr.h> +#include <zenhttp/auth/authservice.h> +#include <zenhttp/diagsvcs.h> +#include <zenhttp/httpstats.h> +#include <zenhttp/httpstatus.h> +#include <zenhttp/httptest.h> +#include <zenstore/gc.h> +#include "admin/admin.h" +#include "cache/httpstructuredcache.h" +#include "cache/structuredcachestore.h" +#include "compute/function.h" +#include "frontend/frontend.h" +#include "httpcidstore.h" +#include "objectstore/objectstore.h" +#include "projectstore/httpprojectstore.h" +#include "projectstore/projectstore.h" +#include "upstream/upstream.h" +#include "vfs/vfsservice.h" + +#ifndef ZEN_APP_NAME +# define ZEN_APP_NAME "Zen store" +#endif + +namespace zen { + +struct ZenServerOptions; + +class ZenServer : public IHttpStatusProvider +{ + ZenServer& operator=(ZenServer&&) = delete; + ZenServer(ZenServer&&) = delete; + +public: + ZenServer(); + ~ZenServer(); + + int Initialize(const ZenServerOptions& ServerOptions, ZenServerState::ZenServerEntry* ServerEntry); + void InitializeState(const ZenServerOptions& ServerOptions); + void InitializeStructuredCache(const ZenServerOptions& ServerOptions); + void InitializeCompute(const ZenServerOptions& ServerOptions); + + void Run(); + void RequestExit(int ExitCode); + void Cleanup(); + + void SetDedicatedMode(bool State) { m_IsDedicatedMode = State; } + void SetTestMode(bool State) { m_TestMode = State; } + void SetDataRoot(std::filesystem::path Root) { m_DataRoot = Root; } + void SetContentRoot(std::filesystem::path Root) { m_ContentRoot = Root; } + + std::function<void()> m_IsReadyFunc; + void SetIsReadyFunc(std::function<void()>&& IsReadyFunc) { m_IsReadyFunc = std::move(IsReadyFunc); } + void OnReady(); + + void EnsureIoRunner(); + void EnqueueTimer(); + void EnqueueStateMarkerTimer(); + void EnqueueSigIntTimer(); + void CheckStateMarker(); + void CheckSigInt(); + void CheckOwnerPid(); + + void ScrubStorage(); + void Flush(); + + virtual void HandleStatusRequest(HttpServerRequest& Request) override; + +private: + ZenServerState::ZenServerEntry* m_ServerEntry = nullptr; + bool m_IsDedicatedMode = false; + bool m_TestMode = false; + CbObject m_RootManifest; + std::filesystem::path m_DataRoot; + std::filesystem::path m_ContentRoot; + std::thread m_IoRunner; + asio::io_context m_IoContext; + asio::steady_timer m_PidCheckTimer{m_IoContext}; + asio::steady_timer m_StateMakerTimer{m_IoContext}; + asio::steady_timer m_SigIntTimer{m_IoContext}; + ProcessMonitor m_ProcessMonitor; + NamedMutex m_ServerMutex; + + enum ServerState + { + kInitializing, + kRunning, + kShuttingDown + } m_CurrentState = kInitializing; + + inline void SetNewState(ServerState NewState) { m_CurrentState = NewState; } + static std::string_view ToString(ServerState Value); + + Ref<HttpServer> m_Http; + std::unique_ptr<AuthMgr> m_AuthMgr; + std::unique_ptr<HttpAuthService> m_AuthService; + HttpStatusService m_StatusService; + HttpStatsService m_StatsService; + GcManager m_GcManager; + GcScheduler m_GcScheduler{m_GcManager}; + std::unique_ptr<CidStore> m_CidStore; + Ref<ZenCacheStore> m_CacheStore; + HttpTestService m_TestService; +#if ZEN_WITH_TESTS + HttpTestingService m_TestingService; +#endif + std::unique_ptr<HttpCidService> m_CidService; + RefPtr<ProjectStore> m_ProjectStore; + std::unique_ptr<HttpProjectService> m_HttpProjectService; + std::unique_ptr<UpstreamCache> m_UpstreamCache; + std::unique_ptr<HttpUpstreamService> m_UpstreamService; + std::unique_ptr<HttpStructuredCacheService> m_StructuredCacheService; + HttpHealthService m_HealthService; +#if ZEN_WITH_COMPUTE_SERVICES + std::unique_ptr<HttpFunctionService> m_HttpFunctionService; +#endif + std::unique_ptr<HttpFrontendService> m_FrontendService; + std::unique_ptr<HttpObjectStoreService> m_ObjStoreService; + std::unique_ptr<VfsService> m_VfsService; + std::unique_ptr<JobQueue> m_JobQueue; + std::unique_ptr<HttpAdminService> m_AdminService; + + bool m_DebugOptionForcedCrash = false; + bool m_UseSentry = false; +}; + +} // namespace zen |