aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-10-06 10:45:48 +0200
committerGitHub <[email protected]>2023-10-06 10:45:48 +0200
commitfb70324d37282910d7fa3047f4ec290d0c5a94b1 (patch)
treea1bc82fcfdb96eb5b461742b613fcbb63f816a54 /src
parentreject known bad bucket names in structured cache (#452) (diff)
downloadzen-fb70324d37282910d7fa3047f4ec290d0c5a94b1.tar.xz
zen-fb70324d37282910d7fa3047f4ec290d0c5a94b1.zip
zenserver project restructuring (#442)
Diffstat (limited to 'src')
-rw-r--r--src/zenserver/config.cpp894
-rw-r--r--src/zenserver/config.h4
-rw-r--r--src/zenserver/diag/logging.cpp8
-rw-r--r--src/zenserver/diag/logging.h6
-rw-r--r--src/zenserver/main.cpp390
-rw-r--r--src/zenserver/sentryintegration.cpp288
-rw-r--r--src/zenserver/sentryintegration.h48
-rw-r--r--src/zenserver/xmake.lua2
-rw-r--r--src/zenserver/zenserver.cpp1487
-rw-r--r--src/zenserver/zenserver.h147
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