aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2025-10-09 18:28:41 +0200
committerStefan Boberg <[email protected]>2025-10-09 18:28:41 +0200
commite2701e73646b382c9e6c6690505ae7bccea3ad34 (patch)
treee37f8d54c7d0a721f8ccdeaa04b338cd8351805c /src
parentminor additions (diff)
parentshorten thread pool names for Linux which has a limit of 15 characters (#563) (diff)
downloadzen-e2701e73646b382c9e6c6690505ae7bccea3ad34.tar.xz
zen-e2701e73646b382c9e6c6690505ae7bccea3ad34.zip
Merge remote-tracking branch 'origin/main' into sb/zen-master
Diffstat (limited to 'src')
-rw-r--r--src/zen/authutils.cpp247
-rw-r--r--src/zen/authutils.h52
-rw-r--r--src/zen/cmds/admin_cmd.cpp264
-rw-r--r--src/zen/cmds/admin_cmd.h16
-rw-r--r--src/zen/cmds/bench_cmd.cpp6
-rw-r--r--src/zen/cmds/bench_cmd.h2
-rw-r--r--src/zen/cmds/builds_cmd.cpp2375
-rw-r--r--src/zen/cmds/builds_cmd.h39
-rw-r--r--src/zen/cmds/cache_cmd.cpp361
-rw-r--r--src/zen/cmds/cache_cmd.h12
-rw-r--r--src/zen/cmds/copy_cmd.cpp35
-rw-r--r--src/zen/cmds/copy_cmd.h2
-rw-r--r--src/zen/cmds/dedup_cmd.cpp12
-rw-r--r--src/zen/cmds/dedup_cmd.h2
-rw-r--r--src/zen/cmds/info_cmd.cpp8
-rw-r--r--src/zen/cmds/info_cmd.h2
-rw-r--r--src/zen/cmds/print_cmd.cpp48
-rw-r--r--src/zen/cmds/print_cmd.h4
-rw-r--r--src/zen/cmds/projectstore_cmd.cpp695
-rw-r--r--src/zen/cmds/projectstore_cmd.h62
-rw-r--r--src/zen/cmds/rpcreplay_cmd.cpp129
-rw-r--r--src/zen/cmds/rpcreplay_cmd.h6
-rw-r--r--src/zen/cmds/run_cmd.cpp20
-rw-r--r--src/zen/cmds/run_cmd.h2
-rw-r--r--src/zen/cmds/serve_cmd.cpp22
-rw-r--r--src/zen/cmds/serve_cmd.h2
-rw-r--r--src/zen/cmds/service_cmd.cpp192
-rw-r--r--src/zen/cmds/service_cmd.h2
-rw-r--r--src/zen/cmds/status_cmd.cpp34
-rw-r--r--src/zen/cmds/status_cmd.h2
-rw-r--r--src/zen/cmds/top_cmd.cpp12
-rw-r--r--src/zen/cmds/top_cmd.h4
-rw-r--r--src/zen/cmds/trace_cmd.cpp32
-rw-r--r--src/zen/cmds/trace_cmd.h2
-rw-r--r--src/zen/cmds/up_cmd.cpp117
-rw-r--r--src/zen/cmds/up_cmd.h6
-rw-r--r--src/zen/cmds/version_cmd.cpp18
-rw-r--r--src/zen/cmds/version_cmd.h2
-rw-r--r--src/zen/cmds/vfs_cmd.cpp18
-rw-r--r--src/zen/cmds/vfs_cmd.h2
-rw-r--r--src/zen/cmds/wipe_cmd.cpp16
-rw-r--r--src/zen/cmds/wipe_cmd.h2
-rw-r--r--src/zen/cmds/workspaces_cmd.cpp142
-rw-r--r--src/zen/cmds/workspaces_cmd.h4
-rw-r--r--src/zen/xmake.lua2
-rw-r--r--src/zen/zen.cpp355
-rw-r--r--src/zen/zen.h21
-rw-r--r--src/zencore-test/zencore-test.cpp7
-rw-r--r--src/zencore/callstack.cpp48
-rw-r--r--src/zencore/compactbinaryfile.cpp10
-rw-r--r--src/zencore/compactbinaryutil.cpp39
-rw-r--r--src/zencore/filesystem.cpp76
-rw-r--r--src/zencore/include/zencore/callstack.h12
-rw-r--r--src/zencore/include/zencore/compactbinaryutil.h9
-rw-r--r--src/zencore/include/zencore/except.h4
-rw-r--r--src/zencore/include/zencore/memory/newdelete.h33
-rw-r--r--src/zencore/include/zencore/parallelwork.h (renamed from src/zenutil/include/zenutil/parallelwork.h)39
-rw-r--r--src/zencore/include/zencore/process.h1
-rw-r--r--src/zencore/include/zencore/string.h3
-rw-r--r--src/zencore/include/zencore/uid.h4
-rw-r--r--src/zencore/include/zencore/workthreadpool.h23
-rw-r--r--src/zencore/include/zencore/zencore.h4
-rw-r--r--src/zencore/jobqueue.cpp72
-rw-r--r--src/zencore/logging.cpp15
-rw-r--r--src/zencore/memory/memory.cpp26
-rw-r--r--src/zencore/parallelwork.cpp (renamed from src/zenutil/parallelwork.cpp)33
-rw-r--r--src/zencore/sentryintegration.cpp15
-rw-r--r--src/zencore/string.cpp55
-rw-r--r--src/zencore/thread.cpp6
-rw-r--r--src/zencore/workthreadpool.cpp151
-rw-r--r--src/zencore/zencore.cpp33
-rw-r--r--src/zenhttp-test/xmake.lua2
-rw-r--r--src/zenhttp-test/zenhttp-test.cpp8
-rw-r--r--src/zenhttp/auth/authmgr.cpp13
-rw-r--r--src/zenhttp/auth/oidc.cpp40
-rw-r--r--src/zenhttp/clients/httpclientcommon.cpp474
-rw-r--r--src/zenhttp/clients/httpclientcommon.h147
-rw-r--r--src/zenhttp/clients/httpclientcpr.cpp1035
-rw-r--r--src/zenhttp/clients/httpclientcpr.h151
-rw-r--r--src/zenhttp/httpclient.cpp1703
-rw-r--r--src/zenhttp/httpclientauth.cpp34
-rw-r--r--src/zenhttp/httpserver.cpp64
-rw-r--r--src/zenhttp/include/zenhttp/cprutils.h86
-rw-r--r--src/zenhttp/include/zenhttp/formatters.h71
-rw-r--r--src/zenhttp/include/zenhttp/httpclient.h53
-rw-r--r--src/zenhttp/include/zenhttp/httpclientauth.h4
-rw-r--r--src/zenhttp/packageformat.cpp49
-rw-r--r--src/zenhttp/servers/httpasio.cpp8
-rw-r--r--src/zenhttp/servers/httpparser.cpp148
-rw-r--r--src/zenhttp/servers/httpparser.h103
-rw-r--r--src/zenhttp/servers/httpplugin.cpp16
-rw-r--r--src/zenhttp/servers/httpsys.cpp6
-rw-r--r--src/zenhttp/transports/winsocktransport.cpp26
-rw-r--r--src/zennet-test/xmake.lua2
-rw-r--r--src/zennet-test/zennet-test.cpp8
-rw-r--r--src/zenremotestore-test/xmake.lua9
-rw-r--r--src/zenremotestore-test/zenremotestore-test.cpp43
-rw-r--r--src/zenremotestore/builds/buildstoragecache.cpp (renamed from src/zenutil/buildstoragecache.cpp)60
-rw-r--r--src/zenremotestore/builds/filebuildstorage.cpp (renamed from src/zenutil/filebuildstorage.cpp)2
-rw-r--r--src/zenremotestore/builds/jupiterbuildstorage.cpp (renamed from src/zenutil/jupiter/jupiterbuildstorage.cpp)88
-rw-r--r--src/zenremotestore/chunking/chunkblock.cpp (renamed from src/zenutil/chunkblock.cpp)65
-rw-r--r--src/zenremotestore/chunking/chunkedcontent.cpp (renamed from src/zenutil/chunkedcontent.cpp)16
-rw-r--r--src/zenremotestore/chunking/chunkedfile.cpp (renamed from src/zenutil/chunkedfile.cpp)2
-rw-r--r--src/zenremotestore/chunking/chunking.cpp (renamed from src/zenutil/chunking.cpp)0
-rw-r--r--src/zenremotestore/chunking/chunking.h (renamed from src/zenutil/chunking.h)0
-rw-r--r--src/zenremotestore/chunking/chunkingcontroller.cpp (renamed from src/zenutil/chunkingcontroller.cpp)2
-rw-r--r--src/zenremotestore/include/zenremotestore/builds/buildstorage.h (renamed from src/zenutil/include/zenutil/buildstorage.h)2
-rw-r--r--src/zenremotestore/include/zenremotestore/builds/buildstoragecache.h (renamed from src/zenutil/include/zenutil/buildstoragecache.h)14
-rw-r--r--src/zenremotestore/include/zenremotestore/builds/filebuildstorage.h (renamed from src/zenutil/include/zenutil/filebuildstorage.h)2
-rw-r--r--src/zenremotestore/include/zenremotestore/builds/jupiterbuildstorage.h (renamed from src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h)9
-rw-r--r--src/zenremotestore/include/zenremotestore/chunking/chunkblock.h (renamed from src/zenutil/include/zenutil/chunkblock.h)2
-rw-r--r--src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h (renamed from src/zenutil/include/zenutil/chunkedcontent.h)0
-rw-r--r--src/zenremotestore/include/zenremotestore/chunking/chunkedfile.h (renamed from src/zenutil/include/zenutil/chunkedfile.h)0
-rw-r--r--src/zenremotestore/include/zenremotestore/chunking/chunkingcontroller.h (renamed from src/zenutil/include/zenutil/chunkingcontroller.h)2
-rw-r--r--src/zenremotestore/include/zenremotestore/jupiter/jupiterclient.h (renamed from src/zenutil/include/zenutil/jupiter/jupiterclient.h)0
-rw-r--r--src/zenremotestore/include/zenremotestore/jupiter/jupiterhost.h35
-rw-r--r--src/zenremotestore/include/zenremotestore/jupiter/jupitersession.h (renamed from src/zenutil/include/zenutil/jupiter/jupitersession.h)0
-rw-r--r--src/zenremotestore/include/zenremotestore/projectstore/buildsremoteprojectstore.h (renamed from src/zenserver/projectstore/buildsremoteprojectstore.h)6
-rw-r--r--src/zenremotestore/include/zenremotestore/projectstore/fileremoteprojectstore.h (renamed from src/zenserver/projectstore/fileremoteprojectstore.h)2
-rw-r--r--src/zenremotestore/include/zenremotestore/projectstore/jupiterremoteprojectstore.h (renamed from src/zenserver/projectstore/jupiterremoteprojectstore.h)6
-rw-r--r--src/zenremotestore/include/zenremotestore/projectstore/remoteprojectstore.h (renamed from src/zenserver/projectstore/remoteprojectstore.h)15
-rw-r--r--src/zenremotestore/include/zenremotestore/projectstore/zenremoteprojectstore.h (renamed from src/zenserver/projectstore/zenremoteprojectstore.h)2
-rw-r--r--src/zenremotestore/include/zenremotestore/zenremotestore.h13
-rw-r--r--src/zenremotestore/jupiter/jupiterclient.cpp (renamed from src/zenutil/jupiter/jupiterclient.cpp)2
-rw-r--r--src/zenremotestore/jupiter/jupiterhost.cpp66
-rw-r--r--src/zenremotestore/jupiter/jupitersession.cpp (renamed from src/zenutil/jupiter/jupitersession.cpp)13
-rw-r--r--src/zenremotestore/projectstore/buildsremoteprojectstore.cpp (renamed from src/zenserver/projectstore/buildsremoteprojectstore.cpp)27
-rw-r--r--src/zenremotestore/projectstore/fileremoteprojectstore.cpp (renamed from src/zenserver/projectstore/fileremoteprojectstore.cpp)13
-rw-r--r--src/zenremotestore/projectstore/jupiterremoteprojectstore.cpp (renamed from src/zenserver/projectstore/jupiterremoteprojectstore.cpp)26
-rw-r--r--src/zenremotestore/projectstore/remoteprojectstore.cpp (renamed from src/zenserver/projectstore/remoteprojectstore.cpp)1851
-rw-r--r--src/zenremotestore/projectstore/zenremoteprojectstore.cpp (renamed from src/zenserver/projectstore/zenremoteprojectstore.cpp)2
-rw-r--r--src/zenremotestore/xmake.lua11
-rw-r--r--src/zenremotestore/zenremotestore.cpp22
-rw-r--r--src/zenserver-test/buildstore-tests.cpp506
-rw-r--r--src/zenserver-test/cache-tests.cpp2366
-rw-r--r--src/zenserver-test/cacherequests.cpp (renamed from src/zenutil/cache/cacherequests.cpp)348
-rw-r--r--src/zenserver-test/cacherequests.h (renamed from src/zenutil/include/zenutil/cache/cacherequests.h)29
-rw-r--r--src/zenserver-test/projectclient.cpp160
-rw-r--r--src/zenserver-test/projectclient.h32
-rw-r--r--src/zenserver-test/projectstore-tests.cpp1058
-rw-r--r--src/zenserver-test/workspace-tests.cpp541
-rw-r--r--src/zenserver-test/xmake.lua2
-rw-r--r--src/zenserver-test/zenserver-test.cpp4455
-rw-r--r--src/zenserver-test/zenserver-test.h207
-rw-r--r--src/zenserver/admin/admin.cpp2
-rw-r--r--src/zenserver/buildstore/httpbuildstore.cpp4
-rw-r--r--src/zenserver/cache/httpstructuredcache.cpp120
-rw-r--r--src/zenserver/cache/httpstructuredcache.h2
-rw-r--r--src/zenserver/config.cpp93
-rw-r--r--src/zenserver/frontend/html.zipbin161185 -> 161191 bytes
-rw-r--r--src/zenserver/frontend/html/pages/tree.js2
-rw-r--r--src/zenserver/main.cpp2
-rw-r--r--src/zenserver/projectstore/httpprojectstore.cpp1777
-rw-r--r--src/zenserver/projectstore/httpprojectstore.h8
-rw-r--r--src/zenserver/upstream/upstreamcache.cpp4
-rw-r--r--src/zenserver/upstream/upstreamcache.h2
-rw-r--r--src/zenserver/upstream/zen.cpp226
-rw-r--r--src/zenserver/upstream/zen.h18
-rw-r--r--src/zenserver/vfs/vfsservice.cpp59
-rw-r--r--src/zenserver/vfs/vfsservice.h13
-rw-r--r--src/zenserver/xmake.lua1
-rw-r--r--src/zenserver/zenserver.cpp118
-rw-r--r--src/zenserver/zenserver.h3
-rw-r--r--src/zenstore-test/xmake.lua2
-rw-r--r--src/zenstore-test/zenstore-test.cpp8
-rw-r--r--src/zenstore/blockstore.cpp303
-rw-r--r--src/zenstore/buildstore/buildstore.cpp42
-rw-r--r--src/zenstore/cache/cache.cpp235
-rw-r--r--src/zenstore/cache/cachedisklayer.cpp66
-rw-r--r--src/zenstore/cache/cachekey.cpp (renamed from src/zenutil/cache/cachekey.cpp)2
-rw-r--r--src/zenstore/cache/cachepolicy.cpp (renamed from src/zenutil/cache/cachepolicy.cpp)2
-rw-r--r--src/zenstore/cache/cacherpc.cpp124
-rw-r--r--src/zenstore/cache/structuredcachestore.cpp196
-rw-r--r--src/zenstore/cas.cpp19
-rw-r--r--src/zenstore/compactcas.cpp400
-rw-r--r--src/zenstore/filecas.cpp16
-rw-r--r--src/zenstore/gc.cpp217
-rw-r--r--src/zenstore/include/zenstore/blockstore.h12
-rw-r--r--src/zenstore/include/zenstore/cache/cache.h52
-rw-r--r--src/zenstore/include/zenstore/cache/cachedisklayer.h3
-rw-r--r--src/zenstore/include/zenstore/cache/cachekey.h (renamed from src/zenutil/include/zenutil/cache/cachekey.h)2
-rw-r--r--src/zenstore/include/zenstore/cache/cachepolicy.h (renamed from src/zenutil/include/zenutil/cache/cachepolicy.h)0
-rw-r--r--src/zenstore/include/zenstore/cache/cacherpc.h6
-rw-r--r--src/zenstore/include/zenstore/cache/structuredcachestore.h2
-rw-r--r--src/zenstore/include/zenstore/cache/upstreamcacheclient.h2
-rw-r--r--src/zenstore/include/zenstore/oplogreferencedset.h (renamed from src/zenserver/projectstore/oplogreferencedset.h)15
-rw-r--r--src/zenstore/include/zenstore/projectstore.h (renamed from src/zenserver/projectstore/projectstore.h)407
-rw-r--r--src/zenstore/include/zenstore/vfsimpl.h (renamed from src/zenserver/vfs/vfsimpl.h)32
-rw-r--r--src/zenstore/oplogreferencedset.cpp (renamed from src/zenserver/projectstore/oplogreferencedset.cpp)29
-rw-r--r--src/zenstore/projectstore.cpp (renamed from src/zenserver/projectstore/projectstore.cpp)4464
-rw-r--r--src/zenstore/referencemetadata.cpp (renamed from src/zenutil/referencemetadata.cpp)2
-rw-r--r--src/zenstore/referencemetadata.h (renamed from src/zenutil/include/zenutil/referencemetadata.h)0
-rw-r--r--src/zenstore/vfsimpl.cpp (renamed from src/zenserver/vfs/vfsimpl.cpp)45
-rw-r--r--src/zenstore/workspaces.cpp24
-rw-r--r--src/zenstore/xmake.lua2
-rw-r--r--src/zenstore/zenstore.cpp4
-rw-r--r--src/zenutil-test/xmake.lua2
-rw-r--r--src/zenutil-test/zenutil-test.cpp10
-rw-r--r--src/zenutil/commandlineoptions.cpp30
-rw-r--r--src/zenutil/include/zenutil/cache/cache.h34
-rw-r--r--src/zenutil/include/zenutil/rpcrecording.h (renamed from src/zenutil/include/zenutil/cache/rpcrecording.h)0
-rw-r--r--src/zenutil/include/zenutil/windows/windowsservice.h (renamed from src/zenutil/include/zenutil/windows/service.h)0
-rw-r--r--src/zenutil/include/zenutil/zenserverprocess.h3
-rw-r--r--src/zenutil/logging.cpp45
-rw-r--r--src/zenutil/rpcrecording.cpp (renamed from src/zenutil/cache/rpcrecording.cpp)2
-rw-r--r--src/zenutil/service.cpp2
-rw-r--r--src/zenutil/windows/windowsservice.cpp (renamed from src/zenutil/windows/service.cpp)2
-rw-r--r--src/zenutil/workerpools.cpp18
-rw-r--r--src/zenutil/zenserverprocess.cpp8
-rw-r--r--src/zenutil/zenutil.cpp9
210 files changed, 17455 insertions, 14412 deletions
diff --git a/src/zen/authutils.cpp b/src/zen/authutils.cpp
new file mode 100644
index 000000000..bc185535b
--- /dev/null
+++ b/src/zen/authutils.cpp
@@ -0,0 +1,247 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "authutils.h"
+
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zencore/logging.h>
+
+#include <zenhttp/auth/authmgr.h>
+#include <zenhttp/httpclient.h>
+#include <zenhttp/httpclientauth.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <json11.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+using namespace std::literals;
+
+std::string_view
+GetDefaultAccessTokenEnvVariableName()
+{
+#if ZEN_PLATFORM_WINDOWS
+ return "UE-CloudDataCacheAccessToken"sv;
+#endif
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ return "UE_CloudDataCacheAccessToken"sv;
+#endif
+}
+
+std::string
+ReadAccessTokenFromJsonFile(const std::filesystem::path& Path)
+{
+ if (!IsFile(Path))
+ {
+ throw std::runtime_error(fmt::format("the file '{}' does not exist", Path));
+ }
+ IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
+ std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
+ std::string JsonError;
+ json11::Json TokenInfo = json11::Json::parse(JsonText, JsonError);
+ if (!JsonError.empty())
+ {
+ throw std::runtime_error(fmt::format("failed parsing json file '{}'. Reason: '{}'", Path, JsonError));
+ }
+ const std::string AuthToken = TokenInfo["Token"].string_value();
+ if (AuthToken.empty())
+ {
+ throw std::runtime_error(fmt::format("the json file '{}' does not contain a value for \"Token\"", Path));
+ }
+ return AuthToken;
+}
+
+std::filesystem::path
+FindOidcTokenExePath(std::string_view OidcTokenAuthExecutablePath)
+{
+ if (OidcTokenAuthExecutablePath.empty())
+ {
+ const std::string OidcExecutableName = "OidcToken" ZEN_EXE_SUFFIX_LITERAL;
+ std::filesystem::path OidcTokenPath = (GetRunningExecutablePath().parent_path() / OidcExecutableName).make_preferred();
+ if (IsFile(OidcTokenPath))
+ {
+ return OidcTokenPath;
+ }
+ OidcTokenPath = (std::filesystem::current_path() / OidcExecutableName).make_preferred();
+ if (IsFile(OidcTokenPath))
+ {
+ return OidcTokenPath;
+ }
+ }
+ else
+ {
+ std::filesystem::path OidcTokenPath = std::filesystem::absolute(StringToPath(OidcTokenAuthExecutablePath)).make_preferred();
+ if (IsFile(OidcTokenPath))
+ {
+ return OidcTokenPath;
+ }
+ }
+ return {};
+};
+
+void
+AuthCommandLineOptions::AddOptions(cxxopts::Options& Ops)
+{
+ // Direct access token (may expire)
+ Ops.add_option("auth-token", "", "access-token", "Remote host access token", cxxopts::value(m_AccessToken), "<accesstoken>");
+ Ops.add_option("auth-token",
+ "",
+ "access-token-env",
+ "Name of environment variable that holds the remote host access token",
+ cxxopts::value(m_AccessTokenEnv)->default_value(std::string(GetDefaultAccessTokenEnvVariableName())),
+ "<envvariable>");
+ Ops.add_option("auth-token",
+ "",
+ "access-token-path",
+ "Path to json file that holds the remote host access token",
+ cxxopts::value(m_AccessTokenPath),
+ "<filepath>");
+
+ // Auth manager token encryption
+ Ops.add_option("security", "", "encryption-aes-key", "256 bit AES encryption key", cxxopts::value<std::string>(m_EncryptionKey), "");
+ Ops.add_option("security",
+ "",
+ "encryption-aes-iv",
+ "128 bit AES encryption initialization vector",
+ cxxopts::value<std::string>(m_EncryptionIV),
+ "");
+
+ // OpenId acccess token
+ Ops.add_option("openid",
+ "",
+ "openid-provider-name",
+ "Open ID provider name",
+ cxxopts::value<std::string>(m_OpenIdProviderName),
+ "Default");
+ Ops.add_option("openid", "", "openid-provider-url", "Open ID provider url", cxxopts::value<std::string>(m_OpenIdProviderUrl), "");
+ Ops.add_option("openid", "", "openid-client-id", "Open ID client id", cxxopts::value<std::string>(m_OpenIdClientId), "");
+ Ops.add_option("openid", "", "openid-refresh-token", "Open ID refresh token", cxxopts::value<std::string>(m_OpenIdRefreshToken), "");
+
+ // OAuth acccess token
+ Ops.add_option("oauth", "", "oauth-url", "OAuth provier url", cxxopts::value<std::string>(m_OAuthUrl)->default_value(""), "");
+ Ops.add_option("oauth", "", "oauth-clientid", "OAuth client id", cxxopts::value<std::string>(m_OAuthClientId)->default_value(""), "");
+ Ops.add_option("oauth",
+ "",
+ "oauth-clientsecret",
+ "OAuth client secret",
+ cxxopts::value<std::string>(m_OAuthClientSecret)->default_value(""),
+ "");
+ Ops.add_option("auth",
+ "",
+ "oidctoken-exe-path",
+ "Path to OidcToken executable",
+ cxxopts::value<std::string>(m_OidcTokenAuthExecutablePath)->default_value(""),
+ "");
+ Ops.add_option("auth",
+ "",
+ "oidctoken-exe-unattended",
+ "Set mode to unattended when launcing OidcToken executable",
+ cxxopts::value<bool>(m_OidcTokenUnattended),
+ "");
+};
+
+void
+AuthCommandLineOptions::ParseOptions(cxxopts::Options& Ops,
+ const std::filesystem::path& SystemRootDir,
+ HttpClientSettings& ClientSettings,
+ std::string_view HostUrl,
+ std::unique_ptr<AuthMgr>& Auth,
+ bool Quiet,
+ bool Hidden)
+{
+ auto CreateAuthMgr = [&]() {
+ ZEN_ASSERT(!SystemRootDir.empty());
+ if (!Auth)
+ {
+ if (m_EncryptionKey.empty())
+ {
+ m_EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456";
+ if (!Quiet)
+ {
+ ZEN_CONSOLE_WARN("Using default encryption key");
+ }
+ }
+
+ if (m_EncryptionIV.empty())
+ {
+ m_EncryptionIV = "0123456789abcdef";
+ if (!Quiet)
+ {
+ ZEN_CONSOLE_WARN("Using default encryption initialization vector");
+ }
+ }
+
+ AuthConfig AuthMgrConfig = {.RootDirectory = SystemRootDir / "auth",
+ .EncryptionKey = AesKey256Bit::FromString(m_EncryptionKey),
+ .EncryptionIV = AesIV128Bit::FromString(m_EncryptionIV)};
+ if (!AuthMgrConfig.EncryptionKey.IsValid())
+ {
+ throw OptionParseException(fmt::format("'--encryption-aes-key' ('{}') is malformed", m_EncryptionKey), Ops.help());
+ }
+ if (!AuthMgrConfig.EncryptionIV.IsValid())
+ {
+ throw OptionParseException(fmt::format("'--encryption-aes-iv' ('{}') is malformed", m_EncryptionIV), Ops.help());
+ }
+ Auth = AuthMgr::Create(AuthMgrConfig);
+ }
+ };
+
+ if (!m_OpenIdProviderUrl.empty() && !m_OpenIdClientId.empty())
+ {
+ CreateAuthMgr();
+ std::string ProviderName = m_OpenIdProviderName.empty() ? "Default" : m_OpenIdProviderName;
+ Auth->AddOpenIdProvider({.Name = ProviderName, .Url = m_OpenIdProviderUrl, .ClientId = m_OpenIdClientId});
+ if (!m_OpenIdRefreshToken.empty())
+ {
+ Auth->AddOpenIdToken({.ProviderName = ProviderName, .RefreshToken = m_OpenIdRefreshToken});
+ }
+ }
+
+ auto GetEnvAccessToken = [](const std::string& AccessTokenEnv) -> std::string {
+ if (!AccessTokenEnv.empty())
+ {
+ return GetEnvVariable(AccessTokenEnv);
+ }
+ return {};
+ };
+
+ if (!m_AccessToken.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(m_AccessToken);
+ }
+ else if (!m_AccessTokenPath.empty())
+ {
+ MakeSafeAbsolutePathÍnPlace(m_AccessTokenPath);
+ std::string ResolvedAccessToken = ReadAccessTokenFromJsonFile(m_AccessTokenPath);
+ if (!ResolvedAccessToken.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
+ }
+ }
+ else if (!m_OAuthUrl.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOAuthClientCredentials(
+ {.Url = m_OAuthUrl, .ClientId = m_OAuthClientId, .ClientSecret = m_OAuthClientSecret});
+ }
+ else if (!m_OpenIdProviderName.empty())
+ {
+ CreateAuthMgr();
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOpenIdProvider(*Auth, m_OpenIdProviderName);
+ }
+ else if (std::string ResolvedAccessToken = GetEnvAccessToken(m_AccessTokenEnv); !ResolvedAccessToken.empty())
+ {
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
+ }
+ else if (std::filesystem::path OidcTokenExePath = FindOidcTokenExePath(m_OidcTokenAuthExecutablePath); !OidcTokenExePath.empty())
+ {
+ ClientSettings.AccessTokenProvider =
+ httpclientauth::CreateFromOidcTokenExecutable(OidcTokenExePath, HostUrl, Quiet, m_OidcTokenUnattended, Hidden);
+ }
+
+ if (!ClientSettings.AccessTokenProvider)
+ {
+ CreateAuthMgr();
+ ClientSettings.AccessTokenProvider = httpclientauth::CreateFromDefaultOpenIdProvider(*Auth);
+ }
+}
+} // namespace zen
diff --git a/src/zen/authutils.h b/src/zen/authutils.h
new file mode 100644
index 000000000..8f041c8fc
--- /dev/null
+++ b/src/zen/authutils.h
@@ -0,0 +1,52 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "zen.h"
+
+namespace zen {
+
+struct HttpClientSettings;
+class AuthMgr;
+
+struct AuthCommandLineOptions
+{
+ // Direct access token (may expire)
+ std::string m_AccessToken;
+ std::string m_AccessTokenEnv;
+ std::filesystem::path m_AccessTokenPath;
+
+ // Auth manager token encryption
+ std::string m_EncryptionKey; // 256 bit AES encryption key
+ std::string m_EncryptionIV; // 128 bit AES initialization vector
+
+ // OpenId acccess token
+ std::string m_OpenIdProviderName;
+ std::string m_OpenIdProviderUrl;
+ std::string m_OpenIdClientId;
+ std::string m_OpenIdRefreshToken;
+
+ // OAuth acccess token
+ std::string m_OAuthUrl;
+ std::string m_OAuthClientId;
+ std::string m_OAuthClientSecret;
+
+ std::string m_OidcTokenAuthExecutablePath;
+ bool m_OidcTokenUnattended = false;
+
+ void AddOptions(cxxopts::Options& Ops);
+
+ void ParseOptions(cxxopts::Options& Ops,
+ const std::filesystem::path& SystemRootDir,
+ HttpClientSettings& InOutClientSettings,
+ std::string_view HostUrl,
+ std::unique_ptr<AuthMgr>& OutAuthMgr,
+ bool Quiet,
+ bool Hidden);
+};
+
+std::string ReadAccessTokenFromJsonFile(const std::filesystem::path& Path);
+std::string_view GetDefaultAccessTokenEnvVariableName();
+std::filesystem::path FindOidcTokenExePath(std::string_view OidcTokenAuthExecutablePath);
+
+} // namespace zen
diff --git a/src/zen/cmds/admin_cmd.cpp b/src/zen/cmds/admin_cmd.cpp
index dca6da6c4..502d1e799 100644
--- a/src/zen/cmds/admin_cmd.cpp
+++ b/src/zen/cmds/admin_cmd.cpp
@@ -4,15 +4,12 @@
#include <zencore/basicfile.h>
#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zenhttp/formatters.h>
#include <zenhttp/httpclient.h>
#include <zenhttp/httpcommon.h>
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
-ZEN_THIRD_PARTY_INCLUDES_END
-
using namespace std::literals;
namespace zen {
@@ -28,21 +25,21 @@ ScrubCommand::ScrubCommand()
ScrubCommand::~ScrubCommand() = default;
-int
+void
ScrubCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -52,19 +49,11 @@ ScrubCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (HttpClient::Response Response = Http.Post("/admin/scrub"sv, /* headers */ HttpClient::KeyValueMap{}, Params))
{
ZEN_CONSOLE("Scrub started OK: {}", Response.ToText());
-
- return 0;
- }
- else if (int StatusCode = (int)Response.StatusCode)
- {
- ZEN_CONSOLE_ERROR("Scrub start failed: {}: {} ({})", StatusCode, ReasonStringForHttpResultCode(StatusCode), Response.ToText());
}
else
{
- ZEN_CONSOLE_ERROR("Scrub start failed: {}", Response.ToText());
+ Response.ThrowError("Scrub start failed");
}
-
- return 1;
}
//////////////////////////////////////////////////////////////////////////
@@ -160,113 +149,115 @@ GcCommand::~GcCommand()
{
}
-int
+void
GcCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
- cpr::Parameters Params;
- Params.Add({"smallobjects", m_SmallObjects ? "true" : "false"});
+ HttpClient::KeyValueMap Params;
+ Params.Entries.insert({"smallobjects", m_SmallObjects ? "true" : "false"});
if (m_MaxCacheDuration != 0)
{
- Params.Add({"maxcacheduration", fmt::format("{}", m_MaxCacheDuration)});
+ Params.Entries.insert({"maxcacheduration", fmt::format("{}", m_MaxCacheDuration)});
}
if (m_DiskSizeSoftLimit != 0)
{
- Params.Add({"disksizesoftlimit", fmt::format("{}", m_DiskSizeSoftLimit)});
+ Params.Entries.insert({"disksizesoftlimit", fmt::format("{}", m_DiskSizeSoftLimit)});
}
- Params.Add({"skipcid", m_SkipCid ? "true" : "false"});
- Params.Add({"skipdelete", m_SkipDelete ? "true" : "false"});
+ Params.Entries.insert({"skipcid", m_SkipCid ? "true" : "false"});
+ Params.Entries.insert({"skipdelete", m_SkipDelete ? "true" : "false"});
if (m_ForceUseGCV1)
{
- throw OptionParseException("usegcv1 is deprecated and can no longer be used");
+ throw OptionParseException("'--usegcv1' is deprecated and can no longer be used", m_Options.help());
}
if (m_ForceUseGCV2)
{
- Params.Add({"forceusegcv2", "true"});
+ Params.Entries.insert({"forceusegcv2", "true"});
}
if (m_CompactBlockThreshold)
{
- Params.Add({"compactblockthreshold", fmt::format("{}", m_CompactBlockThreshold)});
+ Params.Entries.insert({"compactblockthreshold", fmt::format("{}", m_CompactBlockThreshold)});
}
IoHash LowRef = IoHash::Zero;
if (!m_ReferenceHashLow.empty())
{
if (m_ReferenceHashLow.length() != IoHash::StringLength)
{
- throw OptionParseException(fmt::format("reference-low must be a {} character hex string", IoHash::StringLength));
+ throw OptionParseException(fmt::format("'--reference-low' ('{}') is malformed, it must be a {} character hex string",
+ m_ReferenceHashLow,
+ IoHash::StringLength),
+ m_Options.help());
+ }
+ if (!IoHash::TryParse(m_ReferenceHashLow, LowRef))
+ {
+ throw OptionParseException(fmt::format("'--reference-low' ('{}') is malformed", m_ReferenceHashLow), m_Options.help());
}
- LowRef = IoHash::FromHexString(m_ReferenceHashLow);
}
IoHash HighRef = IoHash::Max;
if (!m_ReferenceHashHigh.empty())
{
if (m_ReferenceHashHigh.length() != IoHash::StringLength)
{
- throw OptionParseException(fmt::format("reference-high must be a {} character hex string", IoHash::StringLength));
+ throw OptionParseException(fmt::format("''--reference-high' ('{}') is malformed, it must be a {} character hex string",
+ m_ReferenceHashHigh,
+ IoHash::StringLength),
+ m_Options.help());
+ }
+ if (!IoHash::TryParse(m_ReferenceHashHigh, HighRef))
+ {
+ throw OptionParseException(fmt::format("'--reference-high' ('{}') is malformed", m_ReferenceHashHigh), m_Options.help());
}
- HighRef = IoHash::FromHexString(m_ReferenceHashHigh);
}
if (HighRef < LowRef)
{
- throw OptionParseException(fmt::format("invalid reference range, reference-high must be higher value than reference-low"));
+ throw OptionParseException(
+ fmt::format("'--reference-high' ('{}') is invalid, it must be a higher value than '--reference-low' ('{}')",
+ m_ReferenceHashHigh,
+ m_ReferenceHashLow),
+ m_Options.help());
}
if (!m_ReferenceHashLow.empty() || !m_ReferenceHashHigh.empty())
{
- Params.Add({"referencehashlow", LowRef.ToHexString()});
- Params.Add({"referencehashhigh", HighRef.ToHexString()});
+ Params.Entries.insert({"referencehashlow", LowRef.ToHexString()});
+ Params.Entries.insert({"referencehashhigh", HighRef.ToHexString()});
}
- Params.Add({"verbose", m_Verbose ? "true" : "false"});
- Params.Add({"singlethreaded", m_SingleThreaded ? "true" : "false"});
+ Params.Entries.insert({"verbose", m_Verbose ? "true" : "false"});
+ Params.Entries.insert({"singlethreaded", m_SingleThreaded ? "true" : "false"});
if (m_StoreCacheAttachmentMetaData)
{
- Params.Add({"storecacheattachmentmetadata", m_StoreCacheAttachmentMetaData ? "true" : "false"});
+ Params.Entries.insert({"storecacheattachmentmetadata", m_StoreCacheAttachmentMetaData ? "true" : "false"});
}
if (m_StoreProjectAttachmentMetaData)
{
- Params.Add({"storeprojectattachmentmetadata", m_StoreProjectAttachmentMetaData ? "true" : "false"});
- }
- Params.Add({"enablevalidation", m_EnableValidation ? "true" : "false"});
-
- cpr::Session Session;
- Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
- Session.SetUrl({fmt::format("{}/admin/gc", m_HostName)});
- Session.SetParameters(Params);
-
- cpr::Response Result = Session.Post();
-
- if (zen::IsHttpSuccessCode(Result.status_code))
- {
- ZEN_CONSOLE("OK: {}", Result.text);
- return 0;
+ Params.Entries.insert({"storeprojectattachmentmetadata", m_StoreProjectAttachmentMetaData ? "true" : "false"});
}
+ Params.Entries.insert({"enablevalidation", m_EnableValidation ? "true" : "false"});
- if (Result.status_code)
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Post("/admin/gc"sv, HttpClient::Accept(HttpContentType::kJSON), Params))
{
- ZEN_CONSOLE_ERROR("GC start failed: {}: {} ({})", Result.status_code, Result.reason, Result.text);
+ ZEN_CONSOLE("OK: {}", Response.ToText());
}
else
{
- ZEN_CONSOLE_ERROR("GC start failed: {}", Result.error.message);
+ Response.ThrowError("GC start failed");
}
-
- return 1;
}
GcStatusCommand::GcStatusCommand()
@@ -280,49 +271,32 @@ GcStatusCommand::~GcStatusCommand()
{
}
-int
+void
GcStatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
- }
-
- cpr::Session Session;
- Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
- Session.SetUrl({fmt::format("{}/admin/gc", m_HostName)});
- if (m_Details)
- {
- Session.SetParameters({{"details", "true"}});
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
- cpr::Response Result = Session.Get();
-
- if (zen::IsHttpSuccessCode(Result.status_code))
- {
- ZEN_CONSOLE("OK: {}", Result.text);
- return 0;
- }
-
- if (Result.status_code)
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Get("/admin/gc"sv, HttpClient::Accept(HttpContentType::kJSON)))
{
- ZEN_CONSOLE_ERROR("GC status failed: {}: {} ({})", Result.status_code, Result.reason, Result.text);
+ ZEN_CONSOLE("OK: {}", Response.ToText());
}
else
{
- ZEN_CONSOLE_ERROR("GC status failed: {}", Result.error.message);
+ Response.ThrowError("Gc status failed");
}
-
- return 1;
}
GcStopCommand::GcStopCommand()
@@ -335,48 +309,39 @@ GcStopCommand::~GcStopCommand()
{
}
-int
+void
GcStopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
- }
-
- cpr::Session Session;
- Session.SetUrl({fmt::format("{}/admin/gc-stop", m_HostName)});
- cpr::Response Result = Session.Post();
-
- if (static_cast<HttpResponseCode>(Result.status_code) == HttpResponseCode::Accepted)
- {
- ZEN_CONSOLE("OK: {}", "Cancel request accepted");
- return 0;
- }
- else if (zen::IsHttpSuccessCode(Result.status_code))
- {
- ZEN_CONSOLE("OK: {}", "No GC running");
- return 0;
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
- if (Result.status_code)
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Post("/admin/gc-stop"sv, HttpClient::Accept(HttpContentType::kJSON)))
{
- ZEN_CONSOLE_ERROR("GC status failed: {}: {} ({})", Result.status_code, Result.reason, Result.text);
+ if (Response.StatusCode == HttpResponseCode::Accepted)
+ {
+ ZEN_CONSOLE("OK: {}", "Cancel request accepted");
+ }
+ else
+ {
+ ZEN_CONSOLE("OK: {}", "No GC running");
+ }
}
else
{
- ZEN_CONSOLE_ERROR("GC status failed: {}", Result.error.message);
+ Response.ThrowError("Gc stop failed");
}
-
- return 1;
}
////////////////////////////////////////////
@@ -391,7 +356,7 @@ JobCommand::JobCommand()
JobCommand::~JobCommand() = default;
-int
+void
JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -400,14 +365,14 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -416,8 +381,7 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (m_JobId == 0)
{
- throw OptionParseException("Job id must be given");
- return 1;
+ throw OptionParseException("'--jobid' is required", m_Options.help());
}
}
std::string Url = m_JobId != 0 ? fmt::format("/admin/jobs/{}", m_JobId) : "/admin/jobs";
@@ -431,7 +395,6 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
else
{
Result.ThrowError("failed cancelling job"sv);
- return 1;
}
}
else if (HttpClient::Response Result = Http.Get(Url, HttpClient::Accept(ZenContentType::kJSON)))
@@ -441,10 +404,7 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
else
{
Result.ThrowError("failed fetching job info"sv);
- return 1;
}
-
- return 0;
}
////////////////////////////////////////////
@@ -479,7 +439,7 @@ LoggingCommand::LoggingCommand()
LoggingCommand::~LoggingCommand() = default;
-int
+void
LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -488,14 +448,14 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -514,8 +474,8 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else
{
- ZEN_CONSOLE_ERROR("Invalid value for parameter 'cache-write-log'. Use 'enable' or 'disable'");
- return 1;
+ throw OptionParseException(fmt::format("'--cache-write-log' ('{}') is invalid, use 'enable' or 'disable'", m_CacheWriteLog),
+ m_Options.help());
}
}
@@ -531,8 +491,8 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else
{
- ZEN_CONSOLE_ERROR("Invalid value for parameter 'cache-access-log'. Use 'enable' or 'disable'");
- return 1;
+ throw OptionParseException(fmt::format("'--cache-access-log' ('{}') is invalid, use 'enable' or 'disable'", m_CacheAccessLog),
+ m_Options.help());
}
}
@@ -548,49 +508,37 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ZEN_CONSOLE("{}", Result.ToText());
const CbObject LogsResponse = Result.AsObject();
- auto CopyLog = [](std::string_view SourceName, std::string_view SourcePath, std::string_view TargetPath) -> bool {
+ auto CopyLog = [](std::string_view SourceName, std::string_view SourcePath, std::string_view TargetPath) {
if (SourcePath.empty())
{
- ZEN_CONSOLE_ERROR("Failed to retrieve {} log path", SourceName);
- return false;
+ throw std::runtime_error(fmt::format("Failed to retrieve {} log path", SourceName));
}
if (!CopyFile(SourcePath, TargetPath, {}))
{
- ZEN_CONSOLE_ERROR("Failed to copy {} log file {} to output file '{}'", SourceName, SourcePath, TargetPath);
- return false;
+ throw std::runtime_error(
+ fmt::format("Failed to copy {} log file {} to output file '{}'", SourceName, SourcePath, TargetPath));
}
- return true;
};
if (!m_ServerLogTarget.empty())
{
- if (!CopyLog("server", LogsResponse["Logfile"].AsString(), m_ServerLogTarget))
- {
- return 1;
- }
+ CopyLog("server", LogsResponse["Logfile"].AsString(), m_ServerLogTarget);
}
if (!m_CacheLogTarget.empty())
{
- if (!CopyLog("cache", LogsResponse["cache"].AsObjectView()["Logfile"].AsString(), m_CacheLogTarget))
- {
- return 1;
- }
+ CopyLog("cache", LogsResponse["cache"].AsObjectView()["Logfile"].AsString(), m_CacheLogTarget);
}
if (!m_HttpLogTarget.empty())
{
- if (!CopyLog("http", LogsResponse["http"].AsObjectView()["Logfile"].AsString(), m_HttpLogTarget))
- {
- return 1;
- }
+ CopyLog("http", LogsResponse["http"].AsObjectView()["Logfile"].AsString(), m_HttpLogTarget);
}
}
else
{
Result.ThrowError("failed fetching log info"sv);
- return 1;
}
- return 0;
+ return;
}
if (HttpClient::Response Result = Http.Post("/admin/logs", HttpClient::KeyValueMap{}, Parameters))
{
@@ -599,10 +547,7 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
else
{
Result.ThrowError("failed setting log info"sv);
- return 1;
}
-
- return 0;
}
//////////////////////////////////////////////////////////////////////////
@@ -615,21 +560,21 @@ FlushCommand::FlushCommand()
FlushCommand::~FlushCommand() = default;
-int
+void
FlushCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
zen::HttpClient Http(m_HostName);
@@ -638,18 +583,12 @@ FlushCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_CONSOLE("OK: {}", Response.ToText());
- return 0;
- }
- else if (int StatusCode = (int)Response.StatusCode)
- {
- ZEN_CONSOLE_ERROR("Flush failed: {}: {} ({})", StatusCode, ReasonStringForHttpResultCode(StatusCode), Response.ToText());
+ return;
}
else
{
- ZEN_CONSOLE_ERROR("Flush failed: {}", Response.ToText());
+ Response.ThrowError("Flush failed");
}
-
- return 1;
}
//////////////////////////////////////////////////////////////////////////
@@ -693,29 +632,29 @@ TryCopy(const std::filesystem::path& Source, const std::filesystem::path& Target
return CopyFile(Source, Target, Options);
}
-int
+void
CopyStateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
if (m_DataPath.empty())
{
- throw OptionParseException("data path must be given");
+ throw OptionParseException("'--data-path' is required", m_Options.help());
}
if (!IsDir(m_DataPath))
{
- throw OptionParseException("data path must exist");
+ throw std::runtime_error(fmt::format("'--data-path' '{}' must exist", m_DataPath));
}
if (m_TargetPath.empty())
{
- throw OptionParseException("target path must be given");
+ throw OptionParseException("'--target-path' is required", m_Options.help());
}
std::filesystem::path RootManifestPath = m_DataPath / "root_manifest";
@@ -723,7 +662,8 @@ CopyStateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!TryCopy(RootManifestPath, TargetRootManifestPath))
{
- throw OptionParseException("data path is invalid, missing root_manifest");
+ throw std::runtime_error(
+ fmt::format("'--data-path' ('{}') is invalid, missing root_manifest at '{}'", m_DataPath, RootManifestPath));
}
std::filesystem::path CachePath = m_DataPath / "cache";
@@ -828,8 +768,6 @@ CopyStateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
}
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/admin_cmd.h b/src/zen/cmds/admin_cmd.h
index c593b2cac..4f97b7ad4 100644
--- a/src/zen/cmds/admin_cmd.h
+++ b/src/zen/cmds/admin_cmd.h
@@ -16,7 +16,7 @@ public:
ScrubCommand();
~ScrubCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -35,7 +35,7 @@ public:
GcCommand();
~GcCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -64,7 +64,7 @@ public:
GcStatusCommand();
~GcStatusCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -79,7 +79,7 @@ public:
GcStopCommand();
~GcStopCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -95,7 +95,7 @@ public:
JobCommand();
~JobCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -113,7 +113,7 @@ public:
LoggingCommand();
~LoggingCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -135,7 +135,7 @@ public:
FlushCommand();
~FlushCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -151,7 +151,7 @@ public:
CopyStateCommand();
~CopyStateCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/bench_cmd.cpp b/src/zen/cmds/bench_cmd.cpp
index d904a51f6..b9c45a328 100644
--- a/src/zen/cmds/bench_cmd.cpp
+++ b/src/zen/cmds/bench_cmd.cpp
@@ -25,14 +25,14 @@ BenchCommand::BenchCommand()
BenchCommand::~BenchCommand() = default;
-int
+void
BenchCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
#if ZEN_PLATFORM_WINDOWS
@@ -97,7 +97,7 @@ BenchCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
#endif
- return 0;
+ return;
}
} // namespace zen
diff --git a/src/zen/cmds/bench_cmd.h b/src/zen/cmds/bench_cmd.h
index 29d7fcc08..ed123be75 100644
--- a/src/zen/cmds/bench_cmd.h
+++ b/src/zen/cmds/bench_cmd.h
@@ -12,7 +12,7 @@ public:
BenchCommand();
~BenchCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp
index 0c77db2a0..80ff385b0 100644
--- a/src/zen/cmds/builds_cmd.cpp
+++ b/src/zen/cmds/builds_cmd.cpp
@@ -8,11 +8,11 @@
#include <zencore/compactbinaryfmt.h>
#include <zencore/compactbinaryvalue.h>
#include <zencore/compress.h>
-#include <zencore/config.h>
#include <zencore/except.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/session.h>
#include <zencore/stream.h>
@@ -23,23 +23,22 @@
#include <zenhttp/httpclient.h>
#include <zenhttp/httpclientauth.h>
#include <zenhttp/httpcommon.h>
+#include <zenremotestore/builds/buildstoragecache.h>
+#include <zenremotestore/builds/filebuildstorage.h>
+#include <zenremotestore/builds/jupiterbuildstorage.h>
+#include <zenremotestore/chunking/chunkblock.h>
+#include <zenremotestore/chunking/chunkedcontent.h>
+#include <zenremotestore/chunking/chunkedfile.h>
+#include <zenremotestore/chunking/chunkingcontroller.h>
+#include <zenremotestore/jupiter/jupiterhost.h>
#include <zenutil/bufferedwritefilecache.h>
-#include <zenutil/buildstoragecache.h>
-#include <zenutil/chunkblock.h>
-#include <zenutil/chunkedcontent.h>
-#include <zenutil/chunkedfile.h>
-#include <zenutil/chunkingcontroller.h>
-#include <zenutil/filebuildstorage.h>
-#include <zenutil/jupiter/jupiterbuildstorage.h>
-#include <zenutil/jupiter/jupitersession.h>
-#include <zenutil/parallelwork.h>
#include <zenutil/wildcard.h>
#include <zenutil/workerpools.h>
#include <zenutil/zenserverprocess.h>
#include <signal.h>
#include <memory>
-#include <regex>
+#include <numeric>
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
@@ -224,16 +223,19 @@ namespace {
}
static const size_t DefaultMaxBlockSize = 64u * 1024u * 1024u;
+ static const size_t DefaultMaxChunksPerBlock = 4u * 1000u;
static const size_t DefaultMaxChunkEmbedSize = 3u * 512u * 1024u;
struct ChunksBlockParameters
{
size_t MaxBlockSize = DefaultMaxBlockSize;
+ size_t MaxChunksPerBlock = DefaultMaxChunksPerBlock;
size_t MaxChunkEmbedSize = DefaultMaxChunkEmbedSize;
};
const ChunksBlockParameters DefaultChunksBlockParams{
.MaxBlockSize = 32u * 1024u * 1024u,
+ .MaxChunksPerBlock = DefaultMaxChunksPerBlock,
.MaxChunkEmbedSize = 2u * 1024u * 1024u // DefaultChunkedParams.MaxSize
};
const uint64_t DefaultPreferredMultipartChunkSize = 32u * 1024u * 1024u;
@@ -366,16 +368,34 @@ namespace {
);
- bool IncludePath(const std::string_view IncludeWildcard, const std::string_view ExcludeWildcard, const std::filesystem::path& Path)
+ bool IncludePath(std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards,
+ const std::filesystem::path& Path)
{
- const std::string PathString = Path.generic_string();
- if (!IncludeWildcard.empty() && !MatchWildcard(IncludeWildcard, PathString, /*CaseSensitive*/ false))
+ const std::string PathString = Path.generic_string();
+ bool IncludePath = true;
+ if (!IncludeWildcards.empty())
{
- return false;
+ IncludePath = false;
+ for (const std::string& IncludeWildcard : IncludeWildcards)
+ {
+ if (MatchWildcard(IncludeWildcard, PathString, /*CaseSensitive*/ false))
+ {
+ IncludePath = true;
+ break;
+ }
+ }
+ if (!IncludePath)
+ {
+ return false;
+ }
}
- if (!ExcludeWildcard.empty() && MatchWildcard(ExcludeWildcard, PathString, /*CaseSensitive*/ false))
+ for (const std::string& ExcludeWildcard : ExcludeWildcards)
{
- return false;
+ if (MatchWildcard(ExcludeWildcard, PathString, /*CaseSensitive*/ false))
+ {
+ return false;
+ }
}
return true;
}
@@ -596,7 +616,7 @@ namespace {
std::atomic<uint64_t> DiscoveredItemCount = 0;
std::atomic<uint64_t> DeletedItemCount = 0;
std::atomic<uint64_t> DeletedByteCount = 0;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
struct AsyncVisitor : public GetDirectoryContentVisitor
{
@@ -784,72 +804,6 @@ namespace {
return CleanWipe;
}
- bool ParseCloudUrl(std::string_view InUrl,
- std::string& OutHost,
- std::string& OutNamespace,
- std::string& OutBucket,
- std::string& OutBuildId)
- {
- std::string Url(RemoveQuotes(InUrl));
- const std::string_view ExtendedApiString = "api/v2/builds/";
- if (auto ApiString = ToLower(Url).find(ExtendedApiString); ApiString != std::string::npos)
- {
- Url.erase(ApiString, ExtendedApiString.length());
- }
-
- const std::string ArtifactURLRegExString = R"((http[s]?:\/\/.*?)\/(.*?)\/(.*?)\/(.*))";
- const std::regex ArtifactURLRegEx(ArtifactURLRegExString, std::regex::ECMAScript | std::regex::icase);
- std::match_results<std::string_view::const_iterator> MatchResults;
- std::string_view UrlToParse(Url);
- if (regex_match(begin(UrlToParse), end(UrlToParse), MatchResults, ArtifactURLRegEx) && MatchResults.size() == 5)
- {
- auto GetMatch = [&MatchResults](uint32_t Index) -> std::string_view {
- ZEN_ASSERT(Index < MatchResults.size());
-
- const auto& Match = MatchResults[Index];
-
- return std::string_view(&*Match.first, Match.second - Match.first);
- };
-
- const std::string_view Host = GetMatch(1);
- const std::string_view Namespace = GetMatch(2);
- const std::string_view Bucket = GetMatch(3);
- const std::string_view BuildId = GetMatch(4);
-
- OutHost = Host;
- OutNamespace = Namespace;
- OutBucket = Bucket;
- OutBuildId = BuildId;
- return true;
- }
- else
- {
- return false;
- }
- }
-
- std::string ReadAccessTokenFromFile(const std::filesystem::path& Path)
- {
- if (!IsFile(Path))
- {
- throw std::runtime_error(fmt::format("the file '{}' does not exist", Path));
- }
- IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
- std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
- std::string JsonError;
- json11::Json TokenInfo = json11::Json::parse(JsonText, JsonError);
- if (!JsonError.empty())
- {
- throw std::runtime_error(fmt::format("failed parsing json file '{}'. Reason: '{}'", Path, JsonError));
- }
- const std::string AuthToken = TokenInfo["Token"].string_value();
- if (AuthToken.empty())
- {
- throw std::runtime_error(fmt::format("the json file '{}' does not contain a value for \"Token\"", Path));
- }
- return AuthToken;
- }
-
IoBuffer WriteToTempFile(CompositeBuffer&& Buffer,
const std::filesystem::path& TempFolderPath,
const IoHash& Hash,
@@ -1682,6 +1636,275 @@ namespace {
return Result;
}
+ struct BlockRangeDescriptor
+ {
+ uint32_t BlockIndex = (uint32_t)-1;
+ uint64_t RangeStart = 0;
+ uint64_t RangeLength = 0;
+ uint32_t ChunkBlockIndexStart = 0;
+ uint32_t ChunkBlockIndexCount = 0;
+ };
+
+ BlockRangeDescriptor MergeBlockRanges(std::span<const BlockRangeDescriptor> Ranges)
+ {
+ ZEN_ASSERT(Ranges.size() > 1);
+ const BlockRangeDescriptor& First = Ranges.front();
+ const BlockRangeDescriptor& Last = Ranges.back();
+
+ return BlockRangeDescriptor{
+ .BlockIndex = First.BlockIndex,
+ .RangeStart = First.RangeStart,
+ .RangeLength = Last.RangeStart + Last.RangeLength - First.RangeStart,
+ .ChunkBlockIndexStart = First.ChunkBlockIndexStart,
+ .ChunkBlockIndexCount = Last.ChunkBlockIndexStart + Last.ChunkBlockIndexCount - First.ChunkBlockIndexStart};
+ }
+
+ std::optional<std::vector<BlockRangeDescriptor>> MakeOptionalBlockRangeVector(uint64_t TotalBlockSize,
+ const BlockRangeDescriptor& Range)
+ {
+ if (Range.RangeLength == TotalBlockSize)
+ {
+ return {};
+ }
+ else
+ {
+ return std::vector<BlockRangeDescriptor>{Range};
+ }
+ };
+
+ struct BlockRangeLimit
+ {
+ uint16_t SizePercent;
+ uint16_t MaxRangeCount;
+ };
+
+ static const uint16_t FullBlockRangePercentLimit = 95;
+
+ static const std::vector<BlockRangeLimit> ForceMergeLimits = {{.SizePercent = FullBlockRangePercentLimit, .MaxRangeCount = 1},
+ {.SizePercent = 90, .MaxRangeCount = 2},
+ {.SizePercent = 85, .MaxRangeCount = 8},
+ {.SizePercent = 80, .MaxRangeCount = 16},
+ {.SizePercent = 70, .MaxRangeCount = 32},
+ {.SizePercent = 60, .MaxRangeCount = 48},
+ {.SizePercent = 2, .MaxRangeCount = 56},
+ {.SizePercent = 0, .MaxRangeCount = 64}};
+
+ const BlockRangeLimit* GetBlockRangeLimitForRange(std::span<const BlockRangeLimit> Limits,
+ uint64_t TotalBlockSize,
+ std::span<const BlockRangeDescriptor> Ranges)
+ {
+ if (Ranges.size() > 1)
+ {
+ const std::uint64_t WantedSize =
+ std::accumulate(Ranges.begin(), Ranges.end(), uint64_t(0), [](uint64_t Current, const BlockRangeDescriptor& Range) {
+ return Current + Range.RangeLength;
+ });
+
+ const double RangeRequestedPercent = (WantedSize * 100.0) / TotalBlockSize;
+
+ for (const BlockRangeLimit& Limit : Limits)
+ {
+ if (RangeRequestedPercent >= Limit.SizePercent && Ranges.size() > Limit.MaxRangeCount)
+ {
+ return &Limit;
+ }
+ }
+ }
+ return nullptr;
+ };
+
+ std::vector<BlockRangeDescriptor> CollapseBlockRanges(const uint64_t AlwaysAcceptableGap,
+ std::span<const BlockRangeDescriptor> BlockRanges)
+ {
+ ZEN_ASSERT(BlockRanges.size() > 1);
+ std::vector<BlockRangeDescriptor> CollapsedBlockRanges;
+
+ auto BlockRangesIt = BlockRanges.begin();
+ CollapsedBlockRanges.push_back(*BlockRangesIt++);
+ for (; BlockRangesIt != BlockRanges.end(); BlockRangesIt++)
+ {
+ BlockRangeDescriptor& LastRange = CollapsedBlockRanges.back();
+
+ const uint64_t BothRangeSize = BlockRangesIt->RangeLength + LastRange.RangeLength;
+
+ const uint64_t Gap = BlockRangesIt->RangeStart - (LastRange.RangeStart + LastRange.RangeLength);
+ if (Gap <= Max(BothRangeSize / 16, AlwaysAcceptableGap))
+ {
+ LastRange.ChunkBlockIndexCount =
+ (BlockRangesIt->ChunkBlockIndexStart + BlockRangesIt->ChunkBlockIndexCount) - LastRange.ChunkBlockIndexStart;
+ LastRange.RangeLength = (BlockRangesIt->RangeStart + BlockRangesIt->RangeLength) - LastRange.RangeStart;
+ }
+ else
+ {
+ CollapsedBlockRanges.push_back(*BlockRangesIt);
+ }
+ }
+
+ return CollapsedBlockRanges;
+ };
+
+ uint64_t CalculateNextGap(std::span<const BlockRangeDescriptor> BlockRanges)
+ {
+ ZEN_ASSERT(BlockRanges.size() > 1);
+ uint64_t AcceptableGap = (uint64_t)-1;
+ for (size_t RangeIndex = 0; RangeIndex < BlockRanges.size() - 1; RangeIndex++)
+ {
+ const BlockRangeDescriptor& Range = BlockRanges[RangeIndex];
+ const BlockRangeDescriptor& NextRange = BlockRanges[RangeIndex + 1];
+
+ const uint64_t Gap = NextRange.RangeStart - (Range.RangeStart + Range.RangeLength);
+ AcceptableGap = Min(Gap, AcceptableGap);
+ }
+ AcceptableGap = RoundUp(AcceptableGap, 16u * 1024u);
+ return AcceptableGap;
+ };
+
+ std::optional<std::vector<BlockRangeDescriptor>> CalculateBlockRanges(uint32_t BlockIndex,
+ const ChunkBlockDescription& BlockDescription,
+ std::span<const uint32_t> BlockChunkIndexNeeded,
+ bool LimitToSingleRange,
+ const uint64_t ChunkStartOffsetInBlock,
+ const uint64_t TotalBlockSize,
+ uint64_t& OutTotalWantedChunksSize)
+ {
+ ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis");
+
+ std::vector<BlockRangeDescriptor> BlockRanges;
+ {
+ uint64_t CurrentOffset = ChunkStartOffsetInBlock;
+ uint32_t ChunkBlockIndex = 0;
+ uint32_t NeedBlockChunkIndexOffset = 0;
+ BlockRangeDescriptor NextRange{.BlockIndex = BlockIndex};
+ while (NeedBlockChunkIndexOffset < BlockChunkIndexNeeded.size() && ChunkBlockIndex < BlockDescription.ChunkRawHashes.size())
+ {
+ const uint32_t ChunkCompressedLength = BlockDescription.ChunkCompressedLengths[ChunkBlockIndex];
+ if (ChunkBlockIndex < BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
+ {
+ if (NextRange.RangeLength > 0)
+ {
+ BlockRanges.push_back(NextRange);
+ NextRange = {.BlockIndex = BlockIndex};
+ }
+ ChunkBlockIndex++;
+ CurrentOffset += ChunkCompressedLength;
+ }
+ else if (ChunkBlockIndex == BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
+ {
+ if (NextRange.RangeLength == 0)
+ {
+ NextRange.RangeStart = CurrentOffset;
+ NextRange.ChunkBlockIndexStart = ChunkBlockIndex;
+ }
+ NextRange.RangeLength += ChunkCompressedLength;
+ NextRange.ChunkBlockIndexCount++;
+ ChunkBlockIndex++;
+ CurrentOffset += ChunkCompressedLength;
+ NeedBlockChunkIndexOffset++;
+ }
+ else
+ {
+ ZEN_ASSERT(false);
+ }
+ }
+ if (NextRange.RangeLength > 0)
+ {
+ BlockRanges.push_back(NextRange);
+ }
+ }
+ ZEN_ASSERT(!BlockRanges.empty());
+
+ OutTotalWantedChunksSize =
+ std::accumulate(BlockRanges.begin(), BlockRanges.end(), uint64_t(0), [](uint64_t Current, const BlockRangeDescriptor& Range) {
+ return Current + Range.RangeLength;
+ });
+
+ double RangeWantedPercent = (OutTotalWantedChunksSize * 100.0) / TotalBlockSize;
+
+ if (BlockRanges.size() == 1)
+ {
+ ZEN_CONSOLE_VERBOSE("Range request of {} ({:.2f}%) using single range from block {} ({}) as is",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize));
+ return BlockRanges;
+ }
+
+ if (LimitToSingleRange)
+ {
+ const BlockRangeDescriptor MergedRange = MergeBlockRanges(BlockRanges);
+ const double RangeRequestedPercent = (MergedRange.RangeLength * 100.0) / TotalBlockSize;
+ const double WastedPercent = ((MergedRange.RangeLength - OutTotalWantedChunksSize) * 100.0) / MergedRange.RangeLength;
+ ZEN_CONSOLE_VERBOSE(
+ "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) limited to single block range {} ({:.2f}%) wasting "
+ "{:.2f}% ({})",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockRanges.size(),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ NiceBytes(MergedRange.RangeLength),
+ RangeRequestedPercent,
+ WastedPercent,
+ NiceBytes(MergedRange.RangeLength - OutTotalWantedChunksSize));
+ return MakeOptionalBlockRangeVector(TotalBlockSize, MergedRange);
+ }
+
+ if (RangeWantedPercent > FullBlockRangePercentLimit)
+ {
+ const BlockRangeDescriptor MergedRange = MergeBlockRanges(BlockRanges);
+ const double RangeRequestedPercent = (MergedRange.RangeLength * 100.0) / TotalBlockSize;
+ const double WastedPercent = ((MergedRange.RangeLength - OutTotalWantedChunksSize) * 100.0) / MergedRange.RangeLength;
+ ZEN_CONSOLE_VERBOSE(
+ "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) exceeds {}%. Merged to single block range {} "
+ "({:.2f}%) wasting {:.2f}% ({})",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockRanges.size(),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ FullBlockRangePercentLimit,
+ NiceBytes(MergedRange.RangeLength),
+ RangeRequestedPercent,
+ WastedPercent,
+ NiceBytes(MergedRange.RangeLength - OutTotalWantedChunksSize));
+ return MakeOptionalBlockRangeVector(TotalBlockSize, MergedRange);
+ }
+
+ std::vector<BlockRangeDescriptor> CollapsedBlockRanges = CollapseBlockRanges(16u * 1024u, BlockRanges);
+ while (GetBlockRangeLimitForRange(ForceMergeLimits, TotalBlockSize, CollapsedBlockRanges))
+ {
+ CollapsedBlockRanges = CollapseBlockRanges(CalculateNextGap(CollapsedBlockRanges), CollapsedBlockRanges);
+ }
+
+ const std::uint64_t WantedCollapsedSize =
+ std::accumulate(CollapsedBlockRanges.begin(),
+ CollapsedBlockRanges.end(),
+ uint64_t(0),
+ [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
+
+ const double CollapsedRangeRequestedPercent = (WantedCollapsedSize * 100.0) / TotalBlockSize;
+
+ {
+ const double WastedPercent = ((WantedCollapsedSize - OutTotalWantedChunksSize) * 100.0) / WantedCollapsedSize;
+
+ ZEN_CONSOLE_VERBOSE(
+ "Range request of {} ({:.2f}%) using {} ranges from block {} ({}) collapsed to {} {:.2f}% using {} ranges wasting {:.2f}% "
+ "({})",
+ NiceBytes(OutTotalWantedChunksSize),
+ RangeWantedPercent,
+ BlockRanges.size(),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ NiceBytes(WantedCollapsedSize),
+ CollapsedRangeRequestedPercent,
+ CollapsedBlockRanges.size(),
+ WastedPercent,
+ NiceBytes(WantedCollapsedSize - OutTotalWantedChunksSize));
+ return CollapsedBlockRanges;
+ }
+ };
+
class BufferedOpenFile
{
public:
@@ -2171,7 +2394,7 @@ namespace {
WorkerThreadPool& NetworkPool = GetNetworkPool();
WorkerThreadPool& VerifyPool = GetIOWorkerPool();
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
const std::filesystem::path TempFolder = ".zen-tmp";
@@ -2372,6 +2595,7 @@ namespace {
void ArrangeChunksIntoBlocks(const ChunkedFolderContent& Content,
const ChunkedContentLookup& Lookup,
uint64_t MaxBlockSize,
+ uint64_t MaxChunksPerBlock,
std::vector<uint32_t>& ChunkIndexes,
std::vector<std::vector<uint32_t>>& OutBlocks)
{
@@ -2400,7 +2624,7 @@ namespace {
const uint32_t ChunkIndex = ChunkIndexes[ChunkIndexOffset];
const uint64_t ChunkSize = Content.ChunkedContent.ChunkRawSizes[ChunkIndex];
- if ((BlockSize + ChunkSize) > MaxBlockSize)
+ if (((BlockSize + ChunkSize) > MaxBlockSize) || (ChunkIndexOffset - ChunkIndexStart) > MaxChunksPerBlock)
{
// Within the span of MaxBlockSizeLowThreshold and MaxBlockSize, see if there is a break
// between source paths for chunks. Break the block at the last such break if any.
@@ -2575,11 +2799,12 @@ namespace {
struct GeneratedBlocks
{
- std::vector<ChunkBlockDescription> BlockDescriptions;
- std::vector<uint64_t> BlockSizes;
- std::vector<CompositeBuffer> BlockHeaders;
- std::vector<CbObject> BlockMetaDatas;
- std::vector<bool> MetaDataHasBeenUploaded;
+ std::vector<ChunkBlockDescription> BlockDescriptions;
+ std::vector<uint64_t> BlockSizes;
+ std::vector<CompositeBuffer> BlockHeaders;
+ std::vector<CbObject> BlockMetaDatas;
+ std::vector<uint8_t>
+ MetaDataHasBeenUploaded; // NOTE: Do not use std::vector<bool> here as this vector is modified by multiple threads
tsl::robin_map<IoHash, size_t, IoHash::Hasher> BlockHashToBlockIndex;
};
@@ -2604,7 +2829,7 @@ namespace {
OutBlocks.BlockSizes.resize(NewBlockCount);
OutBlocks.BlockMetaDatas.resize(NewBlockCount);
OutBlocks.BlockHeaders.resize(NewBlockCount);
- OutBlocks.MetaDataHasBeenUploaded.resize(NewBlockCount, false);
+ OutBlocks.MetaDataHasBeenUploaded.resize(NewBlockCount, 0);
OutBlocks.BlockHashToBlockIndex.reserve(NewBlockCount);
RwLock Lock;
@@ -2615,7 +2840,7 @@ namespace {
FilteredRate FilteredGeneratedBytesPerSecond;
FilteredRate FilteredUploadedBytesPerSecond;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
std::atomic<uint64_t> QueuedPendingBlocksForUpload = 0;
@@ -2849,7 +3074,7 @@ namespace {
FilteredRate FilteredCompressedBytesPerSecond;
FilteredRate FilteredUploadedBytesPerSecond;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
std::atomic<size_t> UploadedBlockSize = 0;
std::atomic<size_t> UploadedBlockCount = 0;
@@ -3502,56 +3727,58 @@ namespace {
FindBlocksStatistics FindBlocksStats;
- std::future<PrepareBuildResult> PrepBuildResultFuture = GetNetworkPool().EnqueueTask(std::packaged_task<PrepareBuildResult()>{
- [&Storage, BuildId, FindBlockMaxCount, &MetaData, CreateBuild, AllowMultiparts, IgnoreExistingBlocks, &FindBlocksStats] {
- ZEN_TRACE_CPU("PrepareBuild");
+ std::future<PrepareBuildResult> PrepBuildResultFuture = GetNetworkPool().EnqueueTask(
+ std::packaged_task<PrepareBuildResult()>{
+ [&Storage, BuildId, FindBlockMaxCount, &MetaData, CreateBuild, AllowMultiparts, IgnoreExistingBlocks, &FindBlocksStats] {
+ ZEN_TRACE_CPU("PrepareBuild");
- PrepareBuildResult Result;
- Stopwatch Timer;
- if (CreateBuild)
- {
- ZEN_TRACE_CPU("CreateBuild");
-
- Stopwatch PutBuildTimer;
- CbObject PutBuildResult = Storage.BuildStorage->PutBuild(BuildId, MetaData);
- Result.PrepareBuildTimeMs = PutBuildTimer.GetElapsedTimeMs();
- Result.PreferredMultipartChunkSize = PutBuildResult["chunkSize"sv].AsUInt64(Result.PreferredMultipartChunkSize);
- Result.PayloadSize = MetaData.GetSize();
- }
- else
- {
- ZEN_TRACE_CPU("PutBuild");
- Stopwatch GetBuildTimer;
- CbObject Build = Storage.BuildStorage->GetBuild(BuildId);
- Result.PrepareBuildTimeMs = GetBuildTimer.GetElapsedTimeMs();
- Result.PayloadSize = Build.GetSize();
- if (auto ChunkSize = Build["chunkSize"sv].AsUInt64(); ChunkSize != 0)
+ PrepareBuildResult Result;
+ Stopwatch Timer;
+ if (CreateBuild)
{
- Result.PreferredMultipartChunkSize = ChunkSize;
+ ZEN_TRACE_CPU("CreateBuild");
+
+ Stopwatch PutBuildTimer;
+ CbObject PutBuildResult = Storage.BuildStorage->PutBuild(BuildId, MetaData);
+ Result.PrepareBuildTimeMs = PutBuildTimer.GetElapsedTimeMs();
+ Result.PreferredMultipartChunkSize = PutBuildResult["chunkSize"sv].AsUInt64(Result.PreferredMultipartChunkSize);
+ Result.PayloadSize = MetaData.GetSize();
}
- else if (AllowMultiparts)
+ else
{
- ZEN_CONSOLE_WARN("PreferredMultipartChunkSize is unknown. Defaulting to '{}'",
- NiceBytes(Result.PreferredMultipartChunkSize));
+ ZEN_TRACE_CPU("PutBuild");
+ Stopwatch GetBuildTimer;
+ CbObject Build = Storage.BuildStorage->GetBuild(BuildId);
+ Result.PrepareBuildTimeMs = GetBuildTimer.GetElapsedTimeMs();
+ Result.PayloadSize = Build.GetSize();
+ if (auto ChunkSize = Build["chunkSize"sv].AsUInt64(); ChunkSize != 0)
+ {
+ Result.PreferredMultipartChunkSize = ChunkSize;
+ }
+ else if (AllowMultiparts)
+ {
+ ZEN_CONSOLE_WARN("PreferredMultipartChunkSize is unknown. Defaulting to '{}'",
+ NiceBytes(Result.PreferredMultipartChunkSize));
+ }
}
- }
- if (!IgnoreExistingBlocks)
- {
- ZEN_TRACE_CPU("FindBlocks");
- Stopwatch KnownBlocksTimer;
- CbObject BlockDescriptionList = Storage.BuildStorage->FindBlocks(BuildId, FindBlockMaxCount);
- if (BlockDescriptionList)
+ if (!IgnoreExistingBlocks)
{
- Result.KnownBlocks = ParseChunkBlockDescriptionList(BlockDescriptionList);
+ ZEN_TRACE_CPU("FindBlocks");
+ Stopwatch KnownBlocksTimer;
+ CbObject BlockDescriptionList = Storage.BuildStorage->FindBlocks(BuildId, FindBlockMaxCount);
+ if (BlockDescriptionList)
+ {
+ Result.KnownBlocks = ParseChunkBlockDescriptionList(BlockDescriptionList);
+ }
+ FindBlocksStats.FindBlockTimeMS = KnownBlocksTimer.GetElapsedTimeMs();
+ FindBlocksStats.FoundBlockCount = Result.KnownBlocks.size();
+ Result.FindBlocksTimeMs = KnownBlocksTimer.GetElapsedTimeMs();
}
- FindBlocksStats.FindBlockTimeMS = KnownBlocksTimer.GetElapsedTimeMs();
- FindBlocksStats.FoundBlockCount = Result.KnownBlocks.size();
- Result.FindBlocksTimeMs = KnownBlocksTimer.GetElapsedTimeMs();
- }
- Result.ElapsedTimeMs = Timer.GetElapsedTimeMs();
- return Result;
- }});
+ Result.ElapsedTimeMs = Timer.GetElapsedTimeMs();
+ return Result;
+ }},
+ WorkerThreadPool::EMode::EnableBacklog);
ChunkedFolderContent LocalContent;
@@ -3847,7 +4074,12 @@ namespace {
}
std::vector<std::vector<uint32_t>> NewBlockChunks;
- ArrangeChunksIntoBlocks(LocalContent, LocalLookup, DefaultChunksBlockParams.MaxBlockSize, NewBlockChunkIndexes, NewBlockChunks);
+ ArrangeChunksIntoBlocks(LocalContent,
+ LocalLookup,
+ DefaultChunksBlockParams.MaxBlockSize,
+ DefaultChunksBlockParams.MaxChunksPerBlock,
+ NewBlockChunkIndexes,
+ NewBlockChunks);
FindBlocksStats.NewBlocksCount = NewBlockChunks.size();
for (uint32_t ChunkIndex : NewBlockChunkIndexes)
@@ -4554,7 +4786,7 @@ namespace {
WorkerThreadPool& VerifyPool = GetIOWorkerPool();
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
const uint32_t PathCount = gsl::narrow<uint32_t>(Content.Paths.size());
@@ -5655,7 +5887,7 @@ namespace {
Stopwatch Timer;
auto _ = MakeGuard([&LocalFolderScanStats, &Timer]() { LocalFolderScanStats.ElapsedWallTimeUS = Timer.GetElapsedTimeUs(); });
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
std::atomic<uint64_t> CompletedPathCount = 0;
uint32_t PathIndex = 0;
@@ -5717,21 +5949,52 @@ namespace {
return Result;
}
- void UpdateFolder(const std::filesystem::path& SystemRootDir,
- StorageInstance& Storage,
+ enum EPartialBlockRequestMode
+ {
+ Off,
+ ZenCacheOnly,
+ Mixed,
+ All,
+ Invalid
+ };
+ static EPartialBlockRequestMode PartialBlockRequestModeFromString(const std::string_view ModeString)
+ {
+ switch (HashStringAsLowerDjb2(ModeString))
+ {
+ case HashStringDjb2("false"):
+ return EPartialBlockRequestMode::Off;
+ case HashStringDjb2("zencacheonly"):
+ return EPartialBlockRequestMode::ZenCacheOnly;
+ case HashStringDjb2("mixed"):
+ return EPartialBlockRequestMode::Mixed;
+ case HashStringDjb2("true"):
+ return EPartialBlockRequestMode::All;
+ default:
+ return EPartialBlockRequestMode::Invalid;
+ }
+ }
+
+ struct UpdateOptions
+ {
+ std::filesystem::path SystemRootDir;
+ std::filesystem::path ZenFolderPath;
+ std::uint64_t LargeAttachmentSize = DefaultPreferredMultipartChunkSize * 4u;
+ std::uint64_t PreferredMultipartChunkSize = DefaultPreferredMultipartChunkSize;
+ EPartialBlockRequestMode PartialBlockRequestMode = EPartialBlockRequestMode::Mixed;
+ bool WipeTargetFolder = false;
+ bool PrimeCacheOnly = false;
+ bool EnableOtherDownloadsScavenging = true;
+ bool EnableTargetFolderScavenging = true;
+ };
+
+ void UpdateFolder(StorageInstance& Storage,
const Oid& BuildId,
const std::filesystem::path& Path,
- const std::filesystem::path& ZenFolderPath,
- const std::uint64_t LargeAttachmentSize,
- const std::uint64_t PreferredMultipartChunkSize,
const ChunkedFolderContent& LocalContent,
const ChunkedFolderContent& RemoteContent,
const std::vector<ChunkBlockDescription>& BlockDescriptions,
const std::vector<IoHash>& LooseChunkHashes,
- bool AllowPartialBlockRequests,
- bool WipeTargetFolder,
- bool PrimeCacheOnly,
- bool EnableScavenging,
+ const UpdateOptions& Options,
FolderContent& OutLocalFolderState,
DiskStatistics& DiskStats,
CacheMappingStatistics& CacheMappingStats,
@@ -5741,7 +6004,8 @@ namespace {
{
ZEN_TRACE_CPU("UpdateFolder");
- ZEN_ASSERT((!PrimeCacheOnly) || (PrimeCacheOnly && (!AllowPartialBlockRequests)));
+ ZEN_ASSERT((!Options.PrimeCacheOnly) ||
+ (Options.PrimeCacheOnly && (Options.PartialBlockRequestMode == EPartialBlockRequestMode::Off)));
Stopwatch IndexTimer;
@@ -5754,7 +6018,7 @@ namespace {
ZEN_CONSOLE("Indexed local and remote content in {}", NiceTimeSpanMs(IndexTimer.GetElapsedTimeMs()));
}
- const std::filesystem::path CacheFolderPath = ZenTempCacheFolderPath(ZenFolderPath);
+ const std::filesystem::path CacheFolderPath = ZenTempCacheFolderPath(Options.ZenFolderPath);
Stopwatch CacheMappingTimer;
@@ -5764,7 +6028,7 @@ namespace {
tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> CachedChunkHashesFound;
tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> CachedSequenceHashesFound;
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
ZEN_TRACE_CPU("UpdateFolder_CheckChunkCache");
@@ -5776,40 +6040,43 @@ namespace {
CacheDirContent);
for (size_t Index = 0; Index < CacheDirContent.Files.size(); Index++)
{
- IoHash FileHash;
- if (IoHash::TryParse(CacheDirContent.Files[Index].filename().string(), FileHash))
+ if (Options.EnableTargetFolderScavenging)
{
- if (auto ChunkIt = RemoteLookup.ChunkHashToChunkIndex.find(FileHash);
- ChunkIt != RemoteLookup.ChunkHashToChunkIndex.end())
+ IoHash FileHash;
+ if (IoHash::TryParse(CacheDirContent.Files[Index].filename().string(), FileHash))
{
- const uint32_t ChunkIndex = ChunkIt->second;
- const uint64_t ChunkSize = RemoteContent.ChunkedContent.ChunkRawSizes[ChunkIndex];
- if (ChunkSize == CacheDirContent.FileSizes[Index])
+ if (auto ChunkIt = RemoteLookup.ChunkHashToChunkIndex.find(FileHash);
+ ChunkIt != RemoteLookup.ChunkHashToChunkIndex.end())
{
- CachedChunkHashesFound.insert({FileHash, ChunkIndex});
- CacheMappingStats.CacheChunkCount++;
- CacheMappingStats.CacheChunkByteCount += ChunkSize;
- continue;
+ const uint32_t ChunkIndex = ChunkIt->second;
+ const uint64_t ChunkSize = RemoteContent.ChunkedContent.ChunkRawSizes[ChunkIndex];
+ if (ChunkSize == CacheDirContent.FileSizes[Index])
+ {
+ CachedChunkHashesFound.insert({FileHash, ChunkIndex});
+ CacheMappingStats.CacheChunkCount++;
+ CacheMappingStats.CacheChunkByteCount += ChunkSize;
+ continue;
+ }
}
- }
- else if (auto SequenceIt = RemoteLookup.RawHashToSequenceIndex.find(FileHash);
- SequenceIt != RemoteLookup.RawHashToSequenceIndex.end())
- {
- const uint32_t SequenceIndex = SequenceIt->second;
- const uint32_t PathIndex = RemoteLookup.SequenceIndexFirstPathIndex[SequenceIndex];
- const uint64_t SequenceSize = RemoteContent.RawSizes[PathIndex];
- if (SequenceSize == CacheDirContent.FileSizes[Index])
+ else if (auto SequenceIt = RemoteLookup.RawHashToSequenceIndex.find(FileHash);
+ SequenceIt != RemoteLookup.RawHashToSequenceIndex.end())
{
- CachedSequenceHashesFound.insert({FileHash, SequenceIndex});
- CacheMappingStats.CacheSequenceHashesCount++;
- CacheMappingStats.CacheSequenceHashesByteCount += SequenceSize;
+ const uint32_t SequenceIndex = SequenceIt->second;
+ const uint32_t PathIndex = RemoteLookup.SequenceIndexFirstPathIndex[SequenceIndex];
+ const uint64_t SequenceSize = RemoteContent.RawSizes[PathIndex];
+ if (SequenceSize == CacheDirContent.FileSizes[Index])
+ {
+ CachedSequenceHashesFound.insert({FileHash, SequenceIndex});
+ CacheMappingStats.CacheSequenceHashesCount++;
+ CacheMappingStats.CacheSequenceHashesByteCount += SequenceSize;
- const std::filesystem::path CacheFilePath =
- GetFinalChunkedSequenceFileName(CacheFolderPath,
- RemoteContent.ChunkedContent.SequenceRawHashes[SequenceIndex]);
- ZEN_ASSERT_SLOW(IsFile(CacheFilePath));
+ const std::filesystem::path CacheFilePath =
+ GetFinalChunkedSequenceFileName(CacheFolderPath,
+ RemoteContent.ChunkedContent.SequenceRawHashes[SequenceIndex]);
+ ZEN_ASSERT_SLOW(IsFile(CacheFilePath));
- continue;
+ continue;
+ }
}
}
}
@@ -5819,7 +6086,7 @@ namespace {
}
tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> CachedBlocksFound;
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
ZEN_TRACE_CPU("UpdateFolder_CheckBlockCache");
@@ -5834,31 +6101,34 @@ namespace {
}
DirectoryContent BlockDirContent;
- GetDirectoryContent(ZenTempBlockFolderPath(ZenFolderPath),
+ GetDirectoryContent(ZenTempBlockFolderPath(Options.ZenFolderPath),
DirectoryContentFlags::IncludeFiles | DirectoryContentFlags::IncludeFileSizes,
BlockDirContent);
CachedBlocksFound.reserve(BlockDirContent.Files.size());
for (size_t Index = 0; Index < BlockDirContent.Files.size(); Index++)
{
- IoHash FileHash;
- if (IoHash::TryParse(BlockDirContent.Files[Index].filename().string(), FileHash))
+ if (Options.EnableTargetFolderScavenging)
{
- if (auto BlockIt = AllBlockSizes.find(FileHash); BlockIt != AllBlockSizes.end())
+ IoHash FileHash;
+ if (IoHash::TryParse(BlockDirContent.Files[Index].filename().string(), FileHash))
{
- const uint32_t BlockIndex = BlockIt->second;
- const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
- uint64_t BlockSize = CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize;
- for (uint64_t ChunkSize : BlockDescription.ChunkCompressedLengths)
+ if (auto BlockIt = AllBlockSizes.find(FileHash); BlockIt != AllBlockSizes.end())
{
- BlockSize += ChunkSize;
- }
+ const uint32_t BlockIndex = BlockIt->second;
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+ uint64_t BlockSize = CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize;
+ for (uint64_t ChunkSize : BlockDescription.ChunkCompressedLengths)
+ {
+ BlockSize += ChunkSize;
+ }
- if (BlockSize == BlockDirContent.FileSizes[Index])
- {
- CachedBlocksFound.insert({FileHash, BlockIndex});
- CacheMappingStats.CacheBlockCount++;
- CacheMappingStats.CacheBlocksByteCount += BlockSize;
- continue;
+ if (BlockSize == BlockDirContent.FileSizes[Index])
+ {
+ CachedBlocksFound.insert({FileHash, BlockIndex});
+ CacheMappingStats.CacheBlockCount++;
+ CacheMappingStats.CacheBlocksByteCount += BlockSize;
+ continue;
+ }
}
}
}
@@ -5871,7 +6141,7 @@ namespace {
std::vector<uint32_t> LocalPathIndexesMatchingSequenceIndexes;
tsl::robin_map<IoHash, uint32_t, IoHash::Hasher> SequenceIndexesLeftToFindToRemoteIndex;
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly && Options.EnableTargetFolderScavenging)
{
// Pick up all whole files we can use from current local state
ZEN_TRACE_CPU("UpdateFolder_GetLocalSequences");
@@ -5946,7 +6216,7 @@ namespace {
std::vector<ScavengeCopyOperation> ScavengeCopyOperations;
uint64_t ScavengedPathsCount = 0;
- if (!PrimeCacheOnly && EnableScavenging)
+ if (!Options.PrimeCacheOnly && Options.EnableOtherDownloadsScavenging)
{
ZEN_TRACE_CPU("UpdateFolder_GetScavengedSequences");
@@ -5954,7 +6224,7 @@ namespace {
if (!SequenceIndexesLeftToFindToRemoteIndex.empty())
{
- std::vector<ScavengeSource> ScavengeSources = GetDownloadedStatePaths(SystemRootDir);
+ std::vector<ScavengeSource> ScavengeSources = GetDownloadedStatePaths(Options.SystemRootDir);
auto EraseIt = std::remove_if(ScavengeSources.begin(), ScavengeSources.end(), [&Path](const ScavengeSource& Source) {
return Source.Path == Path;
});
@@ -5967,7 +6237,7 @@ namespace {
ScavengedPaths.resize(ScavengePathCount);
ProgressBar ScavengeProgressBar(ProgressMode, "Scavenging");
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
std::atomic<uint64_t> PathsFound(0);
std::atomic<uint64_t> ChunksFound(0);
@@ -6193,7 +6463,7 @@ namespace {
tsl::robin_map<IoHash, size_t, IoHash::Hasher> RawHashToCacheCopyDataIndex;
std::vector<CacheCopyData> CacheCopyDatas;
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly && Options.EnableTargetFolderScavenging)
{
ZEN_TRACE_CPU("UpdateFolder_GetLocalChunks");
@@ -6274,7 +6544,7 @@ namespace {
CacheMappingStats.LocalScanElapsedWallTimeUs += LocalTimer.GetElapsedTimeUs();
}
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly && Options.EnableOtherDownloadsScavenging)
{
ZEN_TRACE_CPU("UpdateFolder_GetScavengeChunks");
@@ -6434,8 +6704,8 @@ namespace {
WorkerThreadPool& NetworkPool = GetNetworkPool();
WorkerThreadPool& WritePool = GetIOWorkerPool();
- ProgressBar WriteProgressBar(ProgressMode, PrimeCacheOnly ? "Downloading" : "Writing");
- ParallelWork Work(AbortFlag, PauseFlag);
+ ProgressBar WriteProgressBar(ProgressMode, Options.PrimeCacheOnly ? "Downloading" : "Writing");
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
struct LooseChunkHashWorkData
{
@@ -6505,32 +6775,20 @@ namespace {
return NeededBlockChunkIndexes;
};
- std::vector<uint32_t> CachedChunkBlockIndexes;
-
- struct BlockRangeDescriptor
- {
- uint32_t BlockIndex = (uint32_t)-1;
- uint64_t RangeStart = 0;
- uint64_t RangeLength = 0;
- uint32_t ChunkBlockIndexStart = 0;
- uint32_t ChunkBlockIndexCount = 0;
- };
- std::vector<BlockRangeDescriptor> BlockRangeWorks;
-
- std::vector<uint32_t> FullBlockWorks;
+ std::vector<uint32_t> CachedChunkBlockIndexes;
+ std::vector<uint32_t> FetchBlockIndexes;
+ std::vector<std::vector<uint32_t>> AllBlockChunkIndexNeeded;
for (uint32_t BlockIndex = 0; BlockIndex < BlockCount; BlockIndex++)
{
- const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
- const std::vector<uint32_t> BlockChunkIndexNeeded = GetNeededChunkBlockIndexes(BlockDescription);
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+
+ std::vector<uint32_t> BlockChunkIndexNeeded = GetNeededChunkBlockIndexes(BlockDescription);
if (!BlockChunkIndexNeeded.empty())
{
- if (PrimeCacheOnly)
+ if (Options.PrimeCacheOnly)
{
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
+ FetchBlockIndexes.push_back(BlockIndex);
}
else
{
@@ -6542,196 +6800,20 @@ namespace {
TotalPartWriteCount++;
std::filesystem::path BlockPath =
- ZenTempBlockFolderPath(ZenFolderPath) / BlockDescription.BlockHash.ToHexString();
+ ZenTempBlockFolderPath(Options.ZenFolderPath) / BlockDescription.BlockHash.ToHexString();
if (IsFile(BlockPath))
{
CachedChunkBlockIndexes.push_back(BlockIndex);
UsingCachedBlock = true;
}
}
-
if (!UsingCachedBlock)
{
- bool WantsToDoPartialBlockDownload = BlockChunkIndexNeeded.size() < BlockDescription.ChunkRawHashes.size();
- bool CanDoPartialBlockDownload =
- (BlockDescription.HeaderSize > 0) &&
- (BlockDescription.ChunkCompressedLengths.size() == BlockDescription.ChunkRawHashes.size());
- if (AllowPartialBlockRequests && WantsToDoPartialBlockDownload && CanDoPartialBlockDownload)
- {
- std::vector<BlockRangeDescriptor> BlockRanges;
-
- ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis");
-
- uint32_t NeedBlockChunkIndexOffset = 0;
- uint32_t ChunkBlockIndex = 0;
- uint32_t CurrentOffset =
- gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
-
- const uint64_t TotalBlockSize = std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
- BlockDescription.ChunkCompressedLengths.end(),
- std::uint64_t(CurrentOffset));
-
- BlockRangeDescriptor NextRange{.BlockIndex = BlockIndex};
- while (NeedBlockChunkIndexOffset < BlockChunkIndexNeeded.size() &&
- ChunkBlockIndex < BlockDescription.ChunkRawHashes.size())
- {
- const uint32_t ChunkCompressedLength = BlockDescription.ChunkCompressedLengths[ChunkBlockIndex];
- if (ChunkBlockIndex < BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
- {
- if (NextRange.RangeLength > 0)
- {
- BlockRanges.push_back(NextRange);
- NextRange = {.BlockIndex = BlockIndex};
- }
- ChunkBlockIndex++;
- CurrentOffset += ChunkCompressedLength;
- }
- else if (ChunkBlockIndex == BlockChunkIndexNeeded[NeedBlockChunkIndexOffset])
- {
- if (NextRange.RangeLength == 0)
- {
- NextRange.RangeStart = CurrentOffset;
- NextRange.ChunkBlockIndexStart = ChunkBlockIndex;
- }
- NextRange.RangeLength += ChunkCompressedLength;
- NextRange.ChunkBlockIndexCount++;
- ChunkBlockIndex++;
- CurrentOffset += ChunkCompressedLength;
- NeedBlockChunkIndexOffset++;
- }
- else
- {
- ZEN_ASSERT(false);
- }
- }
- if (NextRange.RangeLength > 0)
- {
- BlockRanges.push_back(NextRange);
- }
-
- ZEN_ASSERT(!BlockRanges.empty());
-
- std::vector<BlockRangeDescriptor> CollapsedBlockRanges;
- auto It = BlockRanges.begin();
- CollapsedBlockRanges.push_back(*It++);
- while (It != BlockRanges.end())
- {
- BlockRangeDescriptor& LastRange = CollapsedBlockRanges.back();
- uint64_t Slack = It->RangeStart - (LastRange.RangeStart + LastRange.RangeLength);
- uint64_t BothRangeSize = It->RangeLength + LastRange.RangeLength;
- if (Slack <= Max(BothRangeSize / 8, 64u * 1024u)) // Made up heuristic - we'll see how it pans out
- {
- LastRange.ChunkBlockIndexCount =
- (It->ChunkBlockIndexStart + It->ChunkBlockIndexCount) - LastRange.ChunkBlockIndexStart;
- LastRange.RangeLength = (It->RangeStart + It->RangeLength) - LastRange.RangeStart;
- }
- else
- {
- CollapsedBlockRanges.push_back(*It);
- }
- ++It;
- }
-
- const std::uint64_t WantedSize = std::accumulate(
- CollapsedBlockRanges.begin(),
- CollapsedBlockRanges.end(),
- uint64_t(0),
- [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
- ZEN_ASSERT(WantedSize <= TotalBlockSize);
- if (WantedSize > ((TotalBlockSize * 95) / 100))
- {
- ZEN_CONSOLE_VERBOSE("Using more than 95% ({}) of block {} ({}), requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize));
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 9) / 10)) && CollapsedBlockRanges.size() > 1)
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 90% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 8) / 10)) && (CollapsedBlockRanges.size() > 16))
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 80% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 7) / 10)) && (CollapsedBlockRanges.size() > 48))
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 70% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else if ((WantedSize > ((TotalBlockSize * 6) / 10)) && (CollapsedBlockRanges.size() > 64))
- {
- ZEN_CONSOLE_VERBOSE(
- "Using more than 60% ({}) of block {} ({}) using {} requests, requesting full block",
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
- else
- {
- if (WantedSize > ((TotalBlockSize * 5) / 10))
- {
- ZEN_CONSOLE_VERBOSE("Using {}% ({}) of block {} ({}) using {} requests, requesting partial block",
- (WantedSize * 100) / TotalBlockSize,
- NiceBytes(WantedSize),
- BlockDescription.BlockHash,
- NiceBytes(TotalBlockSize),
- CollapsedBlockRanges.size());
- }
- TotalRequestCount += CollapsedBlockRanges.size();
- TotalPartWriteCount += CollapsedBlockRanges.size();
-
- BlockRangeWorks.insert(BlockRangeWorks.end(), CollapsedBlockRanges.begin(), CollapsedBlockRanges.end());
- }
- }
- else
- {
- TotalRequestCount++;
- TotalPartWriteCount++;
-
- FullBlockWorks.push_back(BlockIndex);
- }
+ FetchBlockIndexes.push_back(BlockIndex);
}
}
}
- else
- {
- ZEN_CONSOLE_VERBOSE("Skipping block {} due to cache reuse", BlockDescriptions[BlockIndex].BlockHash);
- }
+ AllBlockChunkIndexNeeded.emplace_back(std::move(BlockChunkIndexNeeded));
}
struct BlobsExistsResult
@@ -6749,18 +6831,12 @@ namespace {
tsl::robin_set<IoHash> BlobHashesSet;
- BlobHashesSet.reserve(LooseChunkHashWorks.size() + FullBlockWorks.size());
+ BlobHashesSet.reserve(LooseChunkHashWorks.size() + FetchBlockIndexes.size());
for (LooseChunkHashWorkData& LooseChunkHashWork : LooseChunkHashWorks)
{
BlobHashesSet.insert(RemoteContent.ChunkedContent.ChunkHashes[LooseChunkHashWork.RemoteChunkIndex]);
}
- for (const BlockRangeDescriptor& BlockRange : BlockRangeWorks)
- {
- const uint32_t BlockIndex = BlockRange.BlockIndex;
- const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
- BlobHashesSet.insert(BlockDescription.BlockHash);
- }
- for (uint32_t BlockIndex : FullBlockWorks)
+ for (uint32_t BlockIndex : FetchBlockIndexes)
{
const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
BlobHashesSet.insert(BlockDescription.BlockHash);
@@ -6794,6 +6870,162 @@ namespace {
}
}
+ std::vector<BlockRangeDescriptor> BlockRangeWorks;
+ std::vector<uint32_t> FullBlockWorks;
+ {
+ Stopwatch Timer;
+
+ std::vector<uint32_t> PartialBlockIndexes;
+
+ for (uint32_t BlockIndex : FetchBlockIndexes)
+ {
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+
+ const std::vector<uint32_t> BlockChunkIndexNeeded = std::move(AllBlockChunkIndexNeeded[BlockIndex]);
+ if (!BlockChunkIndexNeeded.empty())
+ {
+ bool WantsToDoPartialBlockDownload = BlockChunkIndexNeeded.size() < BlockDescription.ChunkRawHashes.size();
+ bool CanDoPartialBlockDownload =
+ (BlockDescription.HeaderSize > 0) &&
+ (BlockDescription.ChunkCompressedLengths.size() == BlockDescription.ChunkRawHashes.size());
+
+ bool AllowedToDoPartialRequest = false;
+ bool BlockExistInCache = ExistsResult.ExistingBlobs.contains(BlockDescription.BlockHash);
+ switch (Options.PartialBlockRequestMode)
+ {
+ case EPartialBlockRequestMode::Off:
+ break;
+ case EPartialBlockRequestMode::ZenCacheOnly:
+ AllowedToDoPartialRequest = BlockExistInCache;
+ break;
+ case EPartialBlockRequestMode::Mixed:
+ case EPartialBlockRequestMode::All:
+ AllowedToDoPartialRequest = true;
+ break;
+ default:
+ ZEN_ASSERT(false);
+ break;
+ }
+
+ const uint32_t ChunkStartOffsetInBlock =
+ gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
+
+ const uint64_t TotalBlockSize = std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
+ BlockDescription.ChunkCompressedLengths.end(),
+ std::uint64_t(ChunkStartOffsetInBlock));
+
+ if (AllowedToDoPartialRequest && WantsToDoPartialBlockDownload && CanDoPartialBlockDownload)
+ {
+ ZEN_TRACE_CPU("UpdateFolder_HandleBlocks_PartialAnalysis");
+
+ bool LimitToSingleRange =
+ BlockExistInCache ? false : Options.PartialBlockRequestMode == EPartialBlockRequestMode::Mixed;
+ uint64_t TotalWantedChunksSize = 0;
+ std::optional<std::vector<BlockRangeDescriptor>> MaybeBlockRanges =
+ CalculateBlockRanges(BlockIndex,
+ BlockDescription,
+ BlockChunkIndexNeeded,
+ LimitToSingleRange,
+ ChunkStartOffsetInBlock,
+ TotalBlockSize,
+ TotalWantedChunksSize);
+ ZEN_ASSERT(TotalWantedChunksSize <= TotalBlockSize);
+
+ if (MaybeBlockRanges.has_value())
+ {
+ const std::vector<BlockRangeDescriptor>& BlockRanges = MaybeBlockRanges.value();
+ ZEN_ASSERT(!BlockRanges.empty());
+ BlockRangeWorks.insert(BlockRangeWorks.end(), BlockRanges.begin(), BlockRanges.end());
+ TotalRequestCount += BlockRanges.size();
+ TotalPartWriteCount += BlockRanges.size();
+
+ uint64_t RequestedSize = std::accumulate(
+ BlockRanges.begin(),
+ BlockRanges.end(),
+ uint64_t(0),
+ [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
+ PartialBlockIndexes.push_back(BlockIndex);
+
+ if (RequestedSize > TotalWantedChunksSize)
+ {
+ ZEN_CONSOLE_VERBOSE("Requesting {} chunks ({}) from block {} ({}) using {} requests (extra bytes {})",
+ BlockChunkIndexNeeded.size(),
+ NiceBytes(RequestedSize),
+ BlockDescription.BlockHash,
+ NiceBytes(TotalBlockSize),
+ BlockRanges.size(),
+ NiceBytes(RequestedSize - TotalWantedChunksSize));
+ }
+ }
+ else
+ {
+ FullBlockWorks.push_back(BlockIndex);
+ TotalRequestCount++;
+ TotalPartWriteCount++;
+ }
+ }
+ else
+ {
+ FullBlockWorks.push_back(BlockIndex);
+ TotalRequestCount++;
+ TotalPartWriteCount++;
+ }
+ }
+ }
+
+ if (!PartialBlockIndexes.empty())
+ {
+ uint64_t TotalFullBlockRequestBytes = 0;
+ for (uint32_t BlockIndex : FullBlockWorks)
+ {
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+ uint32_t CurrentOffset =
+ gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
+
+ TotalFullBlockRequestBytes += std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
+ BlockDescription.ChunkCompressedLengths.end(),
+ std::uint64_t(CurrentOffset));
+ }
+
+ uint64_t TotalPartialBlockBytes = 0;
+ for (uint32_t BlockIndex : PartialBlockIndexes)
+ {
+ const ChunkBlockDescription& BlockDescription = BlockDescriptions[BlockIndex];
+ uint32_t CurrentOffset =
+ gsl::narrow<uint32_t>(CompressedBuffer::GetHeaderSizeForNoneEncoder() + BlockDescription.HeaderSize);
+
+ TotalPartialBlockBytes += std::accumulate(BlockDescription.ChunkCompressedLengths.begin(),
+ BlockDescription.ChunkCompressedLengths.end(),
+ std::uint64_t(CurrentOffset));
+ }
+
+ uint64_t NonPartialTotalBlockBytes = TotalFullBlockRequestBytes + TotalPartialBlockBytes;
+
+ const uint64_t TotalPartialBlockRequestBytes =
+ std::accumulate(BlockRangeWorks.begin(),
+ BlockRangeWorks.end(),
+ uint64_t(0),
+ [](uint64_t Current, const BlockRangeDescriptor& Range) { return Current + Range.RangeLength; });
+ uint64_t TotalExtraPartialBlocksRequests = BlockRangeWorks.size() - PartialBlockIndexes.size();
+
+ uint64_t TotalSavedBlocksSize = TotalPartialBlockBytes - TotalPartialBlockRequestBytes;
+ double SavedSizePercent = (TotalSavedBlocksSize * 100.0) / NonPartialTotalBlockBytes;
+
+ if (!IsQuiet)
+ {
+ ZEN_CONSOLE(
+ "Analisys of partial block requests saves download of {} out of {} ({:.1f}%) using {} extra "
+ "requests. Completed in {}",
+ NiceBytes(TotalSavedBlocksSize),
+ NiceBytes(NonPartialTotalBlockBytes),
+ SavedSizePercent,
+ TotalExtraPartialBlocksRequests,
+ NiceTimeSpanMs(ExistsResult.ElapsedTimeMs));
+ }
+ // exit(0);
+ }
+ }
+
BufferedWriteFileCache WriteCache;
for (uint32_t ScavengeOpIndex = 0; ScavengeOpIndex < ScavengeCopyOperations.size(); ScavengeOpIndex++)
@@ -6802,7 +7034,7 @@ namespace {
{
break;
}
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
Work.ScheduleWork(
WritePool,
@@ -6865,7 +7097,8 @@ namespace {
std::move(LooseChunkHashWork.ChunkTargetPtrs);
const uint32_t RemoteChunkIndex = LooseChunkHashWork.RemoteChunkIndex;
- if (PrimeCacheOnly && ExistsResult.ExistingBlobs.contains(RemoteContent.ChunkedContent.ChunkHashes[RemoteChunkIndex]))
+ if (Options.PrimeCacheOnly &&
+ ExistsResult.ExistingBlobs.contains(RemoteContent.ChunkedContent.ChunkHashes[RemoteChunkIndex]))
{
DownloadStats.RequestsCompleteCount++;
continue;
@@ -6875,7 +7108,7 @@ namespace {
WritePool,
[&Storage,
&Path,
- &ZenFolderPath,
+ &Options,
&RemoteContent,
&RemoteLookup,
&CacheFolderPath,
@@ -6883,7 +7116,6 @@ namespace {
&Work,
&WritePool,
&NetworkPool,
- PrimeCacheOnly,
&ExistsResult,
&DiskStats,
&DownloadStats,
@@ -6892,8 +7124,6 @@ namespace {
RemoteChunkIndex,
ChunkTargetPtrs,
BuildId = Oid(BuildId),
- LargeAttachmentSize,
- PreferredMultipartChunkSize,
TotalRequestCount,
TotalPartWriteCount,
&WriteCache,
@@ -6903,11 +7133,11 @@ namespace {
{
ZEN_TRACE_CPU("UpdateFolder_ReadPreDownloaded");
std::filesystem::path ExistingCompressedChunkPath;
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
const IoHash& ChunkHash = RemoteContent.ChunkedContent.ChunkHashes[RemoteChunkIndex];
std::filesystem::path CompressedChunkPath =
- ZenTempDownloadFolderPath(ZenFolderPath) / ChunkHash.ToHexString();
+ ZenTempDownloadFolderPath(Options.ZenFolderPath) / ChunkHash.ToHexString();
if (IsFile(CompressedChunkPath))
{
IoBuffer ExistingCompressedPart = IoBufferBuilder::MakeFromFile(ExistingCompressedChunkPath);
@@ -6940,7 +7170,7 @@ namespace {
Work.ScheduleWork(
WritePool,
[&Path,
- &ZenFolderPath,
+ &Options,
&RemoteContent,
&RemoteLookup,
&CacheFolderPath,
@@ -6973,7 +7203,7 @@ namespace {
CompressedChunkPath));
}
- std::filesystem::path TargetFolder = ZenTempCacheFolderPath(ZenFolderPath);
+ std::filesystem::path TargetFolder = ZenTempCacheFolderPath(Options.ZenFolderPath);
bool NeedHashVerify = WriteCompressedChunk(TargetFolder,
RemoteContent,
RemoteLookup,
@@ -7018,10 +7248,9 @@ namespace {
Work.ScheduleWork(
NetworkPool,
[&Path,
- &ZenFolderPath,
+ &Options,
&Storage,
BuildId = Oid(BuildId),
- PrimeCacheOnly,
&RemoteContent,
&RemoteLookup,
&ExistsResult,
@@ -7037,8 +7266,6 @@ namespace {
TotalRequestCount,
&FilteredDownloadedBytesPerSecond,
&FilteredWrittenBytesPerSecond,
- LargeAttachmentSize,
- PreferredMultipartChunkSize,
RemoteChunkIndex,
ChunkTargetPtrs,
&DownloadStats](std::atomic<bool>&) mutable {
@@ -7063,7 +7290,7 @@ namespace {
{
FilteredDownloadedBytesPerSecond.Stop();
}
- AsyncWriteDownloadedChunk(ZenFolderPath,
+ AsyncWriteDownloadedChunk(Options.ZenFolderPath,
RemoteContent,
RemoteLookup,
RemoteChunkIndex,
@@ -7080,23 +7307,23 @@ namespace {
}
else
{
- if (RemoteContent.ChunkedContent.ChunkRawSizes[RemoteChunkIndex] >= LargeAttachmentSize)
+ if (RemoteContent.ChunkedContent.ChunkRawSizes[RemoteChunkIndex] >=
+ Options.LargeAttachmentSize)
{
ZEN_TRACE_CPU("UpdateFolder_GetLargeChunk");
DownloadLargeBlob(*Storage.BuildStorage,
- ZenTempDownloadFolderPath(ZenFolderPath),
+ ZenTempDownloadFolderPath(Options.ZenFolderPath),
BuildId,
ChunkHash,
- PreferredMultipartChunkSize,
+ Options.PreferredMultipartChunkSize,
Work,
NetworkPool,
DownloadStats,
[&Storage,
- &ZenFolderPath,
+ &Options,
&RemoteContent,
&RemoteLookup,
BuildId,
- PrimeCacheOnly,
&SequenceIndexChunksLeftToWriteCounters,
&WriteCache,
&Work,
@@ -7124,12 +7351,12 @@ namespace {
ZenContentType::kCompressedBinary,
CompositeBuffer(SharedBuffer(Payload)));
}
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
if (!AbortFlag)
{
AsyncWriteDownloadedChunk(
- ZenFolderPath,
+ Options.ZenFolderPath,
RemoteContent,
RemoteLookup,
RemoteChunkIndex,
@@ -7156,14 +7383,14 @@ namespace {
Storage.BuildCacheStorage->PutBuildBlob(
BuildId,
ChunkHash,
- BuildBlob.GetContentType(),
+ ZenContentType::kCompressedBinary,
CompositeBuffer(SharedBuffer(BuildBlob)));
}
if (!BuildBlob)
{
throw std::runtime_error(fmt::format("Chunk {} is missing", ChunkHash));
}
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
if (!AbortFlag)
{
@@ -7175,7 +7402,7 @@ namespace {
{
FilteredDownloadedBytesPerSecond.Stop();
}
- AsyncWriteDownloadedChunk(ZenFolderPath,
+ AsyncWriteDownloadedChunk(Options.ZenFolderPath,
RemoteContent,
RemoteLookup,
RemoteChunkIndex,
@@ -7203,7 +7430,7 @@ namespace {
for (size_t CopyDataIndex = 0; CopyDataIndex < CacheCopyDatas.size(); CopyDataIndex++)
{
- ZEN_ASSERT(!PrimeCacheOnly);
+ ZEN_ASSERT(!Options.PrimeCacheOnly);
if (AbortFlag)
{
break;
@@ -7409,7 +7636,7 @@ namespace {
for (uint32_t BlockIndex : CachedChunkBlockIndexes)
{
- ZEN_ASSERT(!PrimeCacheOnly);
+ ZEN_ASSERT(!Options.PrimeCacheOnly);
if (AbortFlag)
{
break;
@@ -7417,7 +7644,7 @@ namespace {
Work.ScheduleWork(
WritePool,
- [&ZenFolderPath,
+ [&Options,
&CacheFolderPath,
&RemoteContent,
&RemoteLookup,
@@ -7440,7 +7667,7 @@ namespace {
FilteredWrittenBytesPerSecond.Start();
std::filesystem::path BlockChunkPath =
- ZenTempBlockFolderPath(ZenFolderPath) / BlockDescription.BlockHash.ToHexString();
+ ZenTempBlockFolderPath(Options.ZenFolderPath) / BlockDescription.BlockHash.ToHexString();
IoBuffer BlockBuffer = IoBufferBuilder::MakeFromFile(BlockChunkPath);
if (!BlockBuffer)
{
@@ -7482,7 +7709,7 @@ namespace {
for (size_t BlockRangeIndex = 0; BlockRangeIndex < BlockRangeWorks.size(); BlockRangeIndex++)
{
- ZEN_ASSERT(!PrimeCacheOnly);
+ ZEN_ASSERT(!Options.PrimeCacheOnly);
if (AbortFlag)
{
break;
@@ -7493,7 +7720,7 @@ namespace {
Work.ScheduleWork(
NetworkPool,
[&Storage,
- &ZenFolderPath,
+ &Options,
BuildId,
&RemoteLookup,
&BlockDescriptions,
@@ -7569,11 +7796,12 @@ namespace {
if (!Ec)
{
BlockBuffer.SetDeleteOnClose(false);
- BlockBuffer = {};
- BlockChunkPath = ZenTempBlockFolderPath(ZenFolderPath) / fmt::format("{}_{:x}_{:x}",
- BlockDescription.BlockHash,
- BlockRange.RangeStart,
- BlockRange.RangeLength);
+ BlockBuffer = {};
+ BlockChunkPath =
+ ZenTempBlockFolderPath(Options.ZenFolderPath) / fmt::format("{}_{:x}_{:x}",
+ BlockDescription.BlockHash,
+ BlockRange.RangeStart,
+ BlockRange.RangeLength);
RenameFile(TempBlobPath, BlockChunkPath, Ec);
if (Ec)
{
@@ -7592,10 +7820,10 @@ namespace {
{
ZEN_TRACE_CPU("UpdateFolder_WriteTempBlock");
// Could not be moved and rather large, lets store it on disk
- BlockChunkPath = ZenTempBlockFolderPath(ZenFolderPath) / fmt::format("{}_{:x}_{:x}",
- BlockDescription.BlockHash,
- BlockRange.RangeStart,
- BlockRange.RangeLength);
+ BlockChunkPath = ZenTempBlockFolderPath(Options.ZenFolderPath) / fmt::format("{}_{:x}_{:x}",
+ BlockDescription.BlockHash,
+ BlockRange.RangeStart,
+ BlockRange.RangeLength);
TemporaryFile::SafeWriteFile(BlockChunkPath, BlockBuffer);
BlockBuffer = {};
}
@@ -7691,7 +7919,7 @@ namespace {
break;
}
- if (PrimeCacheOnly && ExistsResult.ExistingBlobs.contains(BlockDescriptions[BlockIndex].BlockHash))
+ if (Options.PrimeCacheOnly && ExistsResult.ExistingBlobs.contains(BlockDescriptions[BlockIndex].BlockHash))
{
DownloadStats.RequestsCompleteCount++;
continue;
@@ -7700,9 +7928,8 @@ namespace {
Work.ScheduleWork(
NetworkPool,
[&Storage,
- &ZenFolderPath,
+ &Options,
BuildId,
- PrimeCacheOnly,
&BlockDescriptions,
&WritePartsComplete,
TotalPartWriteCount,
@@ -7744,7 +7971,7 @@ namespace {
{
Storage.BuildCacheStorage->PutBuildBlob(BuildId,
BlockDescription.BlockHash,
- BlockBuffer.GetContentType(),
+ ZenContentType::kCompressedBinary,
CompositeBuffer(SharedBuffer(BlockBuffer)));
}
}
@@ -7763,7 +7990,7 @@ namespace {
FilteredDownloadedBytesPerSecond.Stop();
}
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
std::filesystem::path BlockChunkPath;
@@ -7779,9 +8006,9 @@ namespace {
if (!Ec)
{
BlockBuffer.SetDeleteOnClose(false);
- BlockBuffer = {};
- BlockChunkPath =
- ZenTempBlockFolderPath(ZenFolderPath) / BlockDescription.BlockHash.ToHexString();
+ BlockBuffer = {};
+ BlockChunkPath = ZenTempBlockFolderPath(Options.ZenFolderPath) /
+ BlockDescription.BlockHash.ToHexString();
RenameFile(TempBlobPath, BlockChunkPath, Ec);
if (Ec)
{
@@ -7800,7 +8027,8 @@ namespace {
{
ZEN_TRACE_CPU("UpdateFolder_WriteTempBlock");
// Could not be moved and rather large, lets store it on disk
- BlockChunkPath = ZenTempBlockFolderPath(ZenFolderPath) / BlockDescription.BlockHash.ToHexString();
+ BlockChunkPath =
+ ZenTempBlockFolderPath(Options.ZenFolderPath) / BlockDescription.BlockHash.ToHexString();
TemporaryFile::SafeWriteFile(BlockChunkPath, BlockBuffer);
BlockBuffer = {};
}
@@ -7902,11 +8130,11 @@ namespace {
(DownloadStats.RequestsCompleteCount == TotalRequestCount)
? ""
: fmt::format(" {}bits/s", NiceNum(FilteredDownloadedBytesPerSecond.GetCurrent() * 8));
- std::string WriteDetails = PrimeCacheOnly ? ""
- : fmt::format(" {}/{} ({}B/s) written.",
- NiceBytes(DiskStats.WriteByteCount.load()),
- NiceBytes(BytesToWrite),
- NiceNum(FilteredWrittenBytesPerSecond.GetCurrent()));
+ std::string WriteDetails = Options.PrimeCacheOnly ? ""
+ : fmt::format(" {}/{} ({}B/s) written.",
+ NiceBytes(DiskStats.WriteByteCount.load()),
+ NiceBytes(BytesToWrite),
+ NiceNum(FilteredWrittenBytesPerSecond.GetCurrent()));
std::string Details = fmt::format("{}/{} ({}{}) downloaded.{}",
DownloadStats.RequestsCompleteCount.load(),
TotalRequestCount,
@@ -7914,11 +8142,11 @@ namespace {
DownloadRateString,
WriteDetails);
WriteProgressBar.UpdateState(
- {.Task = PrimeCacheOnly ? "Downloading " : "Writing chunks ",
+ {.Task = Options.PrimeCacheOnly ? "Downloading " : "Writing chunks ",
.Details = Details,
- .TotalCount = PrimeCacheOnly ? TotalRequestCount : BytesToWrite,
- .RemainingCount = PrimeCacheOnly ? (TotalRequestCount - DownloadStats.RequestsCompleteCount.load())
- : (BytesToWrite - DiskStats.WriteByteCount.load()),
+ .TotalCount = Options.PrimeCacheOnly ? TotalRequestCount : BytesToWrite,
+ .RemainingCount = Options.PrimeCacheOnly ? (TotalRequestCount - DownloadStats.RequestsCompleteCount.load())
+ : (BytesToWrite - DiskStats.WriteByteCount.load()),
.Status = ProgressBar::State::CalculateStatus(IsAborted, IsPaused)},
false);
});
@@ -7933,7 +8161,7 @@ namespace {
return;
}
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
uint32_t RawSequencesMissingWriteCount = 0;
for (uint32_t SequenceIndex = 0; SequenceIndex < SequenceIndexChunksLeftToWriteCounters.size(); SequenceIndex++)
@@ -7978,7 +8206,7 @@ namespace {
WriteChunkStats.WriteTimeUs = FilteredWrittenBytesPerSecond.GetElapsedTimeUS();
}
- if (PrimeCacheOnly)
+ if (Options.PrimeCacheOnly)
{
return;
}
@@ -8025,7 +8253,7 @@ namespace {
ZEN_ASSERT_SLOW(IsFile((Path / LocalContent.Paths[LocalPathIndex]).make_preferred()));
- if (!WipeTargetFolder)
+ if (!Options.WipeTargetFolder)
{
if (auto RemotePathIt = RemotePathToRemoteIndex.find(LocalPath.generic_string());
RemotePathIt != RemotePathToRemoteIndex.end())
@@ -8064,7 +8292,7 @@ namespace {
SkippedCount++;
}
}
- else if (!WipeTargetFolder)
+ else if (!Options.WipeTargetFolder)
{
// We don't need it
RemoveLocalPathIndexes.push_back(LocalPathIndex);
@@ -8085,7 +8313,7 @@ namespace {
WorkerThreadPool& WritePool = GetIOWorkerPool();
ProgressBar CacheLocalProgressBar(ProgressMode, "Cache Local Data");
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
for (uint32_t LocalPathIndex : FilesToCache)
{
@@ -8147,7 +8375,7 @@ namespace {
}
}
- if (WipeTargetFolder)
+ if (Options.WipeTargetFolder)
{
ZEN_TRACE_CPU("UpdateFolder_WipeTarget");
Stopwatch Timer;
@@ -8172,7 +8400,7 @@ namespace {
WorkerThreadPool& WritePool = GetIOWorkerPool();
ProgressBar RebuildProgressBar(ProgressMode, "Rebuild State");
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
OutLocalFolderState.Paths.resize(RemoteContent.Paths.size());
OutLocalFolderState.RawSizes.resize(RemoteContent.Paths.size());
@@ -8234,7 +8462,8 @@ namespace {
}
size_t TargetCount = 1;
- while (Targets[TargetOffset + TargetCount].RawHash == Targets[TargetOffset].RawHash)
+ while ((TargetOffset + TargetCount) < Targets.size() &&
+ (Targets[TargetOffset + TargetCount].RawHash == Targets[TargetOffset].RawHash))
{
TargetCount++;
}
@@ -8637,8 +8866,8 @@ namespace {
ChunkedFolderContent GetRemoteContent(StorageInstance& Storage,
const Oid& BuildId,
const std::vector<std::pair<Oid, std::string>>& BuildParts,
- std::string_view IncludeWildcard,
- std::string_view ExcludeWildcard,
+ std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards,
std::unique_ptr<ChunkingController>& OutChunkController,
std::vector<ChunkedFolderContent>& OutPartContents,
std::vector<ChunkBlockDescription>& OutBlockDescriptions,
@@ -8671,8 +8900,8 @@ namespace {
const Oid& BuildId,
const Oid& BuildPartId,
CbObject BuildPartManifest,
- std::string_view IncludeWildcard,
- std::string_view ExcludeWildcard,
+ std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards,
ChunkedFolderContent& OutRemoteContent,
std::vector<ChunkBlockDescription>& OutBlockDescriptions,
std::vector<IoHash>& OutLooseChunkHashes) {
@@ -8802,6 +9031,20 @@ namespace {
std::string ErrorDescription =
fmt::format("All required blocks could not be found, {} blocks does not have metadata in this context.",
BlockRawHashes.size() - OutBlockDescriptions.size());
+ if (IsVerbose)
+ {
+ for (const IoHash& BlockHash : BlockRawHashes)
+ {
+ if (auto It = std::find_if(
+ OutBlockDescriptions.begin(),
+ OutBlockDescriptions.end(),
+ [BlockHash](const ChunkBlockDescription& Description) { return Description.BlockHash == BlockHash; });
+ It == OutBlockDescriptions.end())
+ {
+ ErrorDescription += fmt::format("\n {}", BlockHash);
+ }
+ }
+ }
if (AttemptFallback)
{
ZEN_CONSOLE_WARN("{} Attemping fallback options.", ErrorDescription);
@@ -8878,11 +9121,12 @@ namespace {
OutRemoteContent.ChunkedContent.ChunkRawSizes,
OutRemoteContent.ChunkedContent.ChunkOrders);
+ if (!IncludeWildcards.empty() || !ExcludeWildcards.empty())
{
std::vector<std::filesystem::path> DeletedPaths;
for (const std::filesystem::path& RemotePath : OutRemoteContent.Paths)
{
- if (!IncludePath(IncludeWildcard, ExcludeWildcard, RemotePath))
+ if (!IncludePath(IncludeWildcards, ExcludeWildcards, RemotePath))
{
DeletedPaths.push_back(RemotePath);
}
@@ -8914,8 +9158,8 @@ namespace {
BuildId,
BuildPartId,
BuildPartManifest,
- IncludeWildcard,
- ExcludeWildcard,
+ IncludeWildcards,
+ ExcludeWildcards,
OutPartContents[0],
OutBlockDescriptions,
OutLooseChunkHashes);
@@ -8947,8 +9191,8 @@ namespace {
BuildId,
OverlayBuildPartId,
OverlayBuildPartManifest,
- IncludeWildcard,
- ExcludeWildcard,
+ IncludeWildcards,
+ ExcludeWildcards,
OverlayPartContent,
OverlayPartBlockDescriptions,
OverlayPartLooseChunkHashes);
@@ -9003,8 +9247,8 @@ namespace {
const std::filesystem::path& StateFilePath,
ChunkingController& ChunkController,
std::span<const std::filesystem::path> ReferencePaths,
- std::string_view IncludeWildcard,
- std::string_view ExcludeWildcard,
+ std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards,
FolderContent& OutLocalFolderContent)
{
FolderContent LocalFolderState;
@@ -9028,7 +9272,7 @@ namespace {
for (const std::filesystem::path& LocalPath : LocalFolderState.Paths)
{
- if (IncludePath(IncludeWildcard, ExcludeWildcard, LocalPath))
+ if (IncludePath(IncludeWildcards, ExcludeWildcards, LocalPath))
{
FileSet.insert(LocalPath.generic_string());
PathsToCheck.push_back(LocalPath);
@@ -9037,7 +9281,7 @@ namespace {
for (const std::filesystem::path& RemotePath : ReferencePaths)
{
- if (IncludePath(IncludeWildcard, ExcludeWildcard, RemotePath))
+ if (IncludePath(IncludeWildcards, ExcludeWildcards, RemotePath))
{
if (FileSet.insert(RemotePath.generic_string()).second)
{
@@ -9345,21 +9589,27 @@ namespace {
return Result;
};
+ struct DownloadOptions
+ {
+ std::filesystem::path SystemRootDir;
+ std::filesystem::path ZenFolderPath;
+ bool AllowMultiparts = true;
+ EPartialBlockRequestMode PartialBlockRequestMode = EPartialBlockRequestMode::Mixed;
+ bool CleanTargetFolder = false;
+ bool PostDownloadVerify = false;
+ bool PrimeCacheOnly = false;
+ bool EnableOtherDownloadsScavenging = true;
+ bool EnableTargetFolderScavenging = true;
+ std::vector<std::string> IncludeWildcards;
+ std::vector<std::string> ExcludeWildcards;
+ };
+
void DownloadFolder(StorageInstance& Storage,
const Oid& BuildId,
const std::vector<Oid>& BuildPartIds,
std::span<const std::string> BuildPartNames,
const std::filesystem::path& Path,
- const std::filesystem::path& ZenFolderPath,
- const std::filesystem::path SystemRootDir,
- bool AllowMultiparts,
- bool AllowPartialBlockRequests,
- bool WipeTargetFolder,
- bool PostDownloadVerify,
- bool PrimeCacheOnly,
- bool EnableScavenging,
- std::string_view IncludeWildcard,
- std::string_view ExcludeWildcard)
+ const DownloadOptions& Options)
{
ZEN_TRACE_CPU("DownloadFolder");
@@ -9378,18 +9628,19 @@ namespace {
auto EndProgress =
MakeGuard([&]() { ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::StepCount, TaskSteps::StepCount); });
- ZEN_ASSERT((!PrimeCacheOnly) || (PrimeCacheOnly && (!AllowPartialBlockRequests)));
+ ZEN_ASSERT((!Options.PrimeCacheOnly) ||
+ (Options.PrimeCacheOnly && (Options.PartialBlockRequestMode == EPartialBlockRequestMode::Off)));
Stopwatch DownloadTimer;
ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::CheckState, TaskSteps::StepCount);
- const std::filesystem::path ZenTempFolder = ZenTempFolderPath(ZenFolderPath);
+ const std::filesystem::path ZenTempFolder = ZenTempFolderPath(Options.ZenFolderPath);
CreateDirectories(ZenTempFolder);
- CreateDirectories(ZenTempBlockFolderPath(ZenFolderPath));
- CreateDirectories(ZenTempCacheFolderPath(ZenFolderPath));
- CreateDirectories(ZenTempDownloadFolderPath(ZenFolderPath));
+ CreateDirectories(ZenTempBlockFolderPath(Options.ZenFolderPath));
+ CreateDirectories(ZenTempCacheFolderPath(Options.ZenFolderPath));
+ CreateDirectories(ZenTempDownloadFolderPath(Options.ZenFolderPath));
std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u;
@@ -9408,19 +9659,19 @@ namespace {
ChunkedFolderContent RemoteContent = GetRemoteContent(Storage,
BuildId,
AllBuildParts,
- IncludeWildcard,
- ExcludeWildcard,
+ Options.IncludeWildcards,
+ Options.ExcludeWildcards,
ChunkController,
PartContents,
BlockDescriptions,
LooseChunkHashes);
- const std::uint64_t LargeAttachmentSize = AllowMultiparts ? PreferredMultipartChunkSize * 4u : (std::uint64_t)-1;
+ const std::uint64_t LargeAttachmentSize = Options.AllowMultiparts ? PreferredMultipartChunkSize * 4u : (std::uint64_t)-1;
GetFolderContentStatistics LocalFolderScanStats;
ChunkingStatistics ChunkingStats;
ChunkedFolderContent LocalContent;
FolderContent LocalFolderContent;
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
if (IsDir(Path))
{
@@ -9433,11 +9684,11 @@ namespace {
LocalContent = GetLocalContent(LocalFolderScanStats,
ChunkingStats,
Path,
- ZenStateFilePath(ZenFolderPath),
+ ZenStateFilePath(Options.ZenFolderPath),
*ChunkController,
RemoteContent.Paths,
- IncludeWildcard,
- ExcludeWildcard,
+ Options.IncludeWildcards,
+ Options.ExcludeWildcards,
LocalFolderContent);
}
else
@@ -9491,7 +9742,7 @@ namespace {
return true;
};
- if (CompareContent(RemoteContent, LocalContent) && !WipeTargetFolder)
+ if (Options.EnableTargetFolderScavenging && !Options.CleanTargetFolder && CompareContent(RemoteContent, LocalContent))
{
if (!IsQuiet)
{
@@ -9501,14 +9752,14 @@ namespace {
Stopwatch WriteStateTimer;
CbObject StateObject = CreateStateObject(BuildId, AllBuildParts, PartContents, LocalFolderContent, Path);
- CreateDirectories(ZenStateFilePath(ZenFolderPath).parent_path());
- TemporaryFile::SafeWriteFile(ZenStateFilePath(ZenFolderPath), StateObject.GetView());
+ CreateDirectories(ZenStateFilePath(Options.ZenFolderPath).parent_path());
+ TemporaryFile::SafeWriteFile(ZenStateFilePath(Options.ZenFolderPath), StateObject.GetView());
if (!IsQuiet)
{
ZEN_CONSOLE("Wrote local state in {}", NiceTimeSpanMs(WriteStateTimer.GetElapsedTimeMs()));
}
- AddDownloadedPath(SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(ZenFolderPath), Path);
+ AddDownloadedPath(Options.SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(Options.ZenFolderPath), Path);
}
else
{
@@ -9535,21 +9786,22 @@ namespace {
ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Download, TaskSteps::StepCount);
- UpdateFolder(SystemRootDir,
- Storage,
+ UpdateFolder(Storage,
BuildId,
Path,
- ZenFolderPath,
- LargeAttachmentSize,
- PreferredMultipartChunkSize,
LocalContent,
RemoteContent,
BlockDescriptions,
LooseChunkHashes,
- AllowPartialBlockRequests,
- WipeTargetFolder,
- PrimeCacheOnly,
- EnableScavenging,
+ UpdateOptions{.SystemRootDir = Options.SystemRootDir,
+ .ZenFolderPath = Options.ZenFolderPath,
+ .LargeAttachmentSize = LargeAttachmentSize,
+ .PreferredMultipartChunkSize = PreferredMultipartChunkSize,
+ .PartialBlockRequestMode = Options.PartialBlockRequestMode,
+ .WipeTargetFolder = Options.CleanTargetFolder,
+ .PrimeCacheOnly = Options.PrimeCacheOnly,
+ .EnableOtherDownloadsScavenging = Options.EnableOtherDownloadsScavenging,
+ .EnableTargetFolderScavenging = Options.EnableTargetFolderScavenging},
LocalFolderState,
DiskStats,
CacheMappingStats,
@@ -9559,28 +9811,28 @@ namespace {
if (!AbortFlag)
{
- if (!PrimeCacheOnly)
+ if (!Options.PrimeCacheOnly)
{
ProgressBar::SetLogOperationProgress(ProgressMode, TaskSteps::Verify, TaskSteps::StepCount);
- VerifyFolder(RemoteContent, Path, PostDownloadVerify, VerifyFolderStats);
+ VerifyFolder(RemoteContent, Path, Options.PostDownloadVerify, VerifyFolderStats);
Stopwatch WriteStateTimer;
CbObject StateObject = CreateStateObject(BuildId, AllBuildParts, PartContents, LocalFolderState, Path);
- CreateDirectories(ZenStateFilePath(ZenFolderPath).parent_path());
- TemporaryFile::SafeWriteFile(ZenStateFilePath(ZenFolderPath), StateObject.GetView());
+ CreateDirectories(ZenStateFilePath(Options.ZenFolderPath).parent_path());
+ TemporaryFile::SafeWriteFile(ZenStateFilePath(Options.ZenFolderPath), StateObject.GetView());
if (!IsQuiet)
{
ZEN_CONSOLE("Wrote local state in {}", NiceTimeSpanMs(WriteStateTimer.GetElapsedTimeMs()));
}
- AddDownloadedPath(SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(ZenFolderPath), Path);
+ AddDownloadedPath(Options.SystemRootDir, BuildId, AllBuildParts, ZenStateFilePath(Options.ZenFolderPath), Path);
#if 0
ExtendableStringBuilder<1024> SB;
CompactBinaryToJson(StateObject, SB);
- WriteFile(ZenStateFileJsonPath(ZenFolderPath), IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size()));
+ WriteFile(ZenStateFileJsonPath(Options.ZenFolderPath), IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size()));
#endif // 0
}
const uint64_t DownloadCount = DownloadStats.DownloadedChunkCount.load() + DownloadStats.DownloadedBlockCount.load() +
@@ -9619,7 +9871,7 @@ namespace {
}
}
}
- if (PrimeCacheOnly)
+ if (Options.PrimeCacheOnly)
{
if (Storage.BuildCacheStorage)
{
@@ -9652,8 +9904,8 @@ namespace {
const Oid& BuildId,
const std::vector<Oid>& BuildPartIds,
std::span<const std::string> BuildPartNames,
- std::string_view IncludeWildcard,
- std::string_view ExcludeWildcard)
+ std::span<const std::string> IncludeWildcards,
+ std::span<const std::string> ExcludeWildcards)
{
std::uint64_t PreferredMultipartChunkSize = 32u * 1024u * 1024u;
@@ -9716,7 +9968,7 @@ namespace {
for (size_t Index : Order)
{
const std::filesystem::path& Path = Paths[Index];
- if (IncludePath(IncludeWildcard, ExcludeWildcard, Path))
+ if (IncludePath(IncludeWildcards, ExcludeWildcards, Path))
{
const IoHash& RawHash = RawHashes[Index];
const uint64_t RawSize = RawSizes[Index];
@@ -9932,81 +10184,9 @@ BuildsCommand::BuildsCommand()
"<usesparsefiles>");
};
- auto AddAuthOptions = [this](cxxopts::Options& Ops) {
- // Direct access token (may expire)
- Ops.add_option("auth-token",
- "",
- "access-token",
- "Cloud/Builds Storage access token",
- cxxopts::value(m_AccessToken),
- "<accesstoken>");
- Ops.add_option("auth-token",
- "",
- "access-token-env",
- "Name of environment variable that holds the cloud/builds Storage access token",
- cxxopts::value(m_AccessTokenEnv)->default_value(DefaultAccessTokenEnvVariableName),
- "<envvariable>");
- Ops.add_option("auth-token",
- "",
- "access-token-path",
- "Path to json file that holds the cloud/builds Storage access token",
- cxxopts::value(m_AccessTokenPath),
- "<filepath>");
-
- // Auth manager token encryption
- Ops.add_option("security",
- "",
- "encryption-aes-key",
- "256 bit AES encryption key",
- cxxopts::value<std::string>(m_EncryptionKey),
- "");
- Ops.add_option("security",
- "",
- "encryption-aes-iv",
- "128 bit AES encryption initialization vector",
- cxxopts::value<std::string>(m_EncryptionIV),
- "");
+ auto AddCloudOptions = [this](cxxopts::Options& Ops) {
+ m_AuthOptions.AddOptions(Ops);
- // OpenId acccess token
- Ops.add_option("openid",
- "",
- "openid-provider-name",
- "Open ID provider name",
- cxxopts::value<std::string>(m_OpenIdProviderName),
- "Default");
- Ops.add_option("openid", "", "openid-provider-url", "Open ID provider url", cxxopts::value<std::string>(m_OpenIdProviderUrl), "");
- Ops.add_option("openid", "", "openid-client-id", "Open ID client id", cxxopts::value<std::string>(m_OpenIdClientId), "");
- Ops.add_option("openid",
- "",
- "openid-refresh-token",
- "Open ID refresh token",
- cxxopts::value<std::string>(m_OpenIdRefreshToken),
- "");
-
- // OAuth acccess token
- Ops.add_option("oauth", "", "oauth-url", "OAuth provier url", cxxopts::value<std::string>(m_OAuthUrl)->default_value(""), "");
- Ops.add_option("oauth",
- "",
- "oauth-clientid",
- "OAuth client id",
- cxxopts::value<std::string>(m_OAuthClientId)->default_value(""),
- "");
- Ops.add_option("oauth",
- "",
- "oauth-clientsecret",
- "OAuth client secret",
- cxxopts::value<std::string>(m_OAuthClientSecret)->default_value(""),
- "");
- Ops.add_option("auth",
- "",
- "oidctoken-exe-path",
- "Path to OidcToken executable",
- cxxopts::value<std::string>(m_OidcTokenAuthExecutablePath)->default_value(""),
- "");
- };
-
- auto AddCloudOptions = [this, &AddAuthOptions](cxxopts::Options& Ops) {
- AddAuthOptions(Ops);
Ops.add_option("cloud build", "", "override-host", "Cloud Builds URL", cxxopts::value(m_OverrideHost), "<override-host>");
Ops.add_option("cloud build",
"",
@@ -10086,18 +10266,44 @@ BuildsCommand::BuildsCommand()
Ops.add_option("",
"",
"wildcard",
- "Windows style wildcard (using * and ?) to match file paths to include",
+ "Windows style wildcard(s) (using * and ?) to match file paths to include, separated by ;",
cxxopts::value(m_IncludeWildcard),
"<wildcard>");
Ops.add_option("",
"",
"exclude-wildcard",
- "Windows style wildcard (using * and ?) to match file paths to exclude. Applied after --wildcard include filter",
+ "Windows style wildcard(s) (using * and ?) to match file paths to exclude, separated by ;. Applied after --wildcard "
+ "include filter",
cxxopts::value(m_ExcludeWildcard),
"<excludewildcard>");
};
+ auto AddMultipartOptions = [this](cxxopts::Options& Ops) {
+ Ops.add_option("",
+ "",
+ "allow-multipart",
+ "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
+ cxxopts::value(m_AllowMultiparts),
+ "<allowmultipart>");
+ };
+
+ auto AddPartialBlockRequestOptions = [this](cxxopts::Options& Ops) {
+ Ops.add_option("",
+ "",
+ "allow-partial-block-requests",
+ "Allow request for partial chunk blocks.\n"
+ " false = only full block requests allowed\n"
+ " mixed = multiple partial block ranges requests per block allowed to zen cache, single partial block range "
+ "request per block to host\n"
+ " zencacheonly = multiple partial block ranges requests per block allowed to zen cache, only full block requests "
+ "allowed to host\n"
+ " true = multiple partial block ranges requests per block allowed to zen cache and host\n"
+ "Defaults to 'mixed'.",
+ cxxopts::value(m_AllowPartialBlockRequests),
+ "<allowpartialblockrequests>");
+ };
+
m_Options.add_option("",
"v",
"verb",
@@ -10151,6 +10357,25 @@ BuildsCommand::BuildsCommand()
m_ListOptions.parse_positional({"query-path", "result-path"});
m_ListOptions.positional_help("query-path result-path");
+ // list-blocks
+ AddSystemOptions(m_ListBlocksOptions);
+ AddCloudOptions(m_ListBlocksOptions);
+ AddZenFolderOptions(m_ListBlocksOptions);
+ m_ListBlocksOptions.add_options()("h,help", "Print help");
+ m_ListBlocksOptions.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), "<id>");
+ m_ListBlocksOptions.add_option("",
+ "",
+ "result-path",
+ "Path to json or compactbinary to write query result to",
+ cxxopts::value(m_ListResultPath),
+ "<result-path>");
+
+ m_ListBlocksOptions
+ .add_option("", "", "max-count", "Maximum number of blocks to list", cxxopts::value(m_ListBlocksMaxCount), "<maxcount>");
+
+ m_ListBlocksOptions.parse_positional({"build-id"});
+ m_ListBlocksOptions.positional_help("build-id");
+
// upload
AddSystemOptions(m_UploadOptions);
AddCloudOptions(m_UploadOptions);
@@ -10200,12 +10425,9 @@ BuildsCommand::BuildsCommand()
"Percent of an existing block that must be relevant for it to be resused. Defaults to 85.",
cxxopts::value(m_BlockReuseMinPercentLimit),
"<minreuse>");
- m_UploadOptions.add_option("",
- "",
- "allow-multipart",
- "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
- cxxopts::value(m_AllowMultiparts),
- "<allowmultipart>");
+
+ AddMultipartOptions(m_UploadOptions);
+
m_UploadOptions.add_option("",
"",
"manifest-path",
@@ -10258,20 +10480,21 @@ BuildsCommand::BuildsCommand()
"all parts will be downloaded",
cxxopts::value(m_BuildPartNames),
"<name>");
- m_DownloadOptions
- .add_option("", "", "clean", "Delete all data in target folder before downloading", cxxopts::value(m_Clean), "<clean>");
m_DownloadOptions.add_option("",
"",
- "allow-multipart",
- "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
- cxxopts::value(m_AllowMultiparts),
- "<allowmultipart>");
+ "clean",
+ "Delete all data in target folder that is not part of the downloaded content",
+ cxxopts::value(m_Clean),
+ "<clean>");
m_DownloadOptions.add_option("",
"",
- "allow-partial-block-requests",
- "Allow request for partial chunk blocks. Defaults to true.",
- cxxopts::value(m_AllowPartialBlockRequests),
- "<allowpartialblockrequests>");
+ "force",
+ "Force download of all content by ignoring any existing local content",
+ cxxopts::value(m_Force),
+ "<force>");
+ AddMultipartOptions(m_DownloadOptions);
+
+ AddPartialBlockRequestOptions(m_DownloadOptions);
m_DownloadOptions
.add_option("", "", "verify", "Enable post download verify of all tracked files", cxxopts::value(m_PostDownloadVerify), "<verify>");
@@ -10338,18 +10561,8 @@ BuildsCommand::BuildsCommand()
AddWorkerOptions(m_TestOptions);
m_TestOptions.add_options()("h,help", "Print help");
m_TestOptions.add_option("", "l", "local-path", "Root file system folder used as base", cxxopts::value(m_Path), "<local-path>");
- m_TestOptions.add_option("",
- "",
- "allow-multipart",
- "Allow large attachments to be transfered using multipart protocol. Defaults to true.",
- cxxopts::value(m_AllowMultiparts),
- "<allowmultipart>");
- m_TestOptions.add_option("",
- "",
- "allow-partial-block-requests",
- "Allow request for partial chunk blocks. Defaults to true.",
- cxxopts::value(m_AllowPartialBlockRequests),
- "<allowpartialblockrequests>");
+ AddMultipartOptions(m_TestOptions);
+ AddPartialBlockRequestOptions(m_TestOptions);
m_TestOptions.add_option("",
"",
"enable-scavenge",
@@ -10433,7 +10646,7 @@ BuildsCommand::BuildsCommand()
BuildsCommand::~BuildsCommand() = default;
-int
+void
BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -10450,52 +10663,49 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
int ParentCommandArgCount = GetSubCommand(m_Options, argc, argv, m_SubCommands, SubOption, SubCommandArguments);
if (!ParseOptions(ParentCommandArgCount, argv))
{
- return 0;
+ return;
}
if (SubOption == nullptr)
{
- throw zen::OptionParseException("command verb is missing");
+ throw OptionParseException("'verb' option is required", m_Options.help());
}
if (!ParseOptions(*SubOption, gsl::narrow<int>(SubCommandArguments.size()), SubCommandArguments.data()))
{
- return 0;
+ return;
}
- std::filesystem::path SystemRootDir;
- auto ParseSystemOptions = [&]() {
- if (m_SystemRootDir.empty())
- {
- m_SystemRootDir = PickDefaultSystemRootDirectory();
- }
- MakeSafeAbsolutePathÍnPlace(m_SystemRootDir);
+ auto ParseSystemOptions = [&]() {
+ if (m_SystemRootDir.empty())
+ {
+ m_SystemRootDir = PickDefaultSystemRootDirectory();
+ }
+ MakeSafeAbsolutePathÍnPlace(m_SystemRootDir);
};
ParseSystemOptions();
auto ParseStorageOptions = [&](bool RequireNamespace, bool RequireBucket) {
- m_Host = RemoveQuotes(m_Host);
- m_OverrideHost = RemoveQuotes(m_OverrideHost);
- m_Url = RemoveQuotes(m_Url);
- m_Namespace = RemoveQuotes(m_Namespace);
- m_Bucket = RemoveQuotes(m_Bucket);
if (!m_Url.empty())
{
if (!m_Host.empty())
{
- throw zen::OptionParseException(fmt::format("host is not compatible with the url option\n{}", SubOption->help()));
+ throw OptionParseException(fmt::format("'--host' ('{}') conflicts with '--url' ('{}')", m_Host, m_Url), SubOption->help());
}
if (!m_Bucket.empty())
{
- throw zen::OptionParseException(fmt::format("bucket is not compatible with the url option\n{}", SubOption->help()));
+ throw OptionParseException(fmt::format("'--bucket' ('{}') conflicts with '--url' ('{}')", m_Bucket, m_Url),
+ SubOption->help());
}
if (!m_BuildId.empty())
{
- throw zen::OptionParseException(fmt::format("buildid is not compatible with the url option\n{}", SubOption->help()));
+ throw OptionParseException(fmt::format("'--buildid' ('{}') conflicts with '--url' ('{}')", m_BuildId, m_Url),
+ SubOption->help());
}
- if (!ParseCloudUrl(m_Url, m_Host, m_Namespace, m_Bucket, m_BuildId))
+ if (!ParseBuildStorageUrl(m_Url, m_Host, m_Namespace, m_Bucket, m_BuildId))
{
- throw zen::OptionParseException(fmt::format("url does not match the Cloud Artifact URL format\n{}", SubOption->help()));
+ throw OptionParseException("'--url' ('{}') is malformed, it does not match the Cloud Artifact URL format",
+ SubOption->help());
}
}
@@ -10503,183 +10713,51 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!m_StoragePath.empty())
{
- throw zen::OptionParseException(
- fmt::format("host/url/override-host is not compatible with the storage-path option\n{}", SubOption->help()));
+ throw OptionParseException(
+ fmt::format("'--storage-path' ('{}') conflicts with '--host'/'--url'/'--override-host' options", m_StoragePath),
+ SubOption->help());
}
if (RequireNamespace && m_Namespace.empty())
{
- throw zen::OptionParseException(fmt::format("namespace option is required for this storage option\n{}", SubOption->help()));
+ throw OptionParseException("'--namespace' is required", SubOption->help());
}
if (RequireBucket && m_Bucket.empty())
{
- throw zen::OptionParseException(fmt::format("bucket option is required for this storage option\n{}", SubOption->help()));
+ throw OptionParseException("'--bucket' is required", SubOption->help());
}
}
else if (m_StoragePath.empty())
{
- throw zen::OptionParseException(fmt::format("At least one storage option is required\n{}", SubOption->help()));
+ throw OptionParseException("'--host', '--url', '--override-host' or '--storage-path' is required", SubOption->help());
}
MakeSafeAbsolutePathÍnPlace(m_StoragePath);
};
- std::unique_ptr<AuthMgr> Auth;
- HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient",
- .AssumeHttp2 = m_AssumeHttp2,
- .AllowResume = true,
- .RetryCount = 2};
-
- auto CreateAuthMgr = [&]() {
- ZEN_ASSERT(!m_SystemRootDir.empty());
- if (!Auth)
- {
- if (m_EncryptionKey.empty())
- {
- m_EncryptionKey = "abcdefghijklmnopqrstuvxyz0123456";
- if (!IsQuiet)
- {
- ZEN_CONSOLE_WARN("Using default encryption key");
- }
- }
-
- if (m_EncryptionIV.empty())
- {
- m_EncryptionIV = "0123456789abcdef";
- if (!IsQuiet)
- {
- ZEN_CONSOLE_WARN("Using default encryption initialization vector");
- }
- }
-
- AuthConfig AuthMgrConfig = {.RootDirectory = m_SystemRootDir / "auth",
- .EncryptionKey = AesKey256Bit::FromString(m_EncryptionKey),
- .EncryptionIV = AesIV128Bit::FromString(m_EncryptionIV)};
- if (!AuthMgrConfig.EncryptionKey.IsValid())
- {
- throw zen::OptionParseException("Invalid AES encryption key");
- }
- if (!AuthMgrConfig.EncryptionIV.IsValid())
- {
- throw zen::OptionParseException("Invalid AES initialization vector");
- }
- Auth = AuthMgr::Create(AuthMgrConfig);
- }
- };
-
- auto ParseAuthOptions = [&]() {
- m_OpenIdProviderUrl = RemoveQuotes(m_OpenIdProviderUrl);
- m_OpenIdClientId = RemoveQuotes(m_OpenIdClientId);
-
- m_AccessToken = RemoveQuotes(m_AccessToken);
- m_EncryptionKey = RemoveQuotes(m_EncryptionKey);
- m_EncryptionIV = RemoveQuotes(m_EncryptionIV);
-
- m_OAuthUrl = RemoveQuotes(m_OAuthUrl);
- m_OAuthClientId = RemoveQuotes(m_OAuthClientId);
- m_OAuthClientSecret = RemoveQuotes(m_OAuthClientSecret);
-
- m_OidcTokenAuthExecutablePath = RemoveQuotes(m_OidcTokenAuthExecutablePath);
-
- if (!m_OpenIdProviderUrl.empty() && !m_OpenIdClientId.empty())
- {
- CreateAuthMgr();
- std::string ProviderName = m_OpenIdProviderName.empty() ? "Default" : m_OpenIdProviderName;
- Auth->AddOpenIdProvider({.Name = ProviderName, .Url = m_OpenIdProviderUrl, .ClientId = m_OpenIdClientId});
- if (!m_OpenIdRefreshToken.empty())
- {
- Auth->AddOpenIdToken({.ProviderName = ProviderName, .RefreshToken = m_OpenIdRefreshToken});
- }
- }
-
- auto GetEnvAccessToken = [](const std::string& AccessTokenEnv) -> std::string {
- if (!AccessTokenEnv.empty())
- {
- return GetEnvVariable(AccessTokenEnv);
- }
- return {};
- };
-
- auto FindOidcTokenExePath = [](const std::string& OidcTokenAuthExecutablePath) -> std::filesystem::path {
- if (OidcTokenAuthExecutablePath.empty())
- {
- const std::string OidcExecutableName = "OidcToken" ZEN_EXE_SUFFIX_LITERAL;
- std::filesystem::path OidcTokenPath = (GetRunningExecutablePath().parent_path() / OidcExecutableName).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- else
- {
- std::filesystem::path OidcTokenPath = std::filesystem::absolute(StringToPath(OidcTokenAuthExecutablePath)).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- return {};
- };
-
- if (!m_AccessToken.empty())
- {
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(m_AccessToken);
- }
- else if (!m_AccessTokenPath.empty())
- {
- MakeSafeAbsolutePathÍnPlace(m_AccessTokenPath);
- std::string ResolvedAccessToken = ReadAccessTokenFromFile(m_AccessTokenPath);
- if (!ResolvedAccessToken.empty())
- {
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
- }
- }
- else if (!m_OAuthUrl.empty())
- {
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOAuthClientCredentials(
- {.Url = m_OAuthUrl, .ClientId = m_OAuthClientId, .ClientSecret = m_OAuthClientSecret});
- }
- else if (!m_OpenIdProviderName.empty())
- {
- CreateAuthMgr();
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOpenIdProvider(*Auth, m_OpenIdProviderName);
- }
- else if (std::string ResolvedAccessToken = GetEnvAccessToken(m_AccessTokenEnv); !ResolvedAccessToken.empty())
+ auto ParseOutputOptions = [&]() {
+ if (m_Verbose && m_Quiet)
{
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromStaticToken(ResolvedAccessToken);
+ throw OptionParseException("'--verbose' conflicts with '--quiet'", SubOption->help());
}
- else if (std::filesystem::path OidcTokenExePath = FindOidcTokenExePath(m_OidcTokenAuthExecutablePath); !OidcTokenExePath.empty())
+ if (m_LogProgress && m_PlainProgress)
{
- const std::string& CloudHost = m_OverrideHost.empty() ? m_Host : m_OverrideHost;
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromOidcTokenExecutable(OidcTokenExePath, CloudHost, IsQuiet);
+ throw OptionParseException("'--plain-progress' conflicts with '--log-progress'", SubOption->help());
}
-
- if (!ClientSettings.AccessTokenProvider)
+ if (m_LogProgress && m_Quiet)
{
- CreateAuthMgr();
- ClientSettings.AccessTokenProvider = httpclientauth::CreateFromDefaultOpenIdProvider(*Auth);
+ throw OptionParseException("'--quiet' conflicts with '--log-progress'", SubOption->help());
}
- };
-
- auto ParseOutputOptions = [&]() {
- if (m_Verbose && m_Quiet)
+ if (m_PlainProgress && m_Quiet)
{
- throw std::runtime_error("--verbose option is not compatible with --quiet option");
+ throw OptionParseException("'--quiet' conflicts with '--plain-progress'", SubOption->help());
}
IsVerbose = m_Verbose;
IsQuiet = m_Quiet;
if (m_LogProgress)
{
- if (IsQuiet)
- {
- throw std::runtime_error("--quiet option is not compatible with --log-progress option");
- }
ProgressMode = ProgressBar::Mode::Log;
}
- if (m_PlainProgress)
+ else if (m_PlainProgress)
{
- if (IsQuiet)
- {
- throw std::runtime_error("--quiet option is not compatible with --plain-progress option");
- }
ProgressMode = ProgressBar::Mode::Plain;
}
else if (m_Verbose)
@@ -10697,6 +10775,12 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
};
ParseOutputOptions();
+ std::unique_ptr<AuthMgr> Auth;
+ HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient",
+ .AssumeHttp2 = m_AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 2};
+
auto CreateBuildStorage = [&](BuildStorage::Statistics& StorageStats,
BuildStorageCache::Statistics& StorageCacheStats,
const std::filesystem::path& TempPath,
@@ -10704,8 +10788,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
bool RequireBucket) -> StorageInstance {
ParseStorageOptions(RequireNamespace, RequireBucket);
- m_ZenCacheHost = RemoveQuotes(m_ZenCacheHost);
-
StorageInstance Result;
std::string BuildStorageName = ZEN_CLOUD_STORAGE;
@@ -10716,123 +10798,78 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!m_Host.empty() || !m_OverrideHost.empty())
{
- ParseAuthOptions();
+ m_AuthOptions.ParseOptions(*SubOption,
+ m_SystemRootDir,
+ ClientSettings,
+ m_OverrideHost.empty() ? m_Host : m_OverrideHost,
+ Auth,
+ IsQuiet,
+ /*Hidden*/ false);
}
std::string CloudHost;
- auto TestCacheEndpoint = [](std::string_view BaseUrl, const bool AssumeHttp2) -> std::pair<bool, std::string> {
- HttpClientSettings TestClientSettings{.LogCategory = "httpcacheclient",
- .ConnectTimeout = std::chrono::milliseconds{1000},
- .Timeout = std::chrono::milliseconds{2000},
- .AssumeHttp2 = AssumeHttp2,
- .AllowResume = true,
- .RetryCount = 0};
- HttpClient TestHttpClient(BaseUrl, TestClientSettings);
- HttpClient::Response TestResponse = TestHttpClient.Get("/status/builds");
- if (TestResponse.IsSuccess())
- {
- return {true, ""};
- }
- return {false, TestResponse.ErrorMessage("")};
- };
-
if (!m_Host.empty())
{
if (m_OverrideHost.empty() || m_ZenCacheHost.empty())
{
- HttpClient DiscoveryHttpClient(m_Host, ClientSettings);
- HttpClient::Response ServerInfoResponse =
- DiscoveryHttpClient.Get("/api/v1/status/servers", HttpClient::Accept(HttpContentType::kJSON));
- if (!ServerInfoResponse.IsSuccess())
- {
- ServerInfoResponse.ThrowError(fmt::format("Failed to get list of servers from discovery url '{}'", m_Host));
- }
-
- std::string_view JsonResponse = ServerInfoResponse.AsText();
- CbObject ResponseObjectView = LoadCompactBinaryFromJson(JsonResponse).AsObject();
-
- auto TestHostEndpoint = [](std::string_view BaseUrl, const bool AssumeHttp2) -> std::pair<bool, std::string> {
- HttpClientSettings TestClientSettings{.LogCategory = "httpbuildsclient",
- .ConnectTimeout = std::chrono::milliseconds{1000},
- .Timeout = std::chrono::milliseconds{2000},
- .AssumeHttp2 = AssumeHttp2,
- .AllowResume = true,
- .RetryCount = 0};
-
- HttpClient TestHttpClient(BaseUrl, TestClientSettings);
- HttpClient::Response TestResponse = TestHttpClient.Get("/health/live");
- if (TestResponse.IsSuccess())
- {
- return {true, ""};
- }
- return {false, TestResponse.ErrorMessage("")};
- };
+ JupiterServerDiscovery Response = DiscoverJupiterEndpoints(m_Host, ClientSettings);
if (m_OverrideHost.empty())
{
- CbArrayView ServerEndpointsArray = ResponseObjectView["serverEndpoints"sv].AsArrayView();
- std::uint64_t ServerCount = ServerEndpointsArray.Num();
- if (ServerCount == 0)
+ if (Response.ServerEndPoints.empty())
{
throw std::runtime_error(fmt::format("Failed to find any builds hosts at {}", m_Host));
}
- for (CbFieldView ServerEndpointView : ServerEndpointsArray)
+ for (const JupiterServerDiscovery::EndPoint& ServerEndpoint : Response.ServerEndPoints)
{
- CbObjectView ServerEndpointObject = ServerEndpointView.AsObjectView();
-
- std::string_view BaseUrl = ServerEndpointObject["baseUrl"sv].AsString();
- if (!BaseUrl.empty())
+ if (!ServerEndpoint.BaseUrl.empty())
{
- const bool AssumeHttp2 = ServerEndpointObject["assumeHttp2"sv].AsBool(false);
- std::string_view Name = ServerEndpointObject["name"sv].AsString();
- if (auto TestResult = TestHostEndpoint(BaseUrl, AssumeHttp2); TestResult.first)
+ if (JupiterEndpointTestResult TestResult =
+ TestJupiterEndpoint(ServerEndpoint.BaseUrl, ServerEndpoint.AssumeHttp2);
+ TestResult.Success)
{
- CloudHost = BaseUrl;
- m_AssumeHttp2 = AssumeHttp2;
- BuildStorageName = Name;
+ CloudHost = ServerEndpoint.BaseUrl;
+ m_AssumeHttp2 = ServerEndpoint.AssumeHttp2;
+ BuildStorageName = ServerEndpoint.Name;
break;
}
else
{
- ZEN_DEBUG("Unable to reach host {}. Reason: {}", BaseUrl, TestResult.second);
+ ZEN_DEBUG("Unable to reach host {}. Reason: {}", ServerEndpoint.BaseUrl, TestResult.FailureReason);
}
}
}
if (CloudHost.empty())
{
- throw std::runtime_error(
- fmt::format("Failed to find any usable builds hosts out of {} using {}", ServerCount, m_Host));
+ throw std::runtime_error(fmt::format("Failed to find any usable builds hosts out of {} using {}",
+ Response.ServerEndPoints.size(),
+ m_Host));
}
}
- else if (auto TestResult = TestHostEndpoint(m_OverrideHost, m_AssumeHttp2); TestResult.first)
+ else if (JupiterEndpointTestResult TestResult = TestJupiterEndpoint(m_OverrideHost, m_AssumeHttp2); TestResult.Success)
{
CloudHost = m_OverrideHost;
}
else
{
- throw std::runtime_error(fmt::format("Host {} could not be reached. Reason: {}", m_OverrideHost, TestResult.second));
+ throw std::runtime_error(
+ fmt::format("Host {} could not be reached. Reason: {}", m_OverrideHost, TestResult.FailureReason));
}
if (m_ZenCacheHost.empty())
{
- CbArrayView CacheEndpointsArray = ResponseObjectView["cacheEndpoints"sv].AsArrayView();
- std::uint64_t CacheCount = CacheEndpointsArray.Num();
- for (CbFieldView CacheEndpointView : CacheEndpointsArray)
+ for (const JupiterServerDiscovery::EndPoint& CacheEndpoint : Response.CacheEndPoints)
{
- CbObjectView CacheEndpointObject = CacheEndpointView.AsObjectView();
-
- std::string_view BaseUrl = CacheEndpointObject["baseUrl"sv].AsString();
- if (!BaseUrl.empty())
+ if (!CacheEndpoint.BaseUrl.empty())
{
- const bool AssumeHttp2 = CacheEndpointObject["assumeHttp2"sv].AsBool(false);
- std::string_view Name = CacheEndpointObject["name"sv].AsString();
-
- if (auto TestResult = TestCacheEndpoint(BaseUrl, AssumeHttp2); TestResult.first)
+ if (ZenCacheEndpointTestResult TestResult =
+ TestZenCacheEndpoint(CacheEndpoint.BaseUrl, CacheEndpoint.AssumeHttp2);
+ TestResult.Success)
{
- m_ZenCacheHost = BaseUrl;
- CacheAssumeHttp2 = AssumeHttp2;
- BuildCacheName = Name;
+ m_ZenCacheHost = CacheEndpoint.BaseUrl;
+ CacheAssumeHttp2 = CacheEndpoint.AssumeHttp2;
+ BuildCacheName = CacheEndpoint.Name;
break;
}
}
@@ -10847,7 +10884,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
std::string ZenServerLocalHostUrl =
fmt::format("http://127.0.0.1:{}", Entry.EffectiveListenPort.load());
- if (auto TestResult = TestCacheEndpoint(ZenServerLocalHostUrl, false); TestResult.first)
+ if (ZenCacheEndpointTestResult TestResult = TestZenCacheEndpoint(ZenServerLocalHostUrl, false);
+ TestResult.Success)
{
m_ZenCacheHost = ZenServerLocalHostUrl;
CacheAssumeHttp2 = false;
@@ -10858,11 +10896,13 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
if (m_ZenCacheHost.empty() && !IsQuiet)
{
- ZEN_CONSOLE_WARN("Failed to find any usable cache hosts out of {} using {}", CacheCount, m_Host);
+ ZEN_CONSOLE_WARN("Failed to find any usable cache hosts out of {} using {}",
+ Response.CacheEndPoints.size(),
+ m_Host);
}
}
}
- else if (auto TestResult = TestCacheEndpoint(m_ZenCacheHost, false); TestResult.first)
+ else if (ZenCacheEndpointTestResult TestResult = TestZenCacheEndpoint(m_ZenCacheHost, false); TestResult.Success)
{
std::string::size_type HostnameStart = 0;
std::string::size_type HostnameLength = std::string::npos;
@@ -10878,9 +10918,14 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else
{
- ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.second);
+ ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.FailureReason);
+ m_ZenCacheHost = "";
}
}
+ else if (!m_OverrideHost.empty())
+ {
+ CloudHost = m_OverrideHost;
+ }
}
else
{
@@ -10913,37 +10958,46 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else
{
- throw zen::OptionParseException(fmt::format("Storage option is missing\n{}", SubOption->help()));
+ throw OptionParseException("'--host', '--url', '--override-host' or '--storage-path' is required", SubOption->help());
}
if (!m_ZenCacheHost.empty())
{
- Result.CacheHttp = std::make_unique<HttpClient>(m_ZenCacheHost,
- HttpClientSettings{.LogCategory = "httpcacheclient",
- .ConnectTimeout = std::chrono::milliseconds{3000},
- .Timeout = std::chrono::milliseconds{30000},
- .AssumeHttp2 = CacheAssumeHttp2,
- .AllowResume = true,
- .RetryCount = 0});
- Result.BuildCacheStorage = CreateZenBuildStorageCache(*Result.CacheHttp,
- StorageCacheStats,
- m_Namespace,
- m_Bucket,
- TempPath / "zencache",
- m_PrimeCacheOnly);
- CacheDescription = fmt::format("Zen {}{}. SessionId: '{}'",
- BuildCacheName.empty() ? "" : fmt::format("{}, ", BuildCacheName),
- m_ZenCacheHost,
- Result.CacheHttp->GetSessionId());
-
- if (!m_Namespace.empty())
- {
- CacheDescription += fmt::format(". Namespace '{}'", m_Namespace);
+ if (ZenCacheEndpointTestResult TestResult = TestZenCacheEndpoint(m_ZenCacheHost, false); TestResult.Success)
+ {
+ Result.CacheHttp = std::make_unique<HttpClient>(m_ZenCacheHost,
+ HttpClientSettings{.LogCategory = "httpcacheclient",
+ .ConnectTimeout = std::chrono::milliseconds{3000},
+ .Timeout = std::chrono::milliseconds{30000},
+ .AssumeHttp2 = CacheAssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0});
+ Result.BuildCacheStorage = CreateZenBuildStorageCache(
+ *Result.CacheHttp,
+ StorageCacheStats,
+ m_Namespace,
+ m_Bucket,
+ TempPath / "zencache",
+ m_PrimeCacheOnly ? GetSmallWorkerPool(EWorkloadType::Background) : GetTinyWorkerPool(EWorkloadType::Background));
+ CacheDescription = fmt::format("Zen {}{}. SessionId: '{}'",
+ BuildCacheName.empty() ? "" : fmt::format("{}, ", BuildCacheName),
+ m_ZenCacheHost,
+ Result.CacheHttp->GetSessionId());
+
+ if (!m_Namespace.empty())
+ {
+ CacheDescription += fmt::format(". Namespace '{}'", m_Namespace);
+ }
+ if (!m_Bucket.empty())
+ {
+ CacheDescription += fmt::format(" Bucket '{}'", m_Bucket);
+ }
+ Result.CacheName = BuildCacheName.empty() ? m_ZenCacheHost : BuildCacheName;
}
- if (!m_Bucket.empty())
+ else
{
- CacheDescription += fmt::format(" Bucket '{}'", m_Bucket);
+ ZEN_CONSOLE_WARN("Unable to reach cache host {}. Reason: {}", m_ZenCacheHost, TestResult.FailureReason);
+ m_ZenCacheHost = "";
}
- Result.CacheName = BuildCacheName.empty() ? m_ZenCacheHost : BuildCacheName;
}
if (!IsQuiet)
{
@@ -10959,62 +11013,77 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
auto ParsePath = [&]() {
if (m_Path.empty())
{
- throw zen::OptionParseException(fmt::format("local-path is required\n{}", SubOption->help()));
+ throw OptionParseException("'--local-path' is required", SubOption->help());
}
MakeSafeAbsolutePathÍnPlace(m_Path);
};
- auto ParseFileFilters = [&]() {
- for (auto It = begin(m_IncludeWildcard); It != end(m_IncludeWildcard); It++)
- {
- if (*It == '\\')
- {
- *It = '/';
- }
- }
+ auto ParseFileFilters = [&](std::vector<std::string>& OutIncludeWildcards, std::vector<std::string>& OutExcludeWildcards) {
+ auto SplitWildcard = [](const std::string_view Wildcard) -> std::vector<std::string> {
+ std::vector<std::string> Wildcards;
+ ForEachStrTok(Wildcard, ';', [&Wildcards](std::string_view Wildcard) {
+ if (!Wildcard.empty())
+ {
+ std::string CleanWildcard(Wildcard);
+ for (auto It = begin(CleanWildcard); It != end(CleanWildcard); It++)
+ {
+ if (*It == '\\')
+ {
+ *It = '/';
+ }
+ }
- for (auto It = begin(m_ExcludeWildcard); It != end(m_ExcludeWildcard); It++)
- {
- if (*It == '\\')
- {
- *It = '/';
- }
- }
+ Wildcards.emplace_back(std::move(CleanWildcard));
+ }
+ return true;
+ });
+ return Wildcards;
+ };
+
+ OutIncludeWildcards = SplitWildcard(m_IncludeWildcard);
+ OutExcludeWildcards = SplitWildcard(m_ExcludeWildcard);
};
auto ParseDiffPath = [&]() {
if (m_DiffPath.empty())
{
- throw zen::OptionParseException(fmt::format("compare-path is required\n{}", SubOption->help()));
+ throw OptionParseException("'--compare-path' is required", SubOption->help());
}
MakeSafeAbsolutePathÍnPlace(m_DiffPath);
};
auto ParseBlobHash = [&]() -> IoHash {
- m_BlobHash = RemoveQuotes(m_BlobHash);
if (m_BlobHash.empty())
{
- throw zen::OptionParseException(fmt::format("Blob hash string is missing\n{}", SubOption->help()));
+ throw OptionParseException("'--blob-hash' is required", SubOption->help());
+ }
+
+ if (m_BlobHash.length() != IoHash::StringLength)
+ {
+ throw OptionParseException(
+ fmt::format("'--blob-hash' ('{}') is malfomed, it must be {} characters long", m_BlobHash, IoHash::StringLength),
+ SubOption->help());
}
IoHash BlobHash;
if (!IoHash::TryParse(m_BlobHash, BlobHash))
{
- throw zen::OptionParseException(fmt::format("Blob hash string is invalid\n{}", SubOption->help()));
+ throw OptionParseException(fmt::format("'--blob-hash' ('{}') is malformed", m_BlobHash), SubOption->help());
}
return BlobHash;
};
auto ParseBuildId = [&]() -> Oid {
- m_BuildId = RemoveQuotes(m_BuildId);
if (m_BuildId.length() != Oid::StringLength)
{
- throw zen::OptionParseException(fmt::format("Invalid build id\n{}", SubOption->help()));
+ throw OptionParseException(
+ fmt::format("'--build-id' ('{}') is malfomed, it must be {} characters long", m_BuildId, Oid::StringLength),
+ SubOption->help());
}
else if (Oid BuildId = Oid::FromHexString(m_BuildId); BuildId == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("Invalid build id\n{}", SubOption->help()));
+ throw OptionParseException(fmt::format("'--build-id' ('{}') is invalid", m_BuildId), SubOption->help());
}
else
{
@@ -11023,14 +11092,15 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
};
auto ParseBuildPartId = [&]() -> Oid {
- m_BuildPartId = RemoveQuotes(m_BuildPartId);
if (m_BuildPartId.length() != Oid::StringLength)
{
- throw zen::OptionParseException(fmt::format("Invalid build part id\n{}", SubOption->help()));
+ throw OptionParseException(
+ fmt::format("'--build-id' ('{}') is malformed, it must be {} characters long", m_BuildPartId, Oid::StringLength),
+ SubOption->help());
}
else if (Oid BuildPartId = Oid::FromHexString(m_BuildPartId); BuildPartId == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("Invalid build part id\n{}", SubOption->help()));
+ throw OptionParseException(fmt::format("'--build-id' ('{}') is malformed", m_BuildPartId), SubOption->help());
}
else
{
@@ -11045,7 +11115,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
BuildPartIds.push_back(Oid::TryFromHexString(RemoveQuotes(BuildPartId)));
if (BuildPartIds.back() == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("build-part-id '{}' is invalid\n{}", BuildPartId, SubOption->help()));
+ throw OptionParseException(fmt::format("'--build-part-id' ('{}') is malformed", BuildPartId), SubOption->help());
}
}
return BuildPartIds;
@@ -11058,7 +11128,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
BuildPartNames.push_back(std::string(RemoveQuotes(BuildPartName)));
if (BuildPartNames.back().empty())
{
- throw zen::OptionParseException(fmt::format("build-part-names '{}' is invalid\n{}", BuildPartName, SubOption->help()));
+ throw OptionParseException(fmt::format("'--build-part-names' ('{}') is invalid", BuildPartName), SubOption->help());
}
}
return BuildPartNames;
@@ -11069,11 +11139,13 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (m_BuildMetadataPath.empty() && m_BuildMetadata.empty())
{
- throw zen::OptionParseException(fmt::format("Options for builds target are missing\n{}", SubOption->help()));
+ throw OptionParseException("'--metadata-path' or '--metadata' is required", SubOption->help());
}
if (!m_BuildMetadataPath.empty() && !m_BuildMetadata.empty())
{
- throw zen::OptionParseException(fmt::format("Conflicting options for builds target\n{}", SubOption->help()));
+ throw OptionParseException(
+ fmt::format("'--metadata-path' ('{}') conflicts with '--metadata' ('{}')", m_BuildMetadataPath, m_BuildMetadata),
+ SubOption->help());
}
if (!m_BuildMetadataPath.empty())
@@ -11090,7 +11162,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
return MetaData;
}
- m_BuildMetadata = RemoveQuotes(m_BuildMetadata);
if (!m_BuildMetadata.empty())
{
CbObjectWriter MetaDataWriter(1024);
@@ -11110,12 +11181,11 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!m_BuildMetadataPath.empty())
{
- throw zen::OptionParseException(
- fmt::format("metadata-path option is only valid if creating a build\n{}", SubOption->help()));
+ throw OptionParseException("'--metadata-path' requires '--create-build'", SubOption->help());
}
if (!m_BuildMetadata.empty())
{
- throw zen::OptionParseException(fmt::format("metadata option is only valid if creating a build\n{}", SubOption->help()));
+ throw OptionParseException("'--metadata' requires '--create-build'", SubOption->help());
}
}
return {};
@@ -11130,10 +11200,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!IsQuiet)
{
- ZEN_CONSOLE("Running {}: {} (pid {})",
- GetRunningExecutablePath(),
- ZEN_CFG_VERSION_BUILD_STRING_FULL,
- GetCurrentProcessId());
+ LogExecutableVersionAndPid();
}
}
@@ -11181,8 +11248,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
WriteFile(ListResultPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size()));
}
}
-
- return 0;
+ return;
}
if (SubOption == &m_ListOptions)
@@ -11194,10 +11260,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!IsQuiet)
{
- ZEN_CONSOLE("Running {}: {} (pid {})",
- GetRunningExecutablePath(),
- ZEN_CFG_VERSION_BUILD_STRING_FULL,
- GetCurrentProcessId());
+ LogExecutableVersionAndPid();
}
}
CbObject QueryObject;
@@ -11274,15 +11337,90 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
WriteFile(m_ListResultPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size()));
}
}
-
- return 0;
+ return;
}
+ if (SubOption == &m_ListBlocksOptions)
+ {
+ MakeSafeAbsolutePathÍnPlace(m_ListResultPath);
+
+ if (!m_ListResultPath.empty())
+ {
+ if (!IsQuiet)
+ {
+ LogExecutableVersionAndPid();
+ }
+ }
+
+ if (m_ListBlocksMaxCount == 0)
+ {
+ throw OptionParseException(fmt::format("'--max-count' ('{}') is invalid", m_ListBlocksMaxCount), SubOption->help());
+ }
+
+ BuildStorage::Statistics StorageStats;
+ BuildStorageCache::Statistics StorageCacheStats;
+
+ if (m_ZenFolderPath.empty())
+ {
+ m_ZenFolderPath = std::filesystem::current_path() / ZenFolderName;
+ }
+ MakeSafeAbsolutePathÍnPlace(m_ZenFolderPath);
+
+ CreateDirectories(m_ZenFolderPath);
+ auto _ = MakeGuard([this]() {
+ if (CleanDirectory(m_ZenFolderPath, {}))
+ {
+ std::error_code DummyEc;
+ RemoveDir(m_ZenFolderPath, DummyEc);
+ }
+ });
+
+ StorageInstance Storage = CreateBuildStorage(StorageStats,
+ StorageCacheStats,
+ ZenTempFolderPath(m_ZenFolderPath),
+ /*RequriesNamespace*/ true,
+ /*RequireBucket*/ true);
+
+ const Oid BuildId = ParseBuildId();
+
+ CbObject Response = Storage.BuildStorage->FindBlocks(BuildId, m_ListBlocksMaxCount);
+ ZEN_ASSERT(ValidateCompactBinary(Response.GetView(), CbValidateMode::Default) == CbValidateError::None);
+
+ std::vector<ChunkBlockDescription> BlockDescriptions = ParseChunkBlockDescriptionList(Response);
+ if (!IsQuiet)
+ {
+ ZEN_CONSOLE("Response contains {} block", BlockDescriptions.size());
+ }
+ if (m_ListResultPath.empty())
+ {
+ ExtendableStringBuilder<1024> SB;
+ CompactBinaryToJson(Response.GetView(), SB);
+ ForEachStrTok(SB.ToView(), '\n', [](std::string_view Row) {
+ ZEN_CONSOLE("{}", Row);
+ return true;
+ });
+ }
+ else
+ {
+ if (ToLower(m_ListResultPath.extension().string()) == ".cbo")
+ {
+ MemoryView ResponseView = Response.GetView();
+ WriteFile(m_ListResultPath, IoBuffer(IoBuffer::Wrap, ResponseView.GetData(), ResponseView.GetSize()));
+ }
+ else
+ {
+ ExtendableStringBuilder<1024> SB;
+ CompactBinaryToJson(Response.GetView(), SB);
+ WriteFile(m_ListResultPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size()));
+ }
+ }
+ return;
+ }
if (SubOption == &m_UploadOptions)
{
if (!IsQuiet)
{
- ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId());
+ LogExecutableVersionAndPid();
}
ZenState InstanceState;
@@ -11333,8 +11471,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
const std::filesystem::path TempDir = ZenTempFolderPath(m_ZenFolderPath);
- m_ManifestPath = RemoveQuotes(m_ManifestPath);
-
UploadFolder(Storage,
BuildId,
BuildPartId,
@@ -11350,7 +11486,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
m_Clean,
m_PostUploadVerify);
- if (false)
+ if (true)
{
if (!IsQuiet)
{
@@ -11373,20 +11509,40 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
: 0);
}
}
- return AbortFlag ? 11 : 0;
+ if (AbortFlag)
+ {
+ throw std::runtime_error("Upload aborted");
+ }
}
+ auto ParseAllowPartialBlockRequests = [&]() -> EPartialBlockRequestMode {
+ if (m_PrimeCacheOnly)
+ {
+ return EPartialBlockRequestMode::Off;
+ }
+ EPartialBlockRequestMode Mode = PartialBlockRequestModeFromString(m_AllowPartialBlockRequests);
+ if (Mode == EPartialBlockRequestMode::Invalid)
+ {
+ throw OptionParseException(fmt::format("'--allow-partial-block-requests' ('{}') is invalid", m_AllowPartialBlockRequests),
+ SubOption->help());
+ }
+ return Mode;
+ };
+
if (SubOption == &m_DownloadOptions)
{
if (!IsQuiet)
{
- ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId());
+ LogExecutableVersionAndPid();
}
ZenState InstanceState;
ParsePath();
- ParseFileFilters();
+
+ std::vector<std::string> IncludeWildcards;
+ std::vector<std::string> ExcludeWildcards;
+ ParseFileFilters(IncludeWildcards, ExcludeWildcards);
if (m_ZenFolderPath.empty())
{
@@ -11407,53 +11563,64 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (m_PostDownloadVerify && m_PrimeCacheOnly)
{
- throw zen::OptionParseException(
- fmt::format("'cache-prime-only' option is not compatible with 'verify' option\n{}", SubOption->help()));
+ throw OptionParseException("'--cache-prime-only' conflicts with '--verify'", SubOption->help());
}
if (m_Clean && m_PrimeCacheOnly)
{
- ZEN_CONSOLE_WARN("Ignoring 'clean' option when 'cache-prime-only' is enabled");
+ ZEN_CONSOLE_WARN("Ignoring '--clean' option when '--cache-prime-only' is enabled");
+ }
+
+ if (m_Force && m_PrimeCacheOnly)
+ {
+ ZEN_CONSOLE_WARN("Ignoring '--force' option when '--cache-prime-only' is enabled");
}
- if (m_AllowPartialBlockRequests && m_PrimeCacheOnly)
+ if (m_AllowPartialBlockRequests != "false" && m_PrimeCacheOnly)
{
- ZEN_CONSOLE_WARN("Ignoring 'allow-partial-block-requests' option when 'cache-prime-only' is enabled");
+ ZEN_CONSOLE_WARN("Ignoring '--allow-partial-block-requests' option when '--cache-prime-only' is enabled");
}
std::vector<Oid> BuildPartIds = ParseBuildPartIds();
std::vector<std::string> BuildPartNames = ParseBuildPartNames();
- // TODO: Add file filters
+ EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests();
+
DownloadFolder(Storage,
BuildId,
BuildPartIds,
BuildPartNames,
m_Path,
- m_ZenFolderPath,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests && !m_PrimeCacheOnly,
- m_Clean,
- m_PostDownloadVerify,
- m_PrimeCacheOnly,
- m_EnableScavenging,
- m_IncludeWildcard,
- m_ExcludeWildcard);
-
- return AbortFlag ? 11 : 0;
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = m_ZenFolderPath,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = m_Clean,
+ .PostDownloadVerify = m_PostDownloadVerify,
+ .PrimeCacheOnly = m_PrimeCacheOnly,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging && !m_Force,
+ .EnableTargetFolderScavenging = !m_Force,
+ .IncludeWildcards = IncludeWildcards,
+ .ExcludeWildcards = ExcludeWildcards});
+
+ if (AbortFlag)
+ {
+ throw std::runtime_error("Download aborted");
+ }
}
if (SubOption == &m_LsOptions)
{
if (!IsQuiet)
{
- ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId());
+ LogExecutableVersionAndPid();
}
ZenState InstanceState;
- ParseFileFilters();
+ std::vector<std::string> IncludeWildcards;
+ std::vector<std::string> ExcludeWildcards;
+ ParseFileFilters(IncludeWildcards, ExcludeWildcards);
if (m_ZenFolderPath.empty())
{
@@ -11475,9 +11642,12 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::vector<Oid> BuildPartIds = ParseBuildPartIds();
std::vector<std::string> BuildPartNames = ParseBuildPartNames();
- ListBuild(Storage, BuildId, BuildPartIds, BuildPartNames, m_IncludeWildcard, m_ExcludeWildcard);
+ ListBuild(Storage, BuildId, BuildPartIds, BuildPartNames, IncludeWildcards, ExcludeWildcards);
- return AbortFlag ? 11 : 0;
+ if (AbortFlag)
+ {
+ throw std::runtime_error("List build aborted");
+ }
}
if (SubOption == &m_DiffOptions)
@@ -11486,14 +11656,17 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ParseDiffPath();
DiffFolders(m_Path, m_DiffPath, m_OnlyChunked);
- return AbortFlag ? 11 : 0;
+ if (AbortFlag)
+ {
+ throw std::runtime_error("Diff folders aborted");
+ }
}
if (SubOption == &m_FetchBlobOptions)
{
if (!IsQuiet)
{
- ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId());
+ LogExecutableVersionAndPid();
}
BuildStorage::Statistics StorageStats;
@@ -11529,7 +11702,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ValidateBlob(*Storage.BuildStorage, BuildId, BlobHash, CompressedSize, DecompressedSize);
if (AbortFlag)
{
- return 11;
+ throw std::runtime_error("Fetch blob aborted");
}
if (!IsQuiet)
{
@@ -11538,14 +11711,14 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
CompressedSize,
DecompressedSize);
}
- return 0;
+ return;
}
if (SubOption == &m_ValidateBuildPartOptions)
{
if (!IsQuiet)
{
- ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId());
+ LogExecutableVersionAndPid();
}
ZenState InstanceState;
@@ -11578,7 +11751,9 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!m_BuildPartName.empty() && !m_BuildPartId.empty())
{
- throw zen::OptionParseException(fmt::format("build-part-id conflicts with build-part-name\n{}", SubOption->help()));
+ throw OptionParseException(
+ fmt::format("'--build-part-id' ('{}') conflicts with '--build-part-name' ('{}')", m_BuildPartId, m_BuildPartName),
+ SubOption->help());
}
const Oid BuildPartId = m_BuildPartName.empty() ? Oid::Zero : ParseBuildPartId();
@@ -11587,7 +11762,10 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
DownloadStatistics DownloadStats;
ValidateBuildPart(*Storage.BuildStorage, BuildId, BuildPartId, m_BuildPartName, ValidateStats, DownloadStats);
- return AbortFlag ? 13 : 0;
+ if (AbortFlag)
+ {
+ throw std::runtime_error("Validate build part failed");
+ }
}
if (SubOption == &m_MultiTestDownloadOptions)
@@ -11605,6 +11783,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
MakeSafeAbsolutePathÍnPlace(m_ZenFolderPath);
+ EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests();
+
BuildStorage::Statistics StorageStats;
BuildStorageCache::Statistics StorageCacheStats;
@@ -11620,27 +11800,25 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
Oid BuildId = Oid::FromHexString(RemoveQuotes(BuildIdString));
if (BuildId == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("invalid build id {}\n{}", BuildIdString, SubOption->help()));
+ throw OptionParseException(fmt::format("'--build-id' ('{}') is malformed", BuildIdString), SubOption->help());
}
DownloadFolder(Storage,
BuildId,
{},
{},
m_Path,
- m_ZenFolderPath,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- BuildIdString == m_BuildIds.front(),
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = m_ZenFolderPath,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = BuildIdString == m_BuildIds.front(),
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = false});
if (AbortFlag)
{
- ZEN_CONSOLE("Download cancelled");
- return 11;
+ throw std::runtime_error("Multitest aborted");
}
if (!IsQuiet)
{
@@ -11651,7 +11829,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_CONSOLE("Completed in {}", NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
}
- return 0;
}
auto ParseZenProcessId = [&]() {
@@ -11667,7 +11844,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
if (!RunningProcess.IsValid())
{
- throw zen::OptionParseException(
+ throw std::runtime_error(
fmt::format("Unable to find a running instance of the zen executable '{}'", RunningExecutablePath));
}
m_ZenProcessId = RunningProcess.Pid();
@@ -11679,7 +11856,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ParseZenProcessId();
ZenState RunningState(m_ZenProcessId);
RunningState.StateData().Pause.store(true);
- return 0;
}
if (SubOption == &m_ResumeOptions)
@@ -11687,7 +11863,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ParseZenProcessId();
ZenState RunningState(m_ZenProcessId);
RunningState.StateData().Pause.store(false);
- return 0;
}
if (SubOption == &m_AbortOptions)
@@ -11695,7 +11870,6 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ParseZenProcessId();
ZenState RunningState(m_ZenProcessId);
RunningState.StateData().Abort.store(true);
- return 0;
}
if (SubOption == &m_TestOptions)
@@ -11722,6 +11896,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
});
+ EPartialBlockRequestMode PartialBlockRequestMode = ParseAllowPartialBlockRequests();
+
BuildStorage::Statistics StorageStats;
BuildStorageCache::Statistics StorageCacheStats;
@@ -11795,8 +11971,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
true);
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Upload failed.");
- return 11;
+ throw std::runtime_error("Test aborted. (Upload build)");
}
ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}'", BuildId, BuildPartId, m_BuildPartName, DownloadPath);
@@ -11805,20 +11980,19 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{BuildPartId},
{},
DownloadPath,
- DownloadPath / ZenFolderName,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- true,
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = true,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = false});
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Download failed.");
- return 11;
+ throw std::runtime_error("Test aborted. (Download build)");
}
ZEN_CONSOLE("\nRe-download Build {}, Part {} ({}) to '{}' (identical target)", BuildId, BuildPartId, m_BuildPartName, DownloadPath);
@@ -11827,20 +12001,19 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{BuildPartId},
{},
DownloadPath,
- DownloadPath / ZenFolderName,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- false,
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = false,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true});
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Re-download failed. (identical target)");
- return 11;
+ throw std::runtime_error("Test aborted. (Re-download identical target)");
}
auto ScrambleDir = [](const std::filesystem::path& Path) {
@@ -11870,7 +12043,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
return true;
};
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
uint32_t Randomizer = 0;
auto FileSizeIt = DownloadContent.FileSizes.begin();
@@ -11941,20 +12114,19 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{BuildPartId},
{},
DownloadPath,
- DownloadPath / ZenFolderName,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- false,
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = false,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true});
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Re-download failed. (scrambled target)");
- return 11;
+ throw std::runtime_error("Test aborted. (Re-download scrambled target)");
}
ScrambleDir(DownloadPath);
@@ -11985,8 +12157,7 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
true);
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Upload of scrambled failed.");
- return 11;
+ throw std::runtime_error("Test aborted. (Upload scrambled)");
}
ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}' (original)", BuildId, BuildPartId, m_BuildPartName, DownloadPath);
@@ -11995,20 +12166,19 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{BuildPartId},
{},
DownloadPath,
- DownloadPath / ZenFolderName,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- false,
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = false,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true});
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Re-download failed.");
- return 11;
+ throw std::runtime_error("Test aborted. (Download original)");
}
ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}' (scrambled)", BuildId2, BuildPartId2, m_BuildPartName, DownloadPath);
@@ -12017,20 +12187,18 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{BuildPartId2},
{},
DownloadPath,
- DownloadPath / ZenFolderName,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- false,
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = false,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true});
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Re-download failed.");
- return 11;
+ throw std::runtime_error("Test aborted. (Download scrambled)");
}
ZEN_CONSOLE("\nRe-download Build {}, Part {} ({}) to '{}' (scrambled)", BuildId2, BuildPartId2, m_BuildPartName, DownloadPath);
@@ -12039,20 +12207,18 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{BuildPartId2},
{},
DownloadPath,
- DownloadPath / ZenFolderName,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- false,
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = false,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true});
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Re-download failed.");
- return 11;
+ throw std::runtime_error("Test aborted. (Re-download scrambled)");
}
ZEN_CONSOLE("\nDownload Build {}, Part {} ({}) to '{}' (original)", BuildId, BuildPartId, m_BuildPartName, DownloadPath2);
@@ -12061,25 +12227,20 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{BuildPartId},
{},
DownloadPath2,
- DownloadPath2 / ZenFolderName,
- m_SystemRootDir,
- m_AllowMultiparts,
- m_AllowPartialBlockRequests,
- false,
- true,
- false,
- m_EnableScavenging,
- ""sv,
- ""sv);
+ DownloadOptions{.SystemRootDir = m_SystemRootDir,
+ .ZenFolderPath = DownloadPath2 / ZenFolderName,
+ .AllowMultiparts = m_AllowMultiparts,
+ .PartialBlockRequestMode = PartialBlockRequestMode,
+ .CleanTargetFolder = false,
+ .PostDownloadVerify = true,
+ .PrimeCacheOnly = false,
+ .EnableOtherDownloadsScavenging = m_EnableScavenging,
+ .EnableTargetFolderScavenging = true});
if (AbortFlag)
{
- ZEN_CONSOLE_ERROR("Re-download failed.");
- return 11;
+ throw std::runtime_error("Test aborted. (Download original)");
}
-
- return 0;
}
- ZEN_ASSERT(false);
}
} // namespace zen
diff --git a/src/zen/cmds/builds_cmd.h b/src/zen/cmds/builds_cmd.h
index d057d24ac..ddec1f791 100644
--- a/src/zen/cmds/builds_cmd.h
+++ b/src/zen/cmds/builds_cmd.h
@@ -2,6 +2,7 @@
#pragma once
+#include "../authutils.h"
#include "../zen.h"
#include <zenhttp/auth/authmgr.h>
@@ -13,13 +14,14 @@ namespace zen {
class BuildsCommand : public CacheStoreCommand
{
public:
- static constexpr char Name[] = "builds";
- static constexpr char Description[] = "Manage builds - list, upload, download, diff";
+ static constexpr char Name[] = "builds";
+ static constexpr char Description[] =
+ "Manage builds - list, list-namespaces, ls, upload, download, diff, fetch-blob, validate-part, pause, resume, abort";
BuildsCommand();
~BuildsCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -60,32 +62,13 @@ private:
std::string m_BuildPartName; // Defaults to name of leaf folder in m_Path
std::string m_BuildPartId; // Defaults to a generated id when creating part, looked up when downloading using m_BuildPartName
bool m_Clean = false;
+ bool m_Force = false;
uint8_t m_BlockReuseMinPercentLimit = 85;
bool m_AllowMultiparts = true;
- bool m_AllowPartialBlockRequests = true;
+ std::string m_AllowPartialBlockRequests = "mixed";
std::string m_ManifestPath; // Not a std::filesystem::path since it can be relative to m_Path
- // Direct access token (may expire)
- std::string m_AccessToken;
- std::string m_AccessTokenEnv;
- std::filesystem::path m_AccessTokenPath;
-
- // Auth manager token encryption
- std::string m_EncryptionKey; // 256 bit AES encryption key
- std::string m_EncryptionIV; // 128 bit AES initialization vector
-
- // OpenId acccess token
- std::string m_OpenIdProviderName;
- std::string m_OpenIdProviderUrl;
- std::string m_OpenIdClientId;
- std::string m_OpenIdRefreshToken;
-
- // OAuth acccess token
- std::string m_OAuthUrl;
- std::string m_OAuthClientId;
- std::string m_OAuthClientSecret;
-
- std::string m_OidcTokenAuthExecutablePath;
+ AuthCommandLineOptions m_AuthOptions;
std::string m_Verb; // list, upload, download
@@ -96,6 +79,9 @@ private:
std::filesystem::path m_ListQueryPath;
std::filesystem::path m_ListResultPath;
+ cxxopts::Options m_ListBlocksOptions{"list-blocks", "List recent blocks"};
+ uint32_t m_ListBlocksMaxCount = 16;
+
std::filesystem::path m_Path;
cxxopts::Options m_UploadOptions{"upload", "Upload a folder"};
@@ -132,8 +118,9 @@ private:
cxxopts::Options m_MultiTestDownloadOptions{"multi-test-download", "Test multiple sequenced downloads with verify"};
std::vector<std::string> m_BuildIds;
- cxxopts::Options* m_SubCommands[13] = {&m_ListNamespacesOptions,
+ cxxopts::Options* m_SubCommands[14] = {&m_ListNamespacesOptions,
&m_ListOptions,
+ &m_ListBlocksOptions,
&m_UploadOptions,
&m_DownloadOptions,
&m_PauseOptions,
diff --git a/src/zen/cmds/cache_cmd.cpp b/src/zen/cmds/cache_cmd.cpp
index 90f428b29..85dcd7648 100644
--- a/src/zen/cmds/cache_cmd.cpp
+++ b/src/zen/cmds/cache_cmd.cpp
@@ -2,6 +2,7 @@
#include "cache_cmd.h"
+#include <zencore/compactbinarybuilder.h>
#include <zencore/compress.h>
#include <zencore/except.h>
#include <zencore/filesystem.h>
@@ -13,15 +14,11 @@
#include <zenhttp/httpclient.h>
#include <zenhttp/httpcommon.h>
#include <zenhttp/packageformat.h>
-#include <zenutil/cache/cacherequests.h>
+#include <zenstore/cache/cachepolicy.h>
#include <memory>
#include <random>
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
-ZEN_THIRD_PARTY_INCLUDES_END
-
namespace zen {
namespace {
@@ -70,59 +67,53 @@ DropCommand::DropCommand()
DropCommand::~DropCommand() = default;
-int
+void
DropCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_NamespaceName.empty())
{
- throw zen::OptionParseException("Drop command requires a namespace");
+ throw OptionParseException("'--namespace' is required", m_Options.help());
}
- cpr::Session Session;
+ std::string Url;
+ std::string DropDescription;
+
if (m_BucketName.empty())
{
- ZEN_CONSOLE("Dropping cache namespace '{}' from '{}'", m_NamespaceName, m_HostName);
- Session.SetUrl({fmt::format("{}/z$/{}", m_HostName, m_NamespaceName)});
+ DropDescription = fmt::format("cache namespace '{}' from '{}'", m_NamespaceName, m_HostName);
+ Url = fmt::format("/z$/{}", m_NamespaceName);
}
else
{
- ZEN_CONSOLE("Dropping cache bucket '{}/{}' from '{}'", m_NamespaceName, m_BucketName, m_HostName);
- Session.SetUrl({fmt::format("{}/z$/{}/{}", m_HostName, m_NamespaceName, m_BucketName)});
+ DropDescription = fmt::format("cache bucket '{}/{}' from '{}'", m_NamespaceName, m_BucketName, m_HostName);
+ Url = fmt::format("/z$/{}/{}", m_NamespaceName, m_BucketName);
}
- cpr::Response Result = Session.Delete();
-
- if (zen::IsHttpSuccessCode(Result.status_code))
- {
- ZEN_CONSOLE("OK: drop succeeded");
-
- return 0;
- }
+ ZEN_CONSOLE("Dropping {}", DropDescription);
- if (Result.status_code)
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Delete(Url))
{
- ZEN_CONSOLE_ERROR("Drop failed: {}: {} ({})", Result.status_code, Result.reason, Result.text);
+ ZEN_CONSOLE("{}", Response.ToText());
}
else
{
- ZEN_CONSOLE_ERROR("Drop failed: {}", Result.error.message);
+ Response.ThrowError(fmt::format("Failed to drop {}", DropDescription));
}
-
- return 1;
}
CacheInfoCommand::CacheInfoCommand()
@@ -144,87 +135,76 @@ CacheInfoCommand::CacheInfoCommand()
CacheInfoCommand::~CacheInfoCommand() = default;
-int
+void
CacheInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
- cpr::Session Session;
- Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
+ std::string Url;
if (m_HostName.empty())
{
if (!m_SizeInfoBucketNames.empty())
{
- throw zen::OptionParseException("--bucketsizes option needs a --namespace");
+ throw OptionParseException("'--bucketsizes' requires '--namespace'", m_Options.help());
}
if (m_BucketSizeInfo)
{
- throw zen::OptionParseException("--bucketsizes option needs a --namespace and a --bucket");
+ throw OptionParseException("'--bucketsize' requires '--namespace' and '--bucket'", m_Options.help());
}
ZEN_CONSOLE("Info on cache from '{}'", m_HostName);
- Session.SetUrl({fmt::format("{}/z$", m_HostName)});
+ Url = "/z$";
}
else if (m_BucketName.empty())
{
if (m_BucketSizeInfo)
{
- throw zen::OptionParseException("--bucketsizes option needs a --bucket");
+ throw OptionParseException(fmt::format("'--bucketsize' requires '--namespace' and '--bucket' ('{}')", m_BucketName),
+ m_Options.help());
}
ZEN_CONSOLE("Info on cache namespace '{}' from '{}'", m_NamespaceName, m_HostName);
- Session.SetUrl({fmt::format("{}/z$/{}", m_HostName, m_NamespaceName)});
+ Url = fmt::format("/z$/{}", m_NamespaceName);
}
else
{
if (!m_SizeInfoBucketNames.empty())
{
- throw zen::OptionParseException("--bucketsizes option can not be used together with --bucket option");
+ throw OptionParseException("'--bucketsizes' conflicts with '--bucket'", m_Options.help());
}
ZEN_CONSOLE("Info on cache bucket '{}/{}' from '{}'", m_NamespaceName, m_BucketName, m_HostName);
- Session.SetUrl({fmt::format("{}/z$/{}/{}", m_HostName, m_NamespaceName, m_BucketName)});
+ Url = fmt::format("/z$/{}/{}", m_NamespaceName, m_BucketName);
}
- cpr::Parameters Parameters;
+ HttpClient::KeyValueMap Parameters;
if (!m_SizeInfoBucketNames.empty())
{
- Parameters.Add({"bucketsizes", m_SizeInfoBucketNames});
+ Parameters.Entries.insert({"bucketsizes", m_SizeInfoBucketNames});
}
if (m_BucketSizeInfo)
{
- Parameters.Add({"bucketsize", "true"});
+ Parameters.Entries.insert({"bucketsize", "true"});
}
- Session.SetParameters(Parameters);
- cpr::Response Result = Session.Get();
-
- if (zen::IsHttpSuccessCode(Result.status_code))
- {
- ZEN_CONSOLE("{}", Result.text);
-
- return 0;
- }
-
- if (Result.status_code)
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Get(Url, HttpClient::Accept(ZenContentType::kJSON), Parameters))
{
- ZEN_CONSOLE_ERROR("Info failed: {}: {} ({})", Result.status_code, Result.reason, Result.text);
+ ZEN_CONSOLE("{}", Response.ToText());
}
else
{
- ZEN_CONSOLE_ERROR("Info failed: {}", Result.error.message);
+ Response.ThrowError("Info failed");
}
-
- return 1;
}
CacheStatsCommand::CacheStatsCommand()
@@ -235,46 +215,32 @@ CacheStatsCommand::CacheStatsCommand()
CacheStatsCommand::~CacheStatsCommand() = default;
-int
+void
CacheStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
- cpr::Session Session;
- Session.SetUrl({fmt::format("{}/stats/z$", m_HostName)});
- Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
-
- cpr::Response Result = Session.Get();
-
- if (zen::IsHttpSuccessCode(Result.status_code))
- {
- ZEN_CONSOLE("{}", Result.text);
-
- return 0;
- }
-
- if (Result.status_code)
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Get("/stats/z$", HttpClient::Accept(ZenContentType::kJSON)))
{
- ZEN_CONSOLE_ERROR("Info failed: {}: {} ({})", Result.status_code, Result.reason, Result.text);
+ ZEN_CONSOLE("{}", Response.ToText());
}
else
{
- ZEN_CONSOLE_ERROR("Info failed: {}", Result.error.message);
+ Response.ThrowError("Info failed");
}
-
- return 1;
}
CacheDetailsCommand::CacheDetailsCommand()
@@ -296,87 +262,82 @@ CacheDetailsCommand::CacheDetailsCommand()
CacheDetailsCommand::~CacheDetailsCommand() = default;
-int
+void
CacheDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
- cpr::Session Session;
- cpr::Parameters Parameters;
+ HttpClient::KeyValueMap Parameters;
if (m_Details)
{
- Parameters.Add({"details", "true"});
+ Parameters.Entries.insert({"details", "true"});
}
if (m_AttachmentDetails)
{
- Parameters.Add({"attachmentdetails", "true"});
+ Parameters.Entries.insert({"attachmentdetails", "true"});
}
+
+ HttpClient::KeyValueMap Headers;
if (m_CSV)
{
- Parameters.Add({"csv", "true"});
+ Parameters.Entries.insert({"csv", "true"});
}
else
{
- Session.SetHeader(cpr::Header{{"Accept", "application/json"}});
+ Headers = HttpClient::Accept(ZenContentType::kJSON);
}
+ std::string Url;
if (!m_ValueKey.empty())
{
- if (m_Namespace.empty() || m_Bucket.empty())
+ if (m_Namespace.empty())
+ {
+ throw OptionParseException("'--namespace' is required", m_Options.help());
+ }
+ if (m_Bucket.empty())
{
- throw OptionParseException("Provide namespace and bucket name");
+ throw OptionParseException("'--bucket' is required", m_Options.help());
}
- Session.SetUrl({fmt::format("{}/z$/details$/{}/{}/{}", m_HostName, m_Namespace, m_Bucket, m_ValueKey)});
+ Url = fmt::format("/z$/details$/{}/{}/{}", m_Namespace, m_Bucket, m_ValueKey);
}
else if (!m_Bucket.empty())
{
if (m_Namespace.empty())
{
- throw OptionParseException("Provide namespace name");
+ throw OptionParseException("'--namespace' is required", m_Options.help());
}
- Session.SetUrl({fmt::format("{}/z$/details$/{}/{}", m_HostName, m_Namespace, m_Bucket)});
+ Url = fmt::format("/z$/details$/{}/{}", m_Namespace, m_Bucket);
}
else if (!m_Namespace.empty())
{
- Session.SetUrl({fmt::format("{}/z$/details$/{}", m_HostName, m_Namespace)});
+ Url = fmt::format("/z$/details$/{}", m_Namespace);
}
else
{
- Session.SetUrl({fmt::format("{}/z$/details$", m_HostName)});
- }
- Session.SetParameters(Parameters);
-
- cpr::Response Result = Session.Get();
-
- if (zen::IsHttpSuccessCode(Result.status_code))
- {
- ZEN_CONSOLE("{}", Result.text);
-
- return 0;
+ Url = "/z$/details$";
}
- if (Result.status_code)
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Get(Url, Headers, Parameters))
{
- ZEN_CONSOLE_ERROR("Info failed: {}: {} ({})", Result.status_code, Result.reason, Result.text);
+ ZEN_CONSOLE("{}", Response.ToText());
}
else
{
- ZEN_CONSOLE_ERROR("Info failed: {}", Result.error.message);
+ Response.ThrowError("Info failed");
}
-
- return 1;
}
CacheGenerateCommand::CacheGenerateCommand()
@@ -407,21 +368,21 @@ CacheGenerateCommand::CacheGenerateCommand()
CacheGenerateCommand::~CacheGenerateCommand() = default;
-int
+void
CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_MaxSize == 0 && m_MinSize == 0)
@@ -472,32 +433,109 @@ CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
HttpClient Http(m_HostName);
- auto GeneratePutCacheValueRequest([this, &KeyDistribution, &Generator](std::span<std::uint64_t> BatchSizes, uint64_t RequestIndex) {
- cacherequests::PutCacheValuesRequest Request({.AcceptMagic = kCbPkgMagic, .Namespace = m_Namespace});
- for (std::uint64_t ValueSize : BatchSizes)
- {
- uint64_t KeyBase = KeyDistribution(Generator);
- std::string KeyString = fmt::format("{}-{}-{}", RequestIndex, KeyBase, ValueSize);
- IoHash ValueKey = IoHash::HashBuffer(KeyString.c_str(), KeyString.length());
+ auto GeneratePutCacheValueRequest(
+ [this, &KeyDistribution, &Generator](std::span<std::uint64_t> BatchSizes, uint64_t RequestIndex) -> CbPackage {
+ CbPackage Package;
- Request.Requests.emplace_back(cacherequests::PutCacheValueRequest{.Key = {.Bucket = m_Bucket, .Hash = ValueKey},
- .Body = CompressBlob(CreateRandomBlob(ValueSize))});
- }
- return Request;
- });
+ CbObjectWriter Writer;
+ Writer << "Method"
+ << "PutCacheValues";
+ Writer << "Accept" << kCbPkgMagic;
+
+ Writer.BeginObject("Params");
+ {
+ Writer << "DefaultPolicy" << WriteToString<128>(CachePolicy::Default);
+ Writer << "Namespace" << m_Namespace;
+
+ Writer.BeginArray("Requests");
+
+ for (std::uint64_t ValueSize : BatchSizes)
+ {
+ Writer.BeginObject();
+ {
+ uint64_t KeyBase = KeyDistribution(Generator);
+ std::string KeyString = fmt::format("{}-{}-{}", RequestIndex, KeyBase, ValueSize);
+ IoHash ValueKey = IoHash::HashBuffer(KeyString.c_str(), KeyString.length());
+
+ Writer.BeginObject("Key");
+ {
+ Writer << "Bucket" << m_Bucket;
+ Writer << "Hash" << ValueKey;
+ }
+ Writer.EndObject(); // Key
+
+ CompressedBuffer Payload = CompressBlob(CreateRandomBlob(ValueSize));
+ Writer.AddBinaryAttachment("RawHash", Payload.DecodeRawHash());
+ Package.AddAttachment(CbAttachment(Payload, Payload.DecodeRawHash()));
+ }
+ Writer.EndObject();
+ }
+ Writer.EndArray(); // Requests
+ }
+ Writer.EndObject(); // Params
+
+ Package.SetObject(Writer.Save());
+
+ return Package;
+ });
auto GeneratePutCacheRecordRequest([this, &KeyDistribution, &Generator](std::span<std::uint64_t> BatchSizes, uint64_t RequestIndex) {
- cacherequests::PutCacheRecordsRequest Request({.AcceptMagic = kCbPkgMagic, .Namespace = m_Namespace});
- uint64_t KeyBase = KeyDistribution(Generator);
- std::string RecordKeyString = fmt::format("{}-{}-{}", RequestIndex, KeyBase, BatchSizes.size());
- IoHash RecordKey = IoHash::HashBuffer(RecordKeyString.c_str(), RecordKeyString.length());
+ CbPackage Package;
+
+ CbObjectWriter Writer;
+ Writer << "Method"
+ << "PutCacheRecords";
+ Writer << "Accept" << kCbPkgMagic;
- Request.Requests.emplace_back(cacherequests::PutCacheRecordRequest{.Key = {.Bucket = m_Bucket, .Hash = RecordKey}});
- for (std::uint64_t ValueSize : BatchSizes)
+ Writer.BeginObject("Params");
{
- Request.Requests.back().Values.push_back({.Id = Oid::NewOid(), .Body = CompressBlob(CreateRandomBlob(ValueSize))});
+ Writer << "DefaultPolicy" << WriteToString<128>(CachePolicy::Default);
+ Writer << "Namespace" << m_Namespace;
+
+ Writer.BeginArray("Requests");
+ {
+ Writer.BeginObject();
+ {
+ Writer.BeginObject("Record");
+ {
+ uint64_t KeyBase = KeyDistribution(Generator);
+ std::string RecordKeyString = fmt::format("{}-{}-{}", RequestIndex, KeyBase, BatchSizes.size());
+ IoHash RecordKey = IoHash::HashBuffer(RecordKeyString.c_str(), RecordKeyString.length());
+
+ Writer.BeginObject("Key");
+ {
+ Writer << "Bucket" << m_Bucket;
+ Writer << "Hash" << RecordKey;
+ }
+ Writer.EndObject(); // Key
+
+ Writer.BeginArray("Values");
+ for (std::uint64_t ValueSize : BatchSizes)
+ {
+ Writer.BeginObject();
+ {
+ Writer.AddObjectId("Id", Oid::NewOid());
+
+ CompressedBuffer Payload = CompressBlob(CreateRandomBlob(ValueSize));
+ Writer.AddBinaryAttachment("RawHash", Payload.DecodeRawHash());
+ Package.AddAttachment(CbAttachment(Payload, Payload.DecodeRawHash()));
+ Writer.AddInteger("RawSize", Payload.DecodeRawSize());
+ }
+ Writer.EndObject();
+ }
+ Writer.EndArray(); // Values
+ }
+ Writer.EndObject(); // Record
+ }
+ Writer.EndObject();
+ }
+ Writer.EndArray(); // Requests
}
- return Request;
+ Writer.EndObject(); // Params
+
+ Package.SetObject(Writer.Save());
+
+ return Package;
});
WorkerThreadPool WorkerPool(gsl::narrow<int>(Max((std::thread::hardware_concurrency() / 2u), 2u)));
@@ -514,25 +552,26 @@ CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
std::span<uint64_t> BatchSizes = std::span<uint64_t>(Sizes).subspan(Offset, Min(Max(SizeCount, 1u), Sizes.size() - Offset));
WorkLatch.AddCount(1);
- WorkerPool.ScheduleWork([&, BatchSizes, RequestIndex]() {
- auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
- CbPackage Package;
- if (m_MaxAttachmentCount > 0 && SizeCount > 0)
- {
- auto Request = GeneratePutCacheRecordRequest(BatchSizes, RequestIndex);
- ZEN_ASSERT(Request.Format(Package));
- }
- else
- {
- auto Request = GeneratePutCacheValueRequest(BatchSizes, RequestIndex);
- ZEN_ASSERT(Request.Format(Package));
- }
-
- if (HttpClient::Response Response = Http.Post("/z$/$rpc", Package, HttpClient::Accept(ZenContentType::kCbPackage)); !Response)
- {
- ZEN_CONSOLE("{}", Response.ErrorMessage(fmt::format("{}: ", RequestIndex)));
- }
- });
+ WorkerPool.ScheduleWork(
+ [&, BatchSizes, RequestIndex]() {
+ auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
+ CbPackage Package;
+ if (m_MaxAttachmentCount > 0 && SizeCount > 0)
+ {
+ Package = GeneratePutCacheRecordRequest(BatchSizes, RequestIndex);
+ }
+ else
+ {
+ Package = GeneratePutCacheValueRequest(BatchSizes, RequestIndex);
+ }
+
+ if (HttpClient::Response Response = Http.Post("/z$/$rpc", Package, HttpClient::Accept(ZenContentType::kCbPackage));
+ !Response)
+ {
+ ZEN_CONSOLE("{}", Response.ErrorMessage(fmt::format("{}: ", RequestIndex)));
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
Offset += BatchSizes.size();
RequestIndex++;
}
@@ -542,8 +581,6 @@ CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
{
ZEN_INFO("Creating data, {} requests remaining", WorkLatch.Remaining());
}
-
- return 0;
}
CacheGetCommand::CacheGetCommand()
@@ -570,7 +607,7 @@ CacheGetCommand::CacheGetCommand()
CacheGetCommand::~CacheGetCommand() = default;
-int
+void
CacheGetCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -579,35 +616,35 @@ CacheGetCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_Namespace.empty())
{
- throw zen::OptionParseException("cache-get command requires a namespace");
+ throw OptionParseException("'--namespace' is required", m_Options.help());
}
if (m_Bucket.empty())
{
- throw zen::OptionParseException("cache-get command requires a bucket");
+ throw OptionParseException("'--bucket' is required", m_Options.help());
}
if (m_ValueKey.empty())
{
- throw zen::OptionParseException("cache-get command requires a value key");
+ throw OptionParseException("'--valuekey' is required", m_Options.help());
}
IoHash ValueId;
if (!IoHash::TryParse(m_ValueKey, ValueId))
{
- throw zen::OptionParseException("cache-get --valuekey option requires a valid IoHash string");
+ throw OptionParseException(fmt::format("'--value-key' ('{}') is malformed", m_ValueKey), m_Options.help());
}
IoHash AttachmentHash;
@@ -615,7 +652,7 @@ CacheGetCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!IoHash::TryParse(m_AttachmentHash, AttachmentHash))
{
- throw zen::OptionParseException("cache-get --attachmenthash option requires a valid IoHash string");
+ throw OptionParseException(fmt::format("'--attachmenthash' ('{}') is malformed", m_AttachmentHash), m_Options.help());
}
}
@@ -681,8 +718,6 @@ CacheGetCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
Result.ThrowError("Failed to fetch data"sv);
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/cache_cmd.h b/src/zen/cmds/cache_cmd.h
index b8a319359..4dc05bbdc 100644
--- a/src/zen/cmds/cache_cmd.h
+++ b/src/zen/cmds/cache_cmd.h
@@ -12,7 +12,7 @@ public:
DropCommand();
~DropCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -27,7 +27,7 @@ class CacheInfoCommand : public CacheStoreCommand
public:
CacheInfoCommand();
~CacheInfoCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -44,7 +44,7 @@ class CacheStatsCommand : public CacheStoreCommand
public:
CacheStatsCommand();
~CacheStatsCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -57,7 +57,7 @@ class CacheDetailsCommand : public CacheStoreCommand
public:
CacheDetailsCommand();
~CacheDetailsCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -79,7 +79,7 @@ public:
CacheGenerateCommand();
~CacheGenerateCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -104,7 +104,7 @@ public:
CacheGetCommand();
~CacheGetCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/copy_cmd.cpp b/src/zen/cmds/copy_cmd.cpp
index 4e54f27bb..530661607 100644
--- a/src/zen/cmds/copy_cmd.cpp
+++ b/src/zen/cmds/copy_cmd.cpp
@@ -24,23 +24,23 @@ CopyCommand::CopyCommand()
CopyCommand::~CopyCommand() = default;
-int
+void
CopyCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
- if (!ZenCmdBase::ParseOptions(argc, argv))
+ if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
// Validate arguments
if (m_CopySource.empty())
- throw std::runtime_error("No source specified");
+ throw OptionParseException("'--source' is required", m_Options.help());
if (m_CopyTarget.empty())
- throw std::runtime_error("No target specified");
+ throw OptionParseException("'--target' is required", m_Options.help());
std::filesystem::path FromPath = m_CopySource;
std::filesystem::path ToPath = m_CopyTarget;
@@ -180,9 +180,7 @@ CopyCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (Visitor.FailedFileCount)
{
- ZEN_CONSOLE_ERROR("{} file copy operations FAILED", Visitor.FailedFileCount);
-
- return 1;
+ throw std::runtime_error(fmt::format("{} file copy operations FAILED", Visitor.FailedFileCount));
}
}
else
@@ -194,27 +192,16 @@ CopyCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
zen::CopyFileOptions CopyOptions;
CopyOptions.EnableClone = !m_NoClone;
- try
+ zen::CreateDirectories(ToPath.parent_path());
+ if (zen::CopyFile(FromPath, ToPath, CopyOptions))
{
- zen::CreateDirectories(ToPath.parent_path());
- if (zen::CopyFile(FromPath, ToPath, CopyOptions))
- {
- ZEN_CONSOLE("Copy completed in {}", zen::NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
- }
- else
- {
- throw std::logic_error("CopyFile failed in an unexpected way");
- }
+ ZEN_CONSOLE("Copy completed in {}", zen::NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
}
- catch (const std::exception& Ex)
+ else
{
- ZEN_CONSOLE_ERROR("Failed to copy '{}' to '{}': '{}'", FromPath, ToPath, Ex.what());
-
- return 1;
+ throw std::runtime_error(fmt::format("Failed to copy '{}' to '{}'", FromPath, ToPath));
}
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/copy_cmd.h b/src/zen/cmds/copy_cmd.h
index e9735c159..e1a5dcb82 100644
--- a/src/zen/cmds/copy_cmd.h
+++ b/src/zen/cmds/copy_cmd.h
@@ -15,7 +15,7 @@ public:
~CopyCommand();
virtual cxxopts::Options& Options() override { return m_Options; }
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
diff --git a/src/zen/cmds/dedup_cmd.cpp b/src/zen/cmds/dedup_cmd.cpp
index 033ac87c3..9ef50a97d 100644
--- a/src/zen/cmds/dedup_cmd.cpp
+++ b/src/zen/cmds/dedup_cmd.cpp
@@ -51,14 +51,14 @@ DedupCommand::DedupCommand()
DedupCommand::~DedupCommand() = default;
-int
+void
DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
// Validate arguments
@@ -68,14 +68,12 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!SourceGood)
{
- ZEN_CONSOLE_ERROR("Source directory '{}' does not support deduplication", m_DedupSource);
- return 0;
+ throw std::runtime_error(fmt::format("Source directory '{}' does not support deduplication", m_DedupSource));
}
if (!TargetGood)
{
- ZEN_CONSOLE_ERROR("Target directory '{}' does not support deduplication", m_DedupTarget);
- return 0;
+ throw std::runtime_error(fmt::format("Target directory '{}' does not support deduplication", m_DedupTarget));
}
ZEN_CONSOLE("Performing dedup operation between {} and {}, size threshold {}",
@@ -295,8 +293,6 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
ZEN_CONSOLE("Elapsed: {} Deduped: {}", zen::NiceTimeSpanMs(Timer.GetElapsedTimeMs()), zen::NiceBytes(DupeBytes));
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/dedup_cmd.h b/src/zen/cmds/dedup_cmd.h
index 2721be2b9..5b8387dd2 100644
--- a/src/zen/cmds/dedup_cmd.h
+++ b/src/zen/cmds/dedup_cmd.h
@@ -15,7 +15,7 @@ public:
~DedupCommand();
virtual cxxopts::Options& Options() override { return m_Options; }
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
diff --git a/src/zen/cmds/info_cmd.cpp b/src/zen/cmds/info_cmd.cpp
index 8e0b3d663..49ad022cf 100644
--- a/src/zen/cmds/info_cmd.cpp
+++ b/src/zen/cmds/info_cmd.cpp
@@ -21,21 +21,21 @@ InfoCommand::~InfoCommand()
{
}
-int
+void
InfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -48,8 +48,6 @@ InfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
Result.ThrowError(fmt::format("Failed getting info from {}", m_HostName));
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/info_cmd.h b/src/zen/cmds/info_cmd.h
index 9723a075b..231565bfd 100644
--- a/src/zen/cmds/info_cmd.h
+++ b/src/zen/cmds/info_cmd.h
@@ -12,7 +12,7 @@ public:
InfoCommand();
~InfoCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
// virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
diff --git a/src/zen/cmds/print_cmd.cpp b/src/zen/cmds/print_cmd.cpp
index fd6d92f28..557808ba7 100644
--- a/src/zen/cmds/print_cmd.cpp
+++ b/src/zen/cmds/print_cmd.cpp
@@ -19,7 +19,10 @@ PrintCbObject(CbObject Object, bool AddTypeComment)
{
ExtendableStringBuilder<1024> ObjStr;
CompactBinaryToJson(Object, ObjStr, AddTypeComment);
- ZEN_CONSOLE("{}", ObjStr);
+ ForEachStrTok(ObjStr.ToView(), '\n', [](std::string_view Row) {
+ ZEN_CONSOLE("{}", Row);
+ return true;
+ });
}
static void
@@ -27,7 +30,10 @@ PrintCompactBinary(IoBuffer Data, bool AddTypeComment)
{
ExtendableStringBuilder<1024> StreamString;
CompactBinaryToJson(Data.GetView(), StreamString, AddTypeComment);
- ZEN_CONSOLE("{}", StreamString);
+ ForEachStrTok(StreamString.ToView(), '\n', [](std::string_view Row) {
+ ZEN_CONSOLE("{}", Row);
+ return true;
+ });
}
static CbValidateError
@@ -55,20 +61,20 @@ PrintCommand::PrintCommand()
PrintCommand::~PrintCommand() = default;
-int
+void
PrintCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
// Validate arguments
if (m_Filename.empty())
- throw std::runtime_error("No file specified");
+ throw OptionParseException("'--source' is required", m_Options.help());
FileContents Fc;
@@ -84,9 +90,7 @@ PrintCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (Fc.ErrorCode)
{
- ZEN_CONSOLE_ERROR("Failed to read file '{}': {}", m_Filename, Fc.ErrorCode.message());
-
- return 1;
+ throw std::runtime_error(fmt::format("Failed to read file '{}': {}", m_Filename, Fc.ErrorCode.message()));
}
IoBuffer Data = Fc.Flatten();
@@ -158,11 +162,11 @@ PrintCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (FilteredResult != CbValidateError::None)
{
- ZEN_CONSOLE_ERROR("Object in package message file '{}' does not appear to be compact binary (validation error {:#x}: '{}')",
- m_Filename,
- uint32_t(FilteredResult),
- ToString(FilteredResult));
- return 1;
+ throw std::runtime_error(
+ fmt::format("Object in package message file '{}' does not appear to be compact binary (validation error {:#x}: '{}')",
+ m_Filename,
+ uint32_t(FilteredResult),
+ ToString(FilteredResult)));
}
else
{
@@ -185,18 +189,16 @@ PrintCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (FilteredResult != CbValidateError::None)
{
- ZEN_CONSOLE_ERROR("Data in file '{}' does not appear to be compact binary (validation error {:#x}: '{}')",
- m_Filename,
- uint32_t(FilteredResult),
- ToString(FilteredResult));
- return 1;
+ throw std::runtime_error(fmt::format("Data in file '{}' does not appear to be compact binary (validation error {:#x}: '{}')",
+ m_Filename,
+ uint32_t(FilteredResult),
+ ToString(FilteredResult)));
}
else
{
PrintCompactBinary(Data, m_ShowCbObjectTypeInfo);
}
}
- return 0;
}
//////////////////////////////////////////////////////////////////////////
@@ -218,20 +220,20 @@ PrintPackageCommand::~PrintPackageCommand()
{
}
-int
+void
PrintPackageCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
// Validate arguments
if (m_Filename.empty())
- throw std::runtime_error("No file specified");
+ throw OptionParseException("'--source' is required", m_Options.help());
MakeSafeAbsolutePathÍnPlace(m_Filename);
FileContents Fc = ReadFile(m_Filename);
@@ -250,8 +252,6 @@ PrintPackageCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar
{
ZEN_ERROR("error: malformed package?");
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/print_cmd.h b/src/zen/cmds/print_cmd.h
index 80729901e..6c1529b7c 100644
--- a/src/zen/cmds/print_cmd.h
+++ b/src/zen/cmds/print_cmd.h
@@ -14,7 +14,7 @@ public:
PrintCommand();
~PrintCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
@@ -32,7 +32,7 @@ public:
PrintPackageCommand();
~PrintPackageCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp
index f99e93cd1..6f9719909 100644
--- a/src/zen/cmds/projectstore_cmd.cpp
+++ b/src/zen/cmds/projectstore_cmd.cpp
@@ -4,23 +4,30 @@
#include <zencore/basicfile.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compress.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
+#include <zencore/process.h>
#include <zencore/scopeguard.h>
#include <zencore/stream.h>
#include <zencore/timer.h>
#include <zencore/workthreadpool.h>
+#include <zenhttp/auth/authmgr.h>
#include <zenhttp/formatters.h>
#include <zenhttp/httpclient.h>
+#include <zenhttp/httpclientauth.h>
#include <zenhttp/httpcommon.h>
+#include <zenremotestore/builds/jupiterbuildstorage.h>
+#include <zenremotestore/jupiter/jupiterhost.h>
ZEN_THIRD_PARTY_INCLUDES_START
#include <json11.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
#include <signal.h>
+#include <iostream>
namespace zen {
@@ -28,64 +35,7 @@ namespace {
using namespace std::literals;
- const std::string DefaultJupiterAccessTokenEnvVariableName(
-#if ZEN_PLATFORM_WINDOWS
- "UE-CloudDataCacheAccessToken"sv
-#endif
-#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- "UE_CloudDataCacheAccessToken"sv
-#endif
-
- );
-
- std::string ReadJupiterAccessTokenFromFile(const std::filesystem::path& Path)
- {
- if (!IsFile(Path))
- {
- throw std::runtime_error(fmt::format("the file '{}' does not exist", Path));
- }
- IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
- std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
- std::string JsonError;
- json11::Json TokenInfo = json11::Json::parse(JsonText, JsonError);
- if (!JsonError.empty())
- {
- throw std::runtime_error(fmt::format("failed parsing json file '{}'. Reason: '{}'", Path, JsonError));
- }
- const std::string AuthToken = TokenInfo["Token"].string_value();
- if (AuthToken.empty())
- {
- throw std::runtime_error(fmt::format("the json file '{}' does not contain a value for \"Token\"", Path));
- }
- return AuthToken;
- }
-
- std::filesystem::path FindOidcTokenExePath(std::string_view OidcTokenAuthExecutablePath)
- {
- if (OidcTokenAuthExecutablePath.empty())
- {
- const std::string OidcExecutableName = "OidcToken" ZEN_EXE_SUFFIX_LITERAL;
- std::filesystem::path OidcTokenPath = (GetRunningExecutablePath().parent_path() / OidcExecutableName).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- OidcTokenPath = (std::filesystem::current_path() / OidcExecutableName).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- else
- {
- std::filesystem::path OidcTokenPath = std::filesystem::absolute(StringToPath(OidcTokenAuthExecutablePath)).make_preferred();
- if (IsFile(OidcTokenPath))
- {
- return OidcTokenPath;
- }
- }
- return {};
- };
+#define ZEN_CLOUD_STORAGE "Cloud Storage"
void WriteAuthOptions(CbObjectWriter& Writer,
std::string_view JupiterOpenIdProvider,
@@ -104,7 +54,7 @@ namespace {
}
if (!JupiterAccessTokenPath.empty())
{
- std::string ResolvedCloudAccessToken = ReadJupiterAccessTokenFromFile(JupiterAccessTokenPath);
+ std::string ResolvedCloudAccessToken = ReadAccessTokenFromJsonFile(JupiterAccessTokenPath);
if (!ResolvedCloudAccessToken.empty())
{
Writer.AddString("access-token"sv, ResolvedCloudAccessToken);
@@ -148,16 +98,6 @@ namespace {
}
}
- class AsyncJobError : public std::runtime_error
- {
- public:
- using _Mybase = runtime_error;
-
- AsyncJobError(const std::string& Message, int ReturnCode) : _Mybase(Message), m_ReturnCode(ReturnCode) {}
-
- const int m_ReturnCode = 0;
- };
-
void ExecuteAsyncOperation(HttpClient& Http, std::string_view Url, IoBuffer&& Payload, bool PlainProgress)
{
signal(SIGINT, SignalCallbackHandler);
@@ -264,11 +204,11 @@ namespace {
int ReturnCode = StatusObject["ReturnCode"].AsInt32(-1);
if (!AbortReason.empty())
{
- throw AsyncJobError(std::string(AbortReason), ReturnCode);
+ throw ErrorWithReturnCode(std::string(AbortReason), ReturnCode);
}
else
{
- throw AsyncJobError("Aborted", ReturnCode);
+ throw ErrorWithReturnCode("Aborted", ReturnCode);
}
break;
}
@@ -563,21 +503,21 @@ DropProjectCommand::~DropProjectCommand()
{
}
-int
+void
DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -585,7 +525,7 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error(fmt::format("Can't find project '{}'", m_ProjectName));
}
if (m_OplogName.empty())
@@ -604,7 +544,6 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
else
{
Result.ThrowError("delete project failed"sv);
- return 1;
}
}
}
@@ -613,7 +552,7 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- return 1;
+ throw std::runtime_error(fmt::format("Can't find oplog in project '{}'", m_OplogName, m_ProjectName));
}
if (m_DryRun)
{
@@ -632,12 +571,9 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
else
{
Result.ThrowError("delete oplog failed"sv);
- return 1;
}
}
}
-
- return 0;
}
///////////////////////////////////////
@@ -656,26 +592,26 @@ ProjectInfoCommand::~ProjectInfoCommand()
{
}
-int
+void
ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (!m_OplogName.empty() && m_ProjectName.empty())
{
- throw OptionParseException("an oplog can't be specified without also specifying a project");
+ throw OptionParseException("'--project' is required", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -691,7 +627,7 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Unable to resolve project");
}
Url = fmt::format("/prj/{}", m_ProjectName);
@@ -702,13 +638,13 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Unable to resolve project");
}
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- return 1;
+ throw std::runtime_error("Unable to resolve oplog");
}
Url = fmt::format("/prj/{}/oplog/{}", m_ProjectName, m_OplogName);
@@ -718,12 +654,10 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (HttpClient::Response Result = Http.Get(Url, HttpClient::Accept(ZenContentType::kJSON)))
{
ZEN_CONSOLE("{}", Result.ToText());
- return 0;
}
else
{
Result.ThrowError("failed to fetch info"sv);
- return 1;
}
}
@@ -744,7 +678,7 @@ CreateProjectCommand::CreateProjectCommand()
CreateProjectCommand::~CreateProjectCommand() = default;
-int
+void
CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -753,19 +687,19 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_ProjectId.empty())
{
- throw OptionParseException("Project name must be given");
+ throw OptionParseException("'--project' is required", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -776,8 +710,7 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
{
if (HttpClient::Response Result = Http.Get(Url, HttpClient::Accept(ZenContentType::kJSON)))
{
- ZEN_CONSOLE_ERROR("Project already exists.\n{}", Result.ToText());
- return 1;
+ throw std::runtime_error(fmt::format("Project already exists.\n{}", Result.ToText()));
}
}
@@ -792,12 +725,10 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
: Http.Post(Url, Payload, HttpClient::Accept(ZenContentType::kText)))
{
ZEN_CONSOLE("{}", Result);
- return 0;
}
else
{
Result.ThrowError("failed to create project"sv);
- return 1;
}
}
@@ -816,7 +747,7 @@ CreateOplogCommand::CreateOplogCommand()
CreateOplogCommand::~CreateOplogCommand() = default;
-int
+void
CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -825,31 +756,31 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_ProjectId.empty())
{
- throw OptionParseException("project name must be specified");
+ throw OptionParseException("'--project' is required", m_Options.help());
}
HttpClient Http(m_HostName);
m_ProjectId = ResolveProject(Http, m_ProjectId);
if (m_ProjectId.empty())
{
- return 1;
+ throw std::runtime_error("Project can not be found");
}
if (m_OplogId.empty())
{
- throw OptionParseException("oplog name must be specified");
+ throw OptionParseException("'--oplog' is required", m_Options.help());
}
std::string Url = fmt::format("/prj/{}/oplog/{}", m_ProjectId, m_OplogId);
@@ -857,8 +788,7 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
{
if (HttpClient::Response Result = Http.Get(Url, HttpClient::Accept(ZenContentType::kJSON)))
{
- ZEN_CONSOLE("Oplog already exists.\n{}", Result.ToText());
- return 1;
+ throw std::runtime_error(fmt::format("Oplog already exists.\n{}", Result.ToText()));
}
}
@@ -872,12 +802,10 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
: Http.Post(Url, OplogPayload, HttpClient::Accept(ZenContentType::kText)))
{
ZEN_CONSOLE("{}", Result);
- return 0;
}
else
{
Result.ThrowError("failed to create oplog"sv);
- return 1;
}
}
@@ -892,6 +820,12 @@ ExportOplogCommand::ExportOplogCommand()
m_Options.add_option("", "", "maxblocksize", "Max size for bundled attachments", cxxopts::value(m_MaxBlockSize), "<blocksize>");
m_Options.add_option("",
"",
+ "maxchunksperblock",
+ "Max number of chunks in one block",
+ cxxopts::value(m_MaxChunksPerBlock),
+ "<maxchunksperblock>");
+ m_Options.add_option("",
+ "",
"maxchunkembedsize",
"Max size for attachment to be bundled",
cxxopts::value(m_MaxChunkEmbedSize),
@@ -931,7 +865,7 @@ ExportOplogCommand::ExportOplogCommand()
"",
"access-token-env",
"Name of environment variable that holds the cloud/builds Storage access token",
- cxxopts::value(m_JupiterAccessTokenEnv)->default_value(DefaultJupiterAccessTokenEnvVariableName),
+ cxxopts::value(m_JupiterAccessTokenEnv)->default_value(std::string(GetDefaultAccessTokenEnvVariableName())),
"<envvariable>");
m_Options.add_option("",
"",
@@ -1013,7 +947,7 @@ ExportOplogCommand::~ExportOplogCommand()
{
}
-int
+void
ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
using namespace std::literals;
@@ -1022,32 +956,32 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_ProjectName.empty())
{
- throw OptionParseException("project name must be specified");
+ throw OptionParseException("'--project' is required", m_Options.help());
}
HttpClient Http(m_HostName);
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Project can not be found");
}
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- return 1;
+ throw std::runtime_error("Oplog can not be found");
}
size_t TargetCount = 0;
@@ -1059,19 +993,23 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
{
if (TargetCount == 0)
{
- throw OptionParseException("an export target must be specified");
+ throw OptionParseException("'--cloud', '--builds', '--zen' or '--file' is required", m_Options.help());
}
else
{
- throw OptionParseException("a single export target must be specified");
+ throw OptionParseException("'--cloud', '--builds', '--zen' or '--file' are conflicting", m_Options.help());
}
}
if (!m_CloudUrl.empty())
{
- if (m_JupiterNamespace.empty() || m_JupiterBucket.empty())
+ if (m_JupiterNamespace.empty())
+ {
+ throw OptionParseException("'--namespace' is required", m_Options.help());
+ }
+ if (m_JupiterBucket.empty())
{
- throw OptionParseException("Options for cloud target are missing");
+ throw OptionParseException("'--bucket' is required", m_Options.help());
}
if (m_CloudKey.empty())
{
@@ -1086,19 +1024,21 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
{
if (m_JupiterNamespace.empty())
{
- throw OptionParseException("Namespace for builds target it missing");
+ throw OptionParseException("'--namespace' is required", m_Options.help());
}
if (m_JupiterNamespace.empty() || m_JupiterBucket.empty())
{
- throw OptionParseException("Bucket for builds target it missing");
+ throw OptionParseException("'--bucket' is required", m_Options.help());
}
if (m_BuildsMetadataPath.empty() && m_BuildsMetadata.empty())
{
- throw OptionParseException("Options for builds target metadata are missing");
+ throw OptionParseException("'--metadata' or --'metadata-path' is required", m_Options.help());
}
if (!m_BuildsMetadataPath.empty() && !m_BuildsMetadata.empty())
{
- throw OptionParseException("Conflicting options for builds target metadata");
+ throw OptionParseException(
+ fmt::format("'--metadata' ('{}') conflicts with --'metadata-path' ('{}')", m_BuildsMetadata, m_BuildsMetadataPath),
+ m_Options.help());
}
if (m_BuildsId.empty())
{
@@ -1140,7 +1080,6 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (!Result)
{
Result.ThrowError("failed deleting existing zen remote oplog"sv);
- return 1;
}
CreateOplog = true;
}
@@ -1152,7 +1091,6 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
else
{
Result.ThrowError("failed checking zen remote oplog"sv);
- return 1;
}
if (CreateOplog)
@@ -1161,7 +1099,6 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (HttpClient::Response Result = TargetHttp.Post(Url); !Result)
{
Result.ThrowError("failed creating zen remote oplog"sv);
- return 1;
}
}
}
@@ -1185,6 +1122,10 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
{
Writer.AddInteger("maxblocksize"sv, m_MaxBlockSize);
}
+ if (m_MaxChunksPerBlock != 0)
+ {
+ Writer.AddInteger("maxchunksperblock"sv, m_MaxChunksPerBlock);
+ }
if (m_MaxChunkEmbedSize != 0)
{
Writer.AddInteger("maxchunkembedsize"sv, m_MaxChunkEmbedSize);
@@ -1340,36 +1281,24 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
ZEN_CONSOLE("Saving oplog '{}/{}' from '{}' to {}", m_ProjectName, m_OplogName, m_HostName, TargetDescription);
- try
+ if (m_Async)
{
- if (m_Async)
+ if (HttpClient::Response Result = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName),
+ std::move(Payload),
+ HttpClient::Accept(ZenContentType::kJSON));
+ Result)
{
- if (HttpClient::Response Result = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName),
- std::move(Payload),
- HttpClient::Accept(ZenContentType::kJSON));
- Result)
- {
- ZEN_CONSOLE("{}", Result.ToText());
- }
- else
- {
- Result.ThrowError("failed requesting loading oplog export"sv);
- }
+ ZEN_CONSOLE("{}", Result.ToText());
}
else
{
- ExecuteAsyncOperation(Http,
- fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName),
- std::move(Payload),
- m_PlainProgress);
+ Result.ThrowError("failed requesting loading oplog export"sv);
}
}
- catch (const AsyncJobError& Ex)
+ else
{
- ZEN_CONSOLE_ERROR("Oplog export failed: '{}'", Ex.what());
- return Ex.m_ReturnCode;
+ ExecuteAsyncOperation(Http, fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName), std::move(Payload), m_PlainProgress);
}
- return 0;
}
////////////////////////////
@@ -1386,13 +1315,6 @@ ImportOplogCommand::ImportOplogCommand()
"Absolute path to oplog lifetime marker file if we create the oplog",
cxxopts::value(m_GcPath),
"<path>");
- m_Options.add_option("", "", "maxblocksize", "Max size for bundled attachments", cxxopts::value(m_MaxBlockSize), "<blocksize>");
- m_Options.add_option("",
- "",
- "maxchunkembedsize",
- "Max size for attachment to be bundled",
- cxxopts::value(m_MaxChunkEmbedSize),
- "<chunksize>");
m_Options.add_option("", "f", "force", "Force import of all attachments", cxxopts::value(m_Force), "<force>");
m_Options.add_option("", "a", "async", "Trigger import but don't wait for completion", cxxopts::value(m_Async), "<async>");
m_Options.add_option("", "", "clean", "Delete existing target oplog", cxxopts::value(m_Clean), "<clean>");
@@ -1417,7 +1339,7 @@ ImportOplogCommand::ImportOplogCommand()
"",
"access-token-env",
"Name of environment variable that holds the cloud/builds Storage access token",
- cxxopts::value(m_JupiterAccessTokenEnv)->default_value(DefaultJupiterAccessTokenEnvVariableName),
+ cxxopts::value(m_JupiterAccessTokenEnv)->default_value(std::string(GetDefaultAccessTokenEnvVariableName())),
"<envvariable>");
m_Options.add_option("",
"",
@@ -1461,7 +1383,7 @@ ImportOplogCommand::~ImportOplogCommand()
{
}
-int
+void
ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
using namespace std::literals;
@@ -1470,31 +1392,31 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_ProjectName.empty())
{
- throw OptionParseException("Project name must be given");
+ throw OptionParseException("'--project' is required", m_Options.help());
}
if (m_OplogName.empty())
{
- throw OptionParseException("Oplog name must be given");
+ throw OptionParseException("'--oplog' is required", m_Options.help());
}
HttpClient Http(m_HostName);
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Project can not be found");
}
size_t TargetCount = 0;
@@ -1502,20 +1424,24 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
TargetCount += m_BuildsUrl.empty() ? 0 : 1;
TargetCount += m_ZenUrl.empty() ? 0 : 1;
TargetCount += m_FileDirectoryPath.empty() ? 0 : 1;
- if (TargetCount != 1)
+ if (TargetCount == 0)
+ {
+ throw OptionParseException("'--cloud', '--builds', '--zen' or '--file' is required", m_Options.help());
+ }
+ else if (TargetCount > 1)
{
- throw OptionParseException("Provide one source only");
+ throw OptionParseException("'--cloud', '--builds', '--zen' or '--file' are conflicting", m_Options.help());
}
if (!m_CloudUrl.empty())
{
if (m_JupiterNamespace.empty())
{
- throw OptionParseException("Namespace option for cloud source is missing");
+ throw OptionParseException("--'namespace' is required", m_Options.help());
}
if (m_JupiterBucket.empty())
{
- throw OptionParseException("Bucket option for cloud source is missing");
+ throw OptionParseException("--'bucket' is required", m_Options.help());
}
if (m_CloudKey.empty())
{
@@ -1530,15 +1456,15 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
{
if (m_JupiterNamespace.empty())
{
- throw OptionParseException("Namespace option for builds source is missing");
+ throw OptionParseException("'--namespace' is required", m_Options.help());
}
if (m_JupiterBucket.empty())
{
- throw OptionParseException("Bucket option for builds source is missing");
+ throw OptionParseException("'--bucket' is required", m_Options.help());
}
if (m_BuildsId.empty())
{
- throw OptionParseException("Build id option for builds source is missing");
+ throw OptionParseException("'--build-id' is required", m_Options.help());
}
}
@@ -1576,7 +1502,6 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
else if (!IsHttpSuccessCode(Result.StatusCode))
{
Result.ThrowError("failed checking oplog"sv);
- return 1;
}
if (CreateOplog)
@@ -1590,7 +1515,6 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (HttpClient::Response Result = Http.Post(Url, OplogPayload); !Result)
{
Result.ThrowError("failed creating oplog"sv);
- return 1;
}
}
@@ -1687,36 +1611,24 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
ZEN_CONSOLE("Loading oplog '{}/{}' from '{}' to {}", m_ProjectName, m_OplogName, SourceDescription, m_HostName);
- try
+ if (m_Async)
{
- if (m_Async)
+ if (HttpClient::Response Result = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName),
+ std::move(Payload),
+ HttpClient::Accept(ZenContentType::kJSON));
+ Result)
{
- if (HttpClient::Response Result = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName),
- std::move(Payload),
- HttpClient::Accept(ZenContentType::kJSON));
- Result)
- {
- ZEN_CONSOLE("{}", Result.ToText());
- }
- else
- {
- Result.ThrowError("failed requesting loading oplog import"sv);
- }
+ ZEN_CONSOLE("{}", Result.ToText());
}
else
{
- ExecuteAsyncOperation(Http,
- fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName),
- std::move(Payload),
- m_PlainProgress);
+ Result.ThrowError("failed requesting loading oplog import"sv);
}
}
- catch (const AsyncJobError& Ex)
+ else
{
- ZEN_CONSOLE_ERROR("Oplog export failed: '{}'", Ex.what());
- return Ex.m_ReturnCode;
+ ExecuteAsyncOperation(Http, fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName), std::move(Payload), m_PlainProgress);
}
- return 0;
}
////////////////////////////
@@ -1735,7 +1647,7 @@ SnapshotOplogCommand::~SnapshotOplogCommand()
{
}
-int
+void
SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
using namespace std::literals;
@@ -1744,34 +1656,33 @@ SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
if (m_ProjectName.empty())
{
- throw OptionParseException("Project name must be given");
- return 1;
+ throw OptionParseException("'--project' is required", m_Options.help());
}
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Project can not be found");
}
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- return 1;
+ throw std::runtime_error("Oplog can not be found");
}
IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) { Writer.AddString("method"sv, "snapshot"sv); });
@@ -1781,12 +1692,10 @@ SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", m_ProjectName, m_OplogName), Payload, HttpClient::Accept(ZenContentType::kJSON)))
{
ZEN_CONSOLE("{}", Result);
- return 0;
}
else
{
Result.ThrowError("failed to create project"sv);
- return 1;
}
}
@@ -1802,33 +1711,31 @@ ProjectStatsCommand::~ProjectStatsCommand()
{
}
-int
+void
ProjectStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
if (HttpClient::Response Result = Http.Get("/stats/prj", HttpClient::Accept(ZenContentType::kJSON)))
{
ZEN_CONSOLE("{}", Result.ToText());
- return 0;
}
else
{
Result.ThrowError("failed to get project stats"sv);
- return 1;
}
}
@@ -1856,21 +1763,21 @@ ProjectOpDetailsCommand::~ProjectOpDetailsCommand()
{
}
-int
+void
ProjectOpDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -1878,13 +1785,13 @@ ProjectOpDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char*
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Project can not be found");
}
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- return 1;
+ throw std::runtime_error("Oplog can not be found");
}
ExtendableStringBuilder<128> Url;
@@ -1914,12 +1821,10 @@ ProjectOpDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char*
{"csv", m_CSV ? "true" : "false"}}))
{
ZEN_CONSOLE("{}", Result.ToText());
- return 0;
}
else
{
Result.ThrowError("failed to get project details"sv);
- return 1;
}
}
@@ -1973,21 +1878,21 @@ OplogMirrorCommand::~OplogMirrorCommand()
{
}
-int
+void
OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -1995,18 +1900,18 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Project can not be found");
}
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- return 1;
+ throw std::runtime_error("Oplog can not be found");
}
if (m_MirrorRootPath.empty())
{
- throw OptionParseException("a target path must be specified");
+ throw OptionParseException("'--target' is required", m_Options.help());
}
Oid ChunkIdFilter = Oid::Zero;
@@ -2015,7 +1920,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
ChunkIdFilter = Oid::TryFromHexString(m_ChunkIdFilter);
if (ChunkIdFilter == Oid::Zero)
{
- throw OptionParseException("chunkid must be an Oid hex string");
+ throw OptionParseException(fmt::format("'--chunk' ('{}') is malformed", m_ChunkIdFilter), m_Options.help());
}
}
@@ -2128,7 +2033,8 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
ZEN_CONSOLE_ERROR("Failed writing file to '{}'. Reason: '{}'", TargetPath, Ex.what());
}
}
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
}
};
@@ -2137,6 +2043,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (HttpClient::Response Response =
Http.Get(fmt::format("/prj/{}/oplog/{}/entries"sv, m_ProjectName, m_OplogName), HttpClient::KeyValueMap(), Parameters))
{
+ ZEN_CONSOLE("Fetched oplog in {}", NiceTimeSpanMs(uint64_t(Response.ElapsedSeconds * 1000.0)));
if (CbObject ResponseObject = Response.AsObject())
{
std::unique_ptr<ProgressBar> EmitProgressBar;
@@ -2205,25 +2112,20 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (AbortFlag)
{
- // Error has already been reported by async code
- return 1;
+ throw std::runtime_error("Failed top mirror oplog");
}
}
else
{
- ZEN_CONSOLE_ERROR("Unknown format response to oplog entries request");
+ throw std::runtime_error("Unknown format response to oplog entries request");
}
}
else
{
Response.ThrowError("oplog entries fetch failed");
-
- return 1;
}
ZEN_CONSOLE("mirrored {} files from {} oplog entries successfully", FileCount.load(), OplogEntryCount);
-
- return 0;
}
////////////////////////////
@@ -2243,21 +2145,21 @@ OplogValidateCommand::~OplogValidateCommand()
{
}
-int
+void
OplogValidateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
HttpClient Http(m_HostName);
@@ -2265,13 +2167,13 @@ OplogValidateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
m_ProjectName = ResolveProject(Http, m_ProjectName);
if (m_ProjectName.empty())
{
- return 1;
+ throw std::runtime_error("Project can not be found");
}
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- return 1;
+ throw std::runtime_error("Oplog can not be found");
}
std::string Url = fmt::format("/prj/{}/oplog/{}/validate", m_ProjectName, m_OplogName);
@@ -2279,15 +2181,340 @@ OplogValidateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a
if (HttpClient::Response Result = Http.Post(Url, HttpClient::Accept(ZenContentType::kJSON)))
{
ZEN_CONSOLE("{}", Result.ToText());
- return 0;
}
else
{
Result.ThrowError("failed to get validate project oplog"sv);
- return 1;
+ }
+}
+
+////////////////////////////
+
+OplogDownloadCommand::OplogDownloadCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+
+ m_Options.add_option("", "", "system-dir", "Specify system root", cxxopts::value(m_SystemRootDir), "<systemdir>");
+
+ m_Options.add_option("output", "", "quiet", "Suppress non-essential output", cxxopts::value(m_Quiet), "<quiet>");
+ m_Options.add_option("", "y", "yes", "Don't query for confirmation", cxxopts::value(m_Yes), "<yes>");
+
+ auto AddCloudOptions = [this](cxxopts::Options& Ops) {
+ m_AuthOptions.AddOptions(Ops);
+
+ Ops.add_option("cloud build", "", "override-host", "Cloud Builds URL", cxxopts::value(m_OverrideHost), "<override-host>");
+ Ops.add_option("cloud build", "", "cloud-url", "Cloud Artifact URL", cxxopts::value(m_Url), "<cloud-url>");
+ Ops.add_option("cloud build", "", "host", "Cloud Builds host", cxxopts::value(m_Host), "<host>");
+ Ops.add_option("cloud build",
+ "",
+ "assume-http2",
+ "Assume that the builds endpoint is a HTTP/2 endpoint skipping HTTP/1.1 upgrade handshake",
+ cxxopts::value(m_AssumeHttp2),
+ "<assumehttp2>");
+
+ Ops.add_option("cloud build", "", "namespace", "Builds Storage namespace", cxxopts::value(m_Namespace), "<namespace>");
+ Ops.add_option("cloud build", "", "bucket", "Builds Storage bucket", cxxopts::value(m_Bucket), "<bucket>");
+ };
+
+ AddCloudOptions(m_Options);
+
+ m_Options.add_option("", "", "build-id", "Build Id", cxxopts::value(m_BuildId), "<id>");
+ m_Options.add_option("",
+ "",
+ "output-path",
+ "Path to oplog output, extension .json or .cb (compact binary). Default is output to console",
+ cxxopts::value(m_OutputPath),
+ "<path>");
+
+ m_Options.parse_positional({"cloud-url", "output-path"});
+ m_Options.positional_help("[<cloud-url> <output-path>]");
+}
+
+OplogDownloadCommand::~OplogDownloadCommand()
+{
+}
+
+void
+OplogDownloadCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ if (!ParseOptions(argc, argv))
+ {
+ return;
}
- return 0;
+ if (!m_Quiet)
+ {
+ LogExecutableVersionAndPid();
+ }
+
+ auto ParseSystemOptions = [&]() {
+ if (m_SystemRootDir.empty())
+ {
+ m_SystemRootDir = PickDefaultSystemRootDirectory();
+ }
+ MakeSafeAbsolutePathÍnPlace(m_SystemRootDir);
+ };
+ ParseSystemOptions();
+
+ auto ParseStorageOptions = [&](bool RequireNamespace, bool RequireBucket) {
+ if (!m_Url.empty())
+ {
+ if (!m_Host.empty())
+ {
+ throw OptionParseException(fmt::format("'--host' ('{}') conflicts with '--url' ('{}')", m_Host, m_Url), m_Options.help());
+ }
+ if (!m_Bucket.empty())
+ {
+ throw OptionParseException(fmt::format("'--bucket' ('{}') conflicts with '--url' ('{}')", m_Bucket, m_Url),
+ m_Options.help());
+ }
+ if (!m_BuildId.empty())
+ {
+ throw OptionParseException(fmt::format("'--buildid' ('{}') conflicts with '--url' ('{}')", m_BuildId, m_Url),
+ m_Options.help());
+ }
+ if (!ParseBuildStorageUrl(m_Url, m_Host, m_Namespace, m_Bucket, m_BuildId))
+ {
+ throw OptionParseException("'--url' ('{}') is malformed, it does not match the Cloud Artifact URL format",
+ m_Options.help());
+ }
+ }
+
+ if (!m_OverrideHost.empty() || !m_Host.empty())
+ {
+ if (RequireNamespace && m_Namespace.empty())
+ {
+ throw OptionParseException("'--namespace' is required", m_Options.help());
+ }
+ if (RequireBucket && m_Bucket.empty())
+ {
+ throw OptionParseException("'--bucket' is required", m_Options.help());
+ }
+ }
+
+ if (m_OverrideHost.empty() && m_Host.empty())
+ {
+ throw OptionParseException("'--host' or '--overridehost' is required", m_Options.help());
+ }
+ };
+
+ std::unique_ptr<AuthMgr> Auth;
+ HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient",
+ .AssumeHttp2 = m_AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 2};
+
+ ParseStorageOptions(/*RequireNamespace*/ true, /*RequireBucket*/ true);
+
+ m_AuthOptions.ParseOptions(m_Options,
+ m_SystemRootDir,
+ ClientSettings,
+ m_OverrideHost.empty() ? m_Host : m_OverrideHost,
+ Auth,
+ m_Quiet,
+ /*Hidden*/ false);
+
+ std::string BuildStorageName = ZEN_CLOUD_STORAGE;
+
+ std::string CloudHost;
+
+ auto TestHostEndpoint = [](std::string_view BaseUrl, const bool AssumeHttp2) -> std::pair<bool, std::string> {
+ HttpClientSettings TestClientSettings{.LogCategory = "httpbuildsclient",
+ .ConnectTimeout = std::chrono::milliseconds{1000},
+ .Timeout = std::chrono::milliseconds{2000},
+ .AssumeHttp2 = AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0};
+
+ HttpClient TestHttpClient(BaseUrl, TestClientSettings);
+ HttpClient::Response TestResponse = TestHttpClient.Get("/health/live");
+ if (TestResponse.IsSuccess())
+ {
+ return {true, ""};
+ }
+ return {false, TestResponse.ErrorMessage("")};
+ };
+
+ if (m_OverrideHost.empty())
+ {
+ JupiterServerDiscovery Response = DiscoverJupiterEndpoints(m_Host, ClientSettings);
+
+ if (Response.ServerEndPoints.empty())
+ {
+ throw std::runtime_error(fmt::format("Failed to find any builds hosts at {}", m_Host));
+ }
+ for (const JupiterServerDiscovery::EndPoint& ServerEndpoint : Response.ServerEndPoints)
+ {
+ if (!ServerEndpoint.BaseUrl.empty())
+ {
+ if (JupiterEndpointTestResult TestResult = TestJupiterEndpoint(ServerEndpoint.BaseUrl, ServerEndpoint.AssumeHttp2);
+ TestResult.Success)
+ {
+ CloudHost = ServerEndpoint.BaseUrl;
+ m_AssumeHttp2 = ServerEndpoint.AssumeHttp2;
+ BuildStorageName = ServerEndpoint.Name;
+ break;
+ }
+ else
+ {
+ ZEN_DEBUG("Unable to reach host {}. Reason: {}", ServerEndpoint.BaseUrl, TestResult.FailureReason);
+ }
+ }
+ }
+ if (CloudHost.empty())
+ {
+ throw std::runtime_error(
+ fmt::format("Failed to find any usable builds hosts out of {} using {}", Response.ServerEndPoints.size(), m_Host));
+ }
+ }
+ else if (JupiterEndpointTestResult TestResult = TestJupiterEndpoint(m_OverrideHost, m_AssumeHttp2); TestResult.Success)
+ {
+ CloudHost = m_OverrideHost;
+ }
+ else
+ {
+ throw std::runtime_error(fmt::format("Host {} could not be reached. Reason: {}", m_OverrideHost, TestResult.FailureReason));
+ }
+
+ Oid BuildId = Oid::TryFromHexString(m_BuildId);
+ if (BuildId == Oid::Zero)
+ {
+ throw OptionParseException("'--build-id' is malformed", m_Options.help());
+ }
+
+ BuildStorage::Statistics StorageStats;
+ HttpClient BuildStorageHttp(CloudHost, ClientSettings);
+
+ if (!m_Quiet)
+ {
+ std::string StorageDescription = fmt::format("Cloud {}{}. SessionId: '{}'. Namespace '{}', Bucket '{}'",
+ BuildStorageName.empty() ? "" : fmt::format("{}, ", BuildStorageName),
+ CloudHost,
+ BuildStorageHttp.GetSessionId(),
+ m_Namespace,
+ m_Bucket);
+
+ ZEN_CONSOLE("Remote: {}", StorageDescription);
+ }
+
+ std::filesystem::path StorageTempPath = std::filesystem::temp_directory_path() / ("zen_" + Oid::NewOid().ToString());
+
+ std::unique_ptr<BuildStorage> BuildStorage =
+ CreateJupiterBuildStorage(Log(), BuildStorageHttp, StorageStats, m_Namespace, m_Bucket, m_AllowRedirect, StorageTempPath);
+
+ Stopwatch Timer;
+ CbObject BuildObject = BuildStorage->GetBuild(BuildId);
+ if (!m_Quiet)
+ {
+ ZEN_CONSOLE("Fetched {}/{}/{}/{} in {}", m_Url, m_Namespace, m_Bucket, BuildId, NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+ }
+
+ Timer.Reset();
+
+ CbObjectView PartsObject = BuildObject["parts"sv].AsObjectView();
+ if (!PartsObject)
+ {
+ throw std::runtime_error(
+ fmt::format("The build {}/{}/{}/{} payload does not contain a 'parts' object"sv, m_Url, m_Namespace, m_Bucket, m_BuildId));
+ }
+
+ static const std::string_view OplogContainerPartName = "oplogcontainer"sv;
+
+ const Oid OplogBuildPartId = PartsObject[OplogContainerPartName].AsObjectId();
+ if (OplogBuildPartId == Oid::Zero)
+ {
+ throw std::runtime_error(fmt::format("The build {}/{}/{}/{} payload 'parts' object does not contain a '{}' entry"sv,
+ m_Url,
+ m_Namespace,
+ m_Bucket,
+ m_BuildId,
+ OplogContainerPartName));
+ }
+
+ CbObject ContainerObject = BuildStorage->GetBuildPart(BuildId, OplogBuildPartId);
+
+ MemoryView OpsSection = ContainerObject["ops"sv].AsBinaryView();
+ IoBuffer OpsBuffer(IoBuffer::Wrap, OpsSection.GetData(), OpsSection.GetSize());
+ IoBuffer SectionPayload = CompressedBuffer::FromCompressedNoValidate(std::move(OpsBuffer)).Decompress().AsIoBuffer();
+
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject SectionObject = ValidateAndReadCompactBinaryObject(std::move(SectionPayload), ValidateResult);
+ ValidateResult == CbValidateError::None && ContainerObject)
+ {
+ if (!m_Quiet)
+ {
+ ZEN_CONSOLE("Decompressed and validated oplog payload {} -> {} in {}",
+ NiceBytes(OpsSection.GetSize()),
+ NiceBytes(SectionObject.GetSize()),
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+ }
+
+ if (m_OutputPath.empty())
+ {
+ if (!m_Yes)
+ {
+ if (OpsSection.GetSize() > 8u * 1024u * 1024u)
+ {
+ while (!m_Yes)
+ {
+ const std::string Prompt = fmt::format("Do you want to output an oplog of size {} to console? (yes/no) ",
+ NiceBytes(SectionObject.GetSize()));
+ printf("%s", Prompt.c_str());
+ std::string Reponse;
+ std::getline(std::cin, Reponse);
+ Reponse = ToLower(Reponse);
+ if (Reponse == "y" || Reponse == "yes")
+ {
+ m_Yes = true;
+ }
+ else if (Reponse == "n" || Reponse == "no")
+ {
+ return;
+ }
+ }
+ }
+ }
+ ExtendableStringBuilder<1024> SB;
+ SectionObject.ToJson(SB);
+ ForEachStrTok(SB.ToView(), '\n', [](std::string_view Row) {
+ ZEN_CONSOLE("{}", Row);
+ return true;
+ });
+ }
+ else
+ {
+ Timer.Reset();
+ const std::string Extension = ToLower(m_OutputPath.extension().string());
+ if (Extension == ".cb" || Extension == ".cbo")
+ {
+ WriteFile(m_OutputPath, IoBuffer(IoBuffer::Wrap, SectionObject.GetView().GetData(), SectionObject.GetSize()));
+ }
+ else if (Extension == ".json")
+ {
+ ExtendableStringBuilder<1024> SB;
+ SectionObject.ToJson(SB);
+ WriteFile(m_OutputPath, IoBuffer(IoBuffer::Wrap, SB.Data(), SB.Size()));
+ }
+ else
+ {
+ throw std::runtime_error(fmt::format("Unsupported output extension type '{}'", Extension));
+ }
+ if (!m_Quiet)
+ {
+ ZEN_CONSOLE("Wrote {} to '{}' in {}",
+ NiceBytes(FileSizeFromPath(m_OutputPath)),
+ m_OutputPath,
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+ }
+ }
+ }
+ else
+ {
+ throw std::runtime_error(
+ fmt::format("Failed to parse oplog container: '{}' ('{}')", "Section has unexpected data type", ToString(ValidateResult)));
+ }
}
} // namespace zen
diff --git a/src/zen/cmds/projectstore_cmd.h b/src/zen/cmds/projectstore_cmd.h
index 0d24d8529..ae8233f51 100644
--- a/src/zen/cmds/projectstore_cmd.h
+++ b/src/zen/cmds/projectstore_cmd.h
@@ -4,6 +4,8 @@
#include "../zen.h"
+#include "../authutils.h"
+
namespace zen {
class ProjectStoreCommand : public ZenCmdBase
@@ -17,7 +19,7 @@ public:
DropProjectCommand();
~DropProjectCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -33,7 +35,7 @@ class ProjectInfoCommand : public ProjectStoreCommand
public:
ProjectInfoCommand();
~ProjectInfoCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -49,7 +51,7 @@ public:
CreateProjectCommand();
~CreateProjectCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -69,7 +71,7 @@ public:
CreateOplogCommand();
~CreateOplogCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -87,7 +89,7 @@ public:
ExportOplogCommand();
~ExportOplogCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -97,6 +99,7 @@ private:
std::string m_ProjectName;
std::string m_OplogName;
uint64_t m_MaxBlockSize = 0;
+ uint64_t m_MaxChunksPerBlock = 0;
uint64_t m_MaxChunkEmbedSize = 0;
bool m_EmbedLooseFiles = false;
bool m_Force = false;
@@ -142,7 +145,7 @@ public:
ImportOplogCommand();
~ImportOplogCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -152,8 +155,6 @@ private:
std::string m_ProjectName;
std::string m_OplogName;
std::string m_GcPath;
- size_t m_MaxBlockSize = 0;
- size_t m_MaxChunkEmbedSize = 0;
bool m_Force = false;
bool m_Async = false;
bool m_IgnoreMissingAttachments = false;
@@ -162,6 +163,7 @@ private:
std::string m_JupiterNamespace;
std::string m_JupiterBucket;
+
std::string m_JupiterOpenIdProvider;
std::string m_JupiterAccessToken;
std::string m_JupiterAccessTokenEnv;
@@ -189,7 +191,7 @@ public:
SnapshotOplogCommand();
~SnapshotOplogCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -204,7 +206,7 @@ class ProjectStatsCommand : public ProjectStoreCommand
public:
ProjectStatsCommand();
~ProjectStatsCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -217,7 +219,7 @@ class ProjectOpDetailsCommand : public ProjectStoreCommand
public:
ProjectOpDetailsCommand();
~ProjectOpDetailsCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -237,7 +239,7 @@ class OplogMirrorCommand : public ProjectStoreCommand
public:
OplogMirrorCommand();
~OplogMirrorCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -258,7 +260,7 @@ class OplogValidateCommand : public ProjectStoreCommand
public:
OplogValidateCommand();
~OplogValidateCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -268,4 +270,38 @@ private:
std::string m_OplogName;
};
+class OplogDownloadCommand : public ProjectStoreCommand
+{
+public:
+ static constexpr char Name[] = "oplog-download";
+ static constexpr char Description[] = "Download an cloud storage oplog";
+
+ OplogDownloadCommand();
+ ~OplogDownloadCommand();
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+
+private:
+ cxxopts::Options m_Options{Name, Description};
+
+ std::filesystem::path m_SystemRootDir;
+
+ bool m_Quiet = false;
+ bool m_Yes = false;
+
+ AuthCommandLineOptions m_AuthOptions;
+
+ // cloud builds
+ std::string m_OverrideHost;
+ std::string m_Host;
+ std::string m_Url;
+ bool m_AssumeHttp2 = false;
+ bool m_AllowRedirect = false;
+ std::string m_Namespace;
+ std::string m_Bucket;
+ std::string m_BuildId;
+
+ std::filesystem::path m_OutputPath;
+};
+
} // namespace zen
diff --git a/src/zen/cmds/rpcreplay_cmd.cpp b/src/zen/cmds/rpcreplay_cmd.cpp
index 6c26eaf23..dbf15ddd4 100644
--- a/src/zen/cmds/rpcreplay_cmd.cpp
+++ b/src/zen/cmds/rpcreplay_cmd.cpp
@@ -12,12 +12,13 @@
#include <zencore/stream.h>
#include <zencore/timer.h>
#include <zencore/workthreadpool.h>
+#include <zenhttp/formatters.h>
+#include <zenhttp/httpclient.h>
#include <zenhttp/httpcommon.h>
#include <zenhttp/packageformat.h>
-#include <zenutil/cache/rpcrecording.h>
+#include <zenutil/rpcrecording.h>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
#include <fmt/format.h>
#include <gsl/gsl-lite.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -39,33 +40,37 @@ RpcStartRecordingCommand::RpcStartRecordingCommand()
RpcStartRecordingCommand::~RpcStartRecordingCommand() = default;
-int
+void
RpcStartRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions, argc, argv);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_RecordingPath.empty())
{
- throw zen::OptionParseException("Rpc start recording command requires a path");
+ throw OptionParseException("'--path' is required", m_Options.help());
}
- cpr::Session Session;
- Session.SetUrl(fmt::format("{}/z$/exec$/start-recording"sv, m_HostName));
- Session.SetParameters({{"path", m_RecordingPath}});
- cpr::Response Response = Session.Post();
- ZEN_CONSOLE("{}", FormatHttpResponse(Response));
- return MapHttpToCommandReturnCode(Response);
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response =
+ Http.Post("/z$/exec$/start-recording"sv, HttpClient::KeyValueMap{}, HttpClient::KeyValueMap({{"path", m_RecordingPath}})))
+ {
+ ZEN_CONSOLE("{}", Response.ToText());
+ }
+ else
+ {
+ Response.ThrowError("Failed to start recording");
+ }
}
////////////////////////////////////////////////////
@@ -78,28 +83,32 @@ RpcStopRecordingCommand::RpcStopRecordingCommand()
RpcStopRecordingCommand::~RpcStopRecordingCommand() = default;
-int
+void
RpcStopRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions, argc, argv);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
- cpr::Session Session;
- Session.SetUrl(fmt::format("{}/z$/exec$/stop-recording"sv, m_HostName));
- cpr::Response Response = Session.Post();
- ZEN_CONSOLE("{}", FormatHttpResponse(Response));
- return MapHttpToCommandReturnCode(Response);
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response = Http.Post("/z$/exec$/stop-recording"sv))
+ {
+ ZEN_CONSOLE("{}", Response.ToText());
+ }
+ else
+ {
+ Response.ThrowError("Failed to stop recording");
+ }
}
////////////////////////////////////////////////////
@@ -174,26 +183,26 @@ RpcReplayCommand::RpcReplayCommand()
RpcReplayCommand::~RpcReplayCommand() = default;
-int
+void
RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions, argc, argv);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
if (m_RecordingPath.empty())
{
- throw zen::OptionParseException("Rpc replay command requires a path");
+ throw OptionParseException("'--path' is required", m_Options.help());
}
if (!IsDir(m_RecordingPath))
@@ -203,16 +212,31 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
m_ThreadCount = Max(m_ThreadCount, 1);
+ ZEN_CONSOLE("Replay '{}' (start offset {}, stride {}) to '{}', {} threads",
+ m_RecordingPath,
+ m_Offset,
+ m_Stride,
+ m_HostName,
+ m_ThreadCount);
+
Stopwatch TotalTimer;
if (m_OnHost)
{
- cpr::Session Session;
- Session.SetUrl(fmt::format("{}/z$/exec$/replay-recording"sv, m_HostName));
- Session.SetParameters({{"path", m_RecordingPath}, {"thread-count", fmt::format("{}", m_ThreadCount)}});
- cpr::Response Response = Session.Post();
- ZEN_CONSOLE("{}", FormatHttpResponse(Response));
- return MapHttpToCommandReturnCode(Response);
+ HttpClient Http(m_HostName);
+ if (HttpClient::Response Response =
+ Http.Post("/z$/exec$/replay-recording"sv,
+ HttpClient::KeyValueMap{},
+ HttpClient::KeyValueMap({{"path", m_RecordingPath}, {"thread-count", fmt::format("{}", m_ThreadCount)}})))
+ {
+ ZEN_CONSOLE("{}", Response.ToText());
+
+ return;
+ }
+ else
+ {
+ Response.ThrowError("Failed to start replay");
+ }
}
std::unique_ptr<cache::IRpcRequestReplayer> Replayer = cache::MakeDiskRequestReplayer(m_RecordingPath, true);
@@ -251,7 +275,7 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ZEN_CONSOLE("Waiting for worker processes...");
Sleep(1000);
}
- return 0;
+ return;
}
else
{
@@ -278,8 +302,7 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
});
- cpr::Session Session;
- Session.SetUrl(fmt::format("{}/z$/$rpc"sv, m_HostName));
+ HttpClient Http{m_HostName};
uint64_t EntryIndex = EntryOffset.fetch_add(m_Stride);
while (EntryIndex < EntryCount)
@@ -399,46 +422,26 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!m_DryRun)
{
- StringBuilder<32> SessionIdString;
+ Http.SetSessionId(RequestInfo.SessionId);
+ Payload.SetContentType(RequestInfo.ContentType);
- if (RequestInfo.SessionId != Oid::Zero)
- {
- RequestInfo.SessionId.ToString(SessionIdString);
- }
- else
- {
- GetSessionId().ToString(SessionIdString);
- }
+ HttpClient::Response Response =
+ Http.Post("/z$/$rpc", Payload, {HttpClient::Accept(RequestInfo.AcceptType)});
- Session.SetHeader({{"Content-Type", std::string(MapContentTypeToString(RequestInfo.ContentType))},
- {"Accept", std::string(MapContentTypeToString(RequestInfo.AcceptType))},
- {"UE-Session", std::string(SessionIdString)}});
-
- uint64_t Offset = 0;
- auto ReadCallback = [&Payload, &Offset](char* buffer, size_t& size, intptr_t) {
- size = Min<size_t>(size, Payload.GetSize() - Offset);
- IoBuffer PayloadRange = IoBuffer(Payload, Offset, size);
- MutableMemoryView Data(buffer, size);
- Data.CopyFrom(PayloadRange.GetView());
- Offset += size;
- return true;
- };
- Session.SetReadCallback(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
- cpr::Response Response = Session.Post();
BytesSent.fetch_add(Payload.GetSize());
- if (Response.error || !(IsHttpSuccessCode(Response.status_code) ||
- Response.status_code == gsl::narrow<long>(HttpResponseCode::NotFound)))
+ if (!Response)
{
- ZEN_CONSOLE_ERROR("{}", FormatHttpResponse(Response));
+ ZEN_CONSOLE_ERROR("{}", Response);
break;
}
- BytesReceived.fetch_add(Response.downloaded_bytes);
+ BytesReceived.fetch_add(Response.DownloadedBytes);
}
}
EntryIndex = EntryOffset.fetch_add(m_Stride);
}
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
while (!WorkLatch.Wait(1000))
@@ -478,8 +481,6 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
NiceByteRate(Received, ElapsedMS),
NiceTimeSpanMs(ElapsedMS),
NiceTimeSpanMs(TotalTimer.GetElapsedTimeMs()));
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/rpcreplay_cmd.h b/src/zen/cmds/rpcreplay_cmd.h
index 42cdd4ac1..a6363b614 100644
--- a/src/zen/cmds/rpcreplay_cmd.h
+++ b/src/zen/cmds/rpcreplay_cmd.h
@@ -12,7 +12,7 @@ public:
RpcStartRecordingCommand();
~RpcStartRecordingCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -27,7 +27,7 @@ public:
RpcStopRecordingCommand();
~RpcStopRecordingCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -41,7 +41,7 @@ public:
RpcReplayCommand();
~RpcReplayCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/run_cmd.cpp b/src/zen/cmds/run_cmd.cpp
index 309b8996a..00ab16fe5 100644
--- a/src/zen/cmds/run_cmd.cpp
+++ b/src/zen/cmds/run_cmd.cpp
@@ -57,32 +57,32 @@ RunCommand::~RunCommand()
{
}
-int
+void
RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
- ZEN_UNUSED(GlobalOptions);
-
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
// Validate arguments
if (GlobalOptions.PassthroughArgV.empty() || GlobalOptions.PassthroughArgV[0].empty())
- throw OptionParseException("No command specified. The command to run is passed in after a double dash ('--') on the command line");
+ throw OptionParseException("No command specified. The command to run is passed in after a double dash ('--') on the command line",
+ m_Options.help());
if (m_RunCount < 0)
- throw OptionParseException("Invalid count specified");
+ throw OptionParseException(fmt::format("'--count' ('{}') is invalid", m_RunCount), m_Options.help());
if (m_RunTime < -1 || m_RunTime == 0)
- throw OptionParseException("Invalid run time specified");
+ throw OptionParseException(fmt::format("'--time' ('{}') is invalid", m_RunTime), m_Options.help());
if (m_MaxBaseDirectoryCount < 0)
- throw OptionParseException("Invalid directory count specified");
+ throw OptionParseException(fmt::format("'--max-dirs' ('{}') is invalid", m_MaxBaseDirectoryCount), m_Options.help());
if (m_RunTime > 0 && m_RunCount > 0)
- throw OptionParseException("Specify either time or count, not both");
+ throw OptionParseException(fmt::format("'--time' ('{}') conflicts with '--count' ('{}') ", m_RunTime, m_RunCount),
+ m_Options.help());
if (m_RunCount == 0)
m_RunCount = 1;
@@ -190,8 +190,6 @@ RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
fmt::print("run complete, no error exit code\n", m_RunCount);
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/run_cmd.h b/src/zen/cmds/run_cmd.h
index f6512a4e8..570a2e63a 100644
--- a/src/zen/cmds/run_cmd.h
+++ b/src/zen/cmds/run_cmd.h
@@ -12,7 +12,7 @@ public:
RunCommand();
~RunCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
diff --git a/src/zen/cmds/serve_cmd.cpp b/src/zen/cmds/serve_cmd.cpp
index cfaa12bc0..49389bcdf 100644
--- a/src/zen/cmds/serve_cmd.cpp
+++ b/src/zen/cmds/serve_cmd.cpp
@@ -34,19 +34,19 @@ ServeCommand::~ServeCommand()
{
}
-int
+void
ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
if (m_ProjectName.empty())
{
- throw zen::OptionParseException("command requires a project");
+ throw OptionParseException("'--project' is required", m_Options.help());
}
if (m_OplogName.empty())
@@ -58,18 +58,18 @@ ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else
{
- throw zen::OptionParseException("command requires an oplog");
+ throw OptionParseException("'--oplog' is required", m_Options.help());
}
}
if (m_RootPath.empty())
{
- throw zen::OptionParseException("command requires a root path");
+ throw OptionParseException("'--path' is required", m_Options.help());
}
if (!IsDir(m_RootPath))
{
- throw zen::OptionParseException(fmt::format("path must exist and must be a directory: '{}'", m_RootPath));
+ throw std::runtime_error(fmt::format("'--path' ('{}') must exist and must be a directory", m_RootPath));
}
uint16_t ServerPort = 0;
@@ -95,7 +95,7 @@ ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_CONSOLE_ERROR("Failed to spawn server on port {}: '{}'", ServerPort, Ex.what());
- throw zen::OptionParseException("unable to resolve server specification (even after spawning server)");
+ throw std::runtime_error("Unable to resolve server specification (even after spawning server)");
}
}
else
@@ -197,8 +197,7 @@ ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (auto NewProjectResponse = Client.Post(ProjectUri, Project.Save()); !NewProjectResponse)
{
- // TODO: include details
- throw std::runtime_error("failed to create project");
+ NewProjectResponse.ThrowError("Failed to create project");
}
}
@@ -212,8 +211,7 @@ ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (auto NewOplogResponse = Client.Post(ProjectOplogUri, Oplog.Save()); !NewOplogResponse)
{
- // TODO: include details
- throw std::runtime_error("failed to create oplog");
+ NewOplogResponse.ThrowError("Failed to create oplog");
}
}
@@ -233,8 +231,6 @@ ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
#if ZEN_PLATFORM_WINDOWS
_getch(); // TEMPORARY HACK
#endif
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/serve_cmd.h b/src/zen/cmds/serve_cmd.h
index 007038d84..ac74981f2 100644
--- a/src/zen/cmds/serve_cmd.h
+++ b/src/zen/cmds/serve_cmd.h
@@ -14,7 +14,7 @@ public:
ServeCommand();
~ServeCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/service_cmd.cpp b/src/zen/cmds/service_cmd.cpp
index 3847c423d..f64e6860d 100644
--- a/src/zen/cmds/service_cmd.cpp
+++ b/src/zen/cmds/service_cmd.cpp
@@ -19,6 +19,11 @@
# include <unistd.h>
#endif
+#if ZEN_PLATFORM_LINUX
+# include <pwd.h>
+# include <grp.h>
+#endif
+
ZEN_THIRD_PARTY_INCLUDES_START
#include <gsl/gsl-lite.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -52,7 +57,7 @@ namespace {
return fRet;
}
- int WinRelaunchElevated()
+ void WinRelaunchElevated()
{
TCHAR CurrentDir[4096];
GetCurrentDirectory(4096, CurrentDir);
@@ -75,27 +80,26 @@ namespace {
shExInfo.nShow = SW_SHOW;
shExInfo.hInstApp = 0;
- DWORD ReturnCode = 1;
+ DWORD ProcessReturnCode = 1;
if (ShellExecuteEx(&shExInfo))
{
WaitForSingleObject(shExInfo.hProcess, INFINITE);
- GetExitCodeProcess(shExInfo.hProcess, &ReturnCode);
+ GetExitCodeProcess(shExInfo.hProcess, &ProcessReturnCode);
CloseHandle(shExInfo.hProcess);
- if (ReturnCode == 0)
+ if (ProcessReturnCode == 0)
{
ZEN_CONSOLE("Elevated execution completed successfully.");
}
else
{
- ZEN_CONSOLE("Elevated execution completed unsuccessfully, return code: '{}'.", ReturnCode);
+ throw ErrorWithReturnCode(fmt::format("Elevated execution completed unsuccessfully, return code: '{}'.", ProcessReturnCode),
+ (int)ProcessReturnCode);
}
}
else
{
- ZEN_CONSOLE("Failed to run elevated, operation did not complete.");
- ReturnCode = DWORD(-1);
+ ThrowLastError("Failed to run elevated, operation did not complete");
}
- return (int)ReturnCode;
}
#else // ZEN_PLATFORM_WINDOWS
@@ -104,25 +108,23 @@ namespace {
#endif // ZEN_PLATFORM_WINDOWS
- int RunElevated(bool AllowElevation)
+ void RunElevated(bool AllowElevation)
{
#if ZEN_PLATFORM_WINDOWS
if (AllowElevation)
{
- return WinRelaunchElevated();
+ WinRelaunchElevated();
}
else
{
- ZEN_CONSOLE(
+ throw std::runtime_error(fmt::format(
"This command requires elevated priviliges. Run command with elevated priviliges or add '--allow-elevation' command line "
- "option.");
- return 1;
+ "option."));
}
#endif // ZEN_PLATFORM_WINDOWS
#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
ZEN_UNUSED(AllowElevation);
- ZEN_CONSOLE("This command requires elevated priviliges. Run the command with `sudo`");
- return 1;
+ throw std::runtime_error(fmt::format("This command requires elevated priviliges. Run the command with `sudo`"));
#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
}
@@ -295,11 +297,9 @@ enum class ServiceStatusReturnCode
UnknownError
};
-int
+void
ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
- ZEN_UNUSED(GlobalOptions);
-
using namespace std::literals;
std::vector<char*> SubCommandArguments;
@@ -307,17 +307,17 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
int ParentCommandArgCount = GetSubCommand(m_Options, argc, argv, m_SubCommands, SubOption, SubCommandArguments);
if (!ParseOptions(ParentCommandArgCount, argv))
{
- return 0;
+ return;
}
if (SubOption == nullptr)
{
- throw zen::OptionParseException("command verb is missing");
+ throw OptionParseException("'verb' option is required", m_Options.help());
}
if (!ParseOptions(*SubOption, gsl::narrow<int>(SubCommandArguments.size()), SubCommandArguments.data()))
{
- return 0;
+ return;
}
if (SubOption == &m_StatusOptions)
@@ -326,18 +326,15 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::error_code Ec = QueryInstalledService(m_ServiceName, Info);
if (Ec)
{
- ZEN_CONSOLE("Can't get information about service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return gsl::narrow<int>(ServiceStatusReturnCode::UnknownError);
+ throw std::runtime_error(fmt::format("Can't get information about service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
if (Info.Status == ServiceStatus::NotInstalled)
{
- ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName);
- return gsl::narrow<int>(ServiceStatusReturnCode::NotInstalled);
+ throw std::runtime_error(fmt::format("Service '{}' is not installed", m_ServiceName));
}
else if (Info.Status != ServiceStatus::Running)
{
- ZEN_CONSOLE("Service '{}' is not running", m_ServiceName);
- return gsl::narrow<int>(ServiceStatusReturnCode::NotRunning);
+ throw std::runtime_error(fmt::format("Service '{}' is not running", m_ServiceName));
}
else
{
@@ -349,7 +346,8 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!IsElevated())
{
- return RunElevated(m_AllowElevation);
+ RunElevated(m_AllowElevation);
+ return;
}
ServiceInfo Info;
@@ -363,12 +361,10 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
Ec = StopService(m_ServiceName);
if (Ec)
{
- ZEN_CONSOLE("Failed to stop service '{}' using '{}'. Reason: '{}'",
- m_ServiceName,
- m_ServerExecutable,
- Ec.message());
-
- return 1;
+ throw std::runtime_error(fmt::format("Failed to stop service '{}' using '{}'. Reason: '{}'",
+ m_ServiceName,
+ m_ServerExecutable,
+ Ec.message()));
}
int Timeout = 30000; // Wait up to 30 seconds for the service to fully stop before uninstalling
@@ -377,8 +373,7 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
Ec = QueryInstalledService(m_ServiceName, Info);
if (Ec)
{
- ZEN_CONSOLE("Failed to wait for service to stop: '{}'", Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to wait for service to stop: '{}'", Ec.message()));
}
if (Info.Status == ServiceStatus::Stopped)
@@ -392,26 +387,23 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (Info.Status != ServiceStatus::Stopped)
{
- ZEN_CONSOLE("Timed out waiting for service to stop");
- return 1;
+ throw std::runtime_error("Timed out waiting for service to stop");
}
}
Ec = UninstallService(m_ServiceName);
if (Ec)
{
- ZEN_CONSOLE("Failed to uninstall running service '{}' using '{}'. Reason: '{}'",
- m_ServiceName,
- m_ServerExecutable,
- Ec.message());
-
- return 1;
+ throw std::runtime_error(fmt::format("Failed to uninstall running service '{}' using '{}'. Reason: '{}'",
+ m_ServiceName,
+ m_ServerExecutable,
+ Ec.message()));
}
}
else
{
ZEN_CONSOLE("Service '{}' already installed:\n{}", m_ServiceName, FmtServiceInfo(Info, " "));
- return 1;
+ return;
}
}
if (m_ServerExecutable.empty())
@@ -426,7 +418,7 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (m_InstallPath.empty())
{
- throw zen::OptionParseException("--full requires --install-path to be specified");
+ throw OptionParseException("'--full' requires '--install-path'", SubOption->help());
}
std::filesystem::path ExePath = zen::GetRunningExecutablePath();
@@ -435,13 +427,14 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ExePath,
m_ServerExecutable,
#if ZEN_PLATFORM_WINDOWS
- ExePath.replace_extension("pdb"),
- m_ServerExecutable.replace_extension("pdb"),
- ExePath
- .replace_filename("crashpad_handler.exe")
+ ExePath.parent_path() / ExePath.stem().replace_extension("pdb"),
+ m_ServerExecutable.parent_path() / m_ServerExecutable.stem().replace_extension("pdb"),
+ ExePath.parent_path() /
+ std::filesystem::path("crashpad_handler.exe")
#endif
#if ZEN_PLATFORM_MAC
- ExePath.replace_filename("crashpad_handler")
+ ExePath.parent_path() /
+ std::filesystem::path("crashpad_handler")
#endif
};
@@ -450,17 +443,44 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!std::filesystem::is_directory(m_InstallPath) && !CreateDirectories(m_InstallPath))
{
- ZEN_CONSOLE("Unable to create install directory '{}'", m_InstallPath);
- return 1;
+ throw std::runtime_error(fmt::format("Unable to create install directory '{}'", m_InstallPath));
+ }
+
+#if ZEN_PLATFORM_LINUX
+ uid_t UserId = 0;
+ gid_t GroupId = 0;
+ if (!m_UserName.empty())
+ {
+ struct passwd* Passwd = getpwnam(m_UserName.c_str());
+ if (Passwd == NULL)
+ {
+ throw std::runtime_error(fmt::format("Unable to determine user ID for user '{}'", m_UserName));
+ }
+
+ UserId = Passwd->pw_uid;
+
+ struct group* Grp = getgrnam(m_UserName.c_str());
+ if (Grp == NULL)
+ {
+ throw std::runtime_error(fmt::format("Unable to determine group ID for user '{}'", m_UserName));
+ }
+
+ GroupId = Grp->gr_gid;
+ if (chown(m_InstallPath.c_str(), UserId, GroupId) != 0)
+ {
+ throw std::runtime_error(
+ fmt::format("Unable to set ownership of directory '{}' for user '{}'", m_InstallPath, m_UserName));
+ }
}
+#endif
for (const std::filesystem::path& File : FilesToCopy)
{
std::filesystem::path Destination = m_InstallPath / File.filename();
+
if (!CopyFile(File, Destination, {.EnableClone = false}))
{
- ZEN_CONSOLE("Failed to copy '{}' to '{}'", File, Destination);
- return 1;
+ throw std::runtime_error(fmt::format("Failed to copy '{}' to '{}'", File, Destination));
}
ZEN_INFO("Copied '{}' to '{}'", File, Destination);
@@ -469,6 +489,17 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
std::filesystem::permissions(Destination, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add);
}
+
+#if ZEN_PLATFORM_LINUX
+ if (UserId != 0)
+ {
+ if (chown(Destination.c_str(), UserId, GroupId) != 0)
+ {
+ throw std::runtime_error(
+ fmt::format("Unable to set ownership of file '{}' for user '{}'", Destination, m_UserName));
+ }
+ }
+#endif
}
m_ServerExecutable = m_InstallPath / m_ServerExecutable.filename();
@@ -485,8 +516,8 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
});
if (Ec)
{
- ZEN_CONSOLE("Failed to install service '{}' using '{}' . Reason: '{}'", m_ServiceName, m_ServerExecutable, Ec.message());
- return 1;
+ throw std::runtime_error(
+ fmt::format("Failed to install service '{}' using '{}' . Reason: '{}'", m_ServiceName, m_ServerExecutable, Ec.message()));
}
ZEN_CONSOLE("Installed service '{}' using '{}' successfully", m_ServiceName, m_ServerExecutable);
@@ -495,8 +526,7 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
Ec = StartService(m_ServiceName);
if (Ec)
{
- ZEN_CONSOLE("Failed to start service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to start service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
}
}
@@ -507,30 +537,28 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::error_code Ec = QueryInstalledService(m_ServiceName, Info);
if (Ec)
{
- ZEN_CONSOLE("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
if (Info.Status == ServiceStatus::NotInstalled)
{
ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName);
- return 0;
+ return;
}
if (Info.Status != ServiceStatus::Stopped)
{
- ZEN_CONSOLE("Service '{}' is running, stop before uninstalling", m_ServiceName);
- return 0;
+ throw std::runtime_error(fmt::format("Service '{}' is running, stop before uninstalling", m_ServiceName));
}
if (!IsElevated())
{
- return RunElevated(m_AllowElevation);
+ RunElevated(m_AllowElevation);
+ return;
}
Ec = UninstallService(m_ServiceName);
if (Ec)
{
- ZEN_CONSOLE("Failed to uninstall service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to uninstall service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
ZEN_CONSOLE("Uninstalled service {} successfully", m_ServiceName);
}
@@ -541,30 +569,28 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::error_code Ec = QueryInstalledService(m_ServiceName, Info);
if (Ec)
{
- ZEN_CONSOLE("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
if (Info.Status == ServiceStatus::NotInstalled)
{
- ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName);
- return 1;
+ throw std::runtime_error(fmt::format("Service '{}' is not installed", m_ServiceName));
}
if (Info.Status != ServiceStatus::Stopped)
{
ZEN_CONSOLE("Service '{}' is already running", m_ServiceName);
- return 1;
+ return;
}
if (!IsElevated())
{
- return RunElevated(m_AllowElevation);
+ RunElevated(m_AllowElevation);
+ return;
}
Ec = StartService(m_ServiceName);
if (Ec)
{
- ZEN_CONSOLE("Failed to start service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to start service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
ZEN_CONSOLE("Started service '{}' successfully", m_ServiceName);
}
@@ -575,35 +601,31 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::error_code Ec = QueryInstalledService(m_ServiceName, Info);
if (Ec)
{
- ZEN_CONSOLE("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
if (Info.Status == ServiceStatus::NotInstalled)
{
- ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName);
- return 1;
+ throw std::runtime_error(fmt::format("Service '{}' is not installed", m_ServiceName));
}
if (Info.Status != ServiceStatus::Running)
{
ZEN_CONSOLE("Service '{}' is not running", m_ServiceName);
- return 1;
+ return;
}
if (!IsElevated())
{
- return RunElevated(m_AllowElevation);
+ RunElevated(m_AllowElevation);
+ return;
}
Ec = StopService(m_ServiceName);
if (Ec)
{
- ZEN_CONSOLE("Failed to stop service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
- return 1;
+ throw std::runtime_error(fmt::format("Failed to stop service '{}'. Reason: '{}'", m_ServiceName, Ec.message()));
}
ZEN_CONSOLE("Stopped service '{}' successfully", m_ServiceName);
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/service_cmd.h b/src/zen/cmds/service_cmd.h
index 359e8e854..ee98620d1 100644
--- a/src/zen/cmds/service_cmd.h
+++ b/src/zen/cmds/service_cmd.h
@@ -17,7 +17,7 @@ public:
ServiceCommand();
~ServiceCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/status_cmd.cpp b/src/zen/cmds/status_cmd.cpp
index b5764af44..c43f85052 100644
--- a/src/zen/cmds/status_cmd.cpp
+++ b/src/zen/cmds/status_cmd.cpp
@@ -3,6 +3,7 @@
#include "status_cmd.h"
#include <zencore/compactbinary.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/string.h>
@@ -19,14 +20,14 @@ StatusCommand::StatusCommand()
StatusCommand::~StatusCommand() = default;
-int
+void
StatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
uint16_t EffectivePort = 0;
@@ -34,24 +35,33 @@ StatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!IsFile(m_DataDir / ".lock"))
{
- ZEN_CONSOLE_ERROR("Lock file does not exist in directory '{}'", m_DataDir);
- return 1;
+ throw std::runtime_error(fmt::format("Lock file does not exist in directory '{}'", m_DataDir));
}
- LockFileInfo Info = ReadLockFilePayload(LoadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock")));
- std::string Reason;
- if (!ValidateLockFileInfo(Info, Reason))
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject LockFileObject =
+ ValidateAndReadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock"), ValidateResult);
+ ValidateResult == CbValidateError::None)
{
- ZEN_CONSOLE_ERROR("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason);
- return 1;
+ LockFileInfo Info = ReadLockFilePayload(LockFileObject);
+ std::string Reason;
+ if (!ValidateLockFileInfo(Info, Reason))
+ {
+ throw std::runtime_error(fmt::format("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason));
+ }
+ EffectivePort = Info.EffectiveListenPort;
+ }
+ else
+ {
+ throw std::runtime_error(
+ fmt::format("Lock file in directory '{}' is malformed. Reason: '{}'", m_DataDir, ToString(ValidateResult)));
}
- EffectivePort = Info.EffectiveListenPort;
}
ZenServerState State;
if (!State.InitializeReadOnly())
{
ZEN_CONSOLE("No Zen state found");
- return 0;
+ return;
}
ZEN_CONSOLE("{:>5} {:>6} {:>24}", "port", "pid", "session");
@@ -66,8 +76,6 @@ StatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ZEN_CONSOLE("{:>5} {:>6} {:>24}", Entry.EffectiveListenPort.load(), Entry.Pid.load(), SessionStringBuilder);
}
});
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/status_cmd.h b/src/zen/cmds/status_cmd.h
index 46bda9ee6..dc103a196 100644
--- a/src/zen/cmds/status_cmd.h
+++ b/src/zen/cmds/status_cmd.h
@@ -14,7 +14,7 @@ public:
StatusCommand();
~StatusCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/top_cmd.cpp b/src/zen/cmds/top_cmd.cpp
index 9794dc1c0..0e44dbbec 100644
--- a/src/zen/cmds/top_cmd.cpp
+++ b/src/zen/cmds/top_cmd.cpp
@@ -19,7 +19,7 @@ TopCommand::TopCommand()
TopCommand::~TopCommand() = default;
-int
+void
TopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions, argc, argv);
@@ -29,7 +29,7 @@ TopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_CONSOLE("No Zen state found");
- return 0;
+ return;
}
int n = 0;
@@ -55,8 +55,6 @@ TopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
State.Sweep();
}
}
-
- return 0;
}
//////////////////////////////////////////////////////////////////////////
@@ -67,7 +65,7 @@ PsCommand::PsCommand()
PsCommand::~PsCommand() = default;
-int
+void
PsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions, argc, argv);
@@ -77,14 +75,12 @@ PsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_CONSOLE("No Zen state found");
- return 0;
+ return;
}
State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) {
ZEN_CONSOLE("Port {} : pid {}", Entry.EffectiveListenPort.load(), Entry.Pid.load());
});
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/top_cmd.h b/src/zen/cmds/top_cmd.h
index 83410587b..74167ecfd 100644
--- a/src/zen/cmds/top_cmd.h
+++ b/src/zen/cmds/top_cmd.h
@@ -12,7 +12,7 @@ public:
TopCommand();
~TopCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -25,7 +25,7 @@ public:
PsCommand();
~PsCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/trace_cmd.cpp b/src/zen/cmds/trace_cmd.cpp
index 85caf33b8..41a30068c 100644
--- a/src/zen/cmds/trace_cmd.cpp
+++ b/src/zen/cmds/trace_cmd.cpp
@@ -20,21 +20,21 @@ TraceCommand::TraceCommand()
TraceCommand::~TraceCommand() = default;
-int
+void
TraceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
{
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
}
zen::HttpClient Http(m_HostName);
@@ -44,13 +44,12 @@ TraceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (zen::HttpClient::Response Response = Http.Post("/admin/trace/stop"sv))
{
ZEN_CONSOLE("OK: {}", Response.ToText());
- return 0;
}
else
{
- ZEN_CONSOLE_ERROR("trace stop failed: {}", Response.ToText());
- return 1;
+ Response.ThrowError("Trace stop failed");
}
+ return;
}
std::string StartArg;
@@ -68,26 +67,23 @@ TraceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (zen::HttpClient::Response Response = Http.Post(fmt::format("/admin/trace/start?{}"sv, StartArg)))
{
ZEN_CONSOLE("OK: {}", Response.ToText());
- return 0;
}
else
{
- ZEN_CONSOLE_ERROR("trace start failed: {}", Response.ToText());
- return 1;
+ Response.ThrowError("Trace start failed");
}
}
-
- if (zen::HttpClient::Response Response = Http.Get("/admin/trace"sv))
- {
- ZEN_CONSOLE("OK: {}", Response.ToText());
- return 0;
- }
else
{
- ZEN_CONSOLE_ERROR("trace status failed: {}", Response.ToText());
+ if (zen::HttpClient::Response Response = Http.Get("/admin/trace"sv))
+ {
+ ZEN_CONSOLE("OK: {}", Response.ToText());
+ }
+ else
+ {
+ Response.ThrowError("Trace status failed");
+ }
}
-
- return 1;
}
} // namespace zen
diff --git a/src/zen/cmds/trace_cmd.h b/src/zen/cmds/trace_cmd.h
index 404fb57a4..a6c9742b7 100644
--- a/src/zen/cmds/trace_cmd.h
+++ b/src/zen/cmds/trace_cmd.h
@@ -14,7 +14,7 @@ public:
TraceCommand();
~TraceCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/up_cmd.cpp b/src/zen/cmds/up_cmd.cpp
index fd330f616..a219677df 100644
--- a/src/zen/cmds/up_cmd.cpp
+++ b/src/zen/cmds/up_cmd.cpp
@@ -3,6 +3,7 @@
#include "up_cmd.h"
#include <zencore/compactbinary.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
@@ -30,7 +31,7 @@ UpCommand::UpCommand()
UpCommand::~UpCommand() = default;
-int
+void
UpCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
using namespace std::literals;
@@ -39,12 +40,12 @@ UpCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
if (m_ShowConsole && m_ShowLog)
{
- throw OptionParseException("--show-console can not be used in combination with --show-log");
+ throw OptionParseException("'--show-console' conficts with '--show-log'", m_Options.help());
}
{
@@ -72,7 +73,7 @@ UpCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
RunningEntries[0].DesiredPort,
RunningEntries[0].EffectivePort,
RunningEntries[0].Pid);
- return 0;
+ return;
}
}
}
@@ -98,20 +99,25 @@ UpCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (Server.IsRunning())
{
- ZEN_CONSOLE_ERROR("Zen server launch failed (timed out), terminating");
+ ZEN_CONSOLE_WARN("Zen server launch failed (timed out), terminating");
Server.Terminate();
if (!m_ShowConsole)
{
ZEN_CONSOLE("{}", Server.GetLogOutput());
}
- return 111;
+ throw std::runtime_error("Zen server launch failed (timed out), launched process was terminated");
}
- int ReturnCode = Server.Shutdown();
+ int ServerExitCode = Server.Shutdown();
if (!m_ShowConsole)
{
ZEN_CONSOLE("{}", Server.GetLogOutput());
}
- return ReturnCode;
+ if (ServerExitCode != 0)
+ {
+ throw ErrorWithReturnCode(
+ fmt::format("Zen server failed to get to a ready state and exited with return code {}", ServerExitCode),
+ ServerExitCode);
+ }
}
else
{
@@ -124,7 +130,6 @@ UpCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ZEN_CONSOLE("Zen server up");
}
}
- return 0;
}
//////////////////////////////////////////////////////////////////////////
@@ -138,14 +143,14 @@ AttachCommand::AttachCommand()
AttachCommand::~AttachCommand() = default;
-int
+void
AttachCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
ZenServerState Instance;
@@ -157,34 +162,40 @@ AttachCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!IsFile(m_DataDir / ".lock"))
{
- ZEN_CONSOLE("Lock file does not exist in directory '{}'", m_DataDir);
- return 1;
+ throw std::runtime_error(fmt::format("Lock file does not exist in directory '{}'", m_DataDir));
+ }
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject LockFileObject =
+ ValidateAndReadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock"), ValidateResult);
+ ValidateResult == CbValidateError::None && LockFileObject)
+ {
+ LockFileInfo Info = ReadLockFilePayload(LockFileObject);
+ std::string Reason;
+ if (!ValidateLockFileInfo(Info, Reason))
+ {
+ throw std::runtime_error(fmt::format("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason));
+ }
+ Entry = Instance.LookupByEffectivePort(Info.EffectiveListenPort);
}
- LockFileInfo Info = ReadLockFilePayload(LoadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock")));
- std::string Reason;
- if (!ValidateLockFileInfo(Info, Reason))
+ else
{
- ZEN_CONSOLE("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason);
- return 1;
+ throw std::runtime_error(
+ fmt::format("Lock file in directory '{}' is malformed. Reason: '{}'", m_DataDir, ToString(ValidateResult)));
}
- Entry = Instance.LookupByEffectivePort(Info.EffectiveListenPort);
}
if (!Entry)
{
- ZEN_CONSOLE_ERROR("No zen server instance to add sponsor process to");
- return 1;
+ throw std::runtime_error("No zen server instance to add sponsor process to");
}
// Sponsor processes are checked every second, so 2 second wait time should be enough
if (!Entry->AddSponsorProcess(m_OwnerPid, 2000))
{
- ZEN_CONSOLE_ERROR("Unable to add sponsor process to running zen server instance");
- return 1;
+ throw std::runtime_error("Unable to add sponsor process to running zen server instance");
}
ZEN_CONSOLE("Added sponsor process {} to running instance {} on port {}", m_OwnerPid, Entry->Pid.load(), m_Port);
- return 0;
}
//////////////////////////////////////////////////////////////////////////
@@ -199,14 +210,14 @@ DownCommand::DownCommand()
DownCommand::~DownCommand() = default;
-int
+void
DownCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
// Discover executing instances
@@ -224,17 +235,26 @@ DownCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (!IsFile(m_DataDir / ".lock"))
{
- ZEN_CONSOLE_ERROR("Lock file does not exist in directory '{}'", m_DataDir);
- return 1;
+ throw std::runtime_error(fmt::format("Lock file does not exist in directory '{}'", m_DataDir));
+ }
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject LockFileObject =
+ ValidateAndReadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock"), ValidateResult);
+ ValidateResult == CbValidateError::None && LockFileObject)
+ {
+ LockFileInfo Info = ReadLockFilePayload(LockFileObject);
+ std::string Reason;
+ if (!ValidateLockFileInfo(Info, Reason))
+ {
+ throw std::runtime_error(fmt::format("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason));
+ }
+ Entry = Instance.LookupByEffectivePort(Info.EffectiveListenPort);
}
- LockFileInfo Info = ReadLockFilePayload(LoadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock")));
- std::string Reason;
- if (!ValidateLockFileInfo(Info, Reason))
+ else
{
- ZEN_CONSOLE_ERROR("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason);
- return 1;
+ throw std::runtime_error(
+ fmt::format("Lock file in directory '{}' is malformed. Reason: '{}'", m_DataDir, ToString(ValidateResult)));
}
- Entry = Instance.LookupByEffectivePort(Info.EffectiveListenPort);
}
if (Entry)
@@ -259,7 +279,7 @@ DownCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (Server.WaitUntilExited(100, Ec) && !Ec)
{
ZEN_CONSOLE("shutdown complete");
- return 0;
+ return;
}
else if (Ec)
{
@@ -295,12 +315,12 @@ DownCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (Entry == nullptr)
{
ZEN_CONSOLE("Shutdown complete");
- return 0;
+ return;
}
if (Entry->Pid.load() != ServerProcessPid)
{
ZEN_CONSOLE("Shutdown complete");
- return 0;
+ return;
}
Sleep(100);
}
@@ -314,21 +334,12 @@ DownCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (std::error_code Ec = FindProcess(ServerExePath, RunningProcess); !Ec, /*IncludeSelf*/ false)
{
ZEN_CONSOLE_WARN("Attempting hard terminate of zen process with pid ({})", RunningProcess.Pid());
- try
- {
- if (RunningProcess.Terminate(0))
- {
- ZEN_CONSOLE("Terminate complete");
- return 0;
- }
- ZEN_CONSOLE_ERROR("Failed to terminate server, still running");
- return 1;
- }
- catch (const std::exception& Ex)
+ if (RunningProcess.Terminate(0))
{
- ZEN_CONSOLE_ERROR("Failed to terminate server: '{}'", Ex.what());
- return 1;
+ ZEN_CONSOLE("Terminate complete");
+ return;
}
+ throw std::runtime_error("Failed to terminate server, still running");
}
else
{
@@ -337,13 +348,11 @@ DownCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else if (Entry)
{
- ZEN_CONSOLE_ERROR("Failed to shutdown of server on port {}, use --force to hard terminate process",
- Entry->DesiredListenPort.load());
- return 1;
+ throw std::runtime_error(
+ fmt::format("Failed to shutdown of server on port {}, use --force to hard terminate process", Entry->DesiredListenPort.load()));
}
ZEN_CONSOLE("No zen server to bring down");
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/up_cmd.h b/src/zen/cmds/up_cmd.h
index c9af16749..2e822d5fc 100644
--- a/src/zen/cmds/up_cmd.h
+++ b/src/zen/cmds/up_cmd.h
@@ -14,7 +14,7 @@ public:
UpCommand();
~UpCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -31,7 +31,7 @@ public:
AttachCommand();
~AttachCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -47,7 +47,7 @@ public:
DownCommand();
~DownCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/version_cmd.cpp b/src/zen/cmds/version_cmd.cpp
index eda6e6725..ed34d7011 100644
--- a/src/zen/cmds/version_cmd.cpp
+++ b/src/zen/cmds/version_cmd.cpp
@@ -28,13 +28,13 @@ VersionCommand::VersionCommand()
VersionCommand::~VersionCommand() = default;
-int
+void
VersionCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
std::string Version;
@@ -65,13 +65,14 @@ VersionCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
Parameters.Entries.insert_or_assign("detailed", "true");
}
const std::string_view VersionRequest("/health/version"sv);
- HttpClient::Response Response = Client.Get(VersionRequest, {}, Parameters);
- if (!Response.IsSuccess())
+ if (HttpClient::Response Response = Client.Get(VersionRequest, {}, Parameters))
{
- ZEN_CONSOLE_ERROR("{} failed: {}", VersionRequest, Response.ErrorMessage(""sv));
- return 1;
+ Version = Response.AsText();
+ }
+ else
+ {
+ Response.ThrowError(fmt::format("{} failed", VersionRequest));
}
- Version = Response.AsText();
}
if (m_OutputPath.empty())
@@ -86,7 +87,6 @@ VersionCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
OutputFile.Write(Version.data(), Version.length(), 0);
OutputFile.Close();
}
-
- return 0;
}
+
} // namespace zen
diff --git a/src/zen/cmds/version_cmd.h b/src/zen/cmds/version_cmd.h
index 7a910e463..934ede868 100644
--- a/src/zen/cmds/version_cmd.h
+++ b/src/zen/cmds/version_cmd.h
@@ -15,7 +15,7 @@ public:
VersionCommand();
~VersionCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/vfs_cmd.cpp b/src/zen/cmds/vfs_cmd.cpp
index e8802d990..79ec20ce2 100644
--- a/src/zen/cmds/vfs_cmd.cpp
+++ b/src/zen/cmds/vfs_cmd.cpp
@@ -28,14 +28,14 @@ VfsCommand::~VfsCommand()
{
}
-int
+void
VfsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions, argc, argv);
- if (!ZenCmdBase::ParseOptions(argc, argv))
+ if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
// Validate arguments
@@ -43,14 +43,14 @@ VfsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
m_HostName = ResolveTargetHostSpec(m_HostName);
if (m_HostName.empty())
- throw OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", m_Options.help());
HttpClient Http(m_HostName);
if (m_Verb == "mount"sv)
{
if (m_MountPath.empty())
- throw OptionParseException("No source specified");
+ throw OptionParseException("'--vfs-path' is required", m_Options.help());
CbObjectWriter Cbo;
Cbo << "method"
@@ -66,8 +66,6 @@ VfsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
else
{
Result.ThrowError("VFS mount request failed"sv);
-
- return 1;
}
}
else if (m_Verb == "unmount"sv)
@@ -82,8 +80,6 @@ VfsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
else
{
Result.ThrowError("VFS unmount request failed"sv);
-
- return 1;
}
}
else if (m_Verb == "info"sv)
@@ -97,12 +93,8 @@ VfsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
else
{
Result.ThrowError("VFS info fetch failed"sv);
-
- return 1;
}
}
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/vfs_cmd.h b/src/zen/cmds/vfs_cmd.h
index 9b2497c0e..5deaa02fa 100644
--- a/src/zen/cmds/vfs_cmd.h
+++ b/src/zen/cmds/vfs_cmd.h
@@ -12,7 +12,7 @@ public:
VfsCommand();
~VfsCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/cmds/wipe_cmd.cpp b/src/zen/cmds/wipe_cmd.cpp
index 5a9d0174e..a3d40c142 100644
--- a/src/zen/cmds/wipe_cmd.cpp
+++ b/src/zen/cmds/wipe_cmd.cpp
@@ -5,10 +5,10 @@
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
+#include <zencore/parallelwork.h>
#include <zencore/string.h>
#include <zencore/timer.h>
#include <zencore/trace.h>
-#include <zenutil/parallelwork.h>
#include <zenutil/workerpools.h>
#include <signal.h>
@@ -251,7 +251,7 @@ namespace {
return Added;
};
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
struct AsyncVisitor : public GetDirectoryContentVisitor
{
@@ -543,7 +543,7 @@ WipeCommand::WipeCommand()
WipeCommand::~WipeCommand() = default;
-int
+void
WipeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -553,9 +553,9 @@ WipeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
signal(SIGBREAK, SignalCallbackHandler);
#endif // ZEN_PLATFORM_WINDOWS
- if (!ZenCmdBase::ParseOptions(argc, argv))
+ if (!ParseOptions(argc, argv))
{
- return 0;
+ return;
}
Quiet = m_Quiet;
@@ -567,7 +567,7 @@ WipeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!IsDir(m_Directory))
{
- return 0;
+ return;
}
while (!m_Yes)
@@ -583,13 +583,11 @@ WipeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
}
else if (Reponse == "n" || Reponse == "no")
{
- return 0;
+ return;
}
}
CleanDirectory(m_Directory, {}, !m_KeepReadOnlyFiles, m_Dryrun);
-
- return 0;
}
} // namespace zen
diff --git a/src/zen/cmds/wipe_cmd.h b/src/zen/cmds/wipe_cmd.h
index 0e910bb81..d0693a757 100644
--- a/src/zen/cmds/wipe_cmd.h
+++ b/src/zen/cmds/wipe_cmd.h
@@ -18,7 +18,7 @@ public:
~WipeCommand();
virtual cxxopts::Options& Options() override { return m_Options; }
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
diff --git a/src/zen/cmds/workspaces_cmd.cpp b/src/zen/cmds/workspaces_cmd.cpp
index 3930e56b7..9bb118eac 100644
--- a/src/zen/cmds/workspaces_cmd.cpp
+++ b/src/zen/cmds/workspaces_cmd.cpp
@@ -117,7 +117,7 @@ WorkspaceCommand::WorkspaceCommand()
WorkspaceCommand::~WorkspaceCommand() = default;
-int
+void
WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -129,12 +129,12 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
int ParentCommandArgCount = GetSubCommand(m_Options, argc, argv, m_SubCommands, SubOption, SubCommandArguments);
if (!ParseOptions(ParentCommandArgCount, argv))
{
- return 0;
+ return;
}
if (SubOption == nullptr)
{
- throw zen::OptionParseException("command verb is missing");
+ throw OptionParseException("'verb' option is required", m_Options.help());
}
m_HostName = ResolveTargetHostSpec(m_HostName);
@@ -144,7 +144,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
m_SystemRootDir = PickDefaultSystemRootDirectory();
if (m_SystemRootDir.empty())
{
- throw zen::OptionParseException("unable to resolve system root directory");
+ throw std::runtime_error("Unable to resolve default system root directory");
}
}
@@ -152,14 +152,14 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (!ParseOptions(*SubOption, gsl::narrow<int>(SubCommandArguments.size()), SubCommandArguments.data()))
{
- return 0;
+ return;
}
if (SubOption == &m_CreateOptions)
{
if (m_Path.empty())
{
- throw zen::OptionParseException(fmt::format("path is required\n{}", m_CreateOptions.help()));
+ throw OptionParseException("'--root-path' is required", SubOption->help());
}
std::filesystem::path Path = StringToPath(m_Path);
@@ -167,12 +167,12 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (m_Id.empty())
{
m_Id = Workspaces::PathToId(Path).ToString();
- ZEN_CONSOLE("Using generated workspace id {} from path '{}'", m_Id, Path);
+ ZEN_CONSOLE("Using generated workspace id '{}' from '--root-path' '{}'", m_Id, Path);
}
if (Oid::TryFromHexString(m_Id) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_Id));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Id), SubOption->help());
}
if (Workspaces::AddWorkspace(
@@ -185,16 +185,16 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
HttpClient Http(m_HostName);
if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result)
{
- ZEN_CONSOLE_ERROR("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
+ ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
}
}
ZEN_CONSOLE("Added/updated workspace {}", m_Id);
- return 0;
+ return;
}
else
{
ZEN_CONSOLE_WARN("Workspace {} already exists", m_Id);
- return 0;
+ return;
}
}
@@ -206,7 +206,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
static std::vector<Workspaces::WorkspaceConfiguration> Configs = Workspaces::ReadConfig(Log(), StatePath, Error);
if (!Error.empty())
{
- ZEN_CONSOLE_ERROR("Failed to read workspaces state from '{}'. Reason: '{}'", StatePath, Error);
+ throw std::runtime_error(fmt::format("Failed to read workspaces state from '{}'. Reason: '{}'", StatePath, Error));
}
else
{
@@ -216,20 +216,20 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ShowWorkspace(Config, " "sv);
}
}
- return 0;
+ return;
}
else
{
if (Oid::TryFromHexString(m_Id) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_Id));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Id), SubOption->help());
}
Workspaces::WorkspaceConfiguration Workspace = Workspaces::FindWorkspace(Log(), StatePath, Oid::FromHexString(m_Id));
if (Workspace.Id != Oid::Zero)
{
ShowWorkspace(Workspace, ""sv);
- return 0;
+ return;
}
else
{
@@ -242,12 +242,12 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
if (m_Id.empty())
{
- throw zen::OptionParseException(fmt::format("id is required", m_RemoveOptions.help()));
+ throw OptionParseException("'--workspace' is required", SubOption->help());
}
if (Oid::TryFromHexString(m_Id) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_Id));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_Id), SubOption->help());
}
if (Workspaces::RemoveWorkspace(Log(), StatePath, Oid::FromHexString(m_Id)))
@@ -257,7 +257,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
HttpClient Http(m_HostName);
if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result)
{
- ZEN_CONSOLE_ERROR("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
+ ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
}
}
ZEN_CONSOLE("Removed workspace {}", m_Id);
@@ -266,7 +266,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_CONSOLE_WARN("Workspace {} does not exist", m_Id);
}
- return 0;
+ return;
}
ZEN_ASSERT(false);
@@ -368,7 +368,7 @@ WorkspaceShareCommand::WorkspaceShareCommand()
WorkspaceShareCommand::~WorkspaceShareCommand() = default;
-int
+void
WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
ZEN_UNUSED(GlobalOptions);
@@ -380,12 +380,12 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
int ParentCommandArgCount = GetSubCommand(m_Options, argc, argv, m_SubCommands, SubOption, SubCommandArguments);
if (!ParseOptions(ParentCommandArgCount, argv))
{
- return 0;
+ return;
}
if (SubOption == nullptr)
{
- throw zen::OptionParseException("command verb is missing");
+ throw OptionParseException("'verb' option is required", m_Options.help());
}
m_HostName = ResolveTargetHostSpec(m_HostName);
@@ -395,7 +395,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
m_SystemRootDir = PickDefaultSystemRootDirectory();
if (m_SystemRootDir.empty())
{
- throw zen::OptionParseException("unable to resolve system root directory");
+ throw std::runtime_error("Unable to resolve default system root directory");
}
}
else
@@ -407,7 +407,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (!ParseOptions(*SubOption, gsl::narrow<int>(SubCommandArguments.size()), SubCommandArguments.data()))
{
- return 0;
+ return;
}
if (SubOption == &m_CreateOptions)
@@ -416,20 +416,19 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
{
if (m_WorkspaceId.empty())
{
- throw zen::OptionParseException("workspace id or root path is required");
+ throw OptionParseException("'--workspace' or '--root-path' is required", SubOption->help());
}
Oid WorkspaceId = Oid::TryFromHexString(m_WorkspaceId);
if (WorkspaceId == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("id '{}' is invalid", m_WorkspaceId));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_WorkspaceId), SubOption->help());
}
Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), StatePath, WorkspaceId);
if (WorkspaceConfig.Id == Oid::Zero)
{
- ZEN_CONSOLE_ERROR("Workspace {} does not exist", m_WorkspaceId);
- return 0;
+ throw std::runtime_error(fmt::format("Workspace {} does not exist", m_WorkspaceId));
}
m_WorkspaceRoot = WorkspaceConfig.RootPath;
}
@@ -445,7 +444,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
{
if (Oid::TryFromHexString(m_WorkspaceId) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_WorkspaceId));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_WorkspaceId), SubOption->help());
}
}
if (Workspaces::AddWorkspace(Log(), StatePath, {.Id = Oid::FromHexString(m_WorkspaceId), .RootPath = m_WorkspaceRoot}))
@@ -469,7 +468,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (Oid::TryFromHexString(m_ShareId) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_ShareId));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_ShareId), SubOption->help());
}
if (Workspaces::AddWorkspaceShare(Log(),
@@ -481,16 +480,14 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
HttpClient Http(m_HostName);
if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result)
{
- ZEN_CONSOLE_ERROR("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
+ ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
}
}
ZEN_CONSOLE("Created workspace share {}", m_ShareId);
- return 0;
}
else
{
ZEN_CONSOLE("Workspace share {} already exists", m_ShareId);
- return 0;
}
}
@@ -503,50 +500,47 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
Workspaces::FindWorkspaceShare(Log(), StatePath, m_Alias, WorkspaceConfig);
if (ShareConfig.Id == Oid::Zero)
{
- ZEN_CONSOLE("Workspace share with alias {} does not exist", m_Alias);
- return 0;
+ throw std::runtime_error(fmt::format("Workspace share with alias {} does not exist", m_Alias));
}
ShowShare(ShareConfig, WorkspaceConfig.Id, ""sv);
- return 0;
+ return;
}
if (m_WorkspaceId.empty())
{
- throw zen::OptionParseException("workspace id or root path is required");
+ throw OptionParseException("'--workspace' or '--root-path' is required", SubOption->help());
}
if (Oid::TryFromHexString(m_WorkspaceId) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_WorkspaceId));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_WorkspaceId), SubOption->help());
}
Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), StatePath, Oid::FromHexString(m_WorkspaceId));
if (WorkspaceConfig.Id == Oid::Zero)
{
- ZEN_CONSOLE("Workspace {} does not exist", m_WorkspaceId);
- return 0;
+ throw std::runtime_error(fmt::format("Workspace {} does not exist", m_WorkspaceId));
}
std::filesystem::path WorkspaceRoot = WorkspaceConfig.RootPath;
if (m_ShareId.empty())
{
- throw zen::OptionParseException("share id is required");
+ throw OptionParseException("'--share' is required", SubOption->help());
}
if (Oid::TryFromHexString(m_ShareId) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_ShareId));
+ throw OptionParseException(fmt::format("'--share' ('{}') is malformed", m_ShareId), SubOption->help());
}
Workspaces::WorkspaceShareConfiguration Share = Workspaces::FindWorkspaceShare(Log(), WorkspaceRoot, Oid::FromHexString(m_ShareId));
if (Share.Id == Oid::Zero)
{
- ZEN_CONSOLE("Workspace share {} does not exist", m_ShareId);
- return 0;
+ throw std::runtime_error(fmt::format("Workspace share {} does not exist", m_ShareId));
}
ShowShare(Share, Oid::Zero, ""sv);
- return 0;
+ return;
}
if (SubOption == &m_RemoveOptions)
@@ -560,7 +554,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (ShareConfig.Id == Oid::Zero)
{
ZEN_CONSOLE("Workspace share with alias {} does not exist", m_Alias);
- return 0;
+ return;
}
m_ShareId = ShareConfig.Id.ToString();
m_WorkspaceId = WorkspaceConfig.Id.ToString();
@@ -569,30 +563,30 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (m_WorkspaceId.empty())
{
- throw zen::OptionParseException("workspace id or root path is required");
+ throw OptionParseException("'--workspace' or '--root-path' is required", SubOption->help());
}
if (Oid::TryFromHexString(m_WorkspaceId) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_WorkspaceId));
+ throw OptionParseException(fmt::format("'--workspace' ('{}') is malformed", m_WorkspaceId), SubOption->help());
}
Workspaces::WorkspaceConfiguration WorkspaceConfig = Workspaces::FindWorkspace(Log(), StatePath, Oid::FromHexString(m_WorkspaceId));
if (WorkspaceConfig.Id == Oid::Zero)
{
ZEN_CONSOLE("Workspace {} does not exist", m_WorkspaceId);
- return 0;
+ return;
}
WorkspaceRoot = WorkspaceConfig.RootPath;
if (m_ShareId.empty())
{
- throw zen::OptionParseException("share id is required");
+ throw OptionParseException("'--share' is required", SubOption->help());
}
if (Oid::TryFromHexString(m_ShareId) == Oid::Zero)
{
- throw zen::OptionParseException(fmt::format("workspace id '{}' is invalid", m_ShareId));
+ throw OptionParseException(fmt::format("'--share' ('{}') is malformed", m_ShareId), SubOption->help());
}
if (Workspaces::RemoveWorkspaceShare(Log(), WorkspaceRoot, Oid::FromHexString(m_ShareId)))
@@ -602,16 +596,16 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
HttpClient Http(m_HostName);
if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result)
{
- ZEN_CONSOLE_ERROR("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
+ ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv));
}
}
ZEN_CONSOLE("Removed workspace share {}", m_ShareId);
- return 0;
+ return;
}
else
{
ZEN_CONSOLE("Removed workspace share {} does not exist", m_ShareId);
- return 0;
+ return;
}
}
@@ -620,12 +614,12 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
{
if (m_WorkspaceId.empty())
{
- throw zen::OptionParseException("workspace id is required");
+ throw OptionParseException("'--workspace' is required", Opts.help());
}
if (m_ShareId.empty())
{
- throw zen::OptionParseException(fmt::format("share id is required", Opts.help()));
+ throw OptionParseException("'--share' is required", Opts.help());
}
return fmt::format("{}/{}", m_WorkspaceId, m_ShareId);
}
@@ -649,20 +643,19 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", SubOption->help());
}
HttpClient Http(m_HostName);
if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/files", GetShareIdentityUrl(m_FilesOptions)), {}, Params))
{
ZEN_CONSOLE("{}: {}", Result, Result.ToText());
- return 0;
}
else
{
Result.ThrowError("failed to get workspace share files"sv);
- return 1;
}
+ return;
}
if (SubOption == &m_EntriesOptions)
@@ -683,20 +676,19 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (m_HostName.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("Unable to resolve server specification", SubOption->help());
}
HttpClient Http(m_HostName);
if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/entries", GetShareIdentityUrl(m_EntriesOptions)), {}, Params))
{
ZEN_CONSOLE("{}: {}", Result, Result.ToText());
- return 0;
}
else
{
Result.ThrowError("failed to get workspace share entries"sv);
- return 1;
}
+ return;
}
auto ChunksToOidStrings = [](HttpClient& Http,
@@ -751,14 +743,14 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (SubOption == &m_GetChunkOptions)
{
- if (m_ChunkId.empty())
+ if (m_HostName.empty())
{
- throw zen::OptionParseException("chunk id is required");
+ throw OptionParseException("Unable to resolve server specification", SubOption->help());
}
- if (m_HostName.empty())
+ if (m_ChunkId.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("'--chunk' is required", SubOption->help());
}
HttpClient Http(m_HostName);
@@ -777,30 +769,29 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/{}", GetShareIdentityUrl(m_GetChunkOptions), m_ChunkId), {}, Params))
{
ZEN_CONSOLE("{}: Bytes: {}", Result, NiceBytes(Result.ResponsePayload.GetSize()));
- return 0;
}
else
{
Result.ThrowError("failed to get workspace share chunk"sv);
- return 1;
}
+ return;
}
if (SubOption == &m_GetChunkBatchOptions)
{
- if (m_ShareId.empty())
+ if (m_HostName.empty())
{
- throw zen::OptionParseException(fmt::format("share id is required", m_InfoOptions.help()));
+ throw OptionParseException("Unable to resolve server specification", SubOption->help());
}
- if (m_ChunkIds.empty())
+ if (m_ShareId.empty())
{
- throw zen::OptionParseException("share is is required");
+ throw OptionParseException("'--share' is required", SubOption->help());
}
- if (m_HostName.empty())
+ if (m_ChunkIds.empty())
{
- throw zen::OptionParseException("unable to resolve server specification");
+ throw OptionParseException("'--chunks' is required", SubOption->help());
}
HttpClient Http(m_HostName);
@@ -832,13 +823,12 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char**
{
ZEN_CONSOLE("{}: Bytes: {}", m_ChunkIds[Index], NiceBytes(Results[Index].GetSize()));
}
- return 0;
}
else
{
Result.ThrowError("failed to get workspace share batch"sv);
- return 1;
}
+ return;
}
ZEN_ASSERT(false);
diff --git a/src/zen/cmds/workspaces_cmd.h b/src/zen/cmds/workspaces_cmd.h
index d85d8f7d8..f5880125b 100644
--- a/src/zen/cmds/workspaces_cmd.h
+++ b/src/zen/cmds/workspaces_cmd.h
@@ -17,7 +17,7 @@ public:
WorkspaceCommand();
~WorkspaceCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
@@ -49,7 +49,7 @@ public:
WorkspaceShareCommand();
~WorkspaceShareCommand();
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
private:
diff --git a/src/zen/xmake.lua b/src/zen/xmake.lua
index dadda32dd..f36573d62 100644
--- a/src/zen/xmake.lua
+++ b/src/zen/xmake.lua
@@ -5,7 +5,7 @@ target("zen")
add_headerfiles("**.h")
add_files("**.cpp")
add_files("zen.cpp", {unity_ignored = true })
- add_deps("zencore", "zenhttp", "zenstore", "zenutil")
+ add_deps("zencore", "zenhttp", "zenremotestore", "zenstore", "zenutil")
add_includedirs(".")
set_symbols("debug")
diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp
index 6aa278947..8e56e7df9 100644
--- a/src/zen/zen.cpp
+++ b/src/zen/zen.cpp
@@ -29,6 +29,7 @@
#include "cmds/workspaces_cmd.h"
#include <zencore/callstack.h>
+#include <zencore/config.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
@@ -56,7 +57,6 @@
#endif
ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
#include <spdlog/sinks/ansicolor_sink.h>
#include <spdlog/spdlog.h>
#include <gsl/gsl-lite.hpp>
@@ -73,6 +73,117 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
+#if ZEN_PLATFORM_WINDOWS
+static HANDLE
+GetConsoleHandle()
+{
+ static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
+ return hStdOut;
+}
+#endif
+
+static bool
+CheckStdoutTty()
+{
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hStdOut = GetConsoleHandle();
+ DWORD dwMode = 0;
+ static bool IsConsole = ::GetConsoleMode(hStdOut, &dwMode);
+ return IsConsole;
+#else
+ return isatty(fileno(stdout));
+#endif
+}
+
+static bool
+IsStdoutTty()
+{
+ static bool StdoutIsTty = CheckStdoutTty();
+ return StdoutIsTty;
+}
+
+static void
+OutputToConsoleRaw(const char* String, size_t Length)
+{
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hStdOut = GetConsoleHandle();
+#endif
+
+#if ZEN_PLATFORM_WINDOWS
+ if (IsStdoutTty())
+ {
+ WriteConsoleA(hStdOut, String, (DWORD)Length, 0, 0);
+ }
+ else
+ {
+ ::WriteFile(hStdOut, (LPCVOID)String, (DWORD)Length, 0, 0);
+ }
+#else
+ fwrite(String, 1, Length, stdout);
+#endif
+}
+
+static void
+OutputToConsoleRaw(const std::string& String)
+{
+ OutputToConsoleRaw(String.c_str(), String.length());
+}
+
+static void
+OutputToConsoleRaw(const StringBuilderBase& SB)
+{
+ OutputToConsoleRaw(SB.c_str(), SB.Size());
+}
+
+static uint32_t
+GetConsoleColumns(uint32_t Default)
+{
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hStdOut = GetConsoleHandle();
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ if (GetConsoleScreenBufferInfo(hStdOut, &csbi) == TRUE)
+ {
+ return (uint32_t)(csbi.srWindow.Right - csbi.srWindow.Left + 1);
+ }
+#else
+ struct winsize w;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0)
+ {
+ return (uint32_t)w.ws_col;
+ }
+#endif
+ return Default;
+}
+
+enum class ReturnCode : std::int8_t
+{
+ kSuccess = 0,
+
+ kOtherError = 1,
+
+ kBadInput = 2,
+ kOutOfMemory = 16,
+ kOutOfDisk = 17,
+ kAssertError = 70,
+
+ kHttpOtherClientError = 80,
+ kHttpCantConnectError = 81, // CONNECTION_FAILURE
+ kHttpNotFound = 66, // NotFound(404)
+ kHttpUnauthorized = 77, // Unauthorized(401),
+ kHttpSLLError =
+ 82, // SSL_CONNECT_ERROR, SSL_LOCAL_CERTIFICATE_ERROR, SSL_REMOTE_CERTIFICATE_ERROR, SSL_CACERT_ERROR, GENERIC_SSL_ERROR
+ kHttpForbidden = 83, // Forbidden(403)
+ kHttpTimeout = 84, // NETWORK_RECEIVE_ERROR, NETWORK_SEND_FAILURE, OPERATION_TIMEDOUT, RequestTimeout(408)
+ kHttpConflict = 85, // Conflict(409)
+ kHttpNoHost = 86, // HOST_RESOLUTION_FAILURE, PROXY_RESOLUTION_FAILURE
+
+ kHttpOtherServerError = 90,
+ kHttpInternalServerError = 91, // InternalServerError(500)
+ kHttpServiceUnavailable = 69, // ServiceUnavailable(503)
+ kHttpBadGateway = 92, // BadGateway(502)
+ kHttpGatewayTimeout = 93, // GatewayTimeout(504)
+};
+
ZenCmdCategory DefaultCategory{.Name = "general commands"};
ZenCmdCategory g_UtilitiesCategory{.Name = "utility commands"};
ZenCmdCategory g_ProjectStoreCategory{.Name = "project store commands"};
@@ -94,6 +205,8 @@ ZenCmdBase::ParseOptions(int argc, char** argv)
bool
ZenCmdBase::ParseOptions(cxxopts::Options& CmdOptions, int argc, char** argv)
{
+ CmdOptions.set_width(GetConsoleColumns(80));
+
cxxopts::ParseResult Result;
try
@@ -102,7 +215,7 @@ ZenCmdBase::ParseOptions(cxxopts::Options& CmdOptions, int argc, char** argv)
}
catch (const std::exception& Ex)
{
- throw zen::OptionParseException(Ex.what());
+ throw zen::OptionParseException(Ex.what(), CmdOptions.help());
}
CmdOptions.show_positional_help();
@@ -128,7 +241,7 @@ ZenCmdBase::ParseOptions(cxxopts::Options& CmdOptions, int argc, char** argv)
First = false;
}
- throw zen::OptionParseException(fmt::format("Invalid arguments: {}", StringBuilder.ToView()));
+ throw zen::OptionParseException(fmt::format("Invalid arguments: {}", StringBuilder.ToView()), {});
}
return true;
@@ -162,62 +275,39 @@ ZenCmdBase::GetSubCommand(cxxopts::Options&,
return argc;
}
-std::string
-ZenCmdBase::FormatHttpResponse(const cpr::Response& Response)
+static ReturnCode
+GetReturnCodeFromHttpResult(const HttpClientError& Ex)
{
- if (Response.error.code != cpr::ErrorCode::OK)
- {
- if (Response.error.message.empty())
- {
- return fmt::format("Request '{}' failed, error code {}", Response.url.str(), static_cast<int>(Response.error.code));
- }
- return fmt::format("Request '{}' failed. Reason: '{}' ({})",
- Response.url.str(),
- Response.error.message,
- static_cast<int>(Response.error.code));
- }
+ HttpClientError::ResponseClass ResponseClass = Ex.GetResponseClass();
- std::string Content;
- if (auto It = Response.header.find("Content-Type"); It != Response.header.end())
- {
- zen::HttpContentType ContentType = zen::ParseContentType(It->second);
- if (ContentType == zen::HttpContentType::kText)
- {
- Content = Response.text;
- }
- else if (ContentType == zen::HttpContentType::kJSON)
- {
- Content = fmt::format("\n{}", Response.text);
- }
- else if (!Response.text.empty())
- {
- Content = fmt::format("[{}]", MapContentTypeToString(ContentType));
- }
- }
+ if (ResponseClass == HttpClientError::ResponseClass::kSuccess)
+ return ReturnCode::kSuccess;
- std::string_view ResponseString = zen::ReasonStringForHttpResultCode(
- Response.status_code == static_cast<long>(zen::HttpResponseCode::NoContent) ? static_cast<long>(zen::HttpResponseCode::OK)
- : Response.status_code);
- if (Content.empty())
+ switch (ResponseClass)
{
- return std::string(ResponseString);
- }
+#define HANDLE_CASE(ErrorClass) \
+ case HttpClientError::ResponseClass::ErrorClass: \
+ return ReturnCode::ErrorClass
+
+ HANDLE_CASE(kHttpOtherClientError);
+ HANDLE_CASE(kHttpCantConnectError);
+ HANDLE_CASE(kHttpNotFound);
+ HANDLE_CASE(kHttpUnauthorized);
+ HANDLE_CASE(kHttpSLLError);
+ HANDLE_CASE(kHttpForbidden);
+ HANDLE_CASE(kHttpTimeout);
+ HANDLE_CASE(kHttpConflict);
+ HANDLE_CASE(kHttpNoHost);
+ HANDLE_CASE(kHttpOtherServerError);
+ HANDLE_CASE(kHttpInternalServerError);
+ HANDLE_CASE(kHttpServiceUnavailable);
+ HANDLE_CASE(kHttpBadGateway);
+ HANDLE_CASE(kHttpGatewayTimeout);
+#undef HANDLE_CASE
- return fmt::format("{}: {}", ResponseString, Content);
-}
-
-int
-ZenCmdBase::MapHttpToCommandReturnCode(const cpr::Response& Response)
-{
- if (zen::IsHttpSuccessCode(Response.status_code))
- {
- return 0;
- }
- if (Response.error.code != cpr::ErrorCode::OK)
- {
- return static_cast<int>(Response.error.code);
+ default:
+ return ReturnCode::kOtherError;
}
- return 1;
}
std::string
@@ -274,86 +364,10 @@ ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec)
return ResolveTargetHostSpec(InHostSpec, /* out */ Dummy);
}
-#if ZEN_PLATFORM_WINDOWS
-static HANDLE
-GetConsoleHandle()
-{
- static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
- return hStdOut;
-}
-#endif
-
-static bool
-CheckStdoutTty()
-{
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hStdOut = GetConsoleHandle();
- DWORD dwMode = 0;
- static bool IsConsole = ::GetConsoleMode(hStdOut, &dwMode);
- return IsConsole;
-#else
- return isatty(fileno(stdout));
-#endif
-}
-
-static bool
-IsStdoutTty()
-{
- static bool StdoutIsTty = CheckStdoutTty();
- return StdoutIsTty;
-}
-
-static void
-OutputToConsoleRaw(const char* String, size_t Length)
-{
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hStdOut = GetConsoleHandle();
-#endif
-
-#if ZEN_PLATFORM_WINDOWS
- if (IsStdoutTty())
- {
- WriteConsoleA(hStdOut, String, (DWORD)Length, 0, 0);
- }
- else
- {
- ::WriteFile(hStdOut, (LPCVOID)String, (DWORD)Length, 0, 0);
- }
-#else
- fwrite(String, 1, Length, stdout);
-#endif
-}
-
-static void
-OutputToConsoleRaw(const std::string& String)
-{
- OutputToConsoleRaw(String.c_str(), String.length());
-}
-
-static void
-OutputToConsoleRaw(const StringBuilderBase& SB)
-{
- OutputToConsoleRaw(SB.c_str(), SB.Size());
-}
-
-static uint32_t
-GetConsoleColumns()
+void
+ZenCmdBase::LogExecutableVersionAndPid()
{
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hStdOut = GetConsoleHandle();
- CONSOLE_SCREEN_BUFFER_INFO csbi;
- if (GetConsoleScreenBufferInfo(hStdOut, &csbi) == TRUE)
- {
- return (uint32_t)(csbi.srWindow.Right - csbi.srWindow.Left + 1);
- }
-#else
- struct winsize w;
- if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0)
- {
- return (uint32_t)w.ws_col;
- }
-#endif
- return 1024;
+ ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId());
}
void
@@ -470,7 +484,7 @@ ProgressBar::UpdateState(const State& NewState, bool DoLinebreak)
uint64_t ETAMS =
(NewState.Status == State::EStatus::Running) && (PercentDone > 5) ? (ETAElapsedMS * NewState.RemainingCount) / Completed : 0;
- uint32_t ConsoleColumns = GetConsoleColumns();
+ uint32_t ConsoleColumns = GetConsoleColumns(1024);
const std::string PercentString = fmt::format("{:#3}%", PercentDone);
@@ -485,7 +499,7 @@ ProgressBar::UpdateState(const State& NewState, bool DoLinebreak)
ExtendableStringBuilder<256> OutputBuilder;
- OutputBuilder << "\r" << Task << PercentString;
+ OutputBuilder << "\r" << Task << " " << PercentString;
if (OutputBuilder.Size() + 1 < ConsoleColumns)
{
size_t RemainingSpace = ConsoleColumns - (OutputBuilder.Size() + 1);
@@ -675,6 +689,7 @@ main(int argc, char** argv)
MasterCommand MasterCmd;
OplogMirrorCommand OplogMirrorCmd;
SnapshotOplogCommand SnapshotOplogCmd;
+ OplogDownloadCommand OplogDownload;
OplogValidateCommand OplogValidateCmd;
PrintCommand PrintCmd;
PrintPackageCommand PrintPkgCmd;
@@ -732,6 +747,7 @@ main(int argc, char** argv)
{"oplog-import", &ImportOplogCmd, "Import project store oplog"},
{"oplog-mirror", &OplogMirrorCmd, "Mirror project store oplog to file system"},
{"oplog-snapshot", &SnapshotOplogCmd, "Snapshot project store oplog"},
+ {OplogDownloadCommand::Name, &OplogDownload, OplogDownloadCommand::Description},
{"oplog-validate", &OplogValidateCmd, "Validate oplog for missing references"},
{"print", &PrintCmd, "Print compact binary object"},
{"printpackage", &PrintPkgCmd, "Print compact binary package"},
@@ -948,7 +964,7 @@ main(int argc, char** argv)
printf("\n");
}
- exit(0);
+ return (int)ReturnCode::kSuccess;
}
#if ZEN_USE_SENTRY
@@ -1025,86 +1041,107 @@ main(int argc, char** argv)
{
if (StrCaseCompare(SubCommand.c_str(), CmdInfo.CmdName) == 0)
{
- cxxopts::Options& VerbOptions = CmdInfo.Cmd->Options();
-
try
{
- return CmdInfo.Cmd->Run(GlobalOptions, (int)CommandArgVec.size(), CommandArgVec.data());
+ CmdInfo.Cmd->Run(GlobalOptions, (int)CommandArgVec.size(), CommandArgVec.data());
+ return (int)ReturnCode::kSuccess;
}
catch (const OptionParseException& Ex)
{
- ZEN_CONSOLE_ERROR("Invalid arguments for command '{}': {}\n\n{}", SubCommand, Ex.what(), VerbOptions.help());
- exit(11);
+ ZEN_CONSOLE("{}\n", Ex.m_Help);
+ ZEN_CONSOLE_ERROR("Invalid arguments for command '{}': {}", SubCommand, Ex.what());
+ return (int)ReturnCode::kBadInput;
}
catch (const std::system_error& Ex)
{
if (IsOOD(Ex))
{
ZEN_CONSOLE_ERROR("Operation failed due to out of disk space: {}", Ex.what());
- exit(3);
+ return (int)ReturnCode::kOutOfDisk;
}
else if (IsOOM(Ex))
{
ZEN_CONSOLE_ERROR("Operation failed due to out of memory: {}", Ex.what());
- exit(3);
+ return (int)ReturnCode::kOutOfMemory;
}
else
{
ZEN_CONSOLE_ERROR("Operation failed due to system error: {} ({})\n", Ex.what(), Ex.code() ? Ex.code().value() : 0);
- exit(Ex.code() ? Ex.code().value() : 10);
+ return (int)ReturnCode::kOtherError;
}
}
catch (const HttpClientError& Ex)
{
ZEN_CONSOLE_ERROR("Operation failed due to a http error: {}", Ex.what());
- exit(Ex.m_Error != 0 ? Ex.m_Error : (int)Ex.m_ResponseCode);
+ ReturnCode Result = GetReturnCodeFromHttpResult(Ex);
+ return (int)Result;
+ }
+ catch (const AssertException& Ex)
+ {
+ ZEN_CONSOLE_ERROR("Operation failed due to an assert exception: {}", Ex.FullDescription());
+ return (int)ReturnCode::kAssertError;
+ }
+ catch (const ErrorWithReturnCode& Ex)
+ {
+ ZEN_CONSOLE_ERROR("{}", Ex.what());
+ return Ex.m_ReturnCode;
}
catch (const std::exception& Ex)
{
- ZEN_CONSOLE_ERROR("Operation failed due to: {}\n", Ex.what());
- exit(11);
+ ZEN_CONSOLE_ERROR("{}\n", Ex.what());
+ return (int)ReturnCode::kOtherError;
}
}
}
printf("Unknown command specified: '%s', exiting\n", SubCommand.c_str());
+ return (int)ReturnCode::kBadInput;
}
catch (const OptionParseException& Ex)
{
- std::string HelpMessage = Options.help();
-
- printf("Error parsing program arguments: %s\n\n%s", Ex.what(), HelpMessage.c_str());
-
- return 9;
+ printf("%s\n\n", Ex.m_Help.c_str());
+ printf("Invalid arguments arguments: %s", Ex.what());
+ return (int)ReturnCode::kBadInput;
}
catch (const std::system_error& Ex)
{
if (IsOOD(Ex))
{
printf("Operation failed due to out of disk space: %s", Ex.what());
- return 3;
+ return (int)ReturnCode::kOutOfDisk;
}
else if (IsOOM(Ex))
{
printf("Operation failed due to out of memory: %s", Ex.what());
- return 3;
+ return (int)ReturnCode::kOutOfMemory;
}
else
{
printf("Operation failed due to system error: %s (%d)\n", Ex.what(), Ex.code() ? Ex.code().value() : 0);
- return Ex.code() ? Ex.code().value() : 10;
+ return (int)ReturnCode::kOtherError;
}
}
catch (const HttpClientError& Ex)
{
- printf("Operation failed due to a http error: %s", Ex.what());
- return Ex.m_Error != 0 ? Ex.m_Error : (int)Ex.m_ResponseCode;
+ printf("Error: Operation failed due to a http error: %s", Ex.what());
+ ReturnCode Result = GetReturnCodeFromHttpResult(Ex);
+ return (int)Result;
+ }
+ catch (const AssertException& Ex)
+ {
+ printf("Error: Operation failed due to an assert exception: %s", Ex.FullDescription().c_str());
+ return (int)ReturnCode::kAssertError;
+ }
+ catch (const ErrorWithReturnCode& Ex)
+ {
+ printf("Error: %s", Ex.what());
+ return Ex.m_ReturnCode;
}
catch (const std::exception& Ex)
{
- printf("Operation failed due to: %s\n", Ex.what());
- return 11;
+ printf("Error: %s\n", Ex.what());
+ return (int)ReturnCode::kOtherError;
}
- return 0;
+ return (int)ReturnCode::kSuccess;
}
diff --git a/src/zen/zen.h b/src/zen/zen.h
index 40c745bc7..ffb35e5ca 100644
--- a/src/zen/zen.h
+++ b/src/zen/zen.h
@@ -7,10 +7,6 @@
#include <zencore/zencore.h>
#include <zenutil/commandlineoptions.h>
-namespace cpr {
-class Response;
-}
-
namespace zen {
struct ZenCliOptions
@@ -35,13 +31,24 @@ extern ZenCmdCategory g_ProjectStoreCategory;
extern ZenCmdCategory g_CacheStoreCategory;
extern ZenCmdCategory g_StorageCategory;
+class ErrorWithReturnCode : public std::runtime_error
+{
+public:
+ using _Mybase = runtime_error;
+ ErrorWithReturnCode(const std::string& Message, int InReturnCode) : _Mybase(Message), m_ReturnCode(InReturnCode) {}
+
+ ErrorWithReturnCode(const char* Message, int InReturnCode) : _Mybase(Message), m_ReturnCode(InReturnCode) {}
+
+ const int m_ReturnCode = -1;
+};
+
/** Base class for command implementations
*/
class ZenCmdBase
{
public:
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) = 0;
+ virtual void Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) = 0;
virtual cxxopts::Options& Options() = 0;
virtual ZenCmdCategory& CommandCategory() const;
@@ -53,10 +60,10 @@ public:
std::span<cxxopts::Options*> SubOptions,
cxxopts::Options*& OutSubOption,
std::vector<char*>& OutSubCommandArguments);
- static std::string FormatHttpResponse(const cpr::Response& Response);
- static int MapHttpToCommandReturnCode(const cpr::Response& Response);
static std::string ResolveTargetHostSpec(const std::string& InHostSpec);
static std::string ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort);
+
+ static void LogExecutableVersionAndPid();
};
class StorageCommand : public ZenCmdBase
diff --git a/src/zencore-test/zencore-test.cpp b/src/zencore-test/zencore-test.cpp
index 0f64f4a5f..327550b32 100644
--- a/src/zencore-test/zencore-test.cpp
+++ b/src/zencore-test/zencore-test.cpp
@@ -26,7 +26,14 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
zen::IgnoreChildSignals();
# endif
+# if ZEN_WITH_TRACE
zen::TraceInit("zencore-test");
+ zen::TraceOptions TraceCommandlineOptions;
+ if (GetTraceOptionsFromCommandline(TraceCommandlineOptions))
+ {
+ TraceConfigure(TraceCommandlineOptions);
+ }
+# endif // ZEN_WITH_TRACE
zen::logging::InitializeLogging();
zen::MaximizeOpenFileCount();
diff --git a/src/zencore/callstack.cpp b/src/zencore/callstack.cpp
index b22f2ec1f..8aa1111bf 100644
--- a/src/zencore/callstack.cpp
+++ b/src/zencore/callstack.cpp
@@ -210,6 +210,54 @@ CallstackToString(const CallstackFrames* Callstack, std::string_view Prefix)
return SB.ToString();
}
+void
+CallstackToStringRaw(const CallstackFrames* Callstack, void* CallbackUserData, CallstackRawCallback Callback)
+{
+ if (Callstack && Callstack->FrameCount > 0)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ char SymbolBuffer[sizeof(SYMBOL_INFO) + 1024];
+ SYMBOL_INFO* SymbolInfo = (SYMBOL_INFO*)SymbolBuffer;
+ SymbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO);
+ SymbolInfo->MaxNameLen = 1023;
+ DWORD64 Displacement = 0;
+ fmt::basic_memory_buffer<char, 2048> Message;
+ for (uint32_t FrameIndex = 0; FrameIndex < Callstack->FrameCount; FrameIndex++)
+ {
+ if (WinSymbols.GetSymbol(Callstack->Frames[FrameIndex], SymbolInfo, Displacement))
+ {
+ auto Appender = fmt::appender(Message);
+ fmt::format_to(Appender, "{}+{:#x} [{:#x}]", SymbolInfo->Name, Displacement, (uintptr_t)Callstack->Frames[FrameIndex]);
+ Message.push_back('\0');
+ Callback(CallbackUserData, FrameIndex, Message.data());
+ Message.resize(0);
+ }
+ }
+#endif
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ char** messages = backtrace_symbols(Callstack->Frames, (int)Callstack->FrameCount);
+ if (messages)
+ {
+ for (uint32_t FrameIndex = 0; FrameIndex < Callstack->FrameCount; FrameIndex++)
+ {
+ Callback(CallbackUserData, FrameIndex, messages[FrameIndex]);
+ }
+ free(messages);
+ }
+#endif
+ }
+}
+
+CallstackFrames*
+GetCallstackRaw(void* CaptureBuffer, int FramesToSkip, int FramesToCapture)
+{
+ CallstackFrames* Callstack = (CallstackFrames*)CaptureBuffer;
+
+ Callstack->Frames = (void**)&Callstack[1];
+ Callstack->FrameCount = GetCallstack(FramesToSkip, FramesToCapture, Callstack->Frames);
+ return Callstack;
+}
+
#if ZEN_WITH_TESTS
TEST_CASE("Callstack.Basic")
diff --git a/src/zencore/compactbinaryfile.cpp b/src/zencore/compactbinaryfile.cpp
index 1526c21d5..ec2fc3cd5 100644
--- a/src/zencore/compactbinaryfile.cpp
+++ b/src/zencore/compactbinaryfile.cpp
@@ -1,7 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "zencore/compactbinaryfile.h"
-#include "zencore/compactbinaryvalidation.h"
+#include "zencore/compactbinaryutil.h"
#include <zencore/filesystem.h>
@@ -19,12 +19,12 @@ LoadCompactBinaryObject(const std::filesystem::path& FilePath)
IoBuffer ObjectBuffer = ObjectFile.Flatten();
- if (CbValidateError Result = ValidateCompactBinary(ObjectBuffer, CbValidateMode::Default); Result == CbValidateError::None)
+ CbValidateError ValidateResult;
+ CbObject Object = ValidateAndReadCompactBinaryObject(IoBuffer(ObjectBuffer), ValidateResult);
+ if (ValidateResult == CbValidateError::None)
{
- CbObject Object = LoadCompactBinaryObject(ObjectBuffer);
const IoHash WorkerId = IoHash::HashBuffer(ObjectBuffer);
-
- return {.Object = Object, .Hash = WorkerId};
+ return {.Object = std::move(Object), .Hash = WorkerId};
}
return {.Hash = IoHash::Zero};
diff --git a/src/zencore/compactbinaryutil.cpp b/src/zencore/compactbinaryutil.cpp
new file mode 100644
index 000000000..074bdaffd
--- /dev/null
+++ b/src/zencore/compactbinaryutil.cpp
@@ -0,0 +1,39 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/compactbinaryutil.h>
+
+#include <zencore/compress.h>
+#include <zencore/filesystem.h>
+
+namespace zen {
+
+CbObject
+ValidateAndReadCompactBinaryObject(const SharedBuffer&& Payload, CbValidateError& OutError)
+{
+ if (Payload.GetSize() > 0)
+ {
+ if (OutError = ValidateCompactBinary(Payload.GetView(), CbValidateMode::Default); OutError == CbValidateError::None)
+ {
+ CbObject Object(std::move(Payload));
+ if (Object.GetView().GetSize() != Payload.GetSize())
+ {
+ OutError |= CbValidateError::OutOfBounds;
+ return {};
+ }
+ return Object;
+ }
+ }
+ return CbObject();
+}
+
+CbObject
+ValidateAndReadCompactBinaryObject(const CompressedBuffer&& Payload, CbValidateError& OutError)
+{
+ if (CompositeBuffer Decompressed = Payload.DecompressToComposite())
+ {
+ return ValidateAndReadCompactBinaryObject(std::move(Decompressed).Flatten(), OutError);
+ }
+ return CbObject();
+}
+
+} // namespace zen
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index 8327838c9..d18f21dbe 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -1290,6 +1290,27 @@ MoveToFile(std::filesystem::path Path, IoBuffer Data)
zen::CreateDirectories(Path.parent_path());
Success = SetFileInformationByHandle(ChunkFileHandle, FileRenameInfo, RenameInfo, BufferSize);
}
+ if (!Success && (LastError == ERROR_ACCESS_DENIED))
+ {
+ // Fallback to regular rename
+ std::error_code Ec;
+ std::filesystem::path SourcePath = PathFromHandle(FileRef.FileHandle, Ec);
+ if (!Ec)
+ {
+ auto NativeSourcePath = SourcePath.native().c_str();
+ auto NativeTargetPath = Path.native().c_str();
+ Success = ::MoveFile(NativeSourcePath, NativeTargetPath);
+ if (!Success)
+ {
+ LastError = GetLastError();
+ if (LastError == ERROR_PATH_NOT_FOUND)
+ {
+ zen::CreateDirectories(Path.parent_path());
+ Success = ::MoveFile(NativeSourcePath, NativeTargetPath);
+ }
+ }
+ }
+ }
}
Memory::Free(RenameInfo);
if (!Success)
@@ -2439,26 +2460,28 @@ GetDirectoryContent(const std::filesystem::path& RootDir,
PendingWorkCount.AddCount(1);
try
{
- WorkerPool.ScheduleWork([WorkerPool = &WorkerPool,
- PendingWorkCount = &PendingWorkCount,
- Visitor = Visitor,
- Flags = Flags,
- Path = std::move(Path),
- RelativeRoot = RelativeRoot / DirectoryName]() {
- ZEN_ASSERT(Visitor);
- auto _ = MakeGuard([&]() { PendingWorkCount->CountDown(); });
- try
- {
- MultithreadedVisitor SubVisitor(*WorkerPool, *PendingWorkCount, RelativeRoot, Flags, Visitor);
- FileSystemTraversal Traversal;
- Traversal.TraverseFileSystem(Path, SubVisitor);
- Visitor->AsyncVisitDirectory(SubVisitor.RelativeRoot, std::move(SubVisitor.Content));
- }
- catch (const std::exception& Ex)
- {
- ZEN_ERROR("Failed scheduling work to scan subfolder '{}'. Reason: '{}'", Path / RelativeRoot, Ex.what());
- }
- });
+ WorkerPool.ScheduleWork(
+ [WorkerPool = &WorkerPool,
+ PendingWorkCount = &PendingWorkCount,
+ Visitor = Visitor,
+ Flags = Flags,
+ Path = std::move(Path),
+ RelativeRoot = RelativeRoot / DirectoryName]() {
+ ZEN_ASSERT(Visitor);
+ auto _ = MakeGuard([&]() { PendingWorkCount->CountDown(); });
+ try
+ {
+ MultithreadedVisitor SubVisitor(*WorkerPool, *PendingWorkCount, RelativeRoot, Flags, Visitor);
+ FileSystemTraversal Traversal;
+ Traversal.TraverseFileSystem(Path, SubVisitor);
+ Visitor->AsyncVisitDirectory(SubVisitor.RelativeRoot, std::move(SubVisitor.Content));
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_ERROR("Failed scheduling work to scan subfolder '{}'. Reason: '{}'", Path / RelativeRoot, Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
catch (const std::exception Ex)
{
@@ -3089,15 +3112,22 @@ TEST_CASE("filesystem")
{
virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t, uint32_t, uint64_t) override
{
- bFoundExpected |= std::filesystem::equivalent(Parent / File, Expected);
+ // std::filesystem::equivalent is *very* expensive on Windows, filter out unlikely candidates
+ if (ExpectedFilename == ToLower(std::filesystem::path(File).string()))
+ {
+ bFoundExpected |= std::filesystem::equivalent(Parent / File, Expected);
+ }
}
virtual bool VisitDirectory(const std::filesystem::path&, const path_view&, uint32_t) override { return true; }
- bool bFoundExpected = false;
+ bool bFoundExpected = false;
+
+ std::string ExpectedFilename;
std::filesystem::path Expected;
} Visitor;
- Visitor.Expected = BinPath;
+ Visitor.ExpectedFilename = ToLower(BinPath.filename().string());
+ Visitor.Expected = BinPath;
FileSystemTraversal().TraverseFileSystem(BinPath.parent_path().parent_path(), Visitor);
CHECK(Visitor.bFoundExpected);
diff --git a/src/zencore/include/zencore/callstack.h b/src/zencore/include/zencore/callstack.h
index ef4ba0e91..ca8171435 100644
--- a/src/zencore/include/zencore/callstack.h
+++ b/src/zencore/include/zencore/callstack.h
@@ -32,6 +32,18 @@ GetFrameSymbols(const CallstackFrames* Callstack)
void FormatCallstack(const CallstackFrames* Callstack, StringBuilderBase& SB, std::string_view Prefix);
std::string CallstackToString(const CallstackFrames* Callstack, std::string_view Prefix = {});
+typedef void (*CallstackRawCallback)(void* UserData, uint32_t FrameIndex, const char* FrameText);
+
+constexpr size_t
+CallstackRawMemorySize(int FramesToSkip, int FramesToCapture)
+{
+ return sizeof(CallstackFrames) + sizeof(void*) * (FramesToSkip + FramesToCapture);
+}
+
+void CallstackToStringRaw(const CallstackFrames* Callstack, void* CallbackUserData, CallstackRawCallback Callback);
+
+CallstackFrames* GetCallstackRaw(void* CaptureBuffer, int FramesToSkip, int FramesToCapture);
+
void callstack_forcelink(); // internal
} // namespace zen
diff --git a/src/zencore/include/zencore/compactbinaryutil.h b/src/zencore/include/zencore/compactbinaryutil.h
index 9524d1fc4..d750c6492 100644
--- a/src/zencore/include/zencore/compactbinaryutil.h
+++ b/src/zencore/include/zencore/compactbinaryutil.h
@@ -6,6 +6,7 @@
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryvalidation.h>
namespace zen {
@@ -43,4 +44,12 @@ RewriteCbObject(CbObjectView InObj, Invocable<CbObjectWriter&, CbFieldView&> aut
return Writer.Save();
}
+CbObject ValidateAndReadCompactBinaryObject(const SharedBuffer&& Payload, CbValidateError& OutError);
+inline CbObject
+ValidateAndReadCompactBinaryObject(const IoBuffer&& Payload, CbValidateError& OutError)
+{
+ return ValidateAndReadCompactBinaryObject(SharedBuffer(std::move(Payload)), OutError);
+}
+CbObject ValidateAndReadCompactBinaryObject(const CompressedBuffer&& Payload, CbValidateError& OutError);
+
} // namespace zen
diff --git a/src/zencore/include/zencore/except.h b/src/zencore/include/zencore/except.h
index 6810e6ea9..c933adfd8 100644
--- a/src/zencore/include/zencore/except.h
+++ b/src/zencore/include/zencore/except.h
@@ -63,7 +63,9 @@ MakeErrorCodeFromLastError() noexcept
class OptionParseException : public std::runtime_error
{
public:
- inline explicit OptionParseException(const std::string& Message) : std::runtime_error(Message) {}
+ // inline explicit OptionParseException(const std::string& Message) : std::runtime_error(Message) {}
+ inline OptionParseException(const std::string& Message, const std::string& Help) : std::runtime_error(Message), m_Help(Help) {}
+ const std::string m_Help;
};
bool IsOOM(const std::system_error& SystemError);
diff --git a/src/zencore/include/zencore/memory/newdelete.h b/src/zencore/include/zencore/memory/newdelete.h
index 059f1d5ea..2ec92b91b 100644
--- a/src/zencore/include/zencore/memory/newdelete.h
+++ b/src/zencore/include/zencore/memory/newdelete.h
@@ -156,26 +156,13 @@ operator new[](std::size_t n, std::align_val_t al, const std::nothrow_t&) noexce
// EASTL operator new
-void*
-operator new[](size_t size, const char* pName, int flags, unsigned debugFlags, const char* file, int line)
-{
- ZEN_UNUSED(pName, flags, debugFlags, file, line);
- return zen_new(size);
-}
-
-void*
-operator new[](size_t size,
- size_t alignment,
- size_t alignmentOffset,
- const char* pName,
- int flags,
- unsigned debugFlags,
- const char* file,
- int line)
-{
- ZEN_UNUSED(alignmentOffset, pName, flags, debugFlags, file, line);
-
- ZEN_ASSERT_SLOW(alignmentOffset == 0); // currently not supported
-
- return zen_new_aligned(size, alignment);
-}
+void* operator new[](size_t size, const char* pName, int flags, unsigned debugFlags, const char* file, int line);
+
+void* operator new[](size_t size,
+ size_t alignment,
+ size_t alignmentOffset,
+ const char* pName,
+ int flags,
+ unsigned debugFlags,
+ const char* file,
+ int line); \ No newline at end of file
diff --git a/src/zenutil/include/zenutil/parallelwork.h b/src/zencore/include/zencore/parallelwork.h
index 639c6968c..05146d644 100644
--- a/src/zenutil/include/zenutil/parallelwork.h
+++ b/src/zencore/include/zencore/parallelwork.h
@@ -13,7 +13,7 @@ namespace zen {
class ParallelWork
{
public:
- ParallelWork(std::atomic<bool>& AbortFlag, std::atomic<bool>& PauseFlag);
+ ParallelWork(std::atomic<bool>& AbortFlag, std::atomic<bool>& PauseFlag, WorkerThreadPool::EMode Mode);
~ParallelWork();
@@ -26,21 +26,23 @@ public:
m_PendingWork.AddCount(1);
try
{
- WorkerPool.ScheduleWork([this, Work = std::move(Work), OnError = OnError ? std::move(OnError) : DefaultErrorFunction()] {
- auto _ = MakeGuard([this]() { m_PendingWork.CountDown(); });
- try
- {
- while (m_PauseFlag && !m_AbortFlag)
+ WorkerPool.ScheduleWork(
+ [this, Work = std::move(Work), OnError = OnError ? std::move(OnError) : DefaultErrorFunction()] {
+ auto _ = MakeGuard([this]() { m_PendingWork.CountDown(); });
+ try
{
- Sleep(2000);
+ while (m_PauseFlag && !m_AbortFlag)
+ {
+ Sleep(2000);
+ }
+ Work(m_AbortFlag);
}
- Work(m_AbortFlag);
- }
- catch (...)
- {
- OnError(std::current_exception(), m_AbortFlag);
- }
- });
+ catch (...)
+ {
+ OnError(std::current_exception(), m_AbortFlag);
+ }
+ },
+ m_Mode);
}
catch (const std::exception&)
{
@@ -63,10 +65,11 @@ private:
ExceptionCallback DefaultErrorFunction();
void RethrowErrors();
- std::atomic<bool>& m_AbortFlag;
- std::atomic<bool>& m_PauseFlag;
- bool m_DispatchComplete = false;
- Latch m_PendingWork;
+ std::atomic<bool>& m_AbortFlag;
+ std::atomic<bool>& m_PauseFlag;
+ const WorkerThreadPool::EMode m_Mode;
+ bool m_DispatchComplete = false;
+ Latch m_PendingWork;
RwLock m_ErrorLock;
std::vector<std::exception_ptr> m_Errors;
diff --git a/src/zencore/include/zencore/process.h b/src/zencore/include/zencore/process.h
index 98d352db6..04b79a1e0 100644
--- a/src/zencore/include/zencore/process.h
+++ b/src/zencore/include/zencore/process.h
@@ -33,6 +33,7 @@ public:
ZENCORE_API bool Terminate(int ExitCode);
ZENCORE_API void Reset();
[[nodiscard]] inline int Pid() const { return m_Pid; }
+ [[nodiscard]] inline void* Handle() const { return m_ProcessHandle; }
private:
void* m_ProcessHandle = nullptr;
diff --git a/src/zencore/include/zencore/string.h b/src/zencore/include/zencore/string.h
index 68129b691..93f8add0a 100644
--- a/src/zencore/include/zencore/string.h
+++ b/src/zencore/include/zencore/string.h
@@ -679,6 +679,9 @@ ParseHexNumber(const std::string_view HexString, UnsignedIntegral auto& OutValue
return ParseHexNumber(HexString.data(), ExpectedCharacterCount, (uint8_t*)&OutValue);
}
+void UrlDecode(std::string_view InUrl, StringBuilderBase& OutUrl);
+std::string UrlDecode(std::string_view InUrl);
+
//////////////////////////////////////////////////////////////////////////
// Format numbers for humans
//
diff --git a/src/zencore/include/zencore/uid.h b/src/zencore/include/zencore/uid.h
index 64d3b6b9b..0c1079444 100644
--- a/src/zencore/include/zencore/uid.h
+++ b/src/zencore/include/zencore/uid.h
@@ -55,8 +55,8 @@ class StringBuilderBase;
struct Oid
{
- static const int StringLength = 24;
- typedef char String_t[StringLength + 1];
+ static constexpr int StringLength = 24;
+ typedef char String_t[StringLength + 1];
static void Initialize();
[[nodiscard]] static Oid NewOid();
diff --git a/src/zencore/include/zencore/workthreadpool.h b/src/zencore/include/zencore/workthreadpool.h
index 62356495c..4c38dd651 100644
--- a/src/zencore/include/zencore/workthreadpool.h
+++ b/src/zencore/include/zencore/workthreadpool.h
@@ -18,11 +18,7 @@ struct IWork : public RefCounted
{
virtual void Execute() = 0;
- inline std::exception_ptr GetException() { return m_Exception; }
-
private:
- std::exception_ptr m_Exception;
-
friend class WorkerThreadPool;
};
@@ -35,13 +31,18 @@ public:
WorkerThreadPool(int InThreadCount, std::string_view WorkerThreadBaseName);
~WorkerThreadPool();
- void ScheduleWork(Ref<IWork> Work);
- void ScheduleWork(std::function<void()>&& Work);
+ // Decides what to do if there are no free workers in the pool when the work is submitted
+ enum class EMode
+ {
+ EnableBacklog, // The work will be added to a backlog of work to do
+ DisableBacklog // The work will be executed synchronously in the caller thread
+ };
+
+ void ScheduleWork(Ref<IWork> Work, EMode Mode);
+ void ScheduleWork(std::function<void()>&& Work, EMode Mode);
template<typename Func>
- auto EnqueueTask(std::packaged_task<Func> Task);
-
- [[nodiscard]] size_t PendingWorkItemCount() const;
+ auto EnqueueTask(std::packaged_task<Func> Task, EMode Mode);
private:
struct Impl;
@@ -54,7 +55,7 @@ private:
template<typename Func>
auto
-WorkerThreadPool::EnqueueTask(std::packaged_task<Func> Task)
+WorkerThreadPool::EnqueueTask(std::packaged_task<Func> Task, EMode Mode)
{
struct FutureWork : IWork
{
@@ -67,7 +68,7 @@ WorkerThreadPool::EnqueueTask(std::packaged_task<Func> Task)
Ref<FutureWork> Work{new FutureWork(std::move(Task))};
auto Future = Work->m_Task.get_future();
- ScheduleWork(std::move(Work));
+ ScheduleWork(std::move(Work), Mode);
return Future;
}
diff --git a/src/zencore/include/zencore/zencore.h b/src/zencore/include/zencore/zencore.h
index d21c0e7e2..b5eb3e3e8 100644
--- a/src/zencore/include/zencore/zencore.h
+++ b/src/zencore/include/zencore/zencore.h
@@ -58,11 +58,11 @@ struct AssertImpl
[[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, const char* Msg);
virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION
- OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, CallstackFrames* Callstack);
+ OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, const CallstackFrames* Callstack);
protected:
static void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ThrowAssertException
- [[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, CallstackFrames* Callstack);
+ [[noreturn]] (const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, const CallstackFrames* Callstack);
static AssertImpl* CurrentAssertImpl;
static AssertImpl DefaultAssertImpl;
AssertImpl* NextAssertImpl = nullptr;
diff --git a/src/zencore/jobqueue.cpp b/src/zencore/jobqueue.cpp
index 5d727b69c..bd391909d 100644
--- a/src/zencore/jobqueue.cpp
+++ b/src/zencore/jobqueue.cpp
@@ -109,10 +109,12 @@ public:
WorkerCounter.AddCount(1);
try
{
- WorkerPool.ScheduleWork([&]() {
- auto _ = MakeGuard([&]() { WorkerCounter.CountDown(); });
- Worker();
- });
+ WorkerPool.ScheduleWork(
+ [&]() {
+ auto _ = MakeGuard([&]() { WorkerCounter.CountDown(); });
+ Worker();
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
return {.Id = NewJobId};
}
catch (const std::exception& Ex)
@@ -463,37 +465,39 @@ TEST_CASE("JobQueue")
std::unique_ptr<JobQueue> Queue(MakeJobQueue(2, "queue"));
WorkerThreadPool Pool(4);
Latch JobsLatch(1);
- for (uint32_t I = 0; I < 100; I++)
+ for (uint32_t I = 0; I < 32; I++)
{
JobsLatch.AddCount(1);
- Pool.ScheduleWork([&Queue, &JobsLatch, I]() {
- auto _ = MakeGuard([&JobsLatch]() { JobsLatch.CountDown(); });
- JobsLatch.AddCount(1);
- auto Id = Queue->QueueJob(fmt::format("busy {}", I), [&JobsLatch, I](JobContext& Context) {
- auto $ = MakeGuard([&JobsLatch]() { JobsLatch.CountDown(); });
- if (Context.IsCancelled())
- {
- return;
- }
- Context.ReportProgress("going to sleep", "", 100, 100);
- Sleep(10);
- if (Context.IsCancelled())
- {
- return;
- }
- Context.ReportProgress("going to sleep again", "", 100, 50);
- if ((I & 0xFF) == 0x10)
- {
- zen::ThrowSystemError(8, fmt::format("Job {} forced to fail", I));
- }
- Sleep(10);
- if (Context.IsCancelled())
- {
- return;
- }
- Context.ReportProgress("done", "", 100, 0);
- });
- });
+ Pool.ScheduleWork(
+ [&Queue, &JobsLatch, I]() {
+ auto _ = MakeGuard([&JobsLatch]() { JobsLatch.CountDown(); });
+ JobsLatch.AddCount(1);
+ auto Id = Queue->QueueJob(fmt::format("busy {}", I), [&JobsLatch, I](JobContext& Context) {
+ auto $ = MakeGuard([&JobsLatch]() { JobsLatch.CountDown(); });
+ if (Context.IsCancelled())
+ {
+ return;
+ }
+ Context.ReportProgress("going to sleep", "", 100, 100);
+ Sleep(5);
+ if (Context.IsCancelled())
+ {
+ return;
+ }
+ Context.ReportProgress("going to sleep again", "", 100, 50);
+ if ((I & 0xFF) == 0x10)
+ {
+ zen::ThrowSystemError(8, fmt::format("Job {} forced to fail", I));
+ }
+ Sleep(5);
+ if (Context.IsCancelled())
+ {
+ return;
+ }
+ Context.ReportProgress("done", "", 100, 0);
+ });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
auto Join = [](std::span<std::string> Strings, std::string_view Delimiter) -> std::string {
@@ -571,7 +575,7 @@ TEST_CASE("JobQueue")
RemainingJobs.size(),
PendingCount,
RemainingJobs.size() - PendingCount);
- Sleep(100);
+ Sleep(5);
}
JobsLatch.Wait();
}
diff --git a/src/zencore/logging.cpp b/src/zencore/logging.cpp
index 685c79d82..a6697c443 100644
--- a/src/zencore/logging.cpp
+++ b/src/zencore/logging.cpp
@@ -346,6 +346,8 @@ SetErrorLog(std::string_view NewErrorLoggerId)
}
}
+RwLock g_LoggerMutex;
+
LoggerRef
Get(std::string_view Name)
{
@@ -355,9 +357,16 @@ Get(std::string_view Name)
if (!Logger)
{
- Logger = Default().SpdLogger->clone(std::string(Name));
- spdlog::apply_logger_env_levels(Logger);
- spdlog::register_logger(Logger);
+ g_LoggerMutex.WithExclusiveLock([&] {
+ Logger = spdlog::get(std::string(Name));
+
+ if (!Logger)
+ {
+ Logger = Default().SpdLogger->clone(std::string(Name));
+ spdlog::apply_logger_env_levels(Logger);
+ spdlog::register_logger(Logger);
+ }
+ });
}
return *Logger;
diff --git a/src/zencore/memory/memory.cpp b/src/zencore/memory/memory.cpp
index ae1b9abce..fced2a4d3 100644
--- a/src/zencore/memory/memory.cpp
+++ b/src/zencore/memory/memory.cpp
@@ -284,3 +284,29 @@ zen_free_aligned(void* Ptr, size_t Alignment) noexcept
ZEN_UNUSED(Alignment);
zen::Memory::Free(Ptr);
}
+
+// EASTL operator new
+
+void*
+operator new[](size_t size, const char* pName, int flags, unsigned debugFlags, const char* file, int line)
+{
+ ZEN_UNUSED(pName, flags, debugFlags, file, line);
+ return zen_new(size);
+}
+
+void*
+operator new[](size_t size,
+ size_t alignment,
+ size_t alignmentOffset,
+ const char* pName,
+ int flags,
+ unsigned debugFlags,
+ const char* file,
+ int line)
+{
+ ZEN_UNUSED(alignmentOffset, pName, flags, debugFlags, file, line);
+
+ ZEN_ASSERT_SLOW(alignmentOffset == 0); // currently not supported
+
+ return zen_new_aligned(size, alignment);
+}
diff --git a/src/zenutil/parallelwork.cpp b/src/zencore/parallelwork.cpp
index a571d1d11..d86d5815f 100644
--- a/src/zenutil/parallelwork.cpp
+++ b/src/zencore/parallelwork.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/parallelwork.h>
+#include <zencore/parallelwork.h>
#include <zencore/callstack.h>
#include <zencore/except.h>
@@ -15,9 +15,10 @@
namespace zen {
-ParallelWork::ParallelWork(std::atomic<bool>& AbortFlag, std::atomic<bool>& PauseFlag)
+ParallelWork::ParallelWork(std::atomic<bool>& AbortFlag, std::atomic<bool>& PauseFlag, WorkerThreadPool::EMode Mode)
: m_AbortFlag(AbortFlag)
, m_PauseFlag(PauseFlag)
+, m_Mode(Mode)
, m_PendingWork(1)
{
}
@@ -160,7 +161,7 @@ TEST_CASE("parallellwork.nowork")
{
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
Work.Wait();
}
@@ -170,7 +171,7 @@ TEST_CASE("parallellwork.basic")
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
for (uint32_t I = 0; I < 5; I++)
{
Work.ScheduleWork(WorkerPool, [](std::atomic<bool>& AbortFlag) { CHECK(!AbortFlag); });
@@ -184,7 +185,7 @@ TEST_CASE("parallellwork.throws_in_work")
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
for (uint32_t I = 0; I < 10; I++)
{
Work.ScheduleWork(WorkerPool, [I](std::atomic<bool>& AbortFlag) {
@@ -210,7 +211,7 @@ TEST_CASE("parallellwork.throws_in_dispatch")
{
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
for (uint32_t I = 0; I < 5; I++)
{
Work.ScheduleWork(WorkerPool, [I, &ExecutedCount](std::atomic<bool>& AbortFlag) {
@@ -234,6 +235,26 @@ TEST_CASE("parallellwork.throws_in_dispatch")
}
}
+TEST_CASE("parallellwork.limitqueue")
+{
+ WorkerThreadPool WorkerPool(2);
+
+ std::atomic<bool> AbortFlag;
+ std::atomic<bool> PauseFlag;
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
+ for (uint32_t I = 0; I < 5; I++)
+ {
+ Work.ScheduleWork(WorkerPool, [](std::atomic<bool>& AbortFlag) {
+ if (AbortFlag.load())
+ {
+ return;
+ }
+ Sleep(10);
+ });
+ }
+ Work.Wait();
+}
+
void
parallellwork_forcelink()
{
diff --git a/src/zencore/sentryintegration.cpp b/src/zencore/sentryintegration.cpp
index 118c4158a..00e67dc85 100644
--- a/src/zencore/sentryintegration.cpp
+++ b/src/zencore/sentryintegration.cpp
@@ -32,13 +32,16 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace sentry {
namespace {
- static const std::string DefaultDsn("https://[email protected]/5919284");
+ static const std::string DefaultDsn("https://[email protected]/5919284");
}
struct SentryAssertImpl : zen::AssertImpl
{
- virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION
- OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, zen::CallstackFrames* Callstack) override;
+ virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION OnAssert(const char* Filename,
+ int LineNumber,
+ const char* FunctionName,
+ const char* Msg,
+ const zen::CallstackFrames* Callstack) override;
};
class sentry_sink final : public spdlog::sinks::base_sink<spdlog::details::null_mutex>
@@ -107,7 +110,11 @@ sentry_sink::flush_()
}
void
-SentryAssertImpl::OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, zen::CallstackFrames* Callstack)
+SentryAssertImpl::OnAssert(const char* Filename,
+ int LineNumber,
+ const char* FunctionName,
+ const char* Msg,
+ const zen::CallstackFrames* Callstack)
{
// Sentry will provide its own callstack
ZEN_UNUSED(Callstack);
diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp
index a0d8c927f..c8c7c2cde 100644
--- a/src/zencore/string.cpp
+++ b/src/zencore/string.cpp
@@ -483,12 +483,67 @@ template class StringBuilderImpl<char>;
template class StringBuilderImpl<wchar_t>;
//////////////////////////////////////////////////////////////////////////
+
+void
+UrlDecode(std::string_view InUrl, StringBuilderBase& OutUrl)
+{
+ std::string_view::size_type i = 0;
+
+ for (; i != InUrl.size();)
+ {
+ char c = InUrl[i];
+
+ if ((c == '%') && ((i + 2) < InUrl.size()))
+ {
+ char hex[2] = {InUrl[i + 1], InUrl[i + 2]};
+ uint8_t HexedChar;
+ if (ParseHexBytes(hex, 2, &HexedChar))
+ {
+ OutUrl.Append(HexedChar);
+ i += 3;
+
+ continue;
+ }
+ }
+
+ OutUrl.Append(c);
+ ++i;
+ }
+}
+
+std::string
+UrlDecode(std::string_view InUrl)
+{
+ ExtendableStringBuilder<128> Url;
+ UrlDecode(InUrl, Url);
+
+ return std::string(Url.ToView());
+}
+
+//////////////////////////////////////////////////////////////////////////
//
// Unit tests
//
#if ZEN_WITH_TESTS
+TEST_CASE("url")
+{
+ using namespace std::literals;
+
+ ExtendableStringBuilder<32> OutUrl;
+ UrlDecode("http://blah.com/foo?bar=hi%20ho", OutUrl);
+ CHECK_EQ(OutUrl.ToView(), "http://blah.com/foo?bar=hi ho"sv);
+
+ OutUrl.Reset();
+
+ UrlDecode("http://blah.com/foo?bar=hi%ho", OutUrl);
+ CHECK_EQ(OutUrl.ToView(), "http://blah.com/foo?bar=hi%ho"sv);
+
+ CHECK_EQ(UrlDecode("http://blah.com/foo?bar=hi%20ho"), "http://blah.com/foo?bar=hi ho"sv);
+ CHECK_EQ(UrlDecode("http://blah.com/foo?bar=hi%ho"), "http://blah.com/foo?bar=hi%ho"sv);
+}
+
TEST_CASE("niceNum")
{
char Buffer[16];
diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp
index b8ec85a4a..abf282467 100644
--- a/src/zencore/thread.cpp
+++ b/src/zencore/thread.cpp
@@ -608,12 +608,12 @@ TEST_CASE("NamedEvent")
ReadyEvent.Set();
NamedEvent TestEvent(Name);
- TestEvent.Wait(1000);
+ TestEvent.Wait(100);
});
ReadyEvent.Wait();
- zen::Sleep(100);
+ zen::Sleep(10);
TestEvent.Set();
Waiter.join();
@@ -621,7 +621,7 @@ TEST_CASE("NamedEvent")
// Manual reset property
for (uint32_t i = 0; i < 8; ++i)
{
- bool bEventSet = TestEvent.Wait(100);
+ bool bEventSet = TestEvent.Wait(10);
CHECK(bEventSet);
}
}
diff --git a/src/zencore/workthreadpool.cpp b/src/zencore/workthreadpool.cpp
index 445fe939e..e241c0de8 100644
--- a/src/zencore/workthreadpool.cpp
+++ b/src/zencore/workthreadpool.cpp
@@ -5,6 +5,7 @@
#include <zencore/blockingqueue.h>
#include <zencore/except.h>
#include <zencore/logging.h>
+#include <zencore/scopeguard.h>
#include <zencore/string.h>
#include <zencore/testing.h>
#include <zencore/thread.h>
@@ -13,6 +14,10 @@
#include <thread>
#include <vector>
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <gsl/gsl-lite.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
#define ZEN_USE_WINDOWS_THREADPOOL 1
#if ZEN_PLATFORM_WINDOWS && ZEN_USE_WINDOWS_THREADPOOL
@@ -41,18 +46,23 @@ namespace {
struct WorkerThreadPool::Impl
{
+ const int m_ThreadCount = 0;
PTP_POOL m_ThreadPool = nullptr;
PTP_CLEANUP_GROUP m_CleanupGroup = nullptr;
TP_CALLBACK_ENVIRON m_CallbackEnvironment;
PTP_WORK m_Work = nullptr;
- std::string m_WorkerThreadBaseName;
- std::atomic<int> m_WorkerThreadCounter{0};
+ std::string m_WorkerThreadBaseName;
+ std::atomic<size_t> m_WorkerThreadCounter{0};
+ std::atomic<int> m_FreeWorkerCount{0};
- RwLock m_QueueLock;
+ mutable RwLock m_QueueLock;
std::deque<Ref<IWork>> m_WorkQueue;
- Impl(int InThreadCount, std::string_view WorkerThreadBaseName) : m_WorkerThreadBaseName(WorkerThreadBaseName)
+ Impl(int InThreadCount, std::string_view WorkerThreadBaseName)
+ : m_ThreadCount(InThreadCount)
+ , m_WorkerThreadBaseName(WorkerThreadBaseName)
+ , m_FreeWorkerCount(m_ThreadCount)
{
// Thread pool setup
@@ -62,11 +72,11 @@ struct WorkerThreadPool::Impl
ThrowLastError("CreateThreadpool failed");
}
- if (!SetThreadpoolThreadMinimum(m_ThreadPool, InThreadCount))
+ if (!SetThreadpoolThreadMinimum(m_ThreadPool, (DWORD)m_ThreadCount))
{
ThrowLastError("SetThreadpoolThreadMinimum failed");
}
- SetThreadpoolThreadMaximum(m_ThreadPool, InThreadCount * 2);
+ SetThreadpoolThreadMaximum(m_ThreadPool, (DWORD)m_ThreadCount);
InitializeThreadpoolEnvironment(&m_CallbackEnvironment);
@@ -93,12 +103,29 @@ struct WorkerThreadPool::Impl
CloseThreadpool(m_ThreadPool);
}
- void ScheduleWork(Ref<IWork> Work)
+ [[nodiscard]] Ref<IWork> ScheduleWork(Ref<IWork> Work, WorkerThreadPool::EMode Mode)
{
- m_QueueLock.WithExclusiveLock([&] { m_WorkQueue.push_back(std::move(Work)); });
+ if (Mode == WorkerThreadPool::EMode::DisableBacklog)
+ {
+ if (m_FreeWorkerCount <= 0)
+ {
+ return Work;
+ }
+ RwLock::ExclusiveLockScope _(m_QueueLock);
+ const int QueuedCount = gsl::narrow<int>(m_WorkQueue.size());
+ if (QueuedCount >= m_FreeWorkerCount)
+ {
+ return Work;
+ }
+ m_WorkQueue.push_back(std::move(Work));
+ }
+ else
+ {
+ m_QueueLock.WithExclusiveLock([&] { m_WorkQueue.push_back(std::move(Work)); });
+ }
SubmitThreadpoolWork(m_Work);
+ return {};
}
- [[nodiscard]] size_t PendingWorkItemCount() const { return 0; }
static VOID CALLBACK WorkCallback(_Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_ PTP_WORK Work)
{
@@ -109,10 +136,13 @@ struct WorkerThreadPool::Impl
void DoWork()
{
+ m_FreeWorkerCount--;
+ auto _ = MakeGuard([&]() { m_FreeWorkerCount++; });
+
if (!t_IsThreadNamed)
{
t_IsThreadNamed = true;
- const int ThreadIndex = ++m_WorkerThreadCounter;
+ const size_t ThreadIndex = ++m_WorkerThreadCounter;
zen::ExtendableStringBuilder<128> ThreadName;
ThreadName << m_WorkerThreadBaseName << "_" << ThreadIndex;
SetCurrentThreadName(ThreadName);
@@ -121,7 +151,7 @@ struct WorkerThreadPool::Impl
Ref<IWork> WorkFromQueue;
{
- RwLock::ExclusiveLockScope _{m_QueueLock};
+ RwLock::ExclusiveLockScope __{m_QueueLock};
WorkFromQueue = std::move(m_WorkQueue.front());
m_WorkQueue.pop_front();
}
@@ -141,20 +171,25 @@ struct WorkerThreadPool::ThreadStartInfo
struct WorkerThreadPool::Impl
{
+ const int m_ThreadCount = 0;
void WorkerThreadFunction(ThreadStartInfo Info);
std::string m_WorkerThreadBaseName;
std::vector<std::thread> m_WorkerThreads;
BlockingQueue<Ref<IWork>> m_WorkQueue;
+ std::atomic<int> m_FreeWorkerCount{0};
- Impl(int InThreadCount, std::string_view WorkerThreadBaseName) : m_WorkerThreadBaseName(WorkerThreadBaseName)
+ Impl(int InThreadCount, std::string_view WorkerThreadBaseName)
+ : m_ThreadCount(InThreadCount)
+ , m_WorkerThreadBaseName(WorkerThreadBaseName)
+ , m_FreeWorkerCount(m_ThreadCount)
{
# if ZEN_WITH_TRACE
trace::ThreadGroupBegin(m_WorkerThreadBaseName.c_str());
# endif
- zen::Latch WorkerLatch{InThreadCount};
+ zen::Latch WorkerLatch{m_ThreadCount};
- for (int i = 0; i < InThreadCount; ++i)
+ for (int i = 0; i < m_ThreadCount; ++i)
{
m_WorkerThreads.emplace_back(&Impl::WorkerThreadFunction, this, ThreadStartInfo{i + 1, &WorkerLatch});
}
@@ -181,8 +216,23 @@ struct WorkerThreadPool::Impl
m_WorkerThreads.clear();
}
- void ScheduleWork(Ref<IWork> Work) { m_WorkQueue.Enqueue(std::move(Work)); }
- [[nodiscard]] size_t PendingWorkItemCount() const { return m_WorkQueue.Size(); }
+ [[nodiscard]] Ref<IWork> ScheduleWork(Ref<IWork> Work, WorkerThreadPool::EMode Mode)
+ {
+ if (Mode == WorkerThreadPool::EMode::DisableBacklog)
+ {
+ if (m_FreeWorkerCount <= 0)
+ {
+ return Work;
+ }
+ const int QueuedCount = gsl::narrow<int>(m_WorkQueue.Size());
+ if (QueuedCount >= m_FreeWorkerCount)
+ {
+ return Work;
+ }
+ }
+ m_WorkQueue.Enqueue(std::move(Work));
+ return {};
+ }
};
void
@@ -197,21 +247,23 @@ WorkerThreadPool::Impl::WorkerThreadFunction(ThreadStartInfo Info)
Ref<IWork> Work;
if (m_WorkQueue.WaitAndDequeue(Work))
{
+ m_FreeWorkerCount--;
+ auto _ = MakeGuard([&]() { m_FreeWorkerCount++; });
+
try
{
ZEN_TRACE_CPU_FLUSH("AsyncWork");
Work->Execute();
+ Work = {};
}
catch (const AssertException& Ex)
{
- Work->m_Exception = std::current_exception();
-
+ Work = {};
ZEN_WARN("Assert exception in worker thread: {}", Ex.FullDescription());
}
catch (const std::exception& e)
{
- Work->m_Exception = std::current_exception();
-
+ Work = {};
ZEN_WARN("Caught exception in worker thread: {}", e.what());
}
}
@@ -243,48 +295,38 @@ WorkerThreadPool::~WorkerThreadPool()
}
void
-WorkerThreadPool::ScheduleWork(Ref<IWork> Work)
+WorkerThreadPool::ScheduleWork(Ref<IWork> Work, EMode Mode)
{
if (m_Impl)
{
- m_Impl->ScheduleWork(std::move(Work));
- }
- else
- {
- try
+ if (Work = m_Impl->ScheduleWork(std::move(Work), Mode); !Work)
{
- ZEN_TRACE_CPU_FLUSH("SyncWork");
- Work->Execute();
- }
- catch (const AssertException& Ex)
- {
- Work->m_Exception = std::current_exception();
-
- ZEN_WARN("Assert exception in worker thread: {}", Ex.FullDescription());
+ return;
}
- catch (const std::exception& e)
- {
- Work->m_Exception = std::current_exception();
+ }
- ZEN_WARN("Caught exception when executing worker synchronously: {}", e.what());
- }
+ try
+ {
+ ZEN_TRACE_CPU_FLUSH("SyncWork");
+ Work->Execute();
+ Work = {};
+ }
+ catch (const AssertException& Ex)
+ {
+ Work = {};
+ ZEN_WARN("Assert exception in worker thread: {}", Ex.FullDescription());
+ }
+ catch (const std::exception& e)
+ {
+ Work = {};
+ ZEN_WARN("Caught exception when executing worker synchronously: {}", e.what());
}
}
void
-WorkerThreadPool::ScheduleWork(std::function<void()>&& Work)
-{
- ScheduleWork(Ref<IWork>(new detail::LambdaWork(std::move(Work))));
-}
-
-[[nodiscard]] size_t
-WorkerThreadPool::PendingWorkItemCount() const
+WorkerThreadPool::ScheduleWork(std::function<void()>&& Work, EMode Mode)
{
- if (m_Impl)
- {
- return m_Impl->PendingWorkItemCount();
- }
- return 0;
+ ScheduleWork(Ref<IWork>(new detail::LambdaWork(std::move(Work))), Mode);
}
//////////////////////////////////////////////////////////////////////////
@@ -302,9 +344,10 @@ TEST_CASE("threadpool.basic")
{
WorkerThreadPool Threadpool{1};
- auto Future42 = Threadpool.EnqueueTask(std::packaged_task<int()>{[] { return 42; }});
- auto Future99 = Threadpool.EnqueueTask(std::packaged_task<int()>{[] { return 99; }});
- auto FutureThrow = Threadpool.EnqueueTask(std::packaged_task<void()>{[] { throw std::runtime_error("meep!"); }});
+ auto Future42 = Threadpool.EnqueueTask(std::packaged_task<int()>{[] { return 42; }}, WorkerThreadPool::EMode::EnableBacklog);
+ auto Future99 = Threadpool.EnqueueTask(std::packaged_task<int()>{[] { return 99; }}, WorkerThreadPool::EMode::EnableBacklog);
+ auto FutureThrow = Threadpool.EnqueueTask(std::packaged_task<void()>{[] { throw std::runtime_error("meep!"); }},
+ WorkerThreadPool::EMode::EnableBacklog);
CHECK_EQ(Future42.get(), 42);
CHECK_EQ(Future99.get(), 99);
diff --git a/src/zencore/zencore.cpp b/src/zencore/zencore.cpp
index 51e06ae14..b78991918 100644
--- a/src/zencore/zencore.cpp
+++ b/src/zencore/zencore.cpp
@@ -23,6 +23,7 @@
#include <zencore/logging.h>
#include <zencore/memoryview.h>
#include <zencore/mpscqueue.h>
+#include <zencore/parallelwork.h>
#include <zencore/process.h>
#include <zencore/sha1.h>
#include <zencore/stats.h>
@@ -116,10 +117,10 @@ AssertImpl::~AssertImpl()
void
AssertImpl::ExecAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg)
{
- void* Frames[8];
- uint32_t FrameCount = GetCallstack(2, 8, Frames);
-
- CallstackFrames* Callstack = CreateCallstack(FrameCount, Frames);
+ constexpr int SkipFrameCount = 2;
+ constexpr int FrameCount = 8;
+ uint8_t CallstackBuffer[CallstackRawMemorySize(SkipFrameCount, FrameCount)];
+ CallstackFrames* Callstack = GetCallstackRaw(&CallstackBuffer[0], SkipFrameCount, FrameCount);
AssertImpl* AssertImpl = CurrentAssertImpl;
while (AssertImpl)
@@ -137,7 +138,7 @@ AssertImpl::ExecAssert(const char* Filename, int LineNumber, const char* Functio
ThrowAssertException(Filename, LineNumber, FunctionName, Msg, Callstack);
}
void
-AssertImpl::OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, CallstackFrames* Callstack)
+AssertImpl::OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, const CallstackFrames* Callstack)
{
ZEN_UNUSED(FunctionName);
@@ -152,11 +153,11 @@ AssertImpl::OnAssert(const char* Filename, int LineNumber, const char* FunctionN
}
void
-AssertImpl::ThrowAssertException(const char* Filename,
- int LineNumber,
- const char* FunctionName,
- const char* Msg,
- CallstackFrames* Callstack)
+AssertImpl::ThrowAssertException(const char* Filename,
+ int LineNumber,
+ const char* FunctionName,
+ const char* Msg,
+ const CallstackFrames* Callstack)
{
ZEN_UNUSED(FunctionName);
fmt::basic_memory_buffer<char, 2048> Message;
@@ -164,7 +165,7 @@ AssertImpl::ThrowAssertException(const char* Filename,
fmt::format_to(Appender, "{}({}): {}", Filename, LineNumber, Msg);
Message.push_back('\0');
- throw AssertException(Message.data(), Callstack);
+ throw AssertException(Message.data(), CloneCallstack(Callstack));
}
void refcount_forcelink();
@@ -262,6 +263,7 @@ zencore_forcelinktests()
zen::logging_forcelink();
zen::memory_forcelink();
zen::mpscqueue_forcelink();
+ zen::parallellwork_forcelink();
zen::process_forcelink();
zen::refcount_forcelink();
zen::sha1_forcelink();
@@ -308,7 +310,7 @@ TEST_CASE("Assert.Custom")
struct MyAssertImpl : AssertImpl
{
virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION
- OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, CallstackFrames* Callstack)
+ OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, const CallstackFrames* Callstack)
{
ZEN_UNUSED(Callstack);
AssertFileName = Filename;
@@ -350,9 +352,10 @@ TEST_CASE("Assert.Callstack")
WorkerThreadPool Pool(1);
auto Task = Pool.EnqueueTask(std::packaged_task<int()>{[] {
- ZEN_ASSERT(false);
- return 1;
- }});
+ ZEN_ASSERT(false);
+ return 1;
+ }},
+ WorkerThreadPool::EMode::EnableBacklog);
try
{
diff --git a/src/zenhttp-test/xmake.lua b/src/zenhttp-test/xmake.lua
index a6163e506..a7ee731f9 100644
--- a/src/zenhttp-test/xmake.lua
+++ b/src/zenhttp-test/xmake.lua
@@ -5,7 +5,7 @@ target("zenhttp-test")
set_group("tests")
add_headerfiles("**.h")
add_files("*.cpp")
- add_deps("zenhttp", "zencore")
+ add_deps("zenhttp")
add_packages("vcpkg::doctest")
if is_plat("macosx") then
diff --git a/src/zenhttp-test/zenhttp-test.cpp b/src/zenhttp-test/zenhttp-test.cpp
index 116971b02..d18b2167e 100644
--- a/src/zenhttp-test/zenhttp-test.cpp
+++ b/src/zenhttp-test/zenhttp-test.cpp
@@ -22,7 +22,15 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
zen::IgnoreChildSignals();
# endif
+# if ZEN_WITH_TRACE
zen::TraceInit("zenhttp-test");
+ zen::TraceOptions TraceCommandlineOptions;
+ if (GetTraceOptionsFromCommandline(TraceCommandlineOptions))
+ {
+ TraceConfigure(TraceCommandlineOptions);
+ }
+# endif // ZEN_WITH_TRACE
+
zen::logging::InitializeLogging();
zen::MaximizeOpenFileCount();
diff --git a/src/zenhttp/auth/authmgr.cpp b/src/zenhttp/auth/authmgr.cpp
index 6c1a66a99..209276621 100644
--- a/src/zenhttp/auth/authmgr.cpp
+++ b/src/zenhttp/auth/authmgr.cpp
@@ -5,7 +5,7 @@
#include <zencore/basicfile.h>
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
-#include <zencore/compactbinaryvalidation.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/crypto.h>
#include <zencore/filesystem.h>
#include <zencore/logging.h>
@@ -297,15 +297,14 @@ private:
return;
}
- const CbValidateError ValidationError = ValidateCompactBinary(Buffer, CbValidateMode::All);
-
- if (ValidationError != CbValidateError::None)
+ CbValidateError ValidationError;
+ if (CbObject AuthState = ValidateAndReadCompactBinaryObject(std::move(Buffer), ValidationError);
+ ValidationError != CbValidateError::None)
{
- ZEN_WARN("load serialized state FAILED, reason 'Invalid compact binary'");
+ ZEN_WARN("load serialized state FAILED, reason '{}'", ToString(ValidationError));
return;
}
-
- if (CbObject AuthState = LoadCompactBinaryObject(Buffer))
+ else
{
for (CbFieldView ProviderView : AuthState["OpenIdProviders"sv])
{
diff --git a/src/zenhttp/auth/oidc.cpp b/src/zenhttp/auth/oidc.cpp
index 318110c7d..38e7586ad 100644
--- a/src/zenhttp/auth/oidc.cpp
+++ b/src/zenhttp/auth/oidc.cpp
@@ -1,9 +1,9 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "zenhttp/auth/oidc.h"
+#include <zenhttp/httpclient.h>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
#include <fmt/format.h>
#include <json11.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -41,27 +41,21 @@ OidcClient::OidcClient(const OidcClient::Options& Options)
OidcClient::InitResult
OidcClient::Initialize()
{
- ExtendableStringBuilder<256> Uri;
- Uri << m_BaseUrl << "/.well-known/openid-configuration"sv;
+ HttpClient Http{m_BaseUrl};
+ HttpClient::Response Response = Http.Get("/.well-known/openid-configuration"sv);
- cpr::Session Session;
-
- Session.SetOption(cpr::Url{Uri.c_str()});
-
- cpr::Response Response = Session.Get();
-
- if (Response.error)
+ if (!Response)
{
- return {.Reason = std::move(Response.error.message)};
+ return {.Reason = Response.ErrorMessage("")};
}
- if (Response.status_code != 200)
+ if (Response.StatusCode != HttpResponseCode::OK)
{
- return {.Reason = std::move(Response.reason)};
+ return {.Reason = std::string{ToString(Response.StatusCode)}};
}
std::string JsonError;
- json11::Json Json = json11::Json::parse(Response.text, JsonError);
+ json11::Json Json = json11::Json::parse(std::string{Response.AsText()}, JsonError);
if (JsonError.empty() == false)
{
@@ -89,26 +83,24 @@ OidcClient::RefreshToken(std::string_view RefreshToken)
{
const std::string Body = fmt::format("grant_type=refresh_token&refresh_token={}&client_id={}", RefreshToken, m_ClientId);
- cpr::Session Session;
+ HttpClient Http{m_Config.TokenEndpoint};
- Session.SetOption(cpr::Url{m_Config.TokenEndpoint.c_str()});
- Session.SetOption(cpr::Header{{"Content-Type", "application/x-www-form-urlencoded"}});
- Session.SetBody(cpr::Body{Body.data(), Body.size()});
+ HttpClient::KeyValueMap Headers{{"Content-Type", "application/x-www-form-urlencoded"}};
- cpr::Response Response = Session.Post();
+ HttpClient::Response Response = Http.Post("", IoBufferBuilder::MakeFromMemory(MemoryView{Body.data(), Body.size()}), Headers);
- if (Response.error)
+ if (!Response)
{
- return {.Reason = std::move(Response.error.message)};
+ return {.Reason = std::string{Response.ErrorMessage("")}};
}
- if (Response.status_code != 200)
+ if (Response.StatusCode != HttpResponseCode::OK)
{
- return {.Reason = fmt::format("{} ({})", Response.reason, Response.text)};
+ return {.Reason = fmt::format("{} ({})", ToString(Response.StatusCode), Response.AsText())};
}
std::string JsonError;
- json11::Json Json = json11::Json::parse(Response.text, JsonError);
+ json11::Json Json = json11::Json::parse(std::string{Response.AsText()}, JsonError);
if (JsonError.empty() == false)
{
diff --git a/src/zenhttp/clients/httpclientcommon.cpp b/src/zenhttp/clients/httpclientcommon.cpp
new file mode 100644
index 000000000..8e5136dff
--- /dev/null
+++ b/src/zenhttp/clients/httpclientcommon.cpp
@@ -0,0 +1,474 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "httpclientcommon.h"
+
+#include <fmt/format.h>
+#include <zencore/except.h>
+#include <zencore/filesystem.h>
+#include <zencore/iohash.h>
+#include <zencore/logging.h>
+#include <zencore/memory/memory.h>
+#include <zencore/windows.h>
+#include <gsl/gsl-lite.hpp>
+
+#if ZEN_WITH_TESTS
+# include <zencore/basicfile.h>
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+#endif // ZEN_WITH_TESTS
+
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+# include <fcntl.h>
+# include <sys/stat.h>
+# include <unistd.h>
+#endif
+
+namespace zen {
+
+using namespace std::literals;
+
+namespace detail {
+
+ static std::atomic_uint32_t TempFileBaseIndex;
+
+ TempPayloadFile::TempPayloadFile() : m_FileHandle(nullptr), m_WriteOffset(0) {}
+ TempPayloadFile::~TempPayloadFile()
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::Close");
+ try
+ {
+ if (m_FileHandle)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ // Mark file for deletion when final handle is closed
+ FILE_DISPOSITION_INFO Fdi{.DeleteFile = TRUE};
+
+ SetFileInformationByHandle(m_FileHandle, FileDispositionInfo, &Fdi, sizeof Fdi);
+ BOOL Success = CloseHandle(m_FileHandle);
+#else
+ std::error_code Ec;
+ std::filesystem::path FilePath = zen::PathFromHandle(m_FileHandle, Ec);
+ if (Ec)
+ {
+ ZEN_WARN("Error reported on get file path from handle {} for temp payload unlink operation, reason '{}'",
+ m_FileHandle,
+ Ec.message());
+ }
+ else
+ {
+ unlink(FilePath.c_str());
+ }
+ int Fd = int(uintptr_t(m_FileHandle));
+ bool Success = (close(Fd) == 0);
+#endif
+ if (!Success)
+ {
+ ZEN_WARN("Error reported on file handle close, reason '{}'", GetLastErrorAsString());
+ }
+
+ m_FileHandle = nullptr;
+ }
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_ERROR("Failed deleting temp file {}. Reason '{}'", m_FileHandle, Ex.what());
+ }
+ }
+
+ std::error_code TempPayloadFile::Open(const std::filesystem::path& TempFolderPath, uint64_t FinalSize)
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::Open");
+ ZEN_ASSERT(m_FileHandle == nullptr);
+
+ std::uint64_t TmpIndex = ((std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) & 0xffffffffu) << 32) |
+ detail::TempFileBaseIndex.fetch_add(1);
+
+ std::filesystem::path FileName = TempFolderPath / fmt::to_string(TmpIndex);
+#if ZEN_PLATFORM_WINDOWS
+ LPCWSTR lpFileName = FileName.c_str();
+ const DWORD dwDesiredAccess = (GENERIC_READ | GENERIC_WRITE | DELETE);
+ const DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr;
+ const DWORD dwCreationDisposition = CREATE_ALWAYS;
+ const DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
+ const HANDLE hTemplateFile = nullptr;
+ const HANDLE FileHandle = CreateFile(lpFileName,
+ dwDesiredAccess,
+ dwShareMode,
+ lpSecurityAttributes,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ hTemplateFile);
+
+ if (FileHandle == INVALID_HANDLE_VALUE)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+#else // ZEN_PLATFORM_WINDOWS
+ int OpenFlags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC;
+ int Fd = open(FileName.c_str(), OpenFlags, 0666);
+ if (Fd < 0)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+ fchmod(Fd, 0666);
+
+ void* FileHandle = (void*)(uintptr_t(Fd));
+#endif // ZEN_PLATFORM_WINDOWS
+ m_FileHandle = FileHandle;
+
+ PrepareFileForScatteredWrite(m_FileHandle, FinalSize);
+
+ return {};
+ }
+
+ std::error_code TempPayloadFile::Write(std::string_view DataString)
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::Write");
+ const uint8_t* DataPtr = (const uint8_t*)DataString.data();
+ size_t DataSize = DataString.size();
+ if (DataSize >= CacheBufferSize)
+ {
+ std::error_code Ec = Flush();
+ if (Ec)
+ {
+ return Ec;
+ }
+ return AppendData(DataPtr, DataSize);
+ }
+ size_t CopySize = Min(DataSize, CacheBufferSize - m_CacheBufferOffset);
+ memcpy(&m_CacheBuffer[m_CacheBufferOffset], DataPtr, CopySize);
+ m_CacheBufferOffset += CopySize;
+ DataSize -= CopySize;
+ if (m_CacheBufferOffset == CacheBufferSize)
+ {
+ AppendData(m_CacheBuffer, CacheBufferSize);
+ if (DataSize > 0)
+ {
+ ZEN_ASSERT(DataSize < CacheBufferSize);
+ memcpy(m_CacheBuffer, DataPtr + CopySize, DataSize);
+ }
+ m_CacheBufferOffset = DataSize;
+ }
+ else
+ {
+ ZEN_ASSERT(DataSize == 0);
+ }
+ return {};
+ }
+
+ IoBuffer TempPayloadFile::DetachToIoBuffer()
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::DetachToIoBuffer");
+ if (std::error_code Ec = Flush(); Ec)
+ {
+ ThrowSystemError(Ec.value(), Ec.message());
+ }
+ ZEN_ASSERT(m_FileHandle != nullptr);
+ void* FileHandle = m_FileHandle;
+ IoBuffer Buffer(IoBuffer::File, FileHandle, 0, m_WriteOffset, /*IsWholeFile*/ true);
+ Buffer.SetDeleteOnClose(true);
+ m_FileHandle = 0;
+ m_WriteOffset = 0;
+ return Buffer;
+ }
+
+ IoBuffer TempPayloadFile::BorrowIoBuffer()
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::BorrowIoBuffer");
+ if (std::error_code Ec = Flush(); Ec)
+ {
+ ThrowSystemError(Ec.value(), Ec.message());
+ }
+ ZEN_ASSERT(m_FileHandle != nullptr);
+ void* FileHandle = m_FileHandle;
+ IoBuffer Buffer(IoBuffer::BorrowedFile, FileHandle, 0, m_WriteOffset);
+ return Buffer;
+ }
+
+ void TempPayloadFile::ResetWritePos(uint64_t WriteOffset)
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::ResetWritePos");
+ Flush();
+ m_WriteOffset = WriteOffset;
+ }
+
+ std::error_code TempPayloadFile::Flush()
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::Flush");
+ if (m_CacheBufferOffset == 0)
+ {
+ return {};
+ }
+ std::error_code Res = AppendData(m_CacheBuffer, m_CacheBufferOffset);
+ m_CacheBufferOffset = 0;
+ return Res;
+ }
+
+ std::error_code TempPayloadFile::AppendData(const void* Data, uint64_t Size)
+ {
+ ZEN_TRACE_CPU("TempPayloadFile::AppendData");
+ ZEN_ASSERT(m_FileHandle != nullptr);
+ const uint64_t MaxChunkSize = 2u * 1024 * 1024 * 1024;
+
+ while (Size)
+ {
+ const uint64_t NumberOfBytesToWrite = Min(Size, MaxChunkSize);
+ uint64_t NumberOfBytesWritten = 0;
+#if ZEN_PLATFORM_WINDOWS
+ OVERLAPPED Ovl{};
+
+ Ovl.Offset = DWORD(m_WriteOffset & 0xffff'ffffu);
+ Ovl.OffsetHigh = DWORD(m_WriteOffset >> 32);
+
+ DWORD dwNumberOfBytesWritten = 0;
+
+ BOOL Success = ::WriteFile(m_FileHandle, Data, DWORD(NumberOfBytesToWrite), &dwNumberOfBytesWritten, &Ovl);
+ if (Success)
+ {
+ NumberOfBytesWritten = static_cast<uint64_t>(dwNumberOfBytesWritten);
+ }
+#else
+ static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files");
+ int Fd = int(uintptr_t(m_FileHandle));
+ int BytesWritten = pwrite(Fd, Data, NumberOfBytesToWrite, m_WriteOffset);
+ bool Success = (BytesWritten > 0);
+ if (Success)
+ {
+ NumberOfBytesWritten = static_cast<uint64_t>(BytesWritten);
+ }
+#endif
+
+ if (!Success)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ Size -= NumberOfBytesWritten;
+ m_WriteOffset += NumberOfBytesWritten;
+ Data = reinterpret_cast<const uint8_t*>(Data) + NumberOfBytesWritten;
+ }
+ return {};
+ }
+
+ BufferedReadFileStream::BufferedReadFileStream(void* FileHandle, uint64_t FileOffset, uint64_t FileSize, uint64_t BufferSize)
+ : m_FileHandle(FileHandle)
+ , m_FileSize(FileSize)
+ , m_FileEnd(FileOffset + FileSize)
+ , m_BufferSize(Min(BufferSize, FileSize))
+ , m_FileOffset(FileOffset)
+ {
+ }
+
+ BufferedReadFileStream::~BufferedReadFileStream() { Memory::Free(m_Buffer); }
+ void BufferedReadFileStream::Read(void* Data, uint64_t Size)
+ {
+ ZEN_ASSERT(Data != nullptr);
+ if (Size > m_BufferSize)
+ {
+ Read(Data, Size, m_FileOffset);
+ m_FileOffset += Size;
+ return;
+ }
+ uint8_t* WritePtr = ((uint8_t*)Data);
+ uint64_t Begin = m_FileOffset;
+ uint64_t End = m_FileOffset + Size;
+ ZEN_ASSERT(m_FileOffset >= m_BufferStart);
+ if (m_FileOffset < m_BufferEnd)
+ {
+ ZEN_ASSERT(m_Buffer != nullptr);
+ uint64_t Count = Min(m_BufferEnd, End) - m_FileOffset;
+ memcpy(WritePtr + Begin - m_FileOffset, m_Buffer + Begin - m_BufferStart, Count);
+ Begin += Count;
+ if (Begin == End)
+ {
+ m_FileOffset = End;
+ return;
+ }
+ }
+ if (End == m_FileEnd)
+ {
+ Read(WritePtr + Begin - m_FileOffset, End - Begin, Begin);
+ }
+ else
+ {
+ if (!m_Buffer)
+ {
+ m_BufferSize = Min(m_FileEnd - m_FileOffset, m_BufferSize);
+ m_Buffer = (uint8_t*)Memory::Alloc(gsl::narrow<size_t>(m_BufferSize));
+ }
+ m_BufferStart = Begin;
+ m_BufferEnd = Min(Begin + m_BufferSize, m_FileEnd);
+ Read(m_Buffer, m_BufferEnd - m_BufferStart, m_BufferStart);
+ uint64_t Count = Min(m_BufferEnd, End) - m_BufferStart;
+ memcpy(WritePtr + Begin - m_FileOffset, m_Buffer, Count);
+ ZEN_ASSERT(Begin + Count == End);
+ }
+ m_FileOffset = End;
+ }
+
+ void BufferedReadFileStream::Read(void* Data, uint64_t BytesToRead, uint64_t FileOffset)
+ {
+ const uint64_t MaxChunkSize = 1u * 1024 * 1024;
+ std::error_code Ec;
+ ReadFile(m_FileHandle, Data, BytesToRead, FileOffset, MaxChunkSize, Ec);
+
+ if (Ec)
+ {
+ std::error_code DummyEc;
+ throw std::system_error(
+ Ec,
+ fmt::format("HttpClient::BufferedReadFileStream ReadFile/pread failed (offset {:#x}, size {:#x}) file: '{}' (size {:#x})",
+ FileOffset,
+ BytesToRead,
+ PathFromHandle(m_FileHandle, DummyEc).generic_string(),
+ m_FileSize));
+ }
+ }
+
+ CompositeBufferReadStream::CompositeBufferReadStream(const CompositeBuffer& Data, uint64_t BufferSize)
+ : m_Data(Data)
+ , m_BufferSize(BufferSize)
+ , m_SegmentIndex(0)
+ , m_BytesLeftInSegment(0)
+ {
+ }
+ uint64_t CompositeBufferReadStream::Read(void* Data, uint64_t Size)
+ {
+ uint64_t Result = 0;
+ uint8_t* WritePtr = (uint8_t*)Data;
+ while ((Size > 0) && (m_SegmentIndex < m_Data.GetSegments().size()))
+ {
+ if (m_BytesLeftInSegment == 0)
+ {
+ const SharedBuffer& Segment = m_Data.GetSegments()[m_SegmentIndex];
+ IoBufferFileReference FileRef = {nullptr, 0, 0};
+ if (Segment.AsIoBuffer().GetFileReference(FileRef))
+ {
+ m_SegmentDiskBuffer = std::make_unique<BufferedReadFileStream>(FileRef.FileHandle,
+ FileRef.FileChunkOffset,
+ FileRef.FileChunkSize,
+ m_BufferSize);
+ }
+ else
+ {
+ m_SegmentMemoryBuffer = Segment.GetView();
+ }
+ m_BytesLeftInSegment = Segment.GetSize();
+ }
+ uint64_t BytesToRead = Min(m_BytesLeftInSegment, Size);
+ if (m_SegmentDiskBuffer)
+ {
+ m_SegmentDiskBuffer->Read(WritePtr, BytesToRead);
+ }
+ else
+ {
+ ZEN_ASSERT_SLOW(m_SegmentMemoryBuffer.GetSize() >= BytesToRead);
+ memcpy(WritePtr, m_SegmentMemoryBuffer.GetData(), BytesToRead);
+ m_SegmentMemoryBuffer.MidInline(BytesToRead);
+ }
+ WritePtr += BytesToRead;
+ Size -= BytesToRead;
+ Result += BytesToRead;
+
+ m_BytesLeftInSegment -= BytesToRead;
+ if (m_BytesLeftInSegment == 0)
+ {
+ m_SegmentDiskBuffer.reset();
+ m_SegmentMemoryBuffer.Reset();
+ m_SegmentIndex++;
+ }
+ }
+ return Result;
+ }
+
+} // namespace detail
+
+} // namespace zen
+
+#if ZEN_WITH_TESTS
+namespace zen {
+
+namespace testutil {
+ IoHash HashComposite(const CompositeBuffer& Payload)
+ {
+ IoHashStream Hasher;
+ const uint64_t PayloadSize = Payload.GetSize();
+ std::vector<uint8_t> Buffer(64u * 1024u);
+ detail::CompositeBufferReadStream Stream(Payload, 137u * 1024u);
+ for (uint64_t Offset = 0; Offset < PayloadSize;)
+ {
+ uint64_t Count = Min(64u * 1024u, PayloadSize - Offset);
+ Stream.Read(Buffer.data(), Count);
+ Hasher.Append(Buffer.data(), Count);
+ Offset += Count;
+ }
+ return Hasher.GetHash();
+ };
+
+ IoHash HashFileStream(void* FileHandle, uint64_t FileOffset, uint64_t FileSize)
+ {
+ IoHashStream Hasher;
+ std::vector<uint8_t> Buffer(64u * 1024u);
+ detail::BufferedReadFileStream Stream(FileHandle, FileOffset, FileSize, 137u * 1024u);
+ for (uint64_t Offset = 0; Offset < FileSize;)
+ {
+ uint64_t Count = Min(64u * 1024u, FileSize - Offset);
+ Stream.Read(Buffer.data(), Count);
+ Hasher.Append(Buffer.data(), Count);
+ Offset += Count;
+ }
+ return Hasher.GetHash();
+ }
+
+} // namespace testutil
+
+TEST_CASE("BufferedReadFileStream")
+{
+ ScopedTemporaryDirectory TmpDir;
+
+ IoBuffer DiskBuffer = WriteToTempFile(CompositeBuffer(CreateRandomBlob(496 * 5 * 1024)), TmpDir.Path() / "diskbuffer1");
+
+ IoBufferFileReference FileRef = {nullptr, 0, 0};
+ CHECK(DiskBuffer.GetFileReference(FileRef));
+ CHECK_EQ(IoHash::HashBuffer(DiskBuffer), testutil::HashFileStream(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize));
+
+ IoBuffer Partial(DiskBuffer, 37 * 1024, 512 * 1024);
+ CHECK(Partial.GetFileReference(FileRef));
+ CHECK_EQ(IoHash::HashBuffer(Partial), testutil::HashFileStream(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize));
+
+ IoBuffer SmallDiskBuffer = WriteToTempFile(CompositeBuffer(CreateRandomBlob(63 * 1024)), TmpDir.Path() / "diskbuffer2");
+ CHECK(SmallDiskBuffer.GetFileReference(FileRef));
+ CHECK_EQ(IoHash::HashBuffer(SmallDiskBuffer),
+ testutil::HashFileStream(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize));
+}
+
+TEST_CASE("CompositeBufferReadStream")
+{
+ ScopedTemporaryDirectory TmpDir;
+
+ IoBuffer MemoryBuffer1 = CreateRandomBlob(64);
+ CHECK_EQ(IoHash::HashBuffer(MemoryBuffer1), testutil::HashComposite(CompositeBuffer(SharedBuffer(MemoryBuffer1))));
+
+ IoBuffer MemoryBuffer2 = CreateRandomBlob(561 * 1024);
+ CHECK_EQ(IoHash::HashBuffer(MemoryBuffer2), testutil::HashComposite(CompositeBuffer(SharedBuffer(MemoryBuffer2))));
+
+ IoBuffer DiskBuffer1 = WriteToTempFile(CompositeBuffer(CreateRandomBlob(267 * 3 * 1024)), TmpDir.Path() / "diskbuffer1");
+ CHECK_EQ(IoHash::HashBuffer(DiskBuffer1), testutil::HashComposite(CompositeBuffer(SharedBuffer(DiskBuffer1))));
+
+ IoBuffer DiskBuffer2 = WriteToTempFile(CompositeBuffer(CreateRandomBlob(3 * 1024)), TmpDir.Path() / "diskbuffer2");
+ CHECK_EQ(IoHash::HashBuffer(DiskBuffer2), testutil::HashComposite(CompositeBuffer(SharedBuffer(DiskBuffer2))));
+
+ IoBuffer DiskBuffer3 = WriteToTempFile(CompositeBuffer(CreateRandomBlob(496 * 5 * 1024)), TmpDir.Path() / "diskbuffer3");
+ CHECK_EQ(IoHash::HashBuffer(DiskBuffer3), testutil::HashComposite(CompositeBuffer(SharedBuffer(DiskBuffer3))));
+
+ CompositeBuffer Data(SharedBuffer(std::move(MemoryBuffer1)),
+ SharedBuffer(std::move(DiskBuffer1)),
+ SharedBuffer(std::move(DiskBuffer2)),
+ SharedBuffer(std::move(MemoryBuffer2)),
+ SharedBuffer(std::move(DiskBuffer3)));
+ CHECK_EQ(IoHash::HashBuffer(Data), testutil::HashComposite(Data));
+}
+
+} // namespace zen
+#endif
diff --git a/src/zenhttp/clients/httpclientcommon.h b/src/zenhttp/clients/httpclientcommon.h
new file mode 100644
index 000000000..9060cde48
--- /dev/null
+++ b/src/zenhttp/clients/httpclientcommon.h
@@ -0,0 +1,147 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/compositebuffer.h>
+#include <zencore/trace.h>
+
+#include <zenhttp/httpclient.h>
+
+namespace zen {
+
+using namespace std::literals;
+
+class HttpClientBase
+{
+public:
+ HttpClientBase(std::string_view BaseUri, const HttpClientSettings& Connectionsettings = {});
+ virtual ~HttpClientBase() = 0;
+
+ using Response = HttpClient::Response;
+ using KeyValueMap = HttpClient::KeyValueMap;
+
+ [[nodiscard]] virtual Response Put(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Put(std::string_view Url, const KeyValueMap& Parameters = {}) = 0;
+ [[nodiscard]] virtual Response Get(std::string_view Url,
+ const KeyValueMap& AdditionalHeader = {},
+ const KeyValueMap& Parameters = {}) = 0;
+ [[nodiscard]] virtual Response Head(std::string_view Url, const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Delete(std::string_view Url, const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Post(std::string_view Url,
+ const KeyValueMap& AdditionalHeader = {},
+ const KeyValueMap& Parameters = {}) = 0;
+ [[nodiscard]] virtual Response Post(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Post(std::string_view Url,
+ const IoBuffer& Payload,
+ ZenContentType ContentType,
+ const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Post(std::string_view Url, CbObject Payload, const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Post(std::string_view Url, CbPackage Payload, const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Post(std::string_view Url,
+ const CompositeBuffer& Payload,
+ ZenContentType ContentType,
+ const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Upload(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader = {}) = 0;
+ [[nodiscard]] virtual Response Upload(std::string_view Url,
+ const CompositeBuffer& Payload,
+ ZenContentType ContentType,
+ const KeyValueMap& AdditionalHeader = {}) = 0;
+
+ [[nodiscard]] virtual Response Download(std::string_view Url,
+ const std::filesystem::path& TempFolderPath,
+ const KeyValueMap& AdditionalHeader = {}) = 0;
+
+ [[nodiscard]] virtual Response TransactPackage(std::string_view Url, CbPackage Package, const KeyValueMap& AdditionalHeader = {}) = 0;
+
+ LoggerRef Log() { return m_Log; }
+ std::string_view GetBaseUri() const { return m_BaseUri; }
+ std::string_view GetSessionId() const { return m_SessionId; }
+
+ bool Authenticate();
+
+protected:
+ LoggerRef m_Log;
+ std::string m_BaseUri;
+ std::string m_SessionId;
+ const HttpClientSettings m_ConnectionSettings;
+
+ const std::optional<HttpClientAccessToken> GetAccessToken();
+
+ RwLock m_AccessTokenLock;
+ HttpClientAccessToken m_CachedAccessToken;
+};
+
+namespace detail {
+
+ class TempPayloadFile
+ {
+ public:
+ TempPayloadFile(const TempPayloadFile&) = delete;
+ TempPayloadFile& operator=(const TempPayloadFile&) = delete;
+
+ TempPayloadFile();
+ ~TempPayloadFile();
+
+ std::error_code Open(const std::filesystem::path& TempFolderPath, uint64_t FinalSize);
+ std::error_code Write(std::string_view DataString);
+ IoBuffer DetachToIoBuffer();
+ IoBuffer BorrowIoBuffer();
+ inline uint64_t GetSize() const { return m_WriteOffset; }
+ void ResetWritePos(uint64_t WriteOffset);
+
+ private:
+ std::error_code Flush();
+ std::error_code AppendData(const void* Data, uint64_t Size);
+
+ void* m_FileHandle;
+ std::uint64_t m_WriteOffset;
+ static constexpr uint64_t CacheBufferSize = 512u * 1024u;
+ uint8_t m_CacheBuffer[CacheBufferSize];
+ std::uint64_t m_CacheBufferOffset = 0;
+ };
+
+ class BufferedReadFileStream
+ {
+ public:
+ BufferedReadFileStream(const BufferedReadFileStream&) = delete;
+ BufferedReadFileStream& operator=(const BufferedReadFileStream&) = delete;
+
+ BufferedReadFileStream(void* FileHandle, uint64_t FileOffset, uint64_t FileSize, uint64_t BufferSize);
+ ~BufferedReadFileStream();
+
+ void Read(void* Data, uint64_t Size);
+
+ private:
+ void Read(void* Data, uint64_t BytesToRead, uint64_t FileOffset);
+
+ void* m_FileHandle = nullptr;
+ const uint64_t m_FileSize = 0;
+ const uint64_t m_FileEnd = 0;
+ uint64_t m_BufferSize = 0;
+ uint8_t* m_Buffer = nullptr;
+ uint64_t m_BufferStart = 0;
+ uint64_t m_BufferEnd = 0;
+ uint64_t m_FileOffset = 0;
+ };
+
+ class CompositeBufferReadStream
+ {
+ public:
+ CompositeBufferReadStream(const CompositeBufferReadStream&) = delete;
+ CompositeBufferReadStream& operator=(const CompositeBufferReadStream&) = delete;
+
+ CompositeBufferReadStream(const CompositeBuffer& Data, uint64_t BufferSize);
+ uint64_t Read(void* Data, uint64_t Size);
+
+ private:
+ const CompositeBuffer& m_Data;
+ const uint64_t m_BufferSize;
+ size_t m_SegmentIndex;
+ std::unique_ptr<BufferedReadFileStream> m_SegmentDiskBuffer;
+ MemoryView m_SegmentMemoryBuffer;
+ uint64_t m_BytesLeftInSegment;
+ };
+
+} // namespace detail
+
+} // namespace zen
diff --git a/src/zenhttp/clients/httpclientcpr.cpp b/src/zenhttp/clients/httpclientcpr.cpp
new file mode 100644
index 000000000..568106887
--- /dev/null
+++ b/src/zenhttp/clients/httpclientcpr.cpp
@@ -0,0 +1,1035 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "httpclientcpr.h"
+
+#include <zencore/compactbinary.h>
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinarypackage.h>
+#include <zencore/compactbinaryutil.h>
+#include <zencore/compress.h>
+#include <zencore/iobuffer.h>
+#include <zencore/iohash.h>
+#include <zencore/session.h>
+#include <zencore/stream.h>
+#include <zenhttp/packageformat.h>
+
+namespace zen {
+
+HttpClientBase*
+CreateCprHttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings)
+{
+ return new CprHttpClient(BaseUri, ConnectionSettings);
+}
+
+static std::atomic<uint32_t> HttpClientRequestIdCounter{0};
+
+// If we want to support different HTTP client implementations then we'll need to make this more abstract
+
+HttpClientError::ResponseClass
+HttpClientError::GetResponseClass() const
+{
+ if ((cpr::ErrorCode)m_Error != cpr::ErrorCode::OK)
+ {
+ switch ((cpr::ErrorCode)m_Error)
+ {
+ case cpr::ErrorCode::CONNECTION_FAILURE:
+ return ResponseClass::kHttpCantConnectError;
+ case cpr::ErrorCode::HOST_RESOLUTION_FAILURE:
+ case cpr::ErrorCode::PROXY_RESOLUTION_FAILURE:
+ return ResponseClass::kHttpNoHost;
+ case cpr::ErrorCode::INTERNAL_ERROR:
+ case cpr::ErrorCode::NETWORK_RECEIVE_ERROR:
+ case cpr::ErrorCode::NETWORK_SEND_FAILURE:
+ case cpr::ErrorCode::OPERATION_TIMEDOUT:
+ return ResponseClass::kHttpTimeout;
+ case cpr::ErrorCode::SSL_CONNECT_ERROR:
+ case cpr::ErrorCode::SSL_LOCAL_CERTIFICATE_ERROR:
+ case cpr::ErrorCode::SSL_REMOTE_CERTIFICATE_ERROR:
+ case cpr::ErrorCode::SSL_CACERT_ERROR:
+ case cpr::ErrorCode::GENERIC_SSL_ERROR:
+ return ResponseClass::kHttpSLLError;
+ default:
+ return ResponseClass::kHttpOtherClientError;
+ }
+ }
+ else if (IsHttpSuccessCode(m_ResponseCode))
+ {
+ return ResponseClass::kSuccess;
+ }
+ else
+ {
+ switch (m_ResponseCode)
+ {
+ case HttpResponseCode::Unauthorized:
+ return ResponseClass::kHttpUnauthorized;
+ case HttpResponseCode::NotFound:
+ return ResponseClass::kHttpNotFound;
+ case HttpResponseCode::Forbidden:
+ return ResponseClass::kHttpForbidden;
+ case HttpResponseCode::Conflict:
+ return ResponseClass::kHttpConflict;
+ case HttpResponseCode::InternalServerError:
+ return ResponseClass::kHttpInternalServerError;
+ case HttpResponseCode::ServiceUnavailable:
+ return ResponseClass::kHttpServiceUnavailable;
+ case HttpResponseCode::BadGateway:
+ return ResponseClass::kHttpBadGateway;
+ case HttpResponseCode::GatewayTimeout:
+ return ResponseClass::kHttpGatewayTimeout;
+ default:
+ if (m_ResponseCode >= HttpResponseCode::InternalServerError)
+ {
+ return ResponseClass::kHttpOtherServerError;
+ }
+ else
+ {
+ return ResponseClass::kHttpOtherClientError;
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+//
+// CPR helpers
+
+static cpr::Body
+AsCprBody(const CbObject& Obj)
+{
+ return cpr::Body((const char*)Obj.GetBuffer().GetData(), Obj.GetBuffer().GetSize());
+}
+
+static cpr::Body
+AsCprBody(const IoBuffer& Obj)
+{
+ return cpr::Body((const char*)Obj.GetData(), Obj.GetSize());
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+static HttpClient::Response
+ResponseWithPayload(std::string_view SessionId, cpr::Response& HttpResponse, const HttpResponseCode WorkResponseCode, IoBuffer&& Payload)
+{
+ // This ends up doing a memcpy, would be good to get rid of it by streaming results
+ // into buffer directly
+ IoBuffer ResponseBuffer = Payload ? std::move(Payload) : IoBuffer(IoBuffer::Clone, HttpResponse.text.data(), HttpResponse.text.size());
+
+ if (auto It = HttpResponse.header.find("Content-Type"); It != HttpResponse.header.end())
+ {
+ const HttpContentType ContentType = ParseContentType(It->second);
+
+ ResponseBuffer.SetContentType(ContentType);
+ }
+
+ if (!IsHttpSuccessCode(WorkResponseCode) && WorkResponseCode != HttpResponseCode::NotFound)
+ {
+ ZEN_WARN("HttpClient request failed (session: {}): {}", SessionId, HttpResponse);
+ }
+
+ return HttpClient::Response{.StatusCode = WorkResponseCode,
+ .ResponsePayload = std::move(ResponseBuffer),
+ .Header = HttpClient::KeyValueMap(HttpResponse.header.begin(), HttpResponse.header.end()),
+ .UploadedBytes = gsl::narrow<int64_t>(HttpResponse.uploaded_bytes),
+ .DownloadedBytes = gsl::narrow<int64_t>(HttpResponse.downloaded_bytes),
+ .ElapsedSeconds = HttpResponse.elapsed};
+}
+
+static HttpClient::Response
+CommonResponse(std::string_view SessionId, cpr::Response&& HttpResponse, IoBuffer&& Payload = {})
+{
+ const HttpResponseCode WorkResponseCode = HttpResponseCode(HttpResponse.status_code);
+ if (HttpResponse.error)
+ {
+ if (HttpResponse.error.code != cpr::ErrorCode::OPERATION_TIMEDOUT &&
+ HttpResponse.error.code != cpr::ErrorCode::CONNECTION_FAILURE && HttpResponse.error.code != cpr::ErrorCode::REQUEST_CANCELLED)
+ {
+ ZEN_WARN("HttpClient client failure (session: {}): {}", SessionId, HttpResponse);
+ }
+
+ // Client side failure code
+ return HttpClient::Response{
+ .StatusCode = WorkResponseCode,
+ .ResponsePayload = IoBufferBuilder::MakeCloneFromMemory(HttpResponse.text.data(), HttpResponse.text.size()),
+ .Header = HttpClient::KeyValueMap(HttpResponse.header.begin(), HttpResponse.header.end()),
+ .UploadedBytes = gsl::narrow<int64_t>(HttpResponse.uploaded_bytes),
+ .DownloadedBytes = gsl::narrow<int64_t>(HttpResponse.downloaded_bytes),
+ .ElapsedSeconds = HttpResponse.elapsed,
+ .Error = HttpClient::ErrorContext{.ErrorCode = gsl::narrow<int>(HttpResponse.error.code),
+ .ErrorMessage = HttpResponse.error.message}};
+ }
+
+ if (WorkResponseCode == HttpResponseCode::NoContent || (HttpResponse.text.empty() && !Payload))
+ {
+ return HttpClient::Response{.StatusCode = WorkResponseCode,
+ .Header = HttpClient::KeyValueMap(HttpResponse.header.begin(), HttpResponse.header.end()),
+ .UploadedBytes = gsl::narrow<int64_t>(HttpResponse.uploaded_bytes),
+ .DownloadedBytes = gsl::narrow<int64_t>(HttpResponse.downloaded_bytes),
+ .ElapsedSeconds = HttpResponse.elapsed};
+ }
+ else
+ {
+ return ResponseWithPayload(
+ SessionId,
+ HttpResponse,
+ WorkResponseCode,
+ Payload ? std::move(Payload) : IoBufferBuilder::MakeCloneFromMemory(HttpResponse.text.data(), HttpResponse.text.size()));
+ }
+}
+
+static bool
+ShouldRetry(const cpr::Response& Response)
+{
+ switch (Response.error.code)
+ {
+ case cpr::ErrorCode::OK:
+ break;
+ case cpr::ErrorCode::INTERNAL_ERROR:
+ case cpr::ErrorCode::NETWORK_RECEIVE_ERROR:
+ case cpr::ErrorCode::NETWORK_SEND_FAILURE:
+ case cpr::ErrorCode::OPERATION_TIMEDOUT:
+ return true;
+ default:
+ return false;
+ }
+ switch ((HttpResponseCode)Response.status_code)
+ {
+ case HttpResponseCode::RequestTimeout:
+ case HttpResponseCode::TooManyRequests:
+ case HttpResponseCode::InternalServerError:
+ case HttpResponseCode::BadGateway:
+ case HttpResponseCode::ServiceUnavailable:
+ case HttpResponseCode::GatewayTimeout:
+ return true;
+ default:
+ return false;
+ }
+};
+
+static bool
+ValidatePayload(cpr::Response& Response, std::unique_ptr<detail::TempPayloadFile>& PayloadFile)
+{
+ ZEN_TRACE_CPU("ValidatePayload");
+ IoBuffer ResponseBuffer = (Response.text.empty() && PayloadFile) ? PayloadFile->BorrowIoBuffer()
+ : IoBuffer(IoBuffer::Wrap, Response.text.data(), Response.text.size());
+
+ if (auto ContentLength = Response.header.find("Content-Length"); ContentLength != Response.header.end())
+ {
+ std::optional<uint64_t> ExpectedContentSize = ParseInt<uint64_t>(ContentLength->second);
+ if (!ExpectedContentSize.has_value())
+ {
+ Response.error =
+ cpr::Error(/*CURLE_READ_ERROR*/ 26, fmt::format("Can not parse Content-Length header. Value: '{}'", ContentLength->second));
+ return false;
+ }
+ if (ExpectedContentSize.value() != ResponseBuffer.GetSize())
+ {
+ Response.error = cpr::Error(
+ /*CURLE_READ_ERROR*/ 26,
+ fmt::format("Payload size {} does not match Content-Length {}", ResponseBuffer.GetSize(), ContentLength->second));
+ return false;
+ }
+ }
+
+ if (Response.status_code == (long)HttpResponseCode::PartialContent)
+ {
+ return true;
+ }
+
+ if (auto JupiterHash = Response.header.find("X-Jupiter-IoHash"); JupiterHash != Response.header.end())
+ {
+ IoHash ExpectedPayloadHash;
+ if (IoHash::TryParse(JupiterHash->second, ExpectedPayloadHash))
+ {
+ IoHash PayloadHash = IoHash::HashBuffer(ResponseBuffer);
+ if (PayloadHash != ExpectedPayloadHash)
+ {
+ Response.error = cpr::Error(/*CURLE_READ_ERROR*/ 26,
+ fmt::format("Payload hash {} does not match X-Jupiter-IoHash {}",
+ PayloadHash.ToHexString(),
+ ExpectedPayloadHash.ToHexString()));
+ return false;
+ }
+ }
+ }
+
+ if (auto ContentType = Response.header.find("Content-Type"); ContentType != Response.header.end())
+ {
+ if (ContentType->second == "application/x-ue-comp")
+ {
+ IoHash RawHash;
+ uint64_t RawSize;
+ if (CompressedBuffer::ValidateCompressedHeader(ResponseBuffer, RawHash, RawSize))
+ {
+ return true;
+ }
+ else
+ {
+ Response.error = cpr::Error(/*CURLE_READ_ERROR*/ 26, "Compressed binary failed validation");
+ return false;
+ }
+ }
+ if (ContentType->second == "application/x-ue-cb")
+ {
+ if (CbValidateError Error = ValidateCompactBinary(ResponseBuffer.GetView(), CbValidateMode::Default);
+ Error == CbValidateError::None)
+ {
+ return true;
+ }
+ else
+ {
+ Response.error = cpr::Error(/*CURLE_READ_ERROR*/ 26, fmt::format("Compact binary failed validation: {}", ToString(Error)));
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static cpr::Response
+DoWithRetry(
+ std::string_view SessionId,
+ std::function<cpr::Response()>&& Func,
+ uint8_t RetryCount,
+ std::function<bool(cpr::Response& Result)>&& Validate = [](cpr::Response&) { return true; })
+{
+ uint8_t Attempt = 0;
+ cpr::Response Result = Func();
+ while (Attempt < RetryCount)
+ {
+ if (!ShouldRetry(Result))
+ {
+ if (Result.error || !IsHttpSuccessCode(Result.status_code))
+ {
+ break;
+ }
+ if (Validate(Result))
+ {
+ break;
+ }
+ }
+ Sleep(100 * (Attempt + 1));
+ Attempt++;
+ ZEN_INFO("{} Attempt {}/{}", CommonResponse(SessionId, std::move(Result)).ErrorMessage("Retry"), Attempt, RetryCount + 1);
+ Result = Func();
+ }
+ return Result;
+}
+
+static cpr::Response
+DoWithRetry(std::string_view SessionId,
+ std::function<cpr::Response()>&& Func,
+ std::unique_ptr<detail::TempPayloadFile>& PayloadFile,
+ uint8_t RetryCount)
+{
+ uint8_t Attempt = 0;
+ cpr::Response Result = Func();
+ while (Attempt < RetryCount)
+ {
+ if (!ShouldRetry(Result))
+ {
+ if (Result.error || !IsHttpSuccessCode(Result.status_code))
+ {
+ break;
+ }
+ if (ValidatePayload(Result, PayloadFile))
+ {
+ break;
+ }
+ }
+ Sleep(100 * (Attempt + 1));
+ Attempt++;
+ ZEN_INFO("{} Attempt {}/{}", CommonResponse(SessionId, std::move(Result)).ErrorMessage("Retry"), Attempt, RetryCount + 1);
+ Result = Func();
+ }
+ return Result;
+}
+
+static std::pair<std::string, std::string>
+HeaderContentType(ZenContentType ContentType)
+{
+ return std::make_pair("Content-Type", std::string(MapContentTypeToString(ContentType)));
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+CprHttpClient::CprHttpClient(std::string_view BaseUri, const HttpClientSettings& Connectionsettings)
+: HttpClientBase(BaseUri, Connectionsettings)
+{
+}
+
+CprHttpClient::~CprHttpClient()
+{
+ ZEN_TRACE_CPU("CprHttpClient::~CprHttpClient");
+ m_SessionLock.WithExclusiveLock([&] {
+ for (auto CprSession : m_Sessions)
+ {
+ delete CprSession;
+ }
+ m_Sessions.clear();
+ });
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+CprHttpClient::Session
+CprHttpClient::AllocSession(const std::string_view BaseUrl,
+ const std::string_view ResourcePath,
+ const HttpClientSettings& ConnectionSettings,
+ const KeyValueMap& AdditionalHeader,
+ const KeyValueMap& Parameters,
+ const std::string_view SessionId,
+ std::optional<HttpClientAccessToken> AccessToken)
+{
+ ZEN_TRACE_CPU("CprHttpClient::AllocSession");
+ cpr::Session* CprSession = nullptr;
+ m_SessionLock.WithExclusiveLock([&] {
+ if (!m_Sessions.empty())
+ {
+ CprSession = m_Sessions.back();
+ m_Sessions.pop_back();
+ }
+ });
+
+ if (CprSession == nullptr)
+ {
+ CprSession = new cpr::Session();
+ CprSession->SetConnectTimeout(ConnectionSettings.ConnectTimeout);
+ CprSession->SetTimeout(ConnectionSettings.Timeout);
+ if (ConnectionSettings.AssumeHttp2)
+ {
+ CprSession->SetHttpVersion(cpr::HttpVersion{cpr::HttpVersionCode::VERSION_2_0_PRIOR_KNOWLEDGE});
+ }
+ }
+
+ if (!AdditionalHeader->empty())
+ {
+ CprSession->SetHeader(cpr::Header(AdditionalHeader->begin(), AdditionalHeader->end()));
+ }
+ if (!SessionId.empty())
+ {
+ CprSession->UpdateHeader({{"UE-Session", std::string(SessionId)}});
+ }
+ if (AccessToken)
+ {
+ CprSession->UpdateHeader({{"Authorization", AccessToken->Value}});
+ }
+ if (!Parameters->empty())
+ {
+ cpr::Parameters Tmp;
+ for (auto It = Parameters->begin(); It != Parameters->end(); It++)
+ {
+ Tmp.Add({It->first, It->second});
+ }
+ CprSession->SetParameters(Tmp);
+ }
+ else
+ {
+ CprSession->SetParameters({});
+ }
+
+ ExtendableStringBuilder<128> UrlBuffer;
+ UrlBuffer << BaseUrl << ResourcePath;
+ CprSession->SetUrl(UrlBuffer.c_str());
+
+ return Session(this, CprSession);
+}
+
+void
+CprHttpClient::ReleaseSession(cpr::Session* CprSession)
+{
+ ZEN_TRACE_CPU("CprHttpClient::ReleaseSession");
+ CprSession->SetUrl({});
+ CprSession->SetHeader({});
+ CprSession->SetBody({});
+ m_SessionLock.WithExclusiveLock([&] { m_Sessions.push_back(CprSession); });
+}
+
+CprHttpClient::Response
+CprHttpClient::TransactPackage(std::string_view Url, CbPackage Package, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::TransactPackage");
+
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+
+ // First, list of offered chunks for filtering on the server end
+
+ std::vector<IoHash> AttachmentsToSend;
+ std::span<const CbAttachment> Attachments = Package.GetAttachments();
+
+ const uint32_t RequestId = ++HttpClientRequestIdCounter;
+ auto RequestIdString = fmt::to_string(RequestId);
+
+ if (Attachments.empty() == false)
+ {
+ CbObjectWriter Writer;
+ Writer.BeginArray("offer");
+
+ for (const CbAttachment& Attachment : Attachments)
+ {
+ Writer.AddHash(Attachment.GetHash());
+ }
+
+ Writer.EndArray();
+
+ BinaryWriter MemWriter;
+ Writer.Save(MemWriter);
+
+ Sess->UpdateHeader({HeaderContentType(HttpContentType::kCbPackageOffer), {"UE-Request", RequestIdString}});
+ Sess->SetBody(cpr::Body{(const char*)MemWriter.Data(), MemWriter.Size()});
+
+ cpr::Response FilterResponse = Sess.Post();
+
+ if (FilterResponse.status_code == 200)
+ {
+ IoBuffer ResponseBuffer(IoBuffer::Wrap, FilterResponse.text.data(), FilterResponse.text.size());
+ CbValidateError ValidationError = CbValidateError::None;
+ if (CbObject ResponseObject = ValidateAndReadCompactBinaryObject(std::move(ResponseBuffer), ValidationError);
+ ValidationError == CbValidateError::None)
+ {
+ for (CbFieldView& Entry : ResponseObject["need"])
+ {
+ ZEN_ASSERT(Entry.IsHash());
+ AttachmentsToSend.push_back(Entry.AsHash());
+ }
+ }
+ }
+ }
+
+ // Prepare package for send
+
+ CbPackage SendPackage;
+ SendPackage.SetObject(Package.GetObject(), Package.GetObjectHash());
+
+ for (const IoHash& AttachmentCid : AttachmentsToSend)
+ {
+ const CbAttachment* Attachment = Package.FindAttachment(AttachmentCid);
+
+ if (Attachment)
+ {
+ SendPackage.AddAttachment(*Attachment);
+ }
+ else
+ {
+ // This should be an error -- server asked to have something we can't find
+ }
+ }
+
+ // Transmit package payload
+
+ CompositeBuffer Message = FormatPackageMessageBuffer(SendPackage);
+ SharedBuffer FlatMessage = Message.Flatten();
+
+ Sess->UpdateHeader({HeaderContentType(HttpContentType::kCbPackage), {"UE-Request", RequestIdString}});
+ Sess->SetBody(cpr::Body{(const char*)FlatMessage.GetData(), FlatMessage.GetSize()});
+
+ cpr::Response FilterResponse = Sess.Post();
+
+ if (!IsHttpSuccessCode(FilterResponse.status_code))
+ {
+ return {.StatusCode = HttpResponseCode(FilterResponse.status_code)};
+ }
+
+ IoBuffer ResponseBuffer(IoBuffer::Clone, FilterResponse.text.data(), FilterResponse.text.size());
+
+ if (auto It = FilterResponse.header.find("Content-Type"); It != FilterResponse.header.end())
+ {
+ HttpContentType ContentType = ParseContentType(It->second);
+
+ ResponseBuffer.SetContentType(ContentType);
+ }
+
+ return {.StatusCode = HttpResponseCode(FilterResponse.status_code), .ResponsePayload = ResponseBuffer};
+}
+
+//////////////////////////////////////////////////////////////////////////
+//
+// Standard HTTP verbs
+//
+
+CprHttpClient::Response
+CprHttpClient::Put(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Put");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Sess->SetBody(AsCprBody(Payload));
+ Sess->UpdateHeader({HeaderContentType(Payload.GetContentType())});
+ return Sess.Put();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Put(std::string_view Url, const KeyValueMap& Parameters)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Put");
+
+ return CommonResponse(m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri,
+ Url,
+ m_ConnectionSettings,
+ {{"Content-Length", "0"}},
+ Parameters,
+ m_SessionId,
+ GetAccessToken());
+ return Sess.Put();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Get(std::string_view Url, const KeyValueMap& AdditionalHeader, const KeyValueMap& Parameters)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Get");
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess =
+ AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, Parameters, m_SessionId, GetAccessToken());
+ return Sess.Get();
+ },
+ m_ConnectionSettings.RetryCount,
+ [](cpr::Response& Result) {
+ std::unique_ptr<detail::TempPayloadFile> NoTempFile;
+ return ValidatePayload(Result, NoTempFile);
+ }));
+}
+
+CprHttpClient::Response
+CprHttpClient::Head(std::string_view Url, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Head");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ return Sess.Head();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Delete(std::string_view Url, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Delete");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ return Sess.Delete();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Post(std::string_view Url, const KeyValueMap& AdditionalHeader, const KeyValueMap& Parameters)
+{
+ ZEN_TRACE_CPU("CprHttpClient::PostNoPayload");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess =
+ AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, Parameters, m_SessionId, GetAccessToken());
+ return Sess.Post();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Post(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader)
+{
+ return Post(Url, Payload, Payload.GetContentType(), AdditionalHeader);
+}
+
+CprHttpClient::Response
+CprHttpClient::Post(std::string_view Url, const IoBuffer& Payload, ZenContentType ContentType, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::PostWithPayload");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Sess->UpdateHeader({HeaderContentType(ContentType)});
+
+ IoBufferFileReference FileRef = {nullptr, 0, 0};
+ if (Payload.GetFileReference(FileRef))
+ {
+ uint64_t Offset = 0;
+ detail::BufferedReadFileStream Buffer(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize, 512u * 1024u);
+ auto ReadCallback = [&Payload, &Offset, &Buffer](char* buffer, size_t& size, intptr_t) {
+ size = Min<size_t>(size, Payload.GetSize() - Offset);
+ Buffer.Read(buffer, size);
+ Offset += size;
+ return true;
+ };
+ return Sess.Post(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
+ }
+ Sess->SetBody(AsCprBody(Payload));
+ return Sess.Post();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Post(std::string_view Url, CbObject Payload, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::PostObjectPayload");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+
+ Sess->SetBody(AsCprBody(Payload));
+ Sess->UpdateHeader({HeaderContentType(ZenContentType::kCbObject)});
+ return Sess.Post();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Post(std::string_view Url, CbPackage Pkg, const KeyValueMap& AdditionalHeader)
+{
+ return Post(Url, zen::FormatPackageMessageBuffer(Pkg), ZenContentType::kCbPackage, AdditionalHeader);
+}
+
+CprHttpClient::Response
+CprHttpClient::Post(std::string_view Url, const CompositeBuffer& Payload, ZenContentType ContentType, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Post");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Sess->UpdateHeader({HeaderContentType(ContentType)});
+
+ detail::CompositeBufferReadStream Reader(Payload, 512u * 1024u);
+ auto ReadCallback = [&Reader](char* buffer, size_t& size, intptr_t) {
+ size = Reader.Read(buffer, size);
+ return true;
+ };
+ return Sess.Post(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Upload");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Sess->UpdateHeader({HeaderContentType(Payload.GetContentType())});
+
+ IoBufferFileReference FileRef = {nullptr, 0, 0};
+ if (Payload.GetFileReference(FileRef))
+ {
+ uint64_t Offset = 0;
+ detail::BufferedReadFileStream Buffer(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize, 512u * 1024u);
+ auto ReadCallback = [&Payload, &Offset, &Buffer](char* buffer, size_t& size, intptr_t) {
+ size = Min<size_t>(size, Payload.GetSize() - Offset);
+ Buffer.Read(buffer, size);
+ Offset += size;
+ return true;
+ };
+ return Sess.Put(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
+ }
+ Sess->SetBody(AsCprBody(Payload));
+ return Sess.Put();
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Upload(std::string_view Url, const CompositeBuffer& Payload, ZenContentType ContentType, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Upload");
+
+ return CommonResponse(
+ m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() {
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Sess->UpdateHeader({HeaderContentType(ContentType)});
+
+ detail::CompositeBufferReadStream Reader(Payload, 512u * 1024u);
+ auto ReadCallback = [&Reader](char* buffer, size_t& size, intptr_t) {
+ size = Reader.Read(buffer, size);
+ return true;
+ };
+ return Sess.Put(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
+ },
+ m_ConnectionSettings.RetryCount));
+}
+
+CprHttpClient::Response
+CprHttpClient::Download(std::string_view Url, const std::filesystem::path& TempFolderPath, const KeyValueMap& AdditionalHeader)
+{
+ ZEN_TRACE_CPU("CprHttpClient::Download");
+
+ std::string PayloadString;
+ std::unique_ptr<detail::TempPayloadFile> PayloadFile;
+ cpr::Response Response = DoWithRetry(
+ m_SessionId,
+ [&]() {
+ auto GetHeader = [&](std::string header) -> std::pair<std::string, std::string> {
+ size_t DelimiterPos = header.find(':');
+ if (DelimiterPos != std::string::npos)
+ {
+ std::string Key = header.substr(0, DelimiterPos);
+ constexpr AsciiSet WhitespaceCharacters(" \v\f\t\r\n");
+ Key = AsciiSet::TrimSuffixWith(Key, WhitespaceCharacters);
+ Key = AsciiSet::TrimPrefixWith(Key, WhitespaceCharacters);
+
+ std::string Value = header.substr(DelimiterPos + 1);
+ Value = AsciiSet::TrimSuffixWith(Value, WhitespaceCharacters);
+ Value = AsciiSet::TrimPrefixWith(Value, WhitespaceCharacters);
+
+ return std::make_pair(Key, Value);
+ }
+ return std::make_pair(header, "");
+ };
+
+ auto DownloadCallback = [&](std::string data, intptr_t) {
+ if (PayloadFile)
+ {
+ ZEN_ASSERT(PayloadString.empty());
+ std::error_code Ec = PayloadFile->Write(data);
+ if (Ec)
+ {
+ ZEN_WARN("Failed to write to temp file in '{}' for HttpClient::Download. Reason: {}",
+ TempFolderPath.string(),
+ Ec.message());
+ return false;
+ }
+ }
+ else
+ {
+ PayloadString.append(data);
+ }
+ return true;
+ };
+
+ uint64_t RequestedContentLength = (uint64_t)-1;
+ if (auto RangeIt = AdditionalHeader.Entries.find("Range"); RangeIt != AdditionalHeader.Entries.end())
+ {
+ if (RangeIt->second.starts_with("bytes"))
+ {
+ size_t RangeStartPos = RangeIt->second.find('=', 5);
+ if (RangeStartPos != std::string::npos)
+ {
+ RangeStartPos++;
+ size_t RangeSplitPos = RangeIt->second.find('-', RangeStartPos);
+ if (RangeSplitPos != std::string::npos)
+ {
+ std::optional<size_t> RequestedRangeStart =
+ ParseInt<size_t>(RangeIt->second.substr(RangeStartPos, RangeSplitPos - RangeStartPos));
+ std::optional<size_t> RequestedRangeEnd = ParseInt<size_t>(RangeIt->second.substr(RangeStartPos + 1));
+ if (RequestedRangeStart.has_value() && RequestedRangeEnd.has_value())
+ {
+ RequestedContentLength = RequestedRangeEnd.value() - 1;
+ }
+ }
+ }
+ }
+ }
+
+ cpr::Response Response;
+ {
+ std::vector<std::pair<std::string, std::string>> ReceivedHeaders;
+ auto HeaderCallback = [&](std::string header, intptr_t) {
+ std::pair<std::string, std::string> Header = GetHeader(header);
+ if (Header.first == "Content-Length"sv)
+ {
+ std::optional<size_t> ContentLength = ParseInt<size_t>(Header.second);
+ if (ContentLength.has_value())
+ {
+ if (ContentLength.value() > 1024 * 1024)
+ {
+ PayloadFile = std::make_unique<detail::TempPayloadFile>();
+ std::error_code Ec = PayloadFile->Open(TempFolderPath, ContentLength.value());
+ if (Ec)
+ {
+ ZEN_WARN("Failed to create temp file in '{}' for HttpClient::Download. Reason: {}",
+ TempFolderPath.string(),
+ Ec.message());
+ PayloadFile.reset();
+ }
+ }
+ else
+ {
+ PayloadString.reserve(ContentLength.value());
+ }
+ }
+ }
+ if (!Header.first.empty())
+ {
+ ReceivedHeaders.emplace_back(std::move(Header));
+ }
+ return 1;
+ };
+
+ Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Response = Sess.Download(cpr::WriteCallback{DownloadCallback}, cpr::HeaderCallback{HeaderCallback});
+ for (const std::pair<std::string, std::string>& H : ReceivedHeaders)
+ {
+ Response.header.insert_or_assign(H.first, H.second);
+ }
+ }
+ if (m_ConnectionSettings.AllowResume)
+ {
+ auto SupportsRanges = [](const cpr::Response& Response) -> bool {
+ if (Response.header.find("Content-Range") != Response.header.end())
+ {
+ return true;
+ }
+ if (auto It = Response.header.find("Accept-Ranges"); It != Response.header.end())
+ {
+ return It->second == "bytes"sv;
+ }
+ return false;
+ };
+
+ auto ShouldResume = [&SupportsRanges](const cpr::Response& Response) -> bool {
+ if (ShouldRetry(Response))
+ {
+ return SupportsRanges(Response);
+ }
+ return false;
+ };
+
+ if (ShouldResume(Response))
+ {
+ auto It = Response.header.find("Content-Length");
+ if (It != Response.header.end())
+ {
+ std::vector<std::pair<std::string, std::string>> ReceivedHeaders;
+
+ auto HeaderCallback = [&](std::string header, intptr_t) {
+ std::pair<std::string, std::string> Header = GetHeader(header);
+ if (!Header.first.empty())
+ {
+ ReceivedHeaders.emplace_back(std::move(Header));
+ }
+
+ if (Header.first == "Content-Range"sv)
+ {
+ if (Header.second.starts_with("bytes "sv))
+ {
+ size_t RangeStartEnd = Header.second.find('-', 6);
+ if (RangeStartEnd != std::string::npos)
+ {
+ const auto Start = ParseInt<uint64_t>(Header.second.substr(6, RangeStartEnd - 6));
+ if (Start)
+ {
+ uint64_t DownloadedSize = PayloadFile ? PayloadFile->GetSize() : PayloadString.length();
+ if (Start.value() == DownloadedSize)
+ {
+ return 1;
+ }
+ else if (Start.value() > DownloadedSize)
+ {
+ return 0;
+ }
+ if (PayloadFile)
+ {
+ PayloadFile->ResetWritePos(Start.value());
+ }
+ else
+ {
+ PayloadString = PayloadString.substr(0, Start.value());
+ }
+ return 1;
+ }
+ }
+ }
+ return 0;
+ }
+ return 1;
+ };
+
+ KeyValueMap HeadersWithRange(AdditionalHeader);
+ do
+ {
+ uint64_t DownloadedSize = PayloadFile ? PayloadFile->GetSize() : PayloadString.length();
+
+ uint64_t ContentLength = RequestedContentLength;
+ if (ContentLength == uint64_t(-1))
+ {
+ if (auto ParsedContentLength = ParseInt<int64_t>(It->second); ParsedContentLength.has_value())
+ {
+ ContentLength = ParsedContentLength.value();
+ }
+ }
+
+ std::string Range = fmt::format("bytes={}-{}", DownloadedSize, DownloadedSize + ContentLength - 1);
+ if (auto RangeIt = HeadersWithRange.Entries.find("Range"); RangeIt != HeadersWithRange.Entries.end())
+ {
+ if (RangeIt->second == Range)
+ {
+ // If we didn't make any progress, abort
+ break;
+ }
+ }
+ HeadersWithRange.Entries.insert_or_assign("Range", Range);
+
+ Session Sess =
+ AllocSession(m_BaseUri, Url, m_ConnectionSettings, HeadersWithRange, {}, m_SessionId, GetAccessToken());
+ Response = Sess.Download(cpr::WriteCallback{DownloadCallback}, cpr::HeaderCallback{HeaderCallback});
+ for (const std::pair<std::string, std::string>& H : ReceivedHeaders)
+ {
+ Response.header.insert_or_assign(H.first, H.second);
+ }
+ ReceivedHeaders.clear();
+ } while (ShouldResume(Response));
+ }
+ }
+ }
+
+ if (!PayloadString.empty())
+ {
+ Response.text = std::move(PayloadString);
+ }
+ return Response;
+ },
+ PayloadFile,
+ m_ConnectionSettings.RetryCount);
+
+ return CommonResponse(m_SessionId, std::move(Response), PayloadFile ? PayloadFile->DetachToIoBuffer() : IoBuffer{});
+}
+
+} // namespace zen \ No newline at end of file
diff --git a/src/zenhttp/clients/httpclientcpr.h b/src/zenhttp/clients/httpclientcpr.h
new file mode 100644
index 000000000..ed9d10c27
--- /dev/null
+++ b/src/zenhttp/clients/httpclientcpr.h
@@ -0,0 +1,151 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "httpclientcommon.h"
+
+#include <zencore/logging.h>
+#include <zenhttp/cprutils.h>
+#include <zenhttp/httpclient.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <cpr/body.h>
+#include <cpr/session.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+class CprHttpClient : public HttpClientBase
+{
+public:
+ CprHttpClient(std::string_view BaseUri, const HttpClientSettings& Connectionsettings = {});
+ ~CprHttpClient();
+
+ // HttpClientBase
+
+ [[nodiscard]] virtual Response Put(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Put(std::string_view Url, const KeyValueMap& Parameters = {}) override;
+ [[nodiscard]] virtual Response Get(std::string_view Url,
+ const KeyValueMap& AdditionalHeader = {},
+ const KeyValueMap& Parameters = {}) override;
+ [[nodiscard]] virtual Response Head(std::string_view Url, const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Delete(std::string_view Url, const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Post(std::string_view Url,
+ const KeyValueMap& AdditionalHeader = {},
+ const KeyValueMap& Parameters = {}) override;
+ [[nodiscard]] virtual Response Post(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Post(std::string_view Url,
+ const IoBuffer& Payload,
+ ZenContentType ContentType,
+ const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Post(std::string_view Url, CbObject Payload, const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Post(std::string_view Url, CbPackage Payload, const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Post(std::string_view Url,
+ const CompositeBuffer& Payload,
+ ZenContentType ContentType,
+ const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Upload(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader = {}) override;
+ [[nodiscard]] virtual Response Upload(std::string_view Url,
+ const CompositeBuffer& Payload,
+ ZenContentType ContentType,
+ const KeyValueMap& AdditionalHeader = {}) override;
+
+ [[nodiscard]] virtual Response Download(std::string_view Url,
+ const std::filesystem::path& TempFolderPath,
+ const KeyValueMap& AdditionalHeader = {}) override;
+
+ [[nodiscard]] virtual Response TransactPackage(std::string_view Url,
+ CbPackage Package,
+ const KeyValueMap& AdditionalHeader = {}) override;
+
+private:
+ struct Session
+ {
+ Session(CprHttpClient* InOuter, cpr::Session* InSession) : Outer(InOuter), CprSession(InSession) {}
+ ~Session() { Outer->ReleaseSession(CprSession); }
+
+ inline cpr::Session* operator->() const { return CprSession; }
+ inline cpr::Response Get()
+ {
+ ZEN_TRACE_CPU("HttpClient::Impl::Get");
+ cpr::Response Result = CprSession->Get();
+ ZEN_TRACE("GET {}", Result);
+ return Result;
+ }
+ inline cpr::Response Download(cpr::WriteCallback&& Write, std::optional<cpr::HeaderCallback>&& Header = {})
+ {
+ ZEN_TRACE_CPU("HttpClient::Impl::Download");
+ if (Header)
+ {
+ CprSession->SetHeaderCallback(std::move(Header.value()));
+ }
+ cpr::Response Result = CprSession->Download(Write);
+ ZEN_TRACE("GET {}", Result);
+ CprSession->SetHeaderCallback({});
+ CprSession->SetWriteCallback({});
+ return Result;
+ }
+ inline cpr::Response Head()
+ {
+ ZEN_TRACE_CPU("HttpClient::Impl::Head");
+ cpr::Response Result = CprSession->Head();
+ ZEN_TRACE("HEAD {}", Result);
+ return Result;
+ }
+ inline cpr::Response Put(std::optional<cpr::ReadCallback>&& Read = {})
+ {
+ ZEN_TRACE_CPU("HttpClient::Impl::Put");
+ if (Read)
+ {
+ CprSession->SetReadCallback(std::move(Read.value()));
+ }
+ cpr::Response Result = CprSession->Put();
+ ZEN_TRACE("PUT {}", Result);
+ CprSession->SetReadCallback({});
+ return Result;
+ }
+ inline cpr::Response Post(std::optional<cpr::ReadCallback>&& Read = {})
+ {
+ ZEN_TRACE_CPU("HttpClient::Impl::Post");
+ if (Read)
+ {
+ CprSession->SetReadCallback(std::move(Read.value()));
+ }
+ cpr::Response Result = CprSession->Post();
+ ZEN_TRACE("POST {}", Result);
+ CprSession->SetReadCallback({});
+ return Result;
+ }
+ inline cpr::Response Delete()
+ {
+ ZEN_TRACE_CPU("HttpClient::Impl::Delete");
+ cpr::Response Result = CprSession->Delete();
+ ZEN_TRACE("DELETE {}", Result);
+ return Result;
+ }
+
+ LoggerRef Log() { return Outer->Log(); }
+
+ private:
+ CprHttpClient* Outer;
+ cpr::Session* CprSession;
+
+ Session(Session&&) = delete;
+ Session& operator=(Session&&) = delete;
+ };
+
+ Session AllocSession(const std::string_view BaseUrl,
+ const std::string_view Url,
+ const HttpClientSettings& ConnectionSettings,
+ const KeyValueMap& AdditionalHeader,
+ const KeyValueMap& Parameters,
+ const std::string_view SessionId,
+ std::optional<HttpClientAccessToken> AccessToken);
+
+ RwLock m_SessionLock;
+ std::vector<cpr::Session*> m_Sessions;
+
+ void ReleaseSession(cpr::Session*);
+};
+
+} // namespace zen
diff --git a/src/zenhttp/httpclient.cpp b/src/zenhttp/httpclient.cpp
index 30a2bfc65..c5c808c23 100644
--- a/src/zenhttp/httpclient.cpp
+++ b/src/zenhttp/httpclient.cpp
@@ -5,8 +5,8 @@
#include <zenhttp/httpserver.h>
#include <zenhttp/packageformat.h>
-#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compositebuffer.h>
#include <zencore/except.h>
#include <zencore/filesystem.h>
@@ -19,902 +19,44 @@
#include <zencore/string.h>
#include <zencore/trace.h>
+#include "clients/httpclientcommon.h"
+
#if ZEN_WITH_TESTS
-# include <zencore/basicfile.h>
# include <zencore/testing.h>
# include <zencore/testutils.h>
#endif // ZEN_WITH_TESTS
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
-ZEN_THIRD_PARTY_INCLUDES_END
-
-#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
-# include <fcntl.h>
-# include <sys/stat.h>
-# include <unistd.h>
-#endif
-
-static std::atomic<uint32_t> HttpClientRequestIdCounter{0};
-
namespace zen {
-using namespace std::literals;
-
-namespace detail {
-
- static std::atomic_uint32_t TempFileBaseIndex;
-
- class TempPayloadFile
- {
- public:
- TempPayloadFile(const TempPayloadFile&) = delete;
- TempPayloadFile& operator=(const TempPayloadFile&) = delete;
-
- TempPayloadFile() : m_FileHandle(nullptr), m_WriteOffset(0) {}
- ~TempPayloadFile()
- {
- ZEN_TRACE_CPU("TempPayloadFile::Close");
- try
- {
- if (m_FileHandle)
- {
-#if ZEN_PLATFORM_WINDOWS
- // Mark file for deletion when final handle is closed
- FILE_DISPOSITION_INFO Fdi{.DeleteFile = TRUE};
-
- SetFileInformationByHandle(m_FileHandle, FileDispositionInfo, &Fdi, sizeof Fdi);
- BOOL Success = CloseHandle(m_FileHandle);
-#else
- std::error_code Ec;
- std::filesystem::path FilePath = zen::PathFromHandle(m_FileHandle, Ec);
- if (Ec)
- {
- ZEN_WARN("Error reported on get file path from handle {} for temp payload unlink operation, reason '{}'",
- m_FileHandle,
- Ec.message());
- }
- else
- {
- unlink(FilePath.c_str());
- }
- int Fd = int(uintptr_t(m_FileHandle));
- bool Success = (close(Fd) == 0);
-#endif
- if (!Success)
- {
- ZEN_WARN("Error reported on file handle close, reason '{}'", GetLastErrorAsString());
- }
-
- m_FileHandle = nullptr;
- }
- }
- catch (const std::exception& Ex)
- {
- ZEN_ERROR("Failed deleting temp file {}. Reason '{}'", m_FileHandle, Ex.what());
- }
- }
-
- std::error_code Open(const std::filesystem::path& TempFolderPath, uint64_t FinalSize)
- {
- ZEN_TRACE_CPU("TempPayloadFile::Open");
- ZEN_ASSERT(m_FileHandle == nullptr);
-
- std::uint64_t TmpIndex = ((std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) & 0xffffffffu) << 32) |
- detail::TempFileBaseIndex.fetch_add(1);
-
- std::filesystem::path FileName = TempFolderPath / fmt::to_string(TmpIndex);
-#if ZEN_PLATFORM_WINDOWS
- LPCWSTR lpFileName = FileName.c_str();
- const DWORD dwDesiredAccess = (GENERIC_READ | GENERIC_WRITE | DELETE);
- const DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
- LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr;
- const DWORD dwCreationDisposition = CREATE_ALWAYS;
- const DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
- const HANDLE hTemplateFile = nullptr;
- const HANDLE FileHandle = CreateFile(lpFileName,
- dwDesiredAccess,
- dwShareMode,
- lpSecurityAttributes,
- dwCreationDisposition,
- dwFlagsAndAttributes,
- hTemplateFile);
-
- if (FileHandle == INVALID_HANDLE_VALUE)
- {
- return MakeErrorCodeFromLastError();
- }
-#else // ZEN_PLATFORM_WINDOWS
- int OpenFlags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC;
- int Fd = open(FileName.c_str(), OpenFlags, 0666);
- if (Fd < 0)
- {
- return MakeErrorCodeFromLastError();
- }
- fchmod(Fd, 0666);
-
- void* FileHandle = (void*)(uintptr_t(Fd));
-#endif // ZEN_PLATFORM_WINDOWS
- m_FileHandle = FileHandle;
-
- PrepareFileForScatteredWrite(m_FileHandle, FinalSize);
-
- return {};
- }
-
- std::error_code Write(std::string_view DataString)
- {
- ZEN_TRACE_CPU("TempPayloadFile::Write");
- const uint8_t* DataPtr = (const uint8_t*)DataString.data();
- size_t DataSize = DataString.size();
- if (DataSize >= CacheBufferSize)
- {
- std::error_code Ec = Flush();
- if (Ec)
- {
- return Ec;
- }
- return AppendData(DataPtr, DataSize);
- }
- size_t CopySize = Min(DataSize, CacheBufferSize - m_CacheBufferOffset);
- memcpy(&m_CacheBuffer[m_CacheBufferOffset], DataPtr, CopySize);
- m_CacheBufferOffset += CopySize;
- DataSize -= CopySize;
- if (m_CacheBufferOffset == CacheBufferSize)
- {
- AppendData(m_CacheBuffer, CacheBufferSize);
- if (DataSize > 0)
- {
- ZEN_ASSERT(DataSize < CacheBufferSize);
- memcpy(m_CacheBuffer, DataPtr + CopySize, DataSize);
- }
- m_CacheBufferOffset = DataSize;
- }
- else
- {
- ZEN_ASSERT(DataSize == 0);
- }
- return {};
- }
-
- IoBuffer DetachToIoBuffer()
- {
- ZEN_TRACE_CPU("TempPayloadFile::DetachToIoBuffer");
- if (std::error_code Ec = Flush(); Ec)
- {
- ThrowSystemError(Ec.value(), Ec.message());
- }
- ZEN_ASSERT(m_FileHandle != nullptr);
- void* FileHandle = m_FileHandle;
- IoBuffer Buffer(IoBuffer::File, FileHandle, 0, m_WriteOffset, /*IsWholeFile*/ true);
- Buffer.SetDeleteOnClose(true);
- m_FileHandle = 0;
- m_WriteOffset = 0;
- return Buffer;
- }
-
- IoBuffer BorrowIoBuffer()
- {
- ZEN_TRACE_CPU("TempPayloadFile::BorrowIoBuffer");
- if (std::error_code Ec = Flush(); Ec)
- {
- ThrowSystemError(Ec.value(), Ec.message());
- }
- ZEN_ASSERT(m_FileHandle != nullptr);
- void* FileHandle = m_FileHandle;
- IoBuffer Buffer(IoBuffer::BorrowedFile, FileHandle, 0, m_WriteOffset);
- return Buffer;
- }
-
- uint64_t GetSize() const { return m_WriteOffset; }
- void ResetWritePos(uint64_t WriteOffset)
- {
- ZEN_TRACE_CPU("TempPayloadFile::ResetWritePos");
- Flush();
- m_WriteOffset = WriteOffset;
- }
-
- private:
- std::error_code Flush()
- {
- ZEN_TRACE_CPU("TempPayloadFile::Flush");
- if (m_CacheBufferOffset == 0)
- {
- return {};
- }
- std::error_code Res = AppendData(m_CacheBuffer, m_CacheBufferOffset);
- m_CacheBufferOffset = 0;
- return Res;
- }
-
- std::error_code AppendData(const void* Data, uint64_t Size)
- {
- ZEN_TRACE_CPU("TempPayloadFile::AppendData");
- ZEN_ASSERT(m_FileHandle != nullptr);
- const uint64_t MaxChunkSize = 2u * 1024 * 1024 * 1024;
-
- while (Size)
- {
- const uint64_t NumberOfBytesToWrite = Min(Size, MaxChunkSize);
- uint64_t NumberOfBytesWritten = 0;
-#if ZEN_PLATFORM_WINDOWS
- OVERLAPPED Ovl{};
-
- Ovl.Offset = DWORD(m_WriteOffset & 0xffff'ffffu);
- Ovl.OffsetHigh = DWORD(m_WriteOffset >> 32);
-
- DWORD dwNumberOfBytesWritten = 0;
+extern HttpClientBase* CreateCprHttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings);
- BOOL Success = ::WriteFile(m_FileHandle, Data, DWORD(NumberOfBytesToWrite), &dwNumberOfBytesWritten, &Ovl);
- if (Success)
- {
- NumberOfBytesWritten = static_cast<uint64_t>(dwNumberOfBytesWritten);
- }
-#else
- static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files");
- int Fd = int(uintptr_t(m_FileHandle));
- int BytesWritten = pwrite(Fd, Data, NumberOfBytesToWrite, m_WriteOffset);
- bool Success = (BytesWritten > 0);
- if (Success)
- {
- NumberOfBytesWritten = static_cast<uint64_t>(BytesWritten);
- }
-#endif
-
- if (!Success)
- {
- return MakeErrorCodeFromLastError();
- }
-
- Size -= NumberOfBytesWritten;
- m_WriteOffset += NumberOfBytesWritten;
- Data = reinterpret_cast<const uint8_t*>(Data) + NumberOfBytesWritten;
- }
- return {};
- }
-
- void* m_FileHandle;
- std::uint64_t m_WriteOffset;
- static constexpr uint64_t CacheBufferSize = 512u * 1024u;
- uint8_t m_CacheBuffer[CacheBufferSize];
- std::uint64_t m_CacheBufferOffset = 0;
- };
-
- class BufferedReadFileStream
- {
- public:
- BufferedReadFileStream(const BufferedReadFileStream&) = delete;
- BufferedReadFileStream& operator=(const BufferedReadFileStream&) = delete;
-
- BufferedReadFileStream(void* FileHandle, uint64_t FileOffset, uint64_t FileSize, uint64_t BufferSize)
- : m_FileHandle(FileHandle)
- , m_FileSize(FileSize)
- , m_FileEnd(FileOffset + FileSize)
- , m_BufferSize(Min(BufferSize, FileSize))
- , m_FileOffset(FileOffset)
- {
- }
-
- ~BufferedReadFileStream() { Memory::Free(m_Buffer); }
- void Read(void* Data, uint64_t Size)
- {
- ZEN_ASSERT(Data != nullptr);
- if (Size > m_BufferSize)
- {
- Read(Data, Size, m_FileOffset);
- m_FileOffset += Size;
- return;
- }
- uint8_t* WritePtr = ((uint8_t*)Data);
- uint64_t Begin = m_FileOffset;
- uint64_t End = m_FileOffset + Size;
- ZEN_ASSERT(m_FileOffset >= m_BufferStart);
- if (m_FileOffset < m_BufferEnd)
- {
- ZEN_ASSERT(m_Buffer != nullptr);
- uint64_t Count = Min(m_BufferEnd, End) - m_FileOffset;
- memcpy(WritePtr + Begin - m_FileOffset, m_Buffer + Begin - m_BufferStart, Count);
- Begin += Count;
- if (Begin == End)
- {
- m_FileOffset = End;
- return;
- }
- }
- if (End == m_FileEnd)
- {
- Read(WritePtr + Begin - m_FileOffset, End - Begin, Begin);
- }
- else
- {
- if (!m_Buffer)
- {
- m_BufferSize = Min(m_FileEnd - m_FileOffset, m_BufferSize);
- m_Buffer = (uint8_t*)Memory::Alloc(gsl::narrow<size_t>(m_BufferSize));
- }
- m_BufferStart = Begin;
- m_BufferEnd = Min(Begin + m_BufferSize, m_FileEnd);
- Read(m_Buffer, m_BufferEnd - m_BufferStart, m_BufferStart);
- uint64_t Count = Min(m_BufferEnd, End) - m_BufferStart;
- memcpy(WritePtr + Begin - m_FileOffset, m_Buffer, Count);
- ZEN_ASSERT(Begin + Count == End);
- }
- m_FileOffset = End;
- }
-
- private:
- void Read(void* Data, uint64_t BytesToRead, uint64_t FileOffset)
- {
- const uint64_t MaxChunkSize = 1u * 1024 * 1024;
- std::error_code Ec;
- ReadFile(m_FileHandle, Data, BytesToRead, FileOffset, MaxChunkSize, Ec);
-
- if (Ec)
- {
- std::error_code DummyEc;
- throw std::system_error(
- Ec,
- fmt::format(
- "HttpClient::BufferedReadFileStream ReadFile/pread failed (offset {:#x}, size {:#x}) file: '{}' (size {:#x})",
- FileOffset,
- BytesToRead,
- PathFromHandle(m_FileHandle, DummyEc).generic_string(),
- m_FileSize));
- }
- }
-
- void* m_FileHandle = nullptr;
- const uint64_t m_FileSize = 0;
- const uint64_t m_FileEnd = 0;
- uint64_t m_BufferSize = 0;
- uint8_t* m_Buffer = nullptr;
- uint64_t m_BufferStart = 0;
- uint64_t m_BufferEnd = 0;
- uint64_t m_FileOffset = 0;
- };
-
- class CompositeBufferReadStream
- {
- public:
- CompositeBufferReadStream(const CompositeBuffer& Data, uint64_t BufferSize)
- : m_Data(Data)
- , m_BufferSize(BufferSize)
- , m_SegmentIndex(0)
- , m_BytesLeftInSegment(0)
- {
- }
- uint64_t Read(void* Data, uint64_t Size)
- {
- uint64_t Result = 0;
- uint8_t* WritePtr = (uint8_t*)Data;
- while ((Size > 0) && (m_SegmentIndex < m_Data.GetSegments().size()))
- {
- if (m_BytesLeftInSegment == 0)
- {
- const SharedBuffer& Segment = m_Data.GetSegments()[m_SegmentIndex];
- IoBufferFileReference FileRef = {nullptr, 0, 0};
- if (Segment.AsIoBuffer().GetFileReference(FileRef))
- {
- m_SegmentDiskBuffer = std::make_unique<BufferedReadFileStream>(FileRef.FileHandle,
- FileRef.FileChunkOffset,
- FileRef.FileChunkSize,
- m_BufferSize);
- }
- else
- {
- m_SegmentMemoryBuffer = Segment.GetView();
- }
- m_BytesLeftInSegment = Segment.GetSize();
- }
- uint64_t BytesToRead = Min(m_BytesLeftInSegment, Size);
- if (m_SegmentDiskBuffer)
- {
- m_SegmentDiskBuffer->Read(WritePtr, BytesToRead);
- }
- else
- {
- ZEN_ASSERT_SLOW(m_SegmentMemoryBuffer.GetSize() >= BytesToRead);
- memcpy(WritePtr, m_SegmentMemoryBuffer.GetData(), BytesToRead);
- m_SegmentMemoryBuffer.MidInline(BytesToRead);
- }
- WritePtr += BytesToRead;
- Size -= BytesToRead;
- Result += BytesToRead;
-
- m_BytesLeftInSegment -= BytesToRead;
- if (m_BytesLeftInSegment == 0)
- {
- m_SegmentDiskBuffer.reset();
- m_SegmentMemoryBuffer.Reset();
- m_SegmentIndex++;
- }
- }
- return Result;
- }
-
- private:
- const CompositeBuffer& m_Data;
- const uint64_t m_BufferSize;
- size_t m_SegmentIndex;
- std::unique_ptr<BufferedReadFileStream> m_SegmentDiskBuffer;
- MemoryView m_SegmentMemoryBuffer;
- uint64_t m_BytesLeftInSegment;
- };
-
-} // namespace detail
-
-//////////////////////////////////////////////////////////////////////////
-//
-// CPR helpers
-
-static cpr::Body
-AsCprBody(const CbObject& Obj)
-{
- return cpr::Body((const char*)Obj.GetBuffer().GetData(), Obj.GetBuffer().GetSize());
-}
-
-static cpr::Body
-AsCprBody(const IoBuffer& Obj)
-{
- return cpr::Body((const char*)Obj.GetData(), Obj.GetSize());
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-static HttpClient::Response
-ResponseWithPayload(std::string_view SessionId, cpr::Response& HttpResponse, const HttpResponseCode WorkResponseCode, IoBuffer&& Payload)
-{
- // This ends up doing a memcpy, would be good to get rid of it by streaming results
- // into buffer directly
- IoBuffer ResponseBuffer = Payload ? std::move(Payload) : IoBuffer(IoBuffer::Clone, HttpResponse.text.data(), HttpResponse.text.size());
-
- if (auto It = HttpResponse.header.find("Content-Type"); It != HttpResponse.header.end())
- {
- const HttpContentType ContentType = ParseContentType(It->second);
-
- ResponseBuffer.SetContentType(ContentType);
- }
-
- if (!IsHttpSuccessCode(WorkResponseCode) && WorkResponseCode != HttpResponseCode::NotFound)
- {
- ZEN_WARN("HttpClient request failed (session: {}): {}", SessionId, HttpResponse);
- }
-
- return HttpClient::Response{.StatusCode = WorkResponseCode,
- .ResponsePayload = std::move(ResponseBuffer),
- .Header = HttpClient::KeyValueMap(HttpResponse.header.begin(), HttpResponse.header.end()),
- .UploadedBytes = gsl::narrow<int64_t>(HttpResponse.uploaded_bytes),
- .DownloadedBytes = gsl::narrow<int64_t>(HttpResponse.downloaded_bytes),
- .ElapsedSeconds = HttpResponse.elapsed};
-}
-
-static HttpClient::Response
-CommonResponse(std::string_view SessionId, cpr::Response&& HttpResponse, IoBuffer&& Payload = {})
-{
- const HttpResponseCode WorkResponseCode = HttpResponseCode(HttpResponse.status_code);
- if (HttpResponse.error)
- {
- if (HttpResponse.error.code != cpr::ErrorCode::OPERATION_TIMEDOUT &&
- HttpResponse.error.code != cpr::ErrorCode::CONNECTION_FAILURE && HttpResponse.error.code != cpr::ErrorCode::REQUEST_CANCELLED)
- {
- ZEN_WARN("HttpClient client failure (session: {}): {}", SessionId, HttpResponse);
- }
-
- // Client side failure code
- return HttpClient::Response{
- .StatusCode = WorkResponseCode,
- .ResponsePayload = IoBufferBuilder::MakeCloneFromMemory(HttpResponse.text.data(), HttpResponse.text.size()),
- .Header = HttpClient::KeyValueMap(HttpResponse.header.begin(), HttpResponse.header.end()),
- .UploadedBytes = gsl::narrow<int64_t>(HttpResponse.uploaded_bytes),
- .DownloadedBytes = gsl::narrow<int64_t>(HttpResponse.downloaded_bytes),
- .ElapsedSeconds = HttpResponse.elapsed,
- .Error = HttpClient::ErrorContext{.ErrorCode = gsl::narrow<int>(HttpResponse.error.code),
- .ErrorMessage = HttpResponse.error.message}};
- }
-
- if (WorkResponseCode == HttpResponseCode::NoContent || (HttpResponse.text.empty() && !Payload))
- {
- return HttpClient::Response{.StatusCode = WorkResponseCode,
- .Header = HttpClient::KeyValueMap(HttpResponse.header.begin(), HttpResponse.header.end()),
- .UploadedBytes = gsl::narrow<int64_t>(HttpResponse.uploaded_bytes),
- .DownloadedBytes = gsl::narrow<int64_t>(HttpResponse.downloaded_bytes),
- .ElapsedSeconds = HttpResponse.elapsed};
- }
- else
- {
- return ResponseWithPayload(
- SessionId,
- HttpResponse,
- WorkResponseCode,
- Payload ? std::move(Payload) : IoBufferBuilder::MakeCloneFromMemory(HttpResponse.text.data(), HttpResponse.text.size()));
- }
-}
-
-static bool
-ShouldRetry(const cpr::Response& Response)
-{
- switch (Response.error.code)
- {
- case cpr::ErrorCode::OK:
- break;
- case cpr::ErrorCode::INTERNAL_ERROR:
- case cpr::ErrorCode::NETWORK_RECEIVE_ERROR:
- case cpr::ErrorCode::NETWORK_SEND_FAILURE:
- case cpr::ErrorCode::OPERATION_TIMEDOUT:
- return true;
- default:
- return false;
- }
- switch ((HttpResponseCode)Response.status_code)
- {
- case HttpResponseCode::RequestTimeout:
- case HttpResponseCode::TooManyRequests:
- case HttpResponseCode::InternalServerError:
- case HttpResponseCode::BadGateway:
- case HttpResponseCode::ServiceUnavailable:
- case HttpResponseCode::GatewayTimeout:
- return true;
- default:
- return false;
- }
-};
-
-static bool
-ValidatePayload(cpr::Response& Response, std::unique_ptr<detail::TempPayloadFile>& PayloadFile)
-{
- ZEN_TRACE_CPU("ValidatePayload");
- IoBuffer ResponseBuffer = (Response.text.empty() && PayloadFile) ? PayloadFile->BorrowIoBuffer()
- : IoBuffer(IoBuffer::Wrap, Response.text.data(), Response.text.size());
-
- if (auto ContentLength = Response.header.find("Content-Length"); ContentLength != Response.header.end())
- {
- std::optional<uint64_t> ExpectedContentSize = ParseInt<uint64_t>(ContentLength->second);
- if (!ExpectedContentSize.has_value())
- {
- Response.error =
- cpr::Error(/*CURLE_READ_ERROR*/ 26, fmt::format("Can not parse Content-Length header. Value: '{}'", ContentLength->second));
- return false;
- }
- if (ExpectedContentSize.value() != ResponseBuffer.GetSize())
- {
- Response.error = cpr::Error(
- /*CURLE_READ_ERROR*/ 26,
- fmt::format("Payload size {} does not match Content-Length {}", ResponseBuffer.GetSize(), ContentLength->second));
- return false;
- }
- }
-
- if (Response.status_code == (long)HttpResponseCode::PartialContent)
- {
- return true;
- }
-
- if (auto JupiterHash = Response.header.find("X-Jupiter-IoHash"); JupiterHash != Response.header.end())
- {
- IoHash ExpectedPayloadHash;
- if (IoHash::TryParse(JupiterHash->second, ExpectedPayloadHash))
- {
- IoHash PayloadHash = IoHash::HashBuffer(ResponseBuffer);
- if (PayloadHash != ExpectedPayloadHash)
- {
- Response.error = cpr::Error(/*CURLE_READ_ERROR*/ 26,
- fmt::format("Payload hash {} does not match X-Jupiter-IoHash {}",
- PayloadHash.ToHexString(),
- ExpectedPayloadHash.ToHexString()));
- return false;
- }
- }
- }
-
- if (auto ContentType = Response.header.find("Content-Type"); ContentType != Response.header.end())
- {
- if (ContentType->second == "application/x-ue-comp")
- {
- IoHash RawHash;
- uint64_t RawSize;
- if (CompressedBuffer::ValidateCompressedHeader(ResponseBuffer, RawHash, RawSize))
- {
- return true;
- }
- else
- {
- Response.error = cpr::Error(/*CURLE_READ_ERROR*/ 26, "Compressed binary failed validation");
- return false;
- }
- }
- if (ContentType->second == "application/x-ue-cb")
- {
- if (CbValidateError Error = ValidateCompactBinary(ResponseBuffer.GetView(), CbValidateMode::Default);
- Error == CbValidateError::None)
- {
- return true;
- }
- else
- {
- Response.error = cpr::Error(/*CURLE_READ_ERROR*/ 26, fmt::format("Compact binary failed validation: {}", ToString(Error)));
- return false;
- }
- }
- }
-
- return true;
-}
-
-static cpr::Response
-DoWithRetry(
- std::string_view SessionId,
- std::function<cpr::Response()>&& Func,
- uint8_t RetryCount,
- std::function<bool(cpr::Response& Result)>&& Validate = [](cpr::Response&) { return true; })
-{
- uint8_t Attempt = 0;
- cpr::Response Result = Func();
- while (Attempt < RetryCount)
- {
- if (!ShouldRetry(Result))
- {
- if (Result.error || !IsHttpSuccessCode(Result.status_code))
- {
- break;
- }
- if (Validate(Result))
- {
- break;
- }
- }
- Sleep(100 * (Attempt + 1));
- Attempt++;
- ZEN_INFO("{} Attempt {}/{}", CommonResponse(SessionId, std::move(Result)).ErrorMessage("Retry"), Attempt, RetryCount + 1);
- Result = Func();
- }
- return Result;
-}
-
-static cpr::Response
-DoWithRetry(std::string_view SessionId,
- std::function<cpr::Response()>&& Func,
- std::unique_ptr<detail::TempPayloadFile>& PayloadFile,
- uint8_t RetryCount)
-{
- uint8_t Attempt = 0;
- cpr::Response Result = Func();
- while (Attempt < RetryCount)
- {
- if (!ShouldRetry(Result))
- {
- if (Result.error || !IsHttpSuccessCode(Result.status_code))
- {
- break;
- }
- if (ValidatePayload(Result, PayloadFile))
- {
- break;
- }
- }
- Sleep(100 * (Attempt + 1));
- Attempt++;
- ZEN_INFO("{} Attempt {}/{}", CommonResponse(SessionId, std::move(Result)).ErrorMessage("Retry"), Attempt, RetryCount + 1);
- Result = Func();
- }
- return Result;
-}
-
-static std::pair<std::string, std::string>
-HeaderContentType(ZenContentType ContentType)
-{
- return std::make_pair("Content-Type", std::string(MapContentTypeToString(ContentType)));
-}
+using namespace std::literals;
//////////////////////////////////////////////////////////////////////////
-struct HttpClient::Impl : public RefCounted
-{
- Impl(LoggerRef Log);
- ~Impl();
-
- // Session allocation
-
- struct Session
- {
- Session(Impl* InOuter, cpr::Session* InSession) : Outer(InOuter), CprSession(InSession) {}
- ~Session() { Outer->ReleaseSession(CprSession); }
-
- inline cpr::Session* operator->() const { return CprSession; }
- inline cpr::Response Get()
- {
- ZEN_TRACE_CPU("HttpClient::Impl::Get");
- cpr::Response Result = CprSession->Get();
- ZEN_TRACE("GET {}", Result);
- return Result;
- }
- inline cpr::Response Download(cpr::WriteCallback&& Write, std::optional<cpr::HeaderCallback>&& Header = {})
- {
- ZEN_TRACE_CPU("HttpClient::Impl::Download");
- if (Header)
- {
- CprSession->SetHeaderCallback(std::move(Header.value()));
- }
- cpr::Response Result = CprSession->Download(Write);
- ZEN_TRACE("GET {}", Result);
- CprSession->SetHeaderCallback({});
- CprSession->SetWriteCallback({});
- return Result;
- }
- inline cpr::Response Head()
- {
- ZEN_TRACE_CPU("HttpClient::Impl::Head");
- cpr::Response Result = CprSession->Head();
- ZEN_TRACE("HEAD {}", Result);
- return Result;
- }
- inline cpr::Response Put(std::optional<cpr::ReadCallback>&& Read = {})
- {
- ZEN_TRACE_CPU("HttpClient::Impl::Put");
- if (Read)
- {
- CprSession->SetReadCallback(std::move(Read.value()));
- }
- cpr::Response Result = CprSession->Put();
- ZEN_TRACE("PUT {}", Result);
- CprSession->SetReadCallback({});
- return Result;
- }
- inline cpr::Response Post(std::optional<cpr::ReadCallback>&& Read = {})
- {
- ZEN_TRACE_CPU("HttpClient::Impl::Post");
- if (Read)
- {
- CprSession->SetReadCallback(std::move(Read.value()));
- }
- cpr::Response Result = CprSession->Post();
- ZEN_TRACE("POST {}", Result);
- CprSession->SetReadCallback({});
- return Result;
- }
- inline cpr::Response Delete()
- {
- ZEN_TRACE_CPU("HttpClient::Impl::Delete");
- cpr::Response Result = CprSession->Delete();
- ZEN_TRACE("DELETE {}", Result);
- return Result;
- }
-
- LoggerRef Logger() { return Outer->Logger(); }
-
- private:
- Impl* Outer;
- cpr::Session* CprSession;
-
- Session(Session&&) = delete;
- Session& operator=(Session&&) = delete;
- };
-
- Session AllocSession(const std::string_view BaseUrl,
- const std::string_view Url,
- const HttpClientSettings& ConnectionSettings,
- const KeyValueMap& AdditionalHeader,
- const KeyValueMap& Parameters,
- const std::string_view SessionId,
- std::optional<HttpClientAccessToken> AccessToken);
-
- LoggerRef Logger() { return m_Log; }
-
-private:
- LoggerRef m_Log;
- RwLock m_SessionLock;
- std::vector<cpr::Session*> m_Sessions;
-
- void ReleaseSession(cpr::Session*);
-};
-
-HttpClient::Impl::Impl(LoggerRef Log) : m_Log(Log)
-{
-}
-
-HttpClient::Impl::~Impl()
-{
- ZEN_TRACE_CPU("HttpClient::Impl::~Impl");
- m_SessionLock.WithExclusiveLock([&] {
- for (auto CprSession : m_Sessions)
- {
- delete CprSession;
- }
- m_Sessions.clear();
- });
-}
-
-HttpClient::Impl::Session
-HttpClient::Impl::AllocSession(const std::string_view BaseUrl,
- const std::string_view ResourcePath,
- const HttpClientSettings& ConnectionSettings,
- const KeyValueMap& AdditionalHeader,
- const KeyValueMap& Parameters,
- const std::string_view SessionId,
- std::optional<HttpClientAccessToken> AccessToken)
+HttpClientBase::HttpClientBase(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings)
+: m_Log(zen::logging::Get(ConnectionSettings.LogCategory))
+, m_BaseUri(BaseUri)
+, m_ConnectionSettings(ConnectionSettings)
{
- ZEN_TRACE_CPU("HttpClient::Impl::AllocSession");
- cpr::Session* CprSession = nullptr;
- m_SessionLock.WithExclusiveLock([&] {
- if (!m_Sessions.empty())
- {
- CprSession = m_Sessions.back();
- m_Sessions.pop_back();
- }
- });
-
- if (CprSession == nullptr)
- {
- CprSession = new cpr::Session();
- CprSession->SetConnectTimeout(ConnectionSettings.ConnectTimeout);
- CprSession->SetTimeout(ConnectionSettings.Timeout);
- if (ConnectionSettings.AssumeHttp2)
- {
- CprSession->SetHttpVersion(cpr::HttpVersion{cpr::HttpVersionCode::VERSION_2_0_PRIOR_KNOWLEDGE});
- }
- }
-
- if (!AdditionalHeader->empty())
- {
- CprSession->SetHeader(cpr::Header(AdditionalHeader->begin(), AdditionalHeader->end()));
- }
- if (!SessionId.empty())
- {
- CprSession->UpdateHeader({{"UE-Session", std::string(SessionId)}});
- }
- if (AccessToken)
- {
- CprSession->UpdateHeader({{"Authorization", AccessToken->Value}});
- }
- if (!Parameters->empty())
+ if (ConnectionSettings.SessionId == Oid::Zero)
{
- cpr::Parameters Tmp;
- for (auto It = Parameters->begin(); It != Parameters->end(); It++)
- {
- Tmp.Add({It->first, It->second});
- }
- CprSession->SetParameters(Tmp);
+ m_SessionId = GetSessionIdString();
}
else
{
- CprSession->SetParameters({});
+ m_SessionId = ConnectionSettings.SessionId.ToString();
}
-
- ExtendableStringBuilder<128> UrlBuffer;
- UrlBuffer << BaseUrl << ResourcePath;
- CprSession->SetUrl(UrlBuffer.c_str());
-
- return Session(this, CprSession);
}
-void
-HttpClient::Impl::ReleaseSession(cpr::Session* CprSession)
-{
- ZEN_TRACE_CPU("HttpClient::Impl::ReleaseSession");
- CprSession->SetUrl({});
- CprSession->SetHeader({});
- CprSession->SetBody({});
- m_SessionLock.WithExclusiveLock([&] { m_Sessions.push_back(CprSession); });
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-HttpClient::HttpClient(std::string_view BaseUri, const HttpClientSettings& Connectionsettings)
-: m_Log(zen::logging::Get(Connectionsettings.LogCategory))
-, m_BaseUri(BaseUri)
-, m_ConnectionSettings(Connectionsettings)
-, m_Impl(new Impl(m_Log))
-{
- m_SessionId = GetSessionIdString();
-}
-
-HttpClient::~HttpClient()
+HttpClientBase::~HttpClientBase()
{
}
bool
-HttpClient::Authenticate()
+HttpClientBase::Authenticate()
{
- ZEN_TRACE_CPU("HttpClient::Authenticate");
+ ZEN_TRACE_CPU("HttpClientBase::Authenticate");
std::optional<HttpClientAccessToken> Token = GetAccessToken();
if (!Token)
{
@@ -924,9 +66,9 @@ HttpClient::Authenticate()
}
const std::optional<HttpClientAccessToken>
-HttpClient::GetAccessToken()
+HttpClientBase::GetAccessToken()
{
- ZEN_TRACE_CPU("HttpClient::GetAccessToken");
+ ZEN_TRACE_CPU("HttpClientBase::GetAccessToken");
if (!m_ConnectionSettings.AccessTokenProvider.has_value())
{
return {};
@@ -947,614 +89,19 @@ HttpClient::GetAccessToken()
return m_CachedAccessToken;
}
-HttpClient::Response
-HttpClient::TransactPackage(std::string_view Url, CbPackage Package, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::TransactPackage");
-
- Impl::Session Sess = m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
-
- // First, list of offered chunks for filtering on the server end
-
- std::vector<IoHash> AttachmentsToSend;
- std::span<const CbAttachment> Attachments = Package.GetAttachments();
-
- const uint32_t RequestId = ++HttpClientRequestIdCounter;
- auto RequestIdString = fmt::to_string(RequestId);
-
- if (Attachments.empty() == false)
- {
- CbObjectWriter Writer;
- Writer.BeginArray("offer");
-
- for (const CbAttachment& Attachment : Attachments)
- {
- Writer.AddHash(Attachment.GetHash());
- }
-
- Writer.EndArray();
-
- BinaryWriter MemWriter;
- Writer.Save(MemWriter);
-
- Sess->UpdateHeader({HeaderContentType(HttpContentType::kCbPackageOffer), {"UE-Request", RequestIdString}});
- Sess->SetBody(cpr::Body{(const char*)MemWriter.Data(), MemWriter.Size()});
-
- cpr::Response FilterResponse = Sess.Post();
-
- if (FilterResponse.status_code == 200)
- {
- IoBuffer ResponseBuffer(IoBuffer::Wrap, FilterResponse.text.data(), FilterResponse.text.size());
- CbObject ResponseObject = LoadCompactBinaryObject(ResponseBuffer);
-
- for (CbFieldView& Entry : ResponseObject["need"])
- {
- ZEN_ASSERT(Entry.IsHash());
- AttachmentsToSend.push_back(Entry.AsHash());
- }
- }
- }
-
- // Prepare package for send
-
- CbPackage SendPackage;
- SendPackage.SetObject(Package.GetObject(), Package.GetObjectHash());
-
- for (const IoHash& AttachmentCid : AttachmentsToSend)
- {
- const CbAttachment* Attachment = Package.FindAttachment(AttachmentCid);
-
- if (Attachment)
- {
- SendPackage.AddAttachment(*Attachment);
- }
- else
- {
- // This should be an error -- server asked to have something we can't find
- }
- }
-
- // Transmit package payload
-
- CompositeBuffer Message = FormatPackageMessageBuffer(SendPackage);
- SharedBuffer FlatMessage = Message.Flatten();
-
- Sess->UpdateHeader({HeaderContentType(HttpContentType::kCbPackage), {"UE-Request", RequestIdString}});
- Sess->SetBody(cpr::Body{(const char*)FlatMessage.GetData(), FlatMessage.GetSize()});
-
- cpr::Response FilterResponse = Sess.Post();
-
- if (!IsHttpSuccessCode(FilterResponse.status_code))
- {
- return {.StatusCode = HttpResponseCode(FilterResponse.status_code)};
- }
-
- IoBuffer ResponseBuffer(IoBuffer::Clone, FilterResponse.text.data(), FilterResponse.text.size());
-
- if (auto It = FilterResponse.header.find("Content-Type"); It != FilterResponse.header.end())
- {
- HttpContentType ContentType = ParseContentType(It->second);
-
- ResponseBuffer.SetContentType(ContentType);
- }
-
- return {.StatusCode = HttpResponseCode(FilterResponse.status_code), .ResponsePayload = ResponseBuffer};
-}
-
-//////////////////////////////////////////////////////////////////////////
-//
-// Standard HTTP verbs
-//
-
-HttpClient::Response
-HttpClient::Put(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::Put");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- Sess->SetBody(AsCprBody(Payload));
- Sess->UpdateHeader({HeaderContentType(Payload.GetContentType())});
- return Sess.Put();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Put(std::string_view Url, const KeyValueMap& Parameters)
-{
- ZEN_TRACE_CPU("HttpClient::Put");
-
- return CommonResponse(m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess = m_Impl->AllocSession(m_BaseUri,
- Url,
- m_ConnectionSettings,
- {{"Content-Length", "0"}},
- Parameters,
- m_SessionId,
- GetAccessToken());
- return Sess.Put();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Get(std::string_view Url, const KeyValueMap& AdditionalHeader, const KeyValueMap& Parameters)
-{
- ZEN_TRACE_CPU("HttpClient::Get");
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, Parameters, m_SessionId, GetAccessToken());
- return Sess.Get();
- },
- m_ConnectionSettings.RetryCount,
- [](cpr::Response& Result) {
- std::unique_ptr<detail::TempPayloadFile> NoTempFile;
- return ValidatePayload(Result, NoTempFile);
- }));
-}
-
-HttpClient::Response
-HttpClient::Head(std::string_view Url, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::Head");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- return Sess.Head();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Delete(std::string_view Url, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::Delete");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- return Sess.Delete();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Post(std::string_view Url, const KeyValueMap& AdditionalHeader, const KeyValueMap& Parameters)
-{
- ZEN_TRACE_CPU("HttpClient::PostNoPayload");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, Parameters, m_SessionId, GetAccessToken());
- return Sess.Post();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Post(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader)
-{
- return Post(Url, Payload, Payload.GetContentType(), AdditionalHeader);
-}
-
-HttpClient::Response
-HttpClient::Post(std::string_view Url, const IoBuffer& Payload, ZenContentType ContentType, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::PostWithPayload");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- Sess->UpdateHeader({HeaderContentType(ContentType)});
-
- IoBufferFileReference FileRef = {nullptr, 0, 0};
- if (Payload.GetFileReference(FileRef))
- {
- uint64_t Offset = 0;
- detail::BufferedReadFileStream Buffer(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize, 512u * 1024u);
- auto ReadCallback = [&Payload, &Offset, &Buffer](char* buffer, size_t& size, intptr_t) {
- size = Min<size_t>(size, Payload.GetSize() - Offset);
- Buffer.Read(buffer, size);
- Offset += size;
- return true;
- };
- return Sess.Post(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
- }
- Sess->SetBody(AsCprBody(Payload));
- return Sess.Post();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Post(std::string_view Url, CbObject Payload, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::PostObjectPayload");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
-
- Sess->SetBody(AsCprBody(Payload));
- Sess->UpdateHeader({HeaderContentType(ZenContentType::kCbObject)});
- return Sess.Post();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Post(std::string_view Url, CbPackage Pkg, const KeyValueMap& AdditionalHeader)
-{
- return Post(Url, zen::FormatPackageMessageBuffer(Pkg), ZenContentType::kCbPackage, AdditionalHeader);
-}
-
-HttpClient::Response
-HttpClient::Post(std::string_view Url, const CompositeBuffer& Payload, ZenContentType ContentType, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::Post");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- Sess->UpdateHeader({HeaderContentType(ContentType)});
-
- detail::CompositeBufferReadStream Reader(Payload, 512u * 1024u);
- auto ReadCallback = [&Reader](char* buffer, size_t& size, intptr_t) {
- size = Reader.Read(buffer, size);
- return true;
- };
- return Sess.Post(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::Upload");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- Sess->UpdateHeader({HeaderContentType(Payload.GetContentType())});
-
- IoBufferFileReference FileRef = {nullptr, 0, 0};
- if (Payload.GetFileReference(FileRef))
- {
- uint64_t Offset = 0;
- detail::BufferedReadFileStream Buffer(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize, 512u * 1024u);
- auto ReadCallback = [&Payload, &Offset, &Buffer](char* buffer, size_t& size, intptr_t) {
- size = Min<size_t>(size, Payload.GetSize() - Offset);
- Buffer.Read(buffer, size);
- Offset += size;
- return true;
- };
- return Sess.Put(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
- }
- Sess->SetBody(AsCprBody(Payload));
- return Sess.Put();
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Upload(std::string_view Url, const CompositeBuffer& Payload, ZenContentType ContentType, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::Upload");
-
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() {
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- Sess->UpdateHeader({HeaderContentType(ContentType)});
-
- detail::CompositeBufferReadStream Reader(Payload, 512u * 1024u);
- auto ReadCallback = [&Reader](char* buffer, size_t& size, intptr_t) {
- size = Reader.Read(buffer, size);
- return true;
- };
- return Sess.Put(cpr::ReadCallback(gsl::narrow<cpr::cpr_off_t>(Payload.GetSize()), ReadCallback));
- },
- m_ConnectionSettings.RetryCount));
-}
-
-HttpClient::Response
-HttpClient::Download(std::string_view Url, const std::filesystem::path& TempFolderPath, const KeyValueMap& AdditionalHeader)
-{
- ZEN_TRACE_CPU("HttpClient::Download");
-
- std::string PayloadString;
- std::unique_ptr<detail::TempPayloadFile> PayloadFile;
- cpr::Response Response = DoWithRetry(
- m_SessionId,
- [&]() {
- auto GetHeader = [&](std::string header) -> std::pair<std::string, std::string> {
- size_t DelimiterPos = header.find(':');
- if (DelimiterPos != std::string::npos)
- {
- std::string Key = header.substr(0, DelimiterPos);
- constexpr AsciiSet WhitespaceCharacters(" \v\f\t\r\n");
- Key = AsciiSet::TrimSuffixWith(Key, WhitespaceCharacters);
- Key = AsciiSet::TrimPrefixWith(Key, WhitespaceCharacters);
-
- std::string Value = header.substr(DelimiterPos + 1);
- Value = AsciiSet::TrimSuffixWith(Value, WhitespaceCharacters);
- Value = AsciiSet::TrimPrefixWith(Value, WhitespaceCharacters);
-
- return std::make_pair(Key, Value);
- }
- return std::make_pair(header, "");
- };
-
- auto DownloadCallback = [&](std::string data, intptr_t) {
- if (PayloadFile)
- {
- ZEN_ASSERT(PayloadString.empty());
- std::error_code Ec = PayloadFile->Write(data);
- if (Ec)
- {
- ZEN_WARN("Failed to write to temp file in '{}' for HttpClient::Download. Reason: {}",
- TempFolderPath.string(),
- Ec.message());
- return false;
- }
- }
- else
- {
- PayloadString.append(data);
- }
- return true;
- };
-
- uint64_t RequestedContentLength = (uint64_t)-1;
- if (auto RangeIt = AdditionalHeader.Entries.find("Range"); RangeIt != AdditionalHeader.Entries.end())
- {
- if (RangeIt->second.starts_with("bytes"))
- {
- size_t RangeStartPos = RangeIt->second.find('=', 5);
- if (RangeStartPos != std::string::npos)
- {
- RangeStartPos++;
- size_t RangeSplitPos = RangeIt->second.find('-', RangeStartPos);
- if (RangeSplitPos != std::string::npos)
- {
- std::optional<size_t> RequestedRangeStart =
- ParseInt<size_t>(RangeIt->second.substr(RangeStartPos, RangeSplitPos - RangeStartPos));
- std::optional<size_t> RequestedRangeEnd = ParseInt<size_t>(RangeIt->second.substr(RangeStartPos + 1));
- if (RequestedRangeStart.has_value() && RequestedRangeEnd.has_value())
- {
- RequestedContentLength = RequestedRangeEnd.value() - 1;
- }
- }
- }
- }
- }
-
- cpr::Response Response;
- {
- std::vector<std::pair<std::string, std::string>> ReceivedHeaders;
- auto HeaderCallback = [&](std::string header, intptr_t) {
- std::pair<std::string, std::string> Header = GetHeader(header);
- if (Header.first == "Content-Length"sv)
- {
- std::optional<size_t> ContentLength = ParseInt<size_t>(Header.second);
- if (ContentLength.has_value())
- {
- if (ContentLength.value() > 1024 * 1024)
- {
- PayloadFile = std::make_unique<detail::TempPayloadFile>();
- std::error_code Ec = PayloadFile->Open(TempFolderPath, ContentLength.value());
- if (Ec)
- {
- ZEN_WARN("Failed to create temp file in '{}' for HttpClient::Download. Reason: {}",
- TempFolderPath.string(),
- Ec.message());
- PayloadFile.reset();
- }
- }
- else
- {
- PayloadString.reserve(ContentLength.value());
- }
- }
- }
- if (!Header.first.empty())
- {
- ReceivedHeaders.emplace_back(std::move(Header));
- }
- return 1;
- };
-
- Impl::Session Sess =
- m_Impl->AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- Response = Sess.Download(cpr::WriteCallback{DownloadCallback}, cpr::HeaderCallback{HeaderCallback});
- for (const std::pair<std::string, std::string>& H : ReceivedHeaders)
- {
- Response.header.insert_or_assign(H.first, H.second);
- }
- }
- if (m_ConnectionSettings.AllowResume)
- {
- auto SupportsRanges = [](const cpr::Response& Response) -> bool {
- if (Response.header.find("Content-Range") != Response.header.end())
- {
- return true;
- }
- if (auto It = Response.header.find("Accept-Ranges"); It != Response.header.end())
- {
- return It->second == "bytes"sv;
- }
- return false;
- };
-
- auto ShouldResume = [&SupportsRanges](const cpr::Response& Response) -> bool {
- if (ShouldRetry(Response))
- {
- return SupportsRanges(Response);
- }
- return false;
- };
-
- if (ShouldResume(Response))
- {
- auto It = Response.header.find("Content-Length");
- if (It != Response.header.end())
- {
- std::vector<std::pair<std::string, std::string>> ReceivedHeaders;
-
- auto HeaderCallback = [&](std::string header, intptr_t) {
- std::pair<std::string, std::string> Header = GetHeader(header);
- if (!Header.first.empty())
- {
- ReceivedHeaders.emplace_back(std::move(Header));
- }
-
- if (Header.first == "Content-Range"sv)
- {
- if (Header.second.starts_with("bytes "sv))
- {
- size_t RangeStartEnd = Header.second.find('-', 6);
- if (RangeStartEnd != std::string::npos)
- {
- const auto Start = ParseInt<uint64_t>(Header.second.substr(6, RangeStartEnd - 6));
- if (Start)
- {
- uint64_t DownloadedSize = PayloadFile ? PayloadFile->GetSize() : PayloadString.length();
- if (Start.value() == DownloadedSize)
- {
- return 1;
- }
- else if (Start.value() > DownloadedSize)
- {
- return 0;
- }
- if (PayloadFile)
- {
- PayloadFile->ResetWritePos(Start.value());
- }
- else
- {
- PayloadString = PayloadString.substr(0, Start.value());
- }
- return 1;
- }
- }
- }
- return 0;
- }
- return 1;
- };
-
- KeyValueMap HeadersWithRange(AdditionalHeader);
- do
- {
- uint64_t DownloadedSize = PayloadFile ? PayloadFile->GetSize() : PayloadString.length();
-
- uint64_t ContentLength = RequestedContentLength;
- if (ContentLength == uint64_t(-1))
- {
- if (auto ParsedContentLength = ParseInt<int64_t>(It->second); ParsedContentLength.has_value())
- {
- ContentLength = ParsedContentLength.value();
- }
- }
-
- std::string Range = fmt::format("bytes={}-{}", DownloadedSize, DownloadedSize + ContentLength - 1);
- if (auto RangeIt = HeadersWithRange.Entries.find("Range"); RangeIt != HeadersWithRange.Entries.end())
- {
- if (RangeIt->second == Range)
- {
- // If we didn't make any progress, abort
- break;
- }
- }
- HeadersWithRange.Entries.insert_or_assign("Range", Range);
-
- Impl::Session Sess = m_Impl->AllocSession(m_BaseUri,
- Url,
- m_ConnectionSettings,
- HeadersWithRange,
- {},
- m_SessionId,
- GetAccessToken());
- Response = Sess.Download(cpr::WriteCallback{DownloadCallback}, cpr::HeaderCallback{HeaderCallback});
- for (const std::pair<std::string, std::string>& H : ReceivedHeaders)
- {
- Response.header.insert_or_assign(H.first, H.second);
- }
- ReceivedHeaders.clear();
- } while (ShouldResume(Response));
- }
- }
- }
-
- if (!PayloadString.empty())
- {
- Response.text = std::move(PayloadString);
- }
- return Response;
- },
- PayloadFile,
- m_ConnectionSettings.RetryCount);
-
- return CommonResponse(m_SessionId, std::move(Response), PayloadFile ? PayloadFile->DetachToIoBuffer() : IoBuffer{});
-}
-
//////////////////////////////////////////////////////////////////////////
CbObject
HttpClient::Response::AsObject() const
{
- // TODO: sanity check the payload format etc
-
if (ResponsePayload)
{
- return LoadCompactBinaryObject(ResponsePayload);
+ CbValidateError ValidationError = CbValidateError::None;
+ if (CbObject ResponseObject = ValidateAndReadCompactBinaryObject(IoBuffer(ResponsePayload), ValidationError);
+ ValidationError == CbValidateError::None)
+ {
+ return ResponseObject;
+ }
}
return {};
@@ -1653,41 +200,138 @@ HttpClient::Response::ThrowError(std::string_view ErrorPrefix)
//////////////////////////////////////////////////////////////////////////
-#if ZEN_WITH_TESTS
+HttpClient::HttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings)
+: m_BaseUri(BaseUri)
+, m_ConnectionSettings(ConnectionSettings)
+{
+ m_SessionId = GetSessionIdString();
-namespace testutil {
- IoHash HashComposite(const CompositeBuffer& Payload)
- {
- IoHashStream Hasher;
- const uint64_t PayloadSize = Payload.GetSize();
- std::vector<uint8_t> Buffer(64u * 1024u);
- detail::CompositeBufferReadStream Stream(Payload, 137u * 1024u);
- for (uint64_t Offset = 0; Offset < PayloadSize;)
- {
- uint64_t Count = Min(64u * 1024u, PayloadSize - Offset);
- Stream.Read(Buffer.data(), Count);
- Hasher.Append(Buffer.data(), Count);
- Offset += Count;
- }
- return Hasher.GetHash();
- };
+ m_Inner = CreateCprHttpClient(BaseUri, ConnectionSettings);
+}
- IoHash HashFileStream(void* FileHandle, uint64_t FileOffset, uint64_t FileSize)
+HttpClient::~HttpClient()
+{
+ delete m_Inner;
+}
+
+void
+HttpClient::SetSessionId(const Oid& SessionId)
+{
+ if (SessionId == Oid::Zero)
{
- IoHashStream Hasher;
- std::vector<uint8_t> Buffer(64u * 1024u);
- detail::BufferedReadFileStream Stream(FileHandle, FileOffset, FileSize, 137u * 1024u);
- for (uint64_t Offset = 0; Offset < FileSize;)
- {
- uint64_t Count = Min(64u * 1024u, FileSize - Offset);
- Stream.Read(Buffer.data(), Count);
- Hasher.Append(Buffer.data(), Count);
- Offset += Count;
- }
- return Hasher.GetHash();
+ m_SessionId = GetSessionIdString();
+ }
+ else
+ {
+ m_SessionId = SessionId.ToString();
}
+}
-} // namespace testutil
+HttpClient::Response
+HttpClient::Put(std::string_view Url, const IoBuffer& Payload, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Put(Url, Payload, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Put(std::string_view Url, const HttpClient::KeyValueMap& Parameters)
+{
+ return m_Inner->Put(Url, Parameters);
+}
+
+HttpClient::Response
+HttpClient::Get(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader, const HttpClient::KeyValueMap& Parameters)
+{
+ return m_Inner->Get(Url, AdditionalHeader, Parameters);
+}
+
+HttpClient::Response
+HttpClient::Head(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Head(Url, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Delete(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Delete(Url, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Post(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader, const HttpClient::KeyValueMap& Parameters)
+{
+ return m_Inner->Post(Url, AdditionalHeader, Parameters);
+}
+
+HttpClient::Response
+HttpClient::Post(std::string_view Url, const IoBuffer& Payload, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Post(Url, Payload, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Post(std::string_view Url, const IoBuffer& Payload, ZenContentType ContentType, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Post(Url, Payload, ContentType, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Post(std::string_view Url, CbObject Payload, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Post(Url, Payload, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Post(std::string_view Url, CbPackage Payload, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Post(Url, Payload, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Post(std::string_view Url,
+ const CompositeBuffer& Payload,
+ ZenContentType ContentType,
+ const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Post(Url, Payload, ContentType, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Upload(Url, Payload, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Upload(std::string_view Url,
+ const CompositeBuffer& Payload,
+ ZenContentType ContentType,
+ const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Upload(Url, Payload, ContentType, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::Download(std::string_view Url, const std::filesystem::path& TempFolderPath, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->Download(Url, TempFolderPath, AdditionalHeader);
+}
+
+HttpClient::Response
+HttpClient::TransactPackage(std::string_view Url, CbPackage Package, const HttpClient::KeyValueMap& AdditionalHeader)
+{
+ return m_Inner->TransactPackage(Url, Package, AdditionalHeader);
+}
+
+bool
+HttpClient::Authenticate()
+{
+ return m_Inner->Authenticate();
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+#if ZEN_WITH_TESTS
TEST_CASE("responseformat")
{
@@ -1735,53 +379,6 @@ TEST_CASE("responseformat")
}
}
-TEST_CASE("BufferedReadFileStream")
-{
- ScopedTemporaryDirectory TmpDir;
-
- IoBuffer DiskBuffer = WriteToTempFile(CompositeBuffer(CreateRandomBlob(496 * 5 * 1024)), TmpDir.Path() / "diskbuffer1");
-
- IoBufferFileReference FileRef = {nullptr, 0, 0};
- CHECK(DiskBuffer.GetFileReference(FileRef));
- CHECK_EQ(IoHash::HashBuffer(DiskBuffer), testutil::HashFileStream(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize));
-
- IoBuffer Partial(DiskBuffer, 37 * 1024, 512 * 1024);
- CHECK(Partial.GetFileReference(FileRef));
- CHECK_EQ(IoHash::HashBuffer(Partial), testutil::HashFileStream(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize));
-
- IoBuffer SmallDiskBuffer = WriteToTempFile(CompositeBuffer(CreateRandomBlob(63 * 1024)), TmpDir.Path() / "diskbuffer2");
- CHECK(SmallDiskBuffer.GetFileReference(FileRef));
- CHECK_EQ(IoHash::HashBuffer(SmallDiskBuffer),
- testutil::HashFileStream(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize));
-}
-
-TEST_CASE("CompositeBufferReadStream")
-{
- ScopedTemporaryDirectory TmpDir;
-
- IoBuffer MemoryBuffer1 = CreateRandomBlob(64);
- CHECK_EQ(IoHash::HashBuffer(MemoryBuffer1), testutil::HashComposite(CompositeBuffer(SharedBuffer(MemoryBuffer1))));
-
- IoBuffer MemoryBuffer2 = CreateRandomBlob(561 * 1024);
- CHECK_EQ(IoHash::HashBuffer(MemoryBuffer2), testutil::HashComposite(CompositeBuffer(SharedBuffer(MemoryBuffer2))));
-
- IoBuffer DiskBuffer1 = WriteToTempFile(CompositeBuffer(CreateRandomBlob(267 * 3 * 1024)), TmpDir.Path() / "diskbuffer1");
- CHECK_EQ(IoHash::HashBuffer(DiskBuffer1), testutil::HashComposite(CompositeBuffer(SharedBuffer(DiskBuffer1))));
-
- IoBuffer DiskBuffer2 = WriteToTempFile(CompositeBuffer(CreateRandomBlob(3 * 1024)), TmpDir.Path() / "diskbuffer2");
- CHECK_EQ(IoHash::HashBuffer(DiskBuffer2), testutil::HashComposite(CompositeBuffer(SharedBuffer(DiskBuffer2))));
-
- IoBuffer DiskBuffer3 = WriteToTempFile(CompositeBuffer(CreateRandomBlob(496 * 5 * 1024)), TmpDir.Path() / "diskbuffer3");
- CHECK_EQ(IoHash::HashBuffer(DiskBuffer3), testutil::HashComposite(CompositeBuffer(SharedBuffer(DiskBuffer3))));
-
- CompositeBuffer Data(SharedBuffer(std::move(MemoryBuffer1)),
- SharedBuffer(std::move(DiskBuffer1)),
- SharedBuffer(std::move(DiskBuffer2)),
- SharedBuffer(std::move(MemoryBuffer2)),
- SharedBuffer(std::move(DiskBuffer3)));
- CHECK_EQ(IoHash::HashBuffer(Data), testutil::HashComposite(Data));
-}
-
TEST_CASE("httpclient")
{
using namespace std::literals;
diff --git a/src/zenhttp/httpclientauth.cpp b/src/zenhttp/httpclientauth.cpp
index 62e1b77bc..8754c57d6 100644
--- a/src/zenhttp/httpclientauth.cpp
+++ b/src/zenhttp/httpclientauth.cpp
@@ -9,11 +9,11 @@
#include <zencore/timer.h>
#include <zencore/uid.h>
#include <zenhttp/auth/authmgr.h>
+#include <zenhttp/httpclient.h>
#include <ctime>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
#include <fmt/format.h>
#include <json11.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -47,18 +47,22 @@ namespace zen { namespace httpclientauth {
OAuthParams.ClientId,
OAuthParams.ClientSecret);
- cpr::Response Response = cpr::Post(cpr::Url{OAuthParams.Url},
- cpr::Header{{"Content-Type", "application/x-www-form-urlencoded"}},
- cpr::Body{std::move(Body)});
+ HttpClient Http{OAuthParams.Url};
- if (Response.error || Response.status_code != 200)
+ IoBuffer Payload{IoBuffer::Wrap, Body.data(), Body.size()};
+
+ // TODO: ensure this gets the right Content-Type passed along
+
+ HttpClient::Response Response = Http.Post("", Payload, {{"Content-Type", "application/x-www-form-urlencoded"}});
+
+ if (!Response || Response.StatusCode != HttpResponseCode::OK)
{
- ZEN_WARN("Failed fetching OAuth access token {}. Reason: '{}'", OAuthParams.Url, Response.reason);
+ ZEN_WARN("Failed fetching OAuth access token {}. Reason: '{}'", OAuthParams.Url, Response.ErrorMessage(""));
return HttpClientAccessToken{};
}
std::string JsonError;
- json11::Json Json = json11::Json::parse(Response.text, JsonError);
+ json11::Json Json = json11::Json::parse(std::string{Response.AsText()}, JsonError);
if (JsonError.empty() == false)
{
@@ -90,7 +94,8 @@ namespace zen { namespace httpclientauth {
static HttpClientAccessToken GetOidcTokenFromExe(const std::filesystem::path& OidcExecutablePath,
std::string_view CloudHost,
bool Unattended,
- bool Quiet)
+ bool Quiet,
+ bool Hidden)
{
Stopwatch Timer;
@@ -99,6 +104,10 @@ namespace zen { namespace httpclientauth {
{
ProcOptions.StdoutFile = std::filesystem::temp_directory_path() / fmt::format(".zen-auth-output-{}", Oid::NewOid());
}
+ if (Hidden)
+ {
+ ProcOptions.Flags |= CreateProcOptions::Flag_NoConsole;
+ }
const std::filesystem::path AuthTokenPath(std::filesystem::temp_directory_path() / fmt::format(".zen-auth-{}", Oid::NewOid()));
auto _ = MakeGuard([AuthTokenPath, &ProcOptions]() {
@@ -176,14 +185,17 @@ namespace zen { namespace httpclientauth {
std::optional<std::function<HttpClientAccessToken()>> CreateFromOidcTokenExecutable(const std::filesystem::path& OidcExecutablePath,
std::string_view CloudHost,
- bool Quiet)
+ bool Quiet,
+ bool Unattended,
+ bool Hidden)
{
- HttpClientAccessToken InitialToken = GetOidcTokenFromExe(OidcExecutablePath, CloudHost, /* Unattended */ false, Quiet);
+ HttpClientAccessToken InitialToken = GetOidcTokenFromExe(OidcExecutablePath, CloudHost, Unattended, Quiet, Hidden);
if (InitialToken.IsValid())
{
return [OidcExecutablePath = std::filesystem::path(OidcExecutablePath),
CloudHost = std::string(CloudHost),
Quiet,
+ Hidden,
InitialToken]() mutable {
if (InitialToken.IsValid())
{
@@ -191,7 +203,7 @@ namespace zen { namespace httpclientauth {
InitialToken = {};
return Result;
}
- return GetOidcTokenFromExe(OidcExecutablePath, CloudHost, /* Unattended */ true, Quiet);
+ return GetOidcTokenFromExe(OidcExecutablePath, CloudHost, /* Unattended */ true, Quiet, Hidden);
};
}
return {};
diff --git a/src/zenhttp/httpserver.cpp b/src/zenhttp/httpserver.cpp
index 764f2a2a7..2c063d646 100644
--- a/src/zenhttp/httpserver.cpp
+++ b/src/zenhttp/httpserver.cpp
@@ -18,6 +18,7 @@
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/iobuffer.h>
#include <zencore/logging.h>
#include <zencore/stream.h>
@@ -665,9 +666,13 @@ HttpServerRequest::ReadPayloadObject()
}
return CbObject();
}
- return LoadCompactBinaryObject(std::move(Payload));
+ CbValidateError ValidationError = CbValidateError::None;
+ if (CbObject ResponseObject = ValidateAndReadCompactBinaryObject(std::move(Payload), ValidationError);
+ ValidationError == CbValidateError::None)
+ {
+ return ResponseObject;
+ }
}
-
return {};
}
@@ -931,42 +936,51 @@ HandlePackageOffers(HttpService& Service, HttpServerRequest& Request, Ref<IHttpP
if (PackageHandlerRef)
{
- CbObject OfferMessage = LoadCompactBinaryObject(Request.ReadPayload());
-
- std::vector<IoHash> OfferCids;
-
- for (auto& CidEntry : OfferMessage["offer"])
+ CbValidateError ValidationError = CbValidateError::None;
+ if (CbObject OfferMessage = ValidateAndReadCompactBinaryObject(IoBuffer(Request.ReadPayload()), ValidationError);
+ ValidationError == CbValidateError::None)
{
- if (!CidEntry.IsHash())
+ std::vector<IoHash> OfferCids;
+
+ for (auto& CidEntry : OfferMessage["offer"])
{
- // Should yield bad request response?
+ if (!CidEntry.IsHash())
+ {
+ // Should yield bad request response?
+
+ ZEN_WARN("found invalid entry in offer");
- ZEN_WARN("found invalid entry in offer");
+ continue;
+ }
- continue;
+ OfferCids.push_back(CidEntry.AsHash());
}
- OfferCids.push_back(CidEntry.AsHash());
- }
+ ZEN_TRACE("request #{} -> filtering offer of {} entries", Request.RequestId(), OfferCids.size());
+
+ PackageHandlerRef->FilterOffer(OfferCids);
- ZEN_TRACE("request #{} -> filtering offer of {} entries", Request.RequestId(), OfferCids.size());
+ ZEN_TRACE("request #{} -> filtered to {} entries", Request.RequestId(), OfferCids.size());
- PackageHandlerRef->FilterOffer(OfferCids);
+ CbObjectWriter ResponseWriter;
+ ResponseWriter.BeginArray("need");
- ZEN_TRACE("request #{} -> filtered to {} entries", Request.RequestId(), OfferCids.size());
+ for (const IoHash& Cid : OfferCids)
+ {
+ ResponseWriter.AddHash(Cid);
+ }
- CbObjectWriter ResponseWriter;
- ResponseWriter.BeginArray("need");
+ ResponseWriter.EndArray();
- for (const IoHash& Cid : OfferCids)
+ // Emit filter response
+ Request.WriteResponse(HttpResponseCode::OK, ResponseWriter.Save());
+ }
+ else
{
- ResponseWriter.AddHash(Cid);
+ Request.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Invalid request payload: '{}'", ToString(ValidationError)));
}
-
- ResponseWriter.EndArray();
-
- // Emit filter response
- Request.WriteResponse(HttpResponseCode::OK, ResponseWriter.Save());
return true;
}
}
diff --git a/src/zenhttp/include/zenhttp/cprutils.h b/src/zenhttp/include/zenhttp/cprutils.h
new file mode 100644
index 000000000..a3b870c0f
--- /dev/null
+++ b/src/zenhttp/include/zenhttp/cprutils.h
@@ -0,0 +1,86 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/compactbinary.h>
+#include <zencore/compactbinaryvalidation.h>
+#include <zencore/iobuffer.h>
+#include <zencore/string.h>
+#include <zenhttp/formatters.h>
+#include <zenhttp/httpclient.h>
+#include <zenhttp/httpcommon.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <cpr/response.h>
+#include <fmt/format.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+template<>
+struct fmt::formatter<cpr::Response>
+{
+ constexpr auto parse(format_parse_context& Ctx) -> decltype(Ctx.begin()) { return Ctx.end(); }
+
+ template<typename FormatContext>
+ auto format(const cpr::Response& Response, FormatContext& Ctx) const -> decltype(Ctx.out())
+ {
+ using namespace std::literals;
+
+ zen::NiceTimeSpanMs NiceResponseTime(uint64_t(Response.elapsed * 1000));
+
+ if (zen::IsHttpSuccessCode(Response.status_code))
+ {
+ return fmt::format_to(Ctx.out(),
+ "Url: {}, Status: {}, Error: '{}' ({}), Bytes: {}/{} (Up/Down), Elapsed: {}",
+ Response.url.str(),
+ Response.status_code,
+ Response.error.message,
+ int(Response.error.code),
+ Response.uploaded_bytes,
+ Response.downloaded_bytes,
+ NiceResponseTime.c_str());
+ }
+ else
+ {
+ const auto It = Response.header.find("Content-Type");
+ const std::string_view ContentType = It != Response.header.end() ? It->second : "<None>"sv;
+
+ if (ContentType == "application/x-ue-cb"sv)
+ {
+ zen::IoBuffer Body(zen::IoBuffer::Wrap, Response.text.data(), Response.text.size());
+ zen::CbObjectView Obj(Body.Data());
+ zen::ExtendableStringBuilder<256> Sb;
+ std::string_view Json = Obj.ToJson(Sb).ToView();
+
+ return fmt::format_to(
+ Ctx.out(),
+ "Url: {}, Status: {}, Error: '{}' ({}). Bytes: {}/{} (Up/Down), Elapsed: {}, Response: '{}', Reason: '{}'",
+ Response.url.str(),
+ Response.status_code,
+ Response.error.message,
+ int(Response.error.code),
+ Response.uploaded_bytes,
+ Response.downloaded_bytes,
+ NiceResponseTime.c_str(),
+ Json,
+ Response.reason);
+ }
+ else
+ {
+ zen::BodyLogFormatter Body(Response.text);
+
+ return fmt::format_to(
+ Ctx.out(),
+ "Url: {}, Status: {}, Error: '{}' ({}), Bytes: {}/{} (Up/Down), Elapsed: {}, Response: '{}', Reason: '{}'",
+ Response.url.str(),
+ Response.status_code,
+ Response.error.message,
+ int(Response.error.code),
+ Response.uploaded_bytes,
+ Response.downloaded_bytes,
+ NiceResponseTime.c_str(),
+ Body.GetText(),
+ Response.reason);
+ }
+ }
+ }
+};
diff --git a/src/zenhttp/include/zenhttp/formatters.h b/src/zenhttp/include/zenhttp/formatters.h
index 05a23d675..0af31fa30 100644
--- a/src/zenhttp/include/zenhttp/formatters.h
+++ b/src/zenhttp/include/zenhttp/formatters.h
@@ -10,7 +10,6 @@
#include <zenhttp/httpcommon.h>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
#include <fmt/format.h>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -59,76 +58,6 @@ public:
} // namespace zen
template<>
-struct fmt::formatter<cpr::Response>
-{
- constexpr auto parse(format_parse_context& Ctx) -> decltype(Ctx.begin()) { return Ctx.end(); }
-
- template<typename FormatContext>
- auto format(const cpr::Response& Response, FormatContext& Ctx) const -> decltype(Ctx.out())
- {
- using namespace std::literals;
-
- zen::NiceTimeSpanMs NiceResponseTime(uint64_t(Response.elapsed * 1000));
-
- if (zen::IsHttpSuccessCode(Response.status_code))
- {
- return fmt::format_to(Ctx.out(),
- "Url: {}, Status: {}, Error: '{}' ({}), Bytes: {}/{} (Up/Down), Elapsed: {}",
- Response.url.str(),
- Response.status_code,
- Response.error.message,
- int(Response.error.code),
- Response.uploaded_bytes,
- Response.downloaded_bytes,
- NiceResponseTime.c_str());
- }
- else
- {
- const auto It = Response.header.find("Content-Type");
- const std::string_view ContentType = It != Response.header.end() ? It->second : "<None>"sv;
-
- if (ContentType == "application/x-ue-cb"sv)
- {
- zen::IoBuffer Body(zen::IoBuffer::Wrap, Response.text.data(), Response.text.size());
- zen::CbObjectView Obj(Body.Data());
- zen::ExtendableStringBuilder<256> Sb;
- std::string_view Json = Obj.ToJson(Sb).ToView();
-
- return fmt::format_to(
- Ctx.out(),
- "Url: {}, Status: {}, Error: '{}' ({}). Bytes: {}/{} (Up/Down), Elapsed: {}, Response: '{}', Reason: '{}'",
- Response.url.str(),
- Response.status_code,
- Response.error.message,
- int(Response.error.code),
- Response.uploaded_bytes,
- Response.downloaded_bytes,
- NiceResponseTime.c_str(),
- Json,
- Response.reason);
- }
- else
- {
- zen::BodyLogFormatter Body(Response.text);
-
- return fmt::format_to(
- Ctx.out(),
- "Url: {}, Status: {}, Error: '{}' ({}), Bytes: {}/{} (Up/Down), Elapsed: {}, Response: '{}', Reason: '{}'",
- Response.url.str(),
- Response.status_code,
- Response.error.message,
- int(Response.error.code),
- Response.uploaded_bytes,
- Response.downloaded_bytes,
- NiceResponseTime.c_str(),
- Body.GetText(),
- Response.reason);
- }
- }
- }
-};
-
-template<>
struct fmt::formatter<zen::HttpClient::Response>
{
constexpr auto parse(format_parse_context& Ctx) -> decltype(Ctx.begin()) { return Ctx.end(); }
diff --git a/src/zenhttp/include/zenhttp/httpclient.h b/src/zenhttp/include/zenhttp/httpclient.h
index 50bd5b53a..c1fc1efa6 100644
--- a/src/zenhttp/include/zenhttp/httpclient.h
+++ b/src/zenhttp/include/zenhttp/httpclient.h
@@ -55,6 +55,7 @@ struct HttpClientSettings
bool AssumeHttp2 = false;
bool AllowResume = false;
uint8_t RetryCount = 0;
+ Oid SessionId = Oid::Zero;
};
class HttpClientError : public std::runtime_error
@@ -76,10 +77,40 @@ public:
{
}
+ inline int GetInternalErrorCode() const { return m_Error; }
+ inline HttpResponseCode GetHttpResponseCode() const { return m_ResponseCode; }
+
+ enum class ResponseClass : std::int8_t
+ {
+ kSuccess = 0,
+
+ kHttpOtherClientError = 80,
+ kHttpCantConnectError = 81, // CONNECTION_FAILURE
+ kHttpNotFound = 66, // NotFound(404)
+ kHttpUnauthorized = 77, // Unauthorized(401),
+ kHttpSLLError =
+ 82, // SSL_CONNECT_ERROR, SSL_LOCAL_CERTIFICATE_ERROR, SSL_REMOTE_CERTIFICATE_ERROR, SSL_CACERT_ERROR, GENERIC_SSL_ERROR
+ kHttpForbidden = 83, // Forbidden(403)
+ kHttpTimeout = 84, // NETWORK_RECEIVE_ERROR, NETWORK_SEND_FAILURE, OPERATION_TIMEDOUT, RequestTimeout(408)
+ kHttpConflict = 85, // Conflict(409)
+ kHttpNoHost = 86, // HOST_RESOLUTION_FAILURE, PROXY_RESOLUTION_FAILURE
+
+ kHttpOtherServerError = 90,
+ kHttpInternalServerError = 91, // InternalServerError(500)
+ kHttpServiceUnavailable = 69, // ServiceUnavailable(503)
+ kHttpBadGateway = 92, // BadGateway(502)
+ kHttpGatewayTimeout = 93, // GatewayTimeout(504)
+ };
+
+ ResponseClass GetResponseClass() const;
+
+private:
const int m_Error = 0;
const HttpResponseCode m_ResponseCode = HttpResponseCode::ImATeapot;
};
+class HttpClientBase;
+
class HttpClient
{
public:
@@ -163,6 +194,11 @@ public:
std::string ErrorMessage(std::string_view Prefix) const;
};
+ static std::pair<std::string_view, std::string_view> Accept(ZenContentType ContentType)
+ {
+ return std::make_pair("Accept", MapContentTypeToString(ContentType));
+ }
+
[[nodiscard]] Response Put(std::string_view Url, const IoBuffer& Payload, const KeyValueMap& AdditionalHeader = {});
[[nodiscard]] Response Put(std::string_view Url, const KeyValueMap& Parameters = {});
[[nodiscard]] Response Get(std::string_view Url, const KeyValueMap& AdditionalHeader = {}, const KeyValueMap& Parameters = {});
@@ -192,27 +228,20 @@ public:
[[nodiscard]] Response TransactPackage(std::string_view Url, CbPackage Package, const KeyValueMap& AdditionalHeader = {});
- static std::pair<std::string_view, std::string_view> Accept(ZenContentType ContentType)
- {
- return std::make_pair("Accept", MapContentTypeToString(ContentType));
- }
-
- LoggerRef Logger() { return m_Log; }
+ LoggerRef Log() { return m_Log; }
std::string_view GetBaseUri() const { return m_BaseUri; }
- bool Authenticate();
std::string_view GetSessionId() const { return m_SessionId; }
+ void SetSessionId(const Oid& SessionId);
+
+ bool Authenticate();
private:
- const std::optional<HttpClientAccessToken> GetAccessToken();
- struct Impl;
+ HttpClientBase* m_Inner;
LoggerRef m_Log;
std::string m_BaseUri;
std::string m_SessionId;
const HttpClientSettings m_ConnectionSettings;
- RwLock m_AccessTokenLock;
- HttpClientAccessToken m_CachedAccessToken;
- Ref<Impl> m_Impl;
};
void httpclient_forcelink(); // internal
diff --git a/src/zenhttp/include/zenhttp/httpclientauth.h b/src/zenhttp/include/zenhttp/httpclientauth.h
index 32d00f87f..26f31ed2a 100644
--- a/src/zenhttp/include/zenhttp/httpclientauth.h
+++ b/src/zenhttp/include/zenhttp/httpclientauth.h
@@ -28,7 +28,9 @@ namespace httpclientauth {
std::optional<std::function<HttpClientAccessToken()>> CreateFromOidcTokenExecutable(const std::filesystem::path& OidcExecutablePath,
std::string_view CloudHost,
- bool Quiet);
+ bool Quiet,
+ bool Unattended,
+ bool Hidden);
} // namespace httpclientauth
} // namespace zen
diff --git a/src/zenhttp/packageformat.cpp b/src/zenhttp/packageformat.cpp
index 0b7848f79..708238224 100644
--- a/src/zenhttp/packageformat.cpp
+++ b/src/zenhttp/packageformat.cpp
@@ -4,6 +4,7 @@
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compositebuffer.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
@@ -375,7 +376,7 @@ ParsePackageMessage(IoBuffer Payload, std::function<IoBuffer(const IoHash&, uint
if (Hdr->HeaderMagic != kCbPkgMagic)
{
throw std::invalid_argument(
- fmt::format("invalid CbPackage header magic, expected {0:x}, got {0:x}", static_cast<uint32_t>(kCbPkgMagic), Hdr->HeaderMagic));
+ fmt::format("invalid CbPackage header magic, expected {0:x}, got {1:x}", static_cast<uint32_t>(kCbPkgMagic), Hdr->HeaderMagic));
}
Reader.Skip(sizeof(CbPackageHeader));
@@ -499,6 +500,8 @@ ParsePackageMessage(IoBuffer Payload, std::function<IoBuffer(const IoHash&, uint
{
if (Entry.Flags & CbAttachmentEntry::kIsObject)
{
+ CbObject AttachmentObject;
+
CompressedBuffer CompBuf(CompressedBuffer::FromCompressedNoValidate(IoBuffer(AttachmentBuffer)));
if (!CompBuf)
{
@@ -509,7 +512,18 @@ ParsePackageMessage(IoBuffer Payload, std::function<IoBuffer(const IoHash&, uint
AttachmentBuffer.GetSize(),
Entry.AttachmentHash)));
}
- CbObject AttachmentObject = LoadCompactBinaryObject(std::move(CompBuf));
+ else
+ {
+ CbValidateError ValidationError = CbValidateError::None;
+ AttachmentObject = ValidateAndReadCompactBinaryObject(std::move(CompBuf), ValidationError);
+ if (ValidationError != CbValidateError::None)
+ {
+ MalformedAttachments.push_back(std::make_pair(
+ i,
+ fmt::format("Invalid format, CbObject for {}. Reason '{}'", Entry.AttachmentHash, ToString(ValidationError))));
+ }
+ }
+
if (i == 0)
{
// First payload is always a compact binary object
@@ -541,7 +555,15 @@ ParsePackageMessage(IoBuffer Payload, std::function<IoBuffer(const IoHash&, uint
{
if (Entry.Flags & CbAttachmentEntry::kIsObject)
{
- CbObject AttachmentObject = LoadCompactBinaryObject(AttachmentBuffer);
+ CbValidateError ValidationError = CbValidateError::None;
+ CbObject AttachmentObject = ValidateAndReadCompactBinaryObject(std::move(AttachmentBuffer), ValidationError);
+ if (ValidationError != CbValidateError::None)
+ {
+ MalformedAttachments.push_back(std::make_pair(
+ i,
+ fmt::format("Invalid format, CbObject for {}. Reason '{}'", Entry.AttachmentHash, ToString(ValidationError))));
+ }
+
if (i == 0)
{
Package.SetObject(AttachmentObject);
@@ -709,7 +731,12 @@ CbPackageReader::Finalize()
{
if (Entry.Flags & CbAttachmentEntry::kIsLocalRef)
{
- m_RootObject = LoadCompactBinaryObject(MarshalLocalChunkReference(AttachmentBuffer));
+ CbValidateError ValidateError = CbValidateError::None;
+ m_RootObject = ValidateAndReadCompactBinaryObject(MarshalLocalChunkReference(AttachmentBuffer), ValidateError);
+ if (ValidateError != CbValidateError::None)
+ {
+ throw std::runtime_error(fmt::format("Root object format is invalid, reason: '{}'", ToString(ValidateError)));
+ }
}
else if (Entry.Flags & CbAttachmentEntry::kIsCompressed)
{
@@ -718,12 +745,22 @@ CbPackageReader::Finalize()
CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(AttachmentBuffer), RawHash, RawSize);
if (RawHash == Entry.AttachmentHash)
{
- m_RootObject = LoadCompactBinaryObject(Compressed);
+ CbValidateError ValidateError = CbValidateError::None;
+ m_RootObject = ValidateAndReadCompactBinaryObject(std::move(Compressed), ValidateError);
+ if (ValidateError != CbValidateError::None)
+ {
+ throw std::runtime_error(fmt::format("Root object format is invalid, reason: '{}'", ToString(ValidateError)));
+ }
}
}
else
{
- m_RootObject = LoadCompactBinaryObject(std::move(AttachmentBuffer));
+ CbValidateError ValidateError = CbValidateError::None;
+ m_RootObject = ValidateAndReadCompactBinaryObject(std::move(AttachmentBuffer), ValidateError);
+ if (ValidateError != CbValidateError::None)
+ {
+ throw std::runtime_error(fmt::format("Root object format is invalid, reason: '{}'", ToString(ValidateError)));
+ }
}
}
else
diff --git a/src/zenhttp/servers/httpasio.cpp b/src/zenhttp/servers/httpasio.cpp
index 5392140d1..2023b6d98 100644
--- a/src/zenhttp/servers/httpasio.cpp
+++ b/src/zenhttp/servers/httpasio.cpp
@@ -522,9 +522,9 @@ HttpServerConnection::HandleRequest()
}
else
{
- ZEN_ERROR("Caught system error exception while handling request: {}. ({})",
- SystemError.what(),
- SystemError.code().value());
+ ZEN_WARN("Caught system error exception while handling request: {}. ({})",
+ SystemError.what(),
+ SystemError.code().value());
Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, SystemError.what());
}
}
@@ -540,7 +540,7 @@ HttpServerConnection::HandleRequest()
// Drop any partially formatted response
Request.m_Response.reset();
- ZEN_ERROR("Caught exception while handling request: {}", ex.what());
+ ZEN_WARN("Caught exception while handling request: {}", ex.what());
Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, ex.what());
}
}
diff --git a/src/zenhttp/servers/httpparser.cpp b/src/zenhttp/servers/httpparser.cpp
index 9bb354a5e..93094e21b 100644
--- a/src/zenhttp/servers/httpparser.cpp
+++ b/src/zenhttp/servers/httpparser.cpp
@@ -6,6 +6,8 @@
#include <zencore/logging.h>
#include <zencore/string.h>
+#include <limits>
+
namespace zen {
using namespace std::literals;
@@ -69,23 +71,21 @@ HttpRequestParser::ConsumeData(const char* InputData, size_t DataSize)
int
HttpRequestParser::OnUrl(const char* Data, size_t Bytes)
{
- if (!m_Url)
+ const size_t RemainingBufferSpace = std::numeric_limits<std::uint32_t>::max() - m_HeaderData.size();
+ if (RemainingBufferSpace < Bytes)
{
- ZEN_ASSERT_SLOW(m_UrlLength == 0);
- m_Url = m_HeaderCursor;
+ ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace);
+ return 1;
}
- const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor;
-
- if (RemainingBufferSpace < Bytes)
+ if (m_UrlRange.Length == 0)
{
- ZEN_WARN("HTTP parser does not have enough space for incoming request, need {} more bytes", Bytes - RemainingBufferSpace);
- return 1;
+ ZEN_ASSERT_SLOW(m_UrlRange.Offset == 0);
+ m_UrlRange.Offset = (uint32_t)m_HeaderData.size();
}
- memcpy(m_HeaderCursor, Data, Bytes);
- m_HeaderCursor += Bytes;
- m_UrlLength += Bytes;
+ m_HeaderData.insert(m_HeaderData.end(), Data, &Data[Bytes]);
+ m_UrlRange.Length += (uint32_t)Bytes;
return 0;
}
@@ -93,59 +93,66 @@ HttpRequestParser::OnUrl(const char* Data, size_t Bytes)
int
HttpRequestParser::OnHeader(const char* Data, size_t Bytes)
{
- if (m_CurrentHeaderValueLength)
+ const size_t RemainingBufferSpace = std::numeric_limits<std::uint32_t>::max() - m_HeaderData.size();
+ if (RemainingBufferSpace < Bytes)
{
- AppendCurrentHeader();
+ ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace);
+ return 1;
+ }
- m_CurrentHeaderNameLength = 0;
- m_CurrentHeaderValueLength = 0;
- m_CurrentHeaderName = m_HeaderCursor;
+ if (m_HeaderEntries.empty())
+ {
+ m_HeaderEntries.resize(1);
}
- else if (m_CurrentHeaderName == nullptr)
+ HeaderEntry* CurrentHeaderEntry = &m_HeaderEntries.back();
+ if (CurrentHeaderEntry->ValueRange.Length)
{
- m_CurrentHeaderName = m_HeaderCursor;
+ ParseCurrentHeader();
+ m_HeaderEntries.emplace_back(HeaderEntry{.NameRange = {.Offset = (uint32_t)m_HeaderData.size()}});
+ CurrentHeaderEntry = &m_HeaderEntries.back();
}
-
- const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor;
- if (RemainingBufferSpace < Bytes)
+ else if (CurrentHeaderEntry->NameRange.Length == 0)
{
- ZEN_WARN("HTTP parser does not have enough space for incoming header name, need {} more bytes", Bytes - RemainingBufferSpace);
- return 1;
+ m_HeaderEntries.emplace_back(HeaderEntry{.NameRange = {.Offset = (uint32_t)m_HeaderData.size()}});
+ CurrentHeaderEntry = &m_HeaderEntries.back();
}
- memcpy(m_HeaderCursor, Data, Bytes);
- m_HeaderCursor += Bytes;
- m_CurrentHeaderNameLength += Bytes;
+ m_HeaderData.insert(m_HeaderData.end(), Data, &Data[Bytes]);
+ CurrentHeaderEntry->NameRange.Length += (uint32_t)Bytes;
return 0;
}
void
-HttpRequestParser::AppendCurrentHeader()
+HttpRequestParser::ParseCurrentHeader()
{
- std::string_view HeaderName(m_CurrentHeaderName, m_CurrentHeaderNameLength);
- if (m_Headers.size() == std::numeric_limits<int8_t>::max())
+ ZEN_ASSERT_SLOW(!m_HeaderEntries.empty());
+ const HeaderEntry& CurrentHeaderEntry = m_HeaderEntries.back();
+ const size_t CurrentHeaderCount = m_HeaderEntries.size();
+ const std::string_view HeaderName(GetHeaderSubString(CurrentHeaderEntry.NameRange));
+ if (CurrentHeaderCount > std::numeric_limits<int8_t>::max())
{
ZEN_WARN("HttpRequestParser parser only supports up to {} headers, can't store header '{}'. Dropping it.",
std::numeric_limits<int8_t>::max(),
HeaderName);
return;
}
- std::string_view HeaderValue(m_CurrentHeaderValue, m_CurrentHeaderValueLength);
+ const std::string_view HeaderValue(GetHeaderSubString(CurrentHeaderEntry.ValueRange));
- const uint32_t HeaderHash = HashStringAsLowerDjb2(HeaderName);
+ const uint32_t HeaderHash = HashStringAsLowerDjb2(HeaderName);
+ const int8_t CurrentHeaderIndex = int8_t(CurrentHeaderCount - 1);
if (HeaderHash == HashContentLength)
{
- m_ContentLengthHeaderIndex = (int8_t)m_Headers.size();
+ m_ContentLengthHeaderIndex = CurrentHeaderIndex;
}
else if (HeaderHash == HashAccept)
{
- m_AcceptHeaderIndex = (int8_t)m_Headers.size();
+ m_AcceptHeaderIndex = CurrentHeaderIndex;
}
else if (HeaderHash == HashContentType)
{
- m_ContentTypeHeaderIndex = (int8_t)m_Headers.size();
+ m_ContentTypeHeaderIndex = CurrentHeaderIndex;
}
else if (HeaderHash == HashSession)
{
@@ -169,38 +176,38 @@ HttpRequestParser::AppendCurrentHeader()
}
else if (HeaderHash == HashRange)
{
- m_RangeHeaderIndex = (int8_t)m_Headers.size();
+ m_RangeHeaderIndex = CurrentHeaderIndex;
}
-
- m_Headers.emplace_back(HeaderName, HeaderValue);
}
int
HttpRequestParser::OnHeaderValue(const char* Data, size_t Bytes)
{
- if (m_CurrentHeaderValueLength == 0)
- {
- m_CurrentHeaderValue = m_HeaderCursor;
- }
-
- const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor;
+ const size_t RemainingBufferSpace = std::numeric_limits<std::uint32_t>::max() - m_HeaderData.size();
if (RemainingBufferSpace < Bytes)
{
- ZEN_WARN("HTTP parser does not have enough space for incoming header value, need {} more bytes", Bytes - RemainingBufferSpace);
+ ZEN_WARN("HTTP parser does not have enough space for incoming request headers, need {} more bytes", Bytes - RemainingBufferSpace);
return 1;
}
- memcpy(m_HeaderCursor, Data, Bytes);
- m_HeaderCursor += Bytes;
- m_CurrentHeaderValueLength += Bytes;
+ ZEN_ASSERT_SLOW(!m_HeaderEntries.empty());
+ HeaderEntry& CurrentHeaderEntry = m_HeaderEntries.back();
+ if (CurrentHeaderEntry.ValueRange.Length == 0)
+ {
+ CurrentHeaderEntry.ValueRange.Offset = (uint32_t)m_HeaderData.size();
+ }
+ m_HeaderData.insert(m_HeaderData.end(), Data, &Data[Bytes]);
+ CurrentHeaderEntry.ValueRange.Length += (uint32_t)Bytes;
return 0;
}
static void
-NormalizeUrlPath(const char* Url, size_t UrlLength, std::string& NormalizedUrl)
+NormalizeUrlPath(std::string_view InUrl, std::string& NormalizedUrl)
{
- bool LastCharWasSeparator = false;
+ bool LastCharWasSeparator = false;
+ const char* Url = InUrl.data();
+ const size_t UrlLength = InUrl.length();
for (std::string_view::size_type UrlIndex = 0; UrlIndex < UrlLength; ++UrlIndex)
{
const char UrlChar = Url[UrlIndex];
@@ -233,9 +240,13 @@ HttpRequestParser::OnHeadersComplete()
{
try
{
- if (m_CurrentHeaderValueLength)
+ if (!m_HeaderEntries.empty())
{
- AppendCurrentHeader();
+ HeaderEntry& CurrentHeaderEntry = m_HeaderEntries.back();
+ if (CurrentHeaderEntry.NameRange.Length)
+ {
+ ParseCurrentHeader();
+ }
}
m_KeepAlive = !!http_should_keep_alive(&m_Parser);
@@ -275,21 +286,21 @@ HttpRequestParser::OnHeadersComplete()
break;
}
- std::string_view Url(m_Url, m_UrlLength);
+ std::string_view FullUrl(GetHeaderSubString(m_UrlRange));
- if (auto QuerySplit = Url.find_first_of('?'); QuerySplit != std::string_view::npos)
+ if (auto QuerySplit = FullUrl.find_first_of('?'); QuerySplit != std::string_view::npos)
{
- m_UrlLength = QuerySplit;
- m_QueryString = m_Url + QuerySplit + 1;
- m_QueryLength = Url.size() - QuerySplit - 1;
+ m_UrlRange.Length = uint32_t(QuerySplit);
+ m_QueryStringRange = {.Offset = uint32_t(m_UrlRange.Offset + QuerySplit + 1),
+ .Length = uint32_t(FullUrl.size() - QuerySplit - 1)};
}
- NormalizeUrlPath(m_Url, m_UrlLength, m_NormalizedUrl);
+ NormalizeUrlPath(FullUrl, m_NormalizedUrl);
- if (m_ContentLengthHeaderIndex >= 0)
+ std::string_view Value = GetHeaderValue(m_ContentLengthHeaderIndex);
+ if (!Value.empty())
{
- std::string_view& Value = m_Headers[m_ContentLengthHeaderIndex].Value;
- uint64_t ContentLength = 0;
+ uint64_t ContentLength = 0;
std::from_chars(Value.data(), Value.data() + Value.size(), ContentLength);
if (ContentLength)
@@ -337,15 +348,11 @@ HttpRequestParser::OnBody(const char* Data, size_t Bytes)
void
HttpRequestParser::ResetState()
{
- m_HeaderCursor = m_HeaderBuffer;
- m_CurrentHeaderName = nullptr;
- m_CurrentHeaderNameLength = 0;
- m_CurrentHeaderValue = nullptr;
- m_CurrentHeaderValueLength = 0;
- m_Url = nullptr;
- m_UrlLength = 0;
- m_QueryString = nullptr;
- m_QueryLength = 0;
+ m_UrlRange = {};
+ m_QueryStringRange = {};
+
+ m_HeaderEntries.clear();
+
m_ContentLengthHeaderIndex = -1;
m_AcceptHeaderIndex = -1;
m_ContentTypeHeaderIndex = -1;
@@ -353,7 +360,8 @@ HttpRequestParser::ResetState()
m_Expect100Continue = false;
m_BodyBuffer = {};
m_BodyPosition = 0;
- m_Headers.clear();
+
+ m_HeaderData.clear();
m_NormalizedUrl.clear();
}
diff --git a/src/zenhttp/servers/httpparser.h b/src/zenhttp/servers/httpparser.h
index bdbcab4d9..0d2664ec5 100644
--- a/src/zenhttp/servers/httpparser.h
+++ b/src/zenhttp/servers/httpparser.h
@@ -5,6 +5,8 @@
#include <zencore/uid.h>
#include <zenhttp/httpcommon.h>
+#include <EASTL/fixed_vector.h>
+
ZEN_THIRD_PARTY_INCLUDES_START
#include <http_parser.h>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -31,73 +33,68 @@ struct HttpRequestParser
HttpVerb RequestVerb() const { return m_RequestVerb; }
bool IsKeepAlive() const { return m_KeepAlive; }
- std::string_view Url() const { return m_NormalizedUrl.empty() ? std::string_view(m_Url, m_UrlLength) : m_NormalizedUrl; }
- std::string_view QueryString() const { return std::string_view(m_QueryString, m_QueryLength); }
+ std::string_view Url() const { return m_NormalizedUrl.empty() ? GetHeaderSubString(m_UrlRange) : m_NormalizedUrl; }
+ std::string_view QueryString() const { return GetHeaderSubString(m_QueryStringRange); }
IoBuffer Body() { return m_BodyBuffer; }
- inline HttpContentType ContentType()
- {
- if (m_ContentTypeHeaderIndex < 0)
- {
- return HttpContentType::kUnknownContentType;
- }
-
- return ParseContentType(m_Headers[m_ContentTypeHeaderIndex].Value);
- }
+ inline HttpContentType ContentType() { return ParseContentType(GetHeaderValue(m_ContentTypeHeaderIndex)); }
- inline HttpContentType AcceptType()
- {
- if (m_AcceptHeaderIndex < 0)
- {
- return HttpContentType::kUnknownContentType;
- }
-
- return ParseContentType(m_Headers[m_AcceptHeaderIndex].Value);
- }
+ inline HttpContentType AcceptType() { return ParseContentType(GetHeaderValue(m_AcceptHeaderIndex)); }
Oid SessionId() const { return m_SessionId; }
int RequestId() const { return m_RequestId; }
- std::string_view RangeHeader() const { return m_RangeHeaderIndex != -1 ? m_Headers[m_RangeHeaderIndex].Value : std::string_view(); }
+ std::string_view RangeHeader() const { return GetHeaderValue(m_RangeHeaderIndex); }
private:
+ struct HeaderRange
+ {
+ uint32_t Offset = 0;
+ uint32_t Length = 0;
+ };
+
struct HeaderEntry
{
- HeaderEntry() = default;
+ HeaderRange NameRange;
+ HeaderRange ValueRange;
+ };
- HeaderEntry(std::string_view InName, std::string_view InValue) : Name(InName), Value(InValue) {}
+ inline std::string_view GetHeaderValue(int8_t HeaderIndex) const
+ {
+ if (HeaderIndex == -1)
+ {
+ return {};
+ }
+ ZEN_ASSERT(size_t(HeaderIndex) < m_HeaderEntries.size());
+ return GetHeaderSubString(m_HeaderEntries[HeaderIndex].ValueRange);
+ }
- std::string_view Name;
- std::string_view Value;
- };
+ std::string_view GetHeaderSubString(const HeaderRange& Range) const
+ {
+ ZEN_ASSERT_SLOW(Range.Offset + Range.Length <= m_HeaderData.size());
+ return std::string_view(m_HeaderData.begin(), m_HeaderData.size()).substr(Range.Offset, Range.Length);
+ }
- HttpRequestParserCallbacks& m_Connection;
- char* m_HeaderCursor = m_HeaderBuffer;
- char* m_Url = nullptr;
- size_t m_UrlLength = 0;
- char* m_QueryString = nullptr;
- size_t m_QueryLength = 0;
- char* m_CurrentHeaderName = nullptr; // Used while parsing headers
- size_t m_CurrentHeaderNameLength = 0;
- char* m_CurrentHeaderValue = nullptr; // Used while parsing headers
- size_t m_CurrentHeaderValueLength = 0;
- std::vector<HeaderEntry> m_Headers;
- int8_t m_ContentLengthHeaderIndex;
- int8_t m_AcceptHeaderIndex;
- int8_t m_ContentTypeHeaderIndex;
- int8_t m_RangeHeaderIndex;
- HttpVerb m_RequestVerb;
- std::atomic_bool m_KeepAlive{false};
- bool m_Expect100Continue = false;
- int m_RequestId = -1;
- Oid m_SessionId{};
- IoBuffer m_BodyBuffer;
- uint64_t m_BodyPosition = 0;
- http_parser m_Parser;
- char m_HeaderBuffer[1024];
- std::string m_NormalizedUrl;
-
- void AppendCurrentHeader();
+ HttpRequestParserCallbacks& m_Connection;
+ HeaderRange m_UrlRange;
+ HeaderRange m_QueryStringRange;
+ eastl::fixed_vector<HeaderEntry, 16> m_HeaderEntries;
+ int8_t m_ContentLengthHeaderIndex;
+ int8_t m_AcceptHeaderIndex;
+ int8_t m_ContentTypeHeaderIndex;
+ int8_t m_RangeHeaderIndex;
+ HttpVerb m_RequestVerb;
+ std::atomic_bool m_KeepAlive{false};
+ bool m_Expect100Continue = false;
+ int m_RequestId = -1;
+ Oid m_SessionId{};
+ IoBuffer m_BodyBuffer;
+ uint64_t m_BodyPosition = 0;
+ http_parser m_Parser;
+ eastl::fixed_vector<char, 512> m_HeaderData;
+ std::string m_NormalizedUrl;
+
+ void ParseCurrentHeader();
int OnMessageBegin();
int OnUrl(const char* Data, size_t Bytes);
diff --git a/src/zenhttp/servers/httpplugin.cpp b/src/zenhttp/servers/httpplugin.cpp
index 155f3fa02..d6ca7e1c5 100644
--- a/src/zenhttp/servers/httpplugin.cpp
+++ b/src/zenhttp/servers/httpplugin.cpp
@@ -396,6 +396,14 @@ HttpPluginConnectionHandler::HandleRequest()
{
Service->HandleRequest(Request);
}
+ catch (const AssertException& AssertEx)
+ {
+ // Drop any partially formatted response
+ Request.m_Response.reset();
+
+ ZEN_ERROR("Caught assert exception while handling request: {}", AssertEx.FullDescription());
+ Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, AssertEx.FullDescription());
+ }
catch (const std::system_error& SystemError)
{
// Drop any partially formatted response
@@ -407,9 +415,9 @@ HttpPluginConnectionHandler::HandleRequest()
}
else
{
- ZEN_ERROR("Caught system error exception while handling request: {}. ({})",
- SystemError.what(),
- SystemError.code().value());
+ ZEN_WARN("Caught system error exception while handling request: {}. ({})",
+ SystemError.what(),
+ SystemError.code().value());
Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, SystemError.what());
}
}
@@ -425,7 +433,7 @@ HttpPluginConnectionHandler::HandleRequest()
// Drop any partially formatted response
Request.m_Response.reset();
- ZEN_ERROR("Caught exception while handling request: {}", ex.what());
+ ZEN_WARN("Caught exception while handling request: {}", ex.what());
Request.WriteResponse(HttpResponseCode::InternalServerError, HttpContentType::kText, ex.what());
}
}
diff --git a/src/zenhttp/servers/httpsys.cpp b/src/zenhttp/servers/httpsys.cpp
index e57fa8a30..95d83911d 100644
--- a/src/zenhttp/servers/httpsys.cpp
+++ b/src/zenhttp/servers/httpsys.cpp
@@ -835,7 +835,7 @@ HttpAsyncWorkRequest::IssueRequest(std::error_code& ErrorCode)
ZEN_TRACE_CPU("httpsys::AsyncWork::IssueRequest");
ErrorCode.clear();
- Transaction().Server().WorkPool().ScheduleWork(m_WorkItem);
+ Transaction().Server().WorkPool().ScheduleWork(m_WorkItem, WorkerThreadPool::EMode::EnableBacklog);
}
HttpSysRequestHandler*
@@ -2056,7 +2056,7 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT
return new HttpMessageResponseRequest(Transaction(), (uint16_t)HttpResponseCode::InsufficientStorage, SystemError.what());
}
- ZEN_ERROR("Caught system error exception while handling request: {}. ({})", SystemError.what(), SystemError.code().value());
+ ZEN_WARN("Caught system error exception while handling request: {}. ({})", SystemError.what(), SystemError.code().value());
return new HttpMessageResponseRequest(Transaction(), (uint16_t)HttpResponseCode::InternalServerError, SystemError.what());
}
catch (const std::bad_alloc& BadAlloc)
@@ -2065,7 +2065,7 @@ InitialRequestHandler::HandleCompletion(ULONG IoResult, ULONG_PTR NumberOfBytesT
}
catch (const std::exception& ex)
{
- ZEN_ERROR("Caught exception while handling request: '{}'", ex.what());
+ ZEN_WARN("Caught exception while handling request: '{}'", ex.what());
return new HttpMessageResponseRequest(Transaction(), (uint16_t)HttpResponseCode::InternalServerError, ex.what());
}
}
diff --git a/src/zenhttp/transports/winsocktransport.cpp b/src/zenhttp/transports/winsocktransport.cpp
index 8c82760bb..c06a50c95 100644
--- a/src/zenhttp/transports/winsocktransport.cpp
+++ b/src/zenhttp/transports/winsocktransport.cpp
@@ -304,18 +304,20 @@ SocketTransportPluginImpl::Initialize(TransportServer* ServerInterface)
TransportServerConnection* ConnectionInterface{m_ServerInterface->CreateConnectionHandler(Connection)};
Connection->Initialize(ConnectionInterface, ClientSocket);
- m_WorkerThreadpool->ScheduleWork([Connection] {
- try
- {
- Connection->HandleConnection();
- }
- catch (const std::exception& Ex)
- {
- ZEN_WARN("exception caught in connection loop: {}", Ex.what());
- }
-
- delete Connection;
- });
+ m_WorkerThreadpool->ScheduleWork(
+ [Connection] {
+ try
+ {
+ Connection->HandleConnection();
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_WARN("exception caught in connection loop: {}", Ex.what());
+ }
+
+ delete Connection;
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
else
{
diff --git a/src/zennet-test/xmake.lua b/src/zennet-test/xmake.lua
index 8d8c0a598..c84837610 100644
--- a/src/zennet-test/xmake.lua
+++ b/src/zennet-test/xmake.lua
@@ -5,7 +5,7 @@ target("zennet-test")
set_group("tests")
add_headerfiles("**.h")
add_files("*.cpp")
- add_deps("zencore", "zenutil", "zennet")
+ add_deps("zennet")
add_packages("vcpkg::mimalloc")
add_packages("vcpkg::doctest")
diff --git a/src/zennet-test/zennet-test.cpp b/src/zennet-test/zennet-test.cpp
index a341bd344..5e4d29220 100644
--- a/src/zennet-test/zennet-test.cpp
+++ b/src/zennet-test/zennet-test.cpp
@@ -23,7 +23,15 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
zen::IgnoreChildSignals();
# endif
+# if ZEN_WITH_TRACE
zen::TraceInit("zennet-test");
+ zen::TraceOptions TraceCommandlineOptions;
+ if (GetTraceOptionsFromCommandline(TraceCommandlineOptions))
+ {
+ TraceConfigure(TraceCommandlineOptions);
+ }
+# endif // ZEN_WITH_TRACE
+
zen::logging::InitializeLogging();
zen::MaximizeOpenFileCount();
diff --git a/src/zenremotestore-test/xmake.lua b/src/zenremotestore-test/xmake.lua
new file mode 100644
index 000000000..edc356b5b
--- /dev/null
+++ b/src/zenremotestore-test/xmake.lua
@@ -0,0 +1,9 @@
+-- Copyright Epic Games, Inc. All Rights Reserved.
+
+target("zenremotestore-test")
+ set_kind("binary")
+ set_group("tests")
+ add_headerfiles("**.h")
+ add_files("*.cpp")
+ add_deps("zenremotestore")
+ add_packages("vcpkg::doctest")
diff --git a/src/zenremotestore-test/zenremotestore-test.cpp b/src/zenremotestore-test/zenremotestore-test.cpp
new file mode 100644
index 000000000..a49c6d273
--- /dev/null
+++ b/src/zenremotestore-test/zenremotestore-test.cpp
@@ -0,0 +1,43 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/filesystem.h>
+#include <zencore/logging.h>
+#include <zencore/trace.h>
+#include <zenremotestore/projectstore/remoteprojectstore.h>
+#include <zenremotestore/zenremotestore.h>
+
+#include <zencore/memory/newdelete.h>
+
+#if ZEN_WITH_TESTS
+# define ZEN_TEST_WITH_RUNNER 1
+# include <zencore/testing.h>
+# include <zencore/process.h>
+#endif
+
+int
+main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
+{
+#if ZEN_WITH_TESTS
+ zen::zenremotestore_forcelinktests();
+
+# if ZEN_PLATFORM_LINUX
+ zen::IgnoreChildSignals();
+# endif
+
+# if ZEN_WITH_TRACE
+ zen::TraceInit("zenstore-test");
+ zen::TraceOptions TraceCommandlineOptions;
+ if (GetTraceOptionsFromCommandline(TraceCommandlineOptions))
+ {
+ TraceConfigure(TraceCommandlineOptions);
+ }
+# endif // ZEN_WITH_TRACE
+
+ zen::logging::InitializeLogging();
+ zen::MaximizeOpenFileCount();
+
+ return ZEN_RUN_TESTS(argc, argv);
+#else
+ return 0;
+#endif
+}
diff --git a/src/zenutil/buildstoragecache.cpp b/src/zenremotestore/builds/buildstoragecache.cpp
index 2171f4d62..d36d75480 100644
--- a/src/zenutil/buildstoragecache.cpp
+++ b/src/zenremotestore/builds/buildstoragecache.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/buildstoragecache.h>
+#include <zenremotestore/builds/buildstoragecache.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinaryvalidation.h>
@@ -11,7 +11,6 @@
#include <zencore/workthreadpool.h>
#include <zenhttp/httpclient.h>
#include <zenhttp/packageformat.h>
-#include <zenutil/workerpools.h>
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_set.h>
@@ -29,15 +28,13 @@ public:
std::string_view Namespace,
std::string_view Bucket,
const std::filesystem::path& TempFolderPath,
- bool BoostBackgroundThreadCount)
+ WorkerThreadPool& BackgroundWorkerPool)
: m_HttpClient(HttpClient)
, m_Stats(Stats)
, m_Namespace(Namespace.empty() ? "none" : Namespace)
, m_Bucket(Bucket.empty() ? "none" : Bucket)
, m_TempFolderPath(std::filesystem::path(TempFolderPath).make_preferred())
- , m_BoostBackgroundThreadCount(BoostBackgroundThreadCount)
- , m_BackgroundWorkPool(m_BoostBackgroundThreadCount ? GetSmallWorkerPool(EWorkloadType::Background)
- : GetTinyWorkerPool(EWorkloadType::Background))
+ , m_BackgroundWorkPool(BackgroundWorkerPool)
, m_PendingBackgroundWorkCount(1)
, m_CancelBackgroundWork(false)
{
@@ -65,21 +62,23 @@ public:
m_PendingBackgroundWorkCount.AddCount(1);
try
{
- m_BackgroundWorkPool.ScheduleWork([this, Work = std::move(Work)]() {
- ZEN_TRACE_CPU("ZenBuildStorageCache::BackgroundWork");
- auto _ = MakeGuard([this]() { m_PendingBackgroundWorkCount.CountDown(); });
- if (!m_CancelBackgroundWork)
- {
- try
- {
- Work();
- }
- catch (const std::exception& Ex)
+ m_BackgroundWorkPool.ScheduleWork(
+ [this, Work = std::move(Work)]() {
+ ZEN_TRACE_CPU("ZenBuildStorageCache::BackgroundWork");
+ auto _ = MakeGuard([this]() { m_PendingBackgroundWorkCount.CountDown(); });
+ if (!m_CancelBackgroundWork)
{
- ZEN_ERROR("Failed executing background upload to build cache. Reason: {}", Ex.what());
+ try
+ {
+ Work();
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_ERROR("Failed executing background upload to build cache. Reason: {}", Ex.what());
+ }
}
- }
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
catch (const std::exception& Ex)
{
@@ -392,7 +391,6 @@ private:
const std::string m_Namespace;
const std::string m_Bucket;
const std::filesystem::path m_TempFolderPath;
- const bool m_BoostBackgroundThreadCount;
bool IsFlushed = false;
WorkerThreadPool& m_BackgroundWorkPool;
@@ -406,9 +404,27 @@ CreateZenBuildStorageCache(HttpClient& HttpClient,
std::string_view Namespace,
std::string_view Bucket,
const std::filesystem::path& TempFolderPath,
- bool BoostBackgroundThreadCount)
+ WorkerThreadPool& BackgroundWorkerPool)
{
- return std::make_unique<ZenBuildStorageCache>(HttpClient, Stats, Namespace, Bucket, TempFolderPath, BoostBackgroundThreadCount);
+ return std::make_unique<ZenBuildStorageCache>(HttpClient, Stats, Namespace, Bucket, TempFolderPath, BackgroundWorkerPool);
}
+ZenCacheEndpointTestResult
+TestZenCacheEndpoint(std::string_view BaseUrl, const bool AssumeHttp2)
+{
+ HttpClientSettings TestClientSettings{.LogCategory = "httpcacheclient",
+ .ConnectTimeout = std::chrono::milliseconds{1000},
+ .Timeout = std::chrono::milliseconds{2000},
+ .AssumeHttp2 = AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0};
+ HttpClient TestHttpClient(BaseUrl, TestClientSettings);
+ HttpClient::Response TestResponse = TestHttpClient.Get("/status/builds");
+ if (TestResponse.IsSuccess())
+ {
+ return {.Success = true};
+ }
+ return {.Success = false, .FailureReason = TestResponse.ErrorMessage("")};
+};
+
} // namespace zen
diff --git a/src/zenutil/filebuildstorage.cpp b/src/zenremotestore/builds/filebuildstorage.cpp
index f75fe403f..5cfd80666 100644
--- a/src/zenutil/filebuildstorage.cpp
+++ b/src/zenremotestore/builds/filebuildstorage.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/filebuildstorage.h>
+#include <zenremotestore/builds/filebuildstorage.h>
#include <zencore/basicfile.h>
#include <zencore/compactbinarybuilder.h>
diff --git a/src/zenutil/jupiter/jupiterbuildstorage.cpp b/src/zenremotestore/builds/jupiterbuildstorage.cpp
index c9278acb4..14a5ecc85 100644
--- a/src/zenutil/jupiter/jupiterbuildstorage.cpp
+++ b/src/zenremotestore/builds/jupiterbuildstorage.cpp
@@ -1,18 +1,21 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/jupiter/jupiterbuildstorage.h>
+#include <zenremotestore/builds/jupiterbuildstorage.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/fmtutils.h>
#include <zencore/scopeguard.h>
#include <zencore/timer.h>
#include <zencore/trace.h>
-#include <zenutil/jupiter/jupitersession.h>
+#include <zenremotestore/jupiter/jupitersession.h>
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
ZEN_THIRD_PARTY_INCLUDES_END
+#include <regex>
+
namespace zen {
using namespace std::literals;
@@ -430,13 +433,41 @@ private:
}
else if (Payload.GetContentType() == ZenContentType::kCbObject)
{
- return LoadCompactBinaryObject(Payload);
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject Object = ValidateAndReadCompactBinaryObject(IoBuffer(Payload), ValidateResult);
+ ValidateResult == CbValidateError::None)
+ {
+ return Object;
+ }
+ else
+ {
+ throw std::runtime_error(fmt::format("{}: {} ({})",
+ "Invalid compact binary object: '{}'",
+ ErrorContext,
+ ToString(Payload.GetContentType()),
+ ToString(ValidateResult)));
+ }
}
else if (Payload.GetContentType() == ZenContentType::kCompressedBinary)
{
- IoHash RawHash;
- uint64_t RawSize;
- return LoadCompactBinaryObject(CompressedBuffer::FromCompressed(SharedBuffer(Payload), RawHash, RawSize));
+ IoHash RawHash;
+ uint64_t RawSize;
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject Object =
+ ValidateAndReadCompactBinaryObject(CompressedBuffer::FromCompressed(SharedBuffer(Payload), RawHash, RawSize),
+ ValidateResult);
+ ValidateResult == CbValidateError::None)
+ {
+ return Object;
+ }
+ else
+ {
+ throw std::runtime_error(fmt::format("{}: {} ({})",
+ "Invalid compresed compact binary object: '{}'",
+ ErrorContext,
+ ToString(Payload.GetContentType()),
+ ToString(ValidateResult)));
+ }
}
else
{
@@ -482,4 +513,49 @@ CreateJupiterBuildStorage(LoggerRef InLog,
return std::make_unique<JupiterBuildStorage>(InLog, InHttpClient, Stats, Namespace, Bucket, AllowRedirect, TempFolderPath);
}
+bool
+ParseBuildStorageUrl(std::string_view InUrl,
+ std::string& OutHost,
+ std::string& OutNamespace,
+ std::string& OutBucket,
+ std::string& OutBuildId)
+{
+ std::string Url(InUrl);
+ const std::string_view ExtendedApiString = "api/v2/builds/";
+ if (auto ApiString = ToLower(Url).find(ExtendedApiString); ApiString != std::string::npos)
+ {
+ Url.erase(ApiString, ExtendedApiString.length());
+ }
+
+ const std::string ArtifactURLRegExString = R"((http[s]?:\/\/.*?)\/(.*?)\/(.*?)\/(.*))";
+ const std::regex ArtifactURLRegEx(ArtifactURLRegExString, std::regex::ECMAScript | std::regex::icase);
+ std::match_results<std::string_view::const_iterator> MatchResults;
+ std::string_view UrlToParse(Url);
+ if (regex_match(begin(UrlToParse), end(UrlToParse), MatchResults, ArtifactURLRegEx) && MatchResults.size() == 5)
+ {
+ auto GetMatch = [&MatchResults](uint32_t Index) -> std::string_view {
+ ZEN_ASSERT(Index < MatchResults.size());
+
+ const auto& Match = MatchResults[Index];
+
+ return std::string_view(&*Match.first, Match.second - Match.first);
+ };
+
+ const std::string_view Host = GetMatch(1);
+ const std::string_view Namespace = GetMatch(2);
+ const std::string_view Bucket = GetMatch(3);
+ const std::string_view BuildId = GetMatch(4);
+
+ OutHost = Host;
+ OutNamespace = Namespace;
+ OutBucket = Bucket;
+ OutBuildId = BuildId;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
} // namespace zen
diff --git a/src/zenutil/chunkblock.cpp b/src/zenremotestore/chunking/chunkblock.cpp
index abfc0fb63..05ae13de1 100644
--- a/src/zenutil/chunkblock.cpp
+++ b/src/zenremotestore/chunking/chunkblock.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/chunkblock.h>
+#include <zenremotestore/chunking/chunkblock.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/fmtutils.h>
@@ -8,6 +8,13 @@
#include <vector>
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+
+# include <unordered_map>
+#endif // ZEN_WITH_TESTS
+
namespace zen {
using namespace std::literals;
@@ -254,4 +261,60 @@ IterateChunkBlock(const SharedBuffer& BlockPayload,
return true;
};
+#if ZEN_WITH_TESTS
+
+namespace testutils {
+ static std::vector<std::pair<Oid, CompressedBuffer>> CreateAttachments(
+ const std::span<const size_t>& Sizes,
+ OodleCompressionLevel CompressionLevel = OodleCompressionLevel::VeryFast,
+ uint64_t BlockSize = 0)
+ {
+ std::vector<std::pair<Oid, CompressedBuffer>> Result;
+ Result.reserve(Sizes.size());
+ for (size_t Size : Sizes)
+ {
+ CompressedBuffer Compressed =
+ CompressedBuffer::Compress(SharedBuffer(CreateSemiRandomBlob(Size)), OodleCompressor::Mermaid, CompressionLevel, BlockSize);
+ Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed));
+ }
+ return Result;
+ }
+
+} // namespace testutils
+
+TEST_CASE("project.store.block")
+{
+ using namespace std::literals;
+ using namespace testutils;
+
+ std::vector<std::size_t> AttachmentSizes({7633, 6825, 5738, 8031, 7225, 566, 3656, 6006, 24, 3466, 1093, 4269, 2257, 3685, 3489,
+ 7194, 6151, 5482, 6217, 3511, 6738, 5061, 7537, 2759, 1916, 8210, 2235, 4024, 1582, 5251,
+ 491, 5464, 4607, 8135, 3767, 4045, 4415, 5007, 8876, 6761, 3359, 8526, 4097, 4855, 8225});
+
+ std::vector<std::pair<Oid, CompressedBuffer>> AttachmentsWithId = CreateAttachments(AttachmentSizes);
+ std::vector<std::pair<IoHash, FetchChunkFunc>> Chunks;
+ Chunks.reserve(AttachmentSizes.size());
+ for (const auto& It : AttachmentsWithId)
+ {
+ Chunks.push_back(
+ std::make_pair(It.second.DecodeRawHash(), [Buffer = It.second](const IoHash&) -> std::pair<uint64_t, CompressedBuffer> {
+ return {Buffer.DecodeRawSize(), Buffer};
+ }));
+ }
+ ChunkBlockDescription Block;
+ CompressedBuffer BlockBuffer = GenerateChunkBlock(std::move(Chunks), Block);
+ uint64_t HeaderSize;
+ CHECK(IterateChunkBlock(
+ BlockBuffer.Decompress(),
+ [](CompressedBuffer&&, const IoHash&) {},
+ HeaderSize));
+}
+
+void
+chunkblock_forcelink()
+{
+}
+
+#endif // ZEN_WITH_TESTS
+
} // namespace zen
diff --git a/src/zenutil/chunkedcontent.cpp b/src/zenremotestore/chunking/chunkedcontent.cpp
index cd1bf7dd7..9df7725db 100644
--- a/src/zenutil/chunkedcontent.cpp
+++ b/src/zenremotestore/chunking/chunkedcontent.cpp
@@ -1,18 +1,16 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/chunkedcontent.h>
+#include <zenremotestore/chunking/chunkedcontent.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/timer.h>
#include <zencore/trace.h>
-
-#include <zenutil/chunkedfile.h>
-#include <zenutil/chunkingcontroller.h>
-#include <zenutil/parallelwork.h>
-#include <zenutil/workerpools.h>
+#include <zenremotestore/chunking/chunkedfile.h>
+#include <zenremotestore/chunking/chunkingcontroller.h>
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_set.h>
@@ -805,7 +803,7 @@ ChunkFolderContent(ChunkingStatistics& Stats,
RwLock Lock;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
for (uint32_t PathIndex : Order)
{
@@ -930,7 +928,9 @@ BuildChunkedContentLookup(const ChunkedFolderContent& Content)
for (uint32_t PathIndex = 0; PathIndex < Content.Paths.size(); PathIndex++)
{
std::string LowercaseExtension = Content.Paths[PathIndex].extension().string();
- std::transform(LowercaseExtension.begin(), LowercaseExtension.end(), LowercaseExtension.begin(), ::tolower);
+ std::transform(LowercaseExtension.begin(), LowercaseExtension.end(), LowercaseExtension.begin(), [](char c) {
+ return (char)::tolower(c);
+ });
Result.PathExtensionHash[PathIndex] = HashStringDjb2(LowercaseExtension);
if (Content.RawSizes[PathIndex] > 0)
{
diff --git a/src/zenutil/chunkedfile.cpp b/src/zenremotestore/chunking/chunkedfile.cpp
index a2c041ffd..652110605 100644
--- a/src/zenutil/chunkedfile.cpp
+++ b/src/zenremotestore/chunking/chunkedfile.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/chunkedfile.h>
+#include <zenremotestore/chunking/chunkedfile.h>
#include <zencore/basicfile.h>
#include <zencore/trace.h>
diff --git a/src/zenutil/chunking.cpp b/src/zenremotestore/chunking/chunking.cpp
index 71f0a06e4..71f0a06e4 100644
--- a/src/zenutil/chunking.cpp
+++ b/src/zenremotestore/chunking/chunking.cpp
diff --git a/src/zenutil/chunking.h b/src/zenremotestore/chunking/chunking.h
index 09c56454f..09c56454f 100644
--- a/src/zenutil/chunking.h
+++ b/src/zenremotestore/chunking/chunking.h
diff --git a/src/zenutil/chunkingcontroller.cpp b/src/zenremotestore/chunking/chunkingcontroller.cpp
index 6fb4182c0..49332c2ce 100644
--- a/src/zenutil/chunkingcontroller.cpp
+++ b/src/zenremotestore/chunking/chunkingcontroller.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/chunkingcontroller.h>
+#include <zenremotestore/chunking/chunkingcontroller.h>
#include <zencore/basicfile.h>
#include <zencore/compactbinarybuilder.h>
diff --git a/src/zenutil/include/zenutil/buildstorage.h b/src/zenremotestore/include/zenremotestore/builds/buildstorage.h
index 46ecd0a11..ee0ddcaa4 100644
--- a/src/zenutil/include/zenutil/buildstorage.h
+++ b/src/zenremotestore/include/zenremotestore/builds/buildstorage.h
@@ -3,7 +3,7 @@
#pragma once
#include <zencore/compactbinary.h>
-#include <zenutil/chunkblock.h>
+#include <zenremotestore/chunking/chunkblock.h>
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
diff --git a/src/zenutil/include/zenutil/buildstoragecache.h b/src/zenremotestore/include/zenremotestore/builds/buildstoragecache.h
index a0690a16a..e30270848 100644
--- a/src/zenutil/include/zenutil/buildstoragecache.h
+++ b/src/zenremotestore/include/zenremotestore/builds/buildstoragecache.h
@@ -6,11 +6,12 @@
#include <zencore/compactbinary.h>
#include <zencore/compositebuffer.h>
-#include <zenutil/chunkblock.h>
+#include <zenremotestore/chunking/chunkblock.h>
namespace zen {
class HttpClient;
+class WorkerThreadPool;
class BuildStorageCache
{
@@ -56,5 +57,14 @@ std::unique_ptr<BuildStorageCache> CreateZenBuildStorageCache(HttpClient& H
std::string_view Namespace,
std::string_view Bucket,
const std::filesystem::path& TempFolderPath,
- bool BoostBackgroundThreadCount);
+ WorkerThreadPool& BackgroundWorkerPool);
+
+struct ZenCacheEndpointTestResult
+{
+ bool Success = false;
+ std::string FailureReason;
+};
+
+ZenCacheEndpointTestResult TestZenCacheEndpoint(std::string_view BaseUrl, const bool AssumeHttp2);
+
} // namespace zen
diff --git a/src/zenutil/include/zenutil/filebuildstorage.h b/src/zenremotestore/include/zenremotestore/builds/filebuildstorage.h
index c95fb32e6..8c1e3c5df 100644
--- a/src/zenutil/include/zenutil/filebuildstorage.h
+++ b/src/zenremotestore/include/zenremotestore/builds/filebuildstorage.h
@@ -3,7 +3,7 @@
#pragma once
#include <zencore/logging.h>
-#include <zenutil/buildstorage.h>
+#include <zenremotestore/builds/buildstorage.h>
namespace zen {
class HttpClient;
diff --git a/src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h b/src/zenremotestore/include/zenremotestore/builds/jupiterbuildstorage.h
index bbf070993..9e25ead7a 100644
--- a/src/zenutil/include/zenutil/jupiter/jupiterbuildstorage.h
+++ b/src/zenremotestore/include/zenremotestore/builds/jupiterbuildstorage.h
@@ -3,7 +3,7 @@
#pragma once
#include <zencore/logging.h>
-#include <zenutil/buildstorage.h>
+#include <zenremotestore/builds/buildstorage.h>
namespace zen {
class HttpClient;
@@ -15,4 +15,11 @@ std::unique_ptr<BuildStorage> CreateJupiterBuildStorage(LoggerRef InLog,
std::string_view Bucket,
bool AllowRedirect,
const std::filesystem::path& TempFolderPath);
+
+bool ParseBuildStorageUrl(std::string_view InUrl,
+ std::string& OutHost,
+ std::string& OutNamespace,
+ std::string& OutBucket,
+ std::string& OutBuildId);
+
} // namespace zen
diff --git a/src/zenutil/include/zenutil/chunkblock.h b/src/zenremotestore/include/zenremotestore/chunking/chunkblock.h
index 277580c74..b0d8ef24c 100644
--- a/src/zenutil/include/zenutil/chunkblock.h
+++ b/src/zenremotestore/include/zenremotestore/chunking/chunkblock.h
@@ -37,4 +37,6 @@ bool IterateChunkBlock(const SharedBuffer& BlockPayload,
uint64_t& OutHeaderSize);
std::vector<uint32_t> ReadChunkBlockHeader(const MemoryView BlockView, uint64_t& OutHeaderSize);
+void chunkblock_forcelink();
+
} // namespace zen
diff --git a/src/zenutil/include/zenutil/chunkedcontent.h b/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h
index 306a5d990..306a5d990 100644
--- a/src/zenutil/include/zenutil/chunkedcontent.h
+++ b/src/zenremotestore/include/zenremotestore/chunking/chunkedcontent.h
diff --git a/src/zenutil/include/zenutil/chunkedfile.h b/src/zenremotestore/include/zenremotestore/chunking/chunkedfile.h
index 4cec80fdb..4cec80fdb 100644
--- a/src/zenutil/include/zenutil/chunkedfile.h
+++ b/src/zenremotestore/include/zenremotestore/chunking/chunkedfile.h
diff --git a/src/zenutil/include/zenutil/chunkingcontroller.h b/src/zenremotestore/include/zenremotestore/chunking/chunkingcontroller.h
index 315502265..2d1ba36aa 100644
--- a/src/zenutil/include/zenutil/chunkingcontroller.h
+++ b/src/zenremotestore/include/zenremotestore/chunking/chunkingcontroller.h
@@ -4,7 +4,7 @@
#include <zencore/compactbinary.h>
-#include <zenutil/chunkedfile.h>
+#include <zenremotestore/chunking/chunkedfile.h>
#include <atomic>
#include <filesystem>
diff --git a/src/zenutil/include/zenutil/jupiter/jupiterclient.h b/src/zenremotestore/include/zenremotestore/jupiter/jupiterclient.h
index 8a51bd60a..8a51bd60a 100644
--- a/src/zenutil/include/zenutil/jupiter/jupiterclient.h
+++ b/src/zenremotestore/include/zenremotestore/jupiter/jupiterclient.h
diff --git a/src/zenremotestore/include/zenremotestore/jupiter/jupiterhost.h b/src/zenremotestore/include/zenremotestore/jupiter/jupiterhost.h
new file mode 100644
index 000000000..3bbc700b8
--- /dev/null
+++ b/src/zenremotestore/include/zenremotestore/jupiter/jupiterhost.h
@@ -0,0 +1,35 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace zen {
+
+struct HttpClientSettings;
+
+struct JupiterServerDiscovery
+{
+ struct EndPoint
+ {
+ std::string Name;
+ std::string BaseUrl;
+ bool AssumeHttp2 = false;
+ };
+ std::vector<EndPoint> ServerEndPoints;
+ std::vector<EndPoint> CacheEndPoints;
+};
+
+JupiterServerDiscovery DiscoverJupiterEndpoints(std::string_view Host, const HttpClientSettings& ClientSettings);
+
+struct JupiterEndpointTestResult
+{
+ bool Success = false;
+ std::string FailureReason;
+};
+
+JupiterEndpointTestResult TestJupiterEndpoint(std::string_view BaseUrl, const bool AssumeHttp2);
+
+} // namespace zen
diff --git a/src/zenutil/include/zenutil/jupiter/jupitersession.h b/src/zenremotestore/include/zenremotestore/jupiter/jupitersession.h
index b79790f25..b79790f25 100644
--- a/src/zenutil/include/zenutil/jupiter/jupitersession.h
+++ b/src/zenremotestore/include/zenremotestore/jupiter/jupitersession.h
diff --git a/src/zenserver/projectstore/buildsremoteprojectstore.h b/src/zenremotestore/include/zenremotestore/projectstore/buildsremoteprojectstore.h
index 60b6caef7..037325ed1 100644
--- a/src/zenserver/projectstore/buildsremoteprojectstore.h
+++ b/src/zenremotestore/include/zenremotestore/projectstore/buildsremoteprojectstore.h
@@ -2,7 +2,7 @@
#pragma once
-#include "remoteprojectstore.h"
+#include <zenremotestore/projectstore/remoteprojectstore.h>
namespace zen {
@@ -26,6 +26,8 @@ struct BuildsRemoteStoreOptions : RemoteStoreOptions
std::shared_ptr<RemoteProjectStore> CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options,
const std::filesystem::path& TempFilePath,
- bool Quiet);
+ bool Quiet,
+ bool Unattended,
+ bool Hidden);
} // namespace zen
diff --git a/src/zenserver/projectstore/fileremoteprojectstore.h b/src/zenremotestore/include/zenremotestore/projectstore/fileremoteprojectstore.h
index 8da9692d5..ff2ecb405 100644
--- a/src/zenserver/projectstore/fileremoteprojectstore.h
+++ b/src/zenremotestore/include/zenremotestore/projectstore/fileremoteprojectstore.h
@@ -2,7 +2,7 @@
#pragma once
-#include "remoteprojectstore.h"
+#include <zenremotestore/projectstore/remoteprojectstore.h>
namespace zen {
diff --git a/src/zenserver/projectstore/jupiterremoteprojectstore.h b/src/zenremotestore/include/zenremotestore/projectstore/jupiterremoteprojectstore.h
index ac2d25b47..13a346039 100644
--- a/src/zenserver/projectstore/jupiterremoteprojectstore.h
+++ b/src/zenremotestore/include/zenremotestore/projectstore/jupiterremoteprojectstore.h
@@ -2,7 +2,7 @@
#pragma once
-#include "remoteprojectstore.h"
+#include <zenremotestore/projectstore/remoteprojectstore.h>
namespace zen {
@@ -26,6 +26,8 @@ struct JupiterRemoteStoreOptions : RemoteStoreOptions
std::shared_ptr<RemoteProjectStore> CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options,
const std::filesystem::path& TempFilePath,
- bool Quiet);
+ bool Quiet,
+ bool Unattended,
+ bool Hidden);
} // namespace zen
diff --git a/src/zenserver/projectstore/remoteprojectstore.h b/src/zenremotestore/include/zenremotestore/projectstore/remoteprojectstore.h
index 1210afc7c..fbcdde955 100644
--- a/src/zenserver/projectstore/remoteprojectstore.h
+++ b/src/zenremotestore/include/zenremotestore/projectstore/remoteprojectstore.h
@@ -3,9 +3,9 @@
#pragma once
#include <zencore/jobqueue.h>
-#include "projectstore.h"
+#include <zenstore/projectstore.h>
-#include <zenutil/chunkblock.h>
+#include <zenremotestore/chunking/chunkblock.h>
#include <unordered_set>
@@ -110,10 +110,12 @@ public:
struct RemoteStoreOptions
{
static const size_t DefaultMaxBlockSize = 64u * 1024u * 1024u;
+ static const size_t DefaultMaxChunksPerBlock = 4u * 1000u;
static const size_t DefaultMaxChunkEmbedSize = 3u * 512u * 1024u;
static const size_t DefaultChunkFileSizeLimit = 256u * 1024u * 1024u;
size_t MaxBlockSize = DefaultMaxBlockSize;
+ size_t MaxChunksPerBlock = DefaultMaxChunksPerBlock;
size_t MaxChunkEmbedSize = DefaultMaxChunkEmbedSize;
size_t ChunkFileSizeLimit = DefaultChunkFileSizeLimit;
};
@@ -124,7 +126,9 @@ RemoteProjectStore::LoadContainerResult BuildContainer(
CidStore& ChunkStore,
ProjectStore::Project& Project,
ProjectStore::Oplog& Oplog,
+ WorkerThreadPool& WorkerPool,
size_t MaxBlockSize,
+ size_t MaxChunksPerBlock,
size_t MaxChunkEmbedSize,
size_t ChunkFileSizeLimit,
bool BuildBlocks,
@@ -150,7 +154,10 @@ RemoteProjectStore::Result SaveOplog(CidStore& ChunkStore,
RemoteProjectStore& RemoteStore,
ProjectStore::Project& Project,
ProjectStore::Oplog& Oplog,
+ WorkerThreadPool& NetworkWorkerPool,
+ WorkerThreadPool& WorkerPool,
size_t MaxBlockSize,
+ size_t MaxChunksPerBlock,
size_t MaxChunkEmbedSize,
size_t ChunkFileSizeLimit,
bool EmbedLooseFiles,
@@ -161,6 +168,8 @@ RemoteProjectStore::Result SaveOplog(CidStore& ChunkStore,
RemoteProjectStore::Result LoadOplog(CidStore& ChunkStore,
RemoteProjectStore& RemoteStore,
ProjectStore::Oplog& Oplog,
+ WorkerThreadPool& NetworkWorkerPool,
+ WorkerThreadPool& WorkerPool,
bool ForceDownload,
bool IgnoreMissingAttachments,
bool CleanOplog,
@@ -169,4 +178,6 @@ RemoteProjectStore::Result LoadOplog(CidStore& ChunkStore,
std::vector<IoHash> GetBlockHashesFromOplog(CbObjectView ContainerObject);
std::vector<ThinChunkBlockDescription> GetBlocksFromOplog(CbObjectView ContainerObject, std::span<const IoHash> IncludeBlockHashes);
+void remoteprojectstore_forcelink();
+
} // namespace zen
diff --git a/src/zenserver/projectstore/zenremoteprojectstore.h b/src/zenremotestore/include/zenremotestore/projectstore/zenremoteprojectstore.h
index 7c81a597d..d6b62c563 100644
--- a/src/zenserver/projectstore/zenremoteprojectstore.h
+++ b/src/zenremotestore/include/zenremotestore/projectstore/zenremoteprojectstore.h
@@ -2,7 +2,7 @@
#pragma once
-#include "remoteprojectstore.h"
+#include <zenremotestore/projectstore/remoteprojectstore.h>
namespace zen {
diff --git a/src/zenremotestore/include/zenremotestore/zenremotestore.h b/src/zenremotestore/include/zenremotestore/zenremotestore.h
new file mode 100644
index 000000000..576c8ee75
--- /dev/null
+++ b/src/zenremotestore/include/zenremotestore/zenremotestore.h
@@ -0,0 +1,13 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/zencore.h>
+
+#define ZENSTORE_API
+
+namespace zen {
+
+ZENSTORE_API void zenremotestore_forcelinktests();
+
+} // namespace zen
diff --git a/src/zenutil/jupiter/jupiterclient.cpp b/src/zenremotestore/jupiter/jupiterclient.cpp
index dbac218a4..bf9d8e346 100644
--- a/src/zenutil/jupiter/jupiterclient.cpp
+++ b/src/zenremotestore/jupiter/jupiterclient.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/jupiter/jupiterclient.h>
+#include <zenremotestore/jupiter/jupiterclient.h>
namespace zen {
diff --git a/src/zenremotestore/jupiter/jupiterhost.cpp b/src/zenremotestore/jupiter/jupiterhost.cpp
new file mode 100644
index 000000000..df6be10c9
--- /dev/null
+++ b/src/zenremotestore/jupiter/jupiterhost.cpp
@@ -0,0 +1,66 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenremotestore/jupiter/jupiterhost.h>
+
+#include <zencore/compactbinary.h>
+#include <zencore/fmtutils.h>
+#include <zenhttp/httpclient.h>
+
+namespace zen {
+
+JupiterServerDiscovery
+DiscoverJupiterEndpoints(std::string_view Host, const HttpClientSettings& ClientSettings)
+{
+ JupiterServerDiscovery Result;
+
+ HttpClient DiscoveryHttpClient(Host, ClientSettings);
+ HttpClient::Response ServerInfoResponse = DiscoveryHttpClient.Get("/api/v1/status/servers", HttpClient::Accept(HttpContentType::kJSON));
+ if (!ServerInfoResponse.IsSuccess())
+ {
+ ServerInfoResponse.ThrowError(fmt::format("Failed to get list of servers from discovery url '{}'", Host));
+ }
+ std::string_view JsonResponse = ServerInfoResponse.AsText();
+ CbObject CbPayload = LoadCompactBinaryFromJson(JsonResponse).AsObject();
+ CbArrayView ServerEndpoints = CbPayload["serverEndpoints"].AsArrayView();
+ Result.ServerEndPoints.reserve(ServerEndpoints.Num());
+
+ auto ParseEndPoints = [](CbArrayView ServerEndpoints) -> std::vector<JupiterServerDiscovery::EndPoint> {
+ std::vector<JupiterServerDiscovery::EndPoint> Result;
+
+ Result.reserve(ServerEndpoints.Num());
+ for (CbFieldView ServerEndpointView : ServerEndpoints)
+ {
+ CbObjectView ServerEndPoint = ServerEndpointView.AsObjectView();
+ Result.push_back(JupiterServerDiscovery::EndPoint{.Name = std::string(ServerEndPoint["name"].AsString()),
+ .BaseUrl = std::string(ServerEndPoint["baseUrl"].AsString()),
+ .AssumeHttp2 = ServerEndPoint["baseUrl"].AsBool(false)});
+ }
+ return Result;
+ };
+
+ Result.ServerEndPoints = ParseEndPoints(CbPayload["serverEndpoints"].AsArrayView());
+ Result.CacheEndPoints = ParseEndPoints(CbPayload["cacheEndpoints"].AsArrayView());
+
+ return Result;
+}
+
+JupiterEndpointTestResult
+TestJupiterEndpoint(std::string_view BaseUrl, const bool AssumeHttp2)
+{
+ HttpClientSettings TestClientSettings{.LogCategory = "httpbuildsclient",
+ .ConnectTimeout = std::chrono::milliseconds{1000},
+ .Timeout = std::chrono::milliseconds{2000},
+ .AssumeHttp2 = AssumeHttp2,
+ .AllowResume = true,
+ .RetryCount = 0};
+
+ HttpClient TestHttpClient(BaseUrl, TestClientSettings);
+ HttpClient::Response TestResponse = TestHttpClient.Get("/health/live");
+ if (TestResponse.IsSuccess())
+ {
+ return {.Success = true};
+ }
+ return {.Success = false, .FailureReason = TestResponse.ErrorMessage("")};
+}
+
+} // namespace zen
diff --git a/src/zenutil/jupiter/jupitersession.cpp b/src/zenremotestore/jupiter/jupitersession.cpp
index ab2a2ea55..942e2a9dc 100644
--- a/src/zenutil/jupiter/jupitersession.cpp
+++ b/src/zenremotestore/jupiter/jupitersession.cpp
@@ -1,9 +1,10 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/jupiter/jupitersession.h>
+#include <zenremotestore/jupiter/jupitersession.h>
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compositebuffer.h>
#include <zencore/compress.h>
#include <zencore/fmtutils.h>
@@ -524,7 +525,15 @@ JupiterSession::PutMultipartBuildBlob(std::string_view Namespace,
ZEN_WARN("{}", StartMultipartResponse.ErrorMessage("startMultipartUpload: "));
return detail::ConvertResponse(StartMultipartResponse, "JupiterSession::PutMultipartBuildBlob"sv);
}
- CbObject ResponseObject = LoadCompactBinaryObject(StartMultipartResponse.ResponsePayload);
+ CbValidateError ValidateResult = CbValidateError::None;
+ CbObject ResponseObject = ValidateAndReadCompactBinaryObject(IoBuffer(StartMultipartResponse.ResponsePayload), ValidateResult);
+ if (ValidateResult != CbValidateError::None)
+ {
+ JupiterResult Result = detail::ConvertResponse(StartMultipartResponse, "JupiterSession::PutMultipartBuildBlob"sv);
+ Result.ErrorCode = (int32)HttpResponseCode::UnsupportedMediaType;
+ Result.Reason = fmt::format("Invalid multipart response object format: '{}'", ToString(ValidateResult));
+ return Result;
+ }
struct WorkloadData
{
diff --git a/src/zenserver/projectstore/buildsremoteprojectstore.cpp b/src/zenremotestore/projectstore/buildsremoteprojectstore.cpp
index a9dd48510..2cc8ed4aa 100644
--- a/src/zenserver/projectstore/buildsremoteprojectstore.cpp
+++ b/src/zenremotestore/projectstore/buildsremoteprojectstore.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "buildsremoteprojectstore.h"
+#include <zenremotestore/projectstore/buildsremoteprojectstore.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compress.h>
@@ -8,7 +8,7 @@
#include <zencore/scopeguard.h>
#include <zenhttp/httpclientauth.h>
-#include <zenutil/jupiter/jupiterbuildstorage.h>
+#include <zenremotestore/builds/jupiterbuildstorage.h>
namespace zen {
@@ -255,7 +255,9 @@ public:
}
catch (const HttpClientError& Ex)
{
- Result.ErrorCode = Ex.m_Error != 0 ? Ex.m_Error : Ex.m_ResponseCode != HttpResponseCode::ImATeapot ? (int)Ex.m_ResponseCode : 0;
+ Result.ErrorCode = Ex.GetInternalErrorCode() != 0 ? Ex.GetInternalErrorCode()
+ : Ex.GetHttpResponseCode() != HttpResponseCode::ImATeapot ? (int)Ex.GetHttpResponseCode()
+ : 0;
Result.Reason = fmt::format("Failed finalizing oplog container build part to {}/{}/{}/{}/{}. Reason: '{}'",
m_Url,
m_Namespace,
@@ -284,9 +286,9 @@ public:
}
catch (const HttpClientError& Ex)
{
- Result.ErrorCode = Ex.m_Error != 0 ? Ex.m_Error
- : Ex.m_ResponseCode != HttpResponseCode::ImATeapot ? (int)Ex.m_ResponseCode
- : 0;
+ Result.ErrorCode = Ex.GetInternalErrorCode() != 0 ? Ex.GetInternalErrorCode()
+ : Ex.GetHttpResponseCode() != HttpResponseCode::ImATeapot ? (int)Ex.GetHttpResponseCode()
+ : 0;
Result.Reason = fmt::format("Failed finalizing oplog container build to {}/{}/{}/{}. Reason: '{}'",
m_Url,
m_Namespace,
@@ -452,7 +454,9 @@ public:
private:
static int MakeErrorCode(const HttpClientError& Ex)
{
- return Ex.m_Error != 0 ? Ex.m_Error : Ex.m_ResponseCode != HttpResponseCode::ImATeapot ? (int)Ex.m_ResponseCode : 0;
+ return Ex.GetInternalErrorCode() != 0 ? Ex.GetInternalErrorCode()
+ : Ex.GetHttpResponseCode() != HttpResponseCode::ImATeapot ? (int)Ex.GetHttpResponseCode()
+ : 0;
}
std::unique_ptr<BuildStorage::Statistics> m_BuildStorageStats;
@@ -470,7 +474,11 @@ private:
};
std::shared_ptr<RemoteProjectStore>
-CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options, const std::filesystem::path& TempFilePath, bool Quiet)
+CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options,
+ const std::filesystem::path& TempFilePath,
+ bool Quiet,
+ bool Unattended,
+ bool Hidden)
{
std::string Url = Options.Url;
if (Url.find("://"sv) == std::string::npos)
@@ -495,7 +503,8 @@ CreateJupiterBuildsRemoteStore(const BuildsRemoteStoreOptions& Options, const st
}
else if (!Options.OidcExePath.empty())
{
- if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet); TokenProviderMaybe)
+ if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet, Unattended, Hidden);
+ TokenProviderMaybe)
{
TokenProvider = TokenProviderMaybe.value();
}
diff --git a/src/zenserver/projectstore/fileremoteprojectstore.cpp b/src/zenremotestore/projectstore/fileremoteprojectstore.cpp
index 375e44e59..d6e6944f4 100644
--- a/src/zenserver/projectstore/fileremoteprojectstore.cpp
+++ b/src/zenremotestore/projectstore/fileremoteprojectstore.cpp
@@ -1,12 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "fileremoteprojectstore.h"
+#include <zenremotestore/projectstore/fileremoteprojectstore.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compress.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/timer.h>
+#include <zenhttp/httpcommon.h>
namespace zen {
@@ -260,11 +262,14 @@ private:
ContainerPayload = ContainerFile.ReadAll();
}
AddStats(0, ContainerPayload.GetSize(), Timer.GetElapsedTimeUs() * 1000);
- Result.ContainerObject = LoadCompactBinaryObject(ContainerPayload);
- if (!Result.ContainerObject)
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (Result.ContainerObject = ValidateAndReadCompactBinaryObject(std::move(ContainerPayload), ValidateResult);
+ ValidateResult != CbValidateError::None || !Result.ContainerObject)
{
Result.ErrorCode = gsl::narrow<int32_t>(HttpResponseCode::InternalServerError);
- Result.Reason = fmt::format("The file {} is not formatted as a compact binary object", SourcePath.string());
+ Result.Reason = fmt::format("The file {} is not formatted as a compact binary object ('{}')",
+ SourcePath.string(),
+ ToString(ValidateResult));
Result.ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0;
return Result;
}
diff --git a/src/zenserver/projectstore/jupiterremoteprojectstore.cpp b/src/zenremotestore/projectstore/jupiterremoteprojectstore.cpp
index d79ad3cb7..dda5ef99d 100644
--- a/src/zenserver/projectstore/jupiterremoteprojectstore.cpp
+++ b/src/zenremotestore/projectstore/jupiterremoteprojectstore.cpp
@@ -1,14 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "jupiterremoteprojectstore.h"
+#include <zenremotestore/projectstore/jupiterremoteprojectstore.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compress.h>
#include <zencore/fmtutils.h>
#include <zenhttp/httpclientauth.h>
-#include <zenutil/jupiter/jupiterclient.h>
-#include <zenutil/jupiter/jupitersession.h>
+#include <zenremotestore/jupiter/jupiterclient.h>
+#include <zenremotestore/jupiter/jupitersession.h>
namespace zen {
@@ -249,8 +250,9 @@ private:
return Result;
}
- CbObject ContainerObject = LoadCompactBinaryObject(GetResult.Response);
- if (!ContainerObject)
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject ContainerObject = ValidateAndReadCompactBinaryObject(IoBuffer(GetResult.Response), ValidateResult);
+ ValidateResult != CbValidateError::None || !ContainerObject)
{
return LoadContainerResult{
RemoteProjectStore::Result{.ErrorCode = gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
@@ -262,7 +264,10 @@ private:
Key)},
{}};
}
- return LoadContainerResult{ConvertResult(GetResult), std::move(ContainerObject)};
+ else
+ {
+ return LoadContainerResult{ConvertResult(GetResult), std::move(ContainerObject)};
+ }
}
void AddStats(const JupiterResult& Result)
@@ -338,7 +343,11 @@ private:
};
std::shared_ptr<RemoteProjectStore>
-CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options, const std::filesystem::path& TempFilePath, bool Quiet)
+CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options,
+ const std::filesystem::path& TempFilePath,
+ bool Quiet,
+ bool Unattended,
+ bool Hidden)
{
std::string Url = Options.Url;
if (Url.find("://"sv) == std::string::npos)
@@ -369,7 +378,8 @@ CreateJupiterRemoteStore(const JupiterRemoteStoreOptions& Options, const std::fi
}
else if (!Options.OidcExePath.empty())
{
- if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet); TokenProviderMaybe)
+ if (auto TokenProviderMaybe = httpclientauth::CreateFromOidcTokenExecutable(Options.OidcExePath, Url, Quiet, Unattended, Hidden);
+ TokenProviderMaybe)
{
TokenProvider = TokenProviderMaybe.value();
}
diff --git a/src/zenserver/projectstore/remoteprojectstore.cpp b/src/zenremotestore/projectstore/remoteprojectstore.cpp
index 857e868da..0d1e0d93d 100644
--- a/src/zenserver/projectstore/remoteprojectstore.cpp
+++ b/src/zenremotestore/projectstore/remoteprojectstore.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "remoteprojectstore.h"
+#include <zenremotestore/projectstore/remoteprojectstore.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinaryutil.h>
@@ -12,12 +12,18 @@
#include <zencore/stream.h>
#include <zencore/timer.h>
#include <zencore/workthreadpool.h>
+#include <zenhttp/httpcommon.h>
+#include <zenremotestore/chunking/chunkedfile.h>
#include <zenstore/cidstore.h>
-#include <zenutil/chunkedfile.h>
-#include <zenutil/workerpools.h>
#include <unordered_map>
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <zenremotestore/projectstore/fileremoteprojectstore.h>
+#endif // ZEN_WITH_TESTS
+
namespace zen {
/*
@@ -180,7 +186,7 @@ namespace remotestore_impl {
{
Ops.emplace_back(CbObjectView(&OpsData[OpDataOffset]));
}
- std::vector<uint32_t> OpLsns = Oplog.AppendNewOplogEntries(Ops);
+ std::vector<ProjectStore::LogSequenceNumber> OpLsns = Oplog.AppendNewOplogEntries(Ops);
OpsCompleteCount += OpLsns.size();
OpsData.clear();
OpDataOffsets.clear();
@@ -245,101 +251,105 @@ namespace remotestore_impl {
const std::vector<IoHash>& Chunks)
{
AttachmentsDownloadLatch.AddCount(1);
- NetworkWorkerPool.ScheduleWork([&RemoteStore,
- &ChunkStore,
- &WorkerPool,
- &AttachmentsDownloadLatch,
- &AttachmentsWriteLatch,
- &RemoteResult,
- Chunks = Chunks,
- &Info,
- &LoadAttachmentsTimer,
- &DownloadStartMS,
- IgnoreMissingAttachments,
- OptionalContext]() {
- auto _ = MakeGuard([&AttachmentsDownloadLatch] { AttachmentsDownloadLatch.CountDown(); });
- if (RemoteResult.IsError())
- {
- return;
- }
- try
- {
- uint64_t Unset = (std::uint64_t)-1;
- DownloadStartMS.compare_exchange_strong(Unset, LoadAttachmentsTimer.GetElapsedTimeMs());
- RemoteProjectStore::LoadAttachmentsResult Result = RemoteStore.LoadAttachments(Chunks);
- if (Result.ErrorCode)
- {
- ReportMessage(OptionalContext,
- fmt::format("Failed to load attachments with {} chunks ({}): {}",
- Chunks.size(),
- RemoteResult.GetError(),
- RemoteResult.GetErrorReason()));
- Info.MissingAttachmentCount.fetch_add(1);
- if (IgnoreMissingAttachments)
- {
- RemoteResult.SetError(Result.ErrorCode, Result.Reason, Result.Text);
- }
- return;
- }
- Info.AttachmentsDownloaded.fetch_add(Chunks.size());
- ZEN_INFO("Loaded {} bulk attachments in {}",
- Chunks.size(),
- NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)));
+ NetworkWorkerPool.ScheduleWork(
+ [&RemoteStore,
+ &ChunkStore,
+ &WorkerPool,
+ &AttachmentsDownloadLatch,
+ &AttachmentsWriteLatch,
+ &RemoteResult,
+ Chunks = Chunks,
+ &Info,
+ &LoadAttachmentsTimer,
+ &DownloadStartMS,
+ IgnoreMissingAttachments,
+ OptionalContext]() {
+ auto _ = MakeGuard([&AttachmentsDownloadLatch] { AttachmentsDownloadLatch.CountDown(); });
if (RemoteResult.IsError())
{
return;
}
- AttachmentsWriteLatch.AddCount(1);
- WorkerPool.ScheduleWork([&AttachmentsWriteLatch, &RemoteResult, &Info, &ChunkStore, Chunks = std::move(Result.Chunks)]() {
- auto _ = MakeGuard([&AttachmentsWriteLatch] { AttachmentsWriteLatch.CountDown(); });
- if (RemoteResult.IsError())
+ try
+ {
+ uint64_t Unset = (std::uint64_t)-1;
+ DownloadStartMS.compare_exchange_strong(Unset, LoadAttachmentsTimer.GetElapsedTimeMs());
+ RemoteProjectStore::LoadAttachmentsResult Result = RemoteStore.LoadAttachments(Chunks);
+ if (Result.ErrorCode)
{
+ ReportMessage(OptionalContext,
+ fmt::format("Failed to load attachments with {} chunks ({}): {}",
+ Chunks.size(),
+ RemoteResult.GetError(),
+ RemoteResult.GetErrorReason()));
+ Info.MissingAttachmentCount.fetch_add(1);
+ if (IgnoreMissingAttachments)
+ {
+ RemoteResult.SetError(Result.ErrorCode, Result.Reason, Result.Text);
+ }
return;
}
- if (!Chunks.empty())
+ Info.AttachmentsDownloaded.fetch_add(Chunks.size());
+ ZEN_INFO("Loaded {} bulk attachments in {}",
+ Chunks.size(),
+ NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)));
+ if (RemoteResult.IsError())
{
- try
- {
- std::vector<IoBuffer> WriteAttachmentBuffers;
- std::vector<IoHash> WriteRawHashes;
- WriteAttachmentBuffers.reserve(Chunks.size());
- WriteRawHashes.reserve(Chunks.size());
-
- for (const auto& It : Chunks)
+ return;
+ }
+ AttachmentsWriteLatch.AddCount(1);
+ WorkerPool.ScheduleWork(
+ [&AttachmentsWriteLatch, &RemoteResult, &Info, &ChunkStore, Chunks = std::move(Result.Chunks)]() {
+ auto _ = MakeGuard([&AttachmentsWriteLatch] { AttachmentsWriteLatch.CountDown(); });
+ if (RemoteResult.IsError())
{
- uint64_t ChunkSize = It.second.GetCompressedSize();
- Info.AttachmentBytesDownloaded.fetch_add(ChunkSize);
- WriteAttachmentBuffers.push_back(It.second.GetCompressed().Flatten().AsIoBuffer());
- WriteRawHashes.push_back(It.first);
+ return;
}
- std::vector<CidStore::InsertResult> InsertResults =
- ChunkStore.AddChunks(WriteAttachmentBuffers, WriteRawHashes, CidStore::InsertMode::kCopyOnly);
-
- for (size_t Index = 0; Index < InsertResults.size(); Index++)
+ if (!Chunks.empty())
{
- if (InsertResults[Index].New)
+ try
{
- Info.AttachmentBytesStored.fetch_add(WriteAttachmentBuffers[Index].GetSize());
- Info.AttachmentsStored.fetch_add(1);
+ std::vector<IoBuffer> WriteAttachmentBuffers;
+ std::vector<IoHash> WriteRawHashes;
+ WriteAttachmentBuffers.reserve(Chunks.size());
+ WriteRawHashes.reserve(Chunks.size());
+
+ for (const auto& It : Chunks)
+ {
+ uint64_t ChunkSize = It.second.GetCompressedSize();
+ Info.AttachmentBytesDownloaded.fetch_add(ChunkSize);
+ WriteAttachmentBuffers.push_back(It.second.GetCompressed().Flatten().AsIoBuffer());
+ WriteRawHashes.push_back(It.first);
+ }
+ std::vector<CidStore::InsertResult> InsertResults =
+ ChunkStore.AddChunks(WriteAttachmentBuffers, WriteRawHashes, CidStore::InsertMode::kCopyOnly);
+
+ for (size_t Index = 0; Index < InsertResults.size(); Index++)
+ {
+ if (InsertResults[Index].New)
+ {
+ Info.AttachmentBytesStored.fetch_add(WriteAttachmentBuffers[Index].GetSize());
+ Info.AttachmentsStored.fetch_add(1);
+ }
+ }
+ }
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Failed to bulk save {} attachments", Chunks.size()),
+ Ex.what());
}
}
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Failed to bulk save {} attachments", Chunks.size()),
- Ex.what());
- }
- }
- });
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Failed to bulk load {} attachments", Chunks.size()),
- Ex.what());
- }
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
+ }
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Failed to bulk load {} attachments", Chunks.size()),
+ Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
};
void DownloadAndSaveBlock(CidStore& ChunkStore,
@@ -359,226 +369,237 @@ namespace remotestore_impl {
uint32_t RetriesLeft)
{
AttachmentsDownloadLatch.AddCount(1);
- NetworkWorkerPool.ScheduleWork([&AttachmentsDownloadLatch,
- &AttachmentsWriteLatch,
- &ChunkStore,
- &RemoteStore,
- &NetworkWorkerPool,
- &WorkerPool,
- BlockHash,
- &RemoteResult,
- &Info,
- &LoadAttachmentsTimer,
- &DownloadStartMS,
- IgnoreMissingAttachments,
- OptionalContext,
- RetriesLeft,
- Chunks = Chunks]() {
- auto _ = MakeGuard([&AttachmentsDownloadLatch] { AttachmentsDownloadLatch.CountDown(); });
- if (RemoteResult.IsError())
- {
- return;
- }
- try
- {
- uint64_t Unset = (std::uint64_t)-1;
- DownloadStartMS.compare_exchange_strong(Unset, LoadAttachmentsTimer.GetElapsedTimeMs());
- RemoteProjectStore::LoadAttachmentResult BlockResult = RemoteStore.LoadAttachment(BlockHash);
- if (BlockResult.ErrorCode)
- {
- ReportMessage(OptionalContext,
- fmt::format("Failed to download block attachment {} ({}): {}",
- BlockHash,
- RemoteResult.GetError(),
- RemoteResult.GetErrorReason()));
- Info.MissingAttachmentCount.fetch_add(1);
- if (!IgnoreMissingAttachments)
- {
- RemoteResult.SetError(BlockResult.ErrorCode, BlockResult.Reason, BlockResult.Text);
- }
- return;
- }
+ NetworkWorkerPool.ScheduleWork(
+ [&AttachmentsDownloadLatch,
+ &AttachmentsWriteLatch,
+ &ChunkStore,
+ &RemoteStore,
+ &NetworkWorkerPool,
+ &WorkerPool,
+ BlockHash,
+ &RemoteResult,
+ &Info,
+ &LoadAttachmentsTimer,
+ &DownloadStartMS,
+ IgnoreMissingAttachments,
+ OptionalContext,
+ RetriesLeft,
+ Chunks = Chunks]() {
+ auto _ = MakeGuard([&AttachmentsDownloadLatch] { AttachmentsDownloadLatch.CountDown(); });
if (RemoteResult.IsError())
{
return;
}
- uint64_t BlockSize = BlockResult.Bytes.GetSize();
- Info.AttachmentBlocksDownloaded.fetch_add(1);
- ZEN_INFO("Loaded block attachment '{}' in {} ({})",
- BlockHash,
- NiceTimeSpanMs(static_cast<uint64_t>(BlockResult.ElapsedSeconds * 1000)),
- NiceBytes(BlockSize));
- Info.AttachmentBlockBytesDownloaded.fetch_add(BlockSize);
-
- AttachmentsWriteLatch.AddCount(1);
- WorkerPool.ScheduleWork([&AttachmentsDownloadLatch,
- &AttachmentsWriteLatch,
- &ChunkStore,
- &RemoteStore,
- &NetworkWorkerPool,
- &WorkerPool,
- BlockHash,
- &RemoteResult,
- &Info,
- &LoadAttachmentsTimer,
- &DownloadStartMS,
- IgnoreMissingAttachments,
- OptionalContext,
- RetriesLeft,
- Chunks = Chunks,
- Bytes = std::move(BlockResult.Bytes)]() {
- auto _ = MakeGuard([&AttachmentsWriteLatch] { AttachmentsWriteLatch.CountDown(); });
- if (RemoteResult.IsError())
+ try
+ {
+ uint64_t Unset = (std::uint64_t)-1;
+ DownloadStartMS.compare_exchange_strong(Unset, LoadAttachmentsTimer.GetElapsedTimeMs());
+ RemoteProjectStore::LoadAttachmentResult BlockResult = RemoteStore.LoadAttachment(BlockHash);
+ if (BlockResult.ErrorCode)
{
+ ReportMessage(OptionalContext,
+ fmt::format("Failed to download block attachment {} ({}): {}",
+ BlockHash,
+ RemoteResult.GetError(),
+ RemoteResult.GetErrorReason()));
+ Info.MissingAttachmentCount.fetch_add(1);
+ if (!IgnoreMissingAttachments)
+ {
+ RemoteResult.SetError(BlockResult.ErrorCode, BlockResult.Reason, BlockResult.Text);
+ }
return;
}
- try
+ if (RemoteResult.IsError())
{
- ZEN_ASSERT(Bytes.Size() > 0);
- std::unordered_set<IoHash, IoHash::Hasher> WantedChunks;
- WantedChunks.reserve(Chunks.size());
- WantedChunks.insert(Chunks.begin(), Chunks.end());
- std::vector<IoBuffer> WriteAttachmentBuffers;
- std::vector<IoHash> WriteRawHashes;
-
- IoHash RawHash;
- uint64_t RawSize;
- CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Bytes), RawHash, RawSize);
- if (!Compressed)
- {
- if (RetriesLeft > 0)
+ return;
+ }
+ uint64_t BlockSize = BlockResult.Bytes.GetSize();
+ Info.AttachmentBlocksDownloaded.fetch_add(1);
+ ZEN_INFO("Loaded block attachment '{}' in {} ({})",
+ BlockHash,
+ NiceTimeSpanMs(static_cast<uint64_t>(BlockResult.ElapsedSeconds * 1000)),
+ NiceBytes(BlockSize));
+ Info.AttachmentBlockBytesDownloaded.fetch_add(BlockSize);
+
+ AttachmentsWriteLatch.AddCount(1);
+ WorkerPool.ScheduleWork(
+ [&AttachmentsDownloadLatch,
+ &AttachmentsWriteLatch,
+ &ChunkStore,
+ &RemoteStore,
+ &NetworkWorkerPool,
+ &WorkerPool,
+ BlockHash,
+ &RemoteResult,
+ &Info,
+ &LoadAttachmentsTimer,
+ &DownloadStartMS,
+ IgnoreMissingAttachments,
+ OptionalContext,
+ RetriesLeft,
+ Chunks = Chunks,
+ Bytes = std::move(BlockResult.Bytes)]() {
+ auto _ = MakeGuard([&AttachmentsWriteLatch] { AttachmentsWriteLatch.CountDown(); });
+ if (RemoteResult.IsError())
{
- ReportMessage(
- OptionalContext,
- fmt::format("Block attachment {} is malformed, can't parse as compressed binary, retrying download",
- BlockHash));
- return DownloadAndSaveBlock(ChunkStore,
- RemoteStore,
- IgnoreMissingAttachments,
- OptionalContext,
- NetworkWorkerPool,
- WorkerPool,
- AttachmentsDownloadLatch,
- AttachmentsWriteLatch,
- RemoteResult,
- Info,
- LoadAttachmentsTimer,
- DownloadStartMS,
- BlockHash,
- std::move(Chunks),
- RetriesLeft - 1);
+ return;
}
- ReportMessage(OptionalContext,
- fmt::format("Block attachment {} is malformed, can't parse as compressed binary", BlockHash));
- RemoteResult.SetError(
- gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
- fmt::format("Block attachment {} is malformed, can't parse as compressed binary", BlockHash),
- {});
- return;
- }
- SharedBuffer BlockPayload = Compressed.Decompress();
- if (!BlockPayload)
- {
- if (RetriesLeft > 0)
+ try
{
- ReportMessage(OptionalContext,
- fmt::format("Block attachment {} is malformed, can't decompress payload, retrying download",
- BlockHash));
- return DownloadAndSaveBlock(ChunkStore,
- RemoteStore,
- IgnoreMissingAttachments,
- OptionalContext,
- NetworkWorkerPool,
- WorkerPool,
- AttachmentsDownloadLatch,
- AttachmentsWriteLatch,
- RemoteResult,
- Info,
- LoadAttachmentsTimer,
- DownloadStartMS,
- BlockHash,
- std::move(Chunks),
- RetriesLeft - 1);
- }
- ReportMessage(OptionalContext,
- fmt::format("Block attachment {} is malformed, can't decompress payload", BlockHash));
- RemoteResult.SetError(gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
- fmt::format("Block attachment {} is malformed, can't decompress payload", BlockHash),
- {});
- return;
- }
- if (RawHash != BlockHash)
- {
- ReportMessage(OptionalContext,
- fmt::format("Block attachment {} has mismatching raw hash ({})", BlockHash, RawHash));
- RemoteResult.SetError(gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
- fmt::format("Block attachment {} has mismatching raw hash ({})", BlockHash, RawHash),
- {});
- return;
- }
+ ZEN_ASSERT(Bytes.Size() > 0);
+ std::unordered_set<IoHash, IoHash::Hasher> WantedChunks;
+ WantedChunks.reserve(Chunks.size());
+ WantedChunks.insert(Chunks.begin(), Chunks.end());
+ std::vector<IoBuffer> WriteAttachmentBuffers;
+ std::vector<IoHash> WriteRawHashes;
+
+ IoHash RawHash;
+ uint64_t RawSize;
+ CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Bytes), RawHash, RawSize);
+ if (!Compressed)
+ {
+ if (RetriesLeft > 0)
+ {
+ ReportMessage(
+ OptionalContext,
+ fmt::format(
+ "Block attachment {} is malformed, can't parse as compressed binary, retrying download",
+ BlockHash));
+ return DownloadAndSaveBlock(ChunkStore,
+ RemoteStore,
+ IgnoreMissingAttachments,
+ OptionalContext,
+ NetworkWorkerPool,
+ WorkerPool,
+ AttachmentsDownloadLatch,
+ AttachmentsWriteLatch,
+ RemoteResult,
+ Info,
+ LoadAttachmentsTimer,
+ DownloadStartMS,
+ BlockHash,
+ std::move(Chunks),
+ RetriesLeft - 1);
+ }
+ ReportMessage(
+ OptionalContext,
+ fmt::format("Block attachment {} is malformed, can't parse as compressed binary", BlockHash));
+ RemoteResult.SetError(
+ gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
+ fmt::format("Block attachment {} is malformed, can't parse as compressed binary", BlockHash),
+ {});
+ return;
+ }
+ SharedBuffer BlockPayload = Compressed.Decompress();
+ if (!BlockPayload)
+ {
+ if (RetriesLeft > 0)
+ {
+ ReportMessage(
+ OptionalContext,
+ fmt::format("Block attachment {} is malformed, can't decompress payload, retrying download",
+ BlockHash));
+ return DownloadAndSaveBlock(ChunkStore,
+ RemoteStore,
+ IgnoreMissingAttachments,
+ OptionalContext,
+ NetworkWorkerPool,
+ WorkerPool,
+ AttachmentsDownloadLatch,
+ AttachmentsWriteLatch,
+ RemoteResult,
+ Info,
+ LoadAttachmentsTimer,
+ DownloadStartMS,
+ BlockHash,
+ std::move(Chunks),
+ RetriesLeft - 1);
+ }
+ ReportMessage(OptionalContext,
+ fmt::format("Block attachment {} is malformed, can't decompress payload", BlockHash));
+ RemoteResult.SetError(
+ gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
+ fmt::format("Block attachment {} is malformed, can't decompress payload", BlockHash),
+ {});
+ return;
+ }
+ if (RawHash != BlockHash)
+ {
+ ReportMessage(OptionalContext,
+ fmt::format("Block attachment {} has mismatching raw hash ({})", BlockHash, RawHash));
+ RemoteResult.SetError(
+ gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
+ fmt::format("Block attachment {} has mismatching raw hash ({})", BlockHash, RawHash),
+ {});
+ return;
+ }
- uint64_t BlockHeaderSize = 0;
- bool StoreChunksOK = IterateChunkBlock(
- BlockPayload,
- [&WantedChunks, &WriteAttachmentBuffers, &WriteRawHashes, &Info](CompressedBuffer&& Chunk,
- const IoHash& AttachmentRawHash) {
- if (WantedChunks.contains(AttachmentRawHash))
- {
- WriteAttachmentBuffers.emplace_back(Chunk.GetCompressed().Flatten().AsIoBuffer());
- IoHash RawHash;
- uint64_t RawSize;
- ZEN_ASSERT(CompressedBuffer::ValidateCompressedHeader(WriteAttachmentBuffers.back(), RawHash, RawSize));
- ZEN_ASSERT(RawHash == AttachmentRawHash);
- WriteRawHashes.emplace_back(AttachmentRawHash);
- WantedChunks.erase(AttachmentRawHash);
- }
- },
- BlockHeaderSize);
-
- if (!StoreChunksOK)
- {
- ReportMessage(OptionalContext,
- fmt::format("Block attachment {} has invalid format ({}): {}",
- BlockHash,
- RemoteResult.GetError(),
- RemoteResult.GetErrorReason()));
- RemoteResult.SetError(gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
- fmt::format("Invalid format for block {}", BlockHash),
- {});
- return;
- }
+ uint64_t BlockHeaderSize = 0;
+ bool StoreChunksOK = IterateChunkBlock(
+ BlockPayload,
+ [&WantedChunks, &WriteAttachmentBuffers, &WriteRawHashes, &Info](CompressedBuffer&& Chunk,
+ const IoHash& AttachmentRawHash) {
+ if (WantedChunks.contains(AttachmentRawHash))
+ {
+ WriteAttachmentBuffers.emplace_back(Chunk.GetCompressed().Flatten().AsIoBuffer());
+ IoHash RawHash;
+ uint64_t RawSize;
+ ZEN_ASSERT(CompressedBuffer::ValidateCompressedHeader(WriteAttachmentBuffers.back(),
+ RawHash,
+ RawSize));
+ ZEN_ASSERT(RawHash == AttachmentRawHash);
+ WriteRawHashes.emplace_back(AttachmentRawHash);
+ WantedChunks.erase(AttachmentRawHash);
+ }
+ },
+ BlockHeaderSize);
+
+ if (!StoreChunksOK)
+ {
+ ReportMessage(OptionalContext,
+ fmt::format("Block attachment {} has invalid format ({}): {}",
+ BlockHash,
+ RemoteResult.GetError(),
+ RemoteResult.GetErrorReason()));
+ RemoteResult.SetError(gsl::narrow<int32_t>(HttpResponseCode::InternalServerError),
+ fmt::format("Invalid format for block {}", BlockHash),
+ {});
+ return;
+ }
- ZEN_ASSERT(WantedChunks.empty());
+ ZEN_ASSERT(WantedChunks.empty());
- if (!WriteAttachmentBuffers.empty())
- {
- auto Results = ChunkStore.AddChunks(WriteAttachmentBuffers, WriteRawHashes);
- for (size_t Index = 0; Index < Results.size(); Index++)
- {
- const auto& Result = Results[Index];
- if (Result.New)
+ if (!WriteAttachmentBuffers.empty())
{
- Info.AttachmentBytesStored.fetch_add(WriteAttachmentBuffers[Index].GetSize());
- Info.AttachmentsStored.fetch_add(1);
+ auto Results = ChunkStore.AddChunks(WriteAttachmentBuffers, WriteRawHashes);
+ for (size_t Index = 0; Index < Results.size(); Index++)
+ {
+ const auto& Result = Results[Index];
+ if (Result.New)
+ {
+ Info.AttachmentBytesStored.fetch_add(WriteAttachmentBuffers[Index].GetSize());
+ Info.AttachmentsStored.fetch_add(1);
+ }
+ }
}
}
- }
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Failed save block attachment {}", BlockHash),
- Ex.what());
- }
- });
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Failed to block attachment {}", BlockHash),
- Ex.what());
- }
- });
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Failed save block attachment {}", BlockHash),
+ Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
+ }
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Failed to block attachment {}", BlockHash),
+ Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
};
void DownloadAndSaveAttachment(CidStore& ChunkStore,
@@ -596,92 +617,96 @@ namespace remotestore_impl {
const IoHash& RawHash)
{
AttachmentsDownloadLatch.AddCount(1);
- NetworkWorkerPool.ScheduleWork([&RemoteStore,
- &ChunkStore,
- &WorkerPool,
- &RemoteResult,
- &AttachmentsDownloadLatch,
- &AttachmentsWriteLatch,
- RawHash,
- &LoadAttachmentsTimer,
- &DownloadStartMS,
- &Info,
- IgnoreMissingAttachments,
- OptionalContext]() {
- auto _ = MakeGuard([&AttachmentsDownloadLatch] { AttachmentsDownloadLatch.CountDown(); });
- if (RemoteResult.IsError())
- {
- return;
- }
- try
- {
- uint64_t Unset = (std::uint64_t)-1;
- DownloadStartMS.compare_exchange_strong(Unset, LoadAttachmentsTimer.GetElapsedTimeMs());
- RemoteProjectStore::LoadAttachmentResult AttachmentResult = RemoteStore.LoadAttachment(RawHash);
- if (AttachmentResult.ErrorCode)
- {
- ReportMessage(OptionalContext,
- fmt::format("Failed to download large attachment {}: '{}', error code : {}",
- RawHash,
- AttachmentResult.Reason,
- AttachmentResult.ErrorCode));
- Info.MissingAttachmentCount.fetch_add(1);
- if (!IgnoreMissingAttachments)
- {
- RemoteResult.SetError(AttachmentResult.ErrorCode, AttachmentResult.Reason, AttachmentResult.Text);
- }
- return;
- }
- uint64_t AttachmentSize = AttachmentResult.Bytes.GetSize();
- ZEN_INFO("Loaded large attachment '{}' in {} ({})",
- RawHash,
- NiceTimeSpanMs(static_cast<uint64_t>(AttachmentResult.ElapsedSeconds * 1000)),
- NiceBytes(AttachmentSize));
- Info.AttachmentsDownloaded.fetch_add(1);
+ NetworkWorkerPool.ScheduleWork(
+ [&RemoteStore,
+ &ChunkStore,
+ &WorkerPool,
+ &RemoteResult,
+ &AttachmentsDownloadLatch,
+ &AttachmentsWriteLatch,
+ RawHash,
+ &LoadAttachmentsTimer,
+ &DownloadStartMS,
+ &Info,
+ IgnoreMissingAttachments,
+ OptionalContext]() {
+ auto _ = MakeGuard([&AttachmentsDownloadLatch] { AttachmentsDownloadLatch.CountDown(); });
if (RemoteResult.IsError())
{
return;
}
- Info.AttachmentBytesDownloaded.fetch_add(AttachmentSize);
-
- AttachmentsWriteLatch.AddCount(1);
- WorkerPool.ScheduleWork([&AttachmentsWriteLatch,
- &RemoteResult,
- &Info,
- &ChunkStore,
- RawHash,
- AttachmentSize,
- Bytes = std::move(AttachmentResult.Bytes),
- OptionalContext]() {
- auto _ = MakeGuard([&AttachmentsWriteLatch] { AttachmentsWriteLatch.CountDown(); });
- if (RemoteResult.IsError())
- {
- return;
- }
- try
+ try
+ {
+ uint64_t Unset = (std::uint64_t)-1;
+ DownloadStartMS.compare_exchange_strong(Unset, LoadAttachmentsTimer.GetElapsedTimeMs());
+ RemoteProjectStore::LoadAttachmentResult AttachmentResult = RemoteStore.LoadAttachment(RawHash);
+ if (AttachmentResult.ErrorCode)
{
- CidStore::InsertResult InsertResult = ChunkStore.AddChunk(Bytes, RawHash);
- if (InsertResult.New)
+ ReportMessage(OptionalContext,
+ fmt::format("Failed to download large attachment {}: '{}', error code : {}",
+ RawHash,
+ AttachmentResult.Reason,
+ AttachmentResult.ErrorCode));
+ Info.MissingAttachmentCount.fetch_add(1);
+ if (!IgnoreMissingAttachments)
{
- Info.AttachmentBytesStored.fetch_add(AttachmentSize);
- Info.AttachmentsStored.fetch_add(1);
+ RemoteResult.SetError(AttachmentResult.ErrorCode, AttachmentResult.Reason, AttachmentResult.Text);
}
+ return;
}
- catch (const std::exception& Ex)
+ uint64_t AttachmentSize = AttachmentResult.Bytes.GetSize();
+ ZEN_INFO("Loaded large attachment '{}' in {} ({})",
+ RawHash,
+ NiceTimeSpanMs(static_cast<uint64_t>(AttachmentResult.ElapsedSeconds * 1000)),
+ NiceBytes(AttachmentSize));
+ Info.AttachmentsDownloaded.fetch_add(1);
+ if (RemoteResult.IsError())
{
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Saving attachment {} failed", RawHash),
- Ex.what());
+ return;
}
- });
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Loading attachment {} failed", RawHash),
- Ex.what());
- }
- });
+ Info.AttachmentBytesDownloaded.fetch_add(AttachmentSize);
+
+ AttachmentsWriteLatch.AddCount(1);
+ WorkerPool.ScheduleWork(
+ [&AttachmentsWriteLatch,
+ &RemoteResult,
+ &Info,
+ &ChunkStore,
+ RawHash,
+ AttachmentSize,
+ Bytes = std::move(AttachmentResult.Bytes),
+ OptionalContext]() {
+ auto _ = MakeGuard([&AttachmentsWriteLatch] { AttachmentsWriteLatch.CountDown(); });
+ if (RemoteResult.IsError())
+ {
+ return;
+ }
+ try
+ {
+ CidStore::InsertResult InsertResult = ChunkStore.AddChunk(Bytes, RawHash);
+ if (InsertResult.New)
+ {
+ Info.AttachmentBytesStored.fetch_add(AttachmentSize);
+ Info.AttachmentsStored.fetch_add(1);
+ }
+ }
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Saving attachment {} failed", RawHash),
+ Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
+ }
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Loading attachment {} failed", RawHash),
+ Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
};
void CreateBlock(WorkerThreadPool& WorkerPool,
@@ -694,45 +719,47 @@ namespace remotestore_impl {
AsyncRemoteResult& RemoteResult)
{
OpSectionsLatch.AddCount(1);
- WorkerPool.ScheduleWork([&Blocks,
- &SectionsLock,
- &OpSectionsLatch,
- BlockIndex,
- Chunks = std::move(ChunksInBlock),
- &AsyncOnBlock,
- &RemoteResult]() mutable {
- auto _ = MakeGuard([&OpSectionsLatch] { OpSectionsLatch.CountDown(); });
- if (RemoteResult.IsError())
- {
- return;
- }
- size_t ChunkCount = Chunks.size();
- try
- {
- ZEN_ASSERT(ChunkCount > 0);
- Stopwatch Timer;
- ChunkBlockDescription Block;
- CompressedBuffer CompressedBlock = GenerateChunkBlock(std::move(Chunks), Block);
- IoHash BlockHash = CompressedBlock.DecodeRawHash();
+ WorkerPool.ScheduleWork(
+ [&Blocks,
+ &SectionsLock,
+ &OpSectionsLatch,
+ BlockIndex,
+ Chunks = std::move(ChunksInBlock),
+ &AsyncOnBlock,
+ &RemoteResult]() mutable {
+ auto _ = MakeGuard([&OpSectionsLatch] { OpSectionsLatch.CountDown(); });
+ if (RemoteResult.IsError())
{
- // We can share the lock as we are not resizing the vector and only touch BlockHash at our own index
- RwLock::SharedLockScope __(SectionsLock);
- Blocks[BlockIndex] = Block;
+ return;
}
- uint64_t BlockSize = CompressedBlock.GetCompressedSize();
- AsyncOnBlock(std::move(CompressedBlock), std::move(Block));
- ZEN_INFO("Generated block with {} attachments in {} ({})",
- ChunkCount,
- NiceTimeSpanMs(Timer.GetElapsedTimeMs()),
- NiceBytes(BlockSize));
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Failed creating block {} with {} chunks", BlockIndex, ChunkCount),
- Ex.what());
- }
- });
+ size_t ChunkCount = Chunks.size();
+ try
+ {
+ ZEN_ASSERT(ChunkCount > 0);
+ Stopwatch Timer;
+ ChunkBlockDescription Block;
+ CompressedBuffer CompressedBlock = GenerateChunkBlock(std::move(Chunks), Block);
+ IoHash BlockHash = CompressedBlock.DecodeRawHash();
+ {
+ // We can share the lock as we are not resizing the vector and only touch BlockHash at our own index
+ RwLock::SharedLockScope __(SectionsLock);
+ Blocks[BlockIndex] = Block;
+ }
+ uint64_t BlockSize = CompressedBlock.GetCompressedSize();
+ AsyncOnBlock(std::move(CompressedBlock), std::move(Block));
+ ZEN_INFO("Generated block with {} attachments in {} ({})",
+ ChunkCount,
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()),
+ NiceBytes(BlockSize));
+ }
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Failed creating block {} with {} chunks", BlockIndex, ChunkCount),
+ Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
struct UploadInfo
@@ -861,89 +888,91 @@ namespace remotestore_impl {
SaveAttachmentsLatch.AddCount(1);
AttachmentsToSave++;
- WorkerPool.ScheduleWork([&ChunkStore,
- &RemoteStore,
- &SaveAttachmentsLatch,
- &RemoteResult,
- RawHash,
- &CreatedBlocks,
- &LooseFileAttachments,
- &Info,
- OptionalContext]() {
- auto _ = MakeGuard([&SaveAttachmentsLatch] { SaveAttachmentsLatch.CountDown(); });
- if (RemoteResult.IsError())
- {
- return;
- }
- try
- {
- IoBuffer Payload;
- ChunkBlockDescription Block;
- if (auto BlockIt = CreatedBlocks.find(RawHash); BlockIt != CreatedBlocks.end())
- {
- Payload = BlockIt->second.Payload;
- Block = BlockIt->second.Block;
- }
- else if (auto LooseTmpFileIt = LooseFileAttachments.find(RawHash); LooseTmpFileIt != LooseFileAttachments.end())
- {
- Payload = LooseTmpFileIt->second(RawHash);
- }
- else
- {
- Payload = ChunkStore.FindChunkByCid(RawHash);
- }
- if (!Payload)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::NotFound),
- fmt::format("Failed to find attachment {}", RawHash),
- {});
- ZEN_WARN("Failed to save attachment '{}' ({}): {}",
- RawHash,
- RemoteResult.GetError(),
- RemoteResult.GetErrorReason());
- return;
- }
- const bool IsBlock = Block.BlockHash == RawHash;
- size_t PayloadSize = Payload.GetSize();
- RemoteProjectStore::SaveAttachmentResult Result =
- RemoteStore.SaveAttachment(CompositeBuffer(SharedBuffer(std::move(Payload))), RawHash, std::move(Block));
- if (Result.ErrorCode)
+ WorkerPool.ScheduleWork(
+ [&ChunkStore,
+ &RemoteStore,
+ &SaveAttachmentsLatch,
+ &RemoteResult,
+ RawHash,
+ &CreatedBlocks,
+ &LooseFileAttachments,
+ &Info,
+ OptionalContext]() {
+ auto _ = MakeGuard([&SaveAttachmentsLatch] { SaveAttachmentsLatch.CountDown(); });
+ if (RemoteResult.IsError())
{
- RemoteResult.SetError(Result.ErrorCode, Result.Reason, Result.Text);
- ReportMessage(OptionalContext,
- fmt::format("Failed to save attachment '{}', {} ({}): {}",
- RawHash,
- NiceBytes(PayloadSize),
- RemoteResult.GetError(),
- RemoteResult.GetErrorReason()));
return;
}
- if (IsBlock)
+ try
{
- Info.AttachmentBlocksUploaded.fetch_add(1);
- Info.AttachmentBlockBytesUploaded.fetch_add(PayloadSize);
- ZEN_INFO("Saved block attachment '{}' in {} ({})",
- RawHash,
- NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)),
- NiceBytes(PayloadSize));
+ IoBuffer Payload;
+ ChunkBlockDescription Block;
+ if (auto BlockIt = CreatedBlocks.find(RawHash); BlockIt != CreatedBlocks.end())
+ {
+ Payload = BlockIt->second.Payload;
+ Block = BlockIt->second.Block;
+ }
+ else if (auto LooseTmpFileIt = LooseFileAttachments.find(RawHash); LooseTmpFileIt != LooseFileAttachments.end())
+ {
+ Payload = LooseTmpFileIt->second(RawHash);
+ }
+ else
+ {
+ Payload = ChunkStore.FindChunkByCid(RawHash);
+ }
+ if (!Payload)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::NotFound),
+ fmt::format("Failed to find attachment {}", RawHash),
+ {});
+ ZEN_WARN("Failed to save attachment '{}' ({}): {}",
+ RawHash,
+ RemoteResult.GetError(),
+ RemoteResult.GetErrorReason());
+ return;
+ }
+ const bool IsBlock = Block.BlockHash == RawHash;
+ size_t PayloadSize = Payload.GetSize();
+ RemoteProjectStore::SaveAttachmentResult Result =
+ RemoteStore.SaveAttachment(CompositeBuffer(SharedBuffer(std::move(Payload))), RawHash, std::move(Block));
+ if (Result.ErrorCode)
+ {
+ RemoteResult.SetError(Result.ErrorCode, Result.Reason, Result.Text);
+ ReportMessage(OptionalContext,
+ fmt::format("Failed to save attachment '{}', {} ({}): {}",
+ RawHash,
+ NiceBytes(PayloadSize),
+ RemoteResult.GetError(),
+ RemoteResult.GetErrorReason()));
+ return;
+ }
+ if (IsBlock)
+ {
+ Info.AttachmentBlocksUploaded.fetch_add(1);
+ Info.AttachmentBlockBytesUploaded.fetch_add(PayloadSize);
+ ZEN_INFO("Saved block attachment '{}' in {} ({})",
+ RawHash,
+ NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)),
+ NiceBytes(PayloadSize));
+ }
+ else
+ {
+ Info.AttachmentsUploaded.fetch_add(1);
+ Info.AttachmentBytesUploaded.fetch_add(PayloadSize);
+ ZEN_INFO("Saved large attachment '{}' in {} ({})",
+ RawHash,
+ NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)),
+ NiceBytes(PayloadSize));
+ }
}
- else
+ catch (const std::exception& Ex)
{
- Info.AttachmentsUploaded.fetch_add(1);
- Info.AttachmentBytesUploaded.fetch_add(PayloadSize);
- ZEN_INFO("Saved large attachment '{}' in {} ({})",
- RawHash,
- NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)),
- NiceBytes(PayloadSize));
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("To upload attachment {}", RawHash),
+ Ex.what());
}
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("To upload attachment {}", RawHash),
- Ex.what());
- }
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
if (IsCancelled(OptionalContext))
@@ -982,66 +1011,68 @@ namespace remotestore_impl {
SaveAttachmentsLatch.AddCount(1);
AttachmentsToSave++;
- WorkerPool.ScheduleWork([&RemoteStore,
- &ChunkStore,
- &SaveAttachmentsLatch,
- &RemoteResult,
- NeededChunks = std::move(NeededChunks),
- &BulkBlockAttachmentsToUpload,
- &Info,
- OptionalContext]() {
- auto _ = MakeGuard([&SaveAttachmentsLatch] { SaveAttachmentsLatch.CountDown(); });
- if (RemoteResult.IsError())
- {
- return;
- }
- try
- {
- size_t ChunksSize = 0;
- std::vector<SharedBuffer> ChunkBuffers;
- ChunkBuffers.reserve(NeededChunks.size());
- for (const IoHash& Chunk : NeededChunks)
+ WorkerPool.ScheduleWork(
+ [&RemoteStore,
+ &ChunkStore,
+ &SaveAttachmentsLatch,
+ &RemoteResult,
+ NeededChunks = std::move(NeededChunks),
+ &BulkBlockAttachmentsToUpload,
+ &Info,
+ OptionalContext]() {
+ auto _ = MakeGuard([&SaveAttachmentsLatch] { SaveAttachmentsLatch.CountDown(); });
+ if (RemoteResult.IsError())
+ {
+ return;
+ }
+ try
{
- auto It = BulkBlockAttachmentsToUpload.find(Chunk);
- ZEN_ASSERT(It != BulkBlockAttachmentsToUpload.end());
- CompressedBuffer ChunkPayload = It->second(It->first).second;
- if (!ChunkPayload)
+ size_t ChunksSize = 0;
+ std::vector<SharedBuffer> ChunkBuffers;
+ ChunkBuffers.reserve(NeededChunks.size());
+ for (const IoHash& Chunk : NeededChunks)
+ {
+ auto It = BulkBlockAttachmentsToUpload.find(Chunk);
+ ZEN_ASSERT(It != BulkBlockAttachmentsToUpload.end());
+ CompressedBuffer ChunkPayload = It->second(It->first).second;
+ if (!ChunkPayload)
+ {
+ RemoteResult.SetError(static_cast<int32_t>(HttpResponseCode::NotFound),
+ fmt::format("Missing chunk {}"sv, Chunk),
+ fmt::format("Unable to fetch attachment {} required by the oplog"sv, Chunk));
+ ChunkBuffers.clear();
+ break;
+ }
+ ChunksSize += ChunkPayload.GetCompressedSize();
+ ChunkBuffers.emplace_back(SharedBuffer(std::move(ChunkPayload).GetCompressed().Flatten().AsIoBuffer()));
+ }
+ RemoteProjectStore::SaveAttachmentsResult Result = RemoteStore.SaveAttachments(ChunkBuffers);
+ if (Result.ErrorCode)
{
- RemoteResult.SetError(static_cast<int32_t>(HttpResponseCode::NotFound),
- fmt::format("Missing chunk {}"sv, Chunk),
- fmt::format("Unable to fetch attachment {} required by the oplog"sv, Chunk));
- ChunkBuffers.clear();
- break;
+ RemoteResult.SetError(Result.ErrorCode, Result.Reason, Result.Text);
+ ReportMessage(OptionalContext,
+ fmt::format("Failed to save attachments with {} chunks ({}): {}",
+ NeededChunks.size(),
+ RemoteResult.GetError(),
+ RemoteResult.GetErrorReason()));
+ return;
}
- ChunksSize += ChunkPayload.GetCompressedSize();
- ChunkBuffers.emplace_back(SharedBuffer(std::move(ChunkPayload).GetCompressed().Flatten().AsIoBuffer()));
+ Info.AttachmentsUploaded.fetch_add(ChunkBuffers.size());
+ Info.AttachmentBytesUploaded.fetch_add(ChunksSize);
+
+ ZEN_INFO("Saved {} bulk attachments in {} ({})",
+ NeededChunks.size(),
+ NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)),
+ NiceBytes(ChunksSize));
}
- RemoteProjectStore::SaveAttachmentsResult Result = RemoteStore.SaveAttachments(ChunkBuffers);
- if (Result.ErrorCode)
+ catch (const std::exception& Ex)
{
- RemoteResult.SetError(Result.ErrorCode, Result.Reason, Result.Text);
- ReportMessage(OptionalContext,
- fmt::format("Failed to save attachments with {} chunks ({}): {}",
- NeededChunks.size(),
- RemoteResult.GetError(),
- RemoteResult.GetErrorReason()));
- return;
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Failed to buck upload {} attachments", NeededChunks.size()),
+ Ex.what());
}
- Info.AttachmentsUploaded.fetch_add(ChunkBuffers.size());
- Info.AttachmentBytesUploaded.fetch_add(ChunksSize);
-
- ZEN_INFO("Saved {} bulk attachments in {} ({})",
- NeededChunks.size(),
- NiceTimeSpanMs(static_cast<uint64_t>(Result.ElapsedSeconds * 1000)),
- NiceBytes(ChunksSize));
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Failed to buck upload {} attachments", NeededChunks.size()),
- Ex.what());
- }
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
}
@@ -1142,6 +1173,7 @@ BuildContainer(CidStore& ChunkStore,
ProjectStore::Project& Project,
ProjectStore::Oplog& Oplog,
size_t MaxBlockSize,
+ size_t MaxChunksPerBlock,
size_t MaxChunkEmbedSize,
size_t ChunkFileSizeLimit,
bool BuildBlocks,
@@ -1516,148 +1548,152 @@ BuildContainer(CidStore& ChunkStore,
ResolveAttachmentsLatch.AddCount(1);
- WorkerPool.ScheduleWork([&ChunkStore,
- UploadAttachment = &It.second,
- RawHash = It.first,
- &ResolveAttachmentsLatch,
- &ResolveLock,
- &ChunkedHashes,
- &LargeChunkHashes,
- &ChunkedUploadAttachments,
- &LooseUploadAttachments,
- &MissingHashes,
- &OnLargeAttachment,
- &AttachmentTempPath,
- &ChunkFile,
- &ChunkedFiles,
- MaxChunkEmbedSize,
- ChunkFileSizeLimit,
- AllowChunking,
- &RemoteResult,
- OptionalContext]() {
- auto _ = MakeGuard([&ResolveAttachmentsLatch] { ResolveAttachmentsLatch.CountDown(); });
- if (remotestore_impl::IsCancelled(OptionalContext))
- {
- return;
- }
+ WorkerPool.ScheduleWork(
+ [&ChunkStore,
+ UploadAttachment = &It.second,
+ RawHash = It.first,
+ &ResolveAttachmentsLatch,
+ &ResolveLock,
+ &ChunkedHashes,
+ &LargeChunkHashes,
+ &ChunkedUploadAttachments,
+ &LooseUploadAttachments,
+ &MissingHashes,
+ &OnLargeAttachment,
+ &AttachmentTempPath,
+ &ChunkFile,
+ &ChunkedFiles,
+ MaxChunkEmbedSize,
+ ChunkFileSizeLimit,
+ AllowChunking,
+ &RemoteResult,
+ OptionalContext]() {
+ auto _ = MakeGuard([&ResolveAttachmentsLatch] { ResolveAttachmentsLatch.CountDown(); });
+ if (remotestore_impl::IsCancelled(OptionalContext))
+ {
+ return;
+ }
- try
- {
- if (!UploadAttachment->RawPath.empty())
+ try
{
- const std::filesystem::path& FilePath = UploadAttachment->RawPath;
- IoBuffer RawData = IoBufferBuilder::MakeFromFile(FilePath);
- if (RawData)
+ if (!UploadAttachment->RawPath.empty())
{
- if (AllowChunking && RawData.GetSize() > ChunkFileSizeLimit)
+ const std::filesystem::path& FilePath = UploadAttachment->RawPath;
+ IoBuffer RawData = IoBufferBuilder::MakeFromFile(FilePath);
+ if (RawData)
{
- IoBufferFileReference FileRef;
- (void)RawData.GetFileReference(FileRef);
-
- ChunkedFile Chunked = ChunkFile(RawHash, RawData, FileRef, OptionalContext);
- ResolveLock.WithExclusiveLock(
- [RawHash, &ChunkedFiles, &ChunkedUploadAttachments, &ChunkedHashes, &Chunked]() {
- ChunkedUploadAttachments.insert_or_assign(RawHash, ChunkedFiles.size());
- ChunkedHashes.reserve(ChunkedHashes.size() + Chunked.Chunked.Info.ChunkHashes.size());
- for (const IoHash& ChunkHash : Chunked.Chunked.Info.ChunkHashes)
- {
- ChunkedHashes.insert(ChunkHash);
- }
- ChunkedFiles.emplace_back(std::move(Chunked));
+ if (AllowChunking && RawData.GetSize() > ChunkFileSizeLimit)
+ {
+ IoBufferFileReference FileRef;
+ (void)RawData.GetFileReference(FileRef);
+
+ ChunkedFile Chunked = ChunkFile(RawHash, RawData, FileRef, OptionalContext);
+ ResolveLock.WithExclusiveLock(
+ [RawHash, &ChunkedFiles, &ChunkedUploadAttachments, &ChunkedHashes, &Chunked]() {
+ ChunkedUploadAttachments.insert_or_assign(RawHash, ChunkedFiles.size());
+ ChunkedHashes.reserve(ChunkedHashes.size() + Chunked.Chunked.Info.ChunkHashes.size());
+ for (const IoHash& ChunkHash : Chunked.Chunked.Info.ChunkHashes)
+ {
+ ChunkedHashes.insert(ChunkHash);
+ }
+ ChunkedFiles.emplace_back(std::move(Chunked));
+ });
+ }
+ else if (RawData.GetSize() > (MaxChunkEmbedSize * 2))
+ {
+ // Assume the compressed file is going to be larger than MaxChunkEmbedSize, even if it isn't
+ // it will be a loose attachment instead of going into a block
+ OnLargeAttachment(RawHash, [RawData = std::move(RawData), AttachmentTempPath](const IoHash& RawHash) {
+ size_t RawSize = RawData.GetSize();
+ CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(std::move(RawData)),
+ OodleCompressor::Mermaid,
+ OodleCompressionLevel::VeryFast);
+
+ std::filesystem::path AttachmentPath = AttachmentTempPath;
+ AttachmentPath.append(RawHash.ToHexString());
+ IoBuffer TempAttachmentBuffer =
+ WriteToTempFile(std::move(Compressed).GetCompressed(), AttachmentPath);
+ ZEN_INFO("Saved temp attachment to '{}', {} ({})",
+ AttachmentPath,
+ NiceBytes(RawSize),
+ NiceBytes(TempAttachmentBuffer.GetSize()));
+ return TempAttachmentBuffer;
});
- }
- else if (RawData.GetSize() > (MaxChunkEmbedSize * 2))
- {
- // Assume the compressed file is going to be larger than MaxChunkEmbedSize, even if it isn't
- // it will be a loose attachment instead of going into a block
- OnLargeAttachment(RawHash, [RawData = std::move(RawData), AttachmentTempPath](const IoHash& RawHash) {
- size_t RawSize = RawData.GetSize();
- CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(std::move(RawData)),
+ ResolveLock.WithExclusiveLock([RawHash, &LargeChunkHashes]() { LargeChunkHashes.insert(RawHash); });
+ }
+ else
+ {
+ uint64_t RawSize = RawData.GetSize();
+ CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(RawData),
OodleCompressor::Mermaid,
OodleCompressionLevel::VeryFast);
std::filesystem::path AttachmentPath = AttachmentTempPath;
AttachmentPath.append(RawHash.ToHexString());
+
+ uint64_t CompressedSize = Compressed.GetCompressedSize();
IoBuffer TempAttachmentBuffer = WriteToTempFile(std::move(Compressed).GetCompressed(), AttachmentPath);
ZEN_INFO("Saved temp attachment to '{}', {} ({})",
AttachmentPath,
NiceBytes(RawSize),
NiceBytes(TempAttachmentBuffer.GetSize()));
- return TempAttachmentBuffer;
- });
- ResolveLock.WithExclusiveLock([RawHash, &LargeChunkHashes]() { LargeChunkHashes.insert(RawHash); });
+
+ if (CompressedSize > MaxChunkEmbedSize)
+ {
+ OnLargeAttachment(RawHash,
+ [Data = std::move(TempAttachmentBuffer)](const IoHash&) { return Data; });
+ ResolveLock.WithExclusiveLock([RawHash, &LargeChunkHashes]() { LargeChunkHashes.insert(RawHash); });
+ }
+ else
+ {
+ UploadAttachment->Size = CompressedSize;
+ ResolveLock.WithExclusiveLock(
+ [RawHash, RawSize, &LooseUploadAttachments, Data = std::move(TempAttachmentBuffer)]() {
+ LooseUploadAttachments.insert_or_assign(RawHash, std::make_pair(RawSize, std::move(Data)));
+ });
+ }
+ }
}
else
{
- uint64_t RawSize = RawData.GetSize();
- CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(RawData),
- OodleCompressor::Mermaid,
- OodleCompressionLevel::VeryFast);
-
- std::filesystem::path AttachmentPath = AttachmentTempPath;
- AttachmentPath.append(RawHash.ToHexString());
-
- uint64_t CompressedSize = Compressed.GetCompressedSize();
- IoBuffer TempAttachmentBuffer = WriteToTempFile(std::move(Compressed).GetCompressed(), AttachmentPath);
- ZEN_INFO("Saved temp attachment to '{}', {} ({})",
- AttachmentPath,
- NiceBytes(RawSize),
- NiceBytes(TempAttachmentBuffer.GetSize()));
-
- if (CompressedSize > MaxChunkEmbedSize)
- {
- OnLargeAttachment(RawHash, [Data = std::move(TempAttachmentBuffer)](const IoHash&) { return Data; });
- ResolveLock.WithExclusiveLock([RawHash, &LargeChunkHashes]() { LargeChunkHashes.insert(RawHash); });
- }
- else
- {
- UploadAttachment->Size = CompressedSize;
- ResolveLock.WithExclusiveLock(
- [RawHash, RawSize, &LooseUploadAttachments, Data = std::move(TempAttachmentBuffer)]() {
- LooseUploadAttachments.insert_or_assign(RawHash, std::make_pair(RawSize, std::move(Data)));
- });
- }
+ ResolveLock.WithExclusiveLock([RawHash, &MissingHashes]() { MissingHashes.insert(RawHash); });
}
}
else
{
- ResolveLock.WithExclusiveLock([RawHash, &MissingHashes]() { MissingHashes.insert(RawHash); });
- }
- }
- else
- {
- IoBuffer Data = ChunkStore.FindChunkByCid(RawHash);
- if (Data)
- {
- auto GetForChunking =
- [](size_t ChunkFileSizeLimit, const IoBuffer& Data, IoBufferFileReference& OutFileRef) -> bool {
- if (Data.IsWholeFile())
- {
- IoHash VerifyRawHash;
- uint64_t VerifyRawSize;
- CompressedBuffer Compressed =
- CompressedBuffer::FromCompressed(SharedBuffer(Data), VerifyRawHash, VerifyRawSize);
- if (Compressed)
+ IoBuffer Data = ChunkStore.FindChunkByCid(RawHash);
+ if (Data)
+ {
+ auto GetForChunking =
+ [](size_t ChunkFileSizeLimit, const IoBuffer& Data, IoBufferFileReference& OutFileRef) -> bool {
+ if (Data.IsWholeFile())
{
- if (VerifyRawSize > ChunkFileSizeLimit)
+ IoHash VerifyRawHash;
+ uint64_t VerifyRawSize;
+ CompressedBuffer Compressed =
+ CompressedBuffer::FromCompressed(SharedBuffer(Data), VerifyRawHash, VerifyRawSize);
+ if (Compressed)
{
- OodleCompressor Compressor;
- OodleCompressionLevel CompressionLevel;
- uint64_t BlockSize;
- if (Compressed.TryGetCompressParameters(Compressor, CompressionLevel, BlockSize))
+ if (VerifyRawSize > ChunkFileSizeLimit)
{
- if (CompressionLevel == OodleCompressionLevel::None)
+ OodleCompressor Compressor;
+ OodleCompressionLevel CompressionLevel;
+ uint64_t BlockSize;
+ if (Compressed.TryGetCompressParameters(Compressor, CompressionLevel, BlockSize))
{
- CompositeBuffer Decompressed = Compressed.DecompressToComposite();
- if (Decompressed)
+ if (CompressionLevel == OodleCompressionLevel::None)
{
- std::span<const SharedBuffer> Segments = Decompressed.GetSegments();
- if (Segments.size() == 1)
+ CompositeBuffer Decompressed = Compressed.DecompressToComposite();
+ if (Decompressed)
{
- IoBuffer DecompressedData = Segments[0].AsIoBuffer();
- if (DecompressedData.GetFileReference(OutFileRef))
+ std::span<const SharedBuffer> Segments = Decompressed.GetSegments();
+ if (Segments.size() == 1)
{
- return true;
+ IoBuffer DecompressedData = Segments[0].AsIoBuffer();
+ if (DecompressedData.GetFileReference(OutFileRef))
+ {
+ return true;
+ }
}
}
}
@@ -1665,49 +1701,49 @@ BuildContainer(CidStore& ChunkStore,
}
}
}
- }
- return false;
- };
+ return false;
+ };
- IoBufferFileReference FileRef;
- if (AllowChunking && GetForChunking(ChunkFileSizeLimit, Data, FileRef))
- {
- ChunkedFile Chunked = ChunkFile(RawHash, Data, FileRef, OptionalContext);
- ResolveLock.WithExclusiveLock(
- [RawHash, &ChunkedFiles, &ChunkedUploadAttachments, &ChunkedHashes, &Chunked]() {
- ChunkedUploadAttachments.insert_or_assign(RawHash, ChunkedFiles.size());
- ChunkedHashes.reserve(ChunkedHashes.size() + Chunked.Chunked.Info.ChunkHashes.size());
- for (const IoHash& ChunkHash : Chunked.Chunked.Info.ChunkHashes)
- {
- ChunkedHashes.insert(ChunkHash);
- }
- ChunkedFiles.emplace_back(std::move(Chunked));
- });
- }
- else if (Data.GetSize() > MaxChunkEmbedSize)
- {
- OnLargeAttachment(RawHash,
- [&ChunkStore](const IoHash& RawHash) { return ChunkStore.FindChunkByCid(RawHash); });
- ResolveLock.WithExclusiveLock([RawHash, &LargeChunkHashes]() { LargeChunkHashes.insert(RawHash); });
+ IoBufferFileReference FileRef;
+ if (AllowChunking && GetForChunking(ChunkFileSizeLimit, Data, FileRef))
+ {
+ ChunkedFile Chunked = ChunkFile(RawHash, Data, FileRef, OptionalContext);
+ ResolveLock.WithExclusiveLock(
+ [RawHash, &ChunkedFiles, &ChunkedUploadAttachments, &ChunkedHashes, &Chunked]() {
+ ChunkedUploadAttachments.insert_or_assign(RawHash, ChunkedFiles.size());
+ ChunkedHashes.reserve(ChunkedHashes.size() + Chunked.Chunked.Info.ChunkHashes.size());
+ for (const IoHash& ChunkHash : Chunked.Chunked.Info.ChunkHashes)
+ {
+ ChunkedHashes.insert(ChunkHash);
+ }
+ ChunkedFiles.emplace_back(std::move(Chunked));
+ });
+ }
+ else if (Data.GetSize() > MaxChunkEmbedSize)
+ {
+ OnLargeAttachment(RawHash,
+ [&ChunkStore](const IoHash& RawHash) { return ChunkStore.FindChunkByCid(RawHash); });
+ ResolveLock.WithExclusiveLock([RawHash, &LargeChunkHashes]() { LargeChunkHashes.insert(RawHash); });
+ }
+ else
+ {
+ UploadAttachment->Size = Data.GetSize();
+ }
}
else
{
- UploadAttachment->Size = Data.GetSize();
+ ResolveLock.WithExclusiveLock([RawHash, &MissingHashes]() { MissingHashes.insert(RawHash); });
}
}
- else
- {
- ResolveLock.WithExclusiveLock([RawHash, &MissingHashes]() { MissingHashes.insert(RawHash); });
- }
}
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::NotFound),
- fmt::format("Failed to resolve attachment {}", RawHash),
- Ex.what());
- }
- });
+ catch (const std::exception& Ex)
+ {
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::NotFound),
+ fmt::format("Failed to resolve attachment {}", RawHash),
+ Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
ResolveAttachmentsLatch.CountDown();
@@ -1981,17 +2017,31 @@ BuildContainer(CidStore& ChunkStore,
{
ChunksInBlock.emplace_back(
std::make_pair(RawHash, [&ChunkStore](const IoHash& RawHash) -> std::pair<uint64_t, CompressedBuffer> {
- IoBuffer Chunk = ChunkStore.FindChunkByCid(RawHash);
- IoHash _;
- uint64_t RawSize = 0;
- CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(std::move(Chunk)), _, RawSize);
- ZEN_ASSERT(Compressed);
+ IoBuffer Chunk = ChunkStore.FindChunkByCid(RawHash);
+ if (!Chunk)
+ {
+ throw std::runtime_error(fmt::format("Failed to find chunk {} in cid store", RawHash));
+ }
+ IoHash ValidateRawHash;
+ uint64_t RawSize = 0;
+ CompressedBuffer Compressed =
+ CompressedBuffer::FromCompressed(SharedBuffer(std::move(Chunk)), ValidateRawHash, RawSize);
+ if (!Compressed)
+ {
+ throw std::runtime_error(
+ fmt::format("Chunk {} in cid store is malformed (not a compressed buffer)", RawHash));
+ }
+ if (RawHash != ValidateRawHash)
+ {
+ throw std::runtime_error(
+ fmt::format("Chunk {} in cid store is malformed (mismatching raw hash)", RawHash));
+ }
return {RawSize, Compressed};
}));
}
BlockSize += PayloadSize;
- if (BlockSize >= MaxBlockSize && (CurrentOpKey != LastOpKey))
+ if ((BlockSize >= MaxBlockSize || ChunksInBlock.size() > MaxChunksPerBlock) && (CurrentOpKey != LastOpKey))
{
NewBlock();
}
@@ -2050,7 +2100,7 @@ BuildContainer(CidStore& ChunkStore,
BlockSize += CompressedBuffer::GetHeaderSizeForNoneEncoder() + Source.Size;
if (BuildBlocks)
{
- if (BlockSize >= MaxBlockSize)
+ if (BlockSize >= MaxBlockSize || ChunksInBlock.size() > MaxChunksPerBlock)
{
NewBlock();
}
@@ -2256,7 +2306,9 @@ RemoteProjectStore::LoadContainerResult
BuildContainer(CidStore& ChunkStore,
ProjectStore::Project& Project,
ProjectStore::Oplog& Oplog,
+ WorkerThreadPool& WorkerPool,
size_t MaxBlockSize,
+ size_t MaxChunksPerBlock,
size_t MaxChunkEmbedSize,
size_t ChunkFileSizeLimit,
bool BuildBlocks,
@@ -2267,13 +2319,14 @@ BuildContainer(CidStore& ChunkStore,
const std::function<void(std::vector<std::pair<IoHash, FetchChunkFunc>>&&)>& OnBlockChunks,
bool EmbedLooseFiles)
{
- WorkerThreadPool& WorkerPool = GetLargeWorkerPool(EWorkloadType::Background);
+ // WorkerThreadPool& WorkerPool = GetLargeWorkerPool(EWorkloadType::Background);
remotestore_impl::AsyncRemoteResult RemoteResult;
CbObject ContainerObject = BuildContainer(ChunkStore,
Project,
Oplog,
MaxBlockSize,
+ MaxChunksPerBlock,
MaxChunkEmbedSize,
ChunkFileSizeLimit,
BuildBlocks,
@@ -2295,7 +2348,10 @@ SaveOplog(CidStore& ChunkStore,
RemoteProjectStore& RemoteStore,
ProjectStore::Project& Project,
ProjectStore::Oplog& Oplog,
+ WorkerThreadPool& NetworkWorkerPool,
+ WorkerThreadPool& WorkerPool,
size_t MaxBlockSize,
+ size_t MaxChunksPerBlock,
size_t MaxChunkEmbedSize,
size_t ChunkFileSizeLimit,
bool EmbedLooseFiles,
@@ -2309,9 +2365,6 @@ SaveOplog(CidStore& ChunkStore,
remotestore_impl::UploadInfo Info;
- WorkerThreadPool& WorkerPool = GetLargeWorkerPool(EWorkloadType::Background);
- WorkerThreadPool& NetworkWorkerPool = GetMediumWorkerPool(EWorkloadType::Background);
-
const RemoteProjectStore::RemoteStoreInfo RemoteStoreInfo = RemoteStore.GetInfo();
std::filesystem::path AttachmentTempPath;
@@ -2445,6 +2498,7 @@ SaveOplog(CidStore& ChunkStore,
Project,
Oplog,
MaxBlockSize,
+ MaxChunksPerBlock,
MaxChunkEmbedSize,
ChunkFileSizeLimit,
RemoteStoreInfo.CreateBlocks,
@@ -2651,19 +2705,22 @@ ParseOplogContainer(const CbObject& ContainerObject,
IoBuffer OpsBuffer(IoBuffer::Wrap, OpsSection.GetData(), OpsSection.GetSize());
IoBuffer SectionPayload = CompressedBuffer::FromCompressedNoValidate(std::move(OpsBuffer)).Decompress().AsIoBuffer();
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject SectionObject = ValidateAndReadCompactBinaryObject(std::move(SectionPayload), ValidateResult);
+ ValidateResult == CbValidateError::None && ContainerObject)
{
- CbObject SectionObject = LoadCompactBinaryObject(SectionPayload);
- if (!SectionObject)
- {
- remotestore_impl::ReportMessage(OptionalContext,
- fmt::format("Failed to save oplog container: '{}'", "Section has unexpected data type"));
- return RemoteProjectStore::Result{gsl::narrow<int>(HttpResponseCode::BadRequest),
- Timer.GetElapsedTimeMs() / 1000.0,
- "Section has unexpected data type",
- "Failed to save oplog container"};
- }
OutOplogSection = SectionObject;
}
+ else
+ {
+ remotestore_impl::ReportMessage(
+ OptionalContext,
+ fmt::format("Failed to save oplog container: '{}' ('{}')", "Section has unexpected data type", ToString(ValidateResult)));
+ return RemoteProjectStore::Result{gsl::narrow<int>(HttpResponseCode::BadRequest),
+ Timer.GetElapsedTimeMs() / 1000.0,
+ "Section has unexpected data type",
+ "Failed to save oplog container"};
+ }
std::unordered_set<IoHash, IoHash::Hasher> OpsAttachments;
{
CbArrayView OpsArray = OutOplogSection["ops"sv].AsArrayView();
@@ -2711,6 +2768,12 @@ ParseOplogContainer(const CbObject& ContainerObject,
NiceBytes(Chunked.RawSize),
Chunked.ChunkHashes.size());
}
+ if (remotestore_impl::IsCancelled(OptionalContext))
+ {
+ return RemoteProjectStore::Result{.ErrorCode = gsl::narrow<int>(HttpResponseCode::OK),
+ .ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0,
+ .Reason = "Operation cancelled"};
+ }
}
size_t NeedBlockCount = 0;
@@ -2755,6 +2818,12 @@ ParseOplogContainer(const CbObject& ContainerObject,
NeedBlockCount++;
}
}
+ if (remotestore_impl::IsCancelled(OptionalContext))
+ {
+ return RemoteProjectStore::Result{.ErrorCode = gsl::narrow<int>(HttpResponseCode::OK),
+ .ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0,
+ .Reason = "Operation cancelled"};
+ }
}
remotestore_impl::ReportMessage(OptionalContext,
fmt::format("Requesting {} of {} attachment blocks", NeedBlockCount, BlocksArray.Num()));
@@ -2769,6 +2838,12 @@ ParseOplogContainer(const CbObject& ContainerObject,
OnNeedAttachment(AttachmentHash);
NeedAttachmentCount++;
}
+ if (remotestore_impl::IsCancelled(OptionalContext))
+ {
+ return RemoteProjectStore::Result{.ErrorCode = gsl::narrow<int>(HttpResponseCode::OK),
+ .ElapsedSeconds = Timer.GetElapsedTimeMs() / 1000.0,
+ .Reason = "Operation cancelled"};
+ }
};
remotestore_impl::ReportMessage(OptionalContext,
fmt::format("Requesting {} of {} large attachments", NeedAttachmentCount, LargeChunksArray.Num()));
@@ -2811,6 +2886,8 @@ RemoteProjectStore::Result
LoadOplog(CidStore& ChunkStore,
RemoteProjectStore& RemoteStore,
ProjectStore::Oplog& Oplog,
+ WorkerThreadPool& NetworkWorkerPool,
+ WorkerThreadPool& WorkerPool,
bool ForceDownload,
bool IgnoreMissingAttachments,
bool CleanOplog,
@@ -2822,9 +2899,6 @@ LoadOplog(CidStore& ChunkStore,
Stopwatch Timer;
- WorkerThreadPool& WorkerPool = GetLargeWorkerPool(EWorkloadType::Background);
- WorkerThreadPool& NetworkWorkerPool = GetSmallWorkerPool(EWorkloadType::Background);
-
std::unordered_set<IoHash, IoHash::Hasher> Attachments;
uint64_t BlockCountToDownload = 0;
@@ -3074,101 +3148,103 @@ LoadOplog(CidStore& ChunkStore,
{
std::filesystem::path TempFileName = TempFilePath / Chunked.RawHash.ToHexString();
DechunkLatch.AddCount(1);
- WorkerPool.ScheduleWork([&ChunkStore,
- &DechunkLatch,
- TempFileName,
- &Chunked,
- &RemoteResult,
- IgnoreMissingAttachments,
- &Info,
- OptionalContext]() {
- auto _ = MakeGuard([&DechunkLatch, &TempFileName] {
- std::error_code Ec;
- if (IsFile(TempFileName, Ec))
- {
- RemoveFile(TempFileName, Ec);
- if (Ec)
+ WorkerPool.ScheduleWork(
+ [&ChunkStore,
+ &DechunkLatch,
+ TempFileName,
+ &Chunked,
+ &RemoteResult,
+ IgnoreMissingAttachments,
+ &Info,
+ OptionalContext]() {
+ auto _ = MakeGuard([&DechunkLatch, &TempFileName] {
+ std::error_code Ec;
+ if (IsFile(TempFileName, Ec))
{
- ZEN_INFO("Failed to remove temporary file '{}'. Reason: {}", TempFileName, Ec.message());
+ RemoveFile(TempFileName, Ec);
+ if (Ec)
+ {
+ ZEN_INFO("Failed to remove temporary file '{}'. Reason: {}", TempFileName, Ec.message());
+ }
}
- }
- DechunkLatch.CountDown();
- });
- try
- {
- if (RemoteResult.IsError())
- {
- return;
- }
- Stopwatch Timer;
- IoBuffer TmpBuffer;
+ DechunkLatch.CountDown();
+ });
+ try
{
- BasicFile TmpFile;
- TmpFile.Open(TempFileName, BasicFile::Mode::kTruncate);
+ if (RemoteResult.IsError())
{
- BasicFileWriter TmpWriter(TmpFile, 64u * 1024u);
-
- uint64_t Offset = CompressedBuffer::GetHeaderSizeForNoneEncoder();
- BLAKE3Stream HashingStream;
- for (std::uint32_t SequenceIndex : Chunked.ChunkSequence)
+ return;
+ }
+ Stopwatch Timer;
+ IoBuffer TmpBuffer;
+ {
+ BasicFile TmpFile;
+ TmpFile.Open(TempFileName, BasicFile::Mode::kTruncate);
{
- const IoHash& ChunkHash = Chunked.ChunkHashes[SequenceIndex];
- IoBuffer Chunk = ChunkStore.FindChunkByCid(ChunkHash);
- if (!Chunk)
- {
- remotestore_impl::ReportMessage(
- OptionalContext,
- fmt::format("Missing chunk {} for chunked attachment {}", ChunkHash, Chunked.RawHash));
+ BasicFileWriter TmpWriter(TmpFile, 64u * 1024u);
- // We only add 1 as the resulting missing count will be 1 for the dechunked file
- Info.MissingAttachmentCount.fetch_add(1);
- if (!IgnoreMissingAttachments)
+ uint64_t Offset = CompressedBuffer::GetHeaderSizeForNoneEncoder();
+ BLAKE3Stream HashingStream;
+ for (std::uint32_t SequenceIndex : Chunked.ChunkSequence)
+ {
+ const IoHash& ChunkHash = Chunked.ChunkHashes[SequenceIndex];
+ IoBuffer Chunk = ChunkStore.FindChunkByCid(ChunkHash);
+ if (!Chunk)
{
- RemoteResult.SetError(
- gsl::narrow<int>(HttpResponseCode::NotFound),
- "Missing chunk",
+ remotestore_impl::ReportMessage(
+ OptionalContext,
fmt::format("Missing chunk {} for chunked attachment {}", ChunkHash, Chunked.RawHash));
+
+ // We only add 1 as the resulting missing count will be 1 for the dechunked file
+ Info.MissingAttachmentCount.fetch_add(1);
+ if (!IgnoreMissingAttachments)
+ {
+ RemoteResult.SetError(
+ gsl::narrow<int>(HttpResponseCode::NotFound),
+ "Missing chunk",
+ fmt::format("Missing chunk {} for chunked attachment {}", ChunkHash, Chunked.RawHash));
+ }
+ return;
+ }
+ CompositeBuffer Decompressed =
+ CompressedBuffer::FromCompressedNoValidate(std::move(Chunk)).DecompressToComposite();
+ for (const SharedBuffer& Segment : Decompressed.GetSegments())
+ {
+ MemoryView SegmentData = Segment.GetView();
+ HashingStream.Append(SegmentData);
+ TmpWriter.Write(SegmentData.GetData(), SegmentData.GetSize(), Offset);
+ Offset += SegmentData.GetSize();
}
- return;
- }
- CompositeBuffer Decompressed =
- CompressedBuffer::FromCompressedNoValidate(std::move(Chunk)).DecompressToComposite();
- for (const SharedBuffer& Segment : Decompressed.GetSegments())
- {
- MemoryView SegmentData = Segment.GetView();
- HashingStream.Append(SegmentData);
- TmpWriter.Write(SegmentData.GetData(), SegmentData.GetSize(), Offset);
- Offset += SegmentData.GetSize();
}
+ BLAKE3 RawHash = HashingStream.GetHash();
+ ZEN_ASSERT(Chunked.RawHash == IoHash::FromBLAKE3(RawHash));
+ UniqueBuffer Header = CompressedBuffer::CreateHeaderForNoneEncoder(Chunked.RawSize, RawHash);
+ TmpWriter.Write(Header.GetData(), Header.GetSize(), 0);
}
- BLAKE3 RawHash = HashingStream.GetHash();
- ZEN_ASSERT(Chunked.RawHash == IoHash::FromBLAKE3(RawHash));
- UniqueBuffer Header = CompressedBuffer::CreateHeaderForNoneEncoder(Chunked.RawSize, RawHash);
- TmpWriter.Write(Header.GetData(), Header.GetSize(), 0);
+ TmpFile.Close();
+ TmpBuffer = IoBufferBuilder::MakeFromTemporaryFile(TempFileName);
+ }
+ CidStore::InsertResult InsertResult =
+ ChunkStore.AddChunk(TmpBuffer, Chunked.RawHash, CidStore::InsertMode::kMayBeMovedInPlace);
+ if (InsertResult.New)
+ {
+ Info.AttachmentBytesStored.fetch_add(TmpBuffer.GetSize());
+ Info.AttachmentsStored.fetch_add(1);
}
- TmpFile.Close();
- TmpBuffer = IoBufferBuilder::MakeFromTemporaryFile(TempFileName);
+
+ ZEN_INFO("Dechunked attachment {} ({}) in {}",
+ Chunked.RawHash,
+ NiceBytes(Chunked.RawSize),
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
}
- CidStore::InsertResult InsertResult =
- ChunkStore.AddChunk(TmpBuffer, Chunked.RawHash, CidStore::InsertMode::kMayBeMovedInPlace);
- if (InsertResult.New)
+ catch (const std::exception& Ex)
{
- Info.AttachmentBytesStored.fetch_add(TmpBuffer.GetSize());
- Info.AttachmentsStored.fetch_add(1);
+ RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
+ fmt::format("Failed to dechunck file {}", Chunked.RawHash),
+ Ex.what());
}
-
- ZEN_INFO("Dechunked attachment {} ({}) in {}",
- Chunked.RawHash,
- NiceBytes(Chunked.RawSize),
- NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
- }
- catch (const std::exception& Ex)
- {
- RemoteResult.SetError(gsl::narrow<int>(HttpResponseCode::InternalServerError),
- fmt::format("Failed to dechunck file {}", Chunked.RawHash),
- Ex.what());
- }
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
DechunkLatch.CountDown();
@@ -3248,4 +3324,211 @@ RemoteProjectStore::~RemoteProjectStore()
{
}
+#if ZEN_WITH_TESTS
+
+namespace testutils {
+ using namespace std::literals;
+
+ static std::string OidAsString(const Oid& Id)
+ {
+ StringBuilder<25> OidStringBuilder;
+ Id.ToString(OidStringBuilder);
+ return OidStringBuilder.ToString();
+ }
+
+ static CbPackage CreateBulkDataOplogPackage(const Oid& Id, const std::span<const std::pair<Oid, CompressedBuffer>>& Attachments)
+ {
+ CbPackage Package;
+ CbObjectWriter Object;
+ Object << "key"sv << OidAsString(Id);
+ if (!Attachments.empty())
+ {
+ Object.BeginArray("bulkdata");
+ for (const auto& Attachment : Attachments)
+ {
+ CbAttachment Attach(Attachment.second, Attachment.second.DecodeRawHash());
+ Object.BeginObject();
+ Object << "id"sv << Attachment.first;
+ Object << "type"sv
+ << "Standard"sv;
+ Object << "data"sv << Attach;
+ Object.EndObject();
+
+ Package.AddAttachment(Attach);
+ }
+ Object.EndArray();
+ }
+ Package.SetObject(Object.Save());
+ return Package;
+ };
+
+ static std::vector<std::pair<Oid, CompressedBuffer>> CreateAttachments(
+ const std::span<const size_t>& Sizes,
+ OodleCompressionLevel CompressionLevel = OodleCompressionLevel::VeryFast,
+ uint64_t BlockSize = 0)
+ {
+ std::vector<std::pair<Oid, CompressedBuffer>> Result;
+ Result.reserve(Sizes.size());
+ for (size_t Size : Sizes)
+ {
+ CompressedBuffer Compressed =
+ CompressedBuffer::Compress(SharedBuffer(CreateSemiRandomBlob(Size)), OodleCompressor::Mermaid, CompressionLevel, BlockSize);
+ Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed));
+ }
+ return Result;
+ }
+
+} // namespace testutils
+
+struct ExportForceDisableBlocksTrue_ForceTempBlocksFalse
+{
+ static const bool ForceDisableBlocks = true;
+ static const bool ForceEnableTempBlocks = false;
+};
+
+struct ExportForceDisableBlocksFalse_ForceTempBlocksFalse
+{
+ static const bool ForceDisableBlocks = false;
+ static const bool ForceEnableTempBlocks = false;
+};
+
+struct ExportForceDisableBlocksFalse_ForceTempBlocksTrue
+{
+ static const bool ForceDisableBlocks = false;
+ static const bool ForceEnableTempBlocks = true;
+};
+
+TEST_CASE_TEMPLATE("project.store.export",
+ Settings,
+ ExportForceDisableBlocksTrue_ForceTempBlocksFalse,
+ ExportForceDisableBlocksFalse_ForceTempBlocksFalse,
+ ExportForceDisableBlocksFalse_ForceTempBlocksTrue)
+{
+ using namespace std::literals;
+ using namespace testutils;
+
+ ScopedTemporaryDirectory TempDir;
+ ScopedTemporaryDirectory ExportDir;
+
+ GcManager Gc;
+ CidStore CidStore(Gc);
+ CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas", .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
+ CidStore.Initialize(CidConfig);
+
+ std::filesystem::path BasePath = TempDir.Path() / "projectstore";
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
+ std::filesystem::path RootDir = TempDir.Path() / "root";
+ std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
+ std::filesystem::path ProjectRootDir = TempDir.Path() / "game";
+ std::filesystem::path ProjectFilePath = TempDir.Path() / "game" / "game.uproject";
+
+ Ref<ProjectStore::Project> Project(ProjectStore.NewProject(BasePath / "proj1"sv,
+ "proj1"sv,
+ RootDir.string(),
+ EngineRootDir.string(),
+ ProjectRootDir.string(),
+ ProjectFilePath.string()));
+ Ref<ProjectStore::Oplog> Oplog = Project->NewOplog("oplog1", {});
+ CHECK(Oplog);
+
+ Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), {}));
+ Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{77})));
+ Oplog->AppendNewOplogEntry(
+ CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{7123, 583, 690, 99})));
+ Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{55, 122})));
+ Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(
+ Oid::NewOid(),
+ CreateAttachments(std::initializer_list<size_t>{256u * 1024u, 92u * 1024u}, OodleCompressionLevel::None)));
+
+ FileRemoteStoreOptions Options = {RemoteStoreOptions{.MaxBlockSize = 64u * 1024,
+ .MaxChunksPerBlock = 1000,
+ .MaxChunkEmbedSize = 32 * 1024u,
+ .ChunkFileSizeLimit = 64u * 1024u},
+ /*.FolderPath = */ ExportDir.Path(),
+ /*.Name = */ std::string("oplog1"),
+ /*OptionalBaseName = */ std::string(),
+ /*.ForceDisableBlocks = */ Settings::ForceDisableBlocks,
+ /*.ForceEnableTempBlocks = */ Settings::ForceEnableTempBlocks};
+ std::shared_ptr<RemoteProjectStore> RemoteStore = CreateFileRemoteStore(Options);
+ RemoteProjectStore::RemoteStoreInfo StoreInfo = RemoteStore->GetInfo();
+
+ uint32_t NetworkWorkerCount = Max(std::thread::hardware_concurrency() / 4u, 2u);
+ uint32_t WorkerCount =
+ (NetworkWorkerCount < std::thread::hardware_concurrency()) ? Max(std::thread::hardware_concurrency() - NetworkWorkerCount, 4u) : 4u;
+
+ WorkerThreadPool WorkerPool(WorkerCount);
+ WorkerThreadPool NetworkPool(NetworkWorkerCount);
+
+ RemoteProjectStore::Result ExportResult = SaveOplog(CidStore,
+ *RemoteStore,
+ *Project.Get(),
+ *Oplog,
+ NetworkPool,
+ WorkerPool,
+ Options.MaxBlockSize,
+ Options.MaxChunksPerBlock,
+ Options.MaxChunkEmbedSize,
+ Options.ChunkFileSizeLimit,
+ true,
+ false,
+ false,
+ nullptr);
+
+ CHECK(ExportResult.ErrorCode == 0);
+
+ Ref<ProjectStore::Oplog> OplogImport = Project->NewOplog("oplog2", {});
+ CHECK(OplogImport);
+
+ RemoteProjectStore::Result ImportResult = LoadOplog(CidStore,
+ *RemoteStore,
+ *OplogImport,
+ NetworkPool,
+ WorkerPool,
+ /*Force*/ false,
+ /*IgnoreMissingAttachments*/ false,
+ /*CleanOplog*/ false,
+ nullptr);
+ CHECK(ImportResult.ErrorCode == 0);
+
+ RemoteProjectStore::Result ImportForceResult = LoadOplog(CidStore,
+ *RemoteStore,
+ *OplogImport,
+ NetworkPool,
+ WorkerPool,
+ /*Force*/ true,
+ /*IgnoreMissingAttachments*/ false,
+ /*CleanOplog*/ false,
+ nullptr);
+ CHECK(ImportForceResult.ErrorCode == 0);
+
+ RemoteProjectStore::Result ImportCleanResult = LoadOplog(CidStore,
+ *RemoteStore,
+ *OplogImport,
+ NetworkPool,
+ WorkerPool,
+ /*Force*/ false,
+ /*IgnoreMissingAttachments*/ false,
+ /*CleanOplog*/ true,
+ nullptr);
+ CHECK(ImportCleanResult.ErrorCode == 0);
+
+ RemoteProjectStore::Result ImportForceCleanResult = LoadOplog(CidStore,
+ *RemoteStore,
+ *OplogImport,
+ NetworkPool,
+ WorkerPool,
+ /*Force*/ true,
+ /*IgnoreMissingAttachments*/ false,
+ /*CleanOplog*/ true,
+ nullptr);
+ CHECK(ImportForceCleanResult.ErrorCode == 0);
+}
+
+#endif // ZEN_WITH_TESTS
+
+void
+remoteprojectstore_forcelink()
+{
+}
+
} // namespace zen
diff --git a/src/zenserver/projectstore/zenremoteprojectstore.cpp b/src/zenremotestore/projectstore/zenremoteprojectstore.cpp
index 21ddd6cff..000901e45 100644
--- a/src/zenserver/projectstore/zenremoteprojectstore.cpp
+++ b/src/zenremotestore/projectstore/zenremoteprojectstore.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "zenremoteprojectstore.h"
+#include <zenremotestore/projectstore/zenremoteprojectstore.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
diff --git a/src/zenremotestore/xmake.lua b/src/zenremotestore/xmake.lua
new file mode 100644
index 000000000..0818dda7b
--- /dev/null
+++ b/src/zenremotestore/xmake.lua
@@ -0,0 +1,11 @@
+-- Copyright Epic Games, Inc. All Rights Reserved.
+
+target('zenremotestore')
+ set_kind("static")
+ set_group("libs")
+ add_headerfiles("**.h")
+ add_files("**.cpp")
+ add_includedirs("include", {public=true})
+ add_deps("zencore", "zenstore")
+ add_packages("vcpkg::robin-map")
+ add_packages("vcpkg::eastl", {public=true});
diff --git a/src/zenremotestore/zenremotestore.cpp b/src/zenremotestore/zenremotestore.cpp
new file mode 100644
index 000000000..c019bc71d
--- /dev/null
+++ b/src/zenremotestore/zenremotestore.cpp
@@ -0,0 +1,22 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenremotestore/zenremotestore.h>
+
+#include <zenremotestore/chunking/chunkedfile.h>
+#include <zenremotestore/projectstore/remoteprojectstore.h>
+
+#if ZEN_WITH_TESTS
+
+namespace zen {
+
+void
+zenremotestore_forcelinktests()
+{
+ chunkblock_forcelink();
+ chunkedfile_forcelink();
+ remoteprojectstore_forcelink();
+}
+
+} // namespace zen
+
+#endif
diff --git a/src/zenserver-test/buildstore-tests.cpp b/src/zenserver-test/buildstore-tests.cpp
new file mode 100644
index 000000000..29afd3f9d
--- /dev/null
+++ b/src/zenserver-test/buildstore-tests.cpp
@@ -0,0 +1,506 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#if ZEN_WITH_TESTS
+# include "zenserver-test.h"
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <zencore/workthreadpool.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/compress.h>
+# include <zencore/filesystem.h>
+# include <zencore/stream.h>
+# include <zencore/string.h>
+# include <zencore/fmtutils.h>
+# include <zencore/scopeguard.h>
+# include <zenhttp/packageformat.h>
+# include <zenremotestore/builds/buildstoragecache.h>
+# include <zenutil/workerpools.h>
+# include <zenutil/zenserverprocess.h>
+# include <zenhttp/httpclient.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+# include <tsl/robin_set.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen::tests {
+
+using namespace std::literals;
+
+TEST_CASE("buildstore.blobs")
+{
+ std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
+ auto _ = MakeGuard([&SystemRootPath]() { DeleteDirectories(SystemRootPath); });
+
+ std::string_view Namespace = "ns"sv;
+ std::string_view Bucket = "bkt"sv;
+ Oid BuildId = Oid::NewOid();
+
+ std::vector<IoHash> CompressedBlobsHashes;
+ {
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri() + "/builds/");
+
+ for (size_t I = 0; I < 5; I++)
+ {
+ IoBuffer Blob = CreateSemiRandomBlob(4711 + I * 7);
+ CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
+ CompressedBlobsHashes.push_back(CompressedBlob.DecodeRawHash());
+ IoBuffer Payload = std::move(CompressedBlob).GetCompressed().Flatten().AsIoBuffer();
+ Payload.SetContentType(ZenContentType::kCompressedBinary);
+
+ HttpClient::Response Result =
+ Client.Put(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, CompressedBlobsHashes.back()), Payload);
+ CHECK(Result);
+ }
+
+ for (const IoHash& RawHash : CompressedBlobsHashes)
+ {
+ HttpClient::Response Result = Client.Get(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, RawHash),
+ HttpClient::Accept(ZenContentType::kCompressedBinary));
+ CHECK(Result);
+ IoBuffer Payload = Result.ResponsePayload;
+ CHECK(Payload.GetContentType() == ZenContentType::kCompressedBinary);
+ IoHash VerifyRawHash;
+ uint64_t VerifyRawSize;
+ CompressedBuffer CompressedBlob =
+ CompressedBuffer::FromCompressed(SharedBuffer(std::move(Payload)), VerifyRawHash, VerifyRawSize);
+ CHECK(CompressedBlob);
+ CHECK(VerifyRawHash == RawHash);
+ IoBuffer Decompressed = CompressedBlob.Decompress().AsIoBuffer();
+ CHECK(IoHash::HashBuffer(Decompressed) == RawHash);
+ }
+ }
+ {
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri() + "/builds/");
+
+ for (const IoHash& RawHash : CompressedBlobsHashes)
+ {
+ HttpClient::Response Result = Client.Get(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, RawHash),
+ HttpClient::Accept(ZenContentType::kCompressedBinary));
+ CHECK(Result);
+ IoBuffer Payload = Result.ResponsePayload;
+ CHECK(Payload.GetContentType() == ZenContentType::kCompressedBinary);
+ IoHash VerifyRawHash;
+ uint64_t VerifyRawSize;
+ CompressedBuffer CompressedBlob =
+ CompressedBuffer::FromCompressed(SharedBuffer(std::move(Payload)), VerifyRawHash, VerifyRawSize);
+ CHECK(CompressedBlob);
+ CHECK(VerifyRawHash == RawHash);
+ IoBuffer Decompressed = CompressedBlob.Decompress().AsIoBuffer();
+ CHECK(IoHash::HashBuffer(Decompressed) == RawHash);
+ }
+
+ for (size_t I = 0; I < 5; I++)
+ {
+ IoBuffer Blob = CreateSemiRandomBlob(5713 + I * 7);
+ CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
+ CompressedBlobsHashes.push_back(CompressedBlob.DecodeRawHash());
+ IoBuffer Payload = std::move(CompressedBlob).GetCompressed().Flatten().AsIoBuffer();
+ Payload.SetContentType(ZenContentType::kCompressedBinary);
+
+ HttpClient::Response Result =
+ Client.Put(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, CompressedBlobsHashes.back()), Payload);
+ CHECK(Result);
+ }
+ }
+ {
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri() + "/builds/");
+
+ for (const IoHash& RawHash : CompressedBlobsHashes)
+ {
+ HttpClient::Response Result = Client.Get(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, RawHash),
+ HttpClient::Accept(ZenContentType::kCompressedBinary));
+ CHECK(Result);
+ IoBuffer Payload = Result.ResponsePayload;
+ CHECK(Payload.GetContentType() == ZenContentType::kCompressedBinary);
+ IoHash VerifyRawHash;
+ uint64_t VerifyRawSize;
+ CompressedBuffer CompressedBlob =
+ CompressedBuffer::FromCompressed(SharedBuffer(std::move(Payload)), VerifyRawHash, VerifyRawSize);
+ CHECK(CompressedBlob);
+ CHECK(VerifyRawHash == RawHash);
+ IoBuffer Decompressed = CompressedBlob.Decompress().AsIoBuffer();
+ CHECK(IoHash::HashBuffer(Decompressed) == RawHash);
+ }
+ }
+}
+
+namespace {
+ CbObject MakeMetadata(const IoHash& BlobHash, const std::vector<std::pair<std::string, std::string>>& KeyValues)
+ {
+ CbObjectWriter Writer;
+ Writer.AddHash("rawHash"sv, BlobHash);
+ Writer.BeginObject("values");
+ {
+ for (const auto& V : KeyValues)
+ {
+ Writer.AddString(V.first, V.second);
+ }
+ }
+ Writer.EndObject(); // values
+ return Writer.Save();
+ };
+
+} // namespace
+
+TEST_CASE("buildstore.metadata")
+{
+ std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
+ auto _ = MakeGuard([&SystemRootPath]() { DeleteDirectories(SystemRootPath); });
+
+ std::string_view Namespace = "ns"sv;
+ std::string_view Bucket = "bkt"sv;
+ Oid BuildId = Oid::NewOid();
+
+ std::vector<IoHash> BlobHashes;
+ std::vector<CbObject> Metadatas;
+ std::vector<IoHash> MetadataHashes;
+
+ auto GetMetadatas =
+ [](HttpClient& Client, std::string_view Namespace, std::string_view Bucket, const Oid& BuildId, std::vector<IoHash> BlobHashes) {
+ CbObjectWriter Request;
+
+ Request.BeginArray("blobHashes"sv);
+ for (const IoHash& BlobHash : BlobHashes)
+ {
+ Request.AddHash(BlobHash);
+ }
+ Request.EndArray();
+
+ IoBuffer Payload = Request.Save().GetBuffer().AsIoBuffer();
+ Payload.SetContentType(ZenContentType::kCbObject);
+
+ HttpClient::Response Result = Client.Post(fmt::format("{}/{}/{}/blobs/getBlobMetadata", Namespace, Bucket, BuildId),
+ Payload,
+ HttpClient::Accept(ZenContentType::kCbObject));
+ CHECK(Result);
+
+ std::vector<CbObject> ResultMetadatas;
+
+ CbPackage ResponsePackage = ParsePackageMessage(Result.ResponsePayload);
+ CbObject ResponseObject = ResponsePackage.GetObject();
+
+ CbArrayView BlobHashArray = ResponseObject["blobHashes"sv].AsArrayView();
+ CbArrayView MetadatasArray = ResponseObject["metadatas"sv].AsArrayView();
+ ResultMetadatas.reserve(MetadatasArray.Num());
+ auto BlobHashesIt = BlobHashes.begin();
+ auto BlobHashArrayIt = begin(BlobHashArray);
+ auto MetadataArrayIt = begin(MetadatasArray);
+ while (MetadataArrayIt != end(MetadatasArray))
+ {
+ const IoHash BlobHash = (*BlobHashArrayIt).AsHash();
+ while (BlobHash != *BlobHashesIt)
+ {
+ ZEN_ASSERT(BlobHashesIt != BlobHashes.end());
+ BlobHashesIt++;
+ }
+
+ ZEN_ASSERT(BlobHash == *BlobHashesIt);
+
+ const IoHash MetaHash = (*MetadataArrayIt).AsAttachment();
+ const CbAttachment* MetaAttachment = ResponsePackage.FindAttachment(MetaHash);
+ ZEN_ASSERT(MetaAttachment);
+
+ CbObject Metadata = MetaAttachment->AsObject();
+ ResultMetadatas.emplace_back(std::move(Metadata));
+
+ BlobHashArrayIt++;
+ MetadataArrayIt++;
+ BlobHashesIt++;
+ }
+ return ResultMetadatas;
+ };
+
+ {
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri() + "/builds/");
+
+ const size_t BlobCount = 5;
+
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ BlobHashes.push_back(IoHash::HashBuffer(&I, sizeof(I)));
+ Metadatas.push_back(MakeMetadata(BlobHashes.back(), {{"index", fmt::format("{}", I)}}));
+ MetadataHashes.push_back(IoHash::HashBuffer(Metadatas.back().GetBuffer().AsIoBuffer()));
+ }
+
+ {
+ CbPackage RequestPackage;
+ std::vector<CbAttachment> Attachments;
+ tsl::robin_set<IoHash, IoHash::Hasher> AttachmentHashes;
+ Attachments.reserve(BlobCount);
+ AttachmentHashes.reserve(BlobCount);
+ {
+ CbObjectWriter RequestWriter;
+ RequestWriter.BeginArray("blobHashes");
+ for (size_t BlockHashIndex = 0; BlockHashIndex < BlobHashes.size(); BlockHashIndex++)
+ {
+ RequestWriter.AddHash(BlobHashes[BlockHashIndex]);
+ }
+ RequestWriter.EndArray(); // blobHashes
+
+ RequestWriter.BeginArray("metadatas");
+ for (size_t BlockHashIndex = 0; BlockHashIndex < BlobHashes.size(); BlockHashIndex++)
+ {
+ const IoHash ObjectHash = Metadatas[BlockHashIndex].GetHash();
+ RequestWriter.AddBinaryAttachment(ObjectHash);
+ if (!AttachmentHashes.contains(ObjectHash))
+ {
+ Attachments.push_back(CbAttachment(Metadatas[BlockHashIndex], ObjectHash));
+ AttachmentHashes.insert(ObjectHash);
+ }
+ }
+
+ RequestWriter.EndArray(); // metadatas
+
+ RequestPackage.SetObject(RequestWriter.Save());
+ }
+ RequestPackage.AddAttachments(Attachments);
+
+ CompositeBuffer RpcRequestBuffer = FormatPackageMessageBuffer(RequestPackage);
+
+ HttpClient::Response Result = Client.Post(fmt::format("{}/{}/{}/blobs/putBlobMetadata", Namespace, Bucket, BuildId),
+ RpcRequestBuffer,
+ ZenContentType::kCbPackage);
+ CHECK(Result);
+ }
+
+ {
+ std::vector<CbObject> ResultMetadatas = GetMetadatas(Client, Namespace, Bucket, BuildId, BlobHashes);
+
+ for (size_t Index = 0; Index < MetadataHashes.size(); Index++)
+ {
+ const IoHash& ExpectedHash = MetadataHashes[Index];
+ IoHash Hash = IoHash::HashBuffer(ResultMetadatas[Index].GetBuffer().AsIoBuffer());
+ CHECK_EQ(ExpectedHash, Hash);
+ }
+ }
+ }
+ {
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri() + "/builds/");
+
+ std::vector<CbObject> ResultMetadatas = GetMetadatas(Client, Namespace, Bucket, BuildId, BlobHashes);
+
+ for (size_t Index = 0; Index < MetadataHashes.size(); Index++)
+ {
+ const IoHash& ExpectedHash = MetadataHashes[Index];
+ IoHash Hash = IoHash::HashBuffer(ResultMetadatas[Index].GetBuffer().AsIoBuffer());
+ CHECK_EQ(ExpectedHash, Hash);
+ }
+ }
+}
+
+TEST_CASE("buildstore.cache")
+{
+ std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
+ std::filesystem::path TempDir = TestEnv.CreateNewTestDir();
+ auto _ = MakeGuard([&SystemRootPath, &TempDir]() {
+ DeleteDirectories(SystemRootPath);
+ DeleteDirectories(TempDir);
+ });
+
+ std::string_view Namespace = "ns"sv;
+ std::string_view Bucket = "bkt"sv;
+ Oid BuildId = Oid::NewOid();
+
+ std::vector<IoHash> BlobHashes;
+ std::vector<CbObject> Metadatas;
+ std::vector<IoHash> MetadataHashes;
+
+ const size_t BlobCount = 5;
+ {
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri());
+
+ BuildStorageCache::Statistics Stats;
+ std::unique_ptr<BuildStorageCache> Cache(
+ CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, GetTinyWorkerPool(EWorkloadType::Background)));
+
+ {
+ IoHash NoneBlob = IoHash::HashBuffer("data", 4);
+ std::vector<BuildStorageCache::BlobExistsResult> NoneExists = Cache->BlobsExists(BuildId, std::vector<IoHash>{NoneBlob});
+ CHECK(NoneExists.size() == 1);
+ CHECK(!NoneExists[0].HasBody);
+ CHECK(!NoneExists[0].HasMetadata);
+ }
+
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ IoBuffer Blob = CreateSemiRandomBlob(4711 + I * 7);
+ CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
+ BlobHashes.push_back(CompressedBlob.DecodeRawHash());
+ Cache->PutBuildBlob(BuildId, BlobHashes.back(), ZenContentType::kCompressedBinary, CompressedBlob.GetCompressed());
+ }
+
+ Cache->Flush(500);
+
+ Cache = CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, GetTinyWorkerPool(EWorkloadType::Background));
+
+ {
+ std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
+ CHECK(Exists.size() == BlobHashes.size());
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ CHECK(Exists[I].HasBody);
+ CHECK(!Exists[I].HasMetadata);
+ }
+
+ std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
+ CHECK_EQ(0, FetchedMetadatas.size());
+ }
+
+ {
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ IoBuffer BuildBlob = Cache->GetBuildBlob(BuildId, BlobHashes[I]);
+ CHECK(BuildBlob);
+ CHECK_EQ(BlobHashes[I],
+ IoHash::HashBuffer(CompressedBuffer::FromCompressedNoValidate(std::move(BuildBlob)).Decompress().AsIoBuffer()));
+ }
+ }
+
+ {
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ CbObject Metadata = MakeMetadata(BlobHashes[I],
+ {{"key", fmt::format("{}", I)},
+ {"key_plus_one", fmt::format("{}", I + 1)},
+ {"block_hash", fmt::format("{}", BlobHashes[I])}});
+ Metadatas.push_back(Metadata);
+ MetadataHashes.push_back(IoHash::HashBuffer(Metadata.GetBuffer().AsIoBuffer()));
+ }
+ Cache->PutBlobMetadatas(BuildId, BlobHashes, Metadatas);
+ }
+
+ Cache->Flush(500);
+ Cache = CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, GetTinyWorkerPool(EWorkloadType::Background));
+
+ {
+ std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
+ CHECK(Exists.size() == BlobHashes.size());
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ CHECK(Exists[I].HasBody);
+ CHECK(Exists[I].HasMetadata);
+ }
+
+ std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
+ CHECK_EQ(BlobCount, FetchedMetadatas.size());
+
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ CHECK_EQ(MetadataHashes[I], IoHash::HashBuffer(FetchedMetadatas[I].GetBuffer().AsIoBuffer()));
+ }
+ }
+
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ IoBuffer Blob = CreateSemiRandomBlob(4711 + I * 7);
+ CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
+ BlobHashes.push_back(CompressedBlob.DecodeRawHash());
+ Cache->PutBuildBlob(BuildId, BlobHashes.back(), ZenContentType::kCompressedBinary, CompressedBlob.GetCompressed());
+ }
+
+ Cache->Flush(500);
+ Cache = CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, GetTinyWorkerPool(EWorkloadType::Background));
+
+ {
+ std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
+ CHECK(Exists.size() == BlobHashes.size());
+ for (size_t I = 0; I < BlobCount * 2; I++)
+ {
+ CHECK(Exists[I].HasBody);
+ CHECK_EQ(I < BlobCount, Exists[I].HasMetadata);
+ }
+
+ std::vector<CbObject> MetaDatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
+ CHECK_EQ(BlobCount, MetaDatas.size());
+
+ std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
+ CHECK_EQ(BlobCount, FetchedMetadatas.size());
+
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ CHECK_EQ(MetadataHashes[I], IoHash::HashBuffer(FetchedMetadatas[I].GetBuffer().AsIoBuffer()));
+ }
+ }
+ }
+
+ {
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri());
+
+ BuildStorageCache::Statistics Stats;
+ std::unique_ptr<BuildStorageCache> Cache(
+ CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, GetTinyWorkerPool(EWorkloadType::Background)));
+
+ std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
+ CHECK(Exists.size() == BlobHashes.size());
+ for (size_t I = 0; I < BlobCount * 2; I++)
+ {
+ CHECK(Exists[I].HasBody);
+ CHECK_EQ(I < BlobCount, Exists[I].HasMetadata);
+ }
+
+ for (size_t I = 0; I < BlobCount * 2; I++)
+ {
+ IoBuffer BuildBlob = Cache->GetBuildBlob(BuildId, BlobHashes[I]);
+ CHECK(BuildBlob);
+ CHECK_EQ(BlobHashes[I],
+ IoHash::HashBuffer(CompressedBuffer::FromCompressedNoValidate(std::move(BuildBlob)).Decompress().AsIoBuffer()));
+ }
+
+ std::vector<CbObject> MetaDatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
+ CHECK_EQ(BlobCount, MetaDatas.size());
+
+ std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
+ CHECK_EQ(BlobCount, FetchedMetadatas.size());
+
+ for (size_t I = 0; I < BlobCount; I++)
+ {
+ CHECK_EQ(MetadataHashes[I], IoHash::HashBuffer(FetchedMetadatas[I].GetBuffer().AsIoBuffer()));
+ }
+ }
+}
+
+} // namespace zen::tests
+#endif
diff --git a/src/zenserver-test/cache-tests.cpp b/src/zenserver-test/cache-tests.cpp
new file mode 100644
index 000000000..1ce5f3be4
--- /dev/null
+++ b/src/zenserver-test/cache-tests.cpp
@@ -0,0 +1,2366 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#if ZEN_WITH_TESTS
+# include "zenserver-test.h"
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <zencore/workthreadpool.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/compress.h>
+# include <zencore/fmtutils.h>
+# include <zenhttp/packageformat.h>
+# include <zenstore/cache/cachepolicy.h>
+# include <zencore/filesystem.h>
+# include <zencore/stream.h>
+# include <zencore/string.h>
+# include <zenutil/zenserverprocess.h>
+# include <zenhttp/httpclient.h>
+
+# include "cacherequests.h"
+
+# include <random>
+
+namespace zen::tests {
+
+TEST_CASE("zcache.basic")
+{
+ using namespace std::literals;
+
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+
+ const int kIterationCount = 100;
+
+ auto HashKey = [](int i) -> zen::IoHash { return zen::IoHash::HashBuffer(&i, sizeof i); };
+
+ {
+ ZenServerInstance Instance1(TestEnv);
+ Instance1.SetTestDir(TestDir);
+
+ const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
+ const std::string BaseUri = fmt::format("http://localhost:{}/z$", PortNumber);
+
+ // Populate with some simple data
+
+ HttpClient Http{BaseUri};
+
+ for (int i = 0; i < kIterationCount; ++i)
+ {
+ zen::CbObjectWriter Cbo;
+ Cbo << "index" << i;
+
+ IoBuffer Payload = Cbo.Save().GetBuffer().AsIoBuffer();
+ Payload.SetContentType(HttpContentType::kCbObject);
+
+ zen::IoHash Key = HashKey(i);
+
+ HttpClient::Response Result = Http.Put(fmt::format("/test/{}", Key), Payload);
+
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ // Retrieve data
+
+ for (int i = 0; i < kIterationCount; ++i)
+ {
+ zen::IoHash Key = HashKey(i);
+
+ HttpClient::Response Result = Http.Get(fmt::format("/test/{}", Key), {{"Accept", "application/x-ue-cbpkg"}});
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+
+ // Ensure bad bucket identifiers are rejected
+
+ {
+ zen::CbObjectWriter Cbo;
+ Cbo << "index" << 42;
+
+ IoBuffer Payload = Cbo.Save().GetBuffer().AsIoBuffer();
+ Payload.SetContentType(HttpContentType::kCbObject);
+
+ zen::IoHash Key = HashKey(442);
+
+ HttpClient::Response Result = Http.Put(fmt::format("/te!st/{}", Key), Payload);
+
+ CHECK(Result.StatusCode == HttpResponseCode::BadRequest);
+ }
+ }
+
+ // Verify that the data persists between process runs (the previous server has exited at this point)
+
+ {
+ ZenServerInstance Instance1(TestEnv);
+ Instance1.SetTestDir(TestDir);
+ const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
+
+ const std::string BaseUri = fmt::format("http://localhost:{}/z$", PortNumber);
+
+ HttpClient Http{BaseUri};
+
+ // Retrieve data again
+
+ for (int i = 0; i < kIterationCount; ++i)
+ {
+ zen::IoHash Key = HashKey(i);
+
+ HttpClient::Response Result = Http.Get(fmt::format("/{}/{}", "test", Key), {{"Accept", "application/x-ue-cbpkg"}});
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+ }
+}
+
+TEST_CASE("zcache.cbpackage")
+{
+ using namespace std::literals;
+
+ auto CreateTestPackage = [](zen::IoHash& OutAttachmentKey) -> zen::CbPackage {
+ auto Data = zen::SharedBuffer::Clone(zen::MakeMemoryView<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9}));
+ auto CompressedData = zen::CompressedBuffer::Compress(Data);
+
+ OutAttachmentKey = CompressedData.DecodeRawHash();
+
+ zen::CbWriter Obj;
+ Obj.BeginObject("obj"sv);
+ Obj.AddBinaryAttachment("data", OutAttachmentKey);
+ Obj.EndObject();
+
+ zen::CbPackage Package;
+ Package.SetObject(Obj.Save().AsObject());
+ Package.AddAttachment(zen::CbAttachment(CompressedData, OutAttachmentKey));
+
+ return Package;
+ };
+
+ auto IsEqual = [](zen::CbPackage Lhs, zen::CbPackage Rhs) -> bool {
+ std::span<const zen::CbAttachment> LhsAttachments = Lhs.GetAttachments();
+ std::span<const zen::CbAttachment> RhsAttachments = Rhs.GetAttachments();
+
+ if (LhsAttachments.size() != RhsAttachments.size())
+ {
+ return false;
+ }
+
+ for (const zen::CbAttachment& LhsAttachment : LhsAttachments)
+ {
+ const zen::CbAttachment* RhsAttachment = Rhs.FindAttachment(LhsAttachment.GetHash());
+ CHECK(RhsAttachment);
+
+ zen::SharedBuffer LhsBuffer = LhsAttachment.AsCompressedBinary().Decompress();
+ CHECK(!LhsBuffer.IsNull());
+
+ zen::SharedBuffer RhsBuffer = RhsAttachment->AsCompressedBinary().Decompress();
+ CHECK(!RhsBuffer.IsNull());
+
+ if (!LhsBuffer.GetView().EqualBytes(RhsBuffer.GetView()))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ SUBCASE("PUT/GET returns correct package")
+ {
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance Instance1(TestEnv);
+ Instance1.SetTestDir(TestDir);
+ const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
+ const std::string BaseUri = fmt::format("http://localhost:{}/z$", PortNumber);
+
+ HttpClient Http{BaseUri};
+
+ const std::string_view Bucket = "mosdef"sv;
+ zen::IoHash Key;
+ zen::CbPackage ExpectedPackage = CreateTestPackage(Key);
+
+ // PUT
+ {
+ zen::IoBuffer Body = SerializeToBuffer(ExpectedPackage);
+ HttpClient::Response Result = Http.Put(fmt::format("/{}/{}", Bucket, Key), Body);
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ // GET
+ {
+ HttpClient::Response Result = Http.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+
+ zen::CbPackage Package;
+ const bool Ok = Package.TryLoad(Result.ResponsePayload);
+ CHECK(Ok);
+ CHECK(IsEqual(Package, ExpectedPackage));
+ }
+ }
+
+ SUBCASE("PUT propagates upstream")
+ {
+ // Setup local and remote server
+ std::filesystem::path LocalDataDir = TestEnv.CreateNewTestDir();
+ std::filesystem::path RemoteDataDir = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance RemoteInstance(TestEnv);
+ RemoteInstance.SetTestDir(RemoteDataDir);
+ const uint16_t RemotePortNumber = RemoteInstance.SpawnServerAndWaitUntilReady();
+
+ ZenServerInstance LocalInstance(TestEnv);
+ LocalInstance.SetTestDir(LocalDataDir);
+ LocalInstance.SpawnServer(TestEnv.GetNewPortNumber(),
+ fmt::format("--upstream-thread-count=0 --upstream-zen-url=http://localhost:{}", RemotePortNumber));
+ const uint16_t LocalPortNumber = LocalInstance.WaitUntilReady();
+ CHECK_MESSAGE(LocalPortNumber != 0, LocalInstance.GetLogOutput());
+
+ const auto LocalBaseUri = fmt::format("http://localhost:{}/z$", LocalPortNumber);
+ const auto RemoteBaseUri = fmt::format("http://localhost:{}/z$", RemotePortNumber);
+
+ const std::string_view Bucket = "mosdef"sv;
+ zen::IoHash Key;
+ zen::CbPackage ExpectedPackage = CreateTestPackage(Key);
+
+ HttpClient LocalHttp{LocalBaseUri};
+ HttpClient RemoteHttp{RemoteBaseUri};
+
+ // Store the cache record package in the local instance
+ {
+ zen::IoBuffer Body = SerializeToBuffer(ExpectedPackage);
+ HttpClient::Response Result = LocalHttp.Put(fmt::format("/{}/{}", Bucket, Key), Body);
+
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ // The cache record can be retrieved as a package from the local instance
+ {
+ HttpClient::Response Result = LocalHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+
+ zen::CbPackage Package;
+ const bool Ok = Package.TryLoad(Result.ResponsePayload);
+ CHECK(Ok);
+ CHECK(IsEqual(Package, ExpectedPackage));
+ }
+
+ // The cache record can be retrieved as a package from the remote instance
+ {
+ HttpClient::Response Result = RemoteHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+
+ zen::CbPackage Package;
+ const bool Ok = Package.TryLoad(Result.ResponsePayload);
+ CHECK(Ok);
+ CHECK(IsEqual(Package, ExpectedPackage));
+ }
+ }
+
+ SUBCASE("GET finds upstream when missing in local")
+ {
+ // Setup local and remote server
+ std::filesystem::path LocalDataDir = TestEnv.CreateNewTestDir();
+ std::filesystem::path RemoteDataDir = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance RemoteInstance(TestEnv);
+ RemoteInstance.SetTestDir(RemoteDataDir);
+ const uint16_t RemotePortNumber = RemoteInstance.SpawnServerAndWaitUntilReady();
+
+ ZenServerInstance LocalInstance(TestEnv);
+ LocalInstance.SetTestDir(LocalDataDir);
+ LocalInstance.SpawnServer(TestEnv.GetNewPortNumber(),
+ fmt::format("--upstream-thread-count=0 --upstream-zen-url=http://localhost:{}", RemotePortNumber));
+ const uint16_t LocalPortNumber = LocalInstance.WaitUntilReady();
+ CHECK_MESSAGE(LocalPortNumber != 0, LocalInstance.GetLogOutput());
+
+ const auto LocalBaseUri = fmt::format("http://localhost:{}/z$", LocalPortNumber);
+ const auto RemoteBaseUri = fmt::format("http://localhost:{}/z$", RemotePortNumber);
+
+ HttpClient LocalHttp{LocalBaseUri};
+ HttpClient RemoteHttp{RemoteBaseUri};
+
+ const std::string_view Bucket = "mosdef"sv;
+ zen::IoHash Key;
+ zen::CbPackage ExpectedPackage = CreateTestPackage(Key);
+
+ // Store the cache record package in upstream cache
+ {
+ zen::IoBuffer Body = SerializeToBuffer(ExpectedPackage);
+ HttpClient::Response Result = RemoteHttp.Put(fmt::format("/{}/{}", Bucket, Key), Body);
+
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ // The cache record can be retrieved as a package from the local cache
+ {
+ HttpClient::Response Result = LocalHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+
+ zen::CbPackage Package;
+ const bool Ok = Package.TryLoad(Result.ResponsePayload);
+ CHECK(Ok);
+ CHECK(IsEqual(Package, ExpectedPackage));
+ }
+ }
+}
+
+TEST_CASE("zcache.policy")
+{
+ using namespace std::literals;
+ using namespace utils;
+
+ auto GenerateData = [](uint64_t Size, zen::IoHash& OutHash) -> zen::IoBuffer {
+ auto Buf = zen::UniqueBuffer::Alloc(Size);
+ uint8_t* Data = reinterpret_cast<uint8_t*>(Buf.GetData());
+ for (uint64_t Idx = 0; Idx < Size; Idx++)
+ {
+ Data[Idx] = Idx % 256;
+ }
+ OutHash = zen::IoHash::HashBuffer(Data, Size);
+ return Buf.MoveToShared().AsIoBuffer();
+ };
+
+ auto GeneratePackage = [](zen::IoHash& OutRecordKey, zen::IoHash& OutAttachmentKey) -> zen::CbPackage {
+ auto Data = zen::SharedBuffer::Clone(zen::MakeMemoryView<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9}));
+ auto CompressedData = zen::CompressedBuffer::Compress(Data);
+ OutAttachmentKey = CompressedData.DecodeRawHash();
+
+ zen::CbWriter Writer;
+ Writer.BeginObject("obj"sv);
+ Writer.AddBinaryAttachment("data", OutAttachmentKey);
+ Writer.EndObject();
+ CbObject CacheRecord = Writer.Save().AsObject();
+
+ OutRecordKey = IoHash::HashBuffer(CacheRecord.GetBuffer().GetView());
+
+ zen::CbPackage Package;
+ Package.SetObject(CacheRecord);
+ Package.AddAttachment(zen::CbAttachment(CompressedData, OutAttachmentKey));
+
+ return Package;
+ };
+
+ SUBCASE("query - 'local' does not query upstream (binary)")
+ {
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamInst(TestEnv);
+ UpstreamCfg.Spawn(UpstreamInst);
+ const uint16_t UpstreamPort = UpstreamCfg.Port;
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamPort);
+ ZenServerInstance LocalInst(TestEnv);
+ LocalCfg.Spawn(LocalInst);
+
+ const std::string_view Bucket = "legacy"sv;
+
+ zen::IoHash Key;
+ IoBuffer BinaryValue = GenerateData(1024, Key);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient RemoteHttp{UpstreamCfg.BaseUri};
+
+ {
+ HttpClient::Response Result = RemoteHttp.Put(fmt::format("/{}/{}", Bucket, Key), BinaryValue);
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ HttpClient::Response Result =
+ LocalHttp.Get(fmt::format("/{}/{}?Policy=QueryLocal,Store", Bucket, Key), {{"Accept", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::NotFound);
+ }
+
+ {
+ HttpClient::Response Result =
+ LocalHttp.Get(fmt::format("/{}/{}?Policy=Query,Store", Bucket, Key), {{"Accept", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+ }
+
+ SUBCASE("store - 'local' does not store upstream (binary)")
+ {
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamInst(TestEnv);
+ UpstreamCfg.Spawn(UpstreamInst);
+ const uint16_t UpstreamPort = UpstreamCfg.Port;
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamPort);
+ ZenServerInstance LocalInst(TestEnv);
+ LocalCfg.Spawn(LocalInst);
+
+ const auto Bucket = "legacy"sv;
+
+ zen::IoHash Key;
+ IoBuffer BinaryValue = GenerateData(1024, Key);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient RemoteHttp{UpstreamCfg.BaseUri};
+
+ // Store binary cache value locally
+ {
+ HttpClient::Response Result = LocalHttp.Put(fmt::format("/{}/{}?Policy=Query,StoreLocal", Bucket, Key),
+ BinaryValue,
+ {{"Content-Type", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ HttpClient::Response Result = RemoteHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::NotFound);
+ }
+
+ {
+ HttpClient::Response Result = LocalHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+ }
+
+ SUBCASE("store - 'local/remote' stores local and upstream (binary)")
+ {
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamInst(TestEnv);
+ UpstreamCfg.Spawn(UpstreamInst);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalInst(TestEnv);
+ LocalCfg.Spawn(LocalInst);
+
+ const auto Bucket = "legacy"sv;
+
+ zen::IoHash Key;
+ IoBuffer BinaryValue = GenerateData(1024, Key);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient RemoteHttp{UpstreamCfg.BaseUri};
+
+ // Store binary cache value locally and upstream
+ {
+ HttpClient::Response Result = LocalHttp.Put(fmt::format("/{}/{}?Policy=Query,Store", Bucket, Key),
+ BinaryValue,
+ {{"Content-Type", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ HttpClient::Response Result = RemoteHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+
+ {
+ HttpClient::Response Result = LocalHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/octet-stream"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+ }
+
+ SUBCASE("query - 'local' does not query upstream (cbpackage)")
+ {
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamInst(TestEnv);
+ UpstreamCfg.Spawn(UpstreamInst);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalInst(TestEnv);
+ LocalCfg.Spawn(LocalInst);
+
+ const auto Bucket = "legacy"sv;
+
+ zen::IoHash Key;
+ zen::IoHash PayloadId;
+ zen::CbPackage Package = GeneratePackage(Key, PayloadId);
+ IoBuffer Buf = SerializeToBuffer(Package);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient RemoteHttp{UpstreamCfg.BaseUri};
+
+ // Store package upstream
+ {
+ HttpClient::Response Result = RemoteHttp.Put(fmt::format("/{}/{}", Bucket, Key), Buf);
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ HttpClient::Response Result =
+ LocalHttp.Get(fmt::format("/{}/{}?Policy=QueryLocal,Store", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::NotFound);
+ }
+
+ {
+ HttpClient::Response Result =
+ LocalHttp.Get(fmt::format("/{}/{}?Policy=Query,Store", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+ }
+
+ SUBCASE("store - 'local' does not store upstream (cbpackage)")
+ {
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamInst(TestEnv);
+ UpstreamCfg.Spawn(UpstreamInst);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalInst(TestEnv);
+ LocalCfg.Spawn(LocalInst);
+
+ const auto Bucket = "legacy"sv;
+
+ zen::IoHash Key;
+ zen::IoHash PayloadId;
+ zen::CbPackage Package = GeneratePackage(Key, PayloadId);
+ IoBuffer Buf = SerializeToBuffer(Package);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient RemoteHttp{UpstreamCfg.BaseUri};
+
+ // Store package locally
+ {
+ HttpClient::Response Result = LocalHttp.Put(fmt::format("/{}/{}?Policy=Query,StoreLocal", Bucket, Key), Buf);
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ HttpClient::Response Result = RemoteHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::NotFound);
+ }
+
+ {
+ HttpClient::Response Result = LocalHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+ }
+
+ SUBCASE("store - 'local/remote' stores local and upstream (cbpackage)")
+ {
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamInst(TestEnv);
+ UpstreamCfg.Spawn(UpstreamInst);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalInst(TestEnv);
+ LocalCfg.Spawn(LocalInst);
+
+ const auto Bucket = "legacy"sv;
+
+ zen::IoHash Key;
+ zen::IoHash PayloadId;
+ zen::CbPackage Package = GeneratePackage(Key, PayloadId);
+ IoBuffer Buf = SerializeToBuffer(Package);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient RemoteHttp{UpstreamCfg.BaseUri};
+
+ // Store package locally and upstream
+ {
+ HttpClient::Response Result = LocalHttp.Put(fmt::format("/{}/{}?Policy=Query,Store", Bucket, Key), Buf);
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ HttpClient::Response Result = RemoteHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+
+ {
+ HttpClient::Response Result = LocalHttp.Get(fmt::format("/{}/{}", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ }
+ }
+
+ SUBCASE("skip - 'data' returns cache record without attachments/empty payload")
+ {
+ ZenConfig Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance Instance(TestEnv);
+ Cfg.Spawn(Instance);
+
+ const auto Bucket = "test"sv;
+
+ zen::IoHash Key;
+ zen::IoHash PayloadId;
+ zen::CbPackage Package = GeneratePackage(Key, PayloadId);
+ IoBuffer Buf = SerializeToBuffer(Package);
+
+ HttpClient Http{Cfg.BaseUri};
+
+ // Store package
+ {
+ HttpClient::Response Result = Http.Put(fmt::format("/{}/{}", Bucket, Key), Buf);
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ // Get package
+ {
+ HttpClient::Response Result =
+ Http.Get(fmt::format("/{}/{}?Policy=Default,SkipData", Bucket, Key), {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Result);
+ CbPackage ResponsePackage;
+ CHECK(ResponsePackage.TryLoad(Result.ResponsePayload));
+ CHECK(ResponsePackage.GetAttachments().size() == 0);
+ }
+
+ // Get record
+ {
+ HttpClient::Response Result =
+ Http.Get(fmt::format("/{}/{}?Policy=Default,SkipData", Bucket, Key), {{"Accept", "application/x-ue-cb"}});
+ CHECK(Result);
+ CbObject ResponseObject = zen::LoadCompactBinaryObject(Result.ResponsePayload);
+ CHECK(ResponseObject);
+ }
+
+ // Get payload
+ {
+ HttpClient::Response Result =
+ Http.Get(fmt::format("/{}/{}/{}?Policy=Default,SkipData", Bucket, Key, PayloadId), {{"Accept", "application/x-ue-comp"}});
+ CHECK(Result);
+ CHECK(Result.ResponsePayload.GetSize() == 0);
+ }
+ }
+
+ SUBCASE("skip - 'data' returns empty binary value")
+ {
+ ZenConfig Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance Instance(TestEnv);
+ Cfg.Spawn(Instance);
+
+ const auto Bucket = "test"sv;
+
+ zen::IoHash Key;
+ IoBuffer BinaryValue = GenerateData(1024, Key);
+
+ HttpClient Http{Cfg.BaseUri};
+
+ // Store binary cache value
+ {
+ HttpClient::Response Result = Http.Put(fmt::format("/{}/{}", Bucket, Key), BinaryValue);
+ CHECK(Result.StatusCode == HttpResponseCode::Created);
+ }
+
+ // Get package
+ {
+ HttpClient::Response Result =
+ Http.Get(fmt::format("/{}/{}?Policy=Default,SkipData", Bucket, Key), {{"Accept", "application/octet-stream"}});
+ CHECK(Result);
+ CHECK(Result.ResponsePayload.GetSize() == 0);
+ }
+ }
+}
+
+TEST_CASE("zcache.rpc")
+{
+ using namespace std::literals;
+
+ auto AppendCacheRecord = [](cacherequests::PutCacheRecordsRequest& Request,
+ const zen::CacheKey& CacheKey,
+ size_t PayloadSize,
+ CachePolicy RecordPolicy) {
+ std::vector<uint8_t> Data;
+ Data.resize(PayloadSize);
+ uint32_t DataSeed = *reinterpret_cast<const uint32_t*>(&CacheKey.Hash.Hash[0]);
+ uint16_t* DataPtr = reinterpret_cast<uint16_t*>(Data.data());
+ for (size_t Idx = 0; Idx < PayloadSize / 2; ++Idx)
+ {
+ DataPtr[Idx] = static_cast<uint16_t>((Idx + DataSeed) % 0xffffu);
+ }
+ if (PayloadSize & 1)
+ {
+ Data[PayloadSize - 1] = static_cast<uint8_t>((PayloadSize - 1) & 0xff);
+ }
+ CompressedBuffer Value = zen::CompressedBuffer::Compress(SharedBuffer::MakeView(Data.data(), Data.size()));
+ Request.Requests.push_back({.Key = CacheKey, .Values = {{.Id = Oid::NewOid(), .Body = std::move(Value)}}, .Policy = RecordPolicy});
+ };
+
+ auto PutCacheRecords = [&AppendCacheRecord](std::string_view BaseUri,
+ std::string_view Namespace,
+ std::string_view Bucket,
+ size_t Num,
+ size_t PayloadSize = 1024,
+ size_t KeyOffset = 1,
+ CachePolicy PutPolicy = CachePolicy::Default,
+ std::vector<CbPackage>* OutPackages = nullptr) -> std::vector<CacheKey> {
+ std::vector<zen::CacheKey> OutKeys;
+
+ HttpClient Http{BaseUri};
+
+ for (uint32_t Key = 1; Key <= Num; ++Key)
+ {
+ zen::IoHash KeyHash;
+ ((uint32_t*)(KeyHash.Hash))[0] = gsl::narrow<uint32_t>(KeyOffset + Key);
+ const zen::CacheKey CacheKey = zen::CacheKey::Create(Bucket, KeyHash);
+
+ cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
+ AppendCacheRecord(Request, CacheKey, PayloadSize, PutPolicy);
+ OutKeys.push_back(CacheKey);
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+
+ CompositeBuffer Body(FormatPackageMessageBuffer(Package));
+ HttpClient::Response Result =
+ Http.Post("/$rpc", Body, HttpContentType::kCbPackage, HttpClient::Accept(HttpContentType::kCbPackage));
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ if (OutPackages)
+ {
+ OutPackages->emplace_back(std::move(Package));
+ }
+ }
+
+ return OutKeys;
+ };
+
+ struct GetCacheRecordResult
+ {
+ zen::CbPackage Response;
+ cacherequests::GetCacheRecordsResult Result;
+ bool Success;
+ };
+
+ auto GetCacheRecords = [](std::string_view BaseUri,
+ std::string_view Namespace,
+ std::span<zen::CacheKey> Keys,
+ zen::CachePolicy Policy,
+ zen::RpcAcceptOptions AcceptOptions = zen::RpcAcceptOptions::kNone,
+ int Pid = 0) -> GetCacheRecordResult {
+ cacherequests::GetCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
+ .AcceptOptions = static_cast<uint16_t>(AcceptOptions),
+ .ProcessPid = Pid,
+ .DefaultPolicy = Policy,
+ .Namespace = std::string(Namespace)};
+ for (const CacheKey& Key : Keys)
+ {
+ Request.Requests.push_back({.Key = Key});
+ }
+
+ CbObjectWriter RequestWriter;
+ CHECK(Request.Format(RequestWriter));
+
+ IoBuffer Body = RequestWriter.Save().GetBuffer().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbObject);
+
+ HttpClient Http{BaseUri};
+
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ GetCacheRecordResult OutResult;
+
+ if (Result.StatusCode == HttpResponseCode::OK)
+ {
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ CHECK(!Response.IsNull());
+ OutResult.Response = std::move(Response);
+ CHECK(OutResult.Result.Parse(OutResult.Response));
+ OutResult.Success = true;
+ }
+
+ return OutResult;
+ };
+
+ SUBCASE("get cache records")
+ {
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance Inst(TestEnv);
+ Inst.SetTestDir(TestDir);
+
+ const uint16_t BasePort = Inst.SpawnServerAndWaitUntilReady();
+ const std::string BaseUri = fmt::format("http://localhost:{}/z$", BasePort);
+
+ CachePolicy Policy = CachePolicy::Default;
+ std::vector<zen::CacheKey> Keys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 16);
+ GetCacheRecordResult Result = GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record);
+ CHECK(Record->Key == ExpectedKey);
+ CHECK(Record->Values.size() == 1);
+
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ CHECK(Value.Body);
+ }
+ }
+ }
+
+ SUBCASE("get missing cache records")
+ {
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance Inst(TestEnv);
+ Inst.SetTestDir(TestDir);
+ const uint16_t BasePort = Inst.SpawnServerAndWaitUntilReady();
+ const std::string BaseUri = fmt::format("http://localhost:{}/z$", BasePort);
+
+ CachePolicy Policy = CachePolicy::Default;
+ std::vector<zen::CacheKey> ExistingKeys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 16);
+ std::vector<zen::CacheKey> Keys;
+
+ for (const zen::CacheKey& Key : ExistingKeys)
+ {
+ Keys.push_back(Key);
+ Keys.push_back(CacheKey::Create("missing"sv, IoHash::Zero));
+ }
+
+ GetCacheRecordResult Result = GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ size_t KeyIndex = 0;
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ const bool Missing = Index++ % 2 != 0;
+
+ if (Missing)
+ {
+ CHECK(!Record);
+ }
+ else
+ {
+ const CacheKey& ExpectedKey = ExistingKeys[KeyIndex++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ CHECK(Value.Body);
+ }
+ }
+ }
+ }
+
+ SUBCASE("policy - 'QueryLocal' does not query upstream")
+ {
+ using namespace utils;
+
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamServer(TestEnv);
+ SpawnServer(UpstreamServer, UpstreamCfg);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ std::vector<zen::CacheKey> Keys = PutCacheRecords(UpstreamCfg.BaseUri, "ue4.ddc"sv, "mastodon"sv, 4);
+
+ CachePolicy Policy = CachePolicy::QueryLocal;
+ GetCacheRecordResult Result = GetCacheRecords(LocalCfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(!Record);
+ }
+ }
+
+ SUBCASE("policy - 'QueryRemote' does query upstream")
+ {
+ using namespace utils;
+
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamServer(TestEnv);
+ SpawnServer(UpstreamServer, UpstreamCfg);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ std::vector<zen::CacheKey> Keys = PutCacheRecords(UpstreamCfg.BaseUri, "ue4.ddc"sv, "mastodon"sv, 4);
+
+ CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
+ GetCacheRecordResult Result = GetCacheRecords(LocalCfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ }
+ }
+
+ SUBCASE("policy - 'QueryLocal' on put allows overwrite with differing value when not limiting overwrites")
+ {
+ using namespace utils;
+
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamServer(TestEnv);
+ SpawnServer(UpstreamServer, UpstreamCfg);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ size_t PayloadSize = 1024;
+ std::string_view Namespace("ue4.ddc"sv);
+ std::string_view Bucket("mastodon"sv);
+ const size_t NumRecords = 4;
+ std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient UpstreamHttp{UpstreamCfg.BaseUri};
+
+ for (const zen::CacheKey& CacheKey : Keys)
+ {
+ cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
+ AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Default);
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbPackage);
+ HttpClient::Response Result = LocalHttp.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ cacherequests::PutCacheRecordsResult ParsedResult;
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ CHECK(!Response.IsNull());
+ CHECK(ParsedResult.Parse(Response));
+ for (bool ResponseSuccess : ParsedResult.Success)
+ {
+ CHECK(ResponseSuccess);
+ }
+ CHECK(ParsedResult.Details.empty());
+ }
+
+ auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
+ CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
+ GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ CHECK(Value.RawSize == PayloadSize * 2);
+ }
+ }
+ };
+
+ // Check that the records are present and overwritten in the local server
+ CheckRecordCorrectness(LocalCfg);
+
+ // Check that the records are present and overwritten in the upstream server
+ CheckRecordCorrectness(UpstreamCfg);
+ }
+
+ SUBCASE("policy - 'QueryLocal' on put denies overwrite with differing value when limiting overwrites")
+ {
+ using namespace utils;
+
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamServer(TestEnv);
+ SpawnServer(UpstreamServer, UpstreamCfg);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port, "--cache-bucket-limit-overwrites");
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ size_t PayloadSize = 1024;
+ std::string_view Namespace("ue4.ddc"sv);
+ std::string_view Bucket("mastodon"sv);
+ const size_t NumRecords = 4;
+ std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient UpstreamHttp{UpstreamCfg.BaseUri};
+
+ for (const zen::CacheKey& CacheKey : Keys)
+ {
+ cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
+ AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Default);
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbPackage);
+
+ HttpClient::Response Result = LocalHttp.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ cacherequests::PutCacheRecordsResult ParsedResult;
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ CHECK(!Response.IsNull());
+ CHECK(ParsedResult.Parse(Response));
+ CHECK(Request.Requests.size() == ParsedResult.Success.size());
+ for (bool ResponseSuccess : ParsedResult.Success)
+ {
+ CHECK(ResponseSuccess);
+ }
+ CHECK(Request.Requests.size() == ParsedResult.Details.size());
+ for (const CbObjectView& Details : ParsedResult.Details)
+ {
+ CHECK(Details);
+ CHECK(Details["RawHash"sv].IsHash());
+ CHECK(Details["RawSize"sv].IsInteger());
+ CHECK(Details["Record"sv].IsObject());
+ }
+ }
+
+ auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
+ CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
+ GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ CHECK(Value.RawSize == PayloadSize);
+ }
+ }
+ };
+
+ // Check that the records are present and not overwritten in the local server
+ CheckRecordCorrectness(LocalCfg);
+
+ // Check that the records are present and not overwritten in the upstream server
+ CheckRecordCorrectness(UpstreamCfg);
+ }
+
+ SUBCASE("policy - no 'QueryLocal' on put allows overwrite with differing value when limiting overwrites")
+ {
+ using namespace utils;
+
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamServer(TestEnv);
+ SpawnServer(UpstreamServer, UpstreamCfg);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port, "--cache-bucket-limit-overwrites");
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient UpstreamHttp{UpstreamCfg.BaseUri};
+
+ size_t PayloadSize = 1024;
+ std::string_view Namespace("ue4.ddc"sv);
+ std::string_view Bucket("mastodon"sv);
+ const size_t NumRecords = 4;
+ std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize);
+
+ for (const zen::CacheKey& CacheKey : Keys)
+ {
+ cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
+ AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Store);
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbPackage);
+ HttpClient::Response Result = LocalHttp.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ cacherequests::PutCacheRecordsResult ParsedResult;
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ CHECK(!Response.IsNull());
+ CHECK(ParsedResult.Parse(Response));
+ for (bool ResponseSuccess : ParsedResult.Success)
+ {
+ CHECK(ResponseSuccess);
+ }
+ CHECK(ParsedResult.Details.empty());
+ }
+
+ auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
+ CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
+ GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ CHECK(Value.RawSize == PayloadSize * 2);
+ }
+ }
+ };
+
+ // Check that the records are present and overwritten in the local server
+ CheckRecordCorrectness(LocalCfg);
+
+ // Check that the records are present and overwritten in the upstream server
+ CheckRecordCorrectness(UpstreamCfg);
+ }
+
+ SUBCASE("policy - 'QueryLocal' on put allows overwrite with equivalent value when limiting overwrites")
+ {
+ using namespace utils;
+
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamServer(TestEnv);
+ SpawnServer(UpstreamServer, UpstreamCfg);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port, "--cache-bucket-limit-overwrites");
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ HttpClient LocalHttp{LocalCfg.BaseUri};
+ HttpClient UpstreamHttp{UpstreamCfg.BaseUri};
+
+ size_t PayloadSize = 1024;
+ std::string_view Namespace("ue4.ddc"sv);
+ std::string_view Bucket("mastodon"sv);
+ const size_t NumRecords = 4;
+ std::vector<CbPackage> Packages;
+ std::vector<zen::CacheKey> Keys =
+ PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize, 1, CachePolicy::Default, &Packages);
+
+ for (const CbPackage& Package : Packages)
+ {
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbPackage);
+ HttpClient::Response Result = LocalHttp.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+ cacherequests::PutCacheRecordsResult ParsedResult;
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ CHECK(!Response.IsNull());
+ CHECK(ParsedResult.Parse(Response));
+ for (bool ResponseSuccess : ParsedResult.Success)
+ {
+ CHECK(ResponseSuccess);
+ }
+ CHECK(ParsedResult.Details.empty());
+ }
+
+ auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
+ CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
+ GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ CHECK(Value.RawSize == PayloadSize);
+ }
+ }
+ };
+
+ // Check that the records are present and unchanged in the local server
+ CheckRecordCorrectness(LocalCfg);
+
+ // Check that the records are present and unchanged in the upstream server
+ CheckRecordCorrectness(UpstreamCfg);
+ }
+
+ // TODO: Propagation for rejected PUTs
+ // SUBCASE("policy - 'QueryLocal' on put denies overwrite with differing value when limiting overwrites but allows propagation to
+ // upstream")
+ // {
+ // using namespace utils;
+
+ // ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ // ZenServerInstance UpstreamServer(TestEnv);
+ // SpawnServer(UpstreamServer, UpstreamCfg);
+
+ // ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port,
+ // "--cache-bucket-limit-overwrites"); ZenServerInstance LocalServer(TestEnv); SpawnServer(LocalServer, LocalCfg);
+
+ // size_t PayloadSize = 1024;
+ // std::string_view Namespace("ue4.ddc"sv);
+ // std::string_view Bucket("mastodon"sv);
+ // const size_t NumRecords = 4;
+ // std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize, 1,
+ // CachePolicy::Local);
+
+ // for (const zen::CacheKey& CacheKey : Keys)
+ // {
+ // cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
+ // AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Default);
+
+ // CbPackage Package;
+ // CHECK(Request.Format(Package));
+
+ // IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ // cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", LocalCfg.BaseUri)},
+ // cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
+ // cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
+
+ // CHECK(Result.status_code == 200);
+ // cacherequests::PutCacheRecordsResult ParsedResult;
+ // CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
+ // CHECK(!Response.IsNull());
+ // CHECK(ParsedResult.Parse(Response));
+ // for (bool ResponseSuccess : ParsedResult.Success)
+ // {
+ // CHECK(!ResponseSuccess);
+ // }
+ // }
+
+ // auto CheckRecordCorrectness = [&](const ZenConfig& Cfg, size_t ExpectedPayloadSize) {
+ // CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
+ // GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
+
+ // CHECK(Result.Result.Results.size() == Keys.size());
+
+ // for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ // {
+ // CHECK(Record);
+ // const CacheKey& ExpectedKey = Keys[Index++];
+ // CHECK(Record->Key == ExpectedKey);
+ // for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ // {
+ // CHECK(Value.RawSize == ExpectedPayloadSize);
+ // }
+ // }
+ // };
+
+ // // Check that the records are present and not overwritten in the local server
+ // CheckRecordCorrectness(LocalCfg, PayloadSize);
+
+ // // Check that the records are present and are the newer size in the upstream server
+ // CheckRecordCorrectness(UpstreamCfg, PayloadSize*2);
+ // }
+
+ SUBCASE("RpcAcceptOptions")
+ {
+ using namespace utils;
+
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance Inst(TestEnv);
+ Inst.SetTestDir(TestDir);
+
+ const uint16_t BasePort = Inst.SpawnServerAndWaitUntilReady();
+ const std::string BaseUri = fmt::format("http://localhost:{}/z$", BasePort);
+
+ std::vector<zen::CacheKey> SmallKeys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 4, 1024);
+ std::vector<zen::CacheKey> LargeKeys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 2, 1024 * 1024 * 16, SmallKeys.size());
+
+ std::vector<zen::CacheKey> Keys(SmallKeys.begin(), SmallKeys.end());
+ Keys.insert(Keys.end(), LargeKeys.begin(), LargeKeys.end());
+
+ {
+ GetCacheRecordResult Result = GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, CachePolicy::Default);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
+ IoBufferFileReference Ref;
+ bool IsFileRef = Body.GetFileReference(Ref);
+ CHECK(!IsFileRef);
+ }
+ }
+ }
+
+ // File path, but only for large files
+ {
+ GetCacheRecordResult Result =
+ GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, CachePolicy::Default, RpcAcceptOptions::kAllowLocalReferences);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
+ IoBufferFileReference Ref;
+ bool IsFileRef = Body.GetFileReference(Ref);
+ CHECK(IsFileRef == (Body.Size() > 1024));
+ }
+ }
+ }
+
+ // File path, for all files
+ {
+ GetCacheRecordResult Result =
+ GetCacheRecords(BaseUri,
+ "ue4.ddc"sv,
+ Keys,
+ CachePolicy::Default,
+ RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialLocalReferences);
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
+ IoBufferFileReference Ref;
+ bool IsFileRef = Body.GetFileReference(Ref);
+ CHECK(IsFileRef);
+ }
+ }
+ }
+
+ // File handle, but only for large files
+ {
+ GetCacheRecordResult Result = GetCacheRecords(BaseUri,
+ "ue4.ddc"sv,
+ Keys,
+ CachePolicy::Default,
+ RpcAcceptOptions::kAllowLocalReferences,
+ GetCurrentProcessId());
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
+ IoBufferFileReference Ref;
+ bool IsFileRef = Body.GetFileReference(Ref);
+ CHECK(IsFileRef == (Body.Size() > 1024));
+ }
+ }
+ }
+
+ // File handle, for all files
+ {
+ GetCacheRecordResult Result =
+ GetCacheRecords(BaseUri,
+ "ue4.ddc"sv,
+ Keys,
+ CachePolicy::Default,
+ RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialLocalReferences,
+ GetCurrentProcessId());
+
+ CHECK(Result.Result.Results.size() == Keys.size());
+
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ CHECK(Record);
+ const CacheKey& ExpectedKey = Keys[Index++];
+ CHECK(Record->Key == ExpectedKey);
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
+ IoBufferFileReference Ref;
+ bool IsFileRef = Body.GetFileReference(Ref);
+ CHECK(IsFileRef);
+ }
+ }
+ }
+ }
+}
+
+TEST_CASE("zcache.failing.upstream")
+{
+ // This is an exploratory test that takes a long time to run, so lets skip it by default
+ if (true)
+ {
+ return;
+ }
+
+ using namespace std::literals;
+ using namespace utils;
+
+ ZenConfig Upstream1Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance Upstream1Server(TestEnv);
+ SpawnServer(Upstream1Server, Upstream1Cfg);
+
+ ZenConfig Upstream2Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance Upstream2Server(TestEnv);
+ SpawnServer(Upstream2Server, Upstream2Cfg);
+
+ std::vector<std::uint16_t> UpstreamPorts = {Upstream1Cfg.Port, Upstream2Cfg.Port};
+ ZenConfig LocalCfg = ZenConfig::NewWithThreadedUpstreams(TestEnv.GetNewPortNumber(), UpstreamPorts, false);
+ LocalCfg.Args += (" --upstream-thread-count 2");
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ const uint16_t LocalPortNumber = LocalCfg.Port;
+ const auto LocalUri = fmt::format("http://localhost:{}/z$", LocalPortNumber);
+ const auto Upstream1Uri = fmt::format("http://localhost:{}/z$", Upstream1Cfg.Port);
+ const auto Upstream2Uri = fmt::format("http://localhost:{}/z$", Upstream2Cfg.Port);
+
+ bool Upstream1Running = true;
+ bool Upstream2Running = true;
+
+ using namespace std::literals;
+
+ auto AppendCacheRecord = [](cacherequests::PutCacheRecordsRequest& Request,
+ const zen::CacheKey& CacheKey,
+ size_t PayloadSize,
+ CachePolicy RecordPolicy) {
+ std::vector<uint32_t> Data;
+ Data.resize(PayloadSize / 4);
+ for (uint32_t Idx = 0; Idx < PayloadSize / 4; ++Idx)
+ {
+ Data[Idx] = (*reinterpret_cast<const uint32_t*>(&CacheKey.Hash.Hash[0])) + Idx;
+ }
+
+ CompressedBuffer Value = zen::CompressedBuffer::Compress(SharedBuffer::MakeView(Data.data(), Data.size() * 4));
+ Request.Requests.push_back({.Key = CacheKey, .Values = {{.Id = Oid::NewOid(), .Body = std::move(Value)}}, .Policy = RecordPolicy});
+ };
+
+ auto PutCacheRecords = [&AppendCacheRecord](std::string_view BaseUri,
+ std::string_view Namespace,
+ std::string_view Bucket,
+ size_t Num,
+ size_t KeyOffset,
+ size_t PayloadSize = 8192) -> std::vector<CacheKey> {
+ std::vector<zen::CacheKey> OutKeys;
+
+ cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
+ for (size_t Key = 1; Key <= Num; ++Key)
+ {
+ zen::IoHash KeyHash;
+ ((size_t*)(KeyHash.Hash))[0] = KeyOffset + Key;
+ const zen::CacheKey CacheKey = zen::CacheKey::Create(Bucket, KeyHash);
+
+ AppendCacheRecord(Request, CacheKey, PayloadSize, CachePolicy::Default);
+ OutKeys.push_back(CacheKey);
+ }
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+
+ HttpClient Http{BaseUri};
+
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ if (Result.StatusCode != HttpResponseCode::OK)
+ {
+ ZEN_DEBUG("PutCacheRecords failed with {}, reason '{}'", ToString(Result.StatusCode), Result.ErrorMessage(""));
+ OutKeys.clear();
+ }
+
+ return OutKeys;
+ };
+
+ struct GetCacheRecordResult
+ {
+ zen::CbPackage Response;
+ cacherequests::GetCacheRecordsResult Result;
+ bool Success = false;
+ };
+
+ auto GetCacheRecords = [](std::string_view BaseUri,
+ std::string_view Namespace,
+ std::span<zen::CacheKey> Keys,
+ zen::CachePolicy Policy) -> GetCacheRecordResult {
+ cacherequests::GetCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
+ .DefaultPolicy = Policy,
+ .Namespace = std::string(Namespace)};
+ for (const CacheKey& Key : Keys)
+ {
+ Request.Requests.push_back({.Key = Key});
+ }
+
+ CbObjectWriter RequestWriter;
+ CHECK(Request.Format(RequestWriter));
+
+ IoBuffer Body = RequestWriter.Save().GetBuffer().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbObject);
+
+ HttpClient Http{BaseUri};
+
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ GetCacheRecordResult OutResult;
+
+ if (Result.StatusCode == HttpResponseCode::OK)
+ {
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ if (!Response.IsNull())
+ {
+ OutResult.Response = std::move(Response);
+ CHECK(OutResult.Result.Parse(OutResult.Response));
+ OutResult.Success = true;
+ }
+ }
+ else
+ {
+ ZEN_DEBUG("GetCacheRecords with {}, reason '{}'", ToString(Result.StatusCode), Result.ErrorMessage(""));
+ }
+
+ return OutResult;
+ };
+
+ // Populate with some simple data
+
+ CachePolicy Policy = CachePolicy::Default;
+
+ const size_t ThreadCount = 128;
+ const size_t KeyMultiplier = 16384;
+ const size_t RecordsPerRequest = 64;
+ WorkerThreadPool Pool(ThreadCount);
+
+ std::atomic_size_t Completed = 0;
+
+ auto Keys = new std::vector<CacheKey>[ThreadCount * KeyMultiplier];
+ RwLock KeysLock;
+
+ for (size_t I = 0; I < ThreadCount * KeyMultiplier; I++)
+ {
+ size_t Iteration = I;
+ Pool.ScheduleWork(
+ [&] {
+ std::vector<CacheKey> NewKeys =
+ PutCacheRecords(LocalUri, "ue4.ddc"sv, "mastodon"sv, RecordsPerRequest, I * RecordsPerRequest);
+ if (NewKeys.size() != RecordsPerRequest)
+ {
+ ZEN_DEBUG("PutCacheRecords iteration {} failed", Iteration);
+ Completed.fetch_add(1);
+ return;
+ }
+ {
+ RwLock::ExclusiveLockScope _(KeysLock);
+ Keys[Iteration].swap(NewKeys);
+ }
+ Completed.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
+ }
+ bool UseUpstream1 = false;
+ while (Completed < ThreadCount * KeyMultiplier)
+ {
+ Sleep(8000);
+
+ if (UseUpstream1)
+ {
+ if (Upstream2Running)
+ {
+ Upstream2Server.EnableTermination();
+ Upstream2Server.Shutdown();
+ Sleep(100);
+ Upstream2Running = false;
+ }
+ if (!Upstream1Running)
+ {
+ SpawnServer(Upstream1Server, Upstream1Cfg);
+ Upstream1Running = true;
+ }
+ UseUpstream1 = !UseUpstream1;
+ }
+ else
+ {
+ if (Upstream1Running)
+ {
+ Upstream1Server.EnableTermination();
+ Upstream1Server.Shutdown();
+ Sleep(100);
+ Upstream1Running = false;
+ }
+ if (!Upstream2Running)
+ {
+ SpawnServer(Upstream2Server, Upstream2Cfg);
+ Upstream2Running = true;
+ }
+ UseUpstream1 = !UseUpstream1;
+ }
+ }
+
+ Completed = 0;
+ for (size_t I = 0; I < ThreadCount * KeyMultiplier; I++)
+ {
+ size_t Iteration = I;
+ std::vector<CacheKey>& LocalKeys = Keys[Iteration];
+ if (LocalKeys.empty())
+ {
+ Completed.fetch_add(1);
+ continue;
+ }
+ Pool.ScheduleWork(
+ [&] {
+ GetCacheRecordResult Result = GetCacheRecords(LocalUri, "ue4.ddc"sv, LocalKeys, Policy);
+
+ if (!Result.Success)
+ {
+ ZEN_DEBUG("GetCacheRecords iteration {} failed", Iteration);
+ Completed.fetch_add(1);
+ return;
+ }
+
+ if (Result.Result.Results.size() != LocalKeys.size())
+ {
+ ZEN_DEBUG("GetCacheRecords iteration {} empty records", Iteration);
+ Completed.fetch_add(1);
+ return;
+ }
+ for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
+ {
+ const CacheKey& ExpectedKey = LocalKeys[Index++];
+ if (!Record)
+ {
+ continue;
+ }
+ if (Record->Key != ExpectedKey)
+ {
+ continue;
+ }
+ if (Record->Values.size() != 1)
+ {
+ continue;
+ }
+
+ for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
+ {
+ if (!Value.Body)
+ {
+ continue;
+ }
+ }
+ }
+ Completed.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
+ }
+ while (Completed < ThreadCount * KeyMultiplier)
+ {
+ Sleep(10);
+ }
+}
+
+TEST_CASE("zcache.rpc.partialchunks")
+{
+ using namespace std::literals;
+ using namespace utils;
+
+ ZenConfig LocalCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance Server(TestEnv);
+ SpawnServer(Server, LocalCfg);
+
+ std::vector<CompressedBuffer> Attachments;
+
+ const auto BaseUri = fmt::format("http://localhost:{}/z$", Server.GetBasePort());
+
+ auto GenerateKey = [](std::string_view Bucket, size_t KeyIndex) -> CacheKey {
+ IoHash KeyHash;
+ ((size_t*)(KeyHash.Hash))[0] = KeyIndex;
+ return CacheKey::Create(Bucket, KeyHash);
+ };
+
+ auto AppendCacheRecord = [](cacherequests::PutCacheRecordsRequest& Request,
+ const CacheKey& CacheKey,
+ size_t AttachmentCount,
+ size_t AttachmentsSize,
+ CachePolicy RecordPolicy) -> std::vector<std::pair<Oid, CompressedBuffer>> {
+ std::vector<std::pair<Oid, CompressedBuffer>> AttachmentBuffers;
+ std::vector<cacherequests::PutCacheRecordRequestValue> Attachments;
+ for (size_t AttachmentIndex = 0; AttachmentIndex < AttachmentCount; AttachmentIndex++)
+ {
+ CompressedBuffer Value = CreateSemiRandomBlob(AttachmentsSize);
+ AttachmentBuffers.push_back(std::make_pair(Oid::NewOid(), Value));
+ Attachments.push_back({.Id = AttachmentBuffers.back().first, .Body = std::move(Value)});
+ }
+ Request.Requests.push_back({.Key = CacheKey, .Values = Attachments, .Policy = RecordPolicy});
+ return AttachmentBuffers;
+ };
+
+ auto PutCacheRecords = [&AppendCacheRecord, &GenerateKey](
+ std::string_view BaseUri,
+ std::string_view Namespace,
+ std::string_view Bucket,
+ size_t KeyOffset,
+ size_t Num,
+ size_t AttachmentCount,
+ size_t AttachmentsSize =
+ 8192) -> std::vector<std::pair<CacheKey, std::vector<std::pair<Oid, CompressedBuffer>>>> {
+ std::vector<std::pair<CacheKey, std::vector<std::pair<Oid, CompressedBuffer>>>> Keys;
+
+ cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
+ for (size_t Key = 1; Key <= Num; ++Key)
+ {
+ const CacheKey NewCacheKey = GenerateKey(Bucket, KeyOffset + Key);
+ std::vector<std::pair<Oid, CompressedBuffer>> Attachments =
+ AppendCacheRecord(Request, NewCacheKey, AttachmentCount, AttachmentsSize, CachePolicy::Default);
+ Keys.push_back(std::make_pair(NewCacheKey, std::move(Attachments)));
+ }
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+
+ HttpClient Http{BaseUri};
+
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbPackage);
+
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ if (Result.StatusCode != HttpResponseCode::OK)
+ {
+ ZEN_DEBUG("PutCacheRecords failed with {}, reason '{}'", ToString(Result.StatusCode), Result.ErrorMessage(""));
+ Keys.clear();
+ }
+
+ return Keys;
+ };
+
+ std::string_view TestBucket = "partialcachevaluetests"sv;
+ std::string_view TestNamespace = "ue4.ddc"sv;
+ auto RecordsWithSmallAttachments = PutCacheRecords(BaseUri, TestNamespace, TestBucket, 0, 3, 2, 4096u);
+ CHECK(RecordsWithSmallAttachments.size() == 3);
+ auto RecordsWithLargeAttachments = PutCacheRecords(BaseUri, TestNamespace, TestBucket, 10, 1, 2, 8u * 1024u * 1024u);
+ CHECK(RecordsWithLargeAttachments.size() == 1);
+
+ struct PartialOptions
+ {
+ uint64_t Offset = 0ull;
+ uint64_t Size = ~0ull;
+ RpcAcceptOptions AcceptOptions = RpcAcceptOptions::kNone;
+ };
+
+ auto GetCacheChunk = [](std::string_view BaseUri,
+ std::string_view Namespace,
+ const CacheKey& Key,
+ const Oid& ValueId,
+ const PartialOptions& Options = {}) -> cacherequests::GetCacheChunksResult {
+ cacherequests::GetCacheChunksRequest Request = {
+ .AcceptMagic = kCbPkgMagic,
+ .AcceptOptions = (uint16_t)Options.AcceptOptions,
+ .Namespace = std::string(Namespace),
+ .Requests = {{.Key = Key, .ValueId = ValueId, .RawOffset = Options.Offset, .RawSize = Options.Size}}};
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbPackage);
+
+ HttpClient Http{BaseUri};
+
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+
+ CHECK(Result.StatusCode == HttpResponseCode::OK);
+
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ bool Loaded = !Response.IsNull();
+ CHECK_MESSAGE(Loaded, "GetCacheChunks response failed to load.");
+ cacherequests::GetCacheChunksResult GetCacheChunksResult;
+ CHECK(GetCacheChunksResult.Parse(Response));
+ return GetCacheChunksResult;
+ };
+
+ auto GetAndVerifyChunk = [&GetCacheChunk](std::string_view BaseUri,
+ std::string_view Namespace,
+ const CacheKey& Key,
+ const Oid& ChunkId,
+ const CompressedBuffer& VerifyData,
+ const PartialOptions& Options = {}) {
+ cacherequests::GetCacheChunksResult Result = GetCacheChunk(BaseUri, Namespace, Key, ChunkId, Options);
+ CHECK(Result.Results.size() == 1);
+ bool CanGetPartial = ((uint16_t)Options.AcceptOptions & (uint16_t)RpcAcceptOptions::kAllowPartialCacheChunks);
+ if (!CanGetPartial)
+ {
+ CHECK(Result.Results[0].FragmentOffset == 0);
+ CHECK(Result.Results[0].Body.GetCompressedSize() == VerifyData.GetCompressedSize());
+ }
+ IoBuffer SourceDecompressed = VerifyData.Decompress(Options.Offset, Options.Size).AsIoBuffer();
+ IoBuffer ReceivedDecompressed =
+ Result.Results[0].Body.Decompress(Options.Offset - Result.Results[0].FragmentOffset, Options.Size).AsIoBuffer();
+ CHECK(SourceDecompressed.GetView().EqualBytes(ReceivedDecompressed.GetView()));
+ };
+
+ GetAndVerifyChunk(BaseUri,
+ TestNamespace,
+ RecordsWithSmallAttachments[0].first,
+ RecordsWithSmallAttachments[0].second[0].first,
+ RecordsWithSmallAttachments[0].second[0].second);
+ GetAndVerifyChunk(BaseUri,
+ TestNamespace,
+ RecordsWithSmallAttachments[0].first,
+ RecordsWithSmallAttachments[0].second[0].first,
+ RecordsWithSmallAttachments[0].second[0].second,
+ PartialOptions{.Offset = 378, .Size = 519, .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences});
+ GetAndVerifyChunk(
+ BaseUri,
+ TestNamespace,
+ RecordsWithSmallAttachments[0].first,
+ RecordsWithSmallAttachments[0].second[0].first,
+ RecordsWithSmallAttachments[0].second[0].second,
+ PartialOptions{.Offset = 378,
+ .Size = 519,
+ .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialCacheChunks});
+ GetAndVerifyChunk(BaseUri,
+ TestNamespace,
+ RecordsWithLargeAttachments[0].first,
+ RecordsWithLargeAttachments[0].second[0].first,
+ RecordsWithLargeAttachments[0].second[0].second,
+ PartialOptions{.AcceptOptions = RpcAcceptOptions::kAllowLocalReferences});
+ GetAndVerifyChunk(BaseUri,
+ TestNamespace,
+ RecordsWithLargeAttachments[0].first,
+ RecordsWithLargeAttachments[0].second[0].first,
+ RecordsWithLargeAttachments[0].second[0].second,
+ PartialOptions{.Offset = 1024u * 1024u, .Size = 512u * 1024u});
+ GetAndVerifyChunk(
+ BaseUri,
+ TestNamespace,
+ RecordsWithLargeAttachments[0].first,
+ RecordsWithLargeAttachments[0].second[0].first,
+ RecordsWithLargeAttachments[0].second[0].second,
+ PartialOptions{.Offset = 1024u * 1024u, .Size = 512u * 1024u, .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences});
+ GetAndVerifyChunk(
+ BaseUri,
+ TestNamespace,
+ RecordsWithLargeAttachments[0].first,
+ RecordsWithLargeAttachments[0].second[0].first,
+ RecordsWithLargeAttachments[0].second[0].second,
+ PartialOptions{.Offset = 1024u * 1024u, .Size = 512u * 1024u, .AcceptOptions = RpcAcceptOptions::kAllowPartialCacheChunks});
+ GetAndVerifyChunk(
+ BaseUri,
+ TestNamespace,
+ RecordsWithLargeAttachments[0].first,
+ RecordsWithLargeAttachments[0].second[0].first,
+ RecordsWithLargeAttachments[0].second[0].second,
+ PartialOptions{.Offset = 1024u * 1024u,
+ .Size = 512u * 1024u,
+ .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialCacheChunks});
+ GetAndVerifyChunk(
+ BaseUri,
+ TestNamespace,
+ RecordsWithLargeAttachments[0].first,
+ RecordsWithLargeAttachments[0].second[0].first,
+ RecordsWithLargeAttachments[0].second[0].second,
+ PartialOptions{.Offset = 1024u * 1024u,
+ .Size = 512u * 1024u,
+ .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialLocalReferences |
+ RpcAcceptOptions::kAllowPartialCacheChunks});
+}
+
+IoBuffer
+FormatPackageBody(const CbPackage& Package)
+{
+ IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
+ Body.SetContentType(HttpContentType::kCbPackage);
+ return Body;
+}
+
+TEST_CASE("zcache.rpc.allpolicies")
+{
+ using namespace std::literals;
+ using namespace utils;
+
+ ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
+ ZenServerInstance UpstreamServer(TestEnv);
+ SpawnServer(UpstreamServer, UpstreamCfg);
+
+ ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
+ ZenServerInstance LocalServer(TestEnv);
+ SpawnServer(LocalServer, LocalCfg);
+
+ const auto BaseUri = fmt::format("http://localhost:{}/z$", LocalServer.GetBasePort());
+ HttpClient Http{BaseUri};
+
+ std::string_view TestVersion = "F72150A02AE34B57A9EC91D36BA1CE08"sv;
+ std::string_view TestBucket = "allpoliciestest"sv;
+ std::string_view TestNamespace = "ue4.ddc"sv;
+
+ // NumKeys = (2 Value vs Record)*(2 SkipData vs Default)*(2 ForceMiss vs Not)*(2 use local)
+ // *(2 use remote)*(2 UseValue Policy vs not)*(4 cases per type)
+ constexpr int NumKeys = 256;
+ constexpr int NumValues = 4;
+ Oid ValueIds[NumValues];
+ IoHash Hash;
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ ExtendableStringBuilder<16> ValueName;
+ ValueName << "ValueId_"sv << ValueIndex;
+ static_assert(sizeof(IoHash) >= sizeof(Oid));
+ ValueIds[ValueIndex] = Oid::FromMemory(IoHash::HashBuffer(ValueName.Data(), ValueName.Size() * sizeof(ValueName.Data()[0])).Hash);
+ }
+
+ struct KeyData;
+ struct UserData
+ {
+ UserData& Set(KeyData* InKeyData, int InValueIndex)
+ {
+ Data = InKeyData;
+ ValueIndex = InValueIndex;
+ return *this;
+ }
+ KeyData* Data = nullptr;
+ int ValueIndex = 0;
+ };
+ struct KeyData
+ {
+ CompressedBuffer BufferValues[NumValues];
+ uint64_t IntValues[NumValues];
+ UserData ValueUserData[NumValues];
+ bool ReceivedChunk[NumValues];
+ CacheKey Key;
+ UserData KeyUserData;
+ uint32_t KeyIndex = 0;
+ bool GetRequestsData = true;
+ bool UseValueAPI = false;
+ bool UseValuePolicy = false;
+ bool ForceMiss = false;
+ bool UseLocal = true;
+ bool UseRemote = true;
+ bool ShouldBeHit = true;
+ bool ReceivedPut = false;
+ bool ReceivedGet = false;
+ bool ReceivedPutValue = false;
+ bool ReceivedGetValue = false;
+ };
+ struct CachePutRequest
+ {
+ CacheKey Key;
+ CbObject Record;
+ CacheRecordPolicy Policy;
+ KeyData* Values;
+ UserData* Data;
+ };
+ struct CachePutValueRequest
+ {
+ CacheKey Key;
+ CompressedBuffer Value;
+ CachePolicy Policy;
+ UserData* Data;
+ };
+ struct CacheGetRequest
+ {
+ CacheKey Key;
+ CacheRecordPolicy Policy;
+ UserData* Data;
+ };
+ struct CacheGetValueRequest
+ {
+ CacheKey Key;
+ CachePolicy Policy;
+ UserData* Data;
+ };
+ struct CacheGetChunkRequest
+ {
+ CacheKey Key;
+ Oid ValueId;
+ uint64_t RawOffset;
+ uint64_t RawSize;
+ IoHash RawHash;
+ CachePolicy Policy;
+ UserData* Data;
+ };
+
+ KeyData KeyDatas[NumKeys];
+ std::vector<CachePutRequest> PutRequests;
+ std::vector<CachePutValueRequest> PutValueRequests;
+ std::vector<CacheGetRequest> GetRequests;
+ std::vector<CacheGetValueRequest> GetValueRequests;
+ std::vector<CacheGetChunkRequest> ChunkRequests;
+
+ for (uint32_t KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex)
+ {
+ IoHashStream KeyWriter;
+ KeyWriter.Append(TestVersion.data(), TestVersion.length() * sizeof(TestVersion.data()[0]));
+ KeyWriter.Append(&KeyIndex, sizeof(KeyIndex));
+ IoHash KeyHash = KeyWriter.GetHash();
+ KeyData& KeyData = KeyDatas[KeyIndex];
+
+ KeyData.Key = CacheKey::Create(TestBucket, KeyHash);
+ KeyData.KeyIndex = KeyIndex;
+ KeyData.GetRequestsData = (KeyIndex & (1 << 1)) == 0;
+ KeyData.UseValueAPI = (KeyIndex & (1 << 2)) != 0;
+ KeyData.UseValuePolicy = (KeyIndex & (1 << 3)) != 0;
+ KeyData.ForceMiss = (KeyIndex & (1 << 4)) == 0;
+ KeyData.UseLocal = (KeyIndex & (1 << 5)) == 0;
+ KeyData.UseRemote = (KeyIndex & (1 << 6)) == 0;
+ KeyData.ShouldBeHit = !KeyData.ForceMiss && (KeyData.UseLocal || KeyData.UseRemote);
+ CachePolicy SharedPolicy = KeyData.UseLocal ? CachePolicy::Local : CachePolicy::None;
+ SharedPolicy |= KeyData.UseRemote ? CachePolicy::Remote : CachePolicy::None;
+ CachePolicy PutPolicy = SharedPolicy;
+ CachePolicy GetPolicy = SharedPolicy;
+ GetPolicy |= !KeyData.GetRequestsData ? CachePolicy::SkipData : CachePolicy::None;
+ CacheKey& Key = KeyData.Key;
+
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ KeyData.IntValues[ValueIndex] = static_cast<uint64_t>(KeyIndex) | (static_cast<uint64_t>(ValueIndex) << 32);
+ KeyData.BufferValues[ValueIndex] =
+ CompressedBuffer::Compress(SharedBuffer::MakeView(&KeyData.IntValues[ValueIndex], sizeof(KeyData.IntValues[ValueIndex])));
+ KeyData.ReceivedChunk[ValueIndex] = false;
+ }
+
+ UserData& KeyUserData = KeyData.KeyUserData.Set(&KeyData, -1);
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ KeyData.ValueUserData[ValueIndex].Set(&KeyData, ValueIndex);
+ }
+ if (!KeyData.UseValueAPI)
+ {
+ CbObjectWriter Builder;
+ Builder.BeginObject("key"sv);
+ Builder << "Bucket"sv << Key.Bucket << "Hash"sv << Key.Hash;
+ Builder.EndObject();
+ Builder.BeginArray("Values"sv);
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ Builder.BeginObject();
+ Builder.AddObjectId("Id"sv, ValueIds[ValueIndex]);
+ Builder.AddBinaryAttachment("RawHash"sv, KeyData.BufferValues[ValueIndex].DecodeRawHash());
+ Builder.AddInteger("RawSize"sv, KeyData.BufferValues[ValueIndex].DecodeRawSize());
+ Builder.EndObject();
+ }
+ Builder.EndArray();
+
+ CacheRecordPolicy PutRecordPolicy;
+ CacheRecordPolicy GetRecordPolicy;
+ if (!KeyData.UseValuePolicy)
+ {
+ PutRecordPolicy = CacheRecordPolicy(PutPolicy);
+ GetRecordPolicy = CacheRecordPolicy(GetPolicy);
+ }
+ else
+ {
+ // Switch the SkipData field in the Record policy so that if the CacheStore ignores the ValuePolicies
+ // it will use the wrong value for SkipData and fail our tests.
+ CacheRecordPolicyBuilder PutBuilder(PutPolicy ^ CachePolicy::SkipData);
+ CacheRecordPolicyBuilder GetBuilder(GetPolicy ^ CachePolicy::SkipData);
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ PutBuilder.AddValuePolicy(ValueIds[ValueIndex], PutPolicy);
+ GetBuilder.AddValuePolicy(ValueIds[ValueIndex], GetPolicy);
+ }
+ PutRecordPolicy = PutBuilder.Build();
+ GetRecordPolicy = GetBuilder.Build();
+ }
+ if (!KeyData.ForceMiss)
+ {
+ PutRequests.push_back({Key, Builder.Save(), PutRecordPolicy, &KeyData, &KeyUserData});
+ }
+ GetRequests.push_back({Key, GetRecordPolicy, &KeyUserData});
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ UserData& ValueUserData = KeyData.ValueUserData[ValueIndex];
+ ChunkRequests.push_back({Key, ValueIds[ValueIndex], 0, UINT64_MAX, IoHash(), GetPolicy, &ValueUserData});
+ }
+ }
+ else
+ {
+ if (!KeyData.ForceMiss)
+ {
+ PutValueRequests.push_back({Key, KeyData.BufferValues[0], PutPolicy, &KeyUserData});
+ }
+ GetValueRequests.push_back({Key, GetPolicy, &KeyUserData});
+ ChunkRequests.push_back({Key, Oid::Zero, 0, UINT64_MAX, IoHash(), GetPolicy, &KeyUserData});
+ }
+ }
+
+ // PutCacheRecords
+ {
+ CachePolicy BatchDefaultPolicy = CachePolicy::Default;
+ cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
+ .DefaultPolicy = BatchDefaultPolicy,
+ .Namespace = std::string(TestNamespace)};
+ Request.Requests.reserve(PutRequests.size());
+ for (CachePutRequest& PutRequest : PutRequests)
+ {
+ cacherequests::PutCacheRecordRequest& RecordRequest = Request.Requests.emplace_back();
+ RecordRequest.Key = PutRequest.Key;
+ RecordRequest.Policy = PutRequest.Policy;
+ RecordRequest.Values.reserve(NumValues);
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ RecordRequest.Values.push_back({.Id = ValueIds[ValueIndex], .Body = PutRequest.Values->BufferValues[ValueIndex]});
+ }
+ PutRequest.Data->Data->ReceivedPut = true;
+ }
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+ IoBuffer Body = FormatPackageBody(Package);
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK_MESSAGE(Result.StatusCode == HttpResponseCode::OK, "PutCacheRecords unexpectedly failed.");
+ }
+
+ // PutCacheValues
+ {
+ CachePolicy BatchDefaultPolicy = CachePolicy::Default;
+
+ cacherequests::PutCacheValuesRequest Request = {.AcceptMagic = kCbPkgMagic,
+ .DefaultPolicy = BatchDefaultPolicy,
+ .Namespace = std::string(TestNamespace)};
+ Request.Requests.reserve(PutValueRequests.size());
+ for (CachePutValueRequest& PutRequest : PutValueRequests)
+ {
+ Request.Requests.push_back({.Key = PutRequest.Key, .Body = PutRequest.Value, .Policy = PutRequest.Policy});
+ PutRequest.Data->Data->ReceivedPutValue = true;
+ }
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+
+ IoBuffer Body = FormatPackageBody(Package);
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK_MESSAGE(Result.StatusCode == HttpResponseCode::OK, "PutCacheValues unexpectedly failed.");
+ }
+
+ for (KeyData& KeyData : KeyDatas)
+ {
+ if (!KeyData.ForceMiss)
+ {
+ if (!KeyData.UseValueAPI)
+ {
+ CHECK_MESSAGE(KeyData.ReceivedPut, WriteToString<32>("Key ", KeyData.KeyIndex, " was unexpectedly not put.").c_str());
+ }
+ else
+ {
+ CHECK_MESSAGE(KeyData.ReceivedPutValue,
+ WriteToString<32>("Key ", KeyData.KeyIndex, " was unexpectedly not put to ValueAPI.").c_str());
+ }
+ }
+ }
+
+ // GetCacheRecords
+ {
+ CachePolicy BatchDefaultPolicy = CachePolicy::Default;
+ cacherequests::GetCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
+ .DefaultPolicy = BatchDefaultPolicy,
+ .Namespace = std::string(TestNamespace)};
+ Request.Requests.reserve(GetRequests.size());
+ for (CacheGetRequest& GetRequest : GetRequests)
+ {
+ Request.Requests.push_back({.Key = GetRequest.Key, .Policy = GetRequest.Policy});
+ }
+
+ CbPackage Package;
+ CHECK(Request.Format(Package));
+ IoBuffer Body = FormatPackageBody(Package);
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK_MESSAGE(Result.StatusCode == HttpResponseCode::OK, "GetCacheRecords unexpectedly failed.");
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ bool Loaded = !Response.IsNull();
+ CHECK_MESSAGE(Loaded, "GetCacheRecords response failed to load.");
+ cacherequests::GetCacheRecordsResult RequestResult;
+ CHECK(RequestResult.Parse(Response));
+ CHECK_MESSAGE(RequestResult.Results.size() == GetRequests.size(), "GetCacheRecords response count did not match request count.");
+ for (int Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& RecordResult : RequestResult.Results)
+ {
+ bool Succeeded = RecordResult.has_value();
+ CacheGetRequest& GetRequest = GetRequests[Index++];
+ KeyData* KeyData = GetRequest.Data->Data;
+ KeyData->ReceivedGet = true;
+ WriteToString<32> Name("Get(", KeyData->KeyIndex, ")");
+ if (KeyData->ShouldBeHit)
+ {
+ CHECK_MESSAGE(Succeeded, WriteToString<32>(Name, " unexpectedly failed.").c_str());
+ }
+ else if (KeyData->ForceMiss)
+ {
+ CHECK_MESSAGE(!Succeeded, WriteToString<32>(Name, " unexpectedly succeeded.").c_str());
+ }
+ if (!KeyData->ForceMiss && Succeeded)
+ {
+ CHECK_MESSAGE(RecordResult->Values.size() == NumValues,
+ WriteToString<32>(Name, " number of values did not match.").c_str());
+ for (const cacherequests::GetCacheRecordResultValue& Value : RecordResult->Values)
+ {
+ int ExpectedValueIndex = 0;
+ for (; ExpectedValueIndex < NumValues; ++ExpectedValueIndex)
+ {
+ if (ValueIds[ExpectedValueIndex] == Value.Id)
+ {
+ break;
+ }
+ }
+ CHECK_MESSAGE(ExpectedValueIndex < NumValues, WriteToString<32>(Name, " could not find matching ValueId.").c_str());
+
+ WriteToString<32> ValueName("Get(", KeyData->KeyIndex, ",", ExpectedValueIndex, ")");
+
+ CompressedBuffer ExpectedValue = KeyData->BufferValues[ExpectedValueIndex];
+ CHECK_MESSAGE(Value.RawHash == ExpectedValue.DecodeRawHash(),
+ WriteToString<32>(ValueName, " RawHash did not match.").c_str());
+ CHECK_MESSAGE(Value.RawSize == ExpectedValue.DecodeRawSize(),
+ WriteToString<32>(ValueName, " RawSize did not match.").c_str());
+
+ if (KeyData->GetRequestsData)
+ {
+ SharedBuffer Buffer = Value.Body.Decompress();
+ CHECK_MESSAGE(Buffer.GetSize() == Value.RawSize,
+ WriteToString<32>(ValueName, " BufferSize did not match RawSize.").c_str());
+ uint64_t ActualIntValue = ((const uint64_t*)Buffer.GetData())[0];
+ uint64_t ExpectedIntValue = KeyData->IntValues[ExpectedValueIndex];
+ CHECK_MESSAGE(ActualIntValue == ExpectedIntValue, WriteToString<32>(ValueName, " had unexpected data.").c_str());
+ }
+ }
+ }
+ }
+ }
+
+ // GetCacheValues
+ {
+ CachePolicy BatchDefaultPolicy = CachePolicy::Default;
+
+ cacherequests::GetCacheValuesRequest GetCacheValuesRequest = {.AcceptMagic = kCbPkgMagic,
+ .DefaultPolicy = BatchDefaultPolicy,
+ .Namespace = std::string(TestNamespace)};
+ GetCacheValuesRequest.Requests.reserve(GetValueRequests.size());
+ for (CacheGetValueRequest& GetRequest : GetValueRequests)
+ {
+ GetCacheValuesRequest.Requests.push_back({.Key = GetRequest.Key, .Policy = GetRequest.Policy});
+ }
+
+ CbPackage Package;
+ CHECK(GetCacheValuesRequest.Format(Package));
+
+ IoBuffer Body = FormatPackageBody(Package);
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK_MESSAGE(Result.StatusCode == HttpResponseCode::OK, "GetCacheValues unexpectedly failed.");
+ IoBuffer MessageBuffer(Result.ResponsePayload);
+ CbPackage Response = ParsePackageMessage(MessageBuffer);
+ bool Loaded = !Response.IsNull();
+ CHECK_MESSAGE(Loaded, "GetCacheValues response failed to load.");
+ cacherequests::GetCacheValuesResult GetCacheValuesResult;
+ CHECK(GetCacheValuesResult.Parse(Response));
+ for (int Index = 0; const cacherequests::CacheValueResult& ValueResult : GetCacheValuesResult.Results)
+ {
+ bool Succeeded = ValueResult.RawHash != IoHash::Zero;
+ CacheGetValueRequest& Request = GetValueRequests[Index++];
+ KeyData* KeyData = Request.Data->Data;
+ KeyData->ReceivedGetValue = true;
+ WriteToString<32> Name("GetValue("sv, KeyData->KeyIndex, ")"sv);
+
+ if (KeyData->ShouldBeHit)
+ {
+ CHECK_MESSAGE(Succeeded, WriteToString<32>(Name, " unexpectedly failed.").c_str());
+ }
+ else if (KeyData->ForceMiss)
+ {
+ CHECK_MESSAGE(!Succeeded, WriteToString<32>(Name, "unexpectedly succeeded.").c_str());
+ }
+ if (!KeyData->ForceMiss && Succeeded)
+ {
+ CompressedBuffer ExpectedValue = KeyData->BufferValues[0];
+ CHECK_MESSAGE(ValueResult.RawHash == ExpectedValue.DecodeRawHash(),
+ WriteToString<32>(Name, " RawHash did not match.").c_str());
+ CHECK_MESSAGE(ValueResult.RawSize == ExpectedValue.DecodeRawSize(),
+ WriteToString<32>(Name, " RawSize did not match.").c_str());
+
+ if (KeyData->GetRequestsData)
+ {
+ SharedBuffer Buffer = ValueResult.Body.Decompress();
+ CHECK_MESSAGE(Buffer.GetSize() == ValueResult.RawSize,
+ WriteToString<32>(Name, " BufferSize did not match RawSize.").c_str());
+ uint64_t ActualIntValue = ((const uint64_t*)Buffer.GetData())[0];
+ uint64_t ExpectedIntValue = KeyData->IntValues[0];
+ CHECK_MESSAGE(ActualIntValue == ExpectedIntValue, WriteToString<32>(Name, " had unexpected data.").c_str());
+ }
+ }
+ }
+ }
+
+ // GetCacheChunks
+ {
+ std::sort(ChunkRequests.begin(), ChunkRequests.end(), [](CacheGetChunkRequest& A, CacheGetChunkRequest& B) {
+ return A.Key.Hash < B.Key.Hash;
+ });
+ CachePolicy BatchDefaultPolicy = CachePolicy::Default;
+ cacherequests::GetCacheChunksRequest GetCacheChunksRequest = {.AcceptMagic = kCbPkgMagic,
+ .DefaultPolicy = BatchDefaultPolicy,
+ .Namespace = std::string(TestNamespace)};
+ GetCacheChunksRequest.Requests.reserve(ChunkRequests.size());
+ for (CacheGetChunkRequest& ChunkRequest : ChunkRequests)
+ {
+ GetCacheChunksRequest.Requests.push_back({.Key = ChunkRequest.Key,
+ .ValueId = ChunkRequest.ValueId,
+ .ChunkId = IoHash(),
+ .RawOffset = ChunkRequest.RawOffset,
+ .RawSize = ChunkRequest.RawSize,
+ .Policy = ChunkRequest.Policy});
+ }
+ CbPackage Package;
+ CHECK(GetCacheChunksRequest.Format(Package));
+
+ IoBuffer Body = FormatPackageBody(Package);
+ HttpClient::Response Result = Http.Post("/$rpc", Body, {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK_MESSAGE(Result.StatusCode == HttpResponseCode::OK, "GetCacheChunks unexpectedly failed.");
+ CbPackage Response = ParsePackageMessage(Result.ResponsePayload);
+ bool Loaded = !Response.IsNull();
+ CHECK_MESSAGE(Loaded, "GetCacheChunks response failed to load.");
+ cacherequests::GetCacheChunksResult GetCacheChunksResult;
+ CHECK(GetCacheChunksResult.Parse(Response));
+ CHECK_MESSAGE(GetCacheChunksResult.Results.size() == ChunkRequests.size(),
+ "GetCacheChunks response count did not match request count.");
+
+ for (int Index = 0; const cacherequests::CacheValueResult& ValueResult : GetCacheChunksResult.Results)
+ {
+ bool Succeeded = ValueResult.RawHash != IoHash::Zero;
+
+ CacheGetChunkRequest& Request = ChunkRequests[Index++];
+ KeyData* KeyData = Request.Data->Data;
+ int ValueIndex = Request.Data->ValueIndex >= 0 ? Request.Data->ValueIndex : 0;
+ KeyData->ReceivedChunk[ValueIndex] = true;
+ WriteToString<32> Name("GetChunks("sv, KeyData->KeyIndex, ","sv, ValueIndex, ")"sv);
+
+ if (KeyData->ShouldBeHit)
+ {
+ CHECK_MESSAGE(Succeeded, WriteToString<256>(Name, " unexpectedly failed."sv).c_str());
+ }
+ else if (KeyData->ForceMiss)
+ {
+ CHECK_MESSAGE(!Succeeded, WriteToString<256>(Name, " unexpectedly succeeded."sv).c_str());
+ }
+ if (KeyData->ShouldBeHit && Succeeded)
+ {
+ CompressedBuffer ExpectedValue = KeyData->BufferValues[ValueIndex];
+ CHECK_MESSAGE(ValueResult.RawHash == ExpectedValue.DecodeRawHash(),
+ WriteToString<32>(Name, " had unexpected RawHash.").c_str());
+ CHECK_MESSAGE(ValueResult.RawSize == ExpectedValue.DecodeRawSize(),
+ WriteToString<32>(Name, " had unexpected RawSize.").c_str());
+
+ if (KeyData->GetRequestsData)
+ {
+ SharedBuffer Buffer = ValueResult.Body.Decompress();
+ CHECK_MESSAGE(Buffer.GetSize() == ValueResult.RawSize,
+ WriteToString<32>(Name, " BufferSize did not match RawSize.").c_str());
+ uint64_t ActualIntValue = ((const uint64_t*)Buffer.GetData())[0];
+ uint64_t ExpectedIntValue = KeyData->IntValues[ValueIndex];
+ CHECK_MESSAGE(ActualIntValue == ExpectedIntValue, WriteToString<32>(Name, " had unexpected data.").c_str());
+ }
+ }
+ }
+ }
+
+ for (KeyData& KeyData : KeyDatas)
+ {
+ if (!KeyData.UseValueAPI)
+ {
+ CHECK_MESSAGE(KeyData.ReceivedGet, WriteToString<32>("Get(", KeyData.KeyIndex, ") was unexpectedly not received.").c_str());
+ for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
+ {
+ CHECK_MESSAGE(
+ KeyData.ReceivedChunk[ValueIndex],
+ WriteToString<32>("GetChunks(", KeyData.KeyIndex, ",", ValueIndex, ") was unexpectedly not received.").c_str());
+ }
+ }
+ else
+ {
+ CHECK_MESSAGE(KeyData.ReceivedGetValue,
+ WriteToString<32>("GetValue(", KeyData.KeyIndex, ") was unexpectedly not received.").c_str());
+ CHECK_MESSAGE(KeyData.ReceivedChunk[0],
+ WriteToString<32>("GetChunks(", KeyData.KeyIndex, ") was unexpectedly not received.").c_str());
+ }
+ }
+}
+
+} // namespace zen::tests
+
+#endif
diff --git a/src/zenutil/cache/cacherequests.cpp b/src/zenserver-test/cacherequests.cpp
index 7c6f493f2..46339aebb 100644
--- a/src/zenutil/cache/cacherequests.cpp
+++ b/src/zenserver-test/cacherequests.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/cache/cacherequests.h>
+#include "cacherequests.h"
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
@@ -8,6 +8,7 @@
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/zencore.h>
+#include <zenstore/cache/cache.h>
#include <string>
#include <string_view>
@@ -17,124 +18,13 @@
# include <zencore/testing.h>
#endif
-namespace zen {
-
-namespace cacherequests {
-
- namespace {
- constinit AsciiSet ValidNamespaceNameCharactersSet{"abcdefghijklmnopqrstuvwxyz0123456789-_.ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
- constinit AsciiSet ValidBucketNameCharactersSet{"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
- } // namespace
-
- std::optional<std::string> GetValidNamespaceName(std::string_view Name)
- {
- if (Name.empty())
- {
- ZEN_WARN("Namespace is invalid, empty namespace is not allowed");
- return {};
- }
-
- if (Name.length() > 64)
- {
- ZEN_WARN("Namespace '{}' is invalid, length exceeds 64 characters", Name);
- return {};
- }
-
- if (!AsciiSet::HasOnly(Name, ValidNamespaceNameCharactersSet))
- {
- ZEN_WARN("Namespace '{}' is invalid, invalid characters detected", Name);
- return {};
- }
-
- return ToLower(Name);
- }
-
- std::optional<std::string> GetValidBucketName(std::string_view Name)
- {
- if (Name.empty())
- {
- ZEN_WARN("Bucket name is invalid, empty bucket name is not allowed");
- return {};
- }
-
- if (!AsciiSet::HasOnly(Name, ValidBucketNameCharactersSet))
- {
- ZEN_WARN("Bucket name '{}' is invalid, invalid characters detected", Name);
- return {};
- }
-
- return ToLower(Name);
- }
-
- std::optional<IoHash> GetValidIoHash(std::string_view Hash)
- {
- if (Hash.length() != IoHash::StringLength)
- {
- return {};
- }
-
- IoHash KeyHash;
- if (!ParseHexBytes(Hash.data(), Hash.size(), KeyHash.Hash))
- {
- return {};
- }
- return KeyHash;
- }
+namespace zen { namespace cacherequests {
std::optional<CacheRecordPolicy> Convert(const OptionalCacheRecordPolicy& Policy)
{
return Policy.IsValid() ? Policy.Get() : std::optional<CacheRecordPolicy>{};
};
- std::optional<std::string> GetRequestNamespace(const CbObjectView& Params)
- {
- CbFieldView NamespaceField = Params["Namespace"];
- if (!NamespaceField)
- {
- return std::string("!default!"); // ZenCacheStore::DefaultNamespace);
- }
-
- if (NamespaceField.HasError())
- {
- return {};
- }
- if (!NamespaceField.IsString())
- {
- return {};
- }
- return GetValidNamespaceName(NamespaceField.AsString());
- }
-
- bool GetRequestCacheKey(const CbObjectView& KeyView, CacheKey& Key)
- {
- CbFieldView BucketField = KeyView["Bucket"];
- if (BucketField.HasError())
- {
- return false;
- }
- if (!BucketField.IsString())
- {
- return false;
- }
- std::optional<std::string> Bucket = GetValidBucketName(BucketField.AsString());
- if (!Bucket.has_value())
- {
- return false;
- }
- CbFieldView HashField = KeyView["Hash"];
- if (HashField.HasError())
- {
- return false;
- }
- if (!HashField.IsHash())
- {
- return false;
- }
- Key.Bucket = *Bucket;
- Key.Hash = HashField.AsHash();
- return true;
- }
-
void WriteCacheRequestKey(CbObjectWriter& Writer, const CacheKey& Value)
{
Writer.BeginObject("Key");
@@ -179,7 +69,7 @@ namespace cacherequests {
AcceptMagic = BatchObject["AcceptType"].AsUInt32(0);
CbObjectView Params = BatchObject["Params"].AsObjectView();
- std::optional<std::string> RequestNamespace = GetRequestNamespace(Params);
+ std::optional<std::string> RequestNamespace = GetCacheRequestNamespace(Params);
if (!RequestNamespace)
{
return false;
@@ -197,7 +87,7 @@ namespace cacherequests {
PutCacheRecordRequest& Request = Requests[RequestIndex++];
- if (!GetRequestCacheKey(KeyView, Request.Key))
+ if (!GetCacheRequestCacheKey(KeyView, Request.Key))
{
return false;
}
@@ -313,6 +203,17 @@ namespace cacherequests {
Success.push_back(It.AsBool());
It++;
}
+
+ CbArrayView DetailsArray = Package.GetObject()["Details"].AsArrayView();
+ if (DetailsArray)
+ {
+ It = DetailsArray.CreateViewIterator();
+ while (It.HasValue())
+ {
+ Details.push_back(It.AsObjectView());
+ It++;
+ }
+ }
return true;
}
@@ -325,7 +226,15 @@ namespace cacherequests {
ResponseObject.AddBool(Value);
}
ResponseObject.EndArray();
-
+ if (!Details.empty())
+ {
+ ResponseObject.BeginArray("Details");
+ for (CbObjectView Value : Details)
+ {
+ ResponseObject.AddObject(Value);
+ }
+ ResponseObject.EndArray();
+ }
OutPackage.SetObject(ResponseObject.Save());
return true;
}
@@ -338,7 +247,7 @@ namespace cacherequests {
ProcessPid = RpcRequest["Pid"].AsInt32(0);
CbObjectView Params = RpcRequest["Params"].AsObjectView();
- std::optional<std::string> RequestNamespace = GetRequestNamespace(Params);
+ std::optional<std::string> RequestNamespace = GetCacheRequestNamespace(Params);
if (!RequestNamespace)
{
return false;
@@ -356,7 +265,7 @@ namespace cacherequests {
GetCacheRecordRequest& Request = Requests.emplace_back();
- if (!GetRequestCacheKey(KeyObject, Request.Key))
+ if (!GetCacheRequestCacheKey(KeyObject, Request.Key))
{
return false;
}
@@ -464,7 +373,7 @@ namespace cacherequests {
GetCacheRecordResult& Request = Results[ResultIndex].value();
CbObjectView RecordObject = RecordView.AsObjectView();
CbObjectView KeyObject = RecordObject["Key"].AsObjectView();
- if (!GetRequestCacheKey(KeyObject, Request.Key))
+ if (!GetCacheRequestCacheKey(KeyObject, Request.Key))
{
return false;
}
@@ -541,7 +450,7 @@ namespace cacherequests {
AcceptMagic = BatchObject["AcceptType"].AsUInt32(0);
CbObjectView Params = BatchObject["Params"].AsObjectView();
- std::optional<std::string> RequestNamespace = cacherequests::GetRequestNamespace(Params);
+ std::optional<std::string> RequestNamespace = GetCacheRequestNamespace(Params);
if (!RequestNamespace)
{
return false;
@@ -559,7 +468,7 @@ namespace cacherequests {
PutCacheValueRequest& Request = Requests.emplace_back();
- if (!GetRequestCacheKey(KeyObject, Request.Key))
+ if (!GetCacheRequestCacheKey(KeyObject, Request.Key))
{
return false;
}
@@ -672,7 +581,7 @@ namespace cacherequests {
ProcessPid = BatchObject["Pid"].AsInt32(0);
CbObjectView Params = BatchObject["Params"].AsObjectView();
- std::optional<std::string> RequestNamespace = cacherequests::GetRequestNamespace(Params);
+ std::optional<std::string> RequestNamespace = GetCacheRequestNamespace(Params);
if (!RequestNamespace)
{
return false;
@@ -690,7 +599,7 @@ namespace cacherequests {
GetCacheValueRequest& Request = Requests.emplace_back();
- if (!GetRequestCacheKey(KeyObject, Request.Key))
+ if (!GetCacheRequestCacheKey(KeyObject, Request.Key))
{
return false;
}
@@ -846,7 +755,7 @@ namespace cacherequests {
ProcessPid = BatchObject["Pid"].AsInt32(0);
CbObjectView Params = BatchObject["Params"].AsObjectView();
- std::optional<std::string> RequestNamespace = cacherequests::GetRequestNamespace(Params);
+ std::optional<std::string> RequestNamespace = GetCacheRequestNamespace(Params);
if (!RequestNamespace)
{
return false;
@@ -864,7 +773,7 @@ namespace cacherequests {
GetCacheChunkRequest& Request = Requests.emplace_back();
- if (!GetRequestCacheKey(KeyObject, Request.Key))
+ if (!GetCacheRequestCacheKey(KeyObject, Request.Key))
{
return false;
}
@@ -926,122 +835,11 @@ namespace cacherequests {
return true;
}
- bool HttpRequestParseRelativeUri(std::string_view Key, std::string_view DefaultNamespace, HttpRequestData& Data)
- {
- std::vector<std::string_view> Tokens;
- uint32_t TokenCount = ForEachStrTok(Key, '/', [&](const std::string_view& Token) {
- Tokens.push_back(Token);
- return true;
- });
-
- switch (TokenCount)
- {
- case 0:
- return true;
- case 1:
- Data.Namespace = GetValidNamespaceName(Tokens[0]);
- return Data.Namespace.has_value();
- case 2:
- {
- std::optional<IoHash> PossibleHashKey = GetValidIoHash(Tokens[1]);
- if (PossibleHashKey.has_value())
- {
- // Legacy bucket/key request
- Data.Bucket = GetValidBucketName(Tokens[0]);
- if (!Data.Bucket.has_value())
- {
- return false;
- }
- Data.HashKey = PossibleHashKey;
- Data.Namespace = DefaultNamespace;
- return true;
- }
- Data.Namespace = GetValidNamespaceName(Tokens[0]);
- if (!Data.Namespace.has_value())
- {
- return false;
- }
- Data.Bucket = GetValidBucketName(Tokens[1]);
- if (!Data.Bucket.has_value())
- {
- return false;
- }
- return true;
- }
- case 3:
- {
- std::optional<IoHash> PossibleHashKey = GetValidIoHash(Tokens[1]);
- if (PossibleHashKey.has_value())
- {
- // Legacy bucket/key/valueid request
- Data.Bucket = GetValidBucketName(Tokens[0]);
- if (!Data.Bucket.has_value())
- {
- return false;
- }
- Data.HashKey = PossibleHashKey;
- Data.ValueContentId = GetValidIoHash(Tokens[2]);
- if (!Data.ValueContentId.has_value())
- {
- return false;
- }
- Data.Namespace = DefaultNamespace;
- return true;
- }
- Data.Namespace = GetValidNamespaceName(Tokens[0]);
- if (!Data.Namespace.has_value())
- {
- return false;
- }
- Data.Bucket = GetValidBucketName(Tokens[1]);
- if (!Data.Bucket.has_value())
- {
- return false;
- }
- Data.HashKey = GetValidIoHash(Tokens[2]);
- if (!Data.HashKey)
- {
- return false;
- }
- return true;
- }
- case 4:
- {
- Data.Namespace = GetValidNamespaceName(Tokens[0]);
- if (!Data.Namespace.has_value())
- {
- return false;
- }
-
- Data.Bucket = GetValidBucketName(Tokens[1]);
- if (!Data.Bucket.has_value())
- {
- return false;
- }
-
- Data.HashKey = GetValidIoHash(Tokens[2]);
- if (!Data.HashKey.has_value())
- {
- return false;
- }
-
- Data.ValueContentId = GetValidIoHash(Tokens[3]);
- if (!Data.ValueContentId.has_value())
- {
- return false;
- }
- return true;
- }
- default:
- return false;
- }
- }
-
// bool CacheRecord::Parse(CbObjectView& Reader)
// {
// CbObjectView KeyView = Reader["Key"].AsObjectView();
//
- // if (!GetRequestCacheKey(KeyView, Key))
+ // if (!GetCacheRequestCacheKey(KeyView, Key))
// {
// return false;
// }
@@ -1563,80 +1361,80 @@ namespace cacherequests {
{
using namespace std::literals;
- HttpRequestData RootRequest;
- CHECK(HttpRequestParseRelativeUri("", "!default!", RootRequest));
+ HttpCacheRequestData RootRequest;
+ CHECK(HttpCacheRequestParseRelativeUri("", "!default!", RootRequest));
CHECK(!RootRequest.Namespace.has_value());
CHECK(!RootRequest.Bucket.has_value());
CHECK(!RootRequest.HashKey.has_value());
CHECK(!RootRequest.ValueContentId.has_value());
RootRequest = {};
- CHECK(HttpRequestParseRelativeUri("/", "!default!", RootRequest));
+ CHECK(HttpCacheRequestParseRelativeUri("/", "!default!", RootRequest));
CHECK(!RootRequest.Namespace.has_value());
CHECK(!RootRequest.Bucket.has_value());
CHECK(!RootRequest.HashKey.has_value());
CHECK(!RootRequest.ValueContentId.has_value());
- HttpRequestData LegacyBucketRequestBecomesNamespaceRequest;
- CHECK(HttpRequestParseRelativeUri("test", "!default!", LegacyBucketRequestBecomesNamespaceRequest));
+ HttpCacheRequestData LegacyBucketRequestBecomesNamespaceRequest;
+ CHECK(HttpCacheRequestParseRelativeUri("test", "!default!", LegacyBucketRequestBecomesNamespaceRequest));
CHECK(LegacyBucketRequestBecomesNamespaceRequest.Namespace == "test"sv);
CHECK(!LegacyBucketRequestBecomesNamespaceRequest.Bucket.has_value());
CHECK(!LegacyBucketRequestBecomesNamespaceRequest.HashKey.has_value());
CHECK(!LegacyBucketRequestBecomesNamespaceRequest.ValueContentId.has_value());
- HttpRequestData LegacyHashKeyRequest;
- CHECK(HttpRequestParseRelativeUri("test/0123456789abcdef12340123456789abcdef1234", "!default!", LegacyHashKeyRequest));
+ HttpCacheRequestData LegacyHashKeyRequest;
+ CHECK(HttpCacheRequestParseRelativeUri("test/0123456789abcdef12340123456789abcdef1234", "!default!", LegacyHashKeyRequest));
CHECK(LegacyHashKeyRequest.Namespace == "!default!");
CHECK(LegacyHashKeyRequest.Bucket == "test"sv);
CHECK(LegacyHashKeyRequest.HashKey == IoHash::FromHexString("0123456789abcdef12340123456789abcdef1234"sv));
CHECK(!LegacyHashKeyRequest.ValueContentId.has_value());
- HttpRequestData LegacyValueContentIdRequest;
- CHECK(HttpRequestParseRelativeUri("test/0123456789abcdef12340123456789abcdef1234/56789abcdef12345678956789abcdef123456789",
- "!default!",
- LegacyValueContentIdRequest));
+ HttpCacheRequestData LegacyValueContentIdRequest;
+ CHECK(HttpCacheRequestParseRelativeUri("test/0123456789abcdef12340123456789abcdef1234/56789abcdef12345678956789abcdef123456789",
+ "!default!",
+ LegacyValueContentIdRequest));
CHECK(LegacyValueContentIdRequest.Namespace == "!default!");
CHECK(LegacyValueContentIdRequest.Bucket == "test"sv);
CHECK(LegacyValueContentIdRequest.HashKey == IoHash::FromHexString("0123456789abcdef12340123456789abcdef1234"sv));
CHECK(LegacyValueContentIdRequest.ValueContentId == IoHash::FromHexString("56789abcdef12345678956789abcdef123456789"sv));
- HttpRequestData V2DefaultNamespaceRequest;
- CHECK(HttpRequestParseRelativeUri("ue4.ddc", "!default!", V2DefaultNamespaceRequest));
+ HttpCacheRequestData V2DefaultNamespaceRequest;
+ CHECK(HttpCacheRequestParseRelativeUri("ue4.ddc", "!default!", V2DefaultNamespaceRequest));
CHECK(V2DefaultNamespaceRequest.Namespace == "ue4.ddc");
CHECK(!V2DefaultNamespaceRequest.Bucket.has_value());
CHECK(!V2DefaultNamespaceRequest.HashKey.has_value());
CHECK(!V2DefaultNamespaceRequest.ValueContentId.has_value());
- HttpRequestData V2NamespaceRequest;
- CHECK(HttpRequestParseRelativeUri("nicenamespace", "!default!", V2NamespaceRequest));
+ HttpCacheRequestData V2NamespaceRequest;
+ CHECK(HttpCacheRequestParseRelativeUri("nicenamespace", "!default!", V2NamespaceRequest));
CHECK(V2NamespaceRequest.Namespace == "nicenamespace"sv);
CHECK(!V2NamespaceRequest.Bucket.has_value());
CHECK(!V2NamespaceRequest.HashKey.has_value());
CHECK(!V2NamespaceRequest.ValueContentId.has_value());
- HttpRequestData V2BucketRequestWithDefaultNamespace;
- CHECK(HttpRequestParseRelativeUri("ue4.ddc/test", "!default!", V2BucketRequestWithDefaultNamespace));
+ HttpCacheRequestData V2BucketRequestWithDefaultNamespace;
+ CHECK(HttpCacheRequestParseRelativeUri("ue4.ddc/test", "!default!", V2BucketRequestWithDefaultNamespace));
CHECK(V2BucketRequestWithDefaultNamespace.Namespace == "ue4.ddc");
CHECK(V2BucketRequestWithDefaultNamespace.Bucket == "test"sv);
CHECK(!V2BucketRequestWithDefaultNamespace.HashKey.has_value());
CHECK(!V2BucketRequestWithDefaultNamespace.ValueContentId.has_value());
- HttpRequestData V2BucketRequestWithNamespace;
- CHECK(HttpRequestParseRelativeUri("nicenamespace/test", "!default!", V2BucketRequestWithNamespace));
+ HttpCacheRequestData V2BucketRequestWithNamespace;
+ CHECK(HttpCacheRequestParseRelativeUri("nicenamespace/test", "!default!", V2BucketRequestWithNamespace));
CHECK(V2BucketRequestWithNamespace.Namespace == "nicenamespace"sv);
CHECK(V2BucketRequestWithNamespace.Bucket == "test"sv);
CHECK(!V2BucketRequestWithNamespace.HashKey.has_value());
CHECK(!V2BucketRequestWithNamespace.ValueContentId.has_value());
- HttpRequestData V2HashKeyRequest;
- CHECK(HttpRequestParseRelativeUri("test/0123456789abcdef12340123456789abcdef1234", "!default!", V2HashKeyRequest));
+ HttpCacheRequestData V2HashKeyRequest;
+ CHECK(HttpCacheRequestParseRelativeUri("test/0123456789abcdef12340123456789abcdef1234", "!default!", V2HashKeyRequest));
CHECK(V2HashKeyRequest.Namespace == "!default!");
CHECK(V2HashKeyRequest.Bucket == "test");
CHECK(V2HashKeyRequest.HashKey == IoHash::FromHexString("0123456789abcdef12340123456789abcdef1234"sv));
CHECK(!V2HashKeyRequest.ValueContentId.has_value());
- HttpRequestData V2ValueContentIdRequest;
- CHECK(HttpRequestParseRelativeUri(
+ HttpCacheRequestData V2ValueContentIdRequest;
+ CHECK(HttpCacheRequestParseRelativeUri(
"nicenamespace/test/0123456789abcdef12340123456789abcdef1234/56789abcdef12345678956789abcdef123456789",
"!default!",
V2ValueContentIdRequest));
@@ -1645,26 +1443,20 @@ namespace cacherequests {
CHECK(V2ValueContentIdRequest.HashKey == IoHash::FromHexString("0123456789abcdef12340123456789abcdef1234"sv));
CHECK(V2ValueContentIdRequest.ValueContentId == IoHash::FromHexString("56789abcdef12345678956789abcdef123456789"sv));
- HttpRequestData Invalid;
- CHECK(!HttpRequestParseRelativeUri("bad\2_namespace", "!default!", Invalid));
- CHECK(!HttpRequestParseRelativeUri("nice/\2\1bucket", "!default!", Invalid));
- CHECK(!HttpRequestParseRelativeUri("namespace/bucket/0123456789a", "!default!", Invalid));
- CHECK(!HttpRequestParseRelativeUri("namespace/bucket/0123456789abcdef12340123456789abcdef1234/56789abcdef1234",
- "!default!",
- Invalid));
- CHECK(!HttpRequestParseRelativeUri("namespace/bucket/pppppppp89abcdef12340123456789abcdef1234", "!default!", Invalid));
- CHECK(!HttpRequestParseRelativeUri("namespace/bucket/0123456789abcdef12340123456789abcdef1234/56789abcd", "!default!", Invalid));
- CHECK(!HttpRequestParseRelativeUri(
+ HttpCacheRequestData Invalid;
+ CHECK(!HttpCacheRequestParseRelativeUri("bad\2_namespace", "!default!", Invalid));
+ CHECK(!HttpCacheRequestParseRelativeUri("nice/\2\1bucket", "!default!", Invalid));
+ CHECK(!HttpCacheRequestParseRelativeUri("namespace/bucket/0123456789a", "!default!", Invalid));
+ CHECK(!HttpCacheRequestParseRelativeUri("namespace/bucket/0123456789abcdef12340123456789abcdef1234/56789abcdef1234",
+ "!default!",
+ Invalid));
+ CHECK(!HttpCacheRequestParseRelativeUri("namespace/bucket/pppppppp89abcdef12340123456789abcdef1234", "!default!", Invalid));
+ CHECK(
+ !HttpCacheRequestParseRelativeUri("namespace/bucket/0123456789abcdef12340123456789abcdef1234/56789abcd", "!default!", Invalid));
+ CHECK(!HttpCacheRequestParseRelativeUri(
"namespace/bucket/0123456789abcdef12340123456789abcdef1234/ppppppppdef12345678956789abcdef123456789",
"!default!",
Invalid));
}
#endif
-} // namespace cacherequests
-
-void
-cacherequests_forcelink()
-{
-}
-
-} // namespace zen
+}} // namespace zen::cacherequests
diff --git a/src/zenutil/include/zenutil/cache/cacherequests.h b/src/zenserver-test/cacherequests.h
index fbf3e08cc..5b7a53390 100644
--- a/src/zenutil/include/zenutil/cache/cacherequests.h
+++ b/src/zenserver-test/cacherequests.h
@@ -4,8 +4,8 @@
#include <zencore/compress.h>
-#include "cachekey.h"
-#include "cachepolicy.h"
+#include <zenstore/cache/cachekey.h>
+#include <zenstore/cache/cachepolicy.h>
#include <functional>
@@ -85,7 +85,8 @@ namespace cacherequests {
struct PutCacheRecordsResult
{
- std::vector<bool> Success;
+ std::vector<bool> Success;
+ std::vector<CbObjectView> Details;
bool Parse(const CbPackage& Package);
bool Format(CbPackage& OutPackage) const;
@@ -242,26 +243,6 @@ namespace cacherequests {
//////////////////////////////////////////////////////////////////////////
- std::optional<std::string> GetValidNamespaceName(std::string_view Name);
- std::optional<std::string> GetValidBucketName(std::string_view Name);
- std::optional<IoHash> GetValidIoHash(std::string_view Hash);
-
- struct HttpRequestData
- {
- std::optional<std::string> Namespace;
- std::optional<std::string> Bucket;
- std::optional<IoHash> HashKey;
- std::optional<IoHash> ValueContentId;
- };
-
- bool HttpRequestParseRelativeUri(std::string_view Key, std::string_view DefaultNamespace, HttpRequestData& Data);
-
- // Temporarily public
- std::optional<std::string> GetRequestNamespace(const CbObjectView& Params);
- bool GetRequestCacheKey(const CbObjectView& KeyView, CacheKey& Key);
-
- //////////////////////////////////////////////////////////////////////////
-
// struct CacheRecordValue
// {
// Oid Id = Oid::Zero;
@@ -280,6 +261,4 @@ namespace cacherequests {
} // namespace cacherequests
-void cacherequests_forcelink(); // internal
-
} // namespace zen
diff --git a/src/zenserver-test/projectclient.cpp b/src/zenserver-test/projectclient.cpp
deleted file mode 100644
index cb493be77..000000000
--- a/src/zenserver-test/projectclient.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#include "projectclient.h"
-
-#if 0
-
-# include <zencore/compactbinary.h>
-# include <zencore/logging.h>
-# include <zencore/sharedbuffer.h>
-# include <zencore/string.h>
-# include <zencore/zencore.h>
-
-# include <asio.hpp>
-# include <gsl/gsl-lite.hpp>
-
-namespace zen {
-
-struct ProjectClientConnection
-{
- ProjectClientConnection(int BasePort) { Connect(BasePort); }
-
- void Connect(int BasePort)
- {
- ZEN_UNUSED(BasePort);
-
- WideStringBuilder<64> PipeName;
- PipeName << "\\\\.\\pipe\\zenprj"; // TODO: this should use an instance-specific identifier!
-
- HANDLE hPipe = CreateFileW(PipeName.c_str(),
- GENERIC_READ | GENERIC_WRITE,
- 0, // Sharing doesn't make any sense
- nullptr, // No security attributes
- OPEN_EXISTING, // Open existing pipe
- 0, // Attributes
- nullptr // Template file
- );
-
- if (hPipe == INVALID_HANDLE_VALUE)
- {
- ZEN_WARN("failed while creating named pipe {}", WideToUtf8(PipeName));
-
- throw std::system_error(GetLastError(), std::system_category(), fmt::format("Failed to open named pipe '{}'", WideToUtf8(PipeName)));
- }
-
- // Change to message mode
- DWORD dwMode = PIPE_READMODE_MESSAGE;
- BOOL Success = SetNamedPipeHandleState(hPipe, &dwMode, nullptr, nullptr);
-
- if (!Success)
- {
- throw std::system_error(GetLastError(),
- std::system_category(),
- fmt::format("Failed to change named pipe '{}' to message mode", WideToUtf8(PipeName)));
- }
-
- m_hPipe.Attach(hPipe); // This now owns the handle and will close it
- }
-
- ~ProjectClientConnection() {}
-
- CbObject MessageTransaction(CbObject Request)
- {
- DWORD dwWrittenBytes = 0;
-
- MemoryView View = Request.GetView();
-
- BOOL Success = ::WriteFile(m_hPipe, View.GetData(), gsl::narrow_cast<DWORD>(View.GetSize()), &dwWrittenBytes, nullptr);
-
- if (!Success)
- {
- throw std::system_error(GetLastError(), std::system_category(), "Failed to write pipe message");
- }
-
- ZEN_ASSERT(dwWrittenBytes == View.GetSize());
-
- DWORD dwReadBytes = 0;
-
- Success = ReadFile(m_hPipe, m_Buffer, sizeof m_Buffer, &dwReadBytes, nullptr);
-
- if (!Success)
- {
- DWORD ErrorCode = GetLastError();
-
- if (ERROR_MORE_DATA == ErrorCode)
- {
- // Response message is larger than our buffer - handle it by allocating a larger
- // buffer on the heap and read the remainder into that buffer
-
- DWORD dwBytesAvail = 0, dwLeftThisMessage = 0;
-
- Success = PeekNamedPipe(m_hPipe, nullptr, 0, nullptr, &dwBytesAvail, &dwLeftThisMessage);
-
- if (Success)
- {
- UniqueBuffer MessageBuffer = UniqueBuffer::Alloc(dwReadBytes + dwLeftThisMessage);
-
- memcpy(MessageBuffer.GetData(), m_Buffer, dwReadBytes);
-
- Success = ReadFile(m_hPipe,
- reinterpret_cast<uint8_t*>(MessageBuffer.GetData()) + dwReadBytes,
- dwLeftThisMessage,
- &dwReadBytes,
- nullptr);
-
- if (Success)
- {
- return CbObject(SharedBuffer(std::move(MessageBuffer)));
- }
- }
- }
-
- throw std::system_error(GetLastError(), std::system_category(), "Failed to read pipe message");
- }
-
- return CbObject(SharedBuffer::MakeView(MakeMemoryView(m_Buffer)));
- }
-
-private:
- static const int kEmbeddedBufferSize = 512 - 16;
-
- CHandle m_hPipe;
- uint8_t m_Buffer[kEmbeddedBufferSize];
-};
-
-struct LocalProjectClient::ClientImpl
-{
- ClientImpl(int BasePort) : m_BasePort(BasePort) {}
- ~ClientImpl() {}
-
- void Start() {}
- void Stop() {}
-
- inline int BasePort() const { return m_BasePort; }
-
-private:
- int m_BasePort = 0;
-};
-
-LocalProjectClient::LocalProjectClient(int BasePort)
-{
- m_Impl = std::make_unique<ClientImpl>(BasePort);
- m_Impl->Start();
-}
-
-LocalProjectClient::~LocalProjectClient()
-{
- m_Impl->Stop();
-}
-
-CbObject
-LocalProjectClient::MessageTransaction(CbObject Request)
-{
- ProjectClientConnection Cx(m_Impl->BasePort());
-
- return Cx.MessageTransaction(Request);
-}
-
-} // namespace zen
-
-#endif // 0
diff --git a/src/zenserver-test/projectclient.h b/src/zenserver-test/projectclient.h
deleted file mode 100644
index 8362ee0ee..000000000
--- a/src/zenserver-test/projectclient.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#pragma once
-
-#include <memory>
-
-#include <zenbase/refcount.h>
-#include <zencore/compactbinary.h>
-
-namespace zen {
-
-/**
- * Client for communication with local project service
- *
- * This is WIP and not yet functional!
- */
-
-class LocalProjectClient : public RefCounted
-{
-public:
- LocalProjectClient(int BasePort = 0);
- ~LocalProjectClient();
-
- CbObject MessageTransaction(CbObject Request);
-
-private:
- struct ClientImpl;
-
- std::unique_ptr<ClientImpl> m_Impl;
-};
-
-} // namespace zen
diff --git a/src/zenserver-test/projectstore-tests.cpp b/src/zenserver-test/projectstore-tests.cpp
new file mode 100644
index 000000000..c8c96dbbb
--- /dev/null
+++ b/src/zenserver-test/projectstore-tests.cpp
@@ -0,0 +1,1058 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#if ZEN_WITH_TESTS
+# include "zenserver-test.h"
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <zencore/workthreadpool.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/compress.h>
+# include <zencore/filesystem.h>
+# include <zencore/fmtutils.h>
+# include <zencore/stream.h>
+# include <zencore/string.h>
+# include <zencore/xxhash.h>
+# include <zenhttp/packageformat.h>
+# include <zenutil/zenserverprocess.h>
+# include <zenhttp/httpclient.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+# include <tsl/robin_set.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+# include <random>
+
+namespace zen::tests {
+
+using namespace std::literals;
+
+TEST_CASE("project.basic")
+{
+ using namespace std::literals;
+
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance Instance1(TestEnv);
+ Instance1.SetTestDir(TestDir);
+
+ const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
+
+ std::mt19937_64 mt;
+
+ zen::StringBuilder<64> BaseUri;
+ BaseUri << fmt::format("http://localhost:{}", PortNumber);
+
+ std::filesystem::path BinPath = zen::GetRunningExecutablePath();
+ std::filesystem::path RootPath = BinPath.parent_path().parent_path();
+ BinPath = BinPath.lexically_relative(RootPath);
+
+ SUBCASE("build store init")
+ {
+ {
+ HttpClient Http{BaseUri};
+
+ {
+ zen::CbObjectWriter Body;
+ Body << "id"
+ << "test";
+ Body << "root" << RootPath.c_str();
+ Body << "project"
+ << "/zooom";
+ Body << "engine"
+ << "/zooom";
+
+ zen::BinaryWriter MemOut;
+ IoBuffer BodyBuf = Body.Save().GetBuffer().AsIoBuffer();
+
+ auto Response = Http.Post("/prj/test"sv, BodyBuf);
+ CHECK(Response.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ auto Response = Http.Get("/prj/test"sv);
+ CHECK(Response.StatusCode == HttpResponseCode::OK);
+
+ CbObject ResponseObject = Response.AsObject();
+
+ CHECK(ResponseObject["id"].AsString() == "test"sv);
+ CHECK(ResponseObject["root"].AsString() == PathToUtf8(RootPath.c_str()));
+ }
+ }
+
+ BaseUri << "/prj/test/oplog/foobar";
+
+ {
+ HttpClient Http{BaseUri};
+
+ {
+ auto Response = Http.Post(""sv);
+ CHECK(Response.StatusCode == HttpResponseCode::Created);
+ }
+
+ {
+ auto Response = Http.Get(""sv);
+ CHECK(Response.StatusCode == HttpResponseCode::OK);
+
+ CbObject ResponseObject = Response.AsObject();
+
+ CHECK(ResponseObject["id"].AsString() == "foobar"sv);
+ CHECK(ResponseObject["project"].AsString() == "test"sv);
+ }
+ }
+
+ SUBCASE("build store persistence")
+ {
+ uint8_t AttachData[] = {1, 2, 3};
+
+ zen::CompressedBuffer Attachment = zen::CompressedBuffer::Compress(zen::SharedBuffer::Clone(zen::MemoryView{AttachData, 3}));
+ zen::CbAttachment Attach{Attachment, Attachment.DecodeRawHash()};
+
+ zen::CbObjectWriter OpWriter;
+ OpWriter << "key"
+ << "foo"
+ << "attachment" << Attach;
+
+ const std::string_view ChunkId{
+ "00000000"
+ "00000000"
+ "00010000"};
+ auto FileOid = zen::Oid::FromHexString(ChunkId);
+
+ OpWriter.BeginArray("files");
+ OpWriter.BeginObject();
+ OpWriter << "id" << FileOid;
+ OpWriter << "clientpath"
+ << "/{engine}/client/side/path";
+ OpWriter << "serverpath" << BinPath.c_str();
+ OpWriter.EndObject();
+ OpWriter.EndArray();
+
+ zen::CbObject Op = OpWriter.Save();
+
+ zen::CbPackage OpPackage(Op);
+ OpPackage.AddAttachment(Attach);
+
+ zen::BinaryWriter MemOut;
+ legacy::SaveCbPackage(OpPackage, MemOut);
+
+ HttpClient Http{BaseUri};
+
+ {
+ auto Response = Http.Post("/new", IoBufferBuilder::MakeFromMemory(MemOut.GetView()));
+
+ REQUIRE(Response);
+ CHECK(Response.StatusCode == HttpResponseCode::Created);
+ }
+
+ // Read file data
+
+ {
+ zen::StringBuilder<128> ChunkGetUri;
+ ChunkGetUri << "/" << ChunkId;
+ auto Response = Http.Get(ChunkGetUri);
+
+ REQUIRE(Response);
+ CHECK(Response.StatusCode == HttpResponseCode::OK);
+ }
+
+ {
+ zen::StringBuilder<128> ChunkGetUri;
+ ChunkGetUri << "/" << ChunkId << "?offset=1&size=10";
+ auto Response = Http.Get(ChunkGetUri);
+
+ REQUIRE(Response);
+ CHECK(Response.StatusCode == HttpResponseCode::OK);
+ CHECK(Response.ResponsePayload.GetSize() == 10);
+ }
+
+ ZEN_INFO("+++++++");
+ }
+
+ SUBCASE("snapshot")
+ {
+ zen::CbObjectWriter OpWriter;
+ OpWriter << "key"
+ << "foo";
+
+ const std::string_view ChunkId{
+ "00000000"
+ "00000000"
+ "00010000"};
+ auto FileOid = zen::Oid::FromHexString(ChunkId);
+
+ OpWriter.BeginArray("files");
+ OpWriter.BeginObject();
+ OpWriter << "id" << FileOid;
+ OpWriter << "clientpath"
+ << "/{engine}/client/side/path";
+ OpWriter << "serverpath" << BinPath.c_str();
+ OpWriter.EndObject();
+ OpWriter.EndArray();
+
+ zen::CbObject Op = OpWriter.Save();
+
+ zen::CbPackage OpPackage(Op);
+
+ zen::BinaryWriter MemOut;
+ legacy::SaveCbPackage(OpPackage, MemOut);
+
+ HttpClient Http{BaseUri};
+
+ {
+ auto Response = Http.Post("/new", IoBufferBuilder::MakeFromMemory(MemOut.GetView()));
+
+ REQUIRE(Response);
+ CHECK(Response.StatusCode == HttpResponseCode::Created);
+ }
+
+ // Read file data, it is raw and uncompressed
+ {
+ zen::StringBuilder<128> ChunkGetUri;
+ ChunkGetUri << "/" << ChunkId;
+ auto Response = Http.Get(ChunkGetUri);
+
+ REQUIRE(Response);
+ CHECK(Response.StatusCode == HttpResponseCode::OK);
+
+ IoBuffer Data = Response.ResponsePayload;
+ IoBuffer ReferenceData = IoBufferBuilder::MakeFromFile(RootPath / BinPath);
+ CHECK(ReferenceData.GetSize() == Data.GetSize());
+ CHECK(ReferenceData.GetView().EqualBytes(Data.GetView()));
+ }
+
+ {
+ IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) { Writer.AddString("method"sv, "snapshot"sv); });
+ auto Response = Http.Post("/rpc"sv, Payload);
+ REQUIRE(Response);
+ CHECK(Response.StatusCode == HttpResponseCode::OK);
+ }
+
+ // Read chunk data, it is now compressed
+ {
+ zen::StringBuilder<128> ChunkGetUri;
+ ChunkGetUri << "/" << ChunkId;
+ auto Response = Http.Get(ChunkGetUri, {{"Accept-Type", "application/x-ue-comp"}});
+
+ REQUIRE(Response);
+ CHECK(Response.StatusCode == HttpResponseCode::OK);
+
+ IoBuffer Data = Response.ResponsePayload;
+ IoHash RawHash;
+ uint64_t RawSize;
+ CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Data), RawHash, RawSize);
+ CHECK(Compressed);
+ IoBuffer DataDecompressed = Compressed.Decompress().AsIoBuffer();
+ IoBuffer ReferenceData = IoBufferBuilder::MakeFromFile(RootPath / BinPath);
+ CHECK(RawSize == ReferenceData.GetSize());
+ CHECK(ReferenceData.GetSize() == DataDecompressed.GetSize());
+ CHECK(ReferenceData.GetView().EqualBytes(DataDecompressed.GetView()));
+ }
+
+ ZEN_INFO("+++++++");
+ }
+
+ SUBCASE("test chunk not found error")
+ {
+ HttpClient Http{BaseUri};
+
+ for (size_t I = 0; I < 65; I++)
+ {
+ zen::StringBuilder<128> PostUri;
+ PostUri << "/f77c781846caead318084604/info";
+ auto Response = Http.Get(PostUri);
+
+ REQUIRE(!Response.Error);
+ CHECK(Response.StatusCode == HttpResponseCode::NotFound);
+ }
+ }
+ }
+}
+
+CbPackage
+CreateOplogPackage(const Oid& Id, const std::span<const std::pair<Oid, CompressedBuffer>>& Attachments)
+{
+ CbPackage Package;
+ CbObjectWriter Object;
+ Object << "key"sv << OidAsString(Id);
+ if (!Attachments.empty())
+ {
+ Object.BeginArray("bulkdata");
+ for (const auto& Attachment : Attachments)
+ {
+ CbAttachment Attach(Attachment.second, Attachment.second.DecodeRawHash());
+ Object.BeginObject();
+ Object << "id"sv << Attachment.first;
+ Object << "type"sv
+ << "Standard"sv;
+ Object << "data"sv << Attach;
+ Object.EndObject();
+
+ Package.AddAttachment(Attach);
+ ZEN_DEBUG("Added attachment {}", Attach.GetHash());
+ }
+ Object.EndArray();
+ }
+ Package.SetObject(Object.Save());
+ return Package;
+};
+
+CbObject
+CreateOplogOp(const Oid& Id, const std::span<const std::pair<Oid, CompressedBuffer>>& Attachments)
+{
+ CbObjectWriter Object;
+ Object << "key"sv << OidAsString(Id);
+ if (!Attachments.empty())
+ {
+ Object.BeginArray("bulkdata");
+ for (const auto& Attachment : Attachments)
+ {
+ CbAttachment Attach(Attachment.second, Attachment.second.DecodeRawHash());
+ Object.BeginObject();
+ Object << "id"sv << Attachment.first;
+ Object << "type"sv
+ << "Standard"sv;
+ Object << "data"sv << Attach;
+ Object.EndObject();
+
+ ZEN_DEBUG("Added attachment {}", Attach.GetHash());
+ }
+ Object.EndArray();
+ }
+ return Object.Save();
+};
+
+enum CbWriterMeta
+{
+ BeginObject,
+ EndObject,
+ BeginArray,
+ EndArray
+};
+
+inline CbWriter&
+operator<<(CbWriter& Writer, CbWriterMeta Meta)
+{
+ switch (Meta)
+ {
+ case BeginObject:
+ Writer.BeginObject();
+ break;
+ case EndObject:
+ Writer.EndObject();
+ break;
+ case BeginArray:
+ Writer.BeginArray();
+ break;
+ case EndArray:
+ Writer.EndArray();
+ break;
+ default:
+ ZEN_ASSERT(false);
+ }
+ return Writer;
+}
+
+TEST_CASE("project.remote")
+{
+ using namespace std::literals;
+ using namespace utils;
+
+ ZenServerTestHelper Servers("remote", 3);
+ Servers.SpawnServers("--debug");
+
+ std::vector<Oid> OpIds;
+ const size_t OpCount = 24;
+ OpIds.reserve(OpCount);
+ for (size_t I = 0; I < OpCount; ++I)
+ {
+ OpIds.emplace_back(Oid::NewOid());
+ }
+
+ std::unordered_map<Oid, std::vector<std::pair<Oid, CompressedBuffer>>, Oid::Hasher> Attachments;
+ {
+ std::vector<std::size_t> AttachmentSizes(
+ {7633, 6825, 5738, 8031, 7225, 566, 3656, 6006, 24, 33466, 1093, 4269, 2257, 3685, 13489, 97194,
+ 6151, 5482, 6217, 3511, 6738, 5061, 7537, 2759, 1916, 8210, 2235, 224024, 51582, 5251, 491, 2u * 1024u * 1024u + 124u,
+ 74607, 18135, 3767, 154045, 4415, 5007, 8876, 96761, 3359, 8526, 4097, 4855, 48225});
+ auto It = AttachmentSizes.begin();
+ Attachments[OpIds[0]] = {};
+ Attachments[OpIds[1]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[2]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[3]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[4]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
+ Attachments[OpIds[5]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[6]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[7]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[8]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
+ Attachments[OpIds[9]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[10]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[11]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
+ Attachments[OpIds[12]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[13]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[14]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[15]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[16]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
+ Attachments[OpIds[17]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[18]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[19]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
+ Attachments[OpIds[20]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[21]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[22]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
+ Attachments[OpIds[23]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ ZEN_ASSERT(It == AttachmentSizes.end());
+ }
+
+ // Note: This is a clone of the function in projectstore.cpp
+ auto ComputeOpKey = [](const CbObjectView& Op) -> Oid {
+ using namespace std::literals;
+
+ XXH3_128Stream_deprecated KeyHasher;
+ Op["key"sv].WriteToStream([&](const void* Data, size_t Size) { KeyHasher.Append(Data, Size); });
+ XXH3_128 KeyHash128 = KeyHasher.GetHash();
+
+ Oid KeyHash;
+ memcpy(&KeyHash, KeyHash128.Hash, sizeof KeyHash);
+
+ return KeyHash;
+ };
+
+ auto AddOp = [ComputeOpKey](const CbObject& Op, std::unordered_map<Oid, uint32_t, Oid::Hasher>& Ops) {
+ const Oid Id = ComputeOpKey(Op);
+ IoBuffer Buffer = Op.GetBuffer().AsIoBuffer();
+ const uint32_t OpCoreHash = uint32_t(XXH3_64bits(Buffer.GetData(), Buffer.GetSize()) & 0xffffFFFF);
+ Ops.insert({Id, OpCoreHash});
+ };
+
+ auto MakeProject = [](std::string_view UrlBase, std::string_view ProjectName) {
+ CbObjectWriter Project;
+ Project.AddString("id"sv, ProjectName);
+ Project.AddString("root"sv, ""sv);
+ Project.AddString("engine"sv, ""sv);
+ Project.AddString("project"sv, ""sv);
+ Project.AddString("projectfile"sv, ""sv);
+ IoBuffer ProjectPayload = Project.Save().GetBuffer().AsIoBuffer();
+ ProjectPayload.SetContentType(HttpContentType::kCbObject);
+
+ HttpClient Http{UrlBase};
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}", ProjectName), ProjectPayload);
+ CHECK(Response);
+ };
+
+ auto MakeOplog = [](std::string_view UrlBase, std::string_view ProjectName, std::string_view OplogName) {
+ HttpClient Http{UrlBase};
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}", ProjectName, OplogName), IoBuffer{});
+ CHECK(Response);
+ };
+
+ auto MakeOp = [](std::string_view UrlBase, std::string_view ProjectName, std::string_view OplogName, const CbPackage& OpPackage) {
+ zen::BinaryWriter MemOut;
+ legacy::SaveCbPackage(OpPackage, MemOut);
+ IoBuffer Body{IoBuffer::Wrap, MemOut.GetData(), MemOut.GetSize()};
+ Body.SetContentType(HttpContentType::kCbPackage);
+
+ HttpClient Http{UrlBase};
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/new", ProjectName, OplogName), Body);
+ CHECK(Response);
+ };
+
+ MakeProject(Servers.GetInstance(0).GetBaseUri(), "proj0");
+ MakeOplog(Servers.GetInstance(0).GetBaseUri(), "proj0", "oplog0");
+
+ std::unordered_map<Oid, uint32_t, Oid::Hasher> SourceOps;
+ for (const Oid& OpId : OpIds)
+ {
+ CbPackage OpPackage = CreateOplogPackage(OpId, Attachments[OpId]);
+ CHECK(OpPackage.GetAttachments().size() == Attachments[OpId].size());
+ AddOp(OpPackage.GetObject(), SourceOps);
+ MakeOp(Servers.GetInstance(0).GetBaseUri(), "proj0", "oplog0", OpPackage);
+ }
+
+ std::vector<IoHash> AttachmentHashes;
+ AttachmentHashes.reserve(Attachments.size());
+ for (const auto& AttachmentOplog : Attachments)
+ {
+ for (const auto& Attachment : AttachmentOplog.second)
+ {
+ AttachmentHashes.emplace_back(Attachment.second.DecodeRawHash());
+ }
+ }
+
+ auto MakeCbObjectPayload = [](std::function<void(CbObjectWriter & Writer)> Write) -> IoBuffer {
+ CbObjectWriter Writer;
+ Write(Writer);
+ IoBuffer Result = Writer.Save().GetBuffer().AsIoBuffer();
+ Result.MakeOwned();
+ Result.SetContentType(HttpContentType::kCbObject);
+ return Result;
+ };
+
+ auto ValidateAttachments =
+ [&MakeCbObjectPayload, &AttachmentHashes, &Servers](int ServerIndex, std::string_view Project, std::string_view Oplog) {
+ HttpClient Http{Servers.GetInstance(ServerIndex).GetBaseUri()};
+
+ IoBuffer Payload = MakeCbObjectPayload([&AttachmentHashes](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "getchunks"sv;
+ Writer << "chunks"sv << BeginArray;
+ for (const IoHash& Chunk : AttachmentHashes)
+ {
+ Writer << Chunk;
+ }
+ Writer << EndArray; // chunks
+ });
+
+ HttpClient::Response Response =
+ Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", Project, Oplog), Payload, {{"Accept", "application/x-ue-cbpkg"}});
+ CHECK(Response);
+ CbPackage ResponsePackage = ParsePackageMessage(Response.ResponsePayload);
+ CHECK(ResponsePackage.GetAttachments().size() == AttachmentHashes.size());
+ for (auto A : ResponsePackage.GetAttachments())
+ {
+ CHECK(IoHash::HashBuffer(A.AsCompressedBinary().DecompressToComposite()) == A.GetHash());
+ }
+ };
+
+ auto ValidateOplog = [&SourceOps, &AddOp, &Servers](int ServerIndex, std::string_view Project, std::string_view Oplog) {
+ std::unordered_map<Oid, uint32_t, Oid::Hasher> TargetOps;
+ std::vector<CbObject> ResultingOplog;
+
+ HttpClient Http{Servers.GetInstance(ServerIndex).GetBaseUri()};
+ HttpClient::Response Response = Http.Get(fmt::format("/prj/{}/oplog/{}/entries", Project, Oplog));
+ CHECK(Response);
+
+ IoBuffer Payload(Response.ResponsePayload);
+ CbObject OplogResonse = LoadCompactBinaryObject(Payload);
+ CbArrayView EntriesArray = OplogResonse["entries"sv].AsArrayView();
+
+ for (CbFieldView OpEntry : EntriesArray)
+ {
+ CbObjectView Core = OpEntry.AsObjectView();
+ BinaryWriter Writer;
+ Core.CopyTo(Writer);
+ MemoryView OpView = Writer.GetView();
+ IoBuffer OpBuffer(IoBuffer::Wrap, OpView.GetData(), OpView.GetSize());
+ CbObject Op(SharedBuffer(OpBuffer), CbFieldType::HasFieldType);
+ AddOp(Op, TargetOps);
+ }
+ CHECK(SourceOps == TargetOps);
+ };
+
+ auto HttpWaitForCompletion = [](ZenServerInstance& Server, const HttpClient::Response& Response) {
+ REQUIRE(Response);
+ const uint64_t JobId = ParseInt<uint64_t>(Response.AsText()).value_or(0);
+ CHECK(JobId != 0);
+
+ HttpClient Http{Server.GetBaseUri()};
+
+ while (true)
+ {
+ HttpClient::Response StatusResponse =
+ Http.Get(fmt::format("/admin/jobs/{}", JobId), {{"Accept", ToString(ZenContentType::kCbObject)}});
+ CHECK(StatusResponse);
+ CbObject ResponseObject = StatusResponse.AsObject();
+ std::string_view Status = ResponseObject["Status"sv].AsString();
+ CHECK(Status != "Aborted"sv);
+ if (Status == "Complete"sv)
+ {
+ return;
+ }
+ Sleep(10);
+ }
+ };
+
+ SUBCASE("File")
+ {
+ ScopedTemporaryDirectory TempDir;
+ {
+ IoBuffer Payload = MakeCbObjectPayload([&AttachmentHashes, path = TempDir.Path().string()](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "export"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "maxblocksize"sv << 3072u;
+ Writer << "maxchunkembedsize"sv << 1296u;
+ Writer << "chunkfilesizelimit"sv << 5u * 1024u;
+ Writer << "force"sv << false;
+ Writer << "file"sv << BeginObject;
+ {
+ Writer << "path"sv << path;
+ Writer << "name"sv
+ << "proj0_oplog0"sv;
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(0).GetBaseUri()};
+
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj0", "oplog0"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(0), Response);
+ }
+ {
+ MakeProject(Servers.GetInstance(1).GetBaseUri(), "proj0_copy");
+ MakeOplog(Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
+
+ IoBuffer Payload = MakeCbObjectPayload([&AttachmentHashes, path = TempDir.Path().string()](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "import"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "force"sv << false;
+ Writer << "file"sv << BeginObject;
+ {
+ Writer << "path"sv << path;
+ Writer << "name"sv
+ << "proj0_oplog0"sv;
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(1).GetBaseUri()};
+
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj0_copy", "oplog0_copy"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(1), Response);
+ }
+ ValidateAttachments(1, "proj0_copy", "oplog0_copy");
+ ValidateOplog(1, "proj0_copy", "oplog0_copy");
+ }
+
+ SUBCASE("File disable blocks")
+ {
+ ScopedTemporaryDirectory TempDir;
+ {
+ IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "export"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "maxblocksize"sv << 3072u;
+ Writer << "maxchunkembedsize"sv << 1296u;
+ Writer << "chunkfilesizelimit"sv << 5u * 1024u;
+ Writer << "force"sv << false;
+ Writer << "file"sv << BeginObject;
+ {
+ Writer << "path"sv << TempDir.Path().string();
+ Writer << "name"sv
+ << "proj0_oplog0"sv;
+ Writer << "disableblocks"sv << true;
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(0).GetBaseUri()};
+
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj0", "oplog0"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(0), Response);
+ }
+ {
+ MakeProject(Servers.GetInstance(1).GetBaseUri(), "proj0_copy");
+ MakeOplog(Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
+ IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "import"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "force"sv << false;
+ Writer << "file"sv << BeginObject;
+ {
+ Writer << "path"sv << TempDir.Path().string();
+ Writer << "name"sv
+ << "proj0_oplog0"sv;
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(1).GetBaseUri()};
+
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj0_copy", "oplog0_copy"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(1), Response);
+ }
+ ValidateAttachments(1, "proj0_copy", "oplog0_copy");
+ ValidateOplog(1, "proj0_copy", "oplog0_copy");
+ }
+
+ SUBCASE("File force temp blocks")
+ {
+ ScopedTemporaryDirectory TempDir;
+ {
+ IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "export"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "maxblocksize"sv << 3072u;
+ Writer << "maxchunkembedsize"sv << 1296u;
+ Writer << "chunkfilesizelimit"sv << 5u * 1024u;
+ Writer << "force"sv << false;
+ Writer << "file"sv << BeginObject;
+ {
+ Writer << "path"sv << TempDir.Path().string();
+ Writer << "name"sv
+ << "proj0_oplog0"sv;
+ Writer << "enabletempblocks"sv << true;
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(0).GetBaseUri()};
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj0", "oplog0"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(0), Response);
+ }
+ {
+ MakeProject(Servers.GetInstance(1).GetBaseUri(), "proj0_copy");
+ MakeOplog(Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
+ IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "import"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "force"sv << false;
+ Writer << "file"sv << BeginObject;
+ {
+ Writer << "path"sv << TempDir.Path().string();
+ Writer << "name"sv
+ << "proj0_oplog0"sv;
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(1).GetBaseUri()};
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj0_copy", "oplog0_copy"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(1), Response);
+ }
+ ValidateAttachments(1, "proj0_copy", "oplog0_copy");
+ ValidateOplog(1, "proj0_copy", "oplog0_copy");
+ }
+
+ SUBCASE("Zen")
+ {
+ ScopedTemporaryDirectory TempDir;
+ {
+ std::string ExportSourceUri = Servers.GetInstance(0).GetBaseUri();
+ std::string ExportTargetUri = Servers.GetInstance(1).GetBaseUri();
+ MakeProject(ExportTargetUri, "proj0_copy");
+ MakeOplog(ExportTargetUri, "proj0_copy", "oplog0_copy");
+
+ IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "export"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "maxblocksize"sv << 3072u;
+ Writer << "maxchunkembedsize"sv << 1296u;
+ Writer << "chunkfilesizelimit"sv << 5u * 1024u;
+ Writer << "force"sv << false;
+ Writer << "zen"sv << BeginObject;
+ {
+ Writer << "url"sv << ExportTargetUri.substr(7);
+ Writer << "project"
+ << "proj0_copy";
+ Writer << "oplog"
+ << "oplog0_copy";
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(0).GetBaseUri()};
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj0", "oplog0"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(0), Response);
+ }
+ ValidateAttachments(1, "proj0_copy", "oplog0_copy");
+ ValidateOplog(1, "proj0_copy", "oplog0_copy");
+
+ {
+ std::string ImportSourceUri = Servers.GetInstance(1).GetBaseUri();
+ std::string ImportTargetUri = Servers.GetInstance(2).GetBaseUri();
+ MakeProject(ImportTargetUri, "proj1");
+ MakeOplog(ImportTargetUri, "proj1", "oplog1");
+
+ IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
+ Writer << "method"sv
+ << "import"sv;
+ Writer << "params" << BeginObject;
+ {
+ Writer << "force"sv << false;
+ Writer << "zen"sv << BeginObject;
+ {
+ Writer << "url"sv << ImportSourceUri.substr(7);
+ Writer << "project"
+ << "proj0_copy";
+ Writer << "oplog"
+ << "oplog0_copy";
+ }
+ Writer << EndObject; // "file"
+ }
+ Writer << EndObject; // "params"
+ });
+
+ HttpClient Http{Servers.GetInstance(2).GetBaseUri()};
+ HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}/rpc", "proj1", "oplog1"), Payload);
+ HttpWaitForCompletion(Servers.GetInstance(2), Response);
+ }
+ ValidateAttachments(2, "proj1", "oplog1");
+ ValidateOplog(2, "proj1", "oplog1");
+ }
+}
+
+TEST_CASE("project.rpcappendop")
+{
+ using namespace std::literals;
+ using namespace utils;
+
+ ZenServerTestHelper Servers("remote", 2);
+ Servers.SpawnServers("--debug");
+
+ std::vector<Oid> OpIds;
+ const size_t OpCount = 24;
+ OpIds.reserve(OpCount);
+ for (size_t I = 0; I < OpCount; ++I)
+ {
+ OpIds.emplace_back(Oid::NewOid());
+ }
+
+ std::unordered_map<Oid, std::vector<std::pair<Oid, CompressedBuffer>>, Oid::Hasher> Attachments;
+ {
+ std::vector<std::size_t> AttachmentSizes(
+ {7633, 6825, 5738, 8031, 7225, 566, 3656, 6006, 24, 33466, 1093, 4269, 2257, 3685, 13489, 97194,
+ 6151, 5482, 6217, 3511, 6738, 5061, 7537, 2759, 1916, 8210, 2235, 224024, 51582, 5251, 491, 2u * 1024u * 1024u + 124u,
+ 74607, 18135, 3767, 154045, 4415, 5007, 8876, 96761, 3359, 8526, 4097, 4855, 48225});
+ auto It = AttachmentSizes.begin();
+ Attachments[OpIds[0]] = {};
+ Attachments[OpIds[1]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[2]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[3]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[4]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
+ Attachments[OpIds[5]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[6]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[7]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[8]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
+ Attachments[OpIds[9]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[10]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[11]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
+ Attachments[OpIds[12]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
+ Attachments[OpIds[13]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[14]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[15]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[16]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
+ Attachments[OpIds[17]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[18]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
+ Attachments[OpIds[19]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
+ Attachments[OpIds[20]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[21]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ Attachments[OpIds[22]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
+ Attachments[OpIds[23]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
+ ZEN_ASSERT(It == AttachmentSizes.end());
+ }
+
+ // Note: This is a clone of the function in projectstore.cpp
+ auto ComputeOpKey = [](const CbObjectView& Op) -> Oid {
+ using namespace std::literals;
+
+ XXH3_128Stream_deprecated KeyHasher;
+ Op["key"sv].WriteToStream([&](const void* Data, size_t Size) { KeyHasher.Append(Data, Size); });
+ XXH3_128 KeyHash128 = KeyHasher.GetHash();
+
+ Oid KeyHash;
+ memcpy(&KeyHash, KeyHash128.Hash, sizeof KeyHash);
+
+ return KeyHash;
+ };
+
+ auto AddOp = [ComputeOpKey](const CbObject& Op, std::unordered_map<Oid, uint32_t, Oid::Hasher>& Ops) {
+ const Oid Id = ComputeOpKey(Op);
+ IoBuffer Buffer = Op.GetBuffer().AsIoBuffer();
+ const uint32_t OpCoreHash = uint32_t(XXH3_64bits(Buffer.GetData(), Buffer.GetSize()) & 0xffffFFFF);
+ Ops.insert({Id, OpCoreHash});
+ };
+
+ auto MakeProject = [](HttpClient& Client, std::string_view ProjectName) {
+ CbObjectWriter Project;
+ Project.AddString("id"sv, ProjectName);
+ Project.AddString("root"sv, ""sv);
+ Project.AddString("engine"sv, ""sv);
+ Project.AddString("project"sv, ""sv);
+ Project.AddString("projectfile"sv, ""sv);
+ HttpClient::Response Response = Client.Post(fmt::format("/prj/{}", ProjectName), Project.Save());
+ CHECK_MESSAGE(Response.IsSuccess(), Response.ErrorMessage(""));
+ };
+
+ auto MakeOplog = [](HttpClient& Client, std::string_view ProjectName, std::string_view OplogName) {
+ HttpClient::Response Response = Client.Post(fmt::format("/prj/{}/oplog/{}", ProjectName, OplogName));
+ CHECK_MESSAGE(Response.IsSuccess(), Response.ErrorMessage(""));
+ };
+ auto GetOplog = [](HttpClient& Client, std::string_view ProjectName, std::string_view OplogName) {
+ HttpClient::Response Response = Client.Get(fmt::format("/prj/{}/oplog/{}", ProjectName, OplogName));
+ CHECK_MESSAGE(Response.IsSuccess(), Response.ErrorMessage(""));
+ return Response.AsObject();
+ };
+
+ auto MakeOp =
+ [](HttpClient& Client, std::string_view ProjectName, std::string_view OplogName, const CbObjectView& Op) -> std::vector<IoHash> {
+ CbObjectWriter Request;
+ Request.AddString("method"sv, "appendops"sv);
+ Request.BeginArray("ops"sv);
+ {
+ Request.AddObject(Op);
+ }
+ Request.EndArray(); // "ops"
+ HttpClient::Response Response = Client.Post(fmt::format("/prj/{}/oplog/{}/rpc", ProjectName, OplogName), Request.Save());
+ CHECK_MESSAGE(Response.IsSuccess(), Response.ErrorMessage(""));
+
+ CbObjectView ResponsePayload = Response.AsPackage().GetObject();
+ CbArrayView NeedArray = ResponsePayload["need"sv].AsArrayView();
+ std::vector<IoHash> Needs;
+ Needs.reserve(NeedArray.Num());
+ for (CbFieldView NeedView : NeedArray)
+ {
+ Needs.push_back(NeedView.AsHash());
+ }
+ return Needs;
+ };
+
+ auto SendAttachments = [](HttpClient& Client,
+ std::string_view ProjectName,
+ std::string_view OplogName,
+ std::span<const CompressedBuffer> Attachments,
+ void* ServerProcessHandle,
+ const std::filesystem::path& TempPath) {
+ CompositeBuffer PackageMessage;
+ {
+ CbPackage RequestPackage;
+ CbObjectWriter Request;
+ Request.AddString("method"sv, "putchunks"sv);
+ Request.AddBool("usingtmpfiles"sv, true);
+ Request.BeginArray("chunks"sv);
+ for (CompressedBuffer AttachmentPayload : Attachments)
+ {
+ if (AttachmentPayload.DecodeRawSize() > 16u * 1024u)
+ {
+ std::filesystem::path TempAttachmentPath = TempPath / (Oid::NewOid().ToString() + ".tmp");
+ WriteFile(TempAttachmentPath, AttachmentPayload.GetCompressed());
+ IoBuffer OnDiskAttachment = IoBufferBuilder::MakeFromFile(TempAttachmentPath);
+ AttachmentPayload = CompressedBuffer::FromCompressedNoValidate(std::move(OnDiskAttachment));
+ }
+
+ CbAttachment Attachment(AttachmentPayload, AttachmentPayload.DecodeRawHash());
+
+ Request.AddAttachment(Attachment);
+ RequestPackage.AddAttachment(Attachment);
+ }
+ Request.EndArray(); // "chunks"
+ RequestPackage.SetObject(Request.Save());
+
+ PackageMessage = CompositeBuffer(FormatPackageMessage(RequestPackage, FormatFlags::kAllowLocalReferences, ServerProcessHandle));
+ }
+
+ HttpClient::Response Response =
+ Client.Post(fmt::format("/prj/{}/oplog/{}/rpc", ProjectName, OplogName), PackageMessage, HttpContentType::kCbPackage);
+ CHECK_MESSAGE(Response.IsSuccess(), Response.ErrorMessage(""));
+ };
+
+ {
+ HttpClient Client(Servers.GetInstance(0).GetBaseUri());
+ void* ServerProcessHandle = Servers.GetInstance(0).GetProcessHandle();
+
+ MakeProject(Client, "proj0");
+ MakeOplog(Client, "proj0", "oplog0");
+ CbObject Oplog = GetOplog(Client, "proj0", "oplog0");
+ std::filesystem::path TempPath = Oplog["tempdir"sv].AsU8String();
+
+ std::unordered_map<Oid, uint32_t, Oid::Hasher> SourceOps;
+ for (const Oid& OpId : OpIds)
+ {
+ CbObject Op = CreateOplogOp(OpId, Attachments[OpId]);
+ AddOp(Op, SourceOps);
+ std::vector<IoHash> MissingAttachments = MakeOp(Client, "proj0", "oplog0", Op);
+
+ if (!MissingAttachments.empty())
+ {
+ CHECK(MissingAttachments.size() <= Attachments[OpId].size());
+ tsl::robin_set<IoHash, IoHash::Hasher> MissingAttachmentSet(MissingAttachments.begin(), MissingAttachments.end());
+ std::vector<CompressedBuffer> PutAttachments;
+ for (const auto& Attachment : Attachments[OpId])
+ {
+ CompressedBuffer Payload = Attachment.second;
+ const IoHash AttachmentHash = Payload.DecodeRawHash();
+ if (auto It = MissingAttachmentSet.find(AttachmentHash); It != MissingAttachmentSet.end())
+ {
+ PutAttachments.push_back(Payload);
+ }
+ }
+ SendAttachments(Client, "proj0", "oplog0", PutAttachments, ServerProcessHandle, TempPath);
+ }
+ }
+
+ // Do it again, but now we should not need any attachments
+
+ for (const Oid& OpId : OpIds)
+ {
+ CbObject Op = CreateOplogOp(OpId, Attachments[OpId]);
+ AddOp(Op, SourceOps);
+ std::vector<IoHash> MissingAttachments = MakeOp(Client, "proj0", "oplog0", Op);
+ CHECK(MissingAttachments.empty());
+ }
+ }
+
+ {
+ HttpClient Client(Servers.GetInstance(1).GetBaseUri());
+ void* ServerProcessHandle = nullptr; // Force use of path for attachments passed on disk
+
+ MakeProject(Client, "proj0");
+ MakeOplog(Client, "proj0", "oplog0");
+ CbObject Oplog = GetOplog(Client, "proj0", "oplog0");
+ std::filesystem::path TempPath = Oplog["tempdir"sv].AsU8String();
+
+ std::unordered_map<Oid, uint32_t, Oid::Hasher> SourceOps;
+ for (const Oid& OpId : OpIds)
+ {
+ CbObject Op = CreateOplogOp(OpId, Attachments[OpId]);
+ AddOp(Op, SourceOps);
+ std::vector<IoHash> MissingAttachments = MakeOp(Client, "proj0", "oplog0", Op);
+
+ if (!MissingAttachments.empty())
+ {
+ CHECK(MissingAttachments.size() <= Attachments[OpId].size());
+ tsl::robin_set<IoHash, IoHash::Hasher> MissingAttachmentSet(MissingAttachments.begin(), MissingAttachments.end());
+ std::vector<CompressedBuffer> PutAttachments;
+ for (const auto& Attachment : Attachments[OpId])
+ {
+ CompressedBuffer Payload = Attachment.second;
+ const IoHash AttachmentHash = Payload.DecodeRawHash();
+ if (auto It = MissingAttachmentSet.find(AttachmentHash); It != MissingAttachmentSet.end())
+ {
+ PutAttachments.push_back(Payload);
+ }
+ }
+ SendAttachments(Client, "proj0", "oplog0", PutAttachments, ServerProcessHandle, TempPath);
+ }
+ }
+
+ // Do it again, but now we should not need any attachments
+
+ for (const Oid& OpId : OpIds)
+ {
+ CbObject Op = CreateOplogOp(OpId, Attachments[OpId]);
+ AddOp(Op, SourceOps);
+ std::vector<IoHash> MissingAttachments = MakeOp(Client, "proj0", "oplog0", Op);
+ CHECK(MissingAttachments.empty());
+ }
+ }
+}
+
+} // namespace zen::tests
+
+#endif
diff --git a/src/zenserver-test/workspace-tests.cpp b/src/zenserver-test/workspace-tests.cpp
new file mode 100644
index 000000000..f299b6dcf
--- /dev/null
+++ b/src/zenserver-test/workspace-tests.cpp
@@ -0,0 +1,541 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#if ZEN_WITH_TESTS
+# include "zenserver-test.h"
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <zencore/workthreadpool.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/compress.h>
+# include <zencore/fmtutils.h>
+# include <zencore/filesystem.h>
+# include <zencore/stream.h>
+# include <zencore/string.h>
+# include <zenutil/zenserverprocess.h>
+# include <zenutil/chunkrequests.h>
+# include <zenhttp/httpclient.h>
+
+namespace zen::tests {
+
+using namespace std::literals;
+
+std::vector<std::pair<std::filesystem::path, IoBuffer>>
+GenerateFolderContent(const std::filesystem::path& RootPath)
+{
+ CreateDirectories(RootPath);
+ std::vector<std::pair<std::filesystem::path, IoBuffer>> Result;
+ Result.push_back(std::make_pair(RootPath / "root_blob_1.bin", CreateRandomBlob(4122)));
+ Result.push_back(std::make_pair(RootPath / "root_blob_2.bin", CreateRandomBlob(2122)));
+
+ std::filesystem::path EmptyFolder(RootPath / "empty_folder");
+
+ std::filesystem::path FirstFolder(RootPath / "first_folder");
+ CreateDirectories(FirstFolder);
+ Result.push_back(std::make_pair(FirstFolder / "first_folder_blob1.bin", CreateRandomBlob(22)));
+ Result.push_back(std::make_pair(FirstFolder / "first_folder_blob2.bin", CreateRandomBlob(122)));
+
+ std::filesystem::path SecondFolder(RootPath / "second_folder");
+ CreateDirectories(SecondFolder);
+ Result.push_back(std::make_pair(SecondFolder / "second_folder_blob1.bin", CreateRandomBlob(522)));
+ Result.push_back(std::make_pair(SecondFolder / "second_folder_blob2.bin", CreateRandomBlob(122)));
+ Result.push_back(std::make_pair(SecondFolder / "second_folder_blob3.bin", CreateRandomBlob(225)));
+
+ std::filesystem::path SecondFolderChild(SecondFolder / "child_in_second");
+ CreateDirectories(SecondFolderChild);
+ Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob1.bin", CreateRandomBlob(622)));
+
+ for (const auto& It : Result)
+ {
+ WriteFile(It.first, It.second);
+ }
+
+ return Result;
+}
+
+std::vector<std::pair<std::filesystem::path, IoBuffer>>
+GenerateFolderContent2(const std::filesystem::path& RootPath)
+{
+ std::vector<std::pair<std::filesystem::path, IoBuffer>> Result;
+ Result.push_back(std::make_pair(RootPath / "root_blob_3.bin", CreateRandomBlob(312)));
+ std::filesystem::path FirstFolder(RootPath / "first_folder");
+ Result.push_back(std::make_pair(FirstFolder / "first_folder_blob3.bin", CreateRandomBlob(722)));
+ std::filesystem::path SecondFolder(RootPath / "second_folder");
+ std::filesystem::path SecondFolderChild(SecondFolder / "child_in_second");
+ Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob2.bin", CreateRandomBlob(962)));
+ Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob3.bin", CreateRandomBlob(561)));
+
+ for (const auto& It : Result)
+ {
+ WriteFile(It.first, It.second);
+ }
+
+ return Result;
+}
+
+TEST_CASE("workspaces.create")
+{
+ using namespace std::literals;
+
+ std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
+
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+ ZenServerInstance Instance(TestEnv);
+ Instance.SetTestDir(TestDir);
+ const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(
+ fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ ScopedTemporaryDirectory TempDir;
+ std::filesystem::path Root1Path = TempDir.Path() / "root1";
+ std::filesystem::path Root2Path = TempDir.Path() / "root2";
+ DeleteDirectories(Root1Path);
+ DeleteDirectories(Root2Path);
+
+ std::filesystem::path Share1Path = "shared_1";
+ std::filesystem::path Share2Path = "shared_2";
+ CreateDirectories(Root1Path / Share1Path);
+ CreateDirectories(Root1Path / Share2Path);
+ CreateDirectories(Root2Path / Share1Path);
+ CreateDirectories(Root2Path / Share2Path);
+
+ Oid Root1Id = Oid::Zero;
+ Oid Root2Id = Oid::NewOid();
+
+ HttpClient Client(Instance.GetBaseUri());
+
+ CHECK(Client.Put(fmt::format("/ws/{}", Root1Id)).StatusCode == HttpResponseCode::BadRequest);
+
+ if (HttpClient::Response Root1Response =
+ Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}});
+ Root1Response.StatusCode == HttpResponseCode::Created)
+ {
+ Root1Id = Oid::TryFromHexString(Root1Response.AsText());
+ CHECK(Root1Id != Oid::Zero);
+ }
+ else
+ {
+ CHECK(false);
+ }
+ if (HttpClient::Response Root1Response =
+ Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}});
+ Root1Response.StatusCode == HttpResponseCode::OK)
+ {
+ CHECK(Root1Id == Oid::TryFromHexString(Root1Response.AsText()));
+ }
+ else
+ {
+ CHECK(false);
+ }
+ if (HttpClient::Response Root1Response =
+ Client.Put(fmt::format("/ws/{}", Root1Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}});
+ Root1Response.StatusCode == HttpResponseCode::OK)
+ {
+ CHECK(Root1Id == Oid::TryFromHexString(Root1Response.AsText()));
+ }
+ else
+ {
+ CHECK(false);
+ }
+ CHECK(Client.Put(fmt::format("/ws/{}", Root1Id), HttpClient::KeyValueMap{{"root_path", Root2Path.string()}}).StatusCode ==
+ HttpResponseCode::Conflict);
+
+ CHECK(
+ Client.Put(fmt::format("/ws/{}/{}", Root1Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
+ HttpResponseCode::Created);
+
+ CHECK(
+ Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
+ HttpResponseCode::NotFound);
+
+ CHECK(Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode ==
+ HttpResponseCode::Conflict);
+
+ if (HttpClient::Response Root2Response =
+ Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root2Path.string()}});
+ Root2Response.StatusCode == HttpResponseCode::Created)
+ {
+ CHECK(Root2Id == Oid::TryFromHexString(Root2Response.AsText()));
+ }
+ else
+ {
+ CHECK(false);
+ }
+
+ CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero)).StatusCode == HttpResponseCode::BadRequest);
+
+ Oid Share2Id = Oid::Zero;
+ if (HttpClient::Response Share2Response =
+ Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}});
+ Share2Response.StatusCode == HttpResponseCode::Created)
+ {
+ Share2Id = Oid::TryFromHexString(Share2Response.AsText());
+ CHECK(Share2Id != Oid::Zero);
+ }
+ else
+ {
+ CHECK(false);
+ }
+
+ CHECK(
+ Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
+ HttpResponseCode::OK);
+
+ CHECK(
+ Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
+ HttpResponseCode::OK);
+
+ CHECK(
+ Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode ==
+ HttpResponseCode::Conflict);
+
+ CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::NewOid()), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}})
+ .StatusCode == HttpResponseCode::Conflict);
+
+ CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", "idonotexist"}}).StatusCode !=
+ HttpResponseCode::OK);
+
+ while (true)
+ {
+ std::error_code Ec;
+ DeleteDirectories(Root2Path / Share2Path, Ec);
+ if (!Ec)
+ break;
+ }
+
+ CHECK(Client.Get(fmt::format("/ws/{}/{}/files", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound);
+}
+
+TEST_CASE("workspaces.restricted")
+{
+ using namespace std::literals;
+
+ std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
+
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+ ZenServerInstance Instance(TestEnv);
+ Instance.SetTestDir(TestDir);
+ const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ ScopedTemporaryDirectory TempDir;
+ std::filesystem::path Root1Path = TempDir.Path() / "root1";
+ std::filesystem::path Root2Path = TempDir.Path() / "root2";
+ DeleteDirectories(Root1Path);
+ DeleteDirectories(Root2Path);
+
+ std::filesystem::path Share1Path = "shared_1";
+ std::filesystem::path Share2Path = "shared_2";
+ CreateDirectories(Root1Path / Share1Path);
+ CreateDirectories(Root1Path / Share2Path);
+ CreateDirectories(Root2Path / Share1Path);
+ CreateDirectories(Root2Path / Share2Path);
+
+ Oid Root1Id = Oid::NewOid();
+ Oid Root2Id = Oid::NewOid();
+ Oid Share1Id = Oid::NewOid();
+ Oid Share2Id = Oid::NewOid();
+
+ HttpClient Client(Instance.GetBaseUri());
+ CHECK(Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode ==
+ HttpResponseCode::Unauthorized);
+
+ CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::NotFound);
+
+ std::string Config1;
+ {
+ CbObjectWriter Config;
+ Config.BeginArray("workspaces");
+ Config.BeginObject();
+ Config << "id"sv << Root1Id.ToString();
+ Config << "root_path"sv << Root1Path.string();
+ Config << "allow_share_creation_from_http"sv << false;
+ Config.EndObject();
+ Config.EndArray();
+ ExtendableStringBuilder<256> SB;
+ CompactBinaryToJson(Config.Save(), SB);
+ Config1 = SB.ToString();
+ }
+ WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config1.data(), Config1.size()));
+
+ CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode));
+
+ CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::OK);
+
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", Root1Id, Share1Id)).StatusCode == HttpResponseCode::NotFound);
+ CHECK(
+ Client.Put(fmt::format("/ws/{}/{}", Root1Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode ==
+ HttpResponseCode::Unauthorized);
+
+ std::string Config2;
+ {
+ CbObjectWriter Config;
+ Config.BeginArray("workspaces");
+ Config.BeginObject();
+ Config << "id"sv << Root1Id.ToString();
+ Config << "root_path"sv << Root1Path.string();
+ Config << "allow_share_creation_from_http"sv << false;
+ Config.EndObject();
+ Config.BeginObject();
+ Config << "id"sv << Root2Id.ToString();
+ Config << "root_path"sv << Root2Path.string();
+ Config << "allow_share_creation_from_http"sv << true;
+ Config.EndObject();
+ Config.EndArray();
+ ExtendableStringBuilder<256> SB;
+ CompactBinaryToJson(Config.Save(), SB);
+ Config2 = SB.ToString();
+ }
+ WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config2.data(), Config2.size()));
+
+ CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode));
+
+ CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root2Id)).StatusCode, HttpResponseCode::OK);
+
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound);
+ CHECK(
+ Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
+ HttpResponseCode::Created);
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::OK);
+
+ CHECK(IsHttpSuccessCode(Client.Delete(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode));
+}
+
+TEST_CASE("workspaces.lifetimes")
+{
+ using namespace std::literals;
+
+ std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
+
+ Oid WorkspaceId = Oid::NewOid();
+ Oid ShareId = Oid::NewOid();
+
+ ScopedTemporaryDirectory TempDir;
+ std::filesystem::path RootPath = TempDir.Path();
+ DeleteDirectories(RootPath);
+ std::filesystem::path SharePath = RootPath / "shared_folder";
+ CreateDirectories(SharePath);
+
+ {
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+ ZenServerInstance Instance(TestEnv);
+ Instance.SetTestDir(TestDir);
+ const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(
+ fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri());
+ CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode ==
+ HttpResponseCode::Created);
+ CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId);
+ CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode ==
+ HttpResponseCode::OK);
+
+ CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}})
+ .StatusCode == HttpResponseCode::Created);
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId);
+ CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}})
+ .StatusCode == HttpResponseCode::OK);
+ }
+
+ // Restart
+
+ {
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+ ZenServerInstance Instance(TestEnv);
+ Instance.SetTestDir(TestDir);
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri());
+ CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId);
+
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId);
+ }
+
+ // Wipe system config
+ DeleteDirectories(SystemRootPath);
+
+ // Restart
+
+ {
+ std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
+ ZenServerInstance Instance(TestEnv);
+ Instance.SetTestDir(TestDir);
+ const uint16_t PortNumber =
+ Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ HttpClient Client(Instance.GetBaseUri());
+ CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).StatusCode == HttpResponseCode::NotFound);
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).StatusCode == HttpResponseCode::NotFound);
+ }
+}
+
+TEST_CASE("workspaces.share")
+{
+ std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
+
+ ZenServerInstance Instance(TestEnv);
+
+ const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(
+ fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath));
+ CHECK(PortNumber != 0);
+
+ ScopedTemporaryDirectory TempDir;
+ std::filesystem::path RootPath = TempDir.Path();
+ DeleteDirectories(RootPath);
+ std::filesystem::path SharePath = RootPath / "shared_folder";
+ GenerateFolderContent(SharePath);
+
+ HttpClient Client(Instance.GetBaseUri());
+
+ Oid WorkspaceId = Oid::NewOid();
+ CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode ==
+ HttpResponseCode::Created);
+ CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId);
+
+ Oid ShareId = Oid::NewOid();
+ CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}}).StatusCode ==
+ HttpResponseCode::Created);
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId);
+
+ CHECK(Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId)).AsObject()["files"sv].AsArrayView().Num() == 8);
+ GenerateFolderContent2(SharePath);
+ CHECK(Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId)).AsObject()["files"sv].AsArrayView().Num() == 8);
+ HttpClient::Response FilesResponse =
+ Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId),
+ {},
+ HttpClient::KeyValueMap{{"refresh", ToString(true)}, {"fieldnames", "id,clientpath,size"}});
+ CHECK(FilesResponse);
+ std::unordered_map<Oid, std::pair<std::filesystem::path, uint64_t>, Oid::Hasher> Files;
+ {
+ CbArrayView FilesArray = FilesResponse.AsObject()["files"sv].AsArrayView();
+ CHECK(FilesArray.Num() == 12);
+ for (CbFieldView Field : FilesArray)
+ {
+ CbObjectView FileObject = Field.AsObjectView();
+ Oid ChunkId = FileObject["id"sv].AsObjectId();
+ CHECK(ChunkId != Oid::Zero);
+ uint64_t Size = FileObject["size"sv].AsUInt64();
+ std::u8string_view Path = FileObject["clientpath"sv].AsU8String();
+ std::filesystem::path AbsFilePath = SharePath / Path;
+ CHECK(IsFile(AbsFilePath));
+ CHECK(FileSizeFromPath(AbsFilePath) == Size);
+ Files.insert_or_assign(ChunkId, std::make_pair(AbsFilePath, Size));
+ }
+ }
+
+ HttpClient::Response EntriesResponse =
+ Client.Get(fmt::format("/ws/{}/{}/entries", WorkspaceId, ShareId), {}, HttpClient::KeyValueMap{{"fieldfilter", "id,clientpath"}});
+ CHECK(EntriesResponse);
+ {
+ CbArrayView EntriesArray = EntriesResponse.AsObject()["entries"sv].AsArrayView();
+ CHECK(EntriesArray.Num() == 1);
+ for (CbFieldView EntryField : EntriesArray)
+ {
+ CbObjectView EntryObject = EntryField.AsObjectView();
+ CbArrayView FilesArray = EntryObject["files"sv].AsArrayView();
+ CHECK(FilesArray.Num() == 12);
+ for (CbFieldView FileField : FilesArray)
+ {
+ CbObjectView FileObject = FileField.AsObjectView();
+ Oid ChunkId = FileObject["id"sv].AsObjectId();
+ CHECK(ChunkId != Oid::Zero);
+ std::u8string_view Path = FileObject["clientpath"sv].AsU8String();
+ std::filesystem::path AbsFilePath = SharePath / Path;
+ CHECK(IsFile(AbsFilePath));
+ }
+ }
+ }
+
+ HttpClient::Response FileManifestResponse =
+ Client.Get(fmt::format("/ws/{}/{}/entries", WorkspaceId, ShareId),
+ {},
+ HttpClient::KeyValueMap{{"opkey", "file_manifest"}, {"fieldfilter", "id,clientpath"}});
+ CHECK(FileManifestResponse);
+ {
+ CbArrayView EntriesArray = FileManifestResponse.AsObject()["entry"sv].AsObjectView()["files"sv].AsArrayView();
+ CHECK(EntriesArray.Num() == 12);
+ for (CbFieldView Field : EntriesArray)
+ {
+ CbObjectView FileObject = Field.AsObjectView();
+ Oid ChunkId = FileObject["id"sv].AsObjectId();
+ CHECK(ChunkId != Oid::Zero);
+ std::u8string_view Path = FileObject["clientpath"sv].AsU8String();
+ std::filesystem::path AbsFilePath = SharePath / Path;
+ CHECK(IsFile(AbsFilePath));
+ }
+ }
+
+ for (auto It : Files)
+ {
+ const Oid& ChunkId = It.first;
+ const std::filesystem::path& Path = It.second.first;
+ const uint64_t Size = It.second.second;
+
+ CHECK(Client.Get(fmt::format("/ws/{}/{}/{}/info", WorkspaceId, ShareId, ChunkId)).AsObject()["size"sv].AsUInt64() == Size);
+
+ {
+ IoBuffer Payload = Client.Get(fmt::format("/ws/{}/{}/{}", WorkspaceId, ShareId, ChunkId)).ResponsePayload;
+ CHECK(Payload);
+ CHECK(Payload.GetSize() == Size);
+ IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path);
+ CHECK(FileContent);
+ CHECK(FileContent.GetView().EqualBytes(Payload.GetView()));
+ }
+
+ {
+ IoBuffer Payload =
+ Client
+ .Get(fmt::format("/ws/{}/{}/{}", WorkspaceId, ShareId, ChunkId),
+ {},
+ HttpClient::KeyValueMap{{"offset", fmt::format("{}", Size / 4)}, {"size", fmt::format("{}", Size / 2)}})
+ .ResponsePayload;
+ CHECK(Payload);
+ CHECK(Payload.GetSize() == Size / 2);
+ IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path, Size / 4, Size / 2);
+ CHECK(FileContent);
+ CHECK(FileContent.GetView().EqualBytes(Payload.GetView()));
+ }
+ }
+
+ {
+ uint32_t CorrelationId = gsl::narrow<uint32_t>(Files.size());
+ std::vector<RequestChunkEntry> BatchEntries;
+ for (auto It : Files)
+ {
+ const Oid& ChunkId = It.first;
+ const uint64_t Size = It.second.second;
+
+ BatchEntries.push_back(
+ RequestChunkEntry{.ChunkId = ChunkId, .CorrelationId = --CorrelationId, .Offset = Size / 4, .RequestBytes = Size / 2});
+ }
+ IoBuffer BatchResponse =
+ Client.Post(fmt::format("/ws/{}/{}/batch", WorkspaceId, ShareId), BuildChunkBatchRequest(BatchEntries)).ResponsePayload;
+ CHECK(BatchResponse);
+ std::vector<IoBuffer> BatchResult = ParseChunkBatchResponse(BatchResponse);
+ CHECK(BatchResult.size() == Files.size());
+ for (const RequestChunkEntry& Request : BatchEntries)
+ {
+ IoBuffer Result = BatchResult[Request.CorrelationId];
+ auto It = Files.find(Request.ChunkId);
+ const std::filesystem::path& Path = It->second.first;
+ CHECK(Result.GetSize() == Request.RequestBytes);
+ IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path, Request.Offset, Request.RequestBytes);
+ CHECK(FileContent);
+ CHECK(FileContent.GetView().EqualBytes(Result.GetView()));
+ }
+ }
+
+ CHECK(Client.Delete(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)));
+ CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).StatusCode == HttpResponseCode::NotFound);
+ CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)));
+
+ CHECK(Client.Delete(fmt::format("/ws/{}", WorkspaceId)));
+ CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).StatusCode == HttpResponseCode::NotFound);
+}
+
+} // namespace zen::tests
+#endif
diff --git a/src/zenserver-test/xmake.lua b/src/zenserver-test/xmake.lua
index 17d0a3cb7..7832e1f72 100644
--- a/src/zenserver-test/xmake.lua
+++ b/src/zenserver-test/xmake.lua
@@ -6,7 +6,7 @@ target("zenserver-test")
add_headerfiles("**.h")
add_files("*.cpp")
add_files("zenserver-test.cpp", {unity_ignored = true })
- add_deps("zencore", "zenutil", "zenhttp")
+ add_deps("zencore", "zenremotestore", "zenhttp")
add_deps("zenserver", {inherit=false})
add_packages("vcpkg::cpr", "vcpkg::http-parser", "vcpkg::mimalloc")
diff --git a/src/zenserver-test/zenserver-test.cpp b/src/zenserver-test/zenserver-test.cpp
index 77ed87cb1..42296cbe1 100644
--- a/src/zenserver-test/zenserver-test.cpp
+++ b/src/zenserver-test/zenserver-test.cpp
@@ -2,78 +2,32 @@
#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
-#include <zenbase/refcount.h>
-#include <zencore/compactbinary.h>
-#include <zencore/compactbinarybuilder.h>
-#include <zencore/compactbinarypackage.h>
-#include <zencore/compress.h>
-#include <zencore/except.h>
-#include <zencore/filesystem.h>
-#include <zencore/fmtutils.h>
-#include <zencore/iohash.h>
-#include <zencore/logging.h>
-#include <zencore/memoryview.h>
-#include <zencore/scopeguard.h>
-#include <zencore/stream.h>
-#include <zencore/string.h>
-#include <zencore/testutils.h>
-#include <zencore/thread.h>
-#include <zencore/timer.h>
-#include <zencore/xxhash.h>
-#include <zenhttp/httpclient.h>
-#include <zenhttp/packageformat.h>
-#include <zenhttp/zenhttp.h>
-#include <zenutil/buildstoragecache.h>
-#include <zenutil/cache/cache.h>
-#include <zenutil/cache/cacherequests.h>
-#include <zenutil/chunkrequests.h>
-#include <zenutil/logging/testformatter.h>
-#include <zenutil/zenserverprocess.h>
-
-#include <http_parser.h>
-
-#if ZEN_PLATFORM_WINDOWS
-# pragma comment(lib, "Crypt32.lib")
-# pragma comment(lib, "Wldap32.lib")
-#endif
-
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
-#include <tsl/robin_set.h>
-#undef GetObject
-ZEN_THIRD_PARTY_INCLUDES_END
-
-#include <atomic>
-#include <filesystem>
-#include <map>
-#include <random>
-#include <span>
-#include <thread>
-#include <typeindex>
-#include <unordered_map>
-
-#if ZEN_PLATFORM_WINDOWS
-# include <ppl.h>
-# include <process.h>
-#endif
-
-#include <zencore/memory/newdelete.h>
-
-//////////////////////////////////////////////////////////////////////////
-
-#include "projectclient.h"
-
-//////////////////////////////////////////////////////////////////////////
-
#if ZEN_WITH_TESTS
-# define ZEN_TEST_WITH_RUNNER 1
-# include <zencore/testing.h>
-# include <zencore/workthreadpool.h>
-#endif
-
-using namespace std::literals;
-#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+# define ZEN_TEST_WITH_RUNNER 1
+# include "zenserver-test.h"
+
+# include <zencore/except.h>
+# include <zencore/fmtutils.h>
+# include <zencore/logging.h>
+# include <zencore/stream.h>
+# include <zencore/string.h>
+# include <zencore/testutils.h>
+# include <zencore/thread.h>
+# include <zencore/timer.h>
+# include <zenhttp/httpclient.h>
+# include <zenhttp/packageformat.h>
+# include <zenutil/logging/testformatter.h>
+# include <zenutil/zenserverprocess.h>
+
+# include <atomic>
+# include <filesystem>
+
+# if ZEN_PLATFORM_WINDOWS
+# include <ppl.h>
+# include <process.h>
+# else
+# include <thread>
struct Concurrency
{
template<typename... T>
@@ -90,12 +44,19 @@ struct Concurrency
}
}
};
-#endif
+# endif
+
+# include <zencore/memory/newdelete.h>
//////////////////////////////////////////////////////////////////////////
-#if ZEN_WITH_TESTS
+using namespace std::literals;
+
+//////////////////////////////////////////////////////////////////////////
+
+namespace zen::tests {
zen::ZenServerEnvironment TestEnv;
+}
int
main(int argc, char** argv)
@@ -107,7 +68,15 @@ main(int argc, char** argv)
IgnoreChildSignals();
# endif
+# if ZEN_WITH_TRACE
zen::TraceInit("zenserver-test");
+ TraceOptions TraceCommandlineOptions;
+ if (GetTraceOptionsFromCommandline(TraceCommandlineOptions))
+ {
+ TraceConfigure(TraceCommandlineOptions);
+ }
+# endif // ZEN_WITH_TRACE
+
zen::logging::InitializeLogging();
zen::logging::SetLogLevel(zen::logging::level::Debug);
@@ -133,7 +102,7 @@ main(int argc, char** argv)
}
}
- TestEnv.InitializeForTest(ProgramBaseDir, TestBaseDir, ServerClass);
+ zen::tests::TestEnv.InitializeForTest(ProgramBaseDir, TestBaseDir, ServerClass);
ZEN_INFO("Running tests...(base dir: '{}')", TestBaseDir);
@@ -145,16 +114,6 @@ main(int argc, char** argv)
namespace zen::tests {
-IoBuffer
-MakeCbObjectPayload(std::function<void(CbObjectWriter& Writer)> WriteCB)
-{
- CbObjectWriter Writer;
- WriteCB(Writer);
- IoBuffer Payload = Writer.Save().GetBuffer().AsIoBuffer();
- Payload.SetContentType(ZenContentType::kCbObject);
- return Payload;
-};
-
TEST_CASE("default.single")
{
std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
@@ -172,12 +131,12 @@ TEST_CASE("default.single")
const int ThreadId = zen::GetCurrentThreadId();
ZEN_INFO("query batch {} started (thread {})", BatchNo, ThreadId);
- cpr::Session cli;
- cli.SetUrl(cpr::Url{fmt::format("http://localhost:{}/test/hello", PortNumber)});
- for (int i = 0; i < 10000; ++i)
+ HttpClient Http{fmt::format("http://localhost:{}", PortNumber)};
+
+ for (int i = 0; i < 100; ++i)
{
- auto res = cli.Get();
+ auto res = Http.Get("/test/hello"sv);
++RequestCount;
}
ZEN_INFO("query batch {} ended (thread {})", BatchNo, ThreadId);
@@ -216,18 +175,20 @@ TEST_CASE("default.loopback")
SUBCASE("ipv4 endpoint connectivity")
{
- cpr::Session cli;
- cli.SetUrl(cpr::Url{fmt::format("http://127.0.0.1:{}/test/hello", PortNumber)});
- auto res = cli.Get();
- CHECK(!res.error);
+ HttpClient Http{fmt::format("http://127.0.0.1:{}", PortNumber)};
+
+ auto res = Http.Get("/test/hello"sv);
+
+ CHECK(res);
}
SUBCASE("ipv6 endpoint connectivity")
{
- cpr::Session cli;
- cli.SetUrl(cpr::Url{fmt::format("http://[::1]:{}/test/hello", PortNumber)});
- auto res = cli.Get();
- CHECK(!res.error);
+ HttpClient Http{fmt::format("http://[::1]:{}", PortNumber)};
+
+ auto res = Http.Get("/test/hello"sv);
+
+ CHECK(res);
}
}
@@ -259,14 +220,14 @@ TEST_CASE("multi.basic")
ZEN_INFO("query batch {} started (thread {}) for port {}", BatchNo, ThreadId, PortNumber);
- cpr::Session cli;
- cli.SetUrl(cpr::Url{fmt::format("http://localhost:{}/test/hello", PortNumber)});
+ HttpClient Http{fmt::format("http://localhost:{}", PortNumber)};
- for (int i = 0; i < 10000; ++i)
+ for (int i = 0; i < 100; ++i)
{
- auto res = cli.Get();
+ auto res = Http.Get("/test/hello"sv);
++RequestCount;
}
+
ZEN_INFO("query batch {} ended (thread {})", BatchNo, ThreadId);
};
@@ -287,2742 +248,6 @@ TEST_CASE("multi.basic")
zen::NiceRate(RequestCount, (uint32_t)Elapsed, "req"));
}
-TEST_CASE("project.basic")
-{
- using namespace std::literals;
-
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
-
- ZenServerInstance Instance1(TestEnv);
- Instance1.SetTestDir(TestDir);
-
- const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
-
- std::atomic<uint64_t> RequestCount{0};
-
- zen::Stopwatch timer;
-
- std::mt19937_64 mt;
-
- zen::StringBuilder<64> BaseUri;
- BaseUri << fmt::format("http://localhost:{}/prj/test", PortNumber);
-
- std::filesystem::path BinPath = zen::GetRunningExecutablePath();
- std::filesystem::path RootPath = BinPath.parent_path().parent_path();
- BinPath = BinPath.lexically_relative(RootPath);
-
- SUBCASE("build store init")
- {
- {
- {
- zen::CbObjectWriter Body;
- Body << "id"
- << "test";
- Body << "root" << RootPath.c_str();
- Body << "project"
- << "/zooom";
- Body << "engine"
- << "/zooom";
-
- zen::BinaryWriter MemOut;
- Body.Save(MemOut);
-
- auto Response = cpr::Post(cpr::Url{BaseUri.c_str()}, cpr::Body{(const char*)MemOut.Data(), MemOut.Size()});
- CHECK(Response.status_code == 201);
- }
-
- {
- auto Response = cpr::Get(cpr::Url{BaseUri.c_str()});
- CHECK(Response.status_code == 200);
-
- zen::CbObjectView ResponseObject = zen::CbFieldView(Response.text.data()).AsObjectView();
-
- CHECK(ResponseObject["id"].AsString() == "test"sv);
- CHECK(ResponseObject["root"].AsString() == PathToUtf8(RootPath.c_str()));
- }
- }
-
- BaseUri << "/oplog/foobar";
-
- {
- {
- zen::StringBuilder<64> PostUri;
- PostUri << BaseUri;
- auto Response = cpr::Post(cpr::Url{PostUri.c_str()});
- CHECK(Response.status_code == 201);
- }
-
- {
- auto Response = cpr::Get(cpr::Url{BaseUri.c_str()});
- CHECK(Response.status_code == 200);
-
- zen::CbObjectView ResponseObject = zen::CbFieldView(Response.text.data()).AsObjectView();
-
- CHECK(ResponseObject["id"].AsString() == "foobar"sv);
- CHECK(ResponseObject["project"].AsString() == "test"sv);
- }
- }
-
- SUBCASE("build store persistence")
- {
- uint8_t AttachData[] = {1, 2, 3};
-
- zen::CompressedBuffer Attachment = zen::CompressedBuffer::Compress(zen::SharedBuffer::Clone(zen::MemoryView{AttachData, 3}));
- zen::CbAttachment Attach{Attachment, Attachment.DecodeRawHash()};
-
- zen::CbObjectWriter OpWriter;
- OpWriter << "key"
- << "foo"
- << "attachment" << Attach;
-
- const std::string_view ChunkId{
- "00000000"
- "00000000"
- "00010000"};
- auto FileOid = zen::Oid::FromHexString(ChunkId);
-
- OpWriter.BeginArray("files");
- OpWriter.BeginObject();
- OpWriter << "id" << FileOid;
- OpWriter << "clientpath"
- << "/{engine}/client/side/path";
- OpWriter << "serverpath" << BinPath.c_str();
- OpWriter.EndObject();
- OpWriter.EndArray();
-
- zen::CbObject Op = OpWriter.Save();
-
- zen::CbPackage OpPackage(Op);
- OpPackage.AddAttachment(Attach);
-
- zen::BinaryWriter MemOut;
- legacy::SaveCbPackage(OpPackage, MemOut);
-
- {
- zen::StringBuilder<64> PostUri;
- PostUri << BaseUri << "/new";
- auto Response = cpr::Post(cpr::Url{PostUri.c_str()}, cpr::Body{(const char*)MemOut.Data(), MemOut.Size()});
-
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 201);
- }
-
- // Read file data
-
- {
- zen::StringBuilder<128> ChunkGetUri;
- ChunkGetUri << BaseUri << "/" << ChunkId;
- auto Response = cpr::Get(cpr::Url{ChunkGetUri.c_str()});
-
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 200);
- }
-
- {
- zen::StringBuilder<128> ChunkGetUri;
- ChunkGetUri << BaseUri << "/" << ChunkId << "?offset=1&size=10";
- auto Response = cpr::Get(cpr::Url{ChunkGetUri.c_str()});
-
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 200);
- CHECK(Response.text.size() == 10);
- }
-
- ZEN_INFO("+++++++");
- }
-
- SUBCASE("snapshot")
- {
- zen::CbObjectWriter OpWriter;
- OpWriter << "key"
- << "foo";
-
- const std::string_view ChunkId{
- "00000000"
- "00000000"
- "00010000"};
- auto FileOid = zen::Oid::FromHexString(ChunkId);
-
- OpWriter.BeginArray("files");
- OpWriter.BeginObject();
- OpWriter << "id" << FileOid;
- OpWriter << "clientpath"
- << "/{engine}/client/side/path";
- OpWriter << "serverpath" << BinPath.c_str();
- OpWriter.EndObject();
- OpWriter.EndArray();
-
- zen::CbObject Op = OpWriter.Save();
-
- zen::CbPackage OpPackage(Op);
-
- zen::BinaryWriter MemOut;
- legacy::SaveCbPackage(OpPackage, MemOut);
-
- {
- zen::StringBuilder<64> PostUri;
- PostUri << BaseUri << "/new";
- auto Response = cpr::Post(cpr::Url{PostUri.c_str()}, cpr::Body{(const char*)MemOut.Data(), MemOut.Size()});
-
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 201);
- }
-
- // Read file data, it is raw and uncompressed
- {
- zen::StringBuilder<128> ChunkGetUri;
- ChunkGetUri << BaseUri << "/" << ChunkId;
- auto Response = cpr::Get(cpr::Url{ChunkGetUri.c_str()});
-
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 200);
- IoBuffer Data(IoBuffer::Wrap, Response.text.data(), Response.text.length());
- IoBuffer ReferenceData = IoBufferBuilder::MakeFromFile(RootPath / BinPath);
- CHECK(ReferenceData.GetSize() == Data.GetSize());
- CHECK(ReferenceData.GetView().EqualBytes(Data.GetView()));
- }
-
- {
- IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) { Writer.AddString("method"sv, "snapshot"sv); });
- zen::StringBuilder<64> PostUri;
- PostUri << BaseUri << "/rpc";
- auto Response = cpr::Post(cpr::Url{PostUri.c_str()},
- cpr::Body{(const char*)Payload.Data(), Payload.Size()},
- cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 200);
- }
-
- // Read chunk data, it is now compressed
- {
- zen::StringBuilder<128> ChunkGetUri;
- ChunkGetUri << BaseUri << "/" << ChunkId;
- auto Response = cpr::Get(cpr::Url{ChunkGetUri.c_str()}, cpr::Header{{"Accept-Type", "application/x-ue-comp"}});
-
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 200);
- IoBuffer Data(IoBuffer::Wrap, Response.text.data(), Response.text.length());
- IoHash RawHash;
- uint64_t RawSize;
- CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Data), RawHash, RawSize);
- CHECK(Compressed);
- IoBuffer DataDecompressed = Compressed.Decompress().AsIoBuffer();
- IoBuffer ReferenceData = IoBufferBuilder::MakeFromFile(RootPath / BinPath);
- CHECK(RawSize == ReferenceData.GetSize());
- CHECK(ReferenceData.GetSize() == DataDecompressed.GetSize());
- CHECK(ReferenceData.GetView().EqualBytes(DataDecompressed.GetView()));
- }
-
- ZEN_INFO("+++++++");
- }
-
- SUBCASE("test chunk not found error")
- {
- for (size_t I = 0; I < 65; I++)
- {
- zen::StringBuilder<128> PostUri;
- PostUri << BaseUri << "/f77c781846caead318084604/info";
- auto Response = cpr::Get(cpr::Url{PostUri.c_str()});
-
- REQUIRE(!Response.error);
- CHECK(Response.status_code == 404);
- }
- }
- }
-
- const uint64_t Elapsed = timer.GetElapsedTimeMs();
-
- ZEN_INFO("{} requests in {} ({})",
- RequestCount.load(),
- zen::NiceTimeSpanMs(Elapsed),
- zen::NiceRate(RequestCount, (uint32_t)Elapsed, "req"));
-}
-
-namespace utils {
-
- struct ZenConfig
- {
- std::filesystem::path DataDir;
- uint16_t Port;
- std::string BaseUri;
- std::string Args;
-
- static ZenConfig New(std::string Args = "")
- {
- return ZenConfig{.DataDir = TestEnv.CreateNewTestDir(), .Port = TestEnv.GetNewPortNumber(), .Args = std::move(Args)};
- }
-
- static ZenConfig New(uint16_t Port, std::string Args = "")
- {
- return ZenConfig{.DataDir = TestEnv.CreateNewTestDir(), .Port = Port, .Args = std::move(Args)};
- }
-
- static ZenConfig NewWithUpstream(uint16_t Port, uint16_t UpstreamPort, std::string Args = "")
- {
- return New(Port,
- fmt::format("{}{}--debug --upstream-thread-count=0 --upstream-zen-url=http://localhost:{}",
- Args,
- Args.length() > 0 ? " " : "",
- UpstreamPort));
- }
-
- static ZenConfig NewWithThreadedUpstreams(uint16_t NewPort, std::span<uint16_t> UpstreamPorts, bool Debug)
- {
- std::string Args = Debug ? "--debug" : "";
- for (uint16_t Port : UpstreamPorts)
- {
- Args = fmt::format("{}{}--upstream-zen-url=http://localhost:{}", Args, Args.length() > 0 ? " " : "", Port);
- }
- return New(NewPort, Args);
- }
-
- void Spawn(ZenServerInstance& Inst)
- {
- Inst.SetTestDir(DataDir);
- Inst.SpawnServer(Port, Args);
- const uint16_t InstancePort = Inst.WaitUntilReady();
- CHECK_MESSAGE(InstancePort != 0, Inst.GetLogOutput());
-
- if (Port != InstancePort)
- ZEN_DEBUG("relocation detected from {} to {}", Port, InstancePort);
-
- Port = InstancePort;
- BaseUri = fmt::format("http://localhost:{}/z$", Port);
- }
- };
-
- void SpawnServer(ZenServerInstance& Server, ZenConfig& Cfg) { Cfg.Spawn(Server); }
-
- CompressedBuffer CreateSemiRandomBlob(size_t AttachmentSize, OodleCompressionLevel CompressionLevel = OodleCompressionLevel::VeryFast)
- {
- // Convoluted way to get a compressed buffer whose result it large enough to be a separate file
- // but also does actually compress
- const size_t PartCount = (AttachmentSize / (1u * 1024u * 64)) + 1;
- const size_t PartSize = AttachmentSize / PartCount;
- auto Part = SharedBuffer(CreateRandomBlob(PartSize));
- std::vector<SharedBuffer> Parts(PartCount, Part);
- size_t RemainPartSize = AttachmentSize - (PartSize * PartCount);
- if (RemainPartSize > 0)
- {
- Parts.push_back(SharedBuffer(CreateRandomBlob(RemainPartSize)));
- }
- CompressedBuffer Value = CompressedBuffer::Compress(CompositeBuffer(std::move(Parts)), OodleCompressor::Mermaid, CompressionLevel);
- return Value;
- };
-
- std::vector<std::pair<Oid, CompressedBuffer>> CreateAttachments(const std::span<const size_t>& Sizes)
- {
- std::vector<std::pair<Oid, CompressedBuffer>> Result;
- Result.reserve(Sizes.size());
- for (size_t Size : Sizes)
- {
- CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(CreateRandomBlob(Size)));
- Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed));
- }
- return Result;
- }
-
- std::vector<std::pair<Oid, CompressedBuffer>> CreateSemiRandomAttachments(const std::span<const size_t>& Sizes)
- {
- std::vector<std::pair<Oid, CompressedBuffer>> Result;
- Result.reserve(Sizes.size());
- for (size_t Size : Sizes)
- {
- CompressedBuffer Compressed =
- CreateSemiRandomBlob(Size, Size > 1024u * 1024u ? OodleCompressionLevel::None : OodleCompressionLevel::VeryFast);
- Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed));
- }
- return Result;
- }
-
-} // namespace utils
-
-TEST_CASE("zcache.basic")
-{
- using namespace std::literals;
-
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
-
- const int kIterationCount = 100;
-
- auto HashKey = [](int i) -> zen::IoHash { return zen::IoHash::HashBuffer(&i, sizeof i); };
-
- {
- ZenServerInstance Instance1(TestEnv);
- Instance1.SetTestDir(TestDir);
-
- const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
- const std::string BaseUri = fmt::format("http://localhost:{}/z$", PortNumber);
-
- // Populate with some simple data
-
- for (int i = 0; i < kIterationCount; ++i)
- {
- zen::CbObjectWriter Cbo;
- Cbo << "index" << i;
-
- zen::BinaryWriter MemOut;
- Cbo.Save(MemOut);
-
- zen::IoHash Key = HashKey(i);
-
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", BaseUri, "test", Key)},
- cpr::Body{(const char*)MemOut.Data(), MemOut.Size()},
- cpr::Header{{"Content-Type", "application/x-ue-cb"}});
-
- CHECK(Result.status_code == 201);
- }
-
- // Retrieve data
-
- for (int i = 0; i < kIterationCount; ++i)
- {
- zen::IoHash Key = zen::IoHash::HashBuffer(&i, sizeof i);
-
- cpr::Response Result =
- cpr::Get(cpr::Url{fmt::format("{}/{}/{}", BaseUri, "test", Key)}, cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
-
- CHECK(Result.status_code == 200);
- }
-
- // Ensure bad bucket identifiers are rejected
-
- {
- zen::CbObjectWriter Cbo;
- Cbo << "index" << 42;
-
- zen::BinaryWriter MemOut;
- Cbo.Save(MemOut);
-
- zen::IoHash Key = HashKey(442);
-
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", BaseUri, "te!st", Key)},
- cpr::Body{(const char*)MemOut.Data(), MemOut.Size()},
- cpr::Header{{"Content-Type", "application/x-ue-cb"}});
-
- CHECK(Result.status_code == 400);
- }
- }
-
- // Verify that the data persists between process runs (the previous server has exited at this point)
-
- {
- ZenServerInstance Instance1(TestEnv);
- Instance1.SetTestDir(TestDir);
- const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
-
- const std::string BaseUri = fmt::format("http://localhost:{}/z$", PortNumber);
-
- // Retrieve data again
-
- for (int i = 0; i < kIterationCount; ++i)
- {
- zen::IoHash Key = HashKey(i);
-
- cpr::Response Result =
- cpr::Get(cpr::Url{fmt::format("{}/{}/{}", BaseUri, "test", Key)}, cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
-
- CHECK(Result.status_code == 200);
- }
- }
-}
-
-TEST_CASE("zcache.cbpackage")
-{
- using namespace std::literals;
-
- auto CreateTestPackage = [](zen::IoHash& OutAttachmentKey) -> zen::CbPackage {
- auto Data = zen::SharedBuffer::Clone(zen::MakeMemoryView<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9}));
- auto CompressedData = zen::CompressedBuffer::Compress(Data);
-
- OutAttachmentKey = CompressedData.DecodeRawHash();
-
- zen::CbWriter Obj;
- Obj.BeginObject("obj"sv);
- Obj.AddBinaryAttachment("data", OutAttachmentKey);
- Obj.EndObject();
-
- zen::CbPackage Package;
- Package.SetObject(Obj.Save().AsObject());
- Package.AddAttachment(zen::CbAttachment(CompressedData, OutAttachmentKey));
-
- return Package;
- };
-
- auto SerializeToBuffer = [](zen::CbPackage Package) -> zen::IoBuffer {
- zen::BinaryWriter MemStream;
-
- Package.Save(MemStream);
-
- return zen::IoBuffer(zen::IoBuffer::Clone, MemStream.Data(), MemStream.Size());
- };
-
- auto IsEqual = [](zen::CbPackage Lhs, zen::CbPackage Rhs) -> bool {
- std::span<const zen::CbAttachment> LhsAttachments = Lhs.GetAttachments();
- std::span<const zen::CbAttachment> RhsAttachments = Rhs.GetAttachments();
-
- if (LhsAttachments.size() != RhsAttachments.size())
- {
- return false;
- }
-
- for (const zen::CbAttachment& LhsAttachment : LhsAttachments)
- {
- const zen::CbAttachment* RhsAttachment = Rhs.FindAttachment(LhsAttachment.GetHash());
- CHECK(RhsAttachment);
-
- zen::SharedBuffer LhsBuffer = LhsAttachment.AsCompressedBinary().Decompress();
- CHECK(!LhsBuffer.IsNull());
-
- zen::SharedBuffer RhsBuffer = RhsAttachment->AsCompressedBinary().Decompress();
- CHECK(!RhsBuffer.IsNull());
-
- if (!LhsBuffer.GetView().EqualBytes(RhsBuffer.GetView()))
- {
- return false;
- }
- }
-
- return true;
- };
-
- SUBCASE("PUT/GET returns correct package")
- {
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
-
- ZenServerInstance Instance1(TestEnv);
- Instance1.SetTestDir(TestDir);
- const uint16_t PortNumber = Instance1.SpawnServerAndWaitUntilReady();
- const std::string BaseUri = fmt::format("http://localhost:{}/z$", PortNumber);
-
- const std::string_view Bucket = "mosdef"sv;
- zen::IoHash Key;
- zen::CbPackage ExpectedPackage = CreateTestPackage(Key);
-
- // PUT
- {
- zen::IoBuffer Body = SerializeToBuffer(ExpectedPackage);
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", BaseUri, Bucket, Key)},
- cpr::Body{(const char*)Body.Data(), Body.Size()},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 201);
- }
-
- // GET
- {
- cpr::Response Result =
- cpr::Get(cpr::Url{fmt::format("{}/{}/{}", BaseUri, Bucket, Key)}, cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
-
- zen::IoBuffer Response(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size());
-
- zen::CbPackage Package;
- const bool Ok = Package.TryLoad(Response);
- CHECK(Ok);
- CHECK(IsEqual(Package, ExpectedPackage));
- }
- }
-
- SUBCASE("PUT propagates upstream")
- {
- // Setup local and remote server
- std::filesystem::path LocalDataDir = TestEnv.CreateNewTestDir();
- std::filesystem::path RemoteDataDir = TestEnv.CreateNewTestDir();
-
- ZenServerInstance RemoteInstance(TestEnv);
- RemoteInstance.SetTestDir(RemoteDataDir);
- const uint16_t RemotePortNumber = RemoteInstance.SpawnServerAndWaitUntilReady();
-
- ZenServerInstance LocalInstance(TestEnv);
- LocalInstance.SetTestDir(LocalDataDir);
- LocalInstance.SpawnServer(TestEnv.GetNewPortNumber(),
- fmt::format("--upstream-thread-count=0 --upstream-zen-url=http://localhost:{}", RemotePortNumber));
- const uint16_t LocalPortNumber = LocalInstance.WaitUntilReady();
- CHECK_MESSAGE(LocalPortNumber != 0, LocalInstance.GetLogOutput());
-
- const auto LocalBaseUri = fmt::format("http://localhost:{}/z$", LocalPortNumber);
- const auto RemoteBaseUri = fmt::format("http://localhost:{}/z$", RemotePortNumber);
-
- const std::string_view Bucket = "mosdef"sv;
- zen::IoHash Key;
- zen::CbPackage ExpectedPackage = CreateTestPackage(Key);
-
- // Store the cache record package in the local instance
- {
- zen::IoBuffer Body = SerializeToBuffer(ExpectedPackage);
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", LocalBaseUri, Bucket, Key)},
- cpr::Body{(const char*)Body.Data(), Body.Size()},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}});
-
- CHECK(Result.status_code == 201);
- }
-
- // The cache record can be retrieved as a package from the local instance
- {
- cpr::Response Result =
- cpr::Get(cpr::Url{fmt::format("{}/{}/{}", LocalBaseUri, Bucket, Key)}, cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
-
- zen::IoBuffer Body(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size());
- zen::CbPackage Package;
- const bool Ok = Package.TryLoad(Body);
- CHECK(Ok);
- CHECK(IsEqual(Package, ExpectedPackage));
- }
-
- // The cache record can be retrieved as a package from the remote instance
- {
- cpr::Response Result =
- cpr::Get(cpr::Url{fmt::format("{}/{}/{}", RemoteBaseUri, Bucket, Key)}, cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
-
- zen::IoBuffer Body(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size());
- zen::CbPackage Package;
- const bool Ok = Package.TryLoad(Body);
- CHECK(Ok);
- CHECK(IsEqual(Package, ExpectedPackage));
- }
- }
-
- SUBCASE("GET finds upstream when missing in local")
- {
- // Setup local and remote server
- std::filesystem::path LocalDataDir = TestEnv.CreateNewTestDir();
- std::filesystem::path RemoteDataDir = TestEnv.CreateNewTestDir();
-
- ZenServerInstance RemoteInstance(TestEnv);
- RemoteInstance.SetTestDir(RemoteDataDir);
- const uint16_t RemotePortNumber = RemoteInstance.SpawnServerAndWaitUntilReady();
-
- ZenServerInstance LocalInstance(TestEnv);
- LocalInstance.SetTestDir(LocalDataDir);
- LocalInstance.SpawnServer(TestEnv.GetNewPortNumber(),
- fmt::format("--upstream-thread-count=0 --upstream-zen-url=http://localhost:{}", RemotePortNumber));
- const uint16_t LocalPortNumber = LocalInstance.WaitUntilReady();
- CHECK_MESSAGE(LocalPortNumber != 0, LocalInstance.GetLogOutput());
-
- const auto LocalBaseUri = fmt::format("http://localhost:{}/z$", LocalPortNumber);
- const auto RemoteBaseUri = fmt::format("http://localhost:{}/z$", RemotePortNumber);
-
- const std::string_view Bucket = "mosdef"sv;
- zen::IoHash Key;
- zen::CbPackage ExpectedPackage = CreateTestPackage(Key);
-
- // Store the cache record package in upstream cache
- {
- zen::IoBuffer Body = SerializeToBuffer(ExpectedPackage);
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", RemoteBaseUri, Bucket, Key)},
- cpr::Body{(const char*)Body.Data(), Body.Size()},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}});
-
- CHECK(Result.status_code == 201);
- }
-
- // The cache record can be retrieved as a package from the local cache
- {
- cpr::Response Result =
- cpr::Get(cpr::Url{fmt::format("{}/{}/{}", LocalBaseUri, Bucket, Key)}, cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
-
- zen::IoBuffer Body(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size());
- zen::CbPackage Package;
- const bool Ok = Package.TryLoad(Body);
- CHECK(Ok);
- CHECK(IsEqual(Package, ExpectedPackage));
- }
- }
-}
-
-TEST_CASE("zcache.policy")
-{
- using namespace std::literals;
- using namespace utils;
-
- auto GenerateData = [](uint64_t Size, zen::IoHash& OutHash) -> zen::UniqueBuffer {
- auto Buf = zen::UniqueBuffer::Alloc(Size);
- uint8_t* Data = reinterpret_cast<uint8_t*>(Buf.GetData());
- for (uint64_t Idx = 0; Idx < Size; Idx++)
- {
- Data[Idx] = Idx % 256;
- }
- OutHash = zen::IoHash::HashBuffer(Data, Size);
- return Buf;
- };
-
- auto GeneratePackage = [](zen::IoHash& OutRecordKey, zen::IoHash& OutAttachmentKey) -> zen::CbPackage {
- auto Data = zen::SharedBuffer::Clone(zen::MakeMemoryView<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9}));
- auto CompressedData = zen::CompressedBuffer::Compress(Data);
- OutAttachmentKey = CompressedData.DecodeRawHash();
-
- zen::CbWriter Writer;
- Writer.BeginObject("obj"sv);
- Writer.AddBinaryAttachment("data", OutAttachmentKey);
- Writer.EndObject();
- CbObject CacheRecord = Writer.Save().AsObject();
-
- OutRecordKey = IoHash::HashBuffer(CacheRecord.GetBuffer().GetView());
-
- zen::CbPackage Package;
- Package.SetObject(CacheRecord);
- Package.AddAttachment(zen::CbAttachment(CompressedData, OutAttachmentKey));
-
- return Package;
- };
-
- auto ToBuffer = [](zen::CbPackage Package) -> zen::IoBuffer {
- zen::BinaryWriter MemStream;
- Package.Save(MemStream);
-
- return zen::IoBuffer(zen::IoBuffer::Clone, MemStream.Data(), MemStream.Size());
- };
-
- SUBCASE("query - 'local' does not query upstream (binary)")
- {
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamInst(TestEnv);
- UpstreamCfg.Spawn(UpstreamInst);
- const uint16_t UpstreamPort = UpstreamCfg.Port;
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamPort);
- ZenServerInstance LocalInst(TestEnv);
- LocalCfg.Spawn(LocalInst);
-
- const std::string_view Bucket = "legacy"sv;
-
- zen::IoHash Key;
- auto BinaryValue = GenerateData(1024, Key);
-
- // Store binary cache value upstream
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", UpstreamCfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)BinaryValue.GetData(), BinaryValue.GetSize()},
- cpr::Header{{"Content-Type", "application/octet-stream"}});
- CHECK(Result.status_code == 201);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}?Policy=QueryLocal,Store", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/octet-stream"}});
- CHECK(Result.status_code == 404);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}?Policy=Query,Store", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/octet-stream"}});
- CHECK(Result.status_code == 200);
- }
- }
-
- SUBCASE("store - 'local' does not store upstream (binary)")
- {
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamInst(TestEnv);
- UpstreamCfg.Spawn(UpstreamInst);
- const uint16_t UpstreamPort = UpstreamCfg.Port;
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamPort);
- ZenServerInstance LocalInst(TestEnv);
- LocalCfg.Spawn(LocalInst);
-
- const auto Bucket = "legacy"sv;
-
- zen::IoHash Key;
- auto BinaryValue = GenerateData(1024, Key);
-
- // Store binary cache value locally
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}?Policy=Query,StoreLocal", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)BinaryValue.GetData(), BinaryValue.GetSize()},
- cpr::Header{{"Content-Type", "application/octet-stream"}});
- CHECK(Result.status_code == 201);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", UpstreamCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/octet-stream"}});
- CHECK(Result.status_code == 404);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/octet-stream"}});
- CHECK(Result.status_code == 200);
- }
- }
-
- SUBCASE("store - 'local/remote' stores local and upstream (binary)")
- {
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamInst(TestEnv);
- UpstreamCfg.Spawn(UpstreamInst);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalInst(TestEnv);
- LocalCfg.Spawn(LocalInst);
-
- const auto Bucket = "legacy"sv;
-
- zen::IoHash Key;
- auto BinaryValue = GenerateData(1024, Key);
-
- // Store binary cache value locally and upstream
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}?Policy=Query,Store", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)BinaryValue.GetData(), BinaryValue.GetSize()},
- cpr::Header{{"Content-Type", "application/octet-stream"}});
- CHECK(Result.status_code == 201);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", UpstreamCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/octet-stream"}});
- CHECK(Result.status_code == 200);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/octet-stream"}});
- CHECK(Result.status_code == 200);
- }
- }
-
- SUBCASE("query - 'local' does not query upstream (cppackage)")
- {
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamInst(TestEnv);
- UpstreamCfg.Spawn(UpstreamInst);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalInst(TestEnv);
- LocalCfg.Spawn(LocalInst);
-
- const auto Bucket = "legacy"sv;
-
- zen::IoHash Key;
- zen::IoHash PayloadId;
- zen::CbPackage Package = GeneratePackage(Key, PayloadId);
- auto Buf = ToBuffer(Package);
-
- // Store package upstream
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", UpstreamCfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)Buf.GetData(), Buf.GetSize()},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 201);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}?Policy=QueryLocal,Store", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 404);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}?Policy=Query,Store", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
- }
- }
-
- SUBCASE("store - 'local' does not store upstream (cbpackge)")
- {
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamInst(TestEnv);
- UpstreamCfg.Spawn(UpstreamInst);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalInst(TestEnv);
- LocalCfg.Spawn(LocalInst);
-
- const auto Bucket = "legacy"sv;
-
- zen::IoHash Key;
- zen::IoHash PayloadId;
- zen::CbPackage Package = GeneratePackage(Key, PayloadId);
- auto Buf = ToBuffer(Package);
-
- // Store packge locally
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}?Policy=Query,StoreLocal", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)Buf.GetData(), Buf.GetSize()},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 201);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", UpstreamCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 404);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
- }
- }
-
- SUBCASE("store - 'local/remote' stores local and upstream (cbpackage)")
- {
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamInst(TestEnv);
- UpstreamCfg.Spawn(UpstreamInst);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalInst(TestEnv);
- LocalCfg.Spawn(LocalInst);
-
- const auto Bucket = "legacy"sv;
-
- zen::IoHash Key;
- zen::IoHash PayloadId;
- zen::CbPackage Package = GeneratePackage(Key, PayloadId);
- auto Buf = ToBuffer(Package);
-
- // Store package locally and upstream
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}?Policy=Query,Store", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)Buf.GetData(), Buf.GetSize()},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 201);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", UpstreamCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
- }
-
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}", LocalCfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 200);
- }
- }
-
- SUBCASE("skip - 'data' returns cache record without attachments/empty payload")
- {
- ZenConfig Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance Instance(TestEnv);
- Cfg.Spawn(Instance);
-
- const auto Bucket = "test"sv;
-
- zen::IoHash Key;
- zen::IoHash PayloadId;
- zen::CbPackage Package = GeneratePackage(Key, PayloadId);
- auto Buf = ToBuffer(Package);
-
- // Store package
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", Cfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)Buf.GetData(), Buf.GetSize()},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}});
- CHECK(Result.status_code == 201);
- }
-
- // Get package
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}?Policy=Default,SkipData", Cfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cbpkg"}});
- CHECK(IsHttpSuccessCode(Result.status_code));
- IoBuffer Buffer(IoBuffer::Wrap, Result.text.c_str(), Result.text.size());
- CbPackage ResponsePackage;
- CHECK(ResponsePackage.TryLoad(Buffer));
- CHECK(ResponsePackage.GetAttachments().size() == 0);
- }
-
- // Get record
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}?Policy=Default,SkipData", Cfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/x-ue-cb"}});
- CHECK(IsHttpSuccessCode(Result.status_code));
- IoBuffer Buffer(IoBuffer::Wrap, Result.text.c_str(), Result.text.size());
- CbObject ResponseObject = zen::LoadCompactBinaryObject(Buffer);
- CHECK((bool)ResponseObject);
- }
-
- // Get payload
- {
- cpr::Response Result =
- cpr::Get(cpr::Url{fmt::format("{}/{}/{}/{}?Policy=Default,SkipData", Cfg.BaseUri, Bucket, Key, PayloadId)},
- cpr::Header{{"Accept", "application/x-ue-comp"}});
- CHECK(IsHttpSuccessCode(Result.status_code));
- CHECK(Result.text.size() == 0);
- }
- }
-
- SUBCASE("skip - 'data' returns empty binary value")
- {
- ZenConfig Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance Instance(TestEnv);
- Cfg.Spawn(Instance);
-
- const auto Bucket = "test"sv;
-
- zen::IoHash Key;
- auto BinaryValue = GenerateData(1024, Key);
-
- // Store binary cache value
- {
- cpr::Response Result = cpr::Put(cpr::Url{fmt::format("{}/{}/{}", Cfg.BaseUri, Bucket, Key)},
- cpr::Body{(const char*)BinaryValue.GetData(), BinaryValue.GetSize()},
- cpr::Header{{"Content-Type", "application/octet-stream"}});
- CHECK(Result.status_code == 201);
- }
-
- // Get package
- {
- cpr::Response Result = cpr::Get(cpr::Url{fmt::format("{}/{}/{}?Policy=Default,SkipData", Cfg.BaseUri, Bucket, Key)},
- cpr::Header{{"Accept", "application/octet-stream"}});
- CHECK(IsHttpSuccessCode(Result.status_code));
- CHECK(Result.text.size() == 0);
- }
- }
-}
-
-TEST_CASE("zcache.rpc")
-{
- using namespace std::literals;
-
- auto AppendCacheRecord = [](cacherequests::PutCacheRecordsRequest& Request,
- const zen::CacheKey& CacheKey,
- size_t PayloadSize,
- CachePolicy RecordPolicy) {
- std::vector<uint8_t> Data;
- Data.resize(PayloadSize);
- uint32_t DataSeed = *reinterpret_cast<const uint32_t*>(&CacheKey.Hash.Hash[0]);
- uint16_t* DataPtr = reinterpret_cast<uint16_t*>(Data.data());
- for (size_t Idx = 0; Idx < PayloadSize / 2; ++Idx)
- {
- DataPtr[Idx] = static_cast<uint16_t>((Idx + DataSeed) % 0xffffu);
- }
- if (PayloadSize & 1)
- {
- Data[PayloadSize - 1] = static_cast<uint8_t>((PayloadSize - 1) & 0xff);
- }
- CompressedBuffer Value = zen::CompressedBuffer::Compress(SharedBuffer::MakeView(Data.data(), Data.size()));
- Request.Requests.push_back({.Key = CacheKey, .Values = {{.Id = Oid::NewOid(), .Body = std::move(Value)}}, .Policy = RecordPolicy});
- };
-
- auto PutCacheRecords = [&AppendCacheRecord](std::string_view BaseUri,
- std::string_view Namespace,
- std::string_view Bucket,
- size_t Num,
- size_t PayloadSize = 1024,
- size_t KeyOffset = 1,
- CachePolicy PutPolicy = CachePolicy::Default,
- std::vector<CbPackage>* OutPackages = nullptr) -> std::vector<CacheKey> {
- std::vector<zen::CacheKey> OutKeys;
-
- for (uint32_t Key = 1; Key <= Num; ++Key)
- {
- zen::IoHash KeyHash;
- ((uint32_t*)(KeyHash.Hash))[0] = gsl::narrow<uint32_t>(KeyOffset + Key);
- const zen::CacheKey CacheKey = zen::CacheKey::Create(Bucket, KeyHash);
-
- cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
- AppendCacheRecord(Request, CacheKey, PayloadSize, PutPolicy);
- OutKeys.push_back(CacheKey);
-
- CbPackage Package;
- CHECK(Request.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- CHECK(Result.status_code == 200);
- if (OutPackages)
- {
- OutPackages->emplace_back(std::move(Package));
- }
- }
-
- return OutKeys;
- };
-
- struct GetCacheRecordResult
- {
- zen::CbPackage Response;
- cacherequests::GetCacheRecordsResult Result;
- bool Success;
- };
-
- auto GetCacheRecords = [](std::string_view BaseUri,
- std::string_view Namespace,
- std::span<zen::CacheKey> Keys,
- zen::CachePolicy Policy,
- zen::RpcAcceptOptions AcceptOptions = zen::RpcAcceptOptions::kNone,
- int Pid = 0) -> GetCacheRecordResult {
- cacherequests::GetCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
- .AcceptOptions = static_cast<uint16_t>(AcceptOptions),
- .ProcessPid = Pid,
- .DefaultPolicy = Policy,
- .Namespace = std::string(Namespace)};
- for (const CacheKey& Key : Keys)
- {
- Request.Requests.push_back({.Key = Key});
- }
-
- CbObjectWriter RequestWriter;
- CHECK(Request.Format(RequestWriter));
-
- BinaryWriter Body;
- RequestWriter.Save(Body);
-
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cb"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- GetCacheRecordResult OutResult;
-
- if (Result.status_code == 200)
- {
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- CHECK(!Response.IsNull());
- OutResult.Response = std::move(Response);
- CHECK(OutResult.Result.Parse(OutResult.Response));
- OutResult.Success = true;
- }
-
- return OutResult;
- };
-
- SUBCASE("get cache records")
- {
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
-
- ZenServerInstance Inst(TestEnv);
- Inst.SetTestDir(TestDir);
-
- const uint16_t BasePort = Inst.SpawnServerAndWaitUntilReady();
- const std::string BaseUri = fmt::format("http://localhost:{}/z$", BasePort);
-
- CachePolicy Policy = CachePolicy::Default;
- std::vector<zen::CacheKey> Keys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 128);
- GetCacheRecordResult Result = GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record);
- CHECK(Record->Key == ExpectedKey);
- CHECK(Record->Values.size() == 1);
-
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- CHECK(Value.Body);
- }
- }
- }
-
- SUBCASE("get missing cache records")
- {
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
-
- ZenServerInstance Inst(TestEnv);
- Inst.SetTestDir(TestDir);
- const uint16_t BasePort = Inst.SpawnServerAndWaitUntilReady();
- const std::string BaseUri = fmt::format("http://localhost:{}/z$", BasePort);
-
- CachePolicy Policy = CachePolicy::Default;
- std::vector<zen::CacheKey> ExistingKeys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 128);
- std::vector<zen::CacheKey> Keys;
-
- for (const zen::CacheKey& Key : ExistingKeys)
- {
- Keys.push_back(Key);
- Keys.push_back(CacheKey::Create("missing"sv, IoHash::Zero));
- }
-
- GetCacheRecordResult Result = GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- size_t KeyIndex = 0;
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- const bool Missing = Index++ % 2 != 0;
-
- if (Missing)
- {
- CHECK(!Record);
- }
- else
- {
- const CacheKey& ExpectedKey = ExistingKeys[KeyIndex++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- CHECK(Value.Body);
- }
- }
- }
- }
-
- SUBCASE("policy - 'QueryLocal' does not query upstream")
- {
- using namespace utils;
-
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamServer(TestEnv);
- SpawnServer(UpstreamServer, UpstreamCfg);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- std::vector<zen::CacheKey> Keys = PutCacheRecords(UpstreamCfg.BaseUri, "ue4.ddc"sv, "mastodon"sv, 4);
-
- CachePolicy Policy = CachePolicy::QueryLocal;
- GetCacheRecordResult Result = GetCacheRecords(LocalCfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(!Record);
- }
- }
-
- SUBCASE("policy - 'QueryRemote' does query upstream")
- {
- using namespace utils;
-
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamServer(TestEnv);
- SpawnServer(UpstreamServer, UpstreamCfg);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- std::vector<zen::CacheKey> Keys = PutCacheRecords(UpstreamCfg.BaseUri, "ue4.ddc"sv, "mastodon"sv, 4);
-
- CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
- GetCacheRecordResult Result = GetCacheRecords(LocalCfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- }
- }
-
- SUBCASE("policy - 'QueryLocal' on put allows overwrite with differing value when not limiting overwrites")
- {
- using namespace utils;
-
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamServer(TestEnv);
- SpawnServer(UpstreamServer, UpstreamCfg);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- size_t PayloadSize = 1024;
- std::string_view Namespace("ue4.ddc"sv);
- std::string_view Bucket("mastodon"sv);
- const size_t NumRecords = 4;
- std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize);
-
- for (const zen::CacheKey& CacheKey : Keys)
- {
- cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
- AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Default);
-
- CbPackage Package;
- CHECK(Request.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", LocalCfg.BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- CHECK(Result.status_code == 200);
- cacherequests::PutCacheRecordsResult ParsedResult;
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- CHECK(!Response.IsNull());
- CHECK(ParsedResult.Parse(Response));
- for (bool ResponseSuccess : ParsedResult.Success)
- {
- CHECK(ResponseSuccess);
- }
- }
-
- auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
- CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
- GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- CHECK(Value.RawSize == PayloadSize * 2);
- }
- }
- };
-
- // Check that the records are present and overwritten in the local server
- CheckRecordCorrectness(LocalCfg);
-
- // Check that the records are present and overwritten in the upstream server
- CheckRecordCorrectness(UpstreamCfg);
- }
-
- SUBCASE("policy - 'QueryLocal' on put denies overwrite with differing value when limiting overwrites")
- {
- using namespace utils;
-
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamServer(TestEnv);
- SpawnServer(UpstreamServer, UpstreamCfg);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port, "--cache-bucket-limit-overwrites");
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- size_t PayloadSize = 1024;
- std::string_view Namespace("ue4.ddc"sv);
- std::string_view Bucket("mastodon"sv);
- const size_t NumRecords = 4;
- std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize);
-
- for (const zen::CacheKey& CacheKey : Keys)
- {
- cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
- AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Default);
-
- CbPackage Package;
- CHECK(Request.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", LocalCfg.BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- CHECK(Result.status_code == 200);
- cacherequests::PutCacheRecordsResult ParsedResult;
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- CHECK(!Response.IsNull());
- CHECK(ParsedResult.Parse(Response));
- for (bool ResponseSuccess : ParsedResult.Success)
- {
- CHECK(!ResponseSuccess);
- }
- }
-
- auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
- CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
- GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- CHECK(Value.RawSize == PayloadSize);
- }
- }
- };
-
- // Check that the records are present and not overwritten in the local server
- CheckRecordCorrectness(LocalCfg);
-
- // Check that the records are present and not overwritten in the upstream server
- CheckRecordCorrectness(UpstreamCfg);
- }
-
- SUBCASE("policy - no 'QueryLocal' on put allows overwrite with differing value when limiting overwrites")
- {
- using namespace utils;
-
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamServer(TestEnv);
- SpawnServer(UpstreamServer, UpstreamCfg);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port, "--cache-bucket-limit-overwrites");
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- size_t PayloadSize = 1024;
- std::string_view Namespace("ue4.ddc"sv);
- std::string_view Bucket("mastodon"sv);
- const size_t NumRecords = 4;
- std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize);
-
- for (const zen::CacheKey& CacheKey : Keys)
- {
- cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
- AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Store);
-
- CbPackage Package;
- CHECK(Request.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", LocalCfg.BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- CHECK(Result.status_code == 200);
- cacherequests::PutCacheRecordsResult ParsedResult;
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- CHECK(!Response.IsNull());
- CHECK(ParsedResult.Parse(Response));
- for (bool ResponseSuccess : ParsedResult.Success)
- {
- CHECK(ResponseSuccess);
- }
- }
-
- auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
- CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
- GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- CHECK(Value.RawSize == PayloadSize * 2);
- }
- }
- };
-
- // Check that the records are present and overwritten in the local server
- CheckRecordCorrectness(LocalCfg);
-
- // Check that the records are present and overwritten in the upstream server
- CheckRecordCorrectness(UpstreamCfg);
- }
-
- SUBCASE("policy - 'QueryLocal' on put allows overwrite with equivalent value when limiting overwrites")
- {
- using namespace utils;
-
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamServer(TestEnv);
- SpawnServer(UpstreamServer, UpstreamCfg);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port, "--cache-bucket-limit-overwrites");
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- size_t PayloadSize = 1024;
- std::string_view Namespace("ue4.ddc"sv);
- std::string_view Bucket("mastodon"sv);
- const size_t NumRecords = 4;
- std::vector<CbPackage> Packages;
- std::vector<zen::CacheKey> Keys =
- PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize, 1, CachePolicy::Default, &Packages);
-
- for (const CbPackage& Package : Packages)
- {
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", LocalCfg.BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- CHECK(Result.status_code == 200);
- cacherequests::PutCacheRecordsResult ParsedResult;
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- CHECK(!Response.IsNull());
- CHECK(ParsedResult.Parse(Response));
- for (bool ResponseSuccess : ParsedResult.Success)
- {
- CHECK(ResponseSuccess);
- }
- }
-
- auto CheckRecordCorrectness = [&](const ZenConfig& Cfg) {
- CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
- GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- CHECK(Value.RawSize == PayloadSize);
- }
- }
- };
-
- // Check that the records are present and unchanged in the local server
- CheckRecordCorrectness(LocalCfg);
-
- // Check that the records are present and unchanged in the upstream server
- CheckRecordCorrectness(UpstreamCfg);
- }
-
- // TODO: Propagation for rejected PUTs
- // SUBCASE("policy - 'QueryLocal' on put denies overwrite with differing value when limiting overwrites but allows propagation to
- // upstream")
- // {
- // using namespace utils;
-
- // ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- // ZenServerInstance UpstreamServer(TestEnv);
- // SpawnServer(UpstreamServer, UpstreamCfg);
-
- // ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port,
- // "--cache-bucket-limit-overwrites"); ZenServerInstance LocalServer(TestEnv); SpawnServer(LocalServer, LocalCfg);
-
- // size_t PayloadSize = 1024;
- // std::string_view Namespace("ue4.ddc"sv);
- // std::string_view Bucket("mastodon"sv);
- // const size_t NumRecords = 4;
- // std::vector<zen::CacheKey> Keys = PutCacheRecords(LocalCfg.BaseUri, Namespace, Bucket, NumRecords, PayloadSize, 1,
- // CachePolicy::Local);
-
- // for (const zen::CacheKey& CacheKey : Keys)
- // {
- // cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
- // AppendCacheRecord(Request, CacheKey, PayloadSize * 2, CachePolicy::Default);
-
- // CbPackage Package;
- // CHECK(Request.Format(Package));
-
- // IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- // cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", LocalCfg.BaseUri)},
- // cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- // cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- // CHECK(Result.status_code == 200);
- // cacherequests::PutCacheRecordsResult ParsedResult;
- // CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- // CHECK(!Response.IsNull());
- // CHECK(ParsedResult.Parse(Response));
- // for (bool ResponseSuccess : ParsedResult.Success)
- // {
- // CHECK(!ResponseSuccess);
- // }
- // }
-
- // auto CheckRecordCorrectness = [&](const ZenConfig& Cfg, size_t ExpectedPayloadSize) {
- // CachePolicy Policy = (CachePolicy::QueryLocal | CachePolicy::QueryRemote);
- // GetCacheRecordResult Result = GetCacheRecords(Cfg.BaseUri, "ue4.ddc"sv, Keys, Policy);
-
- // CHECK(Result.Result.Results.size() == Keys.size());
-
- // for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- // {
- // CHECK(Record);
- // const CacheKey& ExpectedKey = Keys[Index++];
- // CHECK(Record->Key == ExpectedKey);
- // for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- // {
- // CHECK(Value.RawSize == ExpectedPayloadSize);
- // }
- // }
- // };
-
- // // Check that the records are present and not overwritten in the local server
- // CheckRecordCorrectness(LocalCfg, PayloadSize);
-
- // // Check that the records are present and are the newer size in the upstream server
- // CheckRecordCorrectness(UpstreamCfg, PayloadSize*2);
- // }
-
- SUBCASE("RpcAcceptOptions")
- {
- using namespace utils;
-
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
-
- ZenServerInstance Inst(TestEnv);
- Inst.SetTestDir(TestDir);
-
- const uint16_t BasePort = Inst.SpawnServerAndWaitUntilReady();
- const std::string BaseUri = fmt::format("http://localhost:{}/z$", BasePort);
-
- std::vector<zen::CacheKey> SmallKeys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 4, 1024);
- std::vector<zen::CacheKey> LargeKeys = PutCacheRecords(BaseUri, "ue4.ddc"sv, "mastodon"sv, 4, 1024 * 1024 * 16, SmallKeys.size());
-
- std::vector<zen::CacheKey> Keys(SmallKeys.begin(), SmallKeys.end());
- Keys.insert(Keys.end(), LargeKeys.begin(), LargeKeys.end());
-
- {
- GetCacheRecordResult Result = GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, CachePolicy::Default);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
- IoBufferFileReference Ref;
- bool IsFileRef = Body.GetFileReference(Ref);
- CHECK(!IsFileRef);
- }
- }
- }
-
- // File path, but only for large files
- {
- GetCacheRecordResult Result =
- GetCacheRecords(BaseUri, "ue4.ddc"sv, Keys, CachePolicy::Default, RpcAcceptOptions::kAllowLocalReferences);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
- IoBufferFileReference Ref;
- bool IsFileRef = Body.GetFileReference(Ref);
- CHECK(IsFileRef == (Body.Size() > 1024));
- }
- }
- }
-
- // File path, for all files
- {
- GetCacheRecordResult Result =
- GetCacheRecords(BaseUri,
- "ue4.ddc"sv,
- Keys,
- CachePolicy::Default,
- RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialLocalReferences);
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
- IoBufferFileReference Ref;
- bool IsFileRef = Body.GetFileReference(Ref);
- CHECK(IsFileRef);
- }
- }
- }
-
- // File handle, but only for large files
- {
- GetCacheRecordResult Result = GetCacheRecords(BaseUri,
- "ue4.ddc"sv,
- Keys,
- CachePolicy::Default,
- RpcAcceptOptions::kAllowLocalReferences,
- GetCurrentProcessId());
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
- IoBufferFileReference Ref;
- bool IsFileRef = Body.GetFileReference(Ref);
- CHECK(IsFileRef == (Body.Size() > 1024));
- }
- }
- }
-
- // File handle, for all files
- {
- GetCacheRecordResult Result =
- GetCacheRecords(BaseUri,
- "ue4.ddc"sv,
- Keys,
- CachePolicy::Default,
- RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialLocalReferences,
- GetCurrentProcessId());
-
- CHECK(Result.Result.Results.size() == Keys.size());
-
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- CHECK(Record);
- const CacheKey& ExpectedKey = Keys[Index++];
- CHECK(Record->Key == ExpectedKey);
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- const IoBuffer& Body = Value.Body.GetCompressed().Flatten().AsIoBuffer();
- IoBufferFileReference Ref;
- bool IsFileRef = Body.GetFileReference(Ref);
- CHECK(IsFileRef);
- }
- }
- }
- }
-}
-
-TEST_CASE("zcache.failing.upstream")
-{
- // This is an exploratory test that takes a long time to run, so lets skip it by default
- if (true)
- {
- return;
- }
-
- using namespace std::literals;
- using namespace utils;
-
- ZenConfig Upstream1Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance Upstream1Server(TestEnv);
- SpawnServer(Upstream1Server, Upstream1Cfg);
-
- ZenConfig Upstream2Cfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance Upstream2Server(TestEnv);
- SpawnServer(Upstream2Server, Upstream2Cfg);
-
- std::vector<std::uint16_t> UpstreamPorts = {Upstream1Cfg.Port, Upstream2Cfg.Port};
- ZenConfig LocalCfg = ZenConfig::NewWithThreadedUpstreams(TestEnv.GetNewPortNumber(), UpstreamPorts, false);
- LocalCfg.Args += (" --upstream-thread-count 2");
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- const uint16_t LocalPortNumber = LocalCfg.Port;
- const auto LocalUri = fmt::format("http://localhost:{}/z$", LocalPortNumber);
- const auto Upstream1Uri = fmt::format("http://localhost:{}/z$", Upstream1Cfg.Port);
- const auto Upstream2Uri = fmt::format("http://localhost:{}/z$", Upstream2Cfg.Port);
-
- bool Upstream1Running = true;
- bool Upstream2Running = true;
-
- using namespace std::literals;
-
- auto AppendCacheRecord = [](cacherequests::PutCacheRecordsRequest& Request,
- const zen::CacheKey& CacheKey,
- size_t PayloadSize,
- CachePolicy RecordPolicy) {
- std::vector<uint32_t> Data;
- Data.resize(PayloadSize / 4);
- for (uint32_t Idx = 0; Idx < PayloadSize / 4; ++Idx)
- {
- Data[Idx] = (*reinterpret_cast<const uint32_t*>(&CacheKey.Hash.Hash[0])) + Idx;
- }
-
- CompressedBuffer Value = zen::CompressedBuffer::Compress(SharedBuffer::MakeView(Data.data(), Data.size() * 4));
- Request.Requests.push_back({.Key = CacheKey, .Values = {{.Id = Oid::NewOid(), .Body = std::move(Value)}}, .Policy = RecordPolicy});
- };
-
- auto PutCacheRecords = [&AppendCacheRecord](std::string_view BaseUri,
- std::string_view Namespace,
- std::string_view Bucket,
- size_t Num,
- size_t KeyOffset,
- size_t PayloadSize = 8192) -> std::vector<CacheKey> {
- std::vector<zen::CacheKey> OutKeys;
-
- cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
- for (size_t Key = 1; Key <= Num; ++Key)
- {
- zen::IoHash KeyHash;
- ((size_t*)(KeyHash.Hash))[0] = KeyOffset + Key;
- const zen::CacheKey CacheKey = zen::CacheKey::Create(Bucket, KeyHash);
-
- AppendCacheRecord(Request, CacheKey, PayloadSize, CachePolicy::Default);
- OutKeys.push_back(CacheKey);
- }
-
- CbPackage Package;
- CHECK(Request.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- if (Result.status_code != 200)
- {
- ZEN_DEBUG("PutCacheRecords failed with {}, reason '{}'", Result.status_code, Result.reason);
- OutKeys.clear();
- }
-
- return OutKeys;
- };
-
- struct GetCacheRecordResult
- {
- zen::CbPackage Response;
- cacherequests::GetCacheRecordsResult Result;
- bool Success = false;
- };
-
- auto GetCacheRecords = [](std::string_view BaseUri,
- std::string_view Namespace,
- std::span<zen::CacheKey> Keys,
- zen::CachePolicy Policy) -> GetCacheRecordResult {
- cacherequests::GetCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
- .DefaultPolicy = Policy,
- .Namespace = std::string(Namespace)};
- for (const CacheKey& Key : Keys)
- {
- Request.Requests.push_back({.Key = Key});
- }
-
- CbObjectWriter RequestWriter;
- CHECK(Request.Format(RequestWriter));
-
- BinaryWriter Body;
- RequestWriter.Save(Body);
-
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cb"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- GetCacheRecordResult OutResult;
-
- if (Result.status_code == 200)
- {
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- if (!Response.IsNull())
- {
- OutResult.Response = std::move(Response);
- CHECK(OutResult.Result.Parse(OutResult.Response));
- OutResult.Success = true;
- }
- }
- else
- {
- ZEN_DEBUG("GetCacheRecords with {}, reason '{}'", Result.reason, Result.status_code);
- }
-
- return OutResult;
- };
-
- // Populate with some simple data
-
- CachePolicy Policy = CachePolicy::Default;
-
- const size_t ThreadCount = 128;
- const size_t KeyMultiplier = 16384;
- const size_t RecordsPerRequest = 64;
- WorkerThreadPool Pool(ThreadCount);
-
- std::atomic_size_t Completed = 0;
-
- auto Keys = new std::vector<CacheKey>[ThreadCount * KeyMultiplier];
- RwLock KeysLock;
-
- for (size_t I = 0; I < ThreadCount * KeyMultiplier; I++)
- {
- size_t Iteration = I;
- Pool.ScheduleWork([&] {
- std::vector<CacheKey> NewKeys = PutCacheRecords(LocalUri, "ue4.ddc"sv, "mastodon"sv, RecordsPerRequest, I * RecordsPerRequest);
- if (NewKeys.size() != RecordsPerRequest)
- {
- ZEN_DEBUG("PutCacheRecords iteration {} failed", Iteration);
- Completed.fetch_add(1);
- return;
- }
- {
- RwLock::ExclusiveLockScope _(KeysLock);
- Keys[Iteration].swap(NewKeys);
- }
- Completed.fetch_add(1);
- });
- }
- bool UseUpstream1 = false;
- while (Completed < ThreadCount * KeyMultiplier)
- {
- Sleep(8000);
-
- if (UseUpstream1)
- {
- if (Upstream2Running)
- {
- Upstream2Server.EnableTermination();
- Upstream2Server.Shutdown();
- Sleep(100);
- Upstream2Running = false;
- }
- if (!Upstream1Running)
- {
- SpawnServer(Upstream1Server, Upstream1Cfg);
- Upstream1Running = true;
- }
- UseUpstream1 = !UseUpstream1;
- }
- else
- {
- if (Upstream1Running)
- {
- Upstream1Server.EnableTermination();
- Upstream1Server.Shutdown();
- Sleep(100);
- Upstream1Running = false;
- }
- if (!Upstream2Running)
- {
- SpawnServer(Upstream2Server, Upstream2Cfg);
- Upstream2Running = true;
- }
- UseUpstream1 = !UseUpstream1;
- }
- }
-
- Completed = 0;
- for (size_t I = 0; I < ThreadCount * KeyMultiplier; I++)
- {
- size_t Iteration = I;
- std::vector<CacheKey>& LocalKeys = Keys[Iteration];
- if (LocalKeys.empty())
- {
- Completed.fetch_add(1);
- continue;
- }
- Pool.ScheduleWork([&] {
- GetCacheRecordResult Result = GetCacheRecords(LocalUri, "ue4.ddc"sv, LocalKeys, Policy);
-
- if (!Result.Success)
- {
- ZEN_DEBUG("GetCacheRecords iteration {} failed", Iteration);
- Completed.fetch_add(1);
- return;
- }
-
- if (Result.Result.Results.size() != LocalKeys.size())
- {
- ZEN_DEBUG("GetCacheRecords iteration {} empty records", Iteration);
- Completed.fetch_add(1);
- return;
- }
- for (size_t Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& Record : Result.Result.Results)
- {
- const CacheKey& ExpectedKey = LocalKeys[Index++];
- if (!Record)
- {
- continue;
- }
- if (Record->Key != ExpectedKey)
- {
- continue;
- }
- if (Record->Values.size() != 1)
- {
- continue;
- }
-
- for (const cacherequests::GetCacheRecordResultValue& Value : Record->Values)
- {
- if (!Value.Body)
- {
- continue;
- }
- }
- }
- Completed.fetch_add(1);
- });
- }
- while (Completed < ThreadCount * KeyMultiplier)
- {
- Sleep(10);
- }
-}
-
-TEST_CASE("zcache.rpc.partialchunks")
-{
- using namespace std::literals;
- using namespace utils;
-
- ZenConfig LocalCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance Server(TestEnv);
- SpawnServer(Server, LocalCfg);
-
- std::vector<CompressedBuffer> Attachments;
-
- const auto BaseUri = fmt::format("http://localhost:{}/z$", Server.GetBasePort());
-
- auto GenerateKey = [](std::string_view Bucket, size_t KeyIndex) -> CacheKey {
- IoHash KeyHash;
- ((size_t*)(KeyHash.Hash))[0] = KeyIndex;
- return CacheKey::Create(Bucket, KeyHash);
- };
-
- auto AppendCacheRecord = [](cacherequests::PutCacheRecordsRequest& Request,
- const CacheKey& CacheKey,
- size_t AttachmentCount,
- size_t AttachmentsSize,
- CachePolicy RecordPolicy) -> std::vector<std::pair<Oid, CompressedBuffer>> {
- std::vector<std::pair<Oid, CompressedBuffer>> AttachmentBuffers;
- std::vector<cacherequests::PutCacheRecordRequestValue> Attachments;
- for (size_t AttachmentIndex = 0; AttachmentIndex < AttachmentCount; AttachmentIndex++)
- {
- CompressedBuffer Value = CreateSemiRandomBlob(AttachmentsSize);
- AttachmentBuffers.push_back(std::make_pair(Oid::NewOid(), Value));
- Attachments.push_back({.Id = AttachmentBuffers.back().first, .Body = std::move(Value)});
- }
- Request.Requests.push_back({.Key = CacheKey, .Values = Attachments, .Policy = RecordPolicy});
- return AttachmentBuffers;
- };
-
- auto PutCacheRecords = [&AppendCacheRecord, &GenerateKey](
- std::string_view BaseUri,
- std::string_view Namespace,
- std::string_view Bucket,
- size_t KeyOffset,
- size_t Num,
- size_t AttachmentCount,
- size_t AttachmentsSize =
- 8192) -> std::vector<std::pair<CacheKey, std::vector<std::pair<Oid, CompressedBuffer>>>> {
- std::vector<std::pair<CacheKey, std::vector<std::pair<Oid, CompressedBuffer>>>> Keys;
-
- cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic, .Namespace = std::string(Namespace)};
- for (size_t Key = 1; Key <= Num; ++Key)
- {
- const CacheKey NewCacheKey = GenerateKey(Bucket, KeyOffset + Key);
- std::vector<std::pair<Oid, CompressedBuffer>> Attachments =
- AppendCacheRecord(Request, NewCacheKey, AttachmentCount, AttachmentsSize, CachePolicy::Default);
- Keys.push_back(std::make_pair(NewCacheKey, std::move(Attachments)));
- }
-
- CbPackage Package;
- CHECK(Request.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- if (Result.status_code != 200)
- {
- ZEN_DEBUG("PutCacheRecords failed with {}, reason '{}'", Result.status_code, Result.reason);
- Keys.clear();
- }
-
- return Keys;
- };
-
- std::string_view TestBucket = "partialcachevaluetests"sv;
- std::string_view TestNamespace = "ue4.ddc"sv;
- auto RecordsWithSmallAttachments = PutCacheRecords(BaseUri, TestNamespace, TestBucket, 0, 3, 2, 4096u);
- CHECK(RecordsWithSmallAttachments.size() == 3);
- auto RecordsWithLargeAttachments = PutCacheRecords(BaseUri, TestNamespace, TestBucket, 10, 1, 2, 8u * 1024u * 1024u);
- CHECK(RecordsWithLargeAttachments.size() == 1);
-
- struct PartialOptions
- {
- uint64_t Offset = 0ull;
- uint64_t Size = ~0ull;
- RpcAcceptOptions AcceptOptions = RpcAcceptOptions::kNone;
- };
-
- auto GetCacheChunk = [](std::string_view BaseUri,
- std::string_view Namespace,
- const CacheKey& Key,
- const Oid& ValueId,
- const PartialOptions& Options = {}) -> cacherequests::GetCacheChunksResult {
- cacherequests::GetCacheChunksRequest Request = {
- .AcceptMagic = kCbPkgMagic,
- .AcceptOptions = (uint16_t)Options.AcceptOptions,
- .Namespace = std::string(Namespace),
- .Requests = {{.Key = Key, .ValueId = ValueId, .RawOffset = Options.Offset, .RawSize = Options.Size}}};
- CbPackage Package;
- CHECK(Request.Format(Package));
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
-
- CHECK(Result.status_code == 200);
-
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- bool Loaded = !Response.IsNull();
- CHECK_MESSAGE(Loaded, "GetCacheChunks response failed to load.");
- cacherequests::GetCacheChunksResult GetCacheChunksResult;
- CHECK(GetCacheChunksResult.Parse(Response));
- return GetCacheChunksResult;
- };
-
- auto GetAndVerifyChunk = [&GetCacheChunk](std::string_view BaseUri,
- std::string_view Namespace,
- const CacheKey& Key,
- const Oid& ChunkId,
- const CompressedBuffer& VerifyData,
- const PartialOptions& Options = {}) {
- cacherequests::GetCacheChunksResult Result = GetCacheChunk(BaseUri, Namespace, Key, ChunkId, Options);
- CHECK(Result.Results.size() == 1);
- bool CanGetPartial = ((uint16_t)Options.AcceptOptions & (uint16_t)RpcAcceptOptions::kAllowPartialCacheChunks);
- if (!CanGetPartial)
- {
- CHECK(Result.Results[0].FragmentOffset == 0);
- CHECK(Result.Results[0].Body.GetCompressedSize() == VerifyData.GetCompressedSize());
- }
- IoBuffer SourceDecompressed = VerifyData.Decompress(Options.Offset, Options.Size).AsIoBuffer();
- IoBuffer ReceivedDecompressed =
- Result.Results[0].Body.Decompress(Options.Offset - Result.Results[0].FragmentOffset, Options.Size).AsIoBuffer();
- CHECK(SourceDecompressed.GetView().EqualBytes(ReceivedDecompressed.GetView()));
- };
-
- GetAndVerifyChunk(BaseUri,
- TestNamespace,
- RecordsWithSmallAttachments[0].first,
- RecordsWithSmallAttachments[0].second[0].first,
- RecordsWithSmallAttachments[0].second[0].second);
- GetAndVerifyChunk(BaseUri,
- TestNamespace,
- RecordsWithSmallAttachments[0].first,
- RecordsWithSmallAttachments[0].second[0].first,
- RecordsWithSmallAttachments[0].second[0].second,
- PartialOptions{.Offset = 378, .Size = 519, .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences});
- GetAndVerifyChunk(
- BaseUri,
- TestNamespace,
- RecordsWithSmallAttachments[0].first,
- RecordsWithSmallAttachments[0].second[0].first,
- RecordsWithSmallAttachments[0].second[0].second,
- PartialOptions{.Offset = 378,
- .Size = 519,
- .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialCacheChunks});
- GetAndVerifyChunk(BaseUri,
- TestNamespace,
- RecordsWithLargeAttachments[0].first,
- RecordsWithLargeAttachments[0].second[0].first,
- RecordsWithLargeAttachments[0].second[0].second,
- PartialOptions{.AcceptOptions = RpcAcceptOptions::kAllowLocalReferences});
- GetAndVerifyChunk(BaseUri,
- TestNamespace,
- RecordsWithLargeAttachments[0].first,
- RecordsWithLargeAttachments[0].second[0].first,
- RecordsWithLargeAttachments[0].second[0].second,
- PartialOptions{.Offset = 1024u * 1024u, .Size = 512u * 1024u});
- GetAndVerifyChunk(
- BaseUri,
- TestNamespace,
- RecordsWithLargeAttachments[0].first,
- RecordsWithLargeAttachments[0].second[0].first,
- RecordsWithLargeAttachments[0].second[0].second,
- PartialOptions{.Offset = 1024u * 1024u, .Size = 512u * 1024u, .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences});
- GetAndVerifyChunk(
- BaseUri,
- TestNamespace,
- RecordsWithLargeAttachments[0].first,
- RecordsWithLargeAttachments[0].second[0].first,
- RecordsWithLargeAttachments[0].second[0].second,
- PartialOptions{.Offset = 1024u * 1024u, .Size = 512u * 1024u, .AcceptOptions = RpcAcceptOptions::kAllowPartialCacheChunks});
- GetAndVerifyChunk(
- BaseUri,
- TestNamespace,
- RecordsWithLargeAttachments[0].first,
- RecordsWithLargeAttachments[0].second[0].first,
- RecordsWithLargeAttachments[0].second[0].second,
- PartialOptions{.Offset = 1024u * 1024u,
- .Size = 512u * 1024u,
- .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialCacheChunks});
- GetAndVerifyChunk(
- BaseUri,
- TestNamespace,
- RecordsWithLargeAttachments[0].first,
- RecordsWithLargeAttachments[0].second[0].first,
- RecordsWithLargeAttachments[0].second[0].second,
- PartialOptions{.Offset = 1024u * 1024u,
- .Size = 512u * 1024u,
- .AcceptOptions = RpcAcceptOptions::kAllowLocalReferences | RpcAcceptOptions::kAllowPartialLocalReferences |
- RpcAcceptOptions::kAllowPartialCacheChunks});
-}
-
-TEST_CASE("zcache.rpc.allpolicies")
-{
- using namespace std::literals;
- using namespace utils;
-
- ZenConfig UpstreamCfg = ZenConfig::New(TestEnv.GetNewPortNumber());
- ZenServerInstance UpstreamServer(TestEnv);
- SpawnServer(UpstreamServer, UpstreamCfg);
-
- ZenConfig LocalCfg = ZenConfig::NewWithUpstream(TestEnv.GetNewPortNumber(), UpstreamCfg.Port);
- ZenServerInstance LocalServer(TestEnv);
- SpawnServer(LocalServer, LocalCfg);
-
- const auto BaseUri = fmt::format("http://localhost:{}/z$", LocalServer.GetBasePort());
-
- std::string_view TestVersion = "F72150A02AE34B57A9EC91D36BA1CE08"sv;
- std::string_view TestBucket = "allpoliciestest"sv;
- std::string_view TestNamespace = "ue4.ddc"sv;
-
- // NumKeys = (2 Value vs Record)*(2 SkipData vs Default)*(2 ForceMiss vs Not)*(2 use local)
- // *(2 use remote)*(2 UseValue Policy vs not)*(4 cases per type)
- constexpr int NumKeys = 256;
- constexpr int NumValues = 4;
- Oid ValueIds[NumValues];
- IoHash Hash;
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- ExtendableStringBuilder<16> ValueName;
- ValueName << "ValueId_"sv << ValueIndex;
- static_assert(sizeof(IoHash) >= sizeof(Oid));
- ValueIds[ValueIndex] = Oid::FromMemory(IoHash::HashBuffer(ValueName.Data(), ValueName.Size() * sizeof(ValueName.Data()[0])).Hash);
- }
-
- struct KeyData;
- struct UserData
- {
- UserData& Set(KeyData* InKeyData, int InValueIndex)
- {
- Data = InKeyData;
- ValueIndex = InValueIndex;
- return *this;
- }
- KeyData* Data = nullptr;
- int ValueIndex = 0;
- };
- struct KeyData
- {
- CompressedBuffer BufferValues[NumValues];
- uint64_t IntValues[NumValues];
- UserData ValueUserData[NumValues];
- bool ReceivedChunk[NumValues];
- CacheKey Key;
- UserData KeyUserData;
- uint32_t KeyIndex = 0;
- bool GetRequestsData = true;
- bool UseValueAPI = false;
- bool UseValuePolicy = false;
- bool ForceMiss = false;
- bool UseLocal = true;
- bool UseRemote = true;
- bool ShouldBeHit = true;
- bool ReceivedPut = false;
- bool ReceivedGet = false;
- bool ReceivedPutValue = false;
- bool ReceivedGetValue = false;
- };
- struct CachePutRequest
- {
- CacheKey Key;
- CbObject Record;
- CacheRecordPolicy Policy;
- KeyData* Values;
- UserData* Data;
- };
- struct CachePutValueRequest
- {
- CacheKey Key;
- CompressedBuffer Value;
- CachePolicy Policy;
- UserData* Data;
- };
- struct CacheGetRequest
- {
- CacheKey Key;
- CacheRecordPolicy Policy;
- UserData* Data;
- };
- struct CacheGetValueRequest
- {
- CacheKey Key;
- CachePolicy Policy;
- UserData* Data;
- };
- struct CacheGetChunkRequest
- {
- CacheKey Key;
- Oid ValueId;
- uint64_t RawOffset;
- uint64_t RawSize;
- IoHash RawHash;
- CachePolicy Policy;
- UserData* Data;
- };
-
- KeyData KeyDatas[NumKeys];
- std::vector<CachePutRequest> PutRequests;
- std::vector<CachePutValueRequest> PutValueRequests;
- std::vector<CacheGetRequest> GetRequests;
- std::vector<CacheGetValueRequest> GetValueRequests;
- std::vector<CacheGetChunkRequest> ChunkRequests;
-
- for (uint32_t KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex)
- {
- IoHashStream KeyWriter;
- KeyWriter.Append(TestVersion.data(), TestVersion.length() * sizeof(TestVersion.data()[0]));
- KeyWriter.Append(&KeyIndex, sizeof(KeyIndex));
- IoHash KeyHash = KeyWriter.GetHash();
- KeyData& KeyData = KeyDatas[KeyIndex];
-
- KeyData.Key = CacheKey::Create(TestBucket, KeyHash);
- KeyData.KeyIndex = KeyIndex;
- KeyData.GetRequestsData = (KeyIndex & (1 << 1)) == 0;
- KeyData.UseValueAPI = (KeyIndex & (1 << 2)) != 0;
- KeyData.UseValuePolicy = (KeyIndex & (1 << 3)) != 0;
- KeyData.ForceMiss = (KeyIndex & (1 << 4)) == 0;
- KeyData.UseLocal = (KeyIndex & (1 << 5)) == 0;
- KeyData.UseRemote = (KeyIndex & (1 << 6)) == 0;
- KeyData.ShouldBeHit = !KeyData.ForceMiss && (KeyData.UseLocal || KeyData.UseRemote);
- CachePolicy SharedPolicy = KeyData.UseLocal ? CachePolicy::Local : CachePolicy::None;
- SharedPolicy |= KeyData.UseRemote ? CachePolicy::Remote : CachePolicy::None;
- CachePolicy PutPolicy = SharedPolicy;
- CachePolicy GetPolicy = SharedPolicy;
- GetPolicy |= !KeyData.GetRequestsData ? CachePolicy::SkipData : CachePolicy::None;
- CacheKey& Key = KeyData.Key;
-
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- KeyData.IntValues[ValueIndex] = static_cast<uint64_t>(KeyIndex) | (static_cast<uint64_t>(ValueIndex) << 32);
- KeyData.BufferValues[ValueIndex] =
- CompressedBuffer::Compress(SharedBuffer::MakeView(&KeyData.IntValues[ValueIndex], sizeof(KeyData.IntValues[ValueIndex])));
- KeyData.ReceivedChunk[ValueIndex] = false;
- }
-
- UserData& KeyUserData = KeyData.KeyUserData.Set(&KeyData, -1);
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- KeyData.ValueUserData[ValueIndex].Set(&KeyData, ValueIndex);
- }
- if (!KeyData.UseValueAPI)
- {
- CbObjectWriter Builder;
- Builder.BeginObject("key"sv);
- Builder << "Bucket"sv << Key.Bucket << "Hash"sv << Key.Hash;
- Builder.EndObject();
- Builder.BeginArray("Values"sv);
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- Builder.BeginObject();
- Builder.AddObjectId("Id"sv, ValueIds[ValueIndex]);
- Builder.AddBinaryAttachment("RawHash"sv, KeyData.BufferValues[ValueIndex].DecodeRawHash());
- Builder.AddInteger("RawSize"sv, KeyData.BufferValues[ValueIndex].DecodeRawSize());
- Builder.EndObject();
- }
- Builder.EndArray();
-
- CacheRecordPolicy PutRecordPolicy;
- CacheRecordPolicy GetRecordPolicy;
- if (!KeyData.UseValuePolicy)
- {
- PutRecordPolicy = CacheRecordPolicy(PutPolicy);
- GetRecordPolicy = CacheRecordPolicy(GetPolicy);
- }
- else
- {
- // Switch the SkipData field in the Record policy so that if the CacheStore ignores the ValuePolicies
- // it will use the wrong value for SkipData and fail our tests.
- CacheRecordPolicyBuilder PutBuilder(PutPolicy ^ CachePolicy::SkipData);
- CacheRecordPolicyBuilder GetBuilder(GetPolicy ^ CachePolicy::SkipData);
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- PutBuilder.AddValuePolicy(ValueIds[ValueIndex], PutPolicy);
- GetBuilder.AddValuePolicy(ValueIds[ValueIndex], GetPolicy);
- }
- PutRecordPolicy = PutBuilder.Build();
- GetRecordPolicy = GetBuilder.Build();
- }
- if (!KeyData.ForceMiss)
- {
- PutRequests.push_back({Key, Builder.Save(), PutRecordPolicy, &KeyData, &KeyUserData});
- }
- GetRequests.push_back({Key, GetRecordPolicy, &KeyUserData});
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- UserData& ValueUserData = KeyData.ValueUserData[ValueIndex];
- ChunkRequests.push_back({Key, ValueIds[ValueIndex], 0, UINT64_MAX, IoHash(), GetPolicy, &ValueUserData});
- }
- }
- else
- {
- if (!KeyData.ForceMiss)
- {
- PutValueRequests.push_back({Key, KeyData.BufferValues[0], PutPolicy, &KeyUserData});
- }
- GetValueRequests.push_back({Key, GetPolicy, &KeyUserData});
- ChunkRequests.push_back({Key, Oid::Zero, 0, UINT64_MAX, IoHash(), GetPolicy, &KeyUserData});
- }
- }
-
- // PutCacheRecords
- {
- CachePolicy BatchDefaultPolicy = CachePolicy::Default;
- cacherequests::PutCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
- .DefaultPolicy = BatchDefaultPolicy,
- .Namespace = std::string(TestNamespace)};
- Request.Requests.reserve(PutRequests.size());
- for (CachePutRequest& PutRequest : PutRequests)
- {
- cacherequests::PutCacheRecordRequest& RecordRequest = Request.Requests.emplace_back();
- RecordRequest.Key = PutRequest.Key;
- RecordRequest.Policy = PutRequest.Policy;
- RecordRequest.Values.reserve(NumValues);
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- RecordRequest.Values.push_back({.Id = ValueIds[ValueIndex], .Body = PutRequest.Values->BufferValues[ValueIndex]});
- }
- PutRequest.Data->Data->ReceivedPut = true;
- }
-
- CbPackage Package;
- CHECK(Request.Format(Package));
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
- CHECK_MESSAGE(Result.status_code == 200, "PutCacheRecords unexpectedly failed.");
- }
-
- // PutCacheValues
- {
- CachePolicy BatchDefaultPolicy = CachePolicy::Default;
-
- cacherequests::PutCacheValuesRequest Request = {.AcceptMagic = kCbPkgMagic,
- .DefaultPolicy = BatchDefaultPolicy,
- .Namespace = std::string(TestNamespace)};
- Request.Requests.reserve(PutValueRequests.size());
- for (CachePutValueRequest& PutRequest : PutValueRequests)
- {
- Request.Requests.push_back({.Key = PutRequest.Key, .Body = PutRequest.Value, .Policy = PutRequest.Policy});
- PutRequest.Data->Data->ReceivedPutValue = true;
- }
-
- CbPackage Package;
- CHECK(Request.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
- CHECK_MESSAGE(Result.status_code == 200, "PutCacheValues unexpectedly failed.");
- }
-
- for (KeyData& KeyData : KeyDatas)
- {
- if (!KeyData.ForceMiss)
- {
- if (!KeyData.UseValueAPI)
- {
- CHECK_MESSAGE(KeyData.ReceivedPut, WriteToString<32>("Key ", KeyData.KeyIndex, " was unexpectedly not put.").c_str());
- }
- else
- {
- CHECK_MESSAGE(KeyData.ReceivedPutValue,
- WriteToString<32>("Key ", KeyData.KeyIndex, " was unexpectedly not put to ValueAPI.").c_str());
- }
- }
- }
-
- // GetCacheRecords
- {
- CachePolicy BatchDefaultPolicy = CachePolicy::Default;
- cacherequests::GetCacheRecordsRequest Request = {.AcceptMagic = kCbPkgMagic,
- .DefaultPolicy = BatchDefaultPolicy,
- .Namespace = std::string(TestNamespace)};
- Request.Requests.reserve(GetRequests.size());
- for (CacheGetRequest& GetRequest : GetRequests)
- {
- Request.Requests.push_back({.Key = GetRequest.Key, .Policy = GetRequest.Policy});
- }
-
- CbPackage Package;
- CHECK(Request.Format(Package));
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
- CHECK_MESSAGE(Result.status_code == 200, "GetCacheRecords unexpectedly failed.");
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- bool Loaded = !Response.IsNull();
- CHECK_MESSAGE(Loaded, "GetCacheRecords response failed to load.");
- cacherequests::GetCacheRecordsResult RequestResult;
- CHECK(RequestResult.Parse(Response));
- CHECK_MESSAGE(RequestResult.Results.size() == GetRequests.size(), "GetCacheRecords response count did not match request count.");
- for (int Index = 0; const std::optional<cacherequests::GetCacheRecordResult>& RecordResult : RequestResult.Results)
- {
- bool Succeeded = RecordResult.has_value();
- CacheGetRequest& GetRequest = GetRequests[Index++];
- KeyData* KeyData = GetRequest.Data->Data;
- KeyData->ReceivedGet = true;
- WriteToString<32> Name("Get(", KeyData->KeyIndex, ")");
- if (KeyData->ShouldBeHit)
- {
- CHECK_MESSAGE(Succeeded, WriteToString<32>(Name, " unexpectedly failed.").c_str());
- }
- else if (KeyData->ForceMiss)
- {
- CHECK_MESSAGE(!Succeeded, WriteToString<32>(Name, " unexpectedly succeeded.").c_str());
- }
- if (!KeyData->ForceMiss && Succeeded)
- {
- CHECK_MESSAGE(RecordResult->Values.size() == NumValues,
- WriteToString<32>(Name, " number of values did not match.").c_str());
- for (const cacherequests::GetCacheRecordResultValue& Value : RecordResult->Values)
- {
- int ExpectedValueIndex = 0;
- for (; ExpectedValueIndex < NumValues; ++ExpectedValueIndex)
- {
- if (ValueIds[ExpectedValueIndex] == Value.Id)
- {
- break;
- }
- }
- CHECK_MESSAGE(ExpectedValueIndex < NumValues, WriteToString<32>(Name, " could not find matching ValueId.").c_str());
-
- WriteToString<32> ValueName("Get(", KeyData->KeyIndex, ",", ExpectedValueIndex, ")");
-
- CompressedBuffer ExpectedValue = KeyData->BufferValues[ExpectedValueIndex];
- CHECK_MESSAGE(Value.RawHash == ExpectedValue.DecodeRawHash(),
- WriteToString<32>(ValueName, " RawHash did not match.").c_str());
- CHECK_MESSAGE(Value.RawSize == ExpectedValue.DecodeRawSize(),
- WriteToString<32>(ValueName, " RawSize did not match.").c_str());
-
- if (KeyData->GetRequestsData)
- {
- SharedBuffer Buffer = Value.Body.Decompress();
- CHECK_MESSAGE(Buffer.GetSize() == Value.RawSize,
- WriteToString<32>(ValueName, " BufferSize did not match RawSize.").c_str());
- uint64_t ActualIntValue = ((const uint64_t*)Buffer.GetData())[0];
- uint64_t ExpectedIntValue = KeyData->IntValues[ExpectedValueIndex];
- CHECK_MESSAGE(ActualIntValue == ExpectedIntValue, WriteToString<32>(ValueName, " had unexpected data.").c_str());
- }
- }
- }
- }
- }
-
- // GetCacheValues
- {
- CachePolicy BatchDefaultPolicy = CachePolicy::Default;
-
- cacherequests::GetCacheValuesRequest GetCacheValuesRequest = {.AcceptMagic = kCbPkgMagic,
- .DefaultPolicy = BatchDefaultPolicy,
- .Namespace = std::string(TestNamespace)};
- GetCacheValuesRequest.Requests.reserve(GetValueRequests.size());
- for (CacheGetValueRequest& GetRequest : GetValueRequests)
- {
- GetCacheValuesRequest.Requests.push_back({.Key = GetRequest.Key, .Policy = GetRequest.Policy});
- }
-
- CbPackage Package;
- CHECK(GetCacheValuesRequest.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
- CHECK_MESSAGE(Result.status_code == 200, "GetCacheValues unexpectedly failed.");
- IoBuffer MessageBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size());
- CbPackage Response = ParsePackageMessage(MessageBuffer);
- bool Loaded = !Response.IsNull();
- CHECK_MESSAGE(Loaded, "GetCacheValues response failed to load.");
- cacherequests::GetCacheValuesResult GetCacheValuesResult;
- CHECK(GetCacheValuesResult.Parse(Response));
- for (int Index = 0; const cacherequests::CacheValueResult& ValueResult : GetCacheValuesResult.Results)
- {
- bool Succeeded = ValueResult.RawHash != IoHash::Zero;
- CacheGetValueRequest& Request = GetValueRequests[Index++];
- KeyData* KeyData = Request.Data->Data;
- KeyData->ReceivedGetValue = true;
- WriteToString<32> Name("GetValue("sv, KeyData->KeyIndex, ")"sv);
-
- if (KeyData->ShouldBeHit)
- {
- CHECK_MESSAGE(Succeeded, WriteToString<32>(Name, " unexpectedly failed.").c_str());
- }
- else if (KeyData->ForceMiss)
- {
- CHECK_MESSAGE(!Succeeded, WriteToString<32>(Name, "unexpectedly succeeded.").c_str());
- }
- if (!KeyData->ForceMiss && Succeeded)
- {
- CompressedBuffer ExpectedValue = KeyData->BufferValues[0];
- CHECK_MESSAGE(ValueResult.RawHash == ExpectedValue.DecodeRawHash(),
- WriteToString<32>(Name, " RawHash did not match.").c_str());
- CHECK_MESSAGE(ValueResult.RawSize == ExpectedValue.DecodeRawSize(),
- WriteToString<32>(Name, " RawSize did not match.").c_str());
-
- if (KeyData->GetRequestsData)
- {
- SharedBuffer Buffer = ValueResult.Body.Decompress();
- CHECK_MESSAGE(Buffer.GetSize() == ValueResult.RawSize,
- WriteToString<32>(Name, " BufferSize did not match RawSize.").c_str());
- uint64_t ActualIntValue = ((const uint64_t*)Buffer.GetData())[0];
- uint64_t ExpectedIntValue = KeyData->IntValues[0];
- CHECK_MESSAGE(ActualIntValue == ExpectedIntValue, WriteToString<32>(Name, " had unexpected data.").c_str());
- }
- }
- }
- }
-
- // GetCacheChunks
- {
- std::sort(ChunkRequests.begin(), ChunkRequests.end(), [](CacheGetChunkRequest& A, CacheGetChunkRequest& B) {
- return A.Key.Hash < B.Key.Hash;
- });
- CachePolicy BatchDefaultPolicy = CachePolicy::Default;
- cacherequests::GetCacheChunksRequest GetCacheChunksRequest = {.AcceptMagic = kCbPkgMagic,
- .DefaultPolicy = BatchDefaultPolicy,
- .Namespace = std::string(TestNamespace)};
- GetCacheChunksRequest.Requests.reserve(ChunkRequests.size());
- for (CacheGetChunkRequest& ChunkRequest : ChunkRequests)
- {
- GetCacheChunksRequest.Requests.push_back({.Key = ChunkRequest.Key,
- .ValueId = ChunkRequest.ValueId,
- .ChunkId = IoHash(),
- .RawOffset = ChunkRequest.RawOffset,
- .RawSize = ChunkRequest.RawSize,
- .Policy = ChunkRequest.Policy});
- }
- CbPackage Package;
- CHECK(GetCacheChunksRequest.Format(Package));
-
- IoBuffer Body = FormatPackageMessageBuffer(Package).Flatten().AsIoBuffer();
- cpr::Response Result = cpr::Post(cpr::Url{fmt::format("{}/$rpc", BaseUri)},
- cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}},
- cpr::Body{(const char*)Body.GetData(), Body.GetSize()});
- CHECK_MESSAGE(Result.status_code == 200, "GetCacheChunks unexpectedly failed.");
- CbPackage Response = ParsePackageMessage(zen::IoBuffer(zen::IoBuffer::Wrap, Result.text.data(), Result.text.size()));
- bool Loaded = !Response.IsNull();
- CHECK_MESSAGE(Loaded, "GetCacheChunks response failed to load.");
- cacherequests::GetCacheChunksResult GetCacheChunksResult;
- CHECK(GetCacheChunksResult.Parse(Response));
- CHECK_MESSAGE(GetCacheChunksResult.Results.size() == ChunkRequests.size(),
- "GetCacheChunks response count did not match request count.");
-
- for (int Index = 0; const cacherequests::CacheValueResult& ValueResult : GetCacheChunksResult.Results)
- {
- bool Succeeded = ValueResult.RawHash != IoHash::Zero;
-
- CacheGetChunkRequest& Request = ChunkRequests[Index++];
- KeyData* KeyData = Request.Data->Data;
- int ValueIndex = Request.Data->ValueIndex >= 0 ? Request.Data->ValueIndex : 0;
- KeyData->ReceivedChunk[ValueIndex] = true;
- WriteToString<32> Name("GetChunks("sv, KeyData->KeyIndex, ","sv, ValueIndex, ")"sv);
-
- if (KeyData->ShouldBeHit)
- {
- CHECK_MESSAGE(Succeeded, WriteToString<256>(Name, " unexpectedly failed."sv).c_str());
- }
- else if (KeyData->ForceMiss)
- {
- CHECK_MESSAGE(!Succeeded, WriteToString<256>(Name, " unexpectedly succeeded."sv).c_str());
- }
- if (KeyData->ShouldBeHit && Succeeded)
- {
- CompressedBuffer ExpectedValue = KeyData->BufferValues[ValueIndex];
- CHECK_MESSAGE(ValueResult.RawHash == ExpectedValue.DecodeRawHash(),
- WriteToString<32>(Name, " had unexpected RawHash.").c_str());
- CHECK_MESSAGE(ValueResult.RawSize == ExpectedValue.DecodeRawSize(),
- WriteToString<32>(Name, " had unexpected RawSize.").c_str());
-
- if (KeyData->GetRequestsData)
- {
- SharedBuffer Buffer = ValueResult.Body.Decompress();
- CHECK_MESSAGE(Buffer.GetSize() == ValueResult.RawSize,
- WriteToString<32>(Name, " BufferSize did not match RawSize.").c_str());
- uint64_t ActualIntValue = ((const uint64_t*)Buffer.GetData())[0];
- uint64_t ExpectedIntValue = KeyData->IntValues[ValueIndex];
- CHECK_MESSAGE(ActualIntValue == ExpectedIntValue, WriteToString<32>(Name, " had unexpected data.").c_str());
- }
- }
- }
- }
-
- for (KeyData& KeyData : KeyDatas)
- {
- if (!KeyData.UseValueAPI)
- {
- CHECK_MESSAGE(KeyData.ReceivedGet, WriteToString<32>("Get(", KeyData.KeyIndex, ") was unexpectedly not received.").c_str());
- for (int ValueIndex = 0; ValueIndex < NumValues; ++ValueIndex)
- {
- CHECK_MESSAGE(
- KeyData.ReceivedChunk[ValueIndex],
- WriteToString<32>("GetChunks(", KeyData.KeyIndex, ",", ValueIndex, ") was unexpectedly not received.").c_str());
- }
- }
- else
- {
- CHECK_MESSAGE(KeyData.ReceivedGetValue,
- WriteToString<32>("GetValue(", KeyData.KeyIndex, ") was unexpectedly not received.").c_str());
- CHECK_MESSAGE(KeyData.ReceivedChunk[0],
- WriteToString<32>("GetChunks(", KeyData.KeyIndex, ") was unexpectedly not received.").c_str());
- }
- }
-}
-
-class ZenServerTestHelper
-{
-public:
- ZenServerTestHelper(std::string_view HelperId, int ServerCount) : m_HelperId{HelperId}, m_ServerCount{ServerCount} {}
- ~ZenServerTestHelper() {}
-
- void SpawnServers(std::string_view AdditionalServerArgs = std::string_view())
- {
- SpawnServers([](ZenServerInstance&) {}, AdditionalServerArgs);
- }
-
- void SpawnServers(auto&& Callback, std::string_view AdditionalServerArgs)
- {
- ZEN_INFO("{}: spawning {} server instances", m_HelperId, m_ServerCount);
-
- m_Instances.resize(m_ServerCount);
-
- for (int i = 0; i < m_ServerCount; ++i)
- {
- auto& Instance = m_Instances[i];
- Instance = std::make_unique<ZenServerInstance>(TestEnv);
- Instance->SetTestDir(TestEnv.CreateNewTestDir());
- }
-
- for (int i = 0; i < m_ServerCount; ++i)
- {
- auto& Instance = m_Instances[i];
- Callback(*Instance);
- }
-
- for (int i = 0; i < m_ServerCount; ++i)
- {
- auto& Instance = m_Instances[i];
- Instance->SpawnServer(TestEnv.GetNewPortNumber(), AdditionalServerArgs);
- }
-
- for (int i = 0; i < m_ServerCount; ++i)
- {
- auto& Instance = m_Instances[i];
- uint16_t PortNumber = Instance->WaitUntilReady();
- CHECK_MESSAGE(PortNumber != 0, Instance->GetLogOutput());
- }
- }
-
- ZenServerInstance& GetInstance(int Index) { return *m_Instances[Index]; }
-
-private:
- std::string m_HelperId;
- int m_ServerCount = 0;
- std::vector<std::unique_ptr<ZenServerInstance>> m_Instances;
-};
-
TEST_CASE("http.basics")
{
using namespace std::literals;
@@ -3033,20 +258,23 @@ TEST_CASE("http.basics")
ZenServerInstance& Instance = Servers.GetInstance(0);
const std::string BaseUri = Instance.GetBaseUri();
+ HttpClient Http{BaseUri};
+
{
- cpr::Response r = cpr::Get(cpr::Url{fmt::format("{}/testing/hello", BaseUri)});
- CHECK(IsHttpSuccessCode(r.status_code));
+ HttpClient::Response r = Http.Get("/testing/hello");
+ CHECK(r);
}
{
- cpr::Response r = cpr::Post(cpr::Url{fmt::format("{}/testing/hello", BaseUri)});
- CHECK_EQ(r.status_code, 404);
+ HttpClient::Response r = Http.Post("/testing/hello");
+ CHECK_EQ(r.StatusCode, HttpResponseCode::NotFound);
}
{
- cpr::Response r = cpr::Post(cpr::Url{fmt::format("{}/testing/echo", BaseUri)}, cpr::Body{"yoyoyoyo"});
- CHECK_EQ(r.status_code, 200);
- CHECK_EQ(r.text, "yoyoyoyo");
+ IoBuffer Body{IoBuffer::Wrap, "yoyoyoyo", 8};
+ HttpClient::Response r = Http.Post("/testing/echo", Body);
+ CHECK_EQ(r.StatusCode, HttpResponseCode::OK);
+ CHECK(r.ResponsePayload.GetView().EqualBytes(Body.GetView()));
}
}
@@ -3092,1549 +320,6 @@ TEST_CASE("http.package")
CHECK_EQ(ResponsePackage, TestPackage);
}
-std::string
-OidAsString(const Oid& Id)
-{
- StringBuilder<25> OidStringBuilder;
- Id.ToString(OidStringBuilder);
- return OidStringBuilder.ToString();
-}
-
-CbPackage
-CreateOplogPackage(const Oid& Id, const std::span<const std::pair<Oid, CompressedBuffer>>& Attachments)
-{
- CbPackage Package;
- CbObjectWriter Object;
- Object << "key"sv << OidAsString(Id);
- if (!Attachments.empty())
- {
- Object.BeginArray("bulkdata");
- for (const auto& Attachment : Attachments)
- {
- CbAttachment Attach(Attachment.second, Attachment.second.DecodeRawHash());
- Object.BeginObject();
- Object << "id"sv << Attachment.first;
- Object << "type"sv
- << "Standard"sv;
- Object << "data"sv << Attach;
- Object.EndObject();
-
- Package.AddAttachment(Attach);
- ZEN_DEBUG("Added attachment {}", Attach.GetHash());
- }
- Object.EndArray();
- }
- Package.SetObject(Object.Save());
- return Package;
-};
-
-cpr::Body
-AsBody(const IoBuffer& Payload)
-{
- return cpr::Body{(const char*)Payload.GetData(), Payload.Size()};
-};
-
-enum CbWriterMeta
-{
- BeginObject,
- EndObject,
- BeginArray,
- EndArray
-};
-
-inline CbWriter&
-operator<<(CbWriter& Writer, CbWriterMeta Meta)
-{
- switch (Meta)
- {
- case BeginObject:
- Writer.BeginObject();
- break;
- case EndObject:
- Writer.EndObject();
- break;
- case BeginArray:
- Writer.BeginArray();
- break;
- case EndArray:
- Writer.EndArray();
- break;
- default:
- ZEN_ASSERT(false);
- }
- return Writer;
-}
-
-TEST_CASE("project.remote")
-{
- using namespace std::literals;
- using namespace utils;
-
- ZenServerTestHelper Servers("remote", 3);
- Servers.SpawnServers("--debug");
-
- std::vector<Oid> OpIds;
- const size_t OpCount = 24;
- OpIds.reserve(OpCount);
- for (size_t I = 0; I < OpCount; ++I)
- {
- OpIds.emplace_back(Oid::NewOid());
- }
-
- std::unordered_map<Oid, std::vector<std::pair<Oid, CompressedBuffer>>, Oid::Hasher> Attachments;
- {
- std::vector<std::size_t> AttachmentSizes(
- {7633, 6825, 5738, 8031, 7225, 566, 3656, 6006, 24, 33466, 1093, 4269, 2257, 3685, 13489, 97194,
- 6151, 5482, 6217, 3511, 6738, 5061, 7537, 2759, 1916, 8210, 2235, 224024, 51582, 5251, 491, 2u * 1024u * 1024u + 124u,
- 74607, 18135, 3767, 154045, 4415, 5007, 8876, 96761, 3359, 8526, 4097, 4855, 48225});
- auto It = AttachmentSizes.begin();
- Attachments[OpIds[0]] = {};
- Attachments[OpIds[1]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- Attachments[OpIds[2]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
- Attachments[OpIds[3]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- Attachments[OpIds[4]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
- Attachments[OpIds[5]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
- Attachments[OpIds[6]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- Attachments[OpIds[7]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
- Attachments[OpIds[8]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
- Attachments[OpIds[9]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
- Attachments[OpIds[10]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- Attachments[OpIds[11]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
- Attachments[OpIds[12]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++, *It++});
- Attachments[OpIds[13]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- Attachments[OpIds[14]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
- Attachments[OpIds[15]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
- Attachments[OpIds[16]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
- Attachments[OpIds[17]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
- Attachments[OpIds[18]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++});
- Attachments[OpIds[19]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{});
- Attachments[OpIds[20]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- Attachments[OpIds[21]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- Attachments[OpIds[22]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++, *It++, *It++});
- Attachments[OpIds[23]] = CreateSemiRandomAttachments(std::initializer_list<size_t>{*It++});
- ZEN_ASSERT(It == AttachmentSizes.end());
- }
-
- // Note: This is a clone of the function in projectstore.cpp
- auto ComputeOpKey = [](const CbObjectView& Op) -> Oid {
- using namespace std::literals;
-
- XXH3_128Stream_deprecated KeyHasher;
- Op["key"sv].WriteToStream([&](const void* Data, size_t Size) { KeyHasher.Append(Data, Size); });
- XXH3_128 KeyHash128 = KeyHasher.GetHash();
-
- Oid KeyHash;
- memcpy(&KeyHash, KeyHash128.Hash, sizeof KeyHash);
-
- return KeyHash;
- };
-
- auto AddOp = [ComputeOpKey](const CbObject& Op, std::unordered_map<Oid, uint32_t, Oid::Hasher>& Ops) {
- const Oid Id = ComputeOpKey(Op);
- IoBuffer Buffer = Op.GetBuffer().AsIoBuffer();
- const uint32_t OpCoreHash = uint32_t(XXH3_64bits(Buffer.GetData(), Buffer.GetSize()) & 0xffffFFFF);
- Ops.insert({Id, OpCoreHash});
- };
-
- auto MakeProject = [](cpr::Session& Session, std::string_view UrlBase, std::string_view ProjectName) {
- CbObjectWriter Project;
- Project.AddString("id"sv, ProjectName);
- Project.AddString("root"sv, ""sv);
- Project.AddString("engine"sv, ""sv);
- Project.AddString("project"sv, ""sv);
- Project.AddString("projectfile"sv, ""sv);
- IoBuffer ProjectPayload = Project.Save().GetBuffer().AsIoBuffer();
- std::string ProjectRequest = fmt::format("{}/prj/{}", UrlBase, ProjectName);
- Session.SetUrl({ProjectRequest});
- Session.SetBody(cpr::Body{(const char*)ProjectPayload.GetData(), ProjectPayload.GetSize()});
- cpr::Response Response = Session.Post();
- CHECK(IsHttpSuccessCode(Response.status_code));
- };
-
- auto MakeOplog = [](cpr::Session& Session, std::string_view UrlBase, std::string_view ProjectName, std::string_view OplogName) {
- std::string CreateOplogRequest = fmt::format("{}/prj/{}/oplog/{}", UrlBase, ProjectName, OplogName);
- Session.SetUrl({CreateOplogRequest});
- Session.SetBody(cpr::Body{});
- cpr::Response Response = Session.Post();
- CHECK(IsHttpSuccessCode(Response.status_code));
- };
-
- auto MakeOp = [](cpr::Session& Session,
- std::string_view UrlBase,
- std::string_view ProjectName,
- std::string_view OplogName,
- const CbPackage& OpPackage) {
- std::string CreateOpRequest = fmt::format("{}/prj/{}/oplog/{}/new", UrlBase, ProjectName, OplogName);
- Session.SetUrl({CreateOpRequest});
- zen::BinaryWriter MemOut;
- legacy::SaveCbPackage(OpPackage, MemOut);
- Session.SetBody(cpr::Body{(const char*)MemOut.Data(), MemOut.Size()});
- cpr::Response Response = Session.Post();
- CHECK(IsHttpSuccessCode(Response.status_code));
- };
-
- cpr::Session Session;
- MakeProject(Session, Servers.GetInstance(0).GetBaseUri(), "proj0");
- MakeOplog(Session, Servers.GetInstance(0).GetBaseUri(), "proj0", "oplog0");
-
- std::unordered_map<Oid, uint32_t, Oid::Hasher> SourceOps;
- for (const Oid& OpId : OpIds)
- {
- CbPackage OpPackage = CreateOplogPackage(OpId, Attachments[OpId]);
- CHECK(OpPackage.GetAttachments().size() == Attachments[OpId].size());
- AddOp(OpPackage.GetObject(), SourceOps);
- MakeOp(Session, Servers.GetInstance(0).GetBaseUri(), "proj0", "oplog0", OpPackage);
- }
-
- std::vector<IoHash> AttachmentHashes;
- AttachmentHashes.reserve(Attachments.size());
- for (const auto& AttachmentOplog : Attachments)
- {
- for (const auto& Attachment : AttachmentOplog.second)
- {
- AttachmentHashes.emplace_back(Attachment.second.DecodeRawHash());
- }
- }
-
- auto MakeCbObjectPayload = [](std::function<void(CbObjectWriter & Writer)> Write) -> IoBuffer {
- CbObjectWriter Writer;
- Write(Writer);
- IoBuffer Result = Writer.Save().GetBuffer().AsIoBuffer();
- Result.MakeOwned();
- return Result;
- };
-
- auto ValidateAttachments = [&MakeCbObjectPayload, &AttachmentHashes, &Servers, &Session](int ServerIndex,
- std::string_view Project,
- std::string_view Oplog) {
- std::string GetChunksRequest = fmt::format("{}/prj/{}/oplog/{}/rpc", Servers.GetInstance(ServerIndex).GetBaseUri(), Project, Oplog);
- Session.SetUrl({GetChunksRequest});
- IoBuffer Payload = MakeCbObjectPayload([&AttachmentHashes](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "getchunks"sv;
- Writer << "chunks"sv << BeginArray;
- for (const IoHash& Chunk : AttachmentHashes)
- {
- Writer << Chunk;
- }
- Writer << EndArray; // chunks
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}, {"Accept", "application/x-ue-cbpkg"}});
- cpr::Response Response = Session.Post();
- CHECK(IsHttpSuccessCode(Response.status_code));
- CbPackage ResponsePackage = ParsePackageMessage(IoBuffer(IoBuffer::Wrap, Response.text.data(), Response.text.size()));
- CHECK(ResponsePackage.GetAttachments().size() == AttachmentHashes.size());
- for (auto A : ResponsePackage.GetAttachments())
- {
- CHECK(IoHash::HashBuffer(A.AsCompressedBinary().DecompressToComposite()) == A.GetHash());
- }
- };
-
- auto ValidateOplog = [&SourceOps, &AddOp, &Servers, &Session](int ServerIndex, std::string_view Project, std::string_view Oplog) {
- std::unordered_map<Oid, uint32_t, Oid::Hasher> TargetOps;
- std::vector<CbObject> ResultingOplog;
-
- std::string GetOpsRequest =
- fmt::format("{}/prj/{}/oplog/{}/entries", Servers.GetInstance(ServerIndex).GetBaseUri(), Project, Oplog);
- Session.SetUrl({GetOpsRequest});
- cpr::Response Response = Session.Get();
- CHECK(IsHttpSuccessCode(Response.status_code));
-
- IoBuffer Payload(IoBuffer::Wrap, Response.text.data(), Response.text.size());
- CbObject OplogResonse = LoadCompactBinaryObject(Payload);
- CbArrayView EntriesArray = OplogResonse["entries"sv].AsArrayView();
-
- for (CbFieldView OpEntry : EntriesArray)
- {
- CbObjectView Core = OpEntry.AsObjectView();
- BinaryWriter Writer;
- Core.CopyTo(Writer);
- MemoryView OpView = Writer.GetView();
- IoBuffer OpBuffer(IoBuffer::Wrap, OpView.GetData(), OpView.GetSize());
- CbObject Op(SharedBuffer(OpBuffer), CbFieldType::HasFieldType);
- AddOp(Op, TargetOps);
- }
- CHECK(SourceOps == TargetOps);
- };
-
- auto WaitForCompletion = [&Session](ZenServerInstance& Server, const cpr::Response& Response) {
- CHECK(IsHttpSuccessCode(Response.status_code));
- uint64_t JobId = ParseInt<uint64_t>(Response.text).value_or(0);
- CHECK(JobId != 0);
- Session.SetUrl(fmt::format("{}/admin/jobs/{}", Server.GetBaseUri(), JobId));
- Session.SetHeader(cpr::Header{{"Accept", std::string(ToString(ZenContentType::kCbObject))}});
- while (true)
- {
- cpr::Response StatusResponse = Session.Get();
- CHECK(IsHttpSuccessCode(StatusResponse.status_code));
- CbObject ResponseObject =
- LoadCompactBinaryObject(IoBuffer(IoBuffer::Wrap, StatusResponse.text.data(), StatusResponse.text.size()));
- std::string_view Status = ResponseObject["Status"sv].AsString();
- CHECK(Status != "Aborted"sv);
- if (Status == "Complete"sv)
- {
- return;
- }
- Sleep(10);
- }
- };
-
- SUBCASE("File")
- {
- ScopedTemporaryDirectory TempDir;
- {
- std::string SaveOplogRequest = fmt::format("{}/prj/{}/oplog/{}/rpc", Servers.GetInstance(0).GetBaseUri(), "proj0", "oplog0");
- Session.SetUrl({SaveOplogRequest});
-
- IoBuffer Payload = MakeCbObjectPayload([&AttachmentHashes, path = TempDir.Path().string()](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "export"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "maxblocksize"sv << 3072u;
- Writer << "maxchunkembedsize"sv << 1296u;
- Writer << "chunkfilesizelimit"sv << 5u * 1024u;
- Writer << "force"sv << false;
- Writer << "file"sv << BeginObject;
- {
- Writer << "path"sv << path;
- Writer << "name"sv
- << "proj0_oplog0"sv;
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(0), Response);
- }
- {
- MakeProject(Session, Servers.GetInstance(1).GetBaseUri(), "proj0_copy");
- MakeOplog(Session, Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
- std::string LoadOplogRequest =
- fmt::format("{}/prj/{}/oplog/{}/rpc", Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
- Session.SetUrl({LoadOplogRequest});
-
- IoBuffer Payload = MakeCbObjectPayload([&AttachmentHashes, path = TempDir.Path().string()](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "import"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "force"sv << false;
- Writer << "file"sv << BeginObject;
- {
- Writer << "path"sv << path;
- Writer << "name"sv
- << "proj0_oplog0"sv;
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
-
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(1), Response);
- }
- ValidateAttachments(1, "proj0_copy", "oplog0_copy");
- ValidateOplog(1, "proj0_copy", "oplog0_copy");
- }
-
- SUBCASE("File disable blocks")
- {
- ScopedTemporaryDirectory TempDir;
- {
- std::string SaveOplogRequest = fmt::format("{}/prj/{}/oplog/{}/rpc", Servers.GetInstance(0).GetBaseUri(), "proj0", "oplog0");
- Session.SetUrl({SaveOplogRequest});
-
- IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "export"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "maxblocksize"sv << 3072u;
- Writer << "maxchunkembedsize"sv << 1296u;
- Writer << "chunkfilesizelimit"sv << 5u * 1024u;
- Writer << "force"sv << false;
- Writer << "file"sv << BeginObject;
- {
- Writer << "path"sv << TempDir.Path().string();
- Writer << "name"sv
- << "proj0_oplog0"sv;
- Writer << "disableblocks"sv << true;
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(0), Response);
- }
- {
- MakeProject(Session, Servers.GetInstance(1).GetBaseUri(), "proj0_copy");
- MakeOplog(Session, Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
- std::string LoadOplogRequest =
- fmt::format("{}/prj/{}/oplog/{}/rpc", Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
- Session.SetUrl({LoadOplogRequest});
- IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "import"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "force"sv << false;
- Writer << "file"sv << BeginObject;
- {
- Writer << "path"sv << TempDir.Path().string();
- Writer << "name"sv
- << "proj0_oplog0"sv;
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(1), Response);
- }
- ValidateAttachments(1, "proj0_copy", "oplog0_copy");
- ValidateOplog(1, "proj0_copy", "oplog0_copy");
- }
-
- SUBCASE("File force temp blocks")
- {
- ScopedTemporaryDirectory TempDir;
- {
- std::string SaveOplogRequest = fmt::format("{}/prj/{}/oplog/{}/rpc", Servers.GetInstance(0).GetBaseUri(), "proj0", "oplog0");
- Session.SetUrl({SaveOplogRequest});
- IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "export"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "maxblocksize"sv << 3072u;
- Writer << "maxchunkembedsize"sv << 1296u;
- Writer << "chunkfilesizelimit"sv << 5u * 1024u;
- Writer << "force"sv << false;
- Writer << "file"sv << BeginObject;
- {
- Writer << "path"sv << TempDir.Path().string();
- Writer << "name"sv
- << "proj0_oplog0"sv;
- Writer << "enabletempblocks"sv << true;
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(0), Response);
- }
- {
- MakeProject(Session, Servers.GetInstance(1).GetBaseUri(), "proj0_copy");
- MakeOplog(Session, Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
- std::string LoadOplogRequest =
- fmt::format("{}/prj/{}/oplog/{}/rpc", Servers.GetInstance(1).GetBaseUri(), "proj0_copy", "oplog0_copy");
- Session.SetUrl({LoadOplogRequest});
- IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "import"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "force"sv << false;
- Writer << "file"sv << BeginObject;
- {
- Writer << "path"sv << TempDir.Path().string();
- Writer << "name"sv
- << "proj0_oplog0"sv;
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(1), Response);
- }
- ValidateAttachments(1, "proj0_copy", "oplog0_copy");
- ValidateOplog(1, "proj0_copy", "oplog0_copy");
- }
-
- SUBCASE("Zen")
- {
- ScopedTemporaryDirectory TempDir;
- {
- std::string ExportSourceUri = Servers.GetInstance(0).GetBaseUri();
- std::string ExportTargetUri = Servers.GetInstance(1).GetBaseUri();
- MakeProject(Session, ExportTargetUri, "proj0_copy");
- MakeOplog(Session, ExportTargetUri, "proj0_copy", "oplog0_copy");
-
- std::string SaveOplogRequest = fmt::format("{}/prj/{}/oplog/{}/rpc", ExportSourceUri, "proj0", "oplog0");
- Session.SetUrl({SaveOplogRequest});
-
- IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "export"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "maxblocksize"sv << 3072u;
- Writer << "maxchunkembedsize"sv << 1296u;
- Writer << "chunkfilesizelimit"sv << 5u * 1024u;
- Writer << "force"sv << false;
- Writer << "zen"sv << BeginObject;
- {
- Writer << "url"sv << ExportTargetUri.substr(7);
- Writer << "project"
- << "proj0_copy";
- Writer << "oplog"
- << "oplog0_copy";
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(0), Response);
- }
- ValidateAttachments(1, "proj0_copy", "oplog0_copy");
- ValidateOplog(1, "proj0_copy", "oplog0_copy");
-
- {
- std::string ImportSourceUri = Servers.GetInstance(1).GetBaseUri();
- std::string ImportTargetUri = Servers.GetInstance(2).GetBaseUri();
- MakeProject(Session, ImportTargetUri, "proj1");
- MakeOplog(Session, ImportTargetUri, "proj1", "oplog1");
- std::string LoadOplogRequest = fmt::format("{}/prj/{}/oplog/{}/rpc", ImportTargetUri, "proj1", "oplog1");
- Session.SetUrl({LoadOplogRequest});
-
- IoBuffer Payload = MakeCbObjectPayload([&](CbObjectWriter& Writer) {
- Writer << "method"sv
- << "import"sv;
- Writer << "params" << BeginObject;
- {
- Writer << "force"sv << false;
- Writer << "zen"sv << BeginObject;
- {
- Writer << "url"sv << ImportSourceUri.substr(7);
- Writer << "project"
- << "proj0_copy";
- Writer << "oplog"
- << "oplog0_copy";
- }
- Writer << EndObject; // "file"
- }
- Writer << EndObject; // "params"
- });
- Session.SetBody(AsBody(Payload));
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}});
- cpr::Response Response = Session.Post();
- WaitForCompletion(Servers.GetInstance(2), Response);
- }
- ValidateAttachments(2, "proj1", "oplog1");
- ValidateOplog(2, "proj1", "oplog1");
- }
-}
-
-std::vector<std::pair<std::filesystem::path, IoBuffer>>
-GenerateFolderContent(const std::filesystem::path& RootPath)
-{
- CreateDirectories(RootPath);
- std::vector<std::pair<std::filesystem::path, IoBuffer>> Result;
- Result.push_back(std::make_pair(RootPath / "root_blob_1.bin", CreateRandomBlob(4122)));
- Result.push_back(std::make_pair(RootPath / "root_blob_2.bin", CreateRandomBlob(2122)));
-
- std::filesystem::path EmptyFolder(RootPath / "empty_folder");
-
- std::filesystem::path FirstFolder(RootPath / "first_folder");
- CreateDirectories(FirstFolder);
- Result.push_back(std::make_pair(FirstFolder / "first_folder_blob1.bin", CreateRandomBlob(22)));
- Result.push_back(std::make_pair(FirstFolder / "first_folder_blob2.bin", CreateRandomBlob(122)));
-
- std::filesystem::path SecondFolder(RootPath / "second_folder");
- CreateDirectories(SecondFolder);
- Result.push_back(std::make_pair(SecondFolder / "second_folder_blob1.bin", CreateRandomBlob(522)));
- Result.push_back(std::make_pair(SecondFolder / "second_folder_blob2.bin", CreateRandomBlob(122)));
- Result.push_back(std::make_pair(SecondFolder / "second_folder_blob3.bin", CreateRandomBlob(225)));
-
- std::filesystem::path SecondFolderChild(SecondFolder / "child_in_second");
- CreateDirectories(SecondFolderChild);
- Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob1.bin", CreateRandomBlob(622)));
-
- for (const auto& It : Result)
- {
- WriteFile(It.first, It.second);
- }
-
- return Result;
-}
-
-std::vector<std::pair<std::filesystem::path, IoBuffer>>
-GenerateFolderContent2(const std::filesystem::path& RootPath)
-{
- std::vector<std::pair<std::filesystem::path, IoBuffer>> Result;
- Result.push_back(std::make_pair(RootPath / "root_blob_3.bin", CreateRandomBlob(312)));
- std::filesystem::path FirstFolder(RootPath / "first_folder");
- Result.push_back(std::make_pair(FirstFolder / "first_folder_blob3.bin", CreateRandomBlob(722)));
- std::filesystem::path SecondFolder(RootPath / "second_folder");
- std::filesystem::path SecondFolderChild(SecondFolder / "child_in_second");
- Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob2.bin", CreateRandomBlob(962)));
- Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob3.bin", CreateRandomBlob(561)));
-
- for (const auto& It : Result)
- {
- WriteFile(It.first, It.second);
- }
-
- return Result;
-}
-
-TEST_CASE("workspaces.create")
-{
- using namespace std::literals;
-
- std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
-
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
- ZenServerInstance Instance(TestEnv);
- Instance.SetTestDir(TestDir);
- const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(
- fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- ScopedTemporaryDirectory TempDir;
- std::filesystem::path Root1Path = TempDir.Path() / "root1";
- std::filesystem::path Root2Path = TempDir.Path() / "root2";
- DeleteDirectories(Root1Path);
- DeleteDirectories(Root2Path);
-
- std::filesystem::path Share1Path = "shared_1";
- std::filesystem::path Share2Path = "shared_2";
- CreateDirectories(Root1Path / Share1Path);
- CreateDirectories(Root1Path / Share2Path);
- CreateDirectories(Root2Path / Share1Path);
- CreateDirectories(Root2Path / Share2Path);
-
- Oid Root1Id = Oid::Zero;
- Oid Root2Id = Oid::NewOid();
-
- HttpClient Client(Instance.GetBaseUri());
-
- CHECK(Client.Put(fmt::format("/ws/{}", Root1Id)).StatusCode == HttpResponseCode::BadRequest);
-
- if (HttpClient::Response Root1Response =
- Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}});
- Root1Response.StatusCode == HttpResponseCode::Created)
- {
- Root1Id = Oid::TryFromHexString(Root1Response.AsText());
- CHECK(Root1Id != Oid::Zero);
- }
- else
- {
- CHECK(false);
- }
- if (HttpClient::Response Root1Response =
- Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}});
- Root1Response.StatusCode == HttpResponseCode::OK)
- {
- CHECK(Root1Id == Oid::TryFromHexString(Root1Response.AsText()));
- }
- else
- {
- CHECK(false);
- }
- if (HttpClient::Response Root1Response =
- Client.Put(fmt::format("/ws/{}", Root1Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}});
- Root1Response.StatusCode == HttpResponseCode::OK)
- {
- CHECK(Root1Id == Oid::TryFromHexString(Root1Response.AsText()));
- }
- else
- {
- CHECK(false);
- }
- CHECK(Client.Put(fmt::format("/ws/{}", Root1Id), HttpClient::KeyValueMap{{"root_path", Root2Path.string()}}).StatusCode ==
- HttpResponseCode::Conflict);
-
- CHECK(
- Client.Put(fmt::format("/ws/{}/{}", Root1Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
- HttpResponseCode::Created);
-
- CHECK(
- Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
- HttpResponseCode::NotFound);
-
- CHECK(Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode ==
- HttpResponseCode::Conflict);
-
- if (HttpClient::Response Root2Response =
- Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root2Path.string()}});
- Root2Response.StatusCode == HttpResponseCode::Created)
- {
- CHECK(Root2Id == Oid::TryFromHexString(Root2Response.AsText()));
- }
- else
- {
- CHECK(false);
- }
-
- CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero)).StatusCode == HttpResponseCode::BadRequest);
-
- Oid Share2Id = Oid::Zero;
- if (HttpClient::Response Share2Response =
- Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}});
- Share2Response.StatusCode == HttpResponseCode::Created)
- {
- Share2Id = Oid::TryFromHexString(Share2Response.AsText());
- CHECK(Share2Id != Oid::Zero);
- }
- else
- {
- CHECK(false);
- }
-
- CHECK(
- Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
- HttpResponseCode::OK);
-
- CHECK(
- Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
- HttpResponseCode::OK);
-
- CHECK(
- Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode ==
- HttpResponseCode::Conflict);
-
- CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::NewOid()), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}})
- .StatusCode == HttpResponseCode::Conflict);
-
- CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", "idonotexist"}}).StatusCode !=
- HttpResponseCode::OK);
-
- while (true)
- {
- std::error_code Ec;
- DeleteDirectories(Root2Path / Share2Path, Ec);
- if (!Ec)
- break;
- }
-
- CHECK(Client.Get(fmt::format("/ws/{}/{}/files", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound);
-}
-
-TEST_CASE("workspaces.restricted")
-{
- using namespace std::literals;
-
- std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
-
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
- ZenServerInstance Instance(TestEnv);
- Instance.SetTestDir(TestDir);
- const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- ScopedTemporaryDirectory TempDir;
- std::filesystem::path Root1Path = TempDir.Path() / "root1";
- std::filesystem::path Root2Path = TempDir.Path() / "root2";
- DeleteDirectories(Root1Path);
- DeleteDirectories(Root2Path);
-
- std::filesystem::path Share1Path = "shared_1";
- std::filesystem::path Share2Path = "shared_2";
- CreateDirectories(Root1Path / Share1Path);
- CreateDirectories(Root1Path / Share2Path);
- CreateDirectories(Root2Path / Share1Path);
- CreateDirectories(Root2Path / Share2Path);
-
- Oid Root1Id = Oid::NewOid();
- Oid Root2Id = Oid::NewOid();
- Oid Share1Id = Oid::NewOid();
- Oid Share2Id = Oid::NewOid();
-
- HttpClient Client(Instance.GetBaseUri());
- CHECK(Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode ==
- HttpResponseCode::Unauthorized);
-
- CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::NotFound);
-
- std::string Config1;
- {
- CbObjectWriter Config;
- Config.BeginArray("workspaces");
- Config.BeginObject();
- Config << "id"sv << Root1Id.ToString();
- Config << "root_path"sv << Root1Path.string();
- Config << "allow_share_creation_from_http"sv << false;
- Config.EndObject();
- Config.EndArray();
- ExtendableStringBuilder<256> SB;
- CompactBinaryToJson(Config.Save(), SB);
- Config1 = SB.ToString();
- }
- WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config1.data(), Config1.size()));
-
- CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode));
-
- CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::OK);
-
- CHECK(Client.Get(fmt::format("/ws/{}/{}", Root1Id, Share1Id)).StatusCode == HttpResponseCode::NotFound);
- CHECK(
- Client.Put(fmt::format("/ws/{}/{}", Root1Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode ==
- HttpResponseCode::Unauthorized);
-
- std::string Config2;
- {
- CbObjectWriter Config;
- Config.BeginArray("workspaces");
- Config.BeginObject();
- Config << "id"sv << Root1Id.ToString();
- Config << "root_path"sv << Root1Path.string();
- Config << "allow_share_creation_from_http"sv << false;
- Config.EndObject();
- Config.BeginObject();
- Config << "id"sv << Root2Id.ToString();
- Config << "root_path"sv << Root2Path.string();
- Config << "allow_share_creation_from_http"sv << true;
- Config.EndObject();
- Config.EndArray();
- ExtendableStringBuilder<256> SB;
- CompactBinaryToJson(Config.Save(), SB);
- Config2 = SB.ToString();
- }
- WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config2.data(), Config2.size()));
-
- CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode));
-
- CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root2Id)).StatusCode, HttpResponseCode::OK);
-
- CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound);
- CHECK(
- Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode ==
- HttpResponseCode::Created);
- CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::OK);
-
- CHECK(IsHttpSuccessCode(Client.Delete(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode));
-}
-
-TEST_CASE("workspaces.lifetimes")
-{
- using namespace std::literals;
-
- std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
-
- Oid WorkspaceId = Oid::NewOid();
- Oid ShareId = Oid::NewOid();
-
- ScopedTemporaryDirectory TempDir;
- std::filesystem::path RootPath = TempDir.Path();
- DeleteDirectories(RootPath);
- std::filesystem::path SharePath = RootPath / "shared_folder";
- CreateDirectories(SharePath);
-
- {
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
- ZenServerInstance Instance(TestEnv);
- Instance.SetTestDir(TestDir);
- const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(
- fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri());
- CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode ==
- HttpResponseCode::Created);
- CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId);
- CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode ==
- HttpResponseCode::OK);
-
- CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}})
- .StatusCode == HttpResponseCode::Created);
- CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId);
- CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}})
- .StatusCode == HttpResponseCode::OK);
- }
-
- // Restart
-
- {
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
- ZenServerInstance Instance(TestEnv);
- Instance.SetTestDir(TestDir);
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri());
- CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId);
-
- CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId);
- }
-
- // Wipe system config
- DeleteDirectories(SystemRootPath);
-
- // Restart
-
- {
- std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
- ZenServerInstance Instance(TestEnv);
- Instance.SetTestDir(TestDir);
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri());
- CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).StatusCode == HttpResponseCode::NotFound);
- CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).StatusCode == HttpResponseCode::NotFound);
- }
-}
-
-TEST_CASE("workspaces.share")
-{
- std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
-
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(
- fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- ScopedTemporaryDirectory TempDir;
- std::filesystem::path RootPath = TempDir.Path();
- DeleteDirectories(RootPath);
- std::filesystem::path SharePath = RootPath / "shared_folder";
- GenerateFolderContent(SharePath);
-
- HttpClient Client(Instance.GetBaseUri());
-
- Oid WorkspaceId = Oid::NewOid();
- CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode ==
- HttpResponseCode::Created);
- CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId);
-
- Oid ShareId = Oid::NewOid();
- CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}}).StatusCode ==
- HttpResponseCode::Created);
- CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId);
-
- CHECK(Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId)).AsObject()["files"sv].AsArrayView().Num() == 8);
- GenerateFolderContent2(SharePath);
- CHECK(Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId)).AsObject()["files"sv].AsArrayView().Num() == 8);
- HttpClient::Response FilesResponse =
- Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId),
- {},
- HttpClient::KeyValueMap{{"refresh", ToString(true)}, {"fieldnames", "id,clientpath,size"}});
- CHECK(FilesResponse);
- std::unordered_map<Oid, std::pair<std::filesystem::path, uint64_t>, Oid::Hasher> Files;
- {
- CbArrayView FilesArray = FilesResponse.AsObject()["files"sv].AsArrayView();
- CHECK(FilesArray.Num() == 12);
- for (CbFieldView Field : FilesArray)
- {
- CbObjectView FileObject = Field.AsObjectView();
- Oid ChunkId = FileObject["id"sv].AsObjectId();
- CHECK(ChunkId != Oid::Zero);
- uint64_t Size = FileObject["size"sv].AsUInt64();
- std::u8string_view Path = FileObject["clientpath"sv].AsU8String();
- std::filesystem::path AbsFilePath = SharePath / Path;
- CHECK(IsFile(AbsFilePath));
- CHECK(FileSizeFromPath(AbsFilePath) == Size);
- Files.insert_or_assign(ChunkId, std::make_pair(AbsFilePath, Size));
- }
- }
-
- HttpClient::Response EntriesResponse =
- Client.Get(fmt::format("/ws/{}/{}/entries", WorkspaceId, ShareId), {}, HttpClient::KeyValueMap{{"fieldfilter", "id,clientpath"}});
- CHECK(EntriesResponse);
- {
- CbArrayView EntriesArray = EntriesResponse.AsObject()["entries"sv].AsArrayView();
- CHECK(EntriesArray.Num() == 1);
- for (CbFieldView EntryField : EntriesArray)
- {
- CbObjectView EntryObject = EntryField.AsObjectView();
- CbArrayView FilesArray = EntryObject["files"sv].AsArrayView();
- CHECK(FilesArray.Num() == 12);
- for (CbFieldView FileField : FilesArray)
- {
- CbObjectView FileObject = FileField.AsObjectView();
- Oid ChunkId = FileObject["id"sv].AsObjectId();
- CHECK(ChunkId != Oid::Zero);
- std::u8string_view Path = FileObject["clientpath"sv].AsU8String();
- std::filesystem::path AbsFilePath = SharePath / Path;
- CHECK(IsFile(AbsFilePath));
- }
- }
- }
-
- HttpClient::Response FileManifestResponse =
- Client.Get(fmt::format("/ws/{}/{}/entries", WorkspaceId, ShareId),
- {},
- HttpClient::KeyValueMap{{"opkey", "file_manifest"}, {"fieldfilter", "id,clientpath"}});
- CHECK(FileManifestResponse);
- {
- CbArrayView EntriesArray = FileManifestResponse.AsObject()["entry"sv].AsObjectView()["files"sv].AsArrayView();
- CHECK(EntriesArray.Num() == 12);
- for (CbFieldView Field : EntriesArray)
- {
- CbObjectView FileObject = Field.AsObjectView();
- Oid ChunkId = FileObject["id"sv].AsObjectId();
- CHECK(ChunkId != Oid::Zero);
- std::u8string_view Path = FileObject["clientpath"sv].AsU8String();
- std::filesystem::path AbsFilePath = SharePath / Path;
- CHECK(IsFile(AbsFilePath));
- }
- }
-
- for (auto It : Files)
- {
- const Oid& ChunkId = It.first;
- const std::filesystem::path& Path = It.second.first;
- const uint64_t Size = It.second.second;
-
- CHECK(Client.Get(fmt::format("/ws/{}/{}/{}/info", WorkspaceId, ShareId, ChunkId)).AsObject()["size"sv].AsUInt64() == Size);
-
- {
- IoBuffer Payload = Client.Get(fmt::format("/ws/{}/{}/{}", WorkspaceId, ShareId, ChunkId)).ResponsePayload;
- CHECK(Payload);
- CHECK(Payload.GetSize() == Size);
- IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path);
- CHECK(FileContent);
- CHECK(FileContent.GetView().EqualBytes(Payload.GetView()));
- }
-
- {
- IoBuffer Payload =
- Client
- .Get(fmt::format("/ws/{}/{}/{}", WorkspaceId, ShareId, ChunkId),
- {},
- HttpClient::KeyValueMap{{"offset", fmt::format("{}", Size / 4)}, {"size", fmt::format("{}", Size / 2)}})
- .ResponsePayload;
- CHECK(Payload);
- CHECK(Payload.GetSize() == Size / 2);
- IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path, Size / 4, Size / 2);
- CHECK(FileContent);
- CHECK(FileContent.GetView().EqualBytes(Payload.GetView()));
- }
- }
-
- {
- uint32_t CorrelationId = gsl::narrow<uint32_t>(Files.size());
- std::vector<RequestChunkEntry> BatchEntries;
- for (auto It : Files)
- {
- const Oid& ChunkId = It.first;
- const uint64_t Size = It.second.second;
-
- BatchEntries.push_back(
- RequestChunkEntry{.ChunkId = ChunkId, .CorrelationId = --CorrelationId, .Offset = Size / 4, .RequestBytes = Size / 2});
- }
- IoBuffer BatchResponse =
- Client.Post(fmt::format("/ws/{}/{}/batch", WorkspaceId, ShareId), BuildChunkBatchRequest(BatchEntries)).ResponsePayload;
- CHECK(BatchResponse);
- std::vector<IoBuffer> BatchResult = ParseChunkBatchResponse(BatchResponse);
- CHECK(BatchResult.size() == Files.size());
- for (const RequestChunkEntry& Request : BatchEntries)
- {
- IoBuffer Result = BatchResult[Request.CorrelationId];
- auto It = Files.find(Request.ChunkId);
- const std::filesystem::path& Path = It->second.first;
- CHECK(Result.GetSize() == Request.RequestBytes);
- IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path, Request.Offset, Request.RequestBytes);
- CHECK(FileContent);
- CHECK(FileContent.GetView().EqualBytes(Result.GetView()));
- }
- }
-
- CHECK(Client.Delete(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)));
- CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).StatusCode == HttpResponseCode::NotFound);
- CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)));
-
- CHECK(Client.Delete(fmt::format("/ws/{}", WorkspaceId)));
- CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).StatusCode == HttpResponseCode::NotFound);
-}
-
-TEST_CASE("buildstore.blobs")
-{
- std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
- auto _ = MakeGuard([&SystemRootPath]() { DeleteDirectories(SystemRootPath); });
-
- std::string_view Namespace = "ns"sv;
- std::string_view Bucket = "bkt"sv;
- Oid BuildId = Oid::NewOid();
-
- std::vector<IoHash> CompressedBlobsHashes;
- {
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri() + "/builds/");
-
- for (size_t I = 0; I < 5; I++)
- {
- IoBuffer Blob = CreateSemiRandomBlob(4711 + I * 7);
- CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
- CompressedBlobsHashes.push_back(CompressedBlob.DecodeRawHash());
- IoBuffer Payload = std::move(CompressedBlob).GetCompressed().Flatten().AsIoBuffer();
- Payload.SetContentType(ZenContentType::kCompressedBinary);
-
- HttpClient::Response Result =
- Client.Put(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, CompressedBlobsHashes.back()), Payload);
- CHECK(Result);
- }
-
- for (const IoHash& RawHash : CompressedBlobsHashes)
- {
- HttpClient::Response Result = Client.Get(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, RawHash),
- HttpClient::Accept(ZenContentType::kCompressedBinary));
- CHECK(Result);
- IoBuffer Payload = Result.ResponsePayload;
- CHECK(Payload.GetContentType() == ZenContentType::kCompressedBinary);
- IoHash VerifyRawHash;
- uint64_t VerifyRawSize;
- CompressedBuffer CompressedBlob =
- CompressedBuffer::FromCompressed(SharedBuffer(std::move(Payload)), VerifyRawHash, VerifyRawSize);
- CHECK(CompressedBlob);
- CHECK(VerifyRawHash == RawHash);
- IoBuffer Decompressed = CompressedBlob.Decompress().AsIoBuffer();
- CHECK(IoHash::HashBuffer(Decompressed) == RawHash);
- }
- }
- {
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri() + "/builds/");
-
- for (const IoHash& RawHash : CompressedBlobsHashes)
- {
- HttpClient::Response Result = Client.Get(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, RawHash),
- HttpClient::Accept(ZenContentType::kCompressedBinary));
- CHECK(Result);
- IoBuffer Payload = Result.ResponsePayload;
- CHECK(Payload.GetContentType() == ZenContentType::kCompressedBinary);
- IoHash VerifyRawHash;
- uint64_t VerifyRawSize;
- CompressedBuffer CompressedBlob =
- CompressedBuffer::FromCompressed(SharedBuffer(std::move(Payload)), VerifyRawHash, VerifyRawSize);
- CHECK(CompressedBlob);
- CHECK(VerifyRawHash == RawHash);
- IoBuffer Decompressed = CompressedBlob.Decompress().AsIoBuffer();
- CHECK(IoHash::HashBuffer(Decompressed) == RawHash);
- }
-
- for (size_t I = 0; I < 5; I++)
- {
- IoBuffer Blob = CreateSemiRandomBlob(5713 + I * 7);
- CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
- CompressedBlobsHashes.push_back(CompressedBlob.DecodeRawHash());
- IoBuffer Payload = std::move(CompressedBlob).GetCompressed().Flatten().AsIoBuffer();
- Payload.SetContentType(ZenContentType::kCompressedBinary);
-
- HttpClient::Response Result =
- Client.Put(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, CompressedBlobsHashes.back()), Payload);
- CHECK(Result);
- }
- }
- {
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri() + "/builds/");
-
- for (const IoHash& RawHash : CompressedBlobsHashes)
- {
- HttpClient::Response Result = Client.Get(fmt::format("{}/{}/{}/blobs/{}", Namespace, Bucket, BuildId, RawHash),
- HttpClient::Accept(ZenContentType::kCompressedBinary));
- CHECK(Result);
- IoBuffer Payload = Result.ResponsePayload;
- CHECK(Payload.GetContentType() == ZenContentType::kCompressedBinary);
- IoHash VerifyRawHash;
- uint64_t VerifyRawSize;
- CompressedBuffer CompressedBlob =
- CompressedBuffer::FromCompressed(SharedBuffer(std::move(Payload)), VerifyRawHash, VerifyRawSize);
- CHECK(CompressedBlob);
- CHECK(VerifyRawHash == RawHash);
- IoBuffer Decompressed = CompressedBlob.Decompress().AsIoBuffer();
- CHECK(IoHash::HashBuffer(Decompressed) == RawHash);
- }
- }
-}
-
-namespace {
- CbObject MakeMetadata(const IoHash& BlobHash, const std::vector<std::pair<std::string, std::string>>& KeyValues)
- {
- CbObjectWriter Writer;
- Writer.AddHash("rawHash"sv, BlobHash);
- Writer.BeginObject("values");
- {
- for (const auto& V : KeyValues)
- {
- Writer.AddString(V.first, V.second);
- }
- }
- Writer.EndObject(); // values
- return Writer.Save();
- };
-
-} // namespace
-
-TEST_CASE("buildstore.metadata")
-{
- std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
- auto _ = MakeGuard([&SystemRootPath]() { DeleteDirectories(SystemRootPath); });
-
- std::string_view Namespace = "ns"sv;
- std::string_view Bucket = "bkt"sv;
- Oid BuildId = Oid::NewOid();
-
- std::vector<IoHash> BlobHashes;
- std::vector<CbObject> Metadatas;
- std::vector<IoHash> MetadataHashes;
-
- auto GetMetadatas =
- [](HttpClient& Client, std::string_view Namespace, std::string_view Bucket, const Oid& BuildId, std::vector<IoHash> BlobHashes) {
- CbObjectWriter Request;
-
- Request.BeginArray("blobHashes"sv);
- for (const IoHash& BlobHash : BlobHashes)
- {
- Request.AddHash(BlobHash);
- }
- Request.EndArray();
-
- IoBuffer Payload = Request.Save().GetBuffer().AsIoBuffer();
- Payload.SetContentType(ZenContentType::kCbObject);
-
- HttpClient::Response Result = Client.Post(fmt::format("{}/{}/{}/blobs/getBlobMetadata", Namespace, Bucket, BuildId),
- Payload,
- HttpClient::Accept(ZenContentType::kCbObject));
- CHECK(Result);
-
- std::vector<CbObject> ResultMetadatas;
-
- CbPackage ResponsePackage = ParsePackageMessage(Result.ResponsePayload);
- CbObject ResponseObject = ResponsePackage.GetObject();
-
- CbArrayView BlobHashArray = ResponseObject["blobHashes"sv].AsArrayView();
- CbArrayView MetadatasArray = ResponseObject["metadatas"sv].AsArrayView();
- ResultMetadatas.reserve(MetadatasArray.Num());
- auto BlobHashesIt = BlobHashes.begin();
- auto BlobHashArrayIt = begin(BlobHashArray);
- auto MetadataArrayIt = begin(MetadatasArray);
- while (MetadataArrayIt != end(MetadatasArray))
- {
- const IoHash BlobHash = (*BlobHashArrayIt).AsHash();
- while (BlobHash != *BlobHashesIt)
- {
- ZEN_ASSERT(BlobHashesIt != BlobHashes.end());
- BlobHashesIt++;
- }
-
- ZEN_ASSERT(BlobHash == *BlobHashesIt);
-
- const IoHash MetaHash = (*MetadataArrayIt).AsAttachment();
- const CbAttachment* MetaAttachment = ResponsePackage.FindAttachment(MetaHash);
- ZEN_ASSERT(MetaAttachment);
-
- CbObject Metadata = MetaAttachment->AsObject();
- ResultMetadatas.emplace_back(std::move(Metadata));
-
- BlobHashArrayIt++;
- MetadataArrayIt++;
- BlobHashesIt++;
- }
- return ResultMetadatas;
- };
-
- {
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri() + "/builds/");
-
- const size_t BlobCount = 5;
-
- for (size_t I = 0; I < BlobCount; I++)
- {
- BlobHashes.push_back(IoHash::HashBuffer(&I, sizeof(I)));
- Metadatas.push_back(MakeMetadata(BlobHashes.back(), {{"index", fmt::format("{}", I)}}));
- MetadataHashes.push_back(IoHash::HashBuffer(Metadatas.back().GetBuffer().AsIoBuffer()));
- }
-
- {
- CbPackage RequestPackage;
- std::vector<CbAttachment> Attachments;
- tsl::robin_set<IoHash, IoHash::Hasher> AttachmentHashes;
- Attachments.reserve(BlobCount);
- AttachmentHashes.reserve(BlobCount);
- {
- CbObjectWriter RequestWriter;
- RequestWriter.BeginArray("blobHashes");
- for (size_t BlockHashIndex = 0; BlockHashIndex < BlobHashes.size(); BlockHashIndex++)
- {
- RequestWriter.AddHash(BlobHashes[BlockHashIndex]);
- }
- RequestWriter.EndArray(); // blobHashes
-
- RequestWriter.BeginArray("metadatas");
- for (size_t BlockHashIndex = 0; BlockHashIndex < BlobHashes.size(); BlockHashIndex++)
- {
- const IoHash ObjectHash = Metadatas[BlockHashIndex].GetHash();
- RequestWriter.AddBinaryAttachment(ObjectHash);
- if (!AttachmentHashes.contains(ObjectHash))
- {
- Attachments.push_back(CbAttachment(Metadatas[BlockHashIndex], ObjectHash));
- AttachmentHashes.insert(ObjectHash);
- }
- }
-
- RequestWriter.EndArray(); // metadatas
-
- RequestPackage.SetObject(RequestWriter.Save());
- }
- RequestPackage.AddAttachments(Attachments);
-
- CompositeBuffer RpcRequestBuffer = FormatPackageMessageBuffer(RequestPackage);
-
- HttpClient::Response Result = Client.Post(fmt::format("{}/{}/{}/blobs/putBlobMetadata", Namespace, Bucket, BuildId),
- RpcRequestBuffer,
- ZenContentType::kCbPackage);
- CHECK(Result);
- }
-
- {
- std::vector<CbObject> ResultMetadatas = GetMetadatas(Client, Namespace, Bucket, BuildId, BlobHashes);
-
- for (size_t Index = 0; Index < MetadataHashes.size(); Index++)
- {
- const IoHash& ExpectedHash = MetadataHashes[Index];
- IoHash Hash = IoHash::HashBuffer(ResultMetadatas[Index].GetBuffer().AsIoBuffer());
- CHECK_EQ(ExpectedHash, Hash);
- }
- }
- }
- {
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri() + "/builds/");
-
- std::vector<CbObject> ResultMetadatas = GetMetadatas(Client, Namespace, Bucket, BuildId, BlobHashes);
-
- for (size_t Index = 0; Index < MetadataHashes.size(); Index++)
- {
- const IoHash& ExpectedHash = MetadataHashes[Index];
- IoHash Hash = IoHash::HashBuffer(ResultMetadatas[Index].GetBuffer().AsIoBuffer());
- CHECK_EQ(ExpectedHash, Hash);
- }
- }
-}
-
-TEST_CASE("buildstore.cache")
-{
- std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir();
- std::filesystem::path TempDir = TestEnv.CreateNewTestDir();
- auto _ = MakeGuard([&SystemRootPath, &TempDir]() {
- DeleteDirectories(SystemRootPath);
- DeleteDirectories(TempDir);
- });
-
- std::string_view Namespace = "ns"sv;
- std::string_view Bucket = "bkt"sv;
- Oid BuildId = Oid::NewOid();
-
- std::vector<IoHash> BlobHashes;
- std::vector<CbObject> Metadatas;
- std::vector<IoHash> MetadataHashes;
-
- const size_t BlobCount = 5;
- {
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri());
-
- BuildStorageCache::Statistics Stats;
- std::unique_ptr<BuildStorageCache> Cache(CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, false));
-
- {
- IoHash NoneBlob = IoHash::HashBuffer("data", 4);
- std::vector<BuildStorageCache::BlobExistsResult> NoneExists = Cache->BlobsExists(BuildId, std::vector<IoHash>{NoneBlob});
- CHECK(NoneExists.size() == 1);
- CHECK(!NoneExists[0].HasBody);
- CHECK(!NoneExists[0].HasMetadata);
- }
-
- for (size_t I = 0; I < BlobCount; I++)
- {
- IoBuffer Blob = CreateSemiRandomBlob(4711 + I * 7);
- CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
- BlobHashes.push_back(CompressedBlob.DecodeRawHash());
- Cache->PutBuildBlob(BuildId, BlobHashes.back(), ZenContentType::kCompressedBinary, CompressedBlob.GetCompressed());
- }
-
- Cache->Flush(500);
- Cache = CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, false);
-
- {
- std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
- CHECK(Exists.size() == BlobHashes.size());
- for (size_t I = 0; I < BlobCount; I++)
- {
- CHECK(Exists[I].HasBody);
- CHECK(!Exists[I].HasMetadata);
- }
-
- std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
- CHECK_EQ(0, FetchedMetadatas.size());
- }
-
- {
- for (size_t I = 0; I < BlobCount; I++)
- {
- IoBuffer BuildBlob = Cache->GetBuildBlob(BuildId, BlobHashes[I]);
- CHECK(BuildBlob);
- CHECK_EQ(BlobHashes[I],
- IoHash::HashBuffer(CompressedBuffer::FromCompressedNoValidate(std::move(BuildBlob)).Decompress().AsIoBuffer()));
- }
- }
-
- {
- for (size_t I = 0; I < BlobCount; I++)
- {
- CbObject Metadata = MakeMetadata(BlobHashes[I],
- {{"key", fmt::format("{}", I)},
- {"key_plus_one", fmt::format("{}", I + 1)},
- {"block_hash", fmt::format("{}", BlobHashes[I])}});
- Metadatas.push_back(Metadata);
- MetadataHashes.push_back(IoHash::HashBuffer(Metadata.GetBuffer().AsIoBuffer()));
- }
- Cache->PutBlobMetadatas(BuildId, BlobHashes, Metadatas);
- }
-
- Cache->Flush(500);
- Cache = CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, false);
-
- {
- std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
- CHECK(Exists.size() == BlobHashes.size());
- for (size_t I = 0; I < BlobCount; I++)
- {
- CHECK(Exists[I].HasBody);
- CHECK(Exists[I].HasMetadata);
- }
-
- std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
- CHECK_EQ(BlobCount, FetchedMetadatas.size());
-
- for (size_t I = 0; I < BlobCount; I++)
- {
- CHECK_EQ(MetadataHashes[I], IoHash::HashBuffer(FetchedMetadatas[I].GetBuffer().AsIoBuffer()));
- }
- }
-
- for (size_t I = 0; I < BlobCount; I++)
- {
- IoBuffer Blob = CreateSemiRandomBlob(4711 + I * 7);
- CompressedBuffer CompressedBlob = CompressedBuffer::Compress(SharedBuffer(std::move(Blob)));
- BlobHashes.push_back(CompressedBlob.DecodeRawHash());
- Cache->PutBuildBlob(BuildId, BlobHashes.back(), ZenContentType::kCompressedBinary, CompressedBlob.GetCompressed());
- }
-
- Cache->Flush(500);
- Cache = CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, false);
-
- {
- std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
- CHECK(Exists.size() == BlobHashes.size());
- for (size_t I = 0; I < BlobCount * 2; I++)
- {
- CHECK(Exists[I].HasBody);
- CHECK_EQ(I < BlobCount, Exists[I].HasMetadata);
- }
-
- std::vector<CbObject> MetaDatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
- CHECK_EQ(BlobCount, MetaDatas.size());
-
- std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
- CHECK_EQ(BlobCount, FetchedMetadatas.size());
-
- for (size_t I = 0; I < BlobCount; I++)
- {
- CHECK_EQ(MetadataHashes[I], IoHash::HashBuffer(FetchedMetadatas[I].GetBuffer().AsIoBuffer()));
- }
- }
- }
-
- {
- ZenServerInstance Instance(TestEnv);
-
- const uint16_t PortNumber =
- Instance.SpawnServerAndWaitUntilReady(fmt::format("--buildstore-enabled --system-dir {}", SystemRootPath));
- CHECK(PortNumber != 0);
-
- HttpClient Client(Instance.GetBaseUri());
-
- BuildStorageCache::Statistics Stats;
- std::unique_ptr<BuildStorageCache> Cache(CreateZenBuildStorageCache(Client, Stats, Namespace, Bucket, TempDir, false));
-
- std::vector<BuildStorageCache::BlobExistsResult> Exists = Cache->BlobsExists(BuildId, BlobHashes);
- CHECK(Exists.size() == BlobHashes.size());
- for (size_t I = 0; I < BlobCount * 2; I++)
- {
- CHECK(Exists[I].HasBody);
- CHECK_EQ(I < BlobCount, Exists[I].HasMetadata);
- }
-
- for (size_t I = 0; I < BlobCount * 2; I++)
- {
- IoBuffer BuildBlob = Cache->GetBuildBlob(BuildId, BlobHashes[I]);
- CHECK(BuildBlob);
- CHECK_EQ(BlobHashes[I],
- IoHash::HashBuffer(CompressedBuffer::FromCompressedNoValidate(std::move(BuildBlob)).Decompress().AsIoBuffer()));
- }
-
- std::vector<CbObject> MetaDatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
- CHECK_EQ(BlobCount, MetaDatas.size());
-
- std::vector<CbObject> FetchedMetadatas = Cache->GetBlobMetadatas(BuildId, BlobHashes);
- CHECK_EQ(BlobCount, FetchedMetadatas.size());
-
- for (size_t I = 0; I < BlobCount; I++)
- {
- CHECK_EQ(MetadataHashes[I], IoHash::HashBuffer(FetchedMetadatas[I].GetBuffer().AsIoBuffer()));
- }
- }
-}
-
# if 0
TEST_CASE("lifetime.owner")
{
diff --git a/src/zenserver-test/zenserver-test.h b/src/zenserver-test/zenserver-test.h
new file mode 100644
index 000000000..e7cee3f94
--- /dev/null
+++ b/src/zenserver-test/zenserver-test.h
@@ -0,0 +1,207 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#if ZEN_WITH_TESTS
+
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/iobuffer.h>
+# include <zencore/stream.h>
+# include <zencore/string.h>
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
+# include <zenhttp/httpcommon.h>
+# include <zenutil/zenserverprocess.h>
+
+# include <functional>
+
+namespace zen::tests {
+
+extern zen::ZenServerEnvironment TestEnv;
+
+inline IoBuffer
+MakeCbObjectPayload(std::function<void(CbObjectWriter& Writer)> WriteCB)
+{
+ CbObjectWriter Writer;
+ WriteCB(Writer);
+ IoBuffer Payload = Writer.Save().GetBuffer().AsIoBuffer();
+ Payload.SetContentType(ZenContentType::kCbObject);
+ return Payload;
+};
+
+inline IoBuffer
+SerializeToBuffer(const zen::CbPackage& Package)
+{
+ BinaryWriter MemStream;
+
+ Package.Save(MemStream);
+
+ IoBuffer Buffer = zen::IoBuffer(zen::IoBuffer::Clone, MemStream.Data(), MemStream.Size());
+ Buffer.SetContentType(HttpContentType::kCbPackage);
+ return Buffer;
+};
+
+namespace utils {
+
+ struct ZenConfig
+ {
+ std::filesystem::path DataDir;
+ uint16_t Port;
+ std::string BaseUri;
+ std::string Args;
+
+ static ZenConfig New(std::string Args = "")
+ {
+ return ZenConfig{.DataDir = TestEnv.CreateNewTestDir(), .Port = TestEnv.GetNewPortNumber(), .Args = std::move(Args)};
+ }
+
+ static ZenConfig New(uint16_t Port, std::string Args = "")
+ {
+ return ZenConfig{.DataDir = TestEnv.CreateNewTestDir(), .Port = Port, .Args = std::move(Args)};
+ }
+
+ static ZenConfig NewWithUpstream(uint16_t Port, uint16_t UpstreamPort, std::string Args = "")
+ {
+ return New(Port,
+ fmt::format("{}{}--debug --upstream-thread-count=0 --upstream-zen-url=http://localhost:{}",
+ Args,
+ Args.length() > 0 ? " " : "",
+ UpstreamPort));
+ }
+
+ static ZenConfig NewWithThreadedUpstreams(uint16_t NewPort, std::span<uint16_t> UpstreamPorts, bool Debug)
+ {
+ std::string Args = Debug ? "--debug" : "";
+ for (uint16_t Port : UpstreamPorts)
+ {
+ Args = fmt::format("{}{}--upstream-zen-url=http://localhost:{}", Args, Args.length() > 0 ? " " : "", Port);
+ }
+ return New(NewPort, Args);
+ }
+
+ void Spawn(ZenServerInstance& Inst)
+ {
+ Inst.SetTestDir(DataDir);
+ Inst.SpawnServer(Port, Args);
+ const uint16_t InstancePort = Inst.WaitUntilReady();
+ CHECK_MESSAGE(InstancePort != 0, Inst.GetLogOutput());
+
+ if (Port != InstancePort)
+ ZEN_DEBUG("relocation detected from {} to {}", Port, InstancePort);
+
+ Port = InstancePort;
+ BaseUri = fmt::format("http://localhost:{}/z$", Port);
+ }
+ };
+
+ inline void SpawnServer(ZenServerInstance& Server, ZenConfig& Cfg) { Cfg.Spawn(Server); }
+
+ inline CompressedBuffer CreateSemiRandomBlob(size_t AttachmentSize,
+ OodleCompressionLevel CompressionLevel = OodleCompressionLevel::VeryFast)
+ {
+ // Convoluted way to get a compressed buffer whose result it large enough to be a separate file
+ // but also does actually compress
+ const size_t PartCount = (AttachmentSize / (1u * 1024u * 64)) + 1;
+ const size_t PartSize = AttachmentSize / PartCount;
+ auto Part = SharedBuffer(CreateRandomBlob(PartSize));
+ std::vector<SharedBuffer> Parts(PartCount, Part);
+ size_t RemainPartSize = AttachmentSize - (PartSize * PartCount);
+ if (RemainPartSize > 0)
+ {
+ Parts.push_back(SharedBuffer(CreateRandomBlob(RemainPartSize)));
+ }
+ CompressedBuffer Value = CompressedBuffer::Compress(CompositeBuffer(std::move(Parts)), OodleCompressor::Mermaid, CompressionLevel);
+ return Value;
+ };
+
+ inline std::vector<std::pair<Oid, CompressedBuffer>> CreateAttachments(const std::span<const size_t>& Sizes)
+ {
+ std::vector<std::pair<Oid, CompressedBuffer>> Result;
+ Result.reserve(Sizes.size());
+ for (size_t Size : Sizes)
+ {
+ CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(CreateRandomBlob(Size)));
+ Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed));
+ }
+ return Result;
+ }
+
+ inline std::vector<std::pair<Oid, CompressedBuffer>> CreateSemiRandomAttachments(const std::span<const size_t>& Sizes)
+ {
+ std::vector<std::pair<Oid, CompressedBuffer>> Result;
+ Result.reserve(Sizes.size());
+ for (size_t Size : Sizes)
+ {
+ CompressedBuffer Compressed =
+ CreateSemiRandomBlob(Size, Size > 1024u * 1024u ? OodleCompressionLevel::None : OodleCompressionLevel::VeryFast);
+ Result.emplace_back(std::pair<Oid, CompressedBuffer>(Oid::NewOid(), Compressed));
+ }
+ return Result;
+ }
+
+} // namespace utils
+
+class ZenServerTestHelper
+{
+public:
+ ZenServerTestHelper(std::string_view HelperId, int ServerCount) : m_HelperId{HelperId}, m_ServerCount{ServerCount} {}
+ ~ZenServerTestHelper() {}
+
+ void SpawnServers(std::string_view AdditionalServerArgs = std::string_view())
+ {
+ SpawnServers([](ZenServerInstance&) {}, AdditionalServerArgs);
+ }
+
+ void SpawnServers(auto&& Callback, std::string_view AdditionalServerArgs)
+ {
+ ZEN_INFO("{}: spawning {} server instances", m_HelperId, m_ServerCount);
+
+ m_Instances.resize(m_ServerCount);
+
+ for (int i = 0; i < m_ServerCount; ++i)
+ {
+ auto& Instance = m_Instances[i];
+ Instance = std::make_unique<ZenServerInstance>(TestEnv);
+ Instance->SetTestDir(TestEnv.CreateNewTestDir());
+ }
+
+ for (int i = 0; i < m_ServerCount; ++i)
+ {
+ auto& Instance = m_Instances[i];
+ Callback(*Instance);
+ }
+
+ for (int i = 0; i < m_ServerCount; ++i)
+ {
+ auto& Instance = m_Instances[i];
+ Instance->SpawnServer(TestEnv.GetNewPortNumber(), AdditionalServerArgs);
+ }
+
+ for (int i = 0; i < m_ServerCount; ++i)
+ {
+ auto& Instance = m_Instances[i];
+ uint16_t PortNumber = Instance->WaitUntilReady();
+ CHECK_MESSAGE(PortNumber != 0, Instance->GetLogOutput());
+ }
+ }
+
+ ZenServerInstance& GetInstance(int Index) { return *m_Instances[Index]; }
+
+private:
+ std::string m_HelperId;
+ int m_ServerCount = 0;
+ std::vector<std::unique_ptr<ZenServerInstance>> m_Instances;
+};
+
+inline std::string
+OidAsString(const Oid& Id)
+{
+ StringBuilder<25> OidStringBuilder;
+ Id.ToString(OidStringBuilder);
+ return OidStringBuilder.ToString();
+}
+
+} // namespace zen::tests
+
+#endif
diff --git a/src/zenserver/admin/admin.cpp b/src/zenserver/admin/admin.cpp
index f6ad14422..97522e892 100644
--- a/src/zenserver/admin/admin.cpp
+++ b/src/zenserver/admin/admin.cpp
@@ -73,7 +73,7 @@ GetStatsForDirectory(std::filesystem::path Dir)
GetDirectoryContent(Dir,
DirectoryContentFlags::IncludeAllEntries | DirectoryContentFlags::IncludeFileSizes,
DirTraverser,
- GetSmallWorkerPool(EWorkloadType::Burst),
+ GetSmallWorkerPool(EWorkloadType::Background),
PendingWorkCount);
PendingWorkCount.CountDown();
PendingWorkCount.Wait();
diff --git a/src/zenserver/buildstore/httpbuildstore.cpp b/src/zenserver/buildstore/httpbuildstore.cpp
index 2a3ce41b7..bce993f17 100644
--- a/src/zenserver/buildstore/httpbuildstore.cpp
+++ b/src/zenserver/buildstore/httpbuildstore.cpp
@@ -48,8 +48,8 @@ HttpBuildStoreService::Initialize()
{
ZEN_LOG_INFO(LogBuilds, "Initializing Builds Service");
- m_Router.AddPattern("namespace", "([[:alnum:]-_.]+)");
- m_Router.AddPattern("bucket", "([[:alnum:]-_.]+)");
+ m_Router.AddPattern("namespace", "([[:alnum:]\\-_.]+)");
+ m_Router.AddPattern("bucket", "([[:alnum:]\\-_.]+)");
m_Router.AddPattern("buildid", "([[:xdigit:]]{24})");
m_Router.AddPattern("hash", "([[:xdigit:]]{40})");
diff --git a/src/zenserver/cache/httpstructuredcache.cpp b/src/zenserver/cache/httpstructuredcache.cpp
index 68f1c602e..dd5bf05cb 100644
--- a/src/zenserver/cache/httpstructuredcache.cpp
+++ b/src/zenserver/cache/httpstructuredcache.cpp
@@ -5,12 +5,14 @@
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compactbinaryvalidation.h>
#include <zencore/compress.h>
#include <zencore/enumflags.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/memory/llm.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/stream.h>
#include <zencore/timer.h>
@@ -19,13 +21,11 @@
#include <zenhttp/httpserver.h>
#include <zenhttp/httpstats.h>
#include <zenhttp/packageformat.h>
+#include <zenremotestore/jupiter/jupiterclient.h>
+#include <zenstore/cache/cache.h>
#include <zenstore/cache/structuredcachestore.h>
#include <zenstore/gc.h>
-#include <zenutil/cache/cache.h>
-#include <zenutil/cache/cacherequests.h>
-#include <zenutil/cache/rpcrecording.h>
-#include <zenutil/jupiter/jupiterclient.h>
-#include <zenutil/parallelwork.h>
+#include <zenutil/rpcrecording.h>
#include <zenutil/workerpools.h>
#include "upstream/upstreamcache.h"
@@ -39,7 +39,6 @@
#include <queue>
#include <thread>
-#include <cpr/cpr.h>
#include <gsl/gsl-lite.hpp>
namespace zen {
@@ -71,15 +70,6 @@ namespace {
static constinit std::string_view HttpZCacheUtilStopRecording = "exec$/stop-recording"sv;
static constinit std::string_view HttpZCacheUtilReplayRecording = "exec$/replay-recording"sv;
static constinit std::string_view HttpZCacheDetailsPrefix = "details$"sv;
-
- struct HttpRequestData
- {
- std::optional<std::string> Namespace;
- std::optional<std::string> Bucket;
- std::optional<IoHash> HashKey;
- std::optional<IoHash> ValueContentId;
- };
-
} // namespace
//////////////////////////////////////////////////////////////////////////
@@ -390,8 +380,9 @@ HttpStructuredCacheService::HandleRequest(HttpServerRequest& Request)
if (Key == HttpZCacheUtilStartRecording)
{
- HttpServerRequest::QueryParams Params = Request.GetQueryParams();
- std::string RecordPath = cpr::util::urlDecode(std::string(Params.GetValue("path")));
+ HttpServerRequest::QueryParams Params = Request.GetQueryParams();
+
+ std::string RecordPath = UrlDecode(Params.GetValue("path"));
{
RwLock::ExclusiveLockScope _(m_RequestRecordingLock);
@@ -428,9 +419,11 @@ HttpStructuredCacheService::HandleRequest(HttpServerRequest& Request)
m_RequestRecorder.reset();
}
- HttpServerRequest::QueryParams Params = Request.GetQueryParams();
- std::string RecordPath = cpr::util::urlDecode(std::string(Params.GetValue("path")));
- uint32_t ThreadCount = std::thread::hardware_concurrency();
+ HttpServerRequest::QueryParams Params = Request.GetQueryParams();
+
+ std::string RecordPath = UrlDecode(Params.GetValue("path"));
+
+ uint32_t ThreadCount = std::thread::hardware_concurrency();
if (auto Param = Params.GetValue("thread_count"); Param.empty() == false)
{
if (auto Value = ParseInt<uint64_t>(Param))
@@ -444,7 +437,7 @@ HttpStructuredCacheService::HandleRequest(HttpServerRequest& Request)
std::unique_ptr<cache::IRpcRequestReplayer> Replayer(cache::MakeDiskRequestReplayer(RecordPath, false));
ReplayRequestRecorder(RequestContext, *Replayer, ThreadCount < 1 ? 1 : ThreadCount);
- ZEN_INFO("cache RPC replay STARTED");
+ ZEN_INFO("cache RPC replay COMPLETED");
Request.WriteResponse(HttpResponseCode::OK);
return;
@@ -456,8 +449,8 @@ HttpStructuredCacheService::HandleRequest(HttpServerRequest& Request)
return;
}
- cacherequests::HttpRequestData RequestData;
- if (!cacherequests::HttpRequestParseRelativeUri(Key, ZenCacheStore::DefaultNamespace, RequestData))
+ HttpCacheRequestData RequestData;
+ if (!HttpCacheRequestParseRelativeUri(Key, ZenCacheStore::DefaultNamespace, RequestData))
{
m_CacheStats.BadRequestCount++;
return Request.WriteResponse(HttpResponseCode::BadRequest); // invalid URL
@@ -820,7 +813,8 @@ HttpStructuredCacheService::HandleGetCacheRecord(HttpServerRequest& Request, con
const bool SkipData = EnumHasAllFlags(PolicyFromUrl, CachePolicy::SkipData);
const bool PartialRecord = EnumHasAllFlags(PolicyFromUrl, CachePolicy::PartialRecord);
- bool Success = false;
+ bool Success = false;
+ uint32_t MissingCount = 0;
ZenCacheValue ClientResultValue;
if (!EnumHasAnyFlags(PolicyFromUrl, CachePolicy::Query))
{
@@ -842,45 +836,60 @@ HttpStructuredCacheService::HandleGetCacheRecord(HttpServerRequest& Request, con
{
if (ContentType == ZenContentType::kCbObject)
{
- CbPackage Package;
- uint32_t MissingCount = 0;
-
- CbObjectView CacheRecord(ClientResultValue.Value.Data());
- CacheRecord.IterateAttachments([this, &MissingCount, &Package, SkipData](CbFieldView AttachmentHash) {
- if (SkipData)
- {
- if (!m_CidStore.ContainsChunk(AttachmentHash.AsHash()))
+ CbPackage Package;
+ CbValidateError ValidateError = CbValidateError::None;
+ if (CbObject PackageObject = ValidateAndReadCompactBinaryObject(std::move(ClientResultValue.Value), ValidateError);
+ ValidateError == CbValidateError::None)
+ {
+ CbObjectView CacheRecord(ClientResultValue.Value.Data());
+ CacheRecord.IterateAttachments([this, &MissingCount, &Package, SkipData](CbFieldView AttachmentHash) {
+ if (SkipData)
{
- MissingCount++;
+ if (!m_CidStore.ContainsChunk(AttachmentHash.AsHash()))
+ {
+ MissingCount++;
+ }
}
- }
- else
- {
- if (IoBuffer Chunk = m_CidStore.FindChunkByCid(AttachmentHash.AsHash()))
+ else
{
- CompressedBuffer Compressed = CompressedBuffer::FromCompressedNoValidate(std::move(Chunk));
- if (Compressed)
+ if (IoBuffer Chunk = m_CidStore.FindChunkByCid(AttachmentHash.AsHash()))
{
- Package.AddAttachment(CbAttachment(Compressed, AttachmentHash.AsHash()));
+ CompressedBuffer Compressed = CompressedBuffer::FromCompressedNoValidate(std::move(Chunk));
+ if (Compressed)
+ {
+ Package.AddAttachment(CbAttachment(Compressed, AttachmentHash.AsHash()));
+ }
+ else
+ {
+ ZEN_WARN("invalid compressed binary returned for {}", AttachmentHash.AsHash());
+ MissingCount++;
+ }
}
else
{
- ZEN_WARN("invalid compressed binary returned for {}", AttachmentHash.AsHash());
MissingCount++;
}
}
- else
- {
- MissingCount++;
- }
- }
- });
+ });
- Success = MissingCount == 0 || PartialRecord;
+ Success = MissingCount == 0 || PartialRecord;
+ }
+ else
+ {
+ ZEN_WARN("Invalid compact binary payload returned for {}/{}/{} ({}). Reason: '{}'",
+ Ref.Namespace,
+ Ref.BucketSegment,
+ Ref.HashKey,
+ Ref.ValueContentId,
+ ToString(ValidateError));
+ Success = false;
+ }
if (Success)
{
- Package.SetObject(LoadCompactBinaryObject(ClientResultValue.Value));
+ CbObject PackageObject = LoadCompactBinaryObject(std::move(ClientResultValue.Value));
+
+ Package.SetObject(std::move(PackageObject));
BinaryWriter MemStream;
Package.Save(MemStream);
@@ -919,7 +928,9 @@ HttpStructuredCacheService::HandleGetCacheRecord(HttpServerRequest& Request, con
else
{
// kCbPackage handled SkipData when constructing the ClientResultValue, kcbObject ignores SkipData
- return Request.WriteResponse(HttpResponseCode::OK, ClientResultValue.Value.GetContentType(), ClientResultValue.Value);
+ return Request.WriteResponse((MissingCount == 0) ? HttpResponseCode::OK : HttpResponseCode::PartialContent,
+ ClientResultValue.Value.GetContentType(),
+ ClientResultValue.Value);
}
}
else if (!HasUpstream || !EnumHasAllFlags(PolicyFromUrl, CachePolicy::QueryRemote))
@@ -1204,8 +1215,11 @@ HttpStructuredCacheService::HandlePutCacheRecord(HttpServerRequest& Request, con
break;
}
- return PutResult.Message.empty() ? Request.WriteResponse(ResponseCode)
- : Request.WriteResponse(ResponseCode, zen::HttpContentType::kText, PutResult.Message);
+ if (PutResult.Details)
+ {
+ Request.WriteResponse(ResponseCode, PutResult.Details);
+ }
+ return Request.WriteResponse(ResponseCode);
};
const HttpContentType ContentType = Request.RequestContentType();
@@ -1629,7 +1643,7 @@ HttpStructuredCacheService::ReplayRequestRecorder(const CacheRequestContext& Co
auto _ = MakeGuard([&]() { ZEN_INFO("Replayed {} requests in {}", RequestCount, NiceLatencyNs(Timer.GetElapsedTimeUs() * 1000)); });
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::EnableBacklog);
ZEN_INFO("Replaying {} requests", RequestCount);
for (uint64_t RequestIndex = 0; RequestIndex < RequestCount; ++RequestIndex)
{
diff --git a/src/zenserver/cache/httpstructuredcache.h b/src/zenserver/cache/httpstructuredcache.h
index cb822f117..a157148c9 100644
--- a/src/zenserver/cache/httpstructuredcache.h
+++ b/src/zenserver/cache/httpstructuredcache.h
@@ -6,8 +6,8 @@
#include <zenhttp/httpserver.h>
#include <zenhttp/httpstats.h>
#include <zenhttp/httpstatus.h>
+#include <zenstore/cache/cache.h>
#include <zenstore/cache/cacherpc.h>
-#include <zenutil/cache/cache.h>
#include <zenutil/openprocesscache.h>
#include <memory>
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp
index fb2d9b7f4..0cf5a9ca3 100644
--- a/src/zenserver/config.cpp
+++ b/src/zenserver/config.cpp
@@ -7,6 +7,7 @@
#include <zencore/basicfile.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compactbinaryvalidation.h>
#include <zencore/crypto.h>
#include <zencore/except.h>
@@ -72,13 +73,12 @@ ReadAllCentralManifests(const std::filesystem::path& SystemRoot)
{
try
{
- FileContents FileData = ReadFile(File);
- IoBuffer DataBuffer = FileData.Flatten();
- CbValidateError ValidateError = ValidateCompactBinary(DataBuffer, CbValidateMode::All);
-
- if (ValidateError == CbValidateError::None)
+ FileContents FileData = ReadFile(File);
+ CbValidateError ValidateError;
+ if (CbObject Manifest = ValidateAndReadCompactBinaryObject(FileData.Flatten(), ValidateError);
+ ValidateError == CbValidateError::None)
{
- Manifests.push_back(LoadCompactBinaryObject(DataBuffer));
+ Manifests.emplace_back(std::move(Manifest));
}
else
{
@@ -99,35 +99,37 @@ ValidateOptions(ZenServerOptions& ServerOptions)
{
if (ServerOptions.EncryptionKey.empty() == false)
{
- const auto Key = zen::AesKey256Bit::FromString(ServerOptions.EncryptionKey);
+ const auto Key = AesKey256Bit::FromString(ServerOptions.EncryptionKey);
if (Key.IsValid() == false)
{
- throw zen::OptionParseException("Invalid AES encryption key");
+ throw OptionParseException(fmt::format("'--encryption-aes-key' ('{}') is malformed", ServerOptions.EncryptionKey), {});
}
}
if (ServerOptions.EncryptionIV.empty() == false)
{
- const auto IV = zen::AesIV128Bit::FromString(ServerOptions.EncryptionIV);
+ const auto IV = AesIV128Bit::FromString(ServerOptions.EncryptionIV);
if (IV.IsValid() == false)
{
- throw zen::OptionParseException("Invalid AES initialization vector");
+ throw OptionParseException(fmt::format("'--encryption-aes-iv' ('{}') is malformed", ServerOptions.EncryptionIV), {});
}
}
if (ServerOptions.HttpServerConfig.ForceLoopback && ServerOptions.IsDedicated)
{
- throw zen::OptionParseException("Dedicated server can not be used with forced local server address");
+ throw OptionParseException("'--dedicated' conflicts with '--http-forceloopback'", {});
}
if (ServerOptions.GcConfig.AttachmentPassCount > ZenGcConfig::GcMaxAttachmentPassCount)
{
- throw zen::OptionParseException(
- fmt::format("GC attachment pass count can not be larger than {}", ZenGcConfig::GcMaxAttachmentPassCount));
+ throw OptionParseException(fmt::format("'--gc-attachment-passes' ('{}') is invalid, maximum is {}.",
+ ServerOptions.GcConfig.AttachmentPassCount,
+ ZenGcConfig::GcMaxAttachmentPassCount),
+ {});
}
if (ServerOptions.GcConfig.UseGCV2 == false)
{
- ZEN_WARN("--gc-v2=false is deprecated, reverting to --gc-v2=true");
+ ZEN_WARN("'--gc-v2=false' is deprecated, reverting to '--gc-v2=true'");
ServerOptions.GcConfig.UseGCV2 = true;
}
}
@@ -185,7 +187,7 @@ class CachePolicyOption : public LuaConfig::OptionValue
{
public:
CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {}
- virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override
+ virtual void Print(std::string_view, StringBuilderBase& StringBuilder) override
{
switch (Value)
{
@@ -232,7 +234,7 @@ class ZenAuthConfigOption : public LuaConfig::OptionValue
{
public:
ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {}
- virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
+ virtual void Print(std::string_view Indent, StringBuilderBase& StringBuilder) override
{
if (Value.OpenIdProviders.empty())
{
@@ -275,7 +277,7 @@ class ZenObjectStoreConfigOption : public LuaConfig::OptionValue
{
public:
ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {}
- virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
+ virtual void Print(std::string_view Indent, StringBuilderBase& StringBuilder) override
{
if (Value.Buckets.empty())
{
@@ -318,7 +320,7 @@ class ZenStructuredCacheBucketsConfigOption : public LuaConfig::OptionValue
{
public:
ZenStructuredCacheBucketsConfigOption(std::vector<std::pair<std::string, ZenStructuredCacheBucketConfig>>& Value) : Value(Value) {}
- virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override
+ virtual void Print(std::string_view Indent, StringBuilderBase& StringBuilder) override
{
if (Value.empty())
{
@@ -359,14 +361,15 @@ public:
std::string Name = Kv.first.as<std::string>();
if (Name.empty())
{
- throw zen::OptionParseException(fmt::format("cache bucket option must have a name."));
+ throw OptionParseException("Cache bucket option must have a name.", {});
}
const uint64_t MaxBlockSize = Bucket.value().get_or("maxblocksize", BucketConfig.MaxBlockSize);
if (MaxBlockSize == 0)
{
- throw zen::OptionParseException(
- fmt::format("maxblocksize option for cache bucket '{}' is invalid. It must be non-zero.", Name));
+ throw OptionParseException(
+ fmt::format("'maxblocksize' option for cache bucket '{}' is invalid. It must be non-zero.", Name),
+ {});
}
BucketConfig.MaxBlockSize = MaxBlockSize;
@@ -375,8 +378,9 @@ public:
const uint64_t MemCacheSizeThreshold = Bucket.value().get_or("sizethreshold", BucketConfig.MemCacheSizeThreshold);
if (MemCacheSizeThreshold == 0)
{
- throw zen::OptionParseException(
- fmt::format("memlayer.sizethreshold option for cache bucket '{}' is invalid. It must be non-zero.", Name));
+ throw OptionParseException(
+ fmt::format("'memlayer.sizethreshold' option for cache bucket '{}' is invalid. It must be non-zero.", Name),
+ {});
}
BucketConfig.MemCacheSizeThreshold = Bucket.value().get_or("sizethreshold", BucketConfig.MemCacheSizeThreshold);
}
@@ -384,17 +388,20 @@ public:
const uint32_t PayloadAlignment = Bucket.value().get_or("payloadalignment", BucketConfig.PayloadAlignment);
if (PayloadAlignment == 0 || !IsPow2(PayloadAlignment))
{
- throw zen::OptionParseException(fmt::format(
- "payloadalignment option for cache bucket '{}' is invalid. It needs to be non-zero and a power of two.",
- Name));
+ throw OptionParseException(
+ fmt::format(
+ "'payloadalignment' option for cache bucket '{}' is invalid. It needs to be non-zero and a power of two.",
+ Name),
+ {});
}
BucketConfig.PayloadAlignment = PayloadAlignment;
const uint64_t LargeObjectThreshold = Bucket.value().get_or("largeobjectthreshold", BucketConfig.LargeObjectThreshold);
if (LargeObjectThreshold == 0)
{
- throw zen::OptionParseException(
- fmt::format("largeobjectthreshold option for cache bucket '{}' is invalid. It must be non-zero.", Name));
+ throw OptionParseException(
+ fmt::format("'largeobjectthreshold' option for cache bucket '{}' is invalid. It must be non-zero.", Name),
+ {});
}
BucketConfig.LargeObjectThreshold = LargeObjectThreshold;
@@ -409,19 +416,19 @@ public:
};
std::shared_ptr<LuaConfig::OptionValue>
-MakeOption(zen::UpstreamCachePolicy& Value)
+MakeOption(UpstreamCachePolicy& Value)
{
return std::make_shared<CachePolicyOption>(Value);
};
std::shared_ptr<LuaConfig::OptionValue>
-MakeOption(zen::ZenAuthConfig& Value)
+MakeOption(ZenAuthConfig& Value)
{
return std::make_shared<ZenAuthConfigOption>(Value);
};
std::shared_ptr<LuaConfig::OptionValue>
-MakeOption(zen::ZenObjectStoreConfig& Value)
+MakeOption(ZenObjectStoreConfig& Value)
{
return std::make_shared<ZenObjectStoreConfigOption>(Value);
};
@@ -660,11 +667,11 @@ ParseConfigFile(const std::filesystem::path& Path,
if (!OutputConfigFile.empty())
{
- std::filesystem::path WritePath(MakeSafeAbsolutePath(OutputConfigFile));
- zen::ExtendableStringBuilder<512> ConfigStringBuilder;
+ std::filesystem::path WritePath(MakeSafeAbsolutePath(OutputConfigFile));
+ ExtendableStringBuilder<512> ConfigStringBuilder;
LuaOptions.Print(ConfigStringBuilder, CmdLineResult);
- zen::BasicFile Output;
- Output.Open(WritePath, zen::BasicFile::Mode::kTruncate);
+ BasicFile Output;
+ Output.Open(WritePath, BasicFile::Mode::kTruncate);
Output.Write(ConfigStringBuilder.Data(), ConfigStringBuilder.Size(), 0);
}
}
@@ -737,7 +744,7 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
const char* DefaultHttp = "asio";
#if ZEN_WITH_HTTPSYS
- if (!zen::windows::IsRunningOnWine())
+ if (!windows::IsRunningOnWine())
{
DefaultHttp = "httpsys";
}
@@ -1334,7 +1341,7 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
}
catch (const std::exception& Ex)
{
- throw zen::OptionParseException(Ex.what());
+ throw OptionParseException(Ex.what(), options.help());
}
if (Result.count("help"))
@@ -1399,17 +1406,17 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
if (!BaseSnapshotDir.empty())
{
if (DataDir.empty())
- throw zen::OptionParseException("You must explicitly specify a data directory when specifying a base snapshot");
+ throw OptionParseException("'--snapshot-dir' requires '--data-dir'", options.help());
if (!IsDir(ServerOptions.BaseSnapshotDir))
- throw OptionParseException(fmt::format("Snapshot directory must be a directory: '{}", BaseSnapshotDir));
+ throw std::runtime_error(fmt::format("'--snapshot-dir' ('{}') must be a directory", BaseSnapshotDir));
}
if (OpenIdProviderUrl.empty() == false)
{
if (OpenIdClientId.empty())
{
- throw zen::OptionParseException("Invalid OpenID client ID");
+ throw OptionParseException("'--openid-provider-url' requires '--openid-client-id'", options.help());
}
ServerOptions.AuthConfig.OpenIdProviders.push_back(
@@ -1436,10 +1443,10 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
ValidateOptions(ServerOptions);
}
- catch (const zen::OptionParseException& e)
+ catch (const OptionParseException& e)
{
- ZEN_CONSOLE_ERROR("Invalid zenserver arguments: {}\n\n{}", e.what(), options.help());
-
+ ZEN_CONSOLE("{}\n", options.help());
+ ZEN_CONSOLE_ERROR("Invalid zenserver arguments: {}", e.what());
throw;
}
diff --git a/src/zenserver/frontend/html.zip b/src/zenserver/frontend/html.zip
index 1de4c3d74..bb3d61198 100644
--- a/src/zenserver/frontend/html.zip
+++ b/src/zenserver/frontend/html.zip
Binary files differ
diff --git a/src/zenserver/frontend/html/pages/tree.js b/src/zenserver/frontend/html/pages/tree.js
index 23ff3e819..08a578492 100644
--- a/src/zenserver/frontend/html/pages/tree.js
+++ b/src/zenserver/frontend/html/pages/tree.js
@@ -93,7 +93,7 @@ export class Page extends ZenPage
const is_node_l = l.endsWith("/");
const any_nodes = is_node_l + r.endsWith("/");
if (any_nodes == 1) return is_node_l ? -1 : 1;
- if (sort_by >= 0) return (new_nodes[r][sort_by] - new_nodes[l][sort_by]);
+ if (sort_by >= 0) return Number(new_nodes[r][sort_by] - new_nodes[l][sort_by]);
return r < l;
})
diff --git a/src/zenserver/main.cpp b/src/zenserver/main.cpp
index d512d4894..a91c95ffb 100644
--- a/src/zenserver/main.cpp
+++ b/src/zenserver/main.cpp
@@ -31,7 +31,7 @@
#if ZEN_PLATFORM_WINDOWS
# include <zencore/windows.h>
-# include <zenutil/windows/service.h>
+# include <zenutil/windows/windowsservice.h>
#endif
//////////////////////////////////////////////////////////////////////////
diff --git a/src/zenserver/projectstore/httpprojectstore.cpp b/src/zenserver/projectstore/httpprojectstore.cpp
index 317a419eb..1c6b5d6b0 100644
--- a/src/zenserver/projectstore/httpprojectstore.cpp
+++ b/src/zenserver/projectstore/httpprojectstore.cpp
@@ -2,9 +2,6 @@
#include "httpprojectstore.h"
-#include "oplogreferencedset.h"
-#include "projectstore.h"
-
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
#include <zencore/compactbinaryutil.h>
@@ -13,9 +10,19 @@
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/memory/llm.h>
+#include <zencore/scopeguard.h>
#include <zencore/stream.h>
#include <zencore/trace.h>
+#include <zenhttp/packageformat.h>
+#include <zenremotestore/projectstore/buildsremoteprojectstore.h>
+#include <zenremotestore/projectstore/fileremoteprojectstore.h>
+#include <zenremotestore/projectstore/jupiterremoteprojectstore.h>
+#include <zenremotestore/projectstore/remoteprojectstore.h>
+#include <zenremotestore/projectstore/zenremoteprojectstore.h>
+#include <zenstore/oplogreferencedset.h>
+#include <zenstore/projectstore.h>
#include <zenstore/zenstore.h>
+#include <zenutil/openprocesscache.h>
#include <zenutil/workerpools.h>
namespace zen {
@@ -46,15 +53,15 @@ CSVHeader(bool Details, bool AttachmentDetails, StringBuilderBase& CSVWriter)
}
void
-CSVWriteOp(CidStore& CidStore,
- std::string_view ProjectId,
- std::string_view OplogId,
- bool Details,
- bool AttachmentDetails,
- uint32_t LSN,
- const Oid& Key,
- CbObjectView Op,
- StringBuilderBase& CSVWriter)
+CSVWriteOp(CidStore& CidStore,
+ std::string_view ProjectId,
+ std::string_view OplogId,
+ bool Details,
+ bool AttachmentDetails,
+ ProjectStore::LogSequenceNumber LSN,
+ const Oid& Key,
+ CbObjectView Op,
+ StringBuilderBase& CSVWriter)
{
StringBuilder<32> KeyStringBuilder;
Key.ToString(KeyStringBuilder);
@@ -66,8 +73,8 @@ CSVWriteOp(CidStore& CidStore,
const IoHash AttachmentHash = FieldView.AsAttachment();
IoBuffer Attachment = CidStore.FindChunkByCid(AttachmentHash);
CSVWriter << "\r\n"
- << ProjectId << ", " << OplogId << ", " << LSN << ", " << KeyString << ", " << AttachmentHash.ToHexString() << ", "
- << gsl::narrow<uint64_t>(Attachment.GetSize());
+ << ProjectId << ", " << OplogId << ", " << LSN.Number << ", " << KeyString << ", " << AttachmentHash.ToHexString()
+ << ", " << gsl::narrow<uint64_t>(Attachment.GetSize());
});
}
else if (Details)
@@ -81,8 +88,8 @@ CSVWriteOp(CidStore& CidStore,
AttachmentsSize += Attachment.GetSize();
});
CSVWriter << "\r\n"
- << ProjectId << ", " << OplogId << ", " << LSN << ", " << KeyString << ", " << gsl::narrow<uint64_t>(Op.GetSize()) << ", "
- << AttachmentCount << ", " << gsl::narrow<uint64_t>(AttachmentsSize);
+ << ProjectId << ", " << OplogId << ", " << LSN.Number << ", " << KeyString << ", " << gsl::narrow<uint64_t>(Op.GetSize())
+ << ", " << AttachmentCount << ", " << gsl::narrow<uint64_t>(AttachmentsSize);
}
else
{
@@ -94,21 +101,21 @@ CSVWriteOp(CidStore& CidStore,
namespace {
- void CbWriteOp(CidStore& CidStore,
- bool Details,
- bool OpDetails,
- bool AttachmentDetails,
- uint32_t LSN,
- const Oid& Key,
- CbObjectView Op,
- CbObjectWriter& CbWriter)
+ void CbWriteOp(CidStore& CidStore,
+ bool Details,
+ bool OpDetails,
+ bool AttachmentDetails,
+ ProjectStore::LogSequenceNumber LSN,
+ const Oid& Key,
+ CbObjectView Op,
+ CbObjectWriter& CbWriter)
{
CbWriter.BeginObject();
{
CbWriter.AddObjectId("key", Key);
if (Details)
{
- CbWriter.AddInteger("lsn", LSN);
+ CbWriter.AddInteger("lsn", LSN.Number);
CbWriter.AddInteger("size", gsl::narrow<uint64_t>(Op.GetSize()));
}
if (AttachmentDetails)
@@ -170,10 +177,11 @@ namespace {
{
Cbo.BeginArray("ops");
{
- Oplog.IterateOplogWithKey(
- [&Cbo, &CidStore, Details, OpDetails, AttachmentDetails](uint32_t LSN, const Oid& Key, CbObjectView Op) {
- CbWriteOp(CidStore, Details, OpDetails, AttachmentDetails, LSN, Key, Op, Cbo);
- });
+ Oplog.IterateOplogWithKey([&Cbo, &CidStore, Details, OpDetails, AttachmentDetails](ProjectStore::LogSequenceNumber LSN,
+ const Oid& Key,
+ CbObjectView Op) {
+ CbWriteOp(CidStore, Details, OpDetails, AttachmentDetails, LSN, Key, Op, Cbo);
+ });
}
Cbo.EndArray();
}
@@ -205,8 +213,8 @@ namespace {
{
for (const std::string& OpLogId : OpLogs)
{
- ProjectStore::Oplog* Oplog = Project.OpenOplog(OpLogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
- if (Oplog != nullptr)
+ Ref<ProjectStore::Oplog> Oplog = Project.OpenOplog(OpLogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
+ if (Oplog)
{
CbWriteOplog(CidStore, *Oplog, Details, OpDetails, AttachmentDetails, Cbo);
}
@@ -231,6 +239,264 @@ namespace {
Cbo.EndObject();
}
+ struct CreateRemoteStoreResult
+ {
+ std::shared_ptr<RemoteProjectStore> Store;
+ std::string Description;
+ };
+
+ CreateRemoteStoreResult CreateRemoteStore(CbObjectView Params,
+ AuthMgr& AuthManager,
+ size_t MaxBlockSize,
+ size_t MaxChunkEmbedSize,
+ const std::filesystem::path& TempFilePath)
+ {
+ ZEN_MEMSCOPE(GetProjectHttpTag());
+
+ using namespace std::literals;
+
+ std::shared_ptr<RemoteProjectStore> RemoteStore;
+
+ if (CbObjectView File = Params["file"sv].AsObjectView(); File)
+ {
+ std::filesystem::path FolderPath(File["path"sv].AsString());
+ if (FolderPath.empty())
+ {
+ return {nullptr, "Missing file path"};
+ }
+ std::string_view Name(File["name"sv].AsString());
+ if (Name.empty())
+ {
+ return {nullptr, "Missing file name"};
+ }
+ std::string_view OptionalBaseName(File["basename"sv].AsString());
+ bool ForceDisableBlocks = File["disableblocks"sv].AsBool(false);
+ bool ForceEnableTempBlocks = File["enabletempblocks"sv].AsBool(false);
+
+ FileRemoteStoreOptions Options = {
+ RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunksPerBlock = 1000, .MaxChunkEmbedSize = MaxChunkEmbedSize},
+ FolderPath,
+ std::string(Name),
+ std::string(OptionalBaseName),
+ ForceDisableBlocks,
+ ForceEnableTempBlocks};
+ RemoteStore = CreateFileRemoteStore(Options);
+ }
+
+ if (CbObjectView Cloud = Params["cloud"sv].AsObjectView(); Cloud)
+ {
+ std::string_view CloudServiceUrl = Cloud["url"sv].AsString();
+ if (CloudServiceUrl.empty())
+ {
+ return {nullptr, "Missing service url"};
+ }
+
+ std::string Url = UrlDecode(CloudServiceUrl);
+ std::string_view Namespace = Cloud["namespace"sv].AsString();
+ if (Namespace.empty())
+ {
+ return {nullptr, "Missing namespace"};
+ }
+ std::string_view Bucket = Cloud["bucket"sv].AsString();
+ if (Bucket.empty())
+ {
+ return {nullptr, "Missing bucket"};
+ }
+ std::string_view OpenIdProvider = Cloud["openid-provider"sv].AsString();
+ std::string AccessToken = std::string(Cloud["access-token"sv].AsString());
+ if (AccessToken.empty())
+ {
+ std::string_view AccessTokenEnvVariable = Cloud["access-token-env"].AsString();
+ if (!AccessTokenEnvVariable.empty())
+ {
+ AccessToken = GetEnvVariable(AccessTokenEnvVariable);
+ }
+ }
+ std::filesystem::path OidcExePath;
+ if (std::string_view OidcExePathString = Cloud["oidc-exe-path"].AsString(); !OidcExePathString.empty())
+ {
+ std::filesystem::path OidcExePathMaybe(OidcExePathString);
+ if (IsFile(OidcExePathMaybe))
+ {
+ OidcExePath = std::move(OidcExePathMaybe);
+ }
+ else
+ {
+ ZEN_WARN("Path to OidcToken executable '{}' can not be reached by server", OidcExePathString);
+ }
+ }
+ std::string_view KeyParam = Cloud["key"sv].AsString();
+ if (KeyParam.empty())
+ {
+ return {nullptr, "Missing key"};
+ }
+ if (KeyParam.length() != IoHash::StringLength)
+ {
+ return {nullptr, "Invalid key"};
+ }
+ IoHash Key = IoHash::FromHexString(KeyParam);
+ if (Key == IoHash::Zero)
+ {
+ return {nullptr, "Invalid key string"};
+ }
+ IoHash BaseKey = IoHash::Zero;
+ std::string_view BaseKeyParam = Cloud["basekey"sv].AsString();
+ if (!BaseKeyParam.empty())
+ {
+ if (BaseKeyParam.length() != IoHash::StringLength)
+ {
+ return {nullptr, "Invalid base key"};
+ }
+ BaseKey = IoHash::FromHexString(BaseKeyParam);
+ if (BaseKey == IoHash::Zero)
+ {
+ return {nullptr, "Invalid base key string"};
+ }
+ }
+
+ bool ForceDisableBlocks = Cloud["disableblocks"sv].AsBool(false);
+ bool ForceDisableTempBlocks = Cloud["disabletempblocks"sv].AsBool(false);
+ bool AssumeHttp2 = Cloud["assumehttp2"sv].AsBool(false);
+
+ JupiterRemoteStoreOptions Options = {
+ RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunksPerBlock = 1000, .MaxChunkEmbedSize = MaxChunkEmbedSize},
+ Url,
+ std::string(Namespace),
+ std::string(Bucket),
+ Key,
+ BaseKey,
+ std::string(OpenIdProvider),
+ AccessToken,
+ AuthManager,
+ OidcExePath,
+ ForceDisableBlocks,
+ ForceDisableTempBlocks,
+ AssumeHttp2};
+ RemoteStore = CreateJupiterRemoteStore(Options, TempFilePath, /*Quiet*/ false, /*Unattended*/ false, /*Hidden*/ true);
+ }
+
+ if (CbObjectView Zen = Params["zen"sv].AsObjectView(); Zen)
+ {
+ std::string_view Url = Zen["url"sv].AsString();
+ std::string_view Project = Zen["project"sv].AsString();
+ if (Project.empty())
+ {
+ return {nullptr, "Missing project"};
+ }
+ std::string_view Oplog = Zen["oplog"sv].AsString();
+ if (Oplog.empty())
+ {
+ return {nullptr, "Missing oplog"};
+ }
+ ZenRemoteStoreOptions Options = {
+ RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunksPerBlock = 1000, .MaxChunkEmbedSize = MaxChunkEmbedSize},
+ std::string(Url),
+ std::string(Project),
+ std::string(Oplog)};
+ RemoteStore = CreateZenRemoteStore(Options, TempFilePath);
+ }
+
+ if (CbObjectView Builds = Params["builds"sv].AsObjectView(); Builds)
+ {
+ std::string_view BuildsServiceUrl = Builds["url"sv].AsString();
+ if (BuildsServiceUrl.empty())
+ {
+ return {nullptr, "Missing service url"};
+ }
+
+ std::string Url = UrlDecode(BuildsServiceUrl);
+ std::string_view Namespace = Builds["namespace"sv].AsString();
+ if (Namespace.empty())
+ {
+ return {nullptr, "Missing namespace"};
+ }
+ std::string_view Bucket = Builds["bucket"sv].AsString();
+ if (Bucket.empty())
+ {
+ return {nullptr, "Missing bucket"};
+ }
+ std::string_view OpenIdProvider = Builds["openid-provider"sv].AsString();
+ std::string AccessToken = std::string(Builds["access-token"sv].AsString());
+ if (AccessToken.empty())
+ {
+ std::string_view AccessTokenEnvVariable = Builds["access-token-env"].AsString();
+ if (!AccessTokenEnvVariable.empty())
+ {
+ AccessToken = GetEnvVariable(AccessTokenEnvVariable);
+ }
+ }
+ std::filesystem::path OidcExePath;
+ if (std::string_view OidcExePathString = Builds["oidc-exe-path"].AsString(); !OidcExePathString.empty())
+ {
+ std::filesystem::path OidcExePathMaybe(OidcExePathString);
+ if (IsFile(OidcExePathMaybe))
+ {
+ OidcExePath = std::move(OidcExePathMaybe);
+ }
+ else
+ {
+ ZEN_WARN("Path to OidcToken executable '{}' can not be reached by server", OidcExePathString);
+ }
+ }
+ std::string_view BuildIdParam = Builds["buildsid"sv].AsString();
+ if (BuildIdParam.empty())
+ {
+ return {nullptr, "Missing build id"};
+ }
+ if (BuildIdParam.length() != Oid::StringLength)
+ {
+ return {nullptr, "Invalid build id"};
+ }
+ Oid BuildId = Oid::FromHexString(BuildIdParam);
+ if (BuildId == Oid::Zero)
+ {
+ return {nullptr, "Invalid build id string"};
+ }
+
+ bool ForceDisableBlocks = Builds["disableblocks"sv].AsBool(false);
+ bool ForceDisableTempBlocks = Builds["disabletempblocks"sv].AsBool(false);
+ bool AssumeHttp2 = Builds["assumehttp2"sv].AsBool(false);
+
+ MemoryView MetaDataSection = Builds["metadata"sv].AsBinaryView();
+ IoBuffer MetaData(IoBuffer::Wrap, MetaDataSection.GetData(), MetaDataSection.GetSize());
+
+ BuildsRemoteStoreOptions Options = {
+ RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunksPerBlock = 1000, .MaxChunkEmbedSize = MaxChunkEmbedSize},
+ Url,
+ std::string(Namespace),
+ std::string(Bucket),
+ BuildId,
+ std::string(OpenIdProvider),
+ AccessToken,
+ AuthManager,
+ OidcExePath,
+ ForceDisableBlocks,
+ ForceDisableTempBlocks,
+ AssumeHttp2,
+ MetaData};
+ RemoteStore = CreateJupiterBuildsRemoteStore(Options, TempFilePath, /*Quiet*/ false, /*Unattended*/ false, /*Hidden*/ true);
+ }
+
+ if (!RemoteStore)
+ {
+ return {nullptr, "Unknown remote store type"};
+ }
+
+ return {std::move(RemoteStore), ""};
+ }
+
+ std::pair<HttpResponseCode, std::string> ConvertResult(const RemoteProjectStore::Result& Result)
+ {
+ if (Result.ErrorCode == 0)
+ {
+ return {HttpResponseCode::OK, Result.Text};
+ }
+ return {static_cast<HttpResponseCode>(Result.ErrorCode),
+ Result.Reason.empty() ? Result.Text
+ : Result.Text.empty() ? Result.Reason
+ : fmt::format("{}: {}", Result.Reason, Result.Text)};
+ }
+
} // namespace
//////////////////////////////////////////////////////////////////////////
@@ -239,13 +505,17 @@ HttpProjectService::HttpProjectService(CidStore& Store,
ProjectStore* Projects,
HttpStatusService& StatusService,
HttpStatsService& StatsService,
- AuthMgr& AuthMgr)
+ AuthMgr& AuthMgr,
+ OpenProcessCache& InOpenProcessCache,
+ JobQueue& InJobQueue)
: m_Log(logging::Get("project"))
, m_CidStore(Store)
, m_ProjectStore(Projects)
, m_StatusService(StatusService)
, m_StatsService(StatsService)
, m_AuthMgr(AuthMgr)
+, m_OpenProcessCache(InOpenProcessCache)
+, m_JobQueue(InJobQueue)
{
ZEN_MEMSCOPE(GetProjectHttpTag());
@@ -507,7 +777,7 @@ HttpProjectService::HandleChunkBatchRequest(HttpRouterRequest& Req)
}
Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -580,7 +850,7 @@ HttpProjectService::HandleChunkBatchRequest(HttpRouterRequest& Req)
for (uint32_t ChunkIndex = 0; ChunkIndex < RequestHdr.ChunkCount; ++ChunkIndex)
{
const RequestChunkEntry& RequestedChunk = RequestedChunks[ChunkIndex];
- IoBuffer FoundChunk = FoundLog->FindChunk(RequestedChunk.ChunkId, nullptr);
+ IoBuffer FoundChunk = FoundLog->FindChunk(Project->RootDir, RequestedChunk.ChunkId, nullptr);
if (FoundChunk)
{
if (RequestedChunk.Offset > 0 || RequestedChunk.RequestBytes < uint64_t(-1))
@@ -666,38 +936,35 @@ HttpProjectService::HandleFilesRequest(HttpRouterRequest& Req)
}
}
- CbObject ResponsePayload;
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->GetProjectFiles(ProjectId, OplogId, WantedFieldNames, ResponsePayload);
- if (Result.first == HttpResponseCode::OK)
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
{
- if (HttpReq.AcceptContentType() == HttpContentType::kCompressedBinary)
- {
- CompositeBuffer Payload = CompressedBuffer::Compress(ResponsePayload.GetBuffer()).GetCompressed();
- return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kCompressedBinary, Payload);
- }
- else
- {
- return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
- }
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Project files request for unknown project '{}'", ProjectId));
}
- else
+ Project->TouchProject();
+
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
+ if (!FoundLog)
{
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Project files for unknown oplog '{}/{}'", ProjectId, OplogId));
}
- if (Result.second.empty())
+ Project->TouchOplog(OplogId);
+
+ CbObject ResponsePayload = ProjectStore::GetProjectFiles(Log(), *Project, *FoundLog, WantedFieldNames);
+
+ if (HttpReq.AcceptContentType() == HttpContentType::kCompressedBinary)
{
- return HttpReq.WriteResponse(Result.first);
+ CompositeBuffer Payload = CompressedBuffer::Compress(ResponsePayload.GetBuffer()).GetCompressed();
+ return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kCompressedBinary, Payload);
+ }
+ else
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
void
@@ -730,38 +997,34 @@ HttpProjectService::HandleChunkInfosRequest(HttpRouterRequest& Req)
WantedFieldNames.insert("rawsize");
}
- CbObject ResponsePayload;
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->GetProjectChunkInfos(ProjectId, OplogId, WantedFieldNames, ResponsePayload);
- if (Result.first == HttpResponseCode::OK)
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
{
- if (HttpReq.AcceptContentType() == HttpContentType::kCompressedBinary)
- {
- CompositeBuffer Payload = CompressedBuffer::Compress(ResponsePayload.GetBuffer()).GetCompressed();
- return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kCompressedBinary, Payload);
- }
- else
- {
- return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
- }
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk infos request for unknown project '{}'", ProjectId));
}
- else
+ Project->TouchProject();
+
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
+ if (!FoundLog)
{
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk infos for unknown oplog '{}/{}'", ProjectId, OplogId));
+ }
+ Project->TouchOplog(OplogId);
+
+ CbObject ResponsePayload = ProjectStore::GetProjectChunkInfos(Log(), *Project, *FoundLog, WantedFieldNames);
+ if (HttpReq.AcceptContentType() == HttpContentType::kCompressedBinary)
+ {
+ CompositeBuffer Payload = CompressedBuffer::Compress(ResponsePayload.GetBuffer()).GetCompressed();
+ return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kCompressedBinary, Payload);
}
- if (Result.second.empty())
+ else
{
- return HttpReq.WriteResponse(Result.first);
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
void
@@ -775,35 +1038,48 @@ HttpProjectService::HandleChunkInfoRequest(HttpRouterRequest& Req)
const auto& OplogId = Req.GetCapture(2);
const auto& ChunkId = Req.GetCapture(3);
- CbObject ResponsePayload;
- std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->GetChunkInfo(ProjectId, OplogId, ChunkId, ResponsePayload);
- if (Result.first == HttpResponseCode::OK)
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
{
- m_ProjectStats.ChunkHitCount++;
- return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk info request for unknown project '{}'", ProjectId));
}
- else if (Result.first == HttpResponseCode::NotFound)
+ Project->TouchProject();
+
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
+ if (!FoundLog)
{
- m_ProjectStats.ChunkMissCount++;
- ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk info for unknown oplog '{}/{}'", ProjectId, OplogId));
}
- else
+ Project->TouchOplog(OplogId);
+
+ if (ChunkId.size() != 2 * sizeof(Oid::OidBits))
{
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Chunk info request for invalid chunk id '{}/{}'/'{}'", ProjectId, OplogId, ChunkId));
+ }
+
+ const Oid Obj = Oid::FromHexString(ChunkId);
+
+ CbObject ResponsePayload = ProjectStore::GetChunkInfo(Log(), *Project, *FoundLog, Obj);
+ if (ResponsePayload)
+ {
+ m_ProjectStats.ChunkHitCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
}
- if (Result.second.empty())
+ else
{
- return HttpReq.WriteResponse(Result.first);
+ m_ProjectStats.ChunkMissCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk info for unknown chunk '{}/{}/{}'", ProjectId, OplogId, ChunkId));
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
void
@@ -848,40 +1124,65 @@ HttpProjectService::HandleChunkByIdRequest(HttpRouterRequest& Req)
}
}
- HttpContentType AcceptType = HttpReq.AcceptContentType();
-
- CompositeBuffer Chunk;
- HttpContentType ContentType;
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->GetChunkRange(ProjectId, OplogId, ChunkId, Offset, Size, AcceptType, Chunk, ContentType, nullptr);
- if (Result.first == HttpResponseCode::OK)
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
{
- m_ProjectStats.ChunkHitCount++;
- ZEN_DEBUG("chunk - '{}/{}/{}' '{}'", ProjectId, OplogId, ChunkId, ToString(ContentType));
- return HttpReq.WriteResponse(HttpResponseCode::OK, ContentType, Chunk);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk request for unknown project '{}'", ProjectId));
}
- else if (Result.first == HttpResponseCode::NotFound)
+ Project->TouchProject();
+
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
+ if (!FoundLog)
{
- m_ProjectStats.ChunkMissCount++;
- ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk request for unknown oplog '{}/{}'", ProjectId, OplogId));
}
- else
+ Project->TouchOplog(OplogId);
+
+ if (ChunkId.size() != 2 * sizeof(Oid::OidBits))
{
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Chunk request for invalid chunk id '{}/{}/{}'", ProjectId, OplogId, ChunkId));
}
- if (Result.second.empty())
+
+ const Oid Obj = Oid::FromHexString(ChunkId);
+
+ HttpContentType AcceptType = HttpReq.AcceptContentType();
+
+ ProjectStore::GetChunkRangeResult Result =
+ ProjectStore::GetChunkRange(Log(), *Project, *FoundLog, Obj, Offset, Size, AcceptType, /*OptionalInOutModificationTag*/ nullptr);
+
+ switch (Result.Error)
{
- return HttpReq.WriteResponse(Result.first);
+ case ProjectStore::GetChunkRangeResult::EError::Ok:
+ m_ProjectStats.ChunkHitCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' '{}'", ProjectId, OplogId, ChunkId, ToString(Result.ContentType));
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Result.ContentType, Result.Chunk);
+ case ProjectStore::GetChunkRangeResult::EError::NotFound:
+ m_ProjectStats.ChunkMissCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, ChunkId);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound, Result.ContentType, Result.Chunk);
+ case ProjectStore::GetChunkRangeResult::EError::MalformedContent:
+ return HttpReq.WriteResponse(
+ HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Get chunk {}/{}/{} failed. Reason: {}", ProjectId, OplogId, ChunkId, Result.ErrorDescription));
+ case ProjectStore::GetChunkRangeResult::EError::OutOfRange:
+ m_ProjectStats.ChunkMissCount++;
+ ZEN_DEBUG("chunk - '{}/{}/{}' OUT OF RANGE", ProjectId, OplogId, ChunkId);
+ return HttpReq.WriteResponse(
+ HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Get chunk {}/{}/{} failed. Reason: {}", ProjectId, OplogId, ChunkId, Result.ErrorDescription));
+ default:
+ ZEN_ASSERT(false);
+ break;
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
void
@@ -897,13 +1198,40 @@ HttpProjectService::HandleChunkByCidRequest(HttpRouterRequest& Req)
HttpContentType AcceptType = HttpReq.AcceptContentType();
HttpContentType RequestType = HttpReq.RequestContentType();
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk request for unknown project '{}'", ProjectId));
+ }
+ Project->TouchProject();
+
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
+ if (!FoundLog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Chunk request for unknown oplog '{}/{}'", ProjectId, OplogId));
+ }
+ Project->TouchOplog(OplogId);
+
+ if (Cid.length() != IoHash::StringLength)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Chunk request for invalid chunk id '{}/{}/{}'", ProjectId, OplogId, Cid));
+ }
+
+ const IoHash Hash = IoHash::FromHexString(Cid);
+
switch (HttpReq.RequestVerb())
{
case HttpVerb::kGet:
{
- IoBuffer Value;
- std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->GetChunk(ProjectId, OplogId, Cid, Value, nullptr);
- if (Result.first == HttpResponseCode::OK)
+ IoBuffer Value = m_ProjectStore->GetChunk(*Project, *FoundLog, Hash);
+ if (Value)
{
if (AcceptType == ZenContentType::kUnknownContentType || AcceptType == ZenContentType::kBinary ||
AcceptType == ZenContentType::kJSON || AcceptType == ZenContentType::kYAML ||
@@ -962,28 +1290,12 @@ HttpProjectService::HandleChunkByCidRequest(HttpRouterRequest& Req)
m_ProjectStats.ChunkHitCount++;
return HttpReq.WriteResponse(HttpResponseCode::OK, Value.GetContentType(), Value);
}
- else if (Result.first == HttpResponseCode::NotFound)
+ else
{
m_ProjectStats.ChunkMissCount++;
ZEN_DEBUG("chunk - '{}/{}/{}' MISSING", ProjectId, OplogId, Cid);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- else
- {
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
- }
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
case HttpVerb::kPost:
{
@@ -991,30 +1303,23 @@ HttpProjectService::HandleChunkByCidRequest(HttpRouterRequest& Req)
{
return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
}
- std::pair<HttpResponseCode, std::string> Result =
- m_ProjectStore->PutChunk(ProjectId, OplogId, Cid, RequestType, HttpReq.ReadPayload());
- if (Result.first == HttpResponseCode::OK || Result.first == HttpResponseCode::Created)
- {
- m_ProjectStats.ChunkWriteCount++;
- return HttpReq.WriteResponse(Result.first);
- }
- else
+ if (RequestType != HttpContentType::kCompressedBinary)
{
- if (Result.first == HttpResponseCode::BadRequest)
- {
- m_ProjectStats.BadRequestCount++;
- }
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Chunk request for chunk id '{}/{}'/'{}' as unexpected content type: '{}'",
+ ProjectId,
+ OplogId,
+ Cid,
+ ToString(RequestType)));
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
+ IoBuffer Payload = HttpReq.ReadPayload();
+ Payload.SetContentType(RequestType);
+ bool IsNew = m_ProjectStore->PutChunk(*Project, *FoundLog, Hash, std::move(Payload));
+
+ m_ProjectStats.ChunkWriteCount++;
+ return HttpReq.WriteResponse(IsNew ? HttpResponseCode::Created : HttpResponseCode::OK);
}
break;
}
@@ -1039,7 +1344,7 @@ HttpProjectService::HandleOplogOpPrepRequest(HttpRouterRequest& Req)
}
Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -1050,36 +1355,44 @@ HttpProjectService::HandleOplogOpPrepRequest(HttpRouterRequest& Req)
// chunks are not present on this server. This list is then returned in
// the "need" list in the response
- IoBuffer Payload = HttpReq.ReadPayload();
- CbObject RequestObject = LoadCompactBinaryObject(Payload);
+ CbValidateError ValidateResult;
+ if (CbObject RequestObject = ValidateAndReadCompactBinaryObject(HttpReq.ReadPayload(), ValidateResult);
+ ValidateResult == CbValidateError::None)
+ {
+ std::vector<IoHash> NeedList;
- std::vector<IoHash> NeedList;
+ {
+ eastl::fixed_vector<IoHash, 16> ChunkList;
+ CbArrayView HaveList = RequestObject["have"sv].AsArrayView();
+ ChunkList.reserve(HaveList.Num());
+ for (auto& Entry : HaveList)
+ {
+ ChunkList.push_back(Entry.AsHash());
+ }
- {
- eastl::fixed_vector<IoHash, 16> ChunkList;
- CbArrayView HaveList = RequestObject["have"sv].AsArrayView();
- ChunkList.reserve(HaveList.Num());
- for (auto& Entry : HaveList)
+ NeedList = FoundLog->CheckPendingChunkReferences(std::span(begin(ChunkList), end(ChunkList)), std::chrono::minutes(2));
+ }
+
+ CbObjectWriter Cbo(1 + 1 + 5 + NeedList.size() * (1 + sizeof(IoHash::Hash)) + 1);
+ Cbo.BeginArray("need");
{
- ChunkList.push_back(Entry.AsHash());
+ for (const IoHash& Hash : NeedList)
+ {
+ ZEN_DEBUG("prep - NEED: {}", Hash);
+ Cbo << Hash;
+ }
}
+ Cbo.EndArray();
+ CbObject Response = Cbo.Save();
- NeedList = FoundLog->CheckPendingChunkReferences(std::span(begin(ChunkList), end(ChunkList)), std::chrono::minutes(2));
+ return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
}
-
- CbObjectWriter Cbo(1 + 1 + 5 + NeedList.size() * (1 + sizeof(IoHash::Hash)) + 1);
- Cbo.BeginArray("need");
+ else
{
- for (const IoHash& Hash : NeedList)
- {
- ZEN_DEBUG("prep - NEED: {}", Hash);
- Cbo << Hash;
- }
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Invalid compact binary format: '{}'", ToString(ValidateResult)));
}
- Cbo.EndArray();
- CbObject Response = Cbo.Save();
-
- return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
}
void
@@ -1118,7 +1431,7 @@ HttpProjectService::HandleOplogOpNewRequest(HttpRouterRequest& Req)
}
Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -1173,7 +1486,9 @@ HttpProjectService::HandleOplogOpNewRequest(HttpRouterRequest& Req)
if (!legacy::TryLoadCbPackage(Package, Payload, &UniqueBuffer::Alloc, &Resolver))
{
- if (CbObject Core = LoadCompactBinaryObject(Payload))
+ CbValidateError ValidateResult;
+ if (CbObject Core = ValidateAndReadCompactBinaryObject(IoBuffer(Payload), ValidateResult);
+ ValidateResult == CbValidateError::None && Core)
{
Package.SetObject(Core);
}
@@ -1182,7 +1497,7 @@ HttpProjectService::HandleOplogOpNewRequest(HttpRouterRequest& Req)
std::filesystem::path BadPackagePath =
Oplog.TempPath() / "bad_packages"sv / fmt::format("session{}_request{}"sv, HttpReq.SessionId(), HttpReq.RequestId());
- ZEN_WARN("Received malformed package! Saving payload to '{}'", BadPackagePath);
+ ZEN_WARN("Received malformed package ('{}')! Saving payload to '{}'", ToString(ValidateResult), BadPackagePath);
WriteFile(BadPackagePath, Payload);
@@ -1230,10 +1545,9 @@ HttpProjectService::HandleOplogOpNewRequest(HttpRouterRequest& Req)
// Write core to oplog
- size_t AttachmentCount = Package.GetAttachments().size();
- const uint32_t OpLsn = Oplog.AppendNewOplogEntry(Package);
-
- if (OpLsn == ProjectStore::Oplog::kInvalidOp)
+ size_t AttachmentCount = Package.GetAttachments().size();
+ const ProjectStore::LogSequenceNumber OpLsn = Oplog.AppendNewOplogEntry(Package);
+ if (!OpLsn)
{
m_ProjectStats.BadRequestCount++;
return HttpReq.WriteResponse(HttpResponseCode::BadRequest);
@@ -1247,7 +1561,7 @@ HttpProjectService::HandleOplogOpNewRequest(HttpRouterRequest& Req)
}
m_ProjectStats.OpWriteCount++;
- ZEN_DEBUG("'{}/{}' op #{} ({}) - '{}'", ProjectId, OplogId, OpLsn, NiceBytes(Payload.Size()), Core["key"sv].AsString());
+ ZEN_DEBUG("'{}/{}' op #{} ({}) - '{}'", ProjectId, OplogId, OpLsn.Number, NiceBytes(Payload.Size()), Core["key"sv].AsString());
HttpReq.WriteResponse(HttpResponseCode::Created);
}
@@ -1275,7 +1589,7 @@ HttpProjectService::HandleOplogValidateRequest(HttpRouterRequest& Req)
}
Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound,
@@ -1286,8 +1600,8 @@ HttpProjectService::HandleOplogValidateRequest(HttpRouterRequest& Req)
ProjectStore::Oplog& Oplog = *FoundLog;
- std::atomic_bool CancelFlag = false;
- ProjectStore::Oplog::ValidationResult Result = Oplog.Validate(CancelFlag, &GetSmallWorkerPool(EWorkloadType::Burst));
+ std::atomic_bool CancelFlag = false;
+ ProjectStore::Oplog::ValidationResult Result = Oplog.Validate(Project->RootDir, CancelFlag, &GetSmallWorkerPool(EWorkloadType::Burst));
tsl::robin_map<Oid, std::string, Oid::Hasher> KeyNameLookup;
KeyNameLookup.reserve(Result.OpKeys.size());
for (const auto& It : Result.OpKeys)
@@ -1297,8 +1611,8 @@ HttpProjectService::HandleOplogValidateRequest(HttpRouterRequest& Req)
CbObjectWriter Writer;
Writer << "HasMissingData" << !Result.IsEmpty();
Writer << "OpCount" << Result.OpCount;
- Writer << "LSNLow" << Result.LSNLow;
- Writer << "LSNHigh" << Result.LSNHigh;
+ Writer << "LSNLow" << Result.LSNLow.Number;
+ Writer << "LSNHigh" << Result.LSNHigh.Number;
if (!Result.MissingFiles.empty())
{
Writer.BeginArray("MissingFiles");
@@ -1386,7 +1700,7 @@ HttpProjectService::HandleOpLogOpRequest(HttpRouterRequest& Req)
}
Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -1397,7 +1711,7 @@ HttpProjectService::HandleOpLogOpRequest(HttpRouterRequest& Req)
if (const std::optional<int32_t> OpId = ParseInt<uint32_t>(OpIdString))
{
- if (std::optional<CbObject> MaybeOp = Oplog.GetOpByIndex(OpId.value()))
+ if (std::optional<CbObject> MaybeOp = Oplog.GetOpByIndex(ProjectStore::LogSequenceNumber(OpId.value())))
{
CbObject& Op = MaybeOp.value();
if (HttpReq.AcceptContentType() == ZenContentType::kCbPackage)
@@ -1413,15 +1727,18 @@ HttpProjectService::HandleOpLogOpRequest(HttpRouterRequest& Req)
switch (Payload.GetContentType())
{
case ZenContentType::kCbObject:
- if (CbObject Object = LoadCompactBinaryObject(Payload))
{
- Package.AddAttachment(CbAttachment(Object));
- }
- else
- {
- // Error - malformed object
-
- ZEN_WARN("malformed object returned for {}", AttachmentHash);
+ CbValidateError ValidateResult;
+ if (CbObject Object = ValidateAndReadCompactBinaryObject(std::move(Payload), ValidateResult);
+ ValidateResult == CbValidateError::None && Object)
+ {
+ Package.AddAttachment(CbAttachment(Object));
+ }
+ else
+ {
+ // Error - malformed object
+ ZEN_WARN("malformed object returned for {} ('{}')", AttachmentHash, ToString(ValidateResult));
+ }
}
break;
@@ -1483,7 +1800,7 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req)
{
case HttpVerb::kGet:
{
- ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
+ Ref<ProjectStore::Oplog> OplogIt = Project->ReadOplog(OplogId);
if (!OplogIt)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound,
@@ -1491,8 +1808,6 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req)
fmt::format("oplog {} not found in project {}", OplogId, ProjectId));
}
- Project->TouchOplog(OplogId);
-
ProjectStore::Oplog& Log = *OplogIt;
CbObjectWriter Cb;
@@ -1517,7 +1832,7 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req)
OplogMarkerPath = Params["gcpath"sv].AsString();
}
- ProjectStore::Oplog* OplogIt = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
+ Ref<ProjectStore::Oplog> OplogIt = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
if (!OplogIt)
{
if (!Project->NewOplog(OplogId, OplogMarkerPath))
@@ -1554,7 +1869,7 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req)
OplogMarkerPath = Params["gcpath"sv].AsString();
}
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
if (!FoundLog)
{
if (!Project->NewOplog(OplogId, OplogMarkerPath))
@@ -1604,7 +1919,7 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req)
}
std::optional<OplogReferencedSet>
-LoadReferencedSet(ProjectStore::Oplog& Log)
+LoadReferencedSet(ProjectStore::Project& Project, ProjectStore::Oplog& Log)
{
using namespace std::literals;
@@ -1622,7 +1937,7 @@ LoadReferencedSet(ProjectStore::Oplog& Log)
return std::optional<OplogReferencedSet>();
}
- return OplogReferencedSet::LoadFromChunk(Log.FindChunk(ChunkId, nullptr));
+ return OplogReferencedSet::LoadFromChunk(Log.FindChunk(Project.RootDir, ChunkId, nullptr));
}
void
@@ -1644,7 +1959,7 @@ HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req)
}
Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
+ Ref<ProjectStore::Oplog> FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -1701,13 +2016,6 @@ HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req)
}
else
{
- std::optional<OplogReferencedSet> ReferencedSet;
- if (auto TrimString = Params.GetValue("trim_by_referencedset"); TrimString == "true")
- {
- ReferencedSet = LoadReferencedSet(*FoundLog);
- }
- Response.BeginArray("entries"sv);
-
ProjectStore::Oplog::Paging EntryPaging;
if (std::string_view Param = Params.GetValue("start"); !Param.empty())
{
@@ -1724,24 +2032,56 @@ HttpProjectService::HandleOpLogEntriesRequest(HttpRouterRequest& Req)
}
}
+ std::optional<OplogReferencedSet> MaybeReferencedSet;
+ if (auto TrimString = Params.GetValue("trim_by_referencedset"); TrimString == "true")
+ {
+ MaybeReferencedSet = LoadReferencedSet(*Project, *FoundLog);
+ }
+ Response.BeginArray("entries"sv);
+
bool ShouldFilterFields = !FieldNamesFilter.empty();
- FoundLog->IterateOplogWithKey(
- [this, &Response, &FilterObject, ShouldFilterFields, &ReferencedSet](uint32_t /* LSN */, const Oid& Key, CbObjectView Op) {
- if (ReferencedSet && !ReferencedSet->Contains(Key, Op["key"].AsString()))
- {
- return;
- }
- if (ShouldFilterFields)
- {
- Response << FilterObject(Op);
- }
- else
- {
- Response << Op;
- }
- },
- EntryPaging);
+ if (MaybeReferencedSet)
+ {
+ const OplogReferencedSet& ReferencedSet = MaybeReferencedSet.value();
+ FoundLog->IterateOplogWithKey(
+ [this, &Response, &FilterObject, ShouldFilterFields, &ReferencedSet](ProjectStore::LogSequenceNumber /* LSN */,
+ const Oid& Key,
+ CbObjectView Op) {
+ if (!ReferencedSet.Contains(Key))
+ {
+ if (!OplogReferencedSet::IsNonPackage(Op["key"].AsString()))
+ {
+ return;
+ }
+ }
+
+ if (ShouldFilterFields)
+ {
+ Response << FilterObject(Op);
+ }
+ else
+ {
+ Response << Op;
+ }
+ },
+ EntryPaging);
+ }
+ else
+ {
+ FoundLog->IterateOplog(
+ [this, &Response, &FilterObject, ShouldFilterFields](CbObjectView Op) {
+ if (ShouldFilterFields)
+ {
+ Response << FilterObject(Op);
+ }
+ else
+ {
+ Response << Op;
+ }
+ },
+ EntryPaging);
+ }
Response.EndArray();
}
@@ -1776,66 +2116,20 @@ HttpProjectService::HandleProjectRequest(HttpRouterRequest& Req)
return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
}
- IoBuffer Payload = HttpReq.ReadPayload();
- CbObject Params = LoadCompactBinaryObject(Payload);
- std::filesystem::path Root = Params["root"sv].AsU8String(); // Workspace root (i.e `D:/UE5/`)
- std::filesystem::path EngineRoot = Params["engine"sv].AsU8String(); // Engine root (i.e `D:/UE5/Engine`)
- std::filesystem::path ProjectRoot =
- Params["project"sv].AsU8String(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
- std::filesystem::path ProjectFilePath =
- Params["projectfile"sv].AsU8String(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
-
- const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
- m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
-
- ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
- ProjectId,
- Root,
- EngineRoot,
- ProjectRoot,
- ProjectFilePath,
- ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
-
- m_ProjectStats.ProjectWriteCount++;
- HttpReq.WriteResponse(HttpResponseCode::Created);
- }
- break;
-
- case HttpVerb::kPut:
- {
- if (!m_ProjectStore->AreDiskWritesAllowed())
+ CbValidateError ValidateResult;
+ if (CbObject Params = ValidateAndReadCompactBinaryObject(HttpReq.ReadPayload(), ValidateResult);
+ ValidateResult == CbValidateError::None)
{
- return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- }
-
- IoBuffer Payload = HttpReq.ReadPayload();
- CbObject Params = LoadCompactBinaryObject(Payload);
- std::filesystem::path Root = Params["root"sv].AsU8String(); // Workspace root (i.e `D:/UE5/`)
- std::filesystem::path EngineRoot = Params["engine"sv].AsU8String(); // Engine root (i.e `D:/UE5/Engine`)
- std::filesystem::path ProjectRoot =
- Params["project"sv].AsU8String(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
- std::filesystem::path ProjectFilePath =
- Params["projectfile"sv].AsU8String(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
+ std::filesystem::path Root = Params["root"sv].AsU8String(); // Workspace root (i.e `D:/UE5/`)
+ std::filesystem::path EngineRoot = Params["engine"sv].AsU8String(); // Engine root (i.e `D:/UE5/Engine`)
+ std::filesystem::path ProjectRoot =
+ Params["project"sv].AsU8String(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
+ std::filesystem::path ProjectFilePath =
+ Params["projectfile"sv].AsU8String(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
- if (m_ProjectStore->UpdateProject(ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath))
- {
- m_ProjectStats.ProjectWriteCount++;
- ZEN_INFO("updated project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
- ProjectId,
- Root,
- EngineRoot,
- ProjectRoot,
- ProjectFilePath,
- ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
-
- HttpReq.WriteResponse(HttpResponseCode::OK);
- }
- else
- {
const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
- m_ProjectStats.ProjectWriteCount++;
ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
ProjectId,
Root,
@@ -1844,8 +2138,71 @@ HttpProjectService::HandleProjectRequest(HttpRouterRequest& Req)
ProjectFilePath,
ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+ m_ProjectStats.ProjectWriteCount++;
HttpReq.WriteResponse(HttpResponseCode::Created);
}
+ else
+ {
+ HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Malformed compact binary object: '{}'", ToString(ValidateResult)));
+ }
+ }
+ break;
+
+ case HttpVerb::kPut:
+ {
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+ CbValidateError ValidateResult;
+ if (CbObject Params = ValidateAndReadCompactBinaryObject(HttpReq.ReadPayload(), ValidateResult);
+ ValidateResult == CbValidateError::None)
+ {
+ std::filesystem::path Root = Params["root"sv].AsU8String(); // Workspace root (i.e `D:/UE5/`)
+ std::filesystem::path EngineRoot = Params["engine"sv].AsU8String(); // Engine root (i.e `D:/UE5/Engine`)
+ std::filesystem::path ProjectRoot =
+ Params["project"sv].AsU8String(); // Project root directory (i.e `D:/UE5/Samples/Games/Lyra`)
+ std::filesystem::path ProjectFilePath =
+ Params["projectfile"sv].AsU8String(); // Project file path (i.e `D:/UE5/Samples/Games/Lyra/Lyra.uproject`)
+
+ if (m_ProjectStore->UpdateProject(ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath))
+ {
+ m_ProjectStats.ProjectWriteCount++;
+ ZEN_INFO("updated project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
+ ProjectId,
+ Root,
+ EngineRoot,
+ ProjectRoot,
+ ProjectFilePath,
+ ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+
+ HttpReq.WriteResponse(HttpResponseCode::OK);
+ }
+ else
+ {
+ const std::filesystem::path BasePath = m_ProjectStore->BasePath() / ProjectId;
+ m_ProjectStore->NewProject(BasePath, ProjectId, Root, EngineRoot, ProjectRoot, ProjectFilePath);
+
+ m_ProjectStats.ProjectWriteCount++;
+ ZEN_INFO("established project (id: '{}', roots: '{}', '{}', '{}', '{}'{})",
+ ProjectId,
+ Root,
+ EngineRoot,
+ ProjectRoot,
+ ProjectFilePath,
+ ProjectFilePath.empty() ? ", project will not be GCd due to empty project file path" : "");
+
+ HttpReq.WriteResponse(HttpResponseCode::Created);
+ }
+ }
+ else
+ {
+ HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Malformed compact binary object: '{}'", ToString(ValidateResult)));
+ }
}
break;
@@ -1933,29 +2290,113 @@ HttpProjectService::HandleOplogSaveRequest(HttpRouterRequest& Req)
}
IoBuffer Payload = HttpReq.ReadPayload();
- CbObject Response;
- std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->WriteOplog(ProjectId, OplogId, std::move(Payload), Response);
- if (Result.first == HttpResponseCode::OK)
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
{
- return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Write oplog request for unknown project '{}'", ProjectId));
}
- else
+ Project->TouchProject();
+
+ Ref<ProjectStore::Oplog> Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ false);
+ if (!Oplog)
{
- if (Result.first == HttpResponseCode::BadRequest)
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Write oplog request for unknown oplog '{}/{}'", ProjectId, OplogId));
+ }
+ Project->TouchOplog(OplogId);
+
+ CbValidateError ValidateResult;
+ if (CbObject ContainerObject = ValidateAndReadCompactBinaryObject(std::move(Payload), ValidateResult);
+ ValidateResult == CbValidateError::None && ContainerObject)
+ {
+ RwLock AttachmentsLock;
+ tsl::robin_set<IoHash, IoHash::Hasher> Attachments;
+
+ auto HasAttachment = [this](const IoHash& RawHash) { return m_CidStore.ContainsChunk(RawHash); };
+ auto OnNeedBlock = [&AttachmentsLock, &Attachments](const IoHash& BlockHash, const std::vector<IoHash>&& ChunkHashes) {
+ RwLock::ExclusiveLockScope _(AttachmentsLock);
+ if (BlockHash != IoHash::Zero)
+ {
+ Attachments.insert(BlockHash);
+ }
+ else
+ {
+ Attachments.insert(ChunkHashes.begin(), ChunkHashes.end());
+ }
+ };
+ auto OnNeedAttachment = [&AttachmentsLock, &Attachments](const IoHash& RawHash) {
+ RwLock::ExclusiveLockScope _(AttachmentsLock);
+ Attachments.insert(RawHash);
+ };
+
+ auto OnChunkedAttachment = [](const ChunkedInfo&) {};
+
+ auto OnReferencedAttachments = [&Oplog](std::span<IoHash> RawHashes) { Oplog->CaptureAddedAttachments(RawHashes); };
+
+ // Make sure we retain any attachments we download before writing the oplog
+ Oplog->EnableUpdateCapture();
+ auto _ = MakeGuard([&Oplog]() { Oplog->DisableUpdateCapture(); });
+
+ RemoteProjectStore::Result Result = SaveOplogContainer(*Oplog,
+ ContainerObject,
+ OnReferencedAttachments,
+ HasAttachment,
+ OnNeedBlock,
+ OnNeedAttachment,
+ OnChunkedAttachment,
+ nullptr);
+
+ if (Result.ErrorCode == 0)
{
- m_ProjectStats.BadRequestCount++;
+ if (Attachments.empty())
+ {
+ HttpReq.WriteResponse(HttpResponseCode::OK);
+ }
+ else
+ {
+ CbObjectWriter Cbo(1 + 1 + 5 + Attachments.size() * (1 + sizeof(IoHash::Hash)) + 1);
+ Cbo.BeginArray("need");
+ {
+ for (const IoHash& Hash : Attachments)
+ {
+ ZEN_DEBUG("Need attachment {}", Hash);
+ Cbo << Hash;
+ }
+ }
+ Cbo.EndArray(); // "need"
+
+ CbObject ResponsePayload = Cbo.Save();
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ResponsePayload);
+ }
+ }
+ else
+ {
+ ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
+ ToString(HttpReq.RequestVerb()),
+ HttpReq.QueryString(),
+ Result.ErrorCode,
+ Result.Reason);
+
+ if (Result.Reason.empty())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode(Result.ErrorCode));
+ }
+ else
+ {
+ return HttpReq.WriteResponse(HttpResponseCode(Result.ErrorCode), HttpContentType::kText, Result.Reason);
+ }
}
- ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
- ToString(HttpReq.RequestVerb()),
- HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
}
- if (Result.second.empty())
+ else
{
- return HttpReq.WriteResponse(Result.first);
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Invalid payload: '{}'", ToString(ValidateResult)));
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
void
@@ -1966,42 +2407,114 @@ HttpProjectService::HandleOplogLoadRequest(HttpRouterRequest& Req)
HttpServerRequest& HttpReq = Req.ServerRequest();
const auto& ProjectId = Req.GetCapture(1);
const auto& OplogId = Req.GetCapture(2);
+
+ const HttpServerRequest::QueryParams Params = HttpReq.GetQueryParams();
+
if (HttpReq.AcceptContentType() != HttpContentType::kCbObject)
{
m_ProjectStats.BadRequestCount++;
return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid accept content type");
}
- IoBuffer Payload = HttpReq.ReadPayload();
- CbObject Response;
- std::pair<HttpResponseCode, std::string> Result = m_ProjectStore->ReadOplog(ProjectId, OplogId, HttpReq.GetQueryParams(), Response);
- if (Result.first == HttpResponseCode::OK)
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
{
- return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Read oplog request for unknown project '{}'", ProjectId));
}
- else
+ Project->TouchProject();
+
+ Ref<ProjectStore::Oplog> Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
+ if (!Oplog)
{
- if (Result.first == HttpResponseCode::BadRequest)
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Read oplog request for unknown oplog '{}/{}'", ProjectId, OplogId));
+ }
+ Project->TouchOplog(OplogId);
+
+ size_t MaxBlockSize = RemoteStoreOptions::DefaultMaxBlockSize;
+ if (auto Param = Params.GetValue("maxblocksize"); Param.empty() == false)
+ {
+ if (auto Value = ParseInt<size_t>(Param))
{
- m_ProjectStats.BadRequestCount++;
+ MaxBlockSize = Value.value();
+ }
+ }
+ size_t MaxChunkEmbedSize = RemoteStoreOptions::DefaultMaxChunkEmbedSize;
+ if (auto Param = Params.GetValue("maxchunkembedsize"); Param.empty() == false)
+ {
+ if (auto Value = ParseInt<size_t>(Param))
+ {
+ MaxChunkEmbedSize = Value.value();
+ }
+ }
+ size_t MaxChunksPerBlock = RemoteStoreOptions::DefaultMaxChunksPerBlock;
+ if (auto Param = Params.GetValue("maxchunksperblock"); Param.empty() == false)
+ {
+ if (auto Value = ParseInt<size_t>(Param))
+ {
+ MaxChunksPerBlock = Value.value();
+ }
+ }
+
+ size_t ChunkFileSizeLimit = RemoteStoreOptions::DefaultChunkFileSizeLimit;
+ if (auto Param = Params.GetValue("chunkfilesizelimit"); Param.empty() == false)
+ {
+ if (auto Value = ParseInt<size_t>(Param))
+ {
+ ChunkFileSizeLimit = Value.value();
}
+ }
+
+ WorkerThreadPool& WorkerPool = GetLargeWorkerPool(EWorkloadType::Background);
+
+ RemoteProjectStore::LoadContainerResult ContainerResult = BuildContainer(
+ m_CidStore,
+ *Project,
+ *Oplog,
+ WorkerPool,
+ MaxBlockSize,
+ MaxChunkEmbedSize,
+ MaxChunksPerBlock,
+ ChunkFileSizeLimit,
+ /* BuildBlocks */ false,
+ /* IgnoreMissingAttachments */ false,
+ /* AllowChunking*/ false,
+ [](CompressedBuffer&&, ChunkBlockDescription&&) {},
+ [](const IoHash&, TGetAttachmentBufferFunc&&) {},
+ [](std::vector<std::pair<IoHash, FetchChunkFunc>>&&) {},
+ /* EmbedLooseFiles*/ false);
+
+ if (ContainerResult.ErrorCode == 0)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ContainerResult.ContainerObject);
+ }
+ else
+ {
ZEN_DEBUG("Request {}: '{}' failed with {}. Reason: `{}`",
ToString(HttpReq.RequestVerb()),
HttpReq.QueryString(),
- static_cast<int>(Result.first),
- Result.second);
- }
- if (Result.second.empty())
- {
- return HttpReq.WriteResponse(Result.first);
+ ContainerResult.ErrorCode,
+ ContainerResult.Reason);
+
+ if (ContainerResult.Reason.empty())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode(ContainerResult.ErrorCode));
+ }
+ else
+ {
+ return HttpReq.WriteResponse(HttpResponseCode(ContainerResult.ErrorCode), HttpContentType::kText, ContainerResult.Reason);
+ }
}
- return HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
}
void
HttpProjectService::HandleRpcRequest(HttpRouterRequest& Req)
{
ZEN_TRACE_CPU("ProjectService::Rpc");
+ using namespace std::literals;
HttpServerRequest& HttpReq = Req.ServerRequest();
@@ -2009,10 +2522,558 @@ HttpProjectService::HandleRpcRequest(HttpRouterRequest& Req)
const auto& OplogId = Req.GetCapture(2);
IoBuffer Payload = HttpReq.ReadPayload();
- bool OkRequest = m_ProjectStore->Rpc(HttpReq, ProjectId, OplogId, std::move(Payload), m_AuthMgr);
- if (!OkRequest)
+ HttpContentType PayloadContentType = HttpReq.RequestContentType();
+ CbPackage Package;
+ CbObject Cb;
+ switch (PayloadContentType)
{
- m_ProjectStats.BadRequestCount++;
+ case HttpContentType::kJSON:
+ case HttpContentType::kUnknownContentType:
+ case HttpContentType::kText:
+ {
+ std::string JsonText(reinterpret_cast<const char*>(Payload.GetData()), Payload.GetSize());
+ Cb = LoadCompactBinaryFromJson(JsonText).AsObject();
+ if (!Cb)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ "Content format not supported, expected JSON format");
+ }
+ }
+ break;
+ case HttpContentType::kCbObject:
+ {
+ CbValidateError ValidateResult;
+ if (Cb = ValidateAndReadCompactBinaryObject(std::move(Payload), ValidateResult);
+ ValidateResult != CbValidateError::None || !Cb)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(
+ HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Content format not supported, expected compact binary format ('{}')", ToString(ValidateResult)));
+ }
+ break;
+ }
+ case HttpContentType::kCbPackage:
+ try
+ {
+ Package = ParsePackageMessage(Payload);
+ Cb = Package.GetObject();
+ }
+ catch (const std::invalid_argument& ex)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Failed to parse package request, reason: '{}'", ex.what()));
+ }
+ if (!Cb)
+ {
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ "Content format not supported, expected package message format");
+ }
+ break;
+ default:
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type");
+ }
+
+ Ref<ProjectStore::Project> Project = m_ProjectStore->OpenProject(ProjectId);
+ if (!Project)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Rpc oplog request for unknown project '{}'", ProjectId));
+ }
+ Project->TouchProject();
+
+ std::string_view Method = Cb["method"sv].AsString();
+
+ bool VerifyPathOnDisk = Method != "getchunks"sv;
+
+ Ref<ProjectStore::Oplog> Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, VerifyPathOnDisk);
+ if (!Oplog)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::NotFound,
+ HttpContentType::kText,
+ fmt::format("Rpc oplog request for unknown oplog '{}/{}'", ProjectId, OplogId));
+ }
+ Project->TouchOplog(OplogId);
+
+ uint32_t MethodHash = HashStringDjb2(Method);
+
+ switch (MethodHash)
+ {
+ case HashStringDjb2("import"sv):
+ {
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+
+ CbObjectView Params = Cb["params"sv].AsObjectView();
+ size_t MaxBlockSize = Params["maxblocksize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxBlockSize);
+ size_t MaxChunkEmbedSize = Params["maxchunkembedsize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxChunkEmbedSize);
+ bool Force = Params["force"sv].AsBool(false);
+ bool IgnoreMissingAttachments = Params["ignoremissingattachments"sv].AsBool(false);
+ bool CleanOplog = Params["clean"].AsBool(false);
+
+ CreateRemoteStoreResult RemoteStoreResult =
+ CreateRemoteStore(Params, m_AuthMgr, MaxBlockSize, MaxChunkEmbedSize, Oplog->TempPath());
+
+ if (RemoteStoreResult.Store == nullptr)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, RemoteStoreResult.Description);
+ }
+ std::shared_ptr<RemoteProjectStore> RemoteStore = std::move(RemoteStoreResult.Store);
+ RemoteProjectStore::RemoteStoreInfo StoreInfo = RemoteStore->GetInfo();
+
+ JobId JobId = m_JobQueue.QueueJob(
+ fmt::format("Import oplog '{}/{}'", Project->Identifier, Oplog->OplogId()),
+ [this,
+ ChunkStore = &m_CidStore,
+ ActualRemoteStore = std::move(RemoteStore),
+ Oplog,
+ Force,
+ IgnoreMissingAttachments,
+ CleanOplog](JobContext& Context) {
+ Context.ReportMessage(fmt::format("Loading oplog '{}/{}' from {}",
+ Oplog->GetOuterProjectIdentifier(),
+ Oplog->OplogId(),
+ ActualRemoteStore->GetInfo().Description));
+
+ WorkerThreadPool& WorkerPool = GetLargeWorkerPool(EWorkloadType::Background);
+ WorkerThreadPool& NetworkWorkerPool = GetMediumWorkerPool(EWorkloadType::Background);
+
+ RemoteProjectStore::Result Result = LoadOplog(m_CidStore,
+ *ActualRemoteStore,
+ *Oplog,
+ NetworkWorkerPool,
+ WorkerPool,
+ Force,
+ IgnoreMissingAttachments,
+ CleanOplog,
+ &Context);
+ auto Response = ConvertResult(Result);
+ ZEN_INFO("LoadOplog: Status: {} '{}'", ToString(Response.first), Response.second);
+ if (!IsHttpSuccessCode(Response.first))
+ {
+ throw JobError(Response.second.empty() ? fmt::format("Status: {}", ToString(Response.first)) : Response.second,
+ (int)Response.first);
+ }
+ });
+
+ return HttpReq.WriteResponse(HttpResponseCode::Accepted, HttpContentType::kText, fmt::format("{}", JobId.Id));
+ }
+ case HashStringDjb2("export"sv):
+ {
+ CbObjectView Params = Cb["params"sv].AsObjectView();
+ size_t MaxBlockSize = Params["maxblocksize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxBlockSize);
+ size_t MaxChunkEmbedSize = Params["maxchunkembedsize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxChunkEmbedSize);
+ size_t MaxChunksPerBlock = Params["maxchunksperblock"sv].AsUInt64(RemoteStoreOptions::DefaultMaxChunksPerBlock);
+ size_t ChunkFileSizeLimit = Params["chunkfilesizelimit"sv].AsUInt64(RemoteStoreOptions::DefaultChunkFileSizeLimit);
+ bool Force = Params["force"sv].AsBool(false);
+ bool IgnoreMissingAttachments = Params["ignoremissingattachments"sv].AsBool(false);
+ bool EmbedLooseFile = Params["embedloosefiles"sv].AsBool(false);
+
+ CreateRemoteStoreResult RemoteStoreResult =
+ CreateRemoteStore(Params, m_AuthMgr, MaxBlockSize, MaxChunkEmbedSize, Oplog->TempPath());
+
+ if (RemoteStoreResult.Store == nullptr)
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, RemoteStoreResult.Description);
+ }
+ std::shared_ptr<RemoteProjectStore> RemoteStore = std::move(RemoteStoreResult.Store);
+ RemoteProjectStore::RemoteStoreInfo StoreInfo = RemoteStore->GetInfo();
+
+ JobId JobId = m_JobQueue.QueueJob(
+ fmt::format("Export oplog '{}/{}'", Project->Identifier, Oplog->OplogId()),
+ [this,
+ ActualRemoteStore = std::move(RemoteStore),
+ Project,
+ Oplog,
+ MaxBlockSize,
+ MaxChunksPerBlock,
+ MaxChunkEmbedSize,
+ ChunkFileSizeLimit,
+ EmbedLooseFile,
+ Force,
+ IgnoreMissingAttachments](JobContext& Context) {
+ Context.ReportMessage(fmt::format("Saving oplog '{}/{}' to {}, maxblocksize {}, maxchunkembedsize {}",
+ Project->Identifier,
+ Oplog->OplogId(),
+ ActualRemoteStore->GetInfo().Description,
+ NiceBytes(MaxBlockSize),
+ NiceBytes(MaxChunkEmbedSize)));
+
+ WorkerThreadPool& WorkerPool = GetLargeWorkerPool(EWorkloadType::Background);
+ WorkerThreadPool& NetworkWorkerPool = GetMediumWorkerPool(EWorkloadType::Background);
+
+ RemoteProjectStore::Result Result = SaveOplog(m_CidStore,
+ *ActualRemoteStore,
+ *Project,
+ *Oplog,
+ NetworkWorkerPool,
+ WorkerPool,
+ MaxBlockSize,
+ MaxChunksPerBlock,
+ MaxChunkEmbedSize,
+ ChunkFileSizeLimit,
+ EmbedLooseFile,
+ Force,
+ IgnoreMissingAttachments,
+ &Context);
+ auto Response = ConvertResult(Result);
+ ZEN_INFO("SaveOplog: Status: {} '{}'", ToString(Response.first), Response.second);
+ if (!IsHttpSuccessCode(Response.first))
+ {
+ throw JobError(Response.second.empty() ? fmt::format("Status: {}", ToString(Response.first)) : Response.second,
+ (int)Response.first);
+ }
+ });
+
+ return HttpReq.WriteResponse(HttpResponseCode::Accepted, HttpContentType::kText, fmt::format("{}", JobId.Id));
+ }
+ case HashStringDjb2("getchunks"sv):
+ {
+ RpcAcceptOptions AcceptFlags = static_cast<RpcAcceptOptions>(Cb["AcceptFlags"sv].AsUInt16(0u));
+ int32_t TargetProcessId = Cb["Pid"sv].AsInt32(0);
+
+ std::vector<ProjectStore::ChunkRequest> Requests = m_ProjectStore->ParseChunksRequests(*Project, *Oplog, Cb);
+ std::vector<ProjectStore::ChunkResult> Results =
+ Requests.empty() ? std::vector<ProjectStore::ChunkResult>{} : m_ProjectStore->GetChunks(*Project, *Oplog, Requests);
+ CbPackage Response = m_ProjectStore->WriteChunksRequestResponse(*Project, *Oplog, std::move(Requests), std::move(Results));
+
+ void* TargetProcessHandle = nullptr;
+ FormatFlags Flags = FormatFlags::kDefault;
+ if (EnumHasAllFlags(AcceptFlags, RpcAcceptOptions::kAllowLocalReferences))
+ {
+ Flags |= FormatFlags::kAllowLocalReferences;
+ if (!EnumHasAnyFlags(AcceptFlags, RpcAcceptOptions::kAllowPartialLocalReferences))
+ {
+ Flags |= FormatFlags::kDenyPartialLocalReferences;
+ }
+ TargetProcessHandle = m_OpenProcessCache.GetProcessHandle(HttpReq.SessionId(), TargetProcessId);
+ }
+
+ CompositeBuffer RpcResponseBuffer = FormatPackageMessageBuffer(Response, Flags, TargetProcessHandle);
+ return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kCbPackage, RpcResponseBuffer);
+ }
+ case HashStringDjb2("putchunks"sv):
+ {
+ ZEN_TRACE_CPU("Store::Rpc::putchunks");
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+
+ CbObject Object = Package.GetObject();
+ const bool UsingTempFiles = Object["usingtmpfiles"].AsBool(false);
+
+ std::span<const CbAttachment> Attachments = Package.GetAttachments();
+ if (!Attachments.empty())
+ {
+ std::vector<IoBuffer> WriteAttachmentBuffers;
+ std::vector<IoHash> WriteRawHashes;
+
+ WriteAttachmentBuffers.reserve(Attachments.size());
+ WriteRawHashes.reserve(Attachments.size());
+
+ for (const CbAttachment& Attachment : Attachments)
+ {
+ IoHash RawHash = Attachment.GetHash();
+ const CompressedBuffer& Compressed = Attachment.AsCompressedBinary();
+ IoBuffer AttachmentPayload = Compressed.GetCompressed().Flatten().AsIoBuffer();
+ if (UsingTempFiles)
+ {
+ AttachmentPayload.SetDeleteOnClose(true);
+ }
+ WriteAttachmentBuffers.push_back(std::move(AttachmentPayload));
+ WriteRawHashes.push_back(RawHash);
+ }
+
+ Oplog->CaptureAddedAttachments(WriteRawHashes);
+ m_CidStore.AddChunks(WriteAttachmentBuffers,
+ WriteRawHashes,
+ UsingTempFiles ? CidStore::InsertMode::kMayBeMovedInPlace : CidStore::InsertMode::kCopyOnly);
+ }
+ return HttpReq.WriteResponse(HttpResponseCode::OK);
+ }
+ case HashStringDjb2("snapshot"sv):
+ {
+ ZEN_TRACE_CPU("Store::Rpc::snapshot");
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+
+ // Snapshot all referenced files. This brings the content of all
+ // files into the CID store
+
+ uint32_t OpCount = 0;
+ uint64_t InlinedBytes = 0;
+ uint64_t InlinedFiles = 0;
+ uint64_t TotalBytes = 0;
+ uint64_t TotalFiles = 0;
+
+ std::vector<CbObject> NewOps;
+ struct AddedChunk
+ {
+ IoBuffer Buffer;
+ uint64_t RawSize = 0;
+ };
+ tsl::robin_map<IoHash, AddedChunk, IoHash::Hasher> AddedChunks;
+
+ Oplog->IterateOplog(
+ [&](CbObjectView Op) {
+ bool OpRewritten = false;
+ bool AllOk = true;
+
+ CbWriter FilesArrayWriter;
+ FilesArrayWriter.BeginArray("files"sv);
+
+ for (CbFieldView& Field : Op["files"sv])
+ {
+ bool CopyField = true;
+
+ if (CbObjectView View = Field.AsObjectView())
+ {
+ const IoHash DataHash = View["data"sv].AsHash();
+
+ if (DataHash == IoHash::Zero)
+ {
+ std::string_view ServerPath = View["serverpath"sv].AsString();
+ std::filesystem::path FilePath = Project->RootDir / ServerPath;
+ BasicFile DataFile;
+ std::error_code Ec;
+ DataFile.Open(FilePath, BasicFile::Mode::kRead, Ec);
+
+ if (Ec)
+ {
+ // Error...
+
+ ZEN_ERROR("unable to read data from file '{}': {}", FilePath, Ec.message());
+
+ AllOk = false;
+ }
+ else
+ {
+ // Read file contents into memory, compress and keep in map of chunks to add to Cid store
+ IoBuffer FileIoBuffer = DataFile.ReadAll();
+ CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(std::move(FileIoBuffer)));
+ const uint64_t RawSize = Compressed.DecodeRawSize();
+ const IoHash RawHash = Compressed.DecodeRawHash();
+ if (!AddedChunks.contains(RawHash))
+ {
+ const std::filesystem::path TempChunkPath = Oplog->TempPath() / RawHash.ToHexString();
+ BasicFile ChunkTempFile;
+ ChunkTempFile.Open(TempChunkPath, BasicFile::Mode::kTruncateDelete);
+ ChunkTempFile.Write(Compressed.GetCompressed(), 0, Ec);
+ if (Ec)
+ {
+ Oid ChunkId = View["id"sv].AsObjectId();
+ ZEN_ERROR("unable to write external file as compressed chunk '{}', id {}: {}",
+ FilePath,
+ ChunkId,
+ Ec.message());
+ AllOk = false;
+ }
+ else
+ {
+ void* FileHandle = ChunkTempFile.Detach();
+ IoBuffer ChunkBuffer(IoBuffer::File,
+ FileHandle,
+ 0,
+ Compressed.GetCompressed().GetSize(),
+ /*IsWholeFile*/ true);
+ ChunkBuffer.SetDeleteOnClose(true);
+ AddedChunks.insert_or_assign(
+ RawHash,
+ AddedChunk{.Buffer = std::move(ChunkBuffer), .RawSize = RawSize});
+ }
+ }
+
+ TotalBytes += RawSize;
+ ++TotalFiles;
+
+ // Rewrite file array entry with new data reference
+ CbObjectWriter Writer(View.GetSize());
+ RewriteCbObject(Writer, View, [&](CbObjectWriter&, CbFieldView Field) -> bool {
+ if (Field.GetName() == "data"sv)
+ {
+ // omit this field as we will write it explicitly ourselves
+ return true;
+ }
+ return false;
+ });
+ Writer.AddBinaryAttachment("data"sv, RawHash);
+
+ CbObject RewrittenOp = Writer.Save();
+ FilesArrayWriter.AddObject(std::move(RewrittenOp));
+ CopyField = false;
+ }
+ }
+ }
+
+ if (CopyField)
+ {
+ FilesArrayWriter.AddField(Field);
+ }
+ else
+ {
+ OpRewritten = true;
+ }
+ }
+
+ if (OpRewritten && AllOk)
+ {
+ FilesArrayWriter.EndArray();
+ CbArray FilesArray = FilesArrayWriter.Save().AsArray();
+
+ CbObject RewrittenOp = RewriteCbObject(Op, [&](CbObjectWriter& NewWriter, CbFieldView Field) -> bool {
+ if (Field.GetName() == "files"sv)
+ {
+ NewWriter.AddArray("files"sv, FilesArray);
+
+ return true;
+ }
+
+ return false;
+ });
+
+ NewOps.push_back(std::move(RewrittenOp));
+ }
+
+ OpCount++;
+ },
+ ProjectStore::Oplog::Paging{});
+
+ CbObjectWriter ResponseObj;
+
+ // Persist rewritten oplog entries
+ if (!NewOps.empty())
+ {
+ ResponseObj.BeginArray("rewritten_ops");
+
+ for (CbObject& NewOp : NewOps)
+ {
+ ProjectStore::LogSequenceNumber NewLsn = Oplog->AppendNewOplogEntry(std::move(NewOp));
+
+ ZEN_DEBUG("appended rewritten op at LSN: {}", NewLsn.Number);
+
+ ResponseObj.AddInteger(NewLsn.Number);
+ }
+
+ ResponseObj.EndArray();
+ }
+
+ // Ops that have moved chunks to a compressed buffer for storage in m_CidStore have been rewritten with references to the
+ // new chunk(s). Make sure we add the chunks to m_CidStore, and do it after we update the oplog so GC doesn't think we have
+ // unreferenced chunks.
+ for (auto It : AddedChunks)
+ {
+ const IoHash& RawHash = It.first;
+ AddedChunk& Chunk = It.second;
+ CidStore::InsertResult Result = m_CidStore.AddChunk(Chunk.Buffer, RawHash);
+ if (Result.New)
+ {
+ InlinedBytes += Chunk.RawSize;
+ ++InlinedFiles;
+ }
+ }
+
+ ResponseObj << "inlined_bytes" << InlinedBytes << "inlined_files" << InlinedFiles;
+ ResponseObj << "total_bytes" << TotalBytes << "total_files" << TotalFiles;
+
+ ZEN_INFO("oplog '{}/{}': rewrote {} oplog entries (out of {})", ProjectId, OplogId, NewOps.size(), OpCount);
+
+ return HttpReq.WriteResponse(HttpResponseCode::OK, ResponseObj.Save());
+ }
+ case HashStringDjb2("appendops"sv):
+ {
+ ZEN_TRACE_CPU("Store::Rpc::appendops");
+ if (!m_ProjectStore->AreDiskWritesAllowed())
+ {
+ return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
+ }
+
+ CbArrayView OpsArray = Cb["ops"sv].AsArrayView();
+
+ size_t OpsBufferSize = 0;
+ for (CbFieldView OpView : OpsArray)
+ {
+ OpsBufferSize += OpView.GetSize();
+ }
+ UniqueBuffer OpsBuffers = UniqueBuffer::Alloc(OpsBufferSize);
+ MutableMemoryView OpsBuffersMemory = OpsBuffers.GetMutableView();
+
+ std::vector<CbObjectView> Ops;
+ Ops.reserve(OpsArray.Num());
+ for (CbFieldView OpView : OpsArray)
+ {
+ OpView.CopyTo(OpsBuffersMemory);
+ Ops.push_back(CbObjectView(OpsBuffersMemory.GetData()));
+ OpsBuffersMemory.MidInline(OpView.GetSize());
+ }
+
+ std::vector<ProjectStore::LogSequenceNumber> LSNs = Oplog->AppendNewOplogEntries(Ops);
+ ZEN_ASSERT(LSNs.size() == Ops.size());
+
+ std::vector<IoHash> MissingAttachments;
+ for (size_t OpIndex = 0; OpIndex < Ops.size(); OpIndex++)
+ {
+ if (LSNs[OpIndex])
+ {
+ CbObjectView Op = Ops[OpIndex];
+ Op.IterateAttachments([this, &MissingAttachments](CbFieldView AttachmentView) {
+ const IoHash Cid = AttachmentView.AsAttachment();
+ if (!m_CidStore.ContainsChunk(Cid))
+ {
+ MissingAttachments.push_back(Cid);
+ }
+ });
+ }
+ }
+
+ CbPackage ResponsePackage;
+
+ {
+ CbObjectWriter ResponseObj;
+ ResponseObj.BeginArray("written_ops");
+
+ for (ProjectStore::LogSequenceNumber NewLsn : LSNs)
+ {
+ ZEN_DEBUG("appended written op at LSN: {}", NewLsn.Number);
+ ResponseObj.AddInteger(NewLsn.Number);
+ }
+ ResponseObj.EndArray();
+
+ if (!MissingAttachments.empty())
+ {
+ ResponseObj.BeginArray("need");
+
+ for (const IoHash& Cid : MissingAttachments)
+ {
+ ResponseObj.AddHash(Cid);
+ }
+ ResponseObj.EndArray();
+ }
+ ResponsePackage.SetObject(ResponseObj.Save());
+ }
+
+ std::vector<IoBuffer> ResponseBuffers = FormatPackageMessage(ResponsePackage);
+
+ return HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kCbPackage, ResponseBuffers);
+ }
+ default:
+ m_ProjectStats.BadRequestCount++;
+ return HttpReq.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ fmt::format("Unknown rpc method '{}'", Method));
}
}
void
@@ -2038,7 +3099,9 @@ HttpProjectService::HandleDetailsRequest(HttpRouterRequest& Req)
m_ProjectStore->IterateProjects([&](ProjectStore::Project& Project) {
Project.IterateOplogs([&](const RwLock::SharedLockScope&, ProjectStore::Oplog& Oplog) {
Oplog.IterateOplogWithKey(
- [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](uint32_t LSN, const Oid& Key, CbObjectView Op) {
+ [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](ProjectStore::LogSequenceNumber LSN,
+ const Oid& Key,
+ CbObjectView Op) {
CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
});
});
@@ -2092,10 +3155,11 @@ HttpProjectService::HandleProjectDetailsRequest(HttpRouterRequest& Req)
CSVHeader(Details, AttachmentDetails, CSVWriter);
FoundProject->IterateOplogs([&](const RwLock::SharedLockScope&, ProjectStore::Oplog& Oplog) {
- Oplog.IterateOplogWithKey(
- [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](uint32_t LSN, const Oid& Key, CbObjectView Op) {
- CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
- });
+ Oplog.IterateOplogWithKey([this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](ProjectStore::LogSequenceNumber LSN,
+ const Oid& Key,
+ CbObjectView Op) {
+ CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
+ });
});
HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
}
@@ -2135,7 +3199,7 @@ HttpProjectService::HandleOplogDetailsRequest(HttpRouterRequest& Req)
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
+ Ref<ProjectStore::Oplog> FoundLog = FoundProject->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -2148,10 +3212,11 @@ HttpProjectService::HandleOplogDetailsRequest(HttpRouterRequest& Req)
ExtendableStringBuilder<4096> CSVWriter;
CSVHeader(Details, AttachmentDetails, CSVWriter);
- Oplog.IterateOplogWithKey(
- [this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](uint32_t LSN, const Oid& Key, CbObjectView Op) {
- CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
- });
+ Oplog.IterateOplogWithKey([this, &Project, &Oplog, &CSVWriter, Details, AttachmentDetails](ProjectStore::LogSequenceNumber LSN,
+ const Oid& Key,
+ CbObjectView Op) {
+ CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, Key, Op, CSVWriter);
+ });
HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
}
else
@@ -2190,7 +3255,7 @@ HttpProjectService::HandleOplogOpDetailsRequest(HttpRouterRequest& Req)
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- ProjectStore::Oplog* FoundLog = FoundProject->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
+ Ref<ProjectStore::Oplog> FoundLog = FoundProject->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true);
if (!FoundLog)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
@@ -2213,8 +3278,8 @@ HttpProjectService::HandleOplogOpDetailsRequest(HttpRouterRequest& Req)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
- std::optional<uint32_t> LSN = Oplog.GetOpIndexByKey(ObjId);
- if (!LSN.has_value())
+ ProjectStore::LogSequenceNumber LSN = Oplog.GetOpIndexByKey(ObjId);
+ if (!LSN)
{
return HttpReq.WriteResponse(HttpResponseCode::NotFound);
}
@@ -2224,7 +3289,7 @@ HttpProjectService::HandleOplogOpDetailsRequest(HttpRouterRequest& Req)
ExtendableStringBuilder<4096> CSVWriter;
CSVHeader(Details, AttachmentDetails, CSVWriter);
- CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN.value(), ObjId, Op.value(), CSVWriter);
+ CSVWriteOp(m_CidStore, Project.Identifier, Oplog.OplogId(), Details, AttachmentDetails, LSN, ObjId, Op.value(), CSVWriter);
HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, CSVWriter.ToView());
}
else
@@ -2232,7 +3297,7 @@ HttpProjectService::HandleOplogOpDetailsRequest(HttpRouterRequest& Req)
CbObjectWriter Cbo;
Cbo.BeginArray("ops");
{
- CbWriteOp(m_CidStore, Details, OpDetails, AttachmentDetails, LSN.value(), ObjId, Op.value(), Cbo);
+ CbWriteOp(m_CidStore, Details, OpDetails, AttachmentDetails, LSN, ObjId, Op.value(), Cbo);
}
Cbo.EndArray();
HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
diff --git a/src/zenserver/projectstore/httpprojectstore.h b/src/zenserver/projectstore/httpprojectstore.h
index 295defa5c..f0a0bcfa1 100644
--- a/src/zenserver/projectstore/httpprojectstore.h
+++ b/src/zenserver/projectstore/httpprojectstore.h
@@ -11,6 +11,8 @@
namespace zen {
class AuthMgr;
+class JobQueue;
+class OpenProcessCache;
class ProjectStore;
//////////////////////////////////////////////////////////////////////////
@@ -39,7 +41,9 @@ public:
ProjectStore* InProjectStore,
HttpStatusService& StatusService,
HttpStatsService& StatsService,
- AuthMgr& AuthMgr);
+ AuthMgr& AuthMgr,
+ OpenProcessCache& InOpenProcessCache,
+ JobQueue& InJobQueue);
~HttpProjectService();
virtual const char* BaseUri() const override;
@@ -98,6 +102,8 @@ private:
HttpStatusService& m_StatusService;
HttpStatsService& m_StatsService;
AuthMgr& m_AuthMgr;
+ OpenProcessCache& m_OpenProcessCache;
+ JobQueue& m_JobQueue;
ProjectStats m_ProjectStats;
metrics::OperationTiming m_HttpRequests;
};
diff --git a/src/zenserver/upstream/upstreamcache.cpp b/src/zenserver/upstream/upstreamcache.cpp
index 744b861dd..8558e2a10 100644
--- a/src/zenserver/upstream/upstreamcache.cpp
+++ b/src/zenserver/upstream/upstreamcache.cpp
@@ -20,8 +20,8 @@
#include <zenstore/cache/structuredcachestore.h>
#include <zenstore/cidstore.h>
-#include <zenutil/jupiter/jupiterclient.h>
-#include <zenutil/jupiter/jupitersession.h>
+#include <zenremotestore/jupiter/jupiterclient.h>
+#include <zenremotestore/jupiter/jupitersession.h>
#include "cache/httpstructuredcache.h"
#include "diag/logging.h"
diff --git a/src/zenserver/upstream/upstreamcache.h b/src/zenserver/upstream/upstreamcache.h
index 26e5decac..d5d61c8d9 100644
--- a/src/zenserver/upstream/upstreamcache.h
+++ b/src/zenserver/upstream/upstreamcache.h
@@ -8,8 +8,8 @@
#include <zencore/iohash.h>
#include <zencore/stats.h>
#include <zencore/zencore.h>
+#include <zenstore/cache/cache.h>
#include <zenstore/cache/upstreamcacheclient.h>
-#include <zenutil/cache/cache.h>
#include <atomic>
#include <chrono>
diff --git a/src/zenserver/upstream/zen.cpp b/src/zenserver/upstream/zen.cpp
index 7494ae379..25fd3a3bb 100644
--- a/src/zenserver/upstream/zen.cpp
+++ b/src/zenserver/upstream/zen.cpp
@@ -9,44 +9,18 @@
#include <zencore/session.h>
#include <zencore/stream.h>
#include <zenhttp/formatters.h>
+#include <zenhttp/httpclient.h>
#include <zenhttp/httpcommon.h>
#include <zenhttp/packageformat.h>
#include <zenstore/cache/structuredcachestore.h>
#include "diag/logging.h"
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
-ZEN_THIRD_PARTY_INCLUDES_END
-
#include <xxhash.h>
#include <gsl/gsl-lite.hpp>
namespace zen {
-namespace detail {
- struct ZenCacheSessionState
- {
- ZenCacheSessionState(ZenStructuredCacheClient& Client) : OwnerClient(Client) {}
- ~ZenCacheSessionState() {}
-
- void Reset(std::chrono::milliseconds ConnectTimeout, std::chrono::milliseconds Timeout)
- {
- Session.SetBody({});
- Session.SetHeader({});
- Session.SetConnectTimeout(ConnectTimeout);
- Session.SetTimeout(Timeout);
- }
-
- cpr::Session& GetSession() { return Session; }
-
- private:
- ZenStructuredCacheClient& OwnerClient;
- cpr::Session Session;
- };
-
-} // namespace detail
-
//////////////////////////////////////////////////////////////////////////
ZenStructuredCacheClient::ZenStructuredCacheClient(const ZenStructuredCacheClientOptions& Options)
@@ -59,39 +33,6 @@ ZenStructuredCacheClient::ZenStructuredCacheClient(const ZenStructuredCacheClien
ZenStructuredCacheClient::~ZenStructuredCacheClient()
{
- RwLock::ExclusiveLockScope _(m_SessionStateLock);
- for (auto& CacheEntry : m_SessionStateCache)
- {
- delete CacheEntry;
- }
-}
-
-detail::ZenCacheSessionState*
-ZenStructuredCacheClient::AllocSessionState()
-{
- detail::ZenCacheSessionState* State = nullptr;
-
- if (RwLock::ExclusiveLockScope _(m_SessionStateLock); !m_SessionStateCache.empty())
- {
- State = m_SessionStateCache.front();
- m_SessionStateCache.pop_front();
- }
-
- if (State == nullptr)
- {
- State = new detail::ZenCacheSessionState(*this);
- }
-
- State->Reset(m_ConnectTimeout, m_Timeout);
-
- return State;
-}
-
-void
-ZenStructuredCacheClient::FreeSessionState(detail::ZenCacheSessionState* State)
-{
- RwLock::ExclusiveLockScope _(m_SessionStateLock);
- m_SessionStateCache.push_front(State);
}
//////////////////////////////////////////////////////////////////////////
@@ -102,59 +43,54 @@ ZenStructuredCacheSession::ZenStructuredCacheSession(Ref<ZenStructuredCacheClien
: m_Log(OuterClient->Log())
, m_Client(std::move(OuterClient))
{
- m_SessionState = m_Client->AllocSessionState();
}
ZenStructuredCacheSession::~ZenStructuredCacheSession()
{
- m_Client->FreeSessionState(m_SessionState);
}
ZenCacheResult
ZenStructuredCacheSession::CheckHealth()
{
- ExtendableStringBuilder<256> Uri;
- Uri << m_Client->ServiceUrl() << "/health/check";
+ HttpClient Http{m_Client->ServiceUrl()};
- cpr::Session& Session = m_SessionState->GetSession();
- Session.SetOption(cpr::Url{Uri.c_str()});
- cpr::Response Response = Session.Get();
+ HttpClient::Response Response = Http.Get("/health/check"sv);
- if (Response.error)
+ if (auto& Error = Response.Error; Error)
{
- return {.ErrorCode = static_cast<int32_t>(Response.error.code), .Reason = std::move(Response.error.message)};
+ return {.ErrorCode = static_cast<int32_t>(Error->ErrorCode), .Reason = std::move(Error->ErrorMessage)};
}
- return {.Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Response.status_code == 200};
+ return {.Bytes = Response.DownloadedBytes,
+ .ElapsedSeconds = Response.ElapsedSeconds,
+ .Success = Response.StatusCode == HttpResponseCode::OK};
}
ZenCacheResult
ZenStructuredCacheSession::GetCacheRecord(std::string_view Namespace, std::string_view BucketId, const IoHash& Key, ZenContentType Type)
{
+ HttpClient Http{m_Client->ServiceUrl()};
+
ExtendableStringBuilder<256> Uri;
- Uri << m_Client->ServiceUrl() << "/z$/";
+ Uri << "/z$/";
if (Namespace != ZenCacheStore::DefaultNamespace)
{
Uri << Namespace << "/";
}
Uri << BucketId << "/" << Key.ToHexString();
- cpr::Session& Session = m_SessionState->GetSession();
-
- Session.SetOption(cpr::Url{Uri.c_str()});
- Session.SetHeader(cpr::Header{{"Accept", std::string{MapContentTypeToString(Type)}}});
- cpr::Response Response = Session.Get();
+ HttpClient::Response Response = Http.Get(Uri, {{"Accept", std::string{MapContentTypeToString(Type)}}});
ZEN_DEBUG("GET {}", Response);
- if (Response.error)
+ if (auto& Error = Response.Error; Error)
{
- return {.ErrorCode = static_cast<int32_t>(Response.error.code), .Reason = std::move(Response.error.message)};
+ return {.ErrorCode = static_cast<int32_t>(Error->ErrorCode), .Reason = std::move(Error->ErrorMessage)};
}
- const bool Success = Response.status_code == 200;
- const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
+ const bool Success = Response.StatusCode == HttpResponseCode::OK;
+ const IoBuffer Buffer = Success ? Response.ResponsePayload : IoBuffer{};
- return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.DownloadedBytes, .ElapsedSeconds = Response.ElapsedSeconds, .Success = Success};
}
ZenCacheResult
@@ -163,35 +99,28 @@ ZenStructuredCacheSession::GetCacheChunk(std::string_view Namespace,
const IoHash& Key,
const IoHash& ValueContentId)
{
+ HttpClient Http{m_Client->ServiceUrl()};
+
ExtendableStringBuilder<256> Uri;
- Uri << m_Client->ServiceUrl() << "/z$/";
+ Uri << "/z$/";
if (Namespace != ZenCacheStore::DefaultNamespace)
{
Uri << Namespace << "/";
}
Uri << BucketId << "/" << Key.ToHexString() << "/" << ValueContentId.ToHexString();
- cpr::Session& Session = m_SessionState->GetSession();
-
- Session.SetOption(cpr::Url{Uri.c_str()});
- Session.SetHeader(cpr::Header{{"Accept", "application/x-ue-comp"}});
-
- cpr::Response Response = Session.Get();
+ HttpClient::Response Response = Http.Get(Uri, {{"Accept", "application/x-ue-comp"}});
ZEN_DEBUG("GET {}", Response);
- if (Response.error)
+ if (auto& Error = Response.Error; Error)
{
- return {.ErrorCode = static_cast<int32_t>(Response.error.code), .Reason = std::move(Response.error.message)};
+ return {.ErrorCode = static_cast<int32_t>(Error->ErrorCode), .Reason = std::move(Error->ErrorMessage)};
}
- const bool Success = Response.status_code == 200;
- const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
+ const bool Success = Response.StatusCode == HttpResponseCode::OK;
+ const IoBuffer Buffer = Success ? Response.ResponsePayload : IoBuffer{};
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .Reason = Response.reason,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.DownloadedBytes, .ElapsedSeconds = Response.ElapsedSeconds, .Success = Success};
}
ZenCacheResult
@@ -201,33 +130,29 @@ ZenStructuredCacheSession::PutCacheRecord(std::string_view Namespace,
IoBuffer Value,
ZenContentType Type)
{
+ HttpClient Http{m_Client->ServiceUrl()};
+
ExtendableStringBuilder<256> Uri;
- Uri << m_Client->ServiceUrl() << "/z$/";
+ Uri << "/z$/";
if (Namespace != ZenCacheStore::DefaultNamespace)
{
Uri << Namespace << "/";
}
Uri << BucketId << "/" << Key.ToHexString();
- cpr::Session& Session = m_SessionState->GetSession();
-
- Session.SetOption(cpr::Url{Uri.c_str()});
- Session.SetHeader(cpr::Header{{"Content-Type",
- Type == ZenContentType::kCbPackage ? "application/x-ue-cbpkg"
- : Type == ZenContentType::kCbObject ? "application/x-ue-cb"
- : "application/octet-stream"}});
- Session.SetBody(cpr::Body{static_cast<const char*>(Value.Data()), Value.Size()});
+ Value.SetContentType(Type);
- cpr::Response Response = Session.Put();
+ HttpClient::Response Response = Http.Put(Uri, Value);
ZEN_DEBUG("PUT {}", Response);
- if (Response.error)
+ if (auto& Error = Response.Error; Error)
{
- return {.ErrorCode = static_cast<int32_t>(Response.error.code), .Reason = std::move(Response.error.message)};
+ return {.ErrorCode = static_cast<int32_t>(Error->ErrorCode), .Reason = std::move(Error->ErrorMessage)};
}
- const bool Success = Response.status_code == 200 || Response.status_code == 201;
- return {.Bytes = Response.uploaded_bytes, .ElapsedSeconds = Response.elapsed, .Reason = Response.reason, .Success = Success};
+ const bool Success = Response.StatusCode == HttpResponseCode::OK || Response.StatusCode == HttpResponseCode::Created;
+
+ return {.Bytes = Response.DownloadedBytes, .ElapsedSeconds = Response.ElapsedSeconds, .Success = Success};
}
ZenCacheResult
@@ -237,94 +162,89 @@ ZenStructuredCacheSession::PutCacheValue(std::string_view Namespace,
const IoHash& ValueContentId,
IoBuffer Payload)
{
+ HttpClient Http{m_Client->ServiceUrl()};
+
ExtendableStringBuilder<256> Uri;
- Uri << m_Client->ServiceUrl() << "/z$/";
+ Uri << "/z$/";
if (Namespace != ZenCacheStore::DefaultNamespace)
{
Uri << Namespace << "/";
}
Uri << BucketId << "/" << Key.ToHexString() << "/" << ValueContentId.ToHexString();
- cpr::Session& Session = m_SessionState->GetSession();
-
- Session.SetOption(cpr::Url{Uri.c_str()});
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-comp"}});
- Session.SetBody(cpr::Body{static_cast<const char*>(Payload.Data()), Payload.Size()});
+ Payload.SetContentType(HttpContentType::kCompressedBinary);
- cpr::Response Response = Session.Put();
+ HttpClient::Response Response = Http.Put(Uri, Payload);
ZEN_DEBUG("PUT {}", Response);
- if (Response.error)
+ if (auto& Error = Response.Error; Error)
{
- return {.ErrorCode = static_cast<int32_t>(Response.error.code), .Reason = std::move(Response.error.message)};
+ return {.ErrorCode = static_cast<int32_t>(Error->ErrorCode), .Reason = std::move(Error->ErrorMessage)};
}
- const bool Success = Response.status_code == 200 || Response.status_code == 201;
- return {.Bytes = Response.uploaded_bytes, .ElapsedSeconds = Response.elapsed, .Reason = Response.reason, .Success = Success};
+ const bool Success = Response.StatusCode == HttpResponseCode::OK || Response.StatusCode == HttpResponseCode::Created;
+
+ return {.Bytes = Response.DownloadedBytes, .ElapsedSeconds = Response.ElapsedSeconds, .Success = Success};
}
ZenCacheResult
ZenStructuredCacheSession::InvokeRpc(const CbObjectView& Request)
{
+ HttpClient Http{m_Client->ServiceUrl()};
+
ExtendableStringBuilder<256> Uri;
- Uri << m_Client->ServiceUrl() << "/z$/$rpc";
+ Uri << "/z$/$rpc";
- BinaryWriter Body;
- Request.CopyTo(Body);
+ // TODO: this seems redundant, we should be able to send the data more directly, without the BinaryWriter
- cpr::Session& Session = m_SessionState->GetSession();
+ BinaryWriter BodyWriter;
+ Request.CopyTo(BodyWriter);
- Session.SetOption(cpr::Url{Uri.c_str()});
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cb"}, {"Accept", "application/x-ue-cbpkg"}});
- Session.SetBody(cpr::Body{reinterpret_cast<const char*>(Body.GetData()), Body.GetSize()});
+ IoBuffer Body{IoBuffer::Wrap, BodyWriter.GetData(), BodyWriter.GetSize()};
+ Body.SetContentType(HttpContentType::kCbObject);
- cpr::Response Response = Session.Post();
+ HttpClient::Response Response = Http.Post(Uri, Body, {{"Accept", "application/x-ue-cbpkg"}});
ZEN_DEBUG("POST {}", Response);
- if (Response.error)
+ if (auto& Error = Response.Error; Error)
{
- return {.ErrorCode = static_cast<int32_t>(Response.error.code), .Reason = std::move(Response.error.message)};
+ return {.ErrorCode = static_cast<int32_t>(Error->ErrorCode), .Reason = std::move(Error->ErrorMessage)};
}
- const bool Success = Response.status_code == 200;
- const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
+ const bool Success = Response.StatusCode == HttpResponseCode::OK;
+ const IoBuffer Buffer = Success ? Response.ResponsePayload : IoBuffer{};
return {.Response = std::move(Buffer),
- .Bytes = Response.uploaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .Reason = Response.reason,
+ .Bytes = Response.DownloadedBytes,
+ .ElapsedSeconds = Response.ElapsedSeconds,
.Success = Success};
}
ZenCacheResult
ZenStructuredCacheSession::InvokeRpc(const CbPackage& Request)
{
- ExtendableStringBuilder<256> Uri;
- Uri << m_Client->ServiceUrl() << "/z$/$rpc";
+ HttpClient Http{m_Client->ServiceUrl()};
- SharedBuffer Message = FormatPackageMessageBuffer(Request).Flatten();
-
- cpr::Session& Session = m_SessionState->GetSession();
+ ExtendableStringBuilder<256> Uri;
+ Uri << "/z$/$rpc";
- Session.SetOption(cpr::Url{Uri.c_str()});
- Session.SetHeader(cpr::Header{{"Content-Type", "application/x-ue-cbpkg"}, {"Accept", "application/x-ue-cbpkg"}});
- Session.SetBody(cpr::Body{reinterpret_cast<const char*>(Message.GetData()), Message.GetSize()});
+ IoBuffer Message = FormatPackageMessageBuffer(Request).Flatten().AsIoBuffer();
+ Message.SetContentType(HttpContentType::kCbPackage);
- cpr::Response Response = Session.Post();
+ HttpClient::Response Response = Http.Post(Uri, Message, {{"Accept", "application/x-ue-cbpkg"}});
ZEN_DEBUG("POST {}", Response);
- if (Response.error)
+ if (auto& Error = Response.Error; Error)
{
- return {.ErrorCode = static_cast<int32_t>(Response.error.code), .Reason = std::move(Response.error.message)};
+ return {.ErrorCode = static_cast<int32_t>(Error->ErrorCode), .Reason = std::move(Error->ErrorMessage)};
}
- const bool Success = Response.status_code == 200;
- const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
+ const bool Success = Response.StatusCode == HttpResponseCode::OK;
+ const IoBuffer Buffer = Success ? Response.ResponsePayload : IoBuffer{};
return {.Response = std::move(Buffer),
- .Bytes = Response.uploaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .Reason = Response.reason,
+ .Bytes = Response.DownloadedBytes,
+ .ElapsedSeconds = Response.ElapsedSeconds,
.Success = Success};
}
diff --git a/src/zenserver/upstream/zen.h b/src/zenserver/upstream/zen.h
index 78b6bc589..6321b46b1 100644
--- a/src/zenserver/upstream/zen.h
+++ b/src/zenserver/upstream/zen.h
@@ -6,17 +6,10 @@
#include <zencore/iohash.h>
#include <zencore/logging.h>
#include <zencore/memoryview.h>
-#include <zencore/thread.h>
#include <zencore/uid.h>
#include <zencore/zencore.h>
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <tsl/robin_map.h>
-#include <asio.hpp>
-ZEN_THIRD_PARTY_INCLUDES_END
-
#include <chrono>
-#include <list>
struct ZenCacheValue;
@@ -29,10 +22,6 @@ class ZenStructuredCacheClient;
//////////////////////////////////////////////////////////////////////////
-namespace detail {
- struct ZenCacheSessionState;
-}
-
struct ZenCacheResult
{
IoBuffer Response;
@@ -85,7 +74,6 @@ private:
LoggerRef m_Log;
Ref<ZenStructuredCacheClient> m_Client;
- detail::ZenCacheSessionState* m_SessionState;
};
/** Zen Structured Cache client
@@ -109,12 +97,6 @@ private:
std::chrono::milliseconds m_ConnectTimeout;
std::chrono::milliseconds m_Timeout;
- RwLock m_SessionStateLock;
- std::list<detail::ZenCacheSessionState*> m_SessionStateCache;
-
- detail::ZenCacheSessionState* AllocSessionState();
- void FreeSessionState(detail::ZenCacheSessionState*);
-
friend class ZenStructuredCacheSession;
};
diff --git a/src/zenserver/vfs/vfsservice.cpp b/src/zenserver/vfs/vfsservice.cpp
index bf761f8d1..863ec348a 100644
--- a/src/zenserver/vfs/vfsservice.cpp
+++ b/src/zenserver/vfs/vfsservice.cpp
@@ -1,7 +1,8 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "vfsservice.h"
-#include "vfsimpl.h"
+
+#include <zenstore/vfsimpl.h>
#include <zencore/compactbinarybuilder.h>
@@ -61,10 +62,8 @@ GetContentAsCbObject(HttpServerRequest& HttpReq, CbObject& Cb)
// echo {"method": "mount", "params": {"path": "d:\\VFS_ROOT"}} | curl.exe http://localhost:8558/vfs --data-binary @-
// echo {"method": "unmount"} | curl.exe http://localhost:8558/vfs --data-binary @-
-VfsService::VfsService(HttpStatusService& StatusService) : m_StatusService(StatusService)
+VfsService::VfsService(HttpStatusService& StatusService, VfsServiceImpl* ServiceImpl) : m_StatusService(StatusService), m_Impl(ServiceImpl)
{
- m_Impl = new Impl;
-
m_Router.RegisterRoute(
"info",
[&](HttpRouterRequest& Request) {
@@ -142,67 +141,19 @@ VfsService::VfsService(HttpStatusService& StatusService) : m_StatusService(Statu
VfsService::~VfsService()
{
m_StatusService.UnregisterHandler("vfs", *this);
- delete m_Impl;
-}
-
-void
-VfsService::Mount(std::string_view MountPoint)
-{
- m_Impl->Mount(MountPoint);
-}
-
-void
-VfsService::Unmount()
-{
- m_Impl->Unmount();
-}
-
-void
-VfsService::AddService(Ref<ProjectStore>&& Ps)
-{
- m_Impl->AddService(std::move(Ps));
-}
-
-void
-VfsService::AddService(Ref<ZenCacheStore>&& Z$)
-{
- m_Impl->AddService(std::move(Z$));
}
#else
-VfsService::VfsService(HttpStatusService& StatusService) : m_StatusService(StatusService)
+VfsService::VfsService(HttpStatusService& StatusService, VfsServiceImpl* ServiceImpl) : m_StatusService(StatusService)
{
- ZEN_UNUSED(StatusService);
+ ZEN_UNUSED(ServiceImpl);
}
VfsService::~VfsService()
{
}
-void
-VfsService::Mount(std::string_view MountPoint)
-{
- ZEN_UNUSED(MountPoint);
-}
-
-void
-VfsService::Unmount()
-{
-}
-
-void
-VfsService::AddService(Ref<ProjectStore>&& Ps)
-{
- ZEN_UNUSED(Ps);
-}
-
-void
-VfsService::AddService(Ref<ZenCacheStore>&& Z$)
-{
- ZEN_UNUSED(Z$);
-}
-
#endif
const char*
diff --git a/src/zenserver/vfs/vfsservice.h b/src/zenserver/vfs/vfsservice.h
index 0d0168e23..4e06da878 100644
--- a/src/zenserver/vfs/vfsservice.h
+++ b/src/zenserver/vfs/vfsservice.h
@@ -5,7 +5,6 @@
#include <zenbase/refcount.h>
#include <zenhttp/httpserver.h>
#include <zenhttp/httpstatus.h>
-#include <zenvfs/vfs.h>
#include <memory>
@@ -13,6 +12,7 @@ namespace zen {
class ProjectStore;
class ZenCacheStore;
+struct VfsServiceImpl;
/** Virtual File System service
@@ -28,23 +28,16 @@ class ZenCacheStore;
class VfsService : public HttpService, public IHttpStatusProvider
{
public:
- explicit VfsService(HttpStatusService& StatusService);
+ explicit VfsService(HttpStatusService& StatusService, VfsServiceImpl* ServiceImpl);
~VfsService();
- void Mount(std::string_view MountPoint);
- void Unmount();
-
- void AddService(Ref<ProjectStore>&&);
- void AddService(Ref<ZenCacheStore>&&);
-
protected:
virtual const char* BaseUri() const override;
virtual void HandleRequest(HttpServerRequest& HttpServiceRequest) override;
virtual void HandleStatusRequest(HttpServerRequest& Request) override;
private:
- struct Impl;
- Impl* m_Impl = nullptr;
+ VfsServiceImpl* m_Impl = nullptr;
HttpStatusService& m_StatusService;
HttpRequestRouter m_Router;
diff --git a/src/zenserver/xmake.lua b/src/zenserver/xmake.lua
index 470fbd24e..57105045d 100644
--- a/src/zenserver/xmake.lua
+++ b/src/zenserver/xmake.lua
@@ -5,6 +5,7 @@ target("zenserver")
add_deps("zencore",
"zenhttp",
"zennet",
+ "zenremotestore",
"zenstore",
"zenutil",
"zenvfs")
diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp
index 44c25368c..cab234165 100644
--- a/src/zenserver/zenserver.cpp
+++ b/src/zenserver/zenserver.cpp
@@ -22,11 +22,12 @@
#include <zencore/trace.h>
#include <zencore/workthreadpool.h>
#include <zenhttp/httpserver.h>
+#include <zenremotestore/jupiter/jupiterclient.h>
#include <zenstore/buildstore/buildstore.h>
#include <zenstore/cidstore.h>
#include <zenstore/scrubcontext.h>
+#include <zenstore/vfsimpl.h>
#include <zenstore/workspaces.h>
-#include <zenutil/jupiter/jupiterclient.h>
#include <zenutil/workerpools.h>
#include <zenutil/zenserverprocess.h>
@@ -176,7 +177,7 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen
InitializeState(ServerOptions);
- m_JobQueue = MakeJobQueue(8, "backgroundjobs");
+ m_JobQueue = MakeJobQueue(8, "bgjobs");
m_HealthService.SetHealthInfo({.DataRoot = m_DataRoot,
.AbsLogPath = ServerOptions.AbsLogFile,
@@ -234,18 +235,13 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen
}
m_AuthService = std::make_unique<HttpAuthService>(*m_AuthMgr);
- m_Http->RegisterService(*m_AuthService);
-
- m_Http->RegisterService(m_HealthService);
- m_Http->RegisterService(m_StatsService);
m_StatsReporter.Initialize(ServerOptions.StatsConfig);
if (ServerOptions.StatsConfig.Enabled)
{
EnqueueStatsReportingTimer();
}
- m_Http->RegisterService(m_StatusService);
m_StatusService.RegisterHandler("status", *this);
// Initialize storage and services
@@ -260,13 +256,9 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen
ZEN_INFO("instantiating project service");
- m_ProjectStore = new ProjectStore(*m_CidStore,
- m_DataRoot / "projects",
- m_GcManager,
- *m_JobQueue,
- *m_OpenProcessCache,
- ProjectStore::Configuration{});
- m_HttpProjectService.reset(new HttpProjectService{*m_CidStore, m_ProjectStore, m_StatusService, m_StatsService, *m_AuthMgr});
+ m_ProjectStore = new ProjectStore(*m_CidStore, m_DataRoot / "projects", m_GcManager, ProjectStore::Configuration{});
+ m_HttpProjectService.reset(
+ new HttpProjectService{*m_CidStore, m_ProjectStore, m_StatusService, m_StatsService, *m_AuthMgr, *m_OpenProcessCache, *m_JobQueue});
if (ServerOptions.WorksSpacesConfig.Enabled)
{
@@ -301,29 +293,6 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen
ZEN_INFO("NOT instantiating structured cache service");
}
- 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
-
- if (m_HttpProjectService)
- {
- m_Http->RegisterService(*m_HttpProjectService);
- }
-
- if (m_HttpWorkspacesService)
- {
- m_Http->RegisterService(*m_HttpWorkspacesService);
- }
-
- m_FrontendService = std::make_unique<HttpFrontendService>(m_ContentRoot, m_StatusService);
-
- if (m_FrontendService)
- {
- m_Http->RegisterService(*m_FrontendService);
- }
-
if (ServerOptions.ObjectStoreEnabled)
{
ObjectStoreConfig ObjCfg;
@@ -337,21 +306,20 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen
}
m_ObjStoreService = std::make_unique<HttpObjectStoreService>(m_StatusService, std::move(ObjCfg));
- m_Http->RegisterService(*m_ObjStoreService);
}
if (ServerOptions.BuildStoreConfig.Enabled)
{
m_BuildStoreService = std::make_unique<HttpBuildStoreService>(m_StatusService, m_StatsService, *m_BuildStore);
- m_Http->RegisterService(*m_BuildStoreService);
}
#if ZEN_WITH_VFS
- m_VfsService = std::make_unique<VfsService>(m_StatusService);
- m_VfsService->AddService(Ref<ProjectStore>(m_ProjectStore));
- m_VfsService->AddService(Ref<ZenCacheStore>(m_CacheStore));
- m_Http->RegisterService(*m_VfsService);
-#endif
+ m_VfsServiceImpl = std::make_unique<VfsServiceImpl>();
+ m_VfsServiceImpl->AddService(Ref<ProjectStore>(m_ProjectStore));
+ m_VfsServiceImpl->AddService(Ref<ZenCacheStore>(m_CacheStore));
+
+ m_VfsService = std::make_unique<VfsService>(m_StatusService, m_VfsServiceImpl.get());
+#endif // ZEN_WITH_VFS
ZEN_INFO("initializing GC, enabled '{}', interval {}, lightweight interval {}",
ServerOptions.GcConfig.Enabled,
@@ -387,8 +355,65 @@ ZenServer::Initialize(const ZenServerOptions& ServerOptions, ZenServerState::Zen
.HttpLogPath = ServerOptions.DataDir / "logs" / "http.log",
.CacheLogPath = ServerOptions.DataDir / "logs" / "z$.log"},
ServerOptions);
+
+ // Register all services when all initialization for all services are done
+
+ m_Http->RegisterService(*m_AuthService);
+
+ m_Http->RegisterService(m_StatsService);
+ m_Http->RegisterService(m_StatusService);
+ 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
+
+ if (m_StructuredCacheService)
+ {
+ m_Http->RegisterService(*m_StructuredCacheService);
+ }
+
+ if (m_UpstreamService)
+ {
+ m_Http->RegisterService(*m_UpstreamService);
+ }
+
+ if (m_HttpProjectService)
+ {
+ m_Http->RegisterService(*m_HttpProjectService);
+ }
+
+ if (m_HttpWorkspacesService)
+ {
+ m_Http->RegisterService(*m_HttpWorkspacesService);
+ }
+
+ m_FrontendService = std::make_unique<HttpFrontendService>(m_ContentRoot, m_StatusService);
+
+ if (m_FrontendService)
+ {
+ m_Http->RegisterService(*m_FrontendService);
+ }
+
+ if (m_ObjStoreService)
+ {
+ m_Http->RegisterService(*m_ObjStoreService);
+ }
+
+ if (m_BuildStoreService)
+ {
+ m_Http->RegisterService(*m_BuildStoreService);
+ }
+#if ZEN_WITH_VFS
+ m_Http->RegisterService(*m_VfsService);
+#endif // ZEN_WITH_VFS
+
m_Http->RegisterService(*m_AdminService);
+ // Register health service last so if we return "OK" for health it means all services have been properly initialized
+
+ m_Http->RegisterService(m_HealthService);
+
return EffectiveBasePort;
}
@@ -685,9 +710,6 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions)
m_GcManager.GetDiskWriteBlocker(),
*m_OpenProcessCache);
- m_Http->RegisterService(*m_StructuredCacheService);
- m_Http->RegisterService(*m_UpstreamService);
-
m_StatsReporter.AddProvider(m_CacheStore.Get());
m_StatsReporter.AddProvider(m_CidStore.get());
m_StatsReporter.AddProvider(m_BuildCidStore.get());
@@ -857,6 +879,7 @@ ZenServer::Cleanup()
m_AdminService.reset();
m_VfsService.reset();
+ m_VfsServiceImpl.reset();
m_ObjStoreService.reset();
m_FrontendService.reset();
@@ -1105,7 +1128,6 @@ ZenServer::ToString(ServerState Value)
void
zenserver_forcelinktests()
{
- zen::prj_forcelink();
}
#endif
diff --git a/src/zenserver/zenserver.h b/src/zenserver/zenserver.h
index bcb02d336..ba76c5fff 100644
--- a/src/zenserver/zenserver.h
+++ b/src/zenserver/zenserver.h
@@ -24,6 +24,7 @@ ZEN_THIRD_PARTY_INCLUDES_END
#include <zenhttp/httptest.h>
#include <zenstore/cache/structuredcachestore.h>
#include <zenstore/gc.h>
+#include <zenstore/projectstore.h>
#include "admin/admin.h"
#include "buildstore/httpbuildstore.h"
#include "cache/httpstructuredcache.h"
@@ -31,7 +32,6 @@ ZEN_THIRD_PARTY_INCLUDES_END
#include "frontend/frontend.h"
#include "objectstore/objectstore.h"
#include "projectstore/httpprojectstore.h"
-#include "projectstore/projectstore.h"
#include "stats/statsreporter.h"
#include "upstream/upstream.h"
#include "vfs/vfsservice.h"
@@ -134,6 +134,7 @@ private:
HttpTestingService m_TestingService;
#endif
RefPtr<ProjectStore> m_ProjectStore;
+ std::unique_ptr<VfsServiceImpl> m_VfsServiceImpl;
std::unique_ptr<HttpProjectService> m_HttpProjectService;
std::unique_ptr<Workspaces> m_Workspaces;
std::unique_ptr<HttpWorkspacesService> m_HttpWorkspacesService;
diff --git a/src/zenstore-test/xmake.lua b/src/zenstore-test/xmake.lua
index 6e614459c..ca260ee52 100644
--- a/src/zenstore-test/xmake.lua
+++ b/src/zenstore-test/xmake.lua
@@ -5,5 +5,5 @@ target("zenstore-test")
set_group("tests")
add_headerfiles("**.h")
add_files("*.cpp")
- add_deps("zenstore", "zencore")
+ add_deps("zenstore")
add_packages("vcpkg::doctest")
diff --git a/src/zenstore-test/zenstore-test.cpp b/src/zenstore-test/zenstore-test.cpp
index 6ae0b84b0..6df7162fd 100644
--- a/src/zenstore-test/zenstore-test.cpp
+++ b/src/zenstore-test/zenstore-test.cpp
@@ -23,7 +23,15 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
zen::IgnoreChildSignals();
# endif
+# if ZEN_WITH_TRACE
zen::TraceInit("zenstore-test");
+ zen::TraceOptions TraceCommandlineOptions;
+ if (GetTraceOptionsFromCommandline(TraceCommandlineOptions))
+ {
+ TraceConfigure(TraceCommandlineOptions);
+ }
+# endif // ZEN_WITH_TRACE
+
zen::logging::InitializeLogging();
zen::MaximizeOpenFileCount();
diff --git a/src/zenstore/blockstore.cpp b/src/zenstore/blockstore.cpp
index 7b56c64bd..0861baaf8 100644
--- a/src/zenstore/blockstore.cpp
+++ b/src/zenstore/blockstore.cpp
@@ -113,17 +113,25 @@ BlockStoreFile::Create(uint64_t InitialSize)
uint64_t
BlockStoreFile::FileSize() const
{
- if (m_CachedFileSize == 0)
+ uint64_t CachedSize = m_CachedFileSize;
+ if (CachedSize == 0)
{
std::error_code Ec;
uint64_t Size = m_File.FileSize(Ec);
if (Ec)
{
+ ZEN_WARN("Failed to get file size of block {}. Reason: {}", m_Path, Ec.message());
return 0;
}
+ uint64_t Expected = 0;
+ if (!m_CachedFileSize.compare_exchange_strong(Expected, Size))
+ {
+ // Force a new check next time file size is fetched
+ m_CachedFileSize.store(0);
+ }
return Size;
}
- return m_CachedFileSize;
+ return CachedSize;
}
void
@@ -153,13 +161,9 @@ void
BlockStoreFile::Write(const void* Data, uint64_t Size, uint64_t FileOffset)
{
ZEN_TRACE_CPU("BlockStoreFile::Write");
-#if ZEN_BUILD_DEBUG
- if (uint64_t CachedFileSize = m_CachedFileSize.load(); CachedFileSize > 0)
- {
- ZEN_ASSERT(FileOffset + Size <= CachedFileSize);
- }
-#endif // ZEN_BUILD_DEBUG
+ ZEN_ASSERT(Size + FileOffset <= m_IoBuffer.GetSize());
m_File.Write(Data, Size, FileOffset);
+ m_CachedFileSize.store(0);
}
void
@@ -169,10 +173,11 @@ BlockStoreFile::Flush(uint64_t FinalSize)
m_File.Flush();
if (FinalSize != (uint64_t)-1)
{
+ ZEN_ASSERT(FinalSize <= m_IoBuffer.GetSize());
uint64_t ExpectedSize = 0;
- if (!m_CachedFileSize.compare_exchange_weak(ExpectedSize, FinalSize))
+ while (!m_CachedFileSize.compare_exchange_weak(ExpectedSize, FinalSize))
{
- ZEN_ASSERT(m_CachedFileSize.load() == FinalSize);
+ ZEN_ASSERT(ExpectedSize <= FinalSize);
}
}
}
@@ -724,6 +729,14 @@ BlockStore::HasChunk(const BlockStoreLocation& Location) const
{
return true;
}
+ else
+ {
+ ZEN_WARN("BlockLocation: Block {}, Offset {}, Size {} is outside block size {}",
+ Location.BlockIndex,
+ Location.Offset,
+ Location.Size,
+ BlockSize);
+ }
}
}
return false;
@@ -1152,7 +1165,7 @@ BlockStore::CompactBlocks(const BlockStoreCompactState& CompactState,
ChunkView = MemoryView(ChunkBuffer.data(), ChunkLocation.Size);
}
- if ((WriteOffset + ChunkView.GetSize()) > m_MaxBlockSize)
+ if ((RoundUp(WriteOffset, PayloadAlignment) + ChunkView.GetSize()) > m_MaxBlockSize)
{
if (TargetFileBuffer)
{
@@ -1675,7 +1688,7 @@ TEST_CASE("blockstore.iterate.chunks")
.Size = DefaultIterateSmallChunkWindowSize * 2};
BlockStoreLocation BadBlockIndex = {.BlockIndex = 0xfffff, .Offset = 1024, .Size = 1024};
- WorkerThreadPool WorkerPool(4);
+ WorkerThreadPool WorkerPool(Max(std::thread::hardware_concurrency() - 1u, 4u));
std::vector<BlockStoreLocation> Locations{FirstChunkLocation,
SecondChunkLocation,
@@ -1686,80 +1699,82 @@ TEST_CASE("blockstore.iterate.chunks")
Latch WorkLatch(1);
Store.IterateChunks(Locations, [&](uint32_t, std::span<const size_t> ChunkIndexes) -> bool {
WorkLatch.AddCount(1);
- WorkerPool.ScheduleWork([&, ChunkIndexes = std::vector<size_t>(ChunkIndexes.begin(), ChunkIndexes.end())]() {
- auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
- bool Continue = Store.IterateBlock(
- Locations,
- ChunkIndexes,
- [&](size_t ChunkIndex, const void* Data, uint64_t Size) -> bool {
- switch (ChunkIndex)
- {
- case 0:
- CHECK(Data);
- CHECK(Size == FirstChunkData.size());
- CHECK(std::string((const char*)Data, Size) == FirstChunkData);
- break;
- case 1:
- CHECK(Data);
- CHECK(Size == SecondChunkData.size());
- CHECK(std::string((const char*)Data, Size) == SecondChunkData);
- break;
- case 2:
- CHECK(false);
- break;
- case 3:
- CHECK(!Data);
- break;
- case 4:
- CHECK(!Data);
- break;
- case 5:
- CHECK(!Data);
- break;
- default:
- CHECK(false);
- break;
- }
- return true;
- },
- [&](size_t ChunkIndex, BlockStoreFile& File, uint64_t Offset, uint64_t Size) -> bool {
- switch (ChunkIndex)
- {
- case 0:
- case 1:
- CHECK(false);
- break;
- case 2:
- {
- CHECK(Size == VeryLargeChunk.size());
- char* Buffer = new char[Size];
- size_t HashOffset = 0;
- File.StreamByteRange(Offset, Size, [&](const void* Data, uint64_t Size) {
- memcpy(&Buffer[HashOffset], Data, Size);
- HashOffset += Size;
- });
- CHECK(memcmp(Buffer, VeryLargeChunk.data(), Size) == 0);
- delete[] Buffer;
- }
- break;
- case 3:
- CHECK(false);
- break;
- case 4:
- CHECK(false);
- break;
- case 5:
- CHECK(false);
- break;
- default:
- CHECK(false);
- break;
- }
- return true;
- },
- 0);
- CHECK(Continue);
- });
+ WorkerPool.ScheduleWork(
+ [&, ChunkIndexes = std::vector<size_t>(ChunkIndexes.begin(), ChunkIndexes.end())]() {
+ auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
+ bool Continue = Store.IterateBlock(
+ Locations,
+ ChunkIndexes,
+ [&](size_t ChunkIndex, const void* Data, uint64_t Size) -> bool {
+ switch (ChunkIndex)
+ {
+ case 0:
+ CHECK(Data);
+ CHECK(Size == FirstChunkData.size());
+ CHECK(std::string((const char*)Data, Size) == FirstChunkData);
+ break;
+ case 1:
+ CHECK(Data);
+ CHECK(Size == SecondChunkData.size());
+ CHECK(std::string((const char*)Data, Size) == SecondChunkData);
+ break;
+ case 2:
+ CHECK(false);
+ break;
+ case 3:
+ CHECK(!Data);
+ break;
+ case 4:
+ CHECK(!Data);
+ break;
+ case 5:
+ CHECK(!Data);
+ break;
+ default:
+ CHECK(false);
+ break;
+ }
+ return true;
+ },
+ [&](size_t ChunkIndex, BlockStoreFile& File, uint64_t Offset, uint64_t Size) -> bool {
+ switch (ChunkIndex)
+ {
+ case 0:
+ case 1:
+ CHECK(false);
+ break;
+ case 2:
+ {
+ CHECK(Size == VeryLargeChunk.size());
+ char* Buffer = new char[Size];
+ size_t HashOffset = 0;
+ File.StreamByteRange(Offset, Size, [&](const void* Data, uint64_t Size) {
+ memcpy(&Buffer[HashOffset], Data, Size);
+ HashOffset += Size;
+ });
+ CHECK(memcmp(Buffer, VeryLargeChunk.data(), Size) == 0);
+ delete[] Buffer;
+ }
+ break;
+ case 3:
+ CHECK(false);
+ break;
+ case 4:
+ CHECK(false);
+ break;
+ case 5:
+ CHECK(false);
+ break;
+ default:
+ CHECK(false);
+ break;
+ }
+ return true;
+ },
+ 0);
+ CHECK(Continue);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
return true;
});
WorkLatch.CountDown();
@@ -1776,7 +1791,7 @@ TEST_CASE("blockstore.thread.read.write")
BlockStore Store;
Store.Initialize(RootDirectory / "store", 1088, 1024);
- constexpr size_t ChunkCount = 1000;
+ constexpr size_t ChunkCount = 500;
constexpr size_t Alignment = 8;
std::vector<IoBuffer> Chunks;
std::vector<IoHash> ChunkHashes;
@@ -1792,60 +1807,84 @@ TEST_CASE("blockstore.thread.read.write")
std::vector<BlockStoreLocation> ChunkLocations;
ChunkLocations.resize(ChunkCount);
- WorkerThreadPool WorkerPool(8);
- std::atomic<size_t> WorkCompleted = 0;
- for (size_t ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex)
+ WorkerThreadPool WorkerPool(Max(std::thread::hardware_concurrency() - 1u, 8u));
{
- WorkerPool.ScheduleWork([&Store, ChunkIndex, &Chunks, &ChunkLocations, &WorkCompleted]() {
- IoBuffer& Chunk = Chunks[ChunkIndex];
- Store.WriteChunk(Chunk.Data(), Chunk.Size(), Alignment, [&](const BlockStoreLocation& L) { ChunkLocations[ChunkIndex] = L; });
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < Chunks.size())
- {
- Sleep(1);
+ std::atomic<size_t> WorkCompleted = 0;
+ Latch L(1);
+ for (size_t ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex)
+ {
+ L.AddCount(1);
+ WorkerPool.ScheduleWork(
+ [&Store, ChunkIndex, &Chunks, &ChunkLocations, &WorkCompleted, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ IoBuffer& Chunk = Chunks[ChunkIndex];
+ Store.WriteChunk(Chunk.Data(), Chunk.Size(), Alignment, [&](const BlockStoreLocation& L) {
+ ChunkLocations[ChunkIndex] = L;
+ });
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
+ }
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == Chunks.size());
}
- WorkCompleted = 0;
- for (size_t ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex)
{
- WorkerPool.ScheduleWork([&Store, ChunkIndex, &ChunkLocations, &ChunkHashes, &WorkCompleted]() {
- IoBuffer VerifyChunk = Store.TryGetChunk(ChunkLocations[ChunkIndex]);
- CHECK(VerifyChunk);
- IoHash VerifyHash = IoHash::HashBuffer(VerifyChunk.Data(), VerifyChunk.Size());
- CHECK(VerifyHash == ChunkHashes[ChunkIndex]);
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < Chunks.size())
- {
- Sleep(1);
+ std::atomic<size_t> WorkCompleted = 0;
+ Latch L(1);
+ for (size_t ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex)
+ {
+ L.AddCount(1);
+ WorkerPool.ScheduleWork(
+ [&Store, ChunkIndex, &ChunkLocations, &ChunkHashes, &WorkCompleted, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ IoBuffer VerifyChunk = Store.TryGetChunk(ChunkLocations[ChunkIndex]);
+ CHECK(VerifyChunk);
+ IoHash VerifyHash = IoHash::HashBuffer(VerifyChunk.Data(), VerifyChunk.Size());
+ CHECK(VerifyHash == ChunkHashes[ChunkIndex]);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
+ }
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == Chunks.size());
}
std::vector<BlockStoreLocation> SecondChunkLocations;
SecondChunkLocations.resize(ChunkCount);
- WorkCompleted = 0;
- for (size_t ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex)
{
- WorkerPool.ScheduleWork([&Store, ChunkIndex, &Chunks, &SecondChunkLocations, &WorkCompleted]() {
- IoBuffer& Chunk = Chunks[ChunkIndex];
- Store.WriteChunk(Chunk.Data(), Chunk.Size(), Alignment, [&](const BlockStoreLocation& L) {
- SecondChunkLocations[ChunkIndex] = L;
- });
- WorkCompleted.fetch_add(1);
- });
- WorkerPool.ScheduleWork([&Store, ChunkIndex, &ChunkLocations, &ChunkHashes, &WorkCompleted]() {
- IoBuffer VerifyChunk = Store.TryGetChunk(ChunkLocations[ChunkIndex]);
- CHECK(VerifyChunk);
- IoHash VerifyHash = IoHash::HashBuffer(VerifyChunk.Data(), VerifyChunk.Size());
- CHECK(VerifyHash == ChunkHashes[ChunkIndex]);
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < Chunks.size() * 2)
- {
- Sleep(1);
+ std::atomic<size_t> WorkCompleted = 0;
+ Latch L(1);
+ for (size_t ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex)
+ {
+ L.AddCount(1);
+ WorkerPool.ScheduleWork(
+ [&Store, ChunkIndex, &Chunks, &SecondChunkLocations, &WorkCompleted, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ IoBuffer& Chunk = Chunks[ChunkIndex];
+ Store.WriteChunk(Chunk.Data(), Chunk.Size(), Alignment, [&](const BlockStoreLocation& L) {
+ SecondChunkLocations[ChunkIndex] = L;
+ });
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
+ L.AddCount(1);
+ WorkerPool.ScheduleWork(
+ [&Store, ChunkIndex, &ChunkLocations, &ChunkHashes, &WorkCompleted, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ IoBuffer VerifyChunk = Store.TryGetChunk(ChunkLocations[ChunkIndex]);
+ CHECK(VerifyChunk);
+ IoHash VerifyHash = IoHash::HashBuffer(VerifyChunk.Data(), VerifyChunk.Size());
+ CHECK(VerifyHash == ChunkHashes[ChunkIndex]);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
+ }
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == Chunks.size() * 2);
}
}
diff --git a/src/zenstore/buildstore/buildstore.cpp b/src/zenstore/buildstore/buildstore.cpp
index 1b2cf036b..5c6aa099c 100644
--- a/src/zenstore/buildstore/buildstore.cpp
+++ b/src/zenstore/buildstore/buildstore.cpp
@@ -3,14 +3,15 @@
#include <zenstore/buildstore/buildstore.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryutil.h>
#include <zencore/compress.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/memory/llm.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/trace.h>
#include <zencore/workthreadpool.h>
-#include <zenutil/parallelwork.h>
#include <zencore/uid.h>
#include <zencore/xxhash.h>
@@ -138,24 +139,33 @@ BuildStore::BuildStore(const BuildStoreConfig& Config, GcManager& Gc, CidStore&
{
RwLock::ExclusiveLockScope Lock(m_Lock);
- CbObject ManifestReader = LoadCompactBinaryObject(ReadFile(ManifestPath).Flatten());
- Oid ManifestId = ManifestReader["id"].AsObjectId();
- uint32_t Version = ManifestReader["version"].AsUInt32();
- DateTime CreationDate = ManifestReader["createdAt"].AsDateTime();
- ZEN_UNUSED(CreationDate);
- if (ManifestId == Oid::Zero || Version != blobstore::impl::ManifestVersion)
+ CbValidateError ValidateResult = CbValidateError::None;
+ if (CbObject ManifestReader = ValidateAndReadCompactBinaryObject(ReadFile(ManifestPath).Flatten(), ValidateResult);
+ ValidateResult == CbValidateError::None && ManifestReader)
{
- ZEN_WARN("Invalid manifest at {}, wiping state", ManifestPath);
- IsNew = true;
+ Oid ManifestId = ManifestReader["id"].AsObjectId();
+ uint32_t Version = ManifestReader["version"].AsUInt32();
+ DateTime CreationDate = ManifestReader["createdAt"].AsDateTime();
+ ZEN_UNUSED(CreationDate);
+ if (ManifestId == Oid::Zero || Version != blobstore::impl::ManifestVersion)
+ {
+ ZEN_WARN("Invalid manifest at {}, wiping state", ManifestPath);
+ IsNew = true;
+ }
+ else
+ {
+ m_BlobLogFlushPosition = ReadPayloadLog(Lock, BlobLogPath, 0);
+ m_MetaLogFlushPosition = ReadMetadataLog(Lock, MetaLogPath, 0);
+ if (IsFile(AccessTimesPath))
+ {
+ ReadAccessTimes(Lock, AccessTimesPath);
+ }
+ }
}
else
{
- m_BlobLogFlushPosition = ReadPayloadLog(Lock, BlobLogPath, 0);
- m_MetaLogFlushPosition = ReadMetadataLog(Lock, MetaLogPath, 0);
- if (IsFile(AccessTimesPath))
- {
- ReadAccessTimes(Lock, AccessTimesPath);
- }
+ ZEN_WARN("Invalid manifest at {} ('{}'), wiping state", ManifestPath, ToString(ValidateResult));
+ IsNew = true;
}
}
@@ -366,7 +376,7 @@ BuildStore::PutMetadatas(std::span<const IoHash> BlobHashes, std::span<const IoB
{
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
for (size_t Index = 0; Index < Metadatas.size(); Index++)
{
Work.ScheduleWork(
diff --git a/src/zenstore/cache/cache.cpp b/src/zenstore/cache/cache.cpp
new file mode 100644
index 000000000..0436bfd7b
--- /dev/null
+++ b/src/zenstore/cache/cache.cpp
@@ -0,0 +1,235 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenstore/cache/cache.h>
+
+#include <zencore/logging.h>
+
+namespace zen {
+
+namespace {
+ constinit AsciiSet ValidNamespaceNameCharactersSet{"abcdefghijklmnopqrstuvwxyz0123456789-_.ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
+ constinit AsciiSet ValidBucketNameCharactersSet{"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
+} // namespace
+
+std::optional<std::string>
+GetValidNamespaceName(std::string_view Name)
+{
+ if (Name.empty())
+ {
+ ZEN_WARN("Namespace is invalid, empty namespace is not allowed");
+ return {};
+ }
+
+ if (Name.length() > 64)
+ {
+ ZEN_WARN("Namespace '{}' is invalid, length exceeds 64 characters", Name);
+ return {};
+ }
+
+ if (!AsciiSet::HasOnly(Name, ValidNamespaceNameCharactersSet))
+ {
+ ZEN_WARN("Namespace '{}' is invalid, invalid characters detected", Name);
+ return {};
+ }
+
+ return ToLower(Name);
+}
+
+std::optional<std::string>
+GetValidBucketName(std::string_view Name)
+{
+ if (Name.empty())
+ {
+ ZEN_WARN("Bucket name is invalid, empty bucket name is not allowed");
+ return {};
+ }
+
+ if (!AsciiSet::HasOnly(Name, ValidBucketNameCharactersSet))
+ {
+ ZEN_WARN("Bucket name '{}' is invalid, invalid characters detected", Name);
+ return {};
+ }
+
+ return ToLower(Name);
+}
+
+std::optional<IoHash>
+GetValidIoHash(std::string_view Hash)
+{
+ if (Hash.length() != IoHash::StringLength)
+ {
+ return {};
+ }
+
+ IoHash KeyHash;
+ if (!ParseHexBytes(Hash.data(), Hash.size(), KeyHash.Hash))
+ {
+ return {};
+ }
+ return KeyHash;
+}
+
+bool
+HttpCacheRequestParseRelativeUri(std::string_view Key, std::string_view DefaultNamespace, HttpCacheRequestData& Data)
+{
+ std::vector<std::string_view> Tokens;
+ uint32_t TokenCount = ForEachStrTok(Key, '/', [&](const std::string_view& Token) {
+ Tokens.push_back(Token);
+ return true;
+ });
+
+ switch (TokenCount)
+ {
+ case 0:
+ return true;
+ case 1:
+ Data.Namespace = GetValidNamespaceName(Tokens[0]);
+ return Data.Namespace.has_value();
+ case 2:
+ {
+ std::optional<IoHash> PossibleHashKey = GetValidIoHash(Tokens[1]);
+ if (PossibleHashKey.has_value())
+ {
+ // Legacy bucket/key request
+ Data.Bucket = GetValidBucketName(Tokens[0]);
+ if (!Data.Bucket.has_value())
+ {
+ return false;
+ }
+ Data.HashKey = PossibleHashKey;
+ Data.Namespace = DefaultNamespace;
+ return true;
+ }
+ Data.Namespace = GetValidNamespaceName(Tokens[0]);
+ if (!Data.Namespace.has_value())
+ {
+ return false;
+ }
+ Data.Bucket = GetValidBucketName(Tokens[1]);
+ if (!Data.Bucket.has_value())
+ {
+ return false;
+ }
+ return true;
+ }
+ case 3:
+ {
+ std::optional<IoHash> PossibleHashKey = GetValidIoHash(Tokens[1]);
+ if (PossibleHashKey.has_value())
+ {
+ // Legacy bucket/key/valueid request
+ Data.Bucket = GetValidBucketName(Tokens[0]);
+ if (!Data.Bucket.has_value())
+ {
+ return false;
+ }
+ Data.HashKey = PossibleHashKey;
+ Data.ValueContentId = GetValidIoHash(Tokens[2]);
+ if (!Data.ValueContentId.has_value())
+ {
+ return false;
+ }
+ Data.Namespace = DefaultNamespace;
+ return true;
+ }
+ Data.Namespace = GetValidNamespaceName(Tokens[0]);
+ if (!Data.Namespace.has_value())
+ {
+ return false;
+ }
+ Data.Bucket = GetValidBucketName(Tokens[1]);
+ if (!Data.Bucket.has_value())
+ {
+ return false;
+ }
+ Data.HashKey = GetValidIoHash(Tokens[2]);
+ if (!Data.HashKey)
+ {
+ return false;
+ }
+ return true;
+ }
+ case 4:
+ {
+ Data.Namespace = GetValidNamespaceName(Tokens[0]);
+ if (!Data.Namespace.has_value())
+ {
+ return false;
+ }
+
+ Data.Bucket = GetValidBucketName(Tokens[1]);
+ if (!Data.Bucket.has_value())
+ {
+ return false;
+ }
+
+ Data.HashKey = GetValidIoHash(Tokens[2]);
+ if (!Data.HashKey.has_value())
+ {
+ return false;
+ }
+
+ Data.ValueContentId = GetValidIoHash(Tokens[3]);
+ if (!Data.ValueContentId.has_value())
+ {
+ return false;
+ }
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+std::optional<std::string>
+GetCacheRequestNamespace(const CbObjectView& Params)
+{
+ CbFieldView NamespaceField = Params["Namespace"];
+ if (!NamespaceField)
+ {
+ return std::string("!default!"); // ZenCacheStore::DefaultNamespace);
+ }
+
+ if (NamespaceField.HasError())
+ {
+ return {};
+ }
+ if (!NamespaceField.IsString())
+ {
+ return {};
+ }
+ return GetValidNamespaceName(NamespaceField.AsString());
+}
+
+bool
+GetCacheRequestCacheKey(const CbObjectView& KeyView, CacheKey& Key)
+{
+ CbFieldView BucketField = KeyView["Bucket"];
+ if (BucketField.HasError())
+ {
+ return false;
+ }
+ if (!BucketField.IsString())
+ {
+ return false;
+ }
+ std::optional<std::string> Bucket = GetValidBucketName(BucketField.AsString());
+ if (!Bucket.has_value())
+ {
+ return false;
+ }
+ CbFieldView HashField = KeyView["Hash"];
+ if (HashField.HasError())
+ {
+ return false;
+ }
+ if (!HashField.IsHash())
+ {
+ return false;
+ }
+ Key.Bucket = *Bucket;
+ Key.Hash = HashField.AsHash();
+ return true;
+}
+
+} // namespace zen
diff --git a/src/zenstore/cache/cachedisklayer.cpp b/src/zenstore/cache/cachedisklayer.cpp
index cacbbd966..c2e811003 100644
--- a/src/zenstore/cache/cachedisklayer.cpp
+++ b/src/zenstore/cache/cachedisklayer.cpp
@@ -10,14 +10,15 @@
#include <zencore/fmtutils.h>
#include <zencore/jobqueue.h>
#include <zencore/logging.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/trace.h>
#include <zencore/workthreadpool.h>
#include <zencore/xxhash.h>
-#include <zenutil/parallelwork.h>
-#include <zenutil/referencemetadata.h>
#include <zenutil/workerpools.h>
+#include "../referencemetadata.h"
+
#include <future>
#include <limits>
@@ -1968,17 +1969,19 @@ ZenCacheDiskLayer::CacheBucket::ShouldRejectPut(const IoHash& HashKey,
IndexLock.ReleaseNow();
if (!cache::impl::UpdateValueWithRawSizeAndHash(InOutValue))
{
- OutPutResult = PutResult{zen::PutStatus::Fail, "Value provided is of bad format"};
+ CbObjectWriter DetailWriter;
+ DetailWriter.AddString("Message", "Value provided is of bad format");
+ OutPutResult = PutResult{zen::PutStatus::Fail, DetailWriter.Save()};
return true;
}
else if (MetaData.RawSize != InOutValue.RawSize || MetaData.RawHash != InOutValue.RawHash)
{
- OutPutResult = PutResult{
- zen::PutStatus::Conflict,
- fmt::format("Value exists with different size '{}' or hash '{}'", MetaData.RawSize, MetaData.RawHash)};
- return true;
+ // Deliberate fall through without return so that we load the value and include it in the result
+ }
+ else
+ {
+ return false;
}
- return false;
}
}
@@ -2008,16 +2011,22 @@ ZenCacheDiskLayer::CacheBucket::ShouldRejectPut(const IoHash& HashKey,
{
if (!cache::impl::UpdateValueWithRawSizeAndHash(InOutValue))
{
- OutPutResult = PutResult{zen::PutStatus::Fail, "Value provided is of bad format"};
+ CbObjectWriter DetailWriter;
+ DetailWriter.AddString("Message", "Value provided is of bad format");
+ OutPutResult = PutResult{zen::PutStatus::Fail, DetailWriter.Save()};
return true;
}
if (ExistingValue.RawSize != InOutValue.RawSize || ExistingValue.RawHash != InOutValue.RawHash)
{
- OutPutResult = PutResult{zen::PutStatus::Conflict,
- fmt::format("Value exists with different size '{}' or hash '{}'",
- ExistingValue.RawSize,
- ExistingValue.RawHash)};
+ CbObjectWriter DetailWriter;
+ DetailWriter.AddInteger("RawSize", ExistingValue.RawSize);
+ DetailWriter.AddHash("RawHash", ExistingValue.RawHash);
+ if (Location.IsFlagSet(DiskLocation::kStructured))
+ {
+ DetailWriter.AddObject("Record", CbObject(SharedBuffer(ExistingValue.Value)));
+ }
+ OutPutResult = PutResult{zen::PutStatus::Conflict, DetailWriter.Save()};
return true;
}
}
@@ -3442,13 +3451,27 @@ ZenCacheDiskLayer::CacheBucket::GetReferences(const LoggerRef& Logger,
auto Log = [&Logger]() { return Logger; };
- auto GetAttachments = [&](MemoryView Data) -> bool {
- if (ValidateCompactBinary(Data, CbValidateMode::Default) == CbValidateError::None)
+ auto GetAttachments = [&](const IoHash& RawHash, MemoryView Data) -> bool {
+ if (CbValidateError Error = ValidateCompactBinary(Data, CbValidateMode::Default); Error == CbValidateError::None)
{
CbObjectView Obj(Data.GetData());
- Obj.IterateAttachments([&](CbFieldView Field) { OutReferences.emplace_back(Field.AsAttachment()); });
+ if (Obj.GetSize() == Data.GetSize())
+ {
+ Obj.IterateAttachments([&](CbFieldView Field) { OutReferences.emplace_back(Field.AsAttachment()); });
+ }
+ else
+ {
+ ZEN_WARN("Cache record {} payload is malformed. Payload size is {}, but compact binary object size is {}",
+ RawHash,
+ Data.GetSize(),
+ Obj.GetSize());
+ }
return true;
}
+ else
+ {
+ ZEN_WARN("Cache record {} payload is malformed. Reason: ", RawHash, ToString(Error));
+ }
return false;
};
@@ -3540,7 +3563,7 @@ ZenCacheDiskLayer::CacheBucket::GetReferences(const LoggerRef& Logger,
}
auto CaptureAttachments = [&](size_t ChunkIndex, MemoryView Data) {
- if (GetAttachments(Data))
+ if (GetAttachments(InlineKeys[ChunkIndex], Data))
{
if (WriteMetaData)
{
@@ -3609,7 +3632,7 @@ ZenCacheDiskLayer::CacheBucket::GetReferences(const LoggerRef& Logger,
IoBuffer Buffer = GetStandaloneCacheValue(It.second, It.first);
if (Buffer)
{
- GetAttachments(Buffer.GetView());
+ GetAttachments(It.first, Buffer.GetView());
}
}
return true;
@@ -4215,7 +4238,7 @@ ZenCacheDiskLayer::DiscoverBuckets()
WorkerThreadPool& Pool = GetLargeWorkerPool(EWorkloadType::Burst);
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
try
{
for (auto& BucketPath : FoundBucketDirectories)
@@ -4387,7 +4410,7 @@ ZenCacheDiskLayer::Flush()
WorkerThreadPool& Pool = GetMediumWorkerPool(EWorkloadType::Burst);
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
try
{
for (auto& Bucket : Buckets)
@@ -4434,7 +4457,8 @@ ZenCacheDiskLayer::Scrub(ScrubContext& Ctx)
{
# if 1
Results.push_back(Ctx.ThreadPool().EnqueueTask(
- std::packaged_task<void()>{[this, Bucket = Kv.second.get(), &Ctx] { Bucket->ScrubStorage(Ctx); }}));
+ std::packaged_task<void()>{[this, Bucket = Kv.second.get(), &Ctx] { Bucket->ScrubStorage(Ctx); }},
+ WorkerThreadPool::EMode::EnableBacklog));
# else
CacheBucket& Bucket = *Kv.second;
Bucket.ScrubStorage(Ctx);
diff --git a/src/zenutil/cache/cachekey.cpp b/src/zenstore/cache/cachekey.cpp
index 545b47f11..e5a5d0334 100644
--- a/src/zenutil/cache/cachekey.cpp
+++ b/src/zenstore/cache/cachekey.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/cache/cachekey.h>
+#include <zenstore/cache/cachekey.h>
namespace zen {
diff --git a/src/zenutil/cache/cachepolicy.cpp b/src/zenstore/cache/cachepolicy.cpp
index 0bdfd87ce..ca8a95ca1 100644
--- a/src/zenutil/cache/cachepolicy.cpp
+++ b/src/zenstore/cache/cachepolicy.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/cache/cachepolicy.h>
+#include <zenstore/cache/cachepolicy.h>
#include <zencore/compactbinary.h>
#include <zencore/compactbinarybuilder.h>
diff --git a/src/zenstore/cache/cacherpc.cpp b/src/zenstore/cache/cacherpc.cpp
index 5d9a68919..4c44f43ca 100644
--- a/src/zenstore/cache/cacherpc.cpp
+++ b/src/zenstore/cache/cacherpc.cpp
@@ -15,7 +15,6 @@
#include <zenstore/cache/structuredcachestore.h>
#include <zenstore/cache/upstreamcacheclient.h>
#include <zenstore/cidstore.h>
-#include <zenutil/cache/cacherequests.h>
#include <zenutil/workerpools.h>
#include <zencore/memory/llm.h>
@@ -61,7 +60,7 @@ GetRpcRequestNamespace(const CbObjectView Params)
{
return {};
}
- return cacherequests::GetValidNamespaceName(NamespaceField.AsString());
+ return GetValidNamespaceName(NamespaceField.AsString());
}
bool
@@ -76,7 +75,7 @@ GetRpcRequestCacheKey(const CbObjectView& KeyView, CacheKey& Key)
{
return false;
}
- std::optional<std::string> Bucket = cacherequests::GetValidBucketName(BucketField.AsString());
+ std::optional<std::string> Bucket = GetValidBucketName(BucketField.AsString());
if (!Bucket.has_value())
{
return false;
@@ -190,9 +189,8 @@ CacheRpcHandler::HandleRpcRequest(const CacheRequestContext& Context,
m_CacheStats.RpcRequests.fetch_add(1);
- CbPackage Package;
- CbObjectView Object;
- CbObject ObjectBuffer;
+ CbPackage Package;
+ CbObject Object;
try
{
if (ContentType == ZenContentType::kCbObject)
@@ -203,8 +201,7 @@ CacheRpcHandler::HandleRpcRequest(const CacheRequestContext& Context,
return RpcResponseCode::BadRequest;
}
- ObjectBuffer = LoadCompactBinaryObject(std::move(Body));
- Object = ObjectBuffer;
+ Object = LoadCompactBinaryObject(std::move(Body));
if (!Object)
{
ZEN_WARN("Content format not supported, expected compact binary format");
@@ -312,7 +309,7 @@ CacheRpcHandler::HandleRpcPutCacheRecords(const CacheRequestContext& Context, co
}
DefaultPolicy = !PolicyText.empty() ? ParseCachePolicy(PolicyText) : CachePolicy::Default;
- eastl::fixed_vector<bool, 32> Results;
+ eastl::fixed_vector<ZenCacheStore::PutResult, 32> Results;
CbArrayView RequestsArray = Params["Requests"sv].AsArrayView();
for (CbFieldView RequestField : RequestsArray)
@@ -334,33 +331,50 @@ CacheRpcHandler::HandleRpcPutCacheRecords(const CacheRequestContext& Context, co
.Policy = std::move(Policy),
.Context = Context};
- PutStatus Result = PutCacheRecord(PutRequest, &BatchRequest);
+ ZenCacheStore::PutResult Result = PutCacheRecord(PutRequest, &BatchRequest);
- if (Result == PutStatus::Invalid)
+ if (Result.Status == PutStatus::Invalid)
{
return CbPackage{};
}
- Results.push_back(Result == PutStatus::Success);
+ Results.push_back(Result);
}
if (Results.empty())
{
return CbPackage{};
}
+ bool bWriteAllDetails = false;
CbObjectWriter ResponseObject{256};
ResponseObject.BeginArray("Result"sv);
- for (bool Value : Results)
+ for (const ZenCacheStore::PutResult& Result : Results)
{
- ResponseObject.AddBool(Value);
+ // Conflicts will be treated for response purposes
+ // as successful puts because the key is present in the cache.
+ ResponseObject.AddBool((Result.Status == PutStatus::Success) || (Result.Status == PutStatus::Conflict));
+ if (Result.Details)
+ {
+ bWriteAllDetails = true;
+ }
}
ResponseObject.EndArray();
+ if (bWriteAllDetails)
+ {
+ ResponseObject.BeginArray("Details"sv);
+ for (const ZenCacheStore::PutResult& Result : Results)
+ {
+ ResponseObject.AddObject(Result.Details);
+ }
+ ResponseObject.EndArray();
+ }
+
CbPackage RpcResponse;
RpcResponse.SetObject(ResponseObject.Save());
return RpcResponse;
}
-PutStatus
+ZenCacheStore::PutResult
CacheRpcHandler::PutCacheRecord(PutRequestData& Request, const CbPackage* Package)
{
CbObjectView Record = Request.RecordObject;
@@ -422,7 +436,9 @@ CacheRpcHandler::PutCacheRecord(PutRequestData& Request, const CbPackage* Packag
if (Count.Invalid > 0)
{
- return PutStatus::Invalid;
+ CbObjectWriter DetailWriter;
+ DetailWriter.AddString("Message", fmt::format("Found {}/{} invalid attachments", Count.Invalid, Count.Total));
+ return ZenCacheStore::PutResult{PutStatus::Invalid, DetailWriter.Save()};
}
ZenCacheValue CacheValue;
@@ -442,7 +458,7 @@ CacheRpcHandler::PutCacheRecord(PutRequestData& Request, const CbPackage* Packag
nullptr);
if (PutResult.Status != zen::PutStatus::Success)
{
- return PutResult.Status;
+ return PutResult;
}
m_CacheStats.WriteCount++;
@@ -480,7 +496,7 @@ CacheRpcHandler::PutCacheRecord(PutRequestData& Request, const CbPackage* Packag
.Key = Request.Key,
.ValueContentIds = std::move(ValidAttachments)});
}
- return PutStatus::Success;
+ return PutResult;
}
CbPackage
@@ -843,14 +859,10 @@ CacheRpcHandler::HandleRpcGetCacheRecords(const CacheRequestContext& Context, Cb
Key.Hash);
}
}
- if (!Value.Exists && !EnumHasAllFlags(ValuePolicy, CachePolicy::SkipData))
+ if (!Value.Exists)
{
Request.Complete = false;
}
- // Request.Complete does not need to be set to false for upstream SkipData attachments.
- // In the PartialRecord==false case, the upstream will have failed the entire record if any SkipData attachment
- // didn't exist and we will not get here. In the PartialRecord==true case, we do not need to inform the client of
- // any missing SkipData attachments.
}
Request.ElapsedTimeUs += Timer.GetElapsedTimeUs();
}
@@ -866,7 +878,9 @@ CacheRpcHandler::HandleRpcGetCacheRecords(const CacheRequestContext& Context, Cb
ResponsePackage.ReserveAttachments(Requests.size());
+ eastl::fixed_vector<size_t, 4> IncompleteResultIndexes;
ResponseObject.BeginArray("Result"sv);
+ size_t ResultIndex = 0;
for (RecordRequestData& Request : Requests)
{
const CacheKey& Key = Request.Key;
@@ -882,6 +896,12 @@ CacheRpcHandler::HandleRpcGetCacheRecords(const CacheRequestContext& Context, Cb
}
}
+ if (!Request.Complete)
+ {
+ // If requesting a partial record, report back that the record overall is incomplete to the client
+ IncompleteResultIndexes.push_back(ResultIndex);
+ }
+
ZEN_DEBUG("GETCACHERECORD HIT - '{}/{}/{}' {}{} ({}) in {}",
*Namespace,
Key.Bucket,
@@ -918,8 +938,37 @@ CacheRpcHandler::HandleRpcGetCacheRecords(const CacheRequestContext& Context, Cb
m_CacheStats.MissCount++;
}
}
+ ++ResultIndex;
}
ResponseObject.EndArray();
+
+ if (!IncompleteResultIndexes.empty())
+ {
+ size_t IndexIntoIncompleteResultArray = 0;
+ size_t IncompleteResultIndex = IncompleteResultIndexes[IndexIntoIncompleteResultArray];
+ ResultIndex = 0;
+ ResponseObject.BeginArray("Incomplete"sv);
+ for (ResultIndex = 0; ResultIndex < Requests.size(); ++ResultIndex)
+ {
+ if (IncompleteResultIndex == ResultIndex)
+ {
+ ResponseObject.AddBool(true);
+ if (++IndexIntoIncompleteResultArray >= IncompleteResultIndexes.size())
+ {
+ IncompleteResultIndex = (size_t)-1;
+ }
+ else
+ {
+ IncompleteResultIndex = IncompleteResultIndexes[IndexIntoIncompleteResultArray];
+ }
+ }
+ else
+ {
+ ResponseObject.AddBool(true);
+ }
+ }
+ ResponseObject.EndArray();
+ }
ResponsePackage.SetObject(ResponseObject.Save());
return ResponsePackage;
}
@@ -1050,7 +1099,9 @@ CacheRpcHandler::HandleRpcPutCacheValues(const CacheRequestContext& Context, con
}
else
{
- Results.push_back({zen::PutStatus::Fail, fmt::format("Missing attachment with raw hash {}", RawHash)});
+ CbObjectWriter DetailWriter;
+ DetailWriter.AddString("Message", fmt::format("Missing attachment with raw hash {}", RawHash));
+ Results.push_back({zen::PutStatus::Fail, DetailWriter.Save()});
}
}
// We do not search the Upstream. No data in a put means the caller is probing for whether they need to do a heavy put.
@@ -1101,29 +1152,32 @@ CacheRpcHandler::HandleRpcPutCacheValues(const CacheRequestContext& Context, con
ZEN_TRACE_CPU("Z$::RpcPutCacheValues::Response");
CbObjectWriter ResponseObject{1024};
ResponseObject.BeginArray("Result"sv);
- bool bAnyErrors = false;
+ bool bWriteAllDetails = false;
for (const ZenCacheStore::PutResult& Value : Results)
{
- if (Value.Status == zen::PutStatus::Success)
+ // Conflicts will be treated for response purposes
+ // as successful puts because the key is present in the cache.
+ if ((Value.Status == zen::PutStatus::Success) || (Value.Status == zen::PutStatus::Conflict))
{
ResponseObject.AddBool(true);
}
else
{
- bAnyErrors = true;
ResponseObject.AddBool(false);
}
+ // If one result has details, we must write all details
+ if (Value.Details)
+ {
+ bWriteAllDetails = true;
+ }
}
ResponseObject.EndArray();
- if (bAnyErrors)
+ if (bWriteAllDetails)
{
- ResponseObject.BeginArray("ErrorMessages"sv);
+ ResponseObject.BeginArray("Details"sv);
for (const ZenCacheStore::PutResult& Value : Results)
{
- if (Value.Status != zen::PutStatus::Success)
- {
- ResponseObject.AddString(Value.Message);
- }
+ ResponseObject.AddObject(Value.Details);
}
ResponseObject.EndArray();
}
@@ -1768,7 +1822,9 @@ CacheRpcHandler::GetLocalCacheValues(const CacheRequestContext& Context,
ChunkRequest* Request = ValueRequests[RequestIndex];
if (Chunks[RequestIndex].Value)
{
- if (IsCompressedBinary(Chunks[RequestIndex].Value.GetContentType()))
+ // If the request included a raw hash, only offer a result if the RawHash matched what we found in storage
+ if (IsCompressedBinary(Chunks[RequestIndex].Value.GetContentType()) &&
+ ((Request->Key->ChunkId == IoHash::Zero) || (Request->Key->ChunkId == Chunks[RequestIndex].RawHash)))
{
Request->Key->ChunkId = Chunks[RequestIndex].RawHash;
Request->Exists = true;
diff --git a/src/zenstore/cache/structuredcachestore.cpp b/src/zenstore/cache/structuredcachestore.cpp
index 1f2d6c37f..da6acbde4 100644
--- a/src/zenstore/cache/structuredcachestore.cpp
+++ b/src/zenstore/cache/structuredcachestore.cpp
@@ -15,9 +15,8 @@
#include <zencore/thread.h>
#include <zencore/timer.h>
#include <zencore/trace.h>
-#include <zencore/workthreadpool.h>
+#include <zenstore/cache/cache.h>
#include <zenstore/scrubcontext.h>
-#include <zenutil/cache/cache.h>
#include <future>
#include <limits>
@@ -730,7 +729,9 @@ ZenCacheStore::Put(const CacheRequestContext& Context,
if (IsKnownBadBucketName(Bucket))
{
m_RejectedWriteCount++;
- return PutResult{zen::PutStatus::Invalid, "Bad bucket name"};
+ CbObjectWriter DetailWriter;
+ DetailWriter.AddString("Message", "Bad bucket name");
+ return PutResult{zen::PutStatus::Invalid, DetailWriter.Save()};
}
ZEN_MEMSCOPE(GetCacheStoreTag());
@@ -777,7 +778,10 @@ ZenCacheStore::Put(const CacheRequestContext& Context,
Namespace,
Bucket,
HashKey.ToHexString());
- return PutResult{zen::PutStatus::Fail, fmt::format("Unknown namespace '{}'", Namespace)};
+
+ CbObjectWriter DetailWriter;
+ DetailWriter.AddString("Message", fmt::format("Unknown namespace '{}'", Namespace));
+ return PutResult{zen::PutStatus::Fail, DetailWriter.Save()};
}
bool
@@ -1328,17 +1332,30 @@ namespace testutils {
std::pair<Oid, IoBuffer> CreateBinaryBlob(size_t Size) { return {Oid::NewOid(), CreateRandomBlob(Size)}; }
- std::vector<std::pair<Oid, CompressedBuffer>> CreateCompressedAttachment(CidStore& Store, const std::span<const size_t>& Sizes)
+ std::vector<std::pair<Oid, CompressedBuffer>> CreateCompressedAttachment(CidStore& Store,
+ WorkerThreadPool& ThreadPool,
+ const std::span<const size_t>& Sizes)
{
std::vector<std::pair<Oid, CompressedBuffer>> Result;
- Result.reserve(Sizes.size());
- for (size_t Size : Sizes)
+ Result.resize(Sizes.size());
+ Latch L(1);
+ for (size_t Index = 0; Index < Sizes.size(); Index++)
{
- auto Blob = CreateBinaryBlob(Size);
- CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(Blob.second.Data(), Blob.second.Size()));
- CHECK(!Store.ContainsChunk(Compressed.DecodeRawHash()));
- Result.emplace_back(std::pair<Oid, CompressedBuffer>(Blob.first, Compressed));
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&, Index]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ size_t Size = Sizes[Index];
+ auto Blob = CreateBinaryBlob(Size);
+ CompressedBuffer Compressed =
+ CompressedBuffer::Compress(SharedBuffer::MakeView(Blob.second.Data(), Blob.second.Size()));
+ CHECK(!Store.ContainsChunk(Compressed.DecodeRawHash()));
+ Result[Index] = std::pair<Oid, CompressedBuffer>(Blob.first, Compressed);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
+ L.CountDown();
+ L.Wait();
return Result;
}
@@ -1524,7 +1541,7 @@ TEST_CASE("cachestore.threadedinsert") // * doctest::skip(true))
ScopedTemporaryDirectory TempDir;
const uint64_t kChunkSize = 1048;
- const int32_t kChunkCount = 8192;
+ const int32_t kChunkCount = 4096;
struct Chunk
{
@@ -1565,24 +1582,28 @@ TEST_CASE("cachestore.threadedinsert") // * doctest::skip(true))
CreateDirectories(TempDir.Path());
- WorkerThreadPool ThreadPool(4);
+ WorkerThreadPool ThreadPool(Max(std::thread::hardware_concurrency(), 8u));
GcManager Gc;
auto JobQueue = MakeJobQueue(1, "testqueue");
ZenCacheNamespace Zcs(Gc, *JobQueue, TempDir.Path(), {});
{
+ Latch L(1);
std::atomic<size_t> WorkCompleted = 0;
for (const auto& Chunk : Chunks)
{
- ThreadPool.ScheduleWork([&Zcs, &WorkCompleted, &Chunk]() {
- Zcs.Put(Chunk.second.Bucket, Chunk.first, {.Value = Chunk.second.Buffer}, {}, false);
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < Chunks.size())
- {
- Sleep(1);
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Zcs, &WorkCompleted, &Chunk, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ Zcs.Put(Chunk.second.Bucket, Chunk.first, {.Value = Chunk.second.Buffer}, {}, false);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == Chunks.size());
}
auto DoGC = [](GcManager& Gc, ZenCacheNamespace& Zcs, std::unordered_map<IoHash, std::string, IoHash::Hasher>& GcChunkHashes) {
@@ -1609,24 +1630,28 @@ TEST_CASE("cachestore.threadedinsert") // * doctest::skip(true))
CHECK_LE(kChunkSize * Chunks.size(), TotalSize);
{
+ Latch L(1);
std::atomic<size_t> WorkCompleted = 0;
for (const auto& Chunk : Chunks)
{
- ThreadPool.ScheduleWork([&Zcs, &WorkCompleted, &Chunk]() {
- std::string Bucket = Chunk.second.Bucket;
- IoHash ChunkHash = Chunk.first;
- ZenCacheValue CacheValue;
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Zcs, &WorkCompleted, &Chunk, &L]() {
+ auto _ = MakeGuard([&]() { L.CountDown(); });
+ std::string Bucket = Chunk.second.Bucket;
+ IoHash ChunkHash = Chunk.first;
+ ZenCacheValue CacheValue;
- CHECK(Zcs.Get(Bucket, ChunkHash, CacheValue));
- IoHash Hash = IoHash::HashBuffer(CacheValue.Value);
- CHECK(ChunkHash == Hash);
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < Chunks.size())
- {
- Sleep(1);
+ CHECK(Zcs.Get(Bucket, ChunkHash, CacheValue));
+ IoHash Hash = IoHash::HashBuffer(CacheValue.Value);
+ CHECK(ChunkHash == Hash);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == Chunks.size());
}
std::unordered_map<IoHash, std::string, IoHash::Hasher> GcChunkHashes;
GcChunkHashes.reserve(Chunks.size());
@@ -1655,23 +1680,30 @@ TEST_CASE("cachestore.threadedinsert") // * doctest::skip(true))
std::atomic_uint32_t AddedChunkCount = 0;
for (const auto& Chunk : NewChunks)
{
- ThreadPool.ScheduleWork([&Zcs, &WorkCompleted, Chunk, &AddedChunkCount]() {
- Zcs.Put(Chunk.second.Bucket, Chunk.first, {.Value = Chunk.second.Buffer}, {}, false);
- AddedChunkCount.fetch_add(1);
- WorkCompleted.fetch_add(1);
- });
+ ThreadPool.ScheduleWork(
+ [&Zcs, &WorkCompleted, Chunk, &AddedChunkCount]() {
+ Zcs.Put(Chunk.second.Bucket, Chunk.first, {.Value = Chunk.second.Buffer}, {}, false);
+ AddedChunkCount.fetch_add(1);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
+ Latch L(1);
for (const auto& Chunk : Chunks)
{
- ThreadPool.ScheduleWork([&Zcs, &WorkCompleted, Chunk]() {
- ZenCacheValue CacheValue;
- if (Zcs.Get(Chunk.second.Bucket, Chunk.first, CacheValue))
- {
- CHECK(Chunk.first == IoHash::HashBuffer(CacheValue.Value));
- }
- WorkCompleted.fetch_add(1);
- });
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Zcs, &WorkCompleted, Chunk, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ ZenCacheValue CacheValue;
+ if (Zcs.Get(Chunk.second.Bucket, Chunk.first, CacheValue))
+ {
+ CHECK(Chunk.first == IoHash::HashBuffer(CacheValue.Value));
+ }
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
while (AddedChunkCount.load() < NewChunks.size())
{
@@ -1687,10 +1719,9 @@ TEST_CASE("cachestore.threadedinsert") // * doctest::skip(true))
DoGC(Gc, Zcs, GcChunkHashes);
}
- while (WorkCompleted < NewChunks.size() + Chunks.size())
- {
- Sleep(1);
- }
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == NewChunks.size() + Chunks.size());
{
// Need to be careful since we might GC blocks we don't know outside of RwLock::ExclusiveLockScope
@@ -1706,21 +1737,25 @@ TEST_CASE("cachestore.threadedinsert") // * doctest::skip(true))
}
}
{
+ Latch L(1);
{
std::atomic<size_t> WorkCompleted = 0;
for (const auto& Chunk : GcChunkHashes)
{
- ThreadPool.ScheduleWork([&Zcs, &WorkCompleted, Chunk]() {
- ZenCacheValue CacheValue;
- CHECK(Zcs.Get(Chunk.second, Chunk.first, CacheValue));
- CHECK(Chunk.first == IoHash::HashBuffer(CacheValue.Value));
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < GcChunkHashes.size())
- {
- Sleep(1);
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Zcs, &WorkCompleted, Chunk, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ ZenCacheValue CacheValue;
+ CHECK(Zcs.Get(Chunk.second, Chunk.first, CacheValue));
+ CHECK(Chunk.first == IoHash::HashBuffer(CacheValue.Value));
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == GcChunkHashes.size());
}
}
}
@@ -1848,11 +1883,13 @@ TEST_CASE("cachestore.drop.bucket")
CHECK(Value1.Value);
std::atomic_bool WorkComplete = false;
- Workers.ScheduleWork([&]() {
- zen::Sleep(100);
- Value1.Value = IoBuffer{};
- WorkComplete = true;
- });
+ Workers.ScheduleWork(
+ [&]() {
+ zen::Sleep(100);
+ Value1.Value = IoBuffer{};
+ WorkComplete = true;
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
// On Windows, DropBucket() will be blocked as long as we hold a reference to a buffer in the bucket
// Our DropBucket execution blocks any incoming request from completing until we are done with the drop
CHECK(Zcs.DropBucket(Namespace, Bucket));
@@ -1931,14 +1968,16 @@ TEST_CASE("cachestore.drop.namespace")
CHECK(Value4.Value);
std::atomic_bool WorkComplete = false;
- Workers.ScheduleWork([&]() {
- zen::Sleep(100);
- Value1.Value = IoBuffer{};
- Value2.Value = IoBuffer{};
- Value3.Value = IoBuffer{};
- Value4.Value = IoBuffer{};
- WorkComplete = true;
- });
+ Workers.ScheduleWork(
+ [&]() {
+ zen::Sleep(100);
+ Value1.Value = IoBuffer{};
+ Value2.Value = IoBuffer{};
+ Value3.Value = IoBuffer{};
+ Value4.Value = IoBuffer{};
+ WorkComplete = true;
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
// On Windows, DropBucket() will be blocked as long as we hold a reference to a buffer in the bucket
// Our DropBucket execution blocks any incoming request from completing until we are done with the drop
CHECK(Zcs.DropNamespace(Namespace1));
@@ -2218,6 +2257,8 @@ TEST_CASE("cachestore.newgc.basics")
std::vector<IoHash> CacheRecords;
std::vector<IoHash> UnstructuredCacheValues;
+ WorkerThreadPool WorkerPool(Max(std::thread::hardware_concurrency() - 1u, 2u));
+
const auto TearDrinkerBucket = "teardrinker"sv;
{
GcManager Gc;
@@ -2228,11 +2269,12 @@ TEST_CASE("cachestore.newgc.basics")
// Create some basic data
{
// Structured record with attachments
- auto Attachments1 = CreateCompressedAttachment(CidStore, std::vector<size_t>{77, 1024 * 1024 * 2, 99, 1024 * 1024 * 2 + 87});
+ auto Attachments1 =
+ CreateCompressedAttachment(CidStore, WorkerPool, std::vector<size_t>{77, 1024 * 1024 * 2, 99, 1024 * 1024 * 2 + 87});
CacheRecords.emplace_back(CreateCacheRecord(Zcs, CidStore, TearDrinkerBucket, Attachments1));
// Structured record with reuse of attachments
- auto Attachments2 = CreateCompressedAttachment(CidStore, std::vector<size_t>{971});
+ auto Attachments2 = CreateCompressedAttachment(CidStore, WorkerPool, std::vector<size_t>{971});
Attachments2.push_back(Attachments1[0]);
Attachments2.push_back(Attachments1[1]);
CacheRecords.emplace_back(CreateCacheRecord(Zcs, CidStore, TearDrinkerBucket, Attachments2));
@@ -2646,7 +2688,7 @@ TEST_CASE("cachestore.newgc.basics")
CHECK_EQ(7, Zcs.GetBucketInfo(TearDrinkerBucket).value().DiskLayerInfo.EntryCount);
auto Attachments =
- CreateCompressedAttachment(CidStore, std::vector<size_t>{177, 1024 * 1024 * 2 + 31, 8999, 1024 * 1024 * 2 + 187});
+ CreateCompressedAttachment(CidStore, WorkerPool, std::vector<size_t>{177, 1024 * 1024 * 2 + 31, 8999, 1024 * 1024 * 2 + 187});
IoHash CacheRecord = CreateCacheRecord(Zcs, CidStore, TearDrinkerBucket, Attachments);
{
// Do get so it ends up in memcache
diff --git a/src/zenstore/cas.cpp b/src/zenstore/cas.cpp
index 6b89beb3d..49d24c21e 100644
--- a/src/zenstore/cas.cpp
+++ b/src/zenstore/cas.cpp
@@ -132,13 +132,18 @@ CasImpl::Initialize(const CidStoreConfiguration& InConfig)
WorkerThreadPool& WorkerPool = GetMediumWorkerPool(EWorkloadType::Burst);
std::vector<std::future<void>> Work;
Work.emplace_back(
- WorkerPool.EnqueueTask(std::packaged_task<void()>{[&]() { m_LargeStrategy.Initialize(m_Config.RootDirectory, IsNewStore); }}));
- Work.emplace_back(WorkerPool.EnqueueTask(std::packaged_task<void()>{[&]() {
- m_TinyStrategy.Initialize(m_Config.RootDirectory, "tobs", 1u << 28, 16, IsNewStore); // 256 Mb per block
- }}));
- Work.emplace_back(WorkerPool.EnqueueTask(std::packaged_task<void()>{[&]() {
- m_SmallStrategy.Initialize(m_Config.RootDirectory, "sobs", 1u << 30, 4096, IsNewStore); // 1 Gb per block
- }}));
+ WorkerPool.EnqueueTask(std::packaged_task<void()>{[&]() { m_LargeStrategy.Initialize(m_Config.RootDirectory, IsNewStore); }},
+ WorkerThreadPool::EMode::DisableBacklog));
+ Work.emplace_back(WorkerPool.EnqueueTask(
+ std::packaged_task<void()>{[&]() {
+ m_TinyStrategy.Initialize(m_Config.RootDirectory, "tobs", 1u << 28, 16, IsNewStore); // 256 Mb per block
+ }},
+ WorkerThreadPool::EMode::DisableBacklog));
+ Work.emplace_back(WorkerPool.EnqueueTask(
+ std::packaged_task<void()>{[&]() {
+ m_SmallStrategy.Initialize(m_Config.RootDirectory, "sobs", 1u << 30, 4096, IsNewStore); // 1 Gb per block
+ }},
+ WorkerThreadPool::EMode::DisableBacklog));
for (std::future<void>& Result : Work)
{
if (Result.valid())
diff --git a/src/zenstore/compactcas.cpp b/src/zenstore/compactcas.cpp
index b00abb2cb..5cc4dad54 100644
--- a/src/zenstore/compactcas.cpp
+++ b/src/zenstore/compactcas.cpp
@@ -11,11 +11,11 @@
#include <zencore/iobuffer.h>
#include <zencore/logging.h>
#include <zencore/memory/llm.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/trace.h>
#include <zencore/workthreadpool.h>
#include <zenstore/scrubcontext.h>
-#include <zenutil/parallelwork.h>
#include <gsl/gsl-lite.hpp>
@@ -368,6 +368,14 @@ CasContainerStrategy::IterateChunks(std::span<const IoHash> ChunkHas
{
IoBuffer Chunk = m_BlockStore.TryGetChunk(FoundChunkLocations[Index]);
size_t OuterIndex = FoundChunkIndexes[Index];
+ if (!Chunk)
+ {
+ ZEN_WARN("Failed to fetch chunk {} from block {}, Offset {}, Size {}",
+ ChunkHashes[OuterIndex],
+ FoundChunkLocations[Index].BlockIndex,
+ FoundChunkLocations[Index].Offset,
+ FoundChunkLocations[Index].Size);
+ }
if (!AsyncCallback(OuterIndex, Chunk))
{
return false;
@@ -376,17 +384,26 @@ CasContainerStrategy::IterateChunks(std::span<const IoHash> ChunkHas
return true;
}
- auto DoOneBlock = [this](const std::function<bool(size_t Index, const IoBuffer& Payload)>& AsyncCallback,
- uint64_t LargeSizeLimit,
- std::span<const size_t> FoundChunkIndexes,
- std::span<const BlockStoreLocation> FoundChunkLocations,
- std::span<const size_t> ChunkIndexes) {
+ auto DoOneBlock = [this, &ChunkHashes](const std::function<bool(size_t Index, const IoBuffer& Payload)>& AsyncCallback,
+ uint64_t LargeSizeLimit,
+ std::span<const size_t> FoundChunkIndexes,
+ std::span<const BlockStoreLocation> FoundChunkLocations,
+ std::span<const size_t> ChunkIndexes) {
if (ChunkIndexes.size() < 4)
{
for (size_t ChunkIndex : ChunkIndexes)
{
- IoBuffer Chunk = m_BlockStore.TryGetChunk(FoundChunkLocations[ChunkIndex]);
- if (!AsyncCallback(FoundChunkIndexes[ChunkIndex], Chunk))
+ size_t OuterIndex = FoundChunkIndexes[ChunkIndex];
+ IoBuffer Chunk = m_BlockStore.TryGetChunk(FoundChunkLocations[ChunkIndex]);
+ if (!Chunk)
+ {
+ ZEN_WARN("Failed to fetch chunk {} from block {}, Offset {}, Size {}",
+ ChunkHashes[OuterIndex],
+ FoundChunkLocations[ChunkIndex].BlockIndex,
+ FoundChunkLocations[ChunkIndex].Offset,
+ FoundChunkLocations[ChunkIndex].Size);
+ }
+ if (!AsyncCallback(OuterIndex, Chunk))
{
return false;
}
@@ -396,15 +413,30 @@ CasContainerStrategy::IterateChunks(std::span<const IoHash> ChunkHas
return m_BlockStore.IterateBlock(
FoundChunkLocations,
ChunkIndexes,
- [AsyncCallback, FoundChunkIndexes](size_t ChunkIndex, const void* Data, uint64_t Size) {
+ [this, &ChunkHashes, AsyncCallback, FoundChunkIndexes](size_t ChunkIndex, const void* Data, uint64_t Size) {
+ size_t OuterIndex = FoundChunkIndexes[ChunkIndex];
if (Data == nullptr)
{
- return AsyncCallback(FoundChunkIndexes[ChunkIndex], IoBuffer());
+ ZEN_WARN("Failed to fetch chunk {}, Size {}", ChunkHashes[OuterIndex], Size);
+ return AsyncCallback(OuterIndex, IoBuffer());
}
- return AsyncCallback(FoundChunkIndexes[ChunkIndex], IoBuffer(IoBuffer::Wrap, Data, Size));
+ return AsyncCallback(OuterIndex, IoBuffer(IoBuffer::Wrap, Data, Size));
},
- [AsyncCallback, FoundChunkIndexes](size_t ChunkIndex, BlockStoreFile& File, uint64_t Offset, uint64_t Size) {
- return AsyncCallback(FoundChunkIndexes[ChunkIndex], File.GetChunk(Offset, Size));
+ [this, &ChunkHashes, AsyncCallback, FoundChunkIndexes](size_t ChunkIndex,
+ BlockStoreFile& File,
+ uint64_t Offset,
+ uint64_t Size) {
+ size_t OuterIndex = FoundChunkIndexes[ChunkIndex];
+ IoBuffer Chunk = File.GetChunk(Offset, Size);
+ if (!Chunk)
+ {
+ ZEN_WARN("Failed to fetch chunk {} from '{}', Offset {}, Size {}",
+ ChunkHashes[OuterIndex],
+ File.GetPath(),
+ Offset,
+ Size);
+ }
+ return AsyncCallback(OuterIndex, Chunk);
},
LargeSizeLimit);
};
@@ -412,7 +444,7 @@ CasContainerStrategy::IterateChunks(std::span<const IoHash> ChunkHas
std::atomic<bool> AbortFlag;
{
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
try
{
const bool Continue = m_BlockStore.IterateChunks(
@@ -1459,7 +1491,7 @@ TEST_CASE("compactcas.threadedinsert")
ScopedTemporaryDirectory TempDir;
const uint64_t kChunkSize = 1048;
- const int32_t kChunkCount = 4096;
+ const int32_t kChunkCount = 2048;
uint64_t ExpectedSize = 0;
tsl::robin_map<IoHash, IoBuffer, IoHash::Hasher> Chunks;
@@ -1481,48 +1513,58 @@ TEST_CASE("compactcas.threadedinsert")
}
}
- std::atomic<size_t> WorkCompleted = 0;
WorkerThreadPool ThreadPool(4);
GcManager Gc;
CasContainerStrategy Cas(Gc);
- Cas.Initialize(TempDir.Path(), "test", 32768, 16, true);
{
- for (const auto& Chunk : Chunks)
- {
- const IoHash& Hash = Chunk.first;
- const IoBuffer& Buffer = Chunk.second;
- ThreadPool.ScheduleWork([&Cas, &WorkCompleted, Buffer, Hash]() {
- CasStore::InsertResult InsertResult = Cas.InsertChunk(Buffer, Hash);
- ZEN_ASSERT(InsertResult.New);
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < Chunks.size())
+ std::atomic<size_t> WorkCompleted = 0;
+ Latch L(1);
+ Cas.Initialize(TempDir.Path(), "test", 32768, 16, true);
{
- Sleep(1);
+ for (const auto& Chunk : Chunks)
+ {
+ const IoHash& Hash = Chunk.first;
+ const IoBuffer& Buffer = Chunk.second;
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Cas, &WorkCompleted, Buffer, Hash, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ CasStore::InsertResult InsertResult = Cas.InsertChunk(Buffer, Hash);
+ ZEN_ASSERT(InsertResult.New);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
+ }
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == Chunks.size());
}
}
- WorkCompleted = 0;
- const uint64_t TotalSize = Cas.StorageSize().DiskSize;
- CHECK_LE(ExpectedSize, TotalSize);
- CHECK_GE(ExpectedSize + 32768, TotalSize);
-
{
+ std::atomic<size_t> WorkCompleted = 0;
+ Latch L(1);
+ const uint64_t TotalSize = Cas.StorageSize().DiskSize;
+ CHECK_LE(ExpectedSize, TotalSize);
+ CHECK_GE(ExpectedSize + 32768, TotalSize);
+
for (const auto& Chunk : Chunks)
{
- ThreadPool.ScheduleWork([&Cas, &WorkCompleted, &Chunk]() {
- IoHash ChunkHash = Chunk.first;
- IoBuffer Buffer = Cas.FindChunk(ChunkHash);
- IoHash Hash = IoHash::HashBuffer(Buffer);
- CHECK(ChunkHash == Hash);
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < Chunks.size())
- {
- Sleep(1);
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Cas, &WorkCompleted, &Chunk, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ IoHash ChunkHash = Chunk.first;
+ IoBuffer Buffer = Cas.FindChunk(ChunkHash);
+ IoHash Hash = IoHash::HashBuffer(Buffer);
+ CHECK(ChunkHash == Hash);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == Chunks.size());
}
tsl::robin_set<IoHash, IoHash::Hasher> GcChunkHashes;
@@ -1532,7 +1574,8 @@ TEST_CASE("compactcas.threadedinsert")
GcChunkHashes.insert(Chunk.first);
}
{
- WorkCompleted = 0;
+ std::atomic<size_t> WorkCompleted = 0;
+ Latch L(1);
tsl::robin_map<IoHash, IoBuffer, IoHash::Hasher> NewChunks;
NewChunks.reserve(kChunkCount);
@@ -1548,23 +1591,31 @@ TEST_CASE("compactcas.threadedinsert")
for (const auto& Chunk : NewChunks)
{
- ThreadPool.ScheduleWork([&Cas, &WorkCompleted, Chunk, &AddedChunkCount]() {
- Cas.InsertChunk(Chunk.second, Chunk.first);
- AddedChunkCount.fetch_add(1);
- WorkCompleted.fetch_add(1);
- });
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Cas, &WorkCompleted, Chunk, &AddedChunkCount, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ Cas.InsertChunk(Chunk.second, Chunk.first);
+ AddedChunkCount.fetch_add(1);
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
for (const auto& Chunk : Chunks)
{
- ThreadPool.ScheduleWork([&Cas, &WorkCompleted, Chunk]() {
- IoHash ChunkHash = Chunk.first;
- IoBuffer Buffer = Cas.FindChunk(ChunkHash);
- if (Buffer)
- {
- CHECK(ChunkHash == IoHash::HashBuffer(Buffer));
- }
- WorkCompleted.fetch_add(1);
- });
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Cas, &WorkCompleted, Chunk, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ IoHash ChunkHash = Chunk.first;
+ IoBuffer Buffer = Cas.FindChunk(ChunkHash);
+ if (Buffer)
+ {
+ CHECK(ChunkHash == IoHash::HashBuffer(Buffer));
+ }
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
tsl::robin_set<IoHash, IoHash::Hasher> ChunksToDelete;
@@ -1638,27 +1689,31 @@ TEST_CASE("compactcas.threadedinsert")
DoGC(Cas, ChunksToDelete, KeepHashes, GcChunkHashes);
}
- while (WorkCompleted < NewChunks.size() + Chunks.size())
- {
- Sleep(1);
- }
+ L.CountDown();
+ L.Wait();
+
+ CHECK(WorkCompleted == NewChunks.size() + Chunks.size());
DoGC(Cas, ChunksToDelete, KeepHashes, GcChunkHashes);
}
{
- WorkCompleted = 0;
+ std::atomic<size_t> WorkCompleted = 0;
+ Latch L(1);
for (const IoHash& ChunkHash : GcChunkHashes)
{
- ThreadPool.ScheduleWork([&Cas, &WorkCompleted, ChunkHash]() {
- CHECK(Cas.HaveChunk(ChunkHash));
- CHECK(ChunkHash == IoHash::HashBuffer(Cas.FindChunk(ChunkHash)));
- WorkCompleted.fetch_add(1);
- });
- }
- while (WorkCompleted < GcChunkHashes.size())
- {
- Sleep(1);
+ L.AddCount(1);
+ ThreadPool.ScheduleWork(
+ [&Cas, &WorkCompleted, ChunkHash, &L]() {
+ auto _ = MakeGuard([&L]() { L.CountDown(); });
+ CHECK(Cas.HaveChunk(ChunkHash));
+ CHECK(ChunkHash == IoHash::HashBuffer(Cas.FindChunk(ChunkHash)));
+ WorkCompleted.fetch_add(1);
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
+ L.CountDown();
+ L.Wait();
+ CHECK(WorkCompleted == GcChunkHashes.size());
}
}
}
@@ -1667,9 +1722,9 @@ TEST_CASE("compactcas.restart")
{
uint64_t ExpectedSize = 0;
- auto GenerateChunks = [&](CasContainerStrategy& Cas, size_t ChunkCount, uint64_t ChunkSize, std::vector<IoHash>& Hashes) {
- WorkerThreadPool ThreadPool(Max(std::thread::hardware_concurrency() - 1u, 2u), "put");
+ WorkerThreadPool ThreadPool(Max(std::thread::hardware_concurrency() - 1u, 2u), "put");
+ auto GenerateChunks = [&](CasContainerStrategy& Cas, size_t ChunkCount, uint64_t ChunkSize, std::vector<IoHash>& Hashes) {
Latch WorkLatch(1);
tsl::robin_set<IoHash, IoHash::Hasher> ChunkHashesLookup;
ChunkHashesLookup.reserve(ChunkCount);
@@ -1711,11 +1766,13 @@ TEST_CASE("compactcas.restart")
RwLock::ExclusiveLockScope __(InsertLock);
Hashes.insert(Hashes.end(), BatchHashes.begin(), BatchHashes.end());
}
- });
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
Offset += BatchCount;
}
WorkLatch.CountDown();
WorkLatch.Wait();
+ CHECK(ChunkHashesLookup.size() == ChunkCount);
};
ScopedTemporaryDirectory TempDir;
@@ -1737,26 +1794,32 @@ TEST_CASE("compactcas.restart")
Hashes.reserve(kChunkCount);
auto ValidateChunks = [&](CasContainerStrategy& Cas, std::span<const IoHash> Hashes, bool ShouldExist) {
- for (const IoHash& Hash : Hashes)
- {
- if (ShouldExist)
- {
- CHECK(Cas.HaveChunk(Hash));
- IoBuffer Buffer = Cas.FindChunk(Hash);
- CHECK(Buffer);
- IoHash ValidateHash;
- uint64_t ValidateRawSize;
- CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Buffer), ValidateHash, ValidateRawSize);
- CHECK(Compressed);
- CHECK(ValidateHash == Hash);
- }
- else
- {
- CHECK(!Cas.HaveChunk(Hash));
- IoBuffer Buffer = Cas.FindChunk(Hash);
- CHECK(!Buffer);
- }
- }
+ // It's important to not use std::vector<bool> here as that is not safe to use from multiple threads
+ // due to its non-atomic updates using bit masking
+ std::vector<uint8_t> Exists(Hashes.size(), false);
+ Cas.IterateChunks(
+ Hashes,
+ [&](size_t Index, const IoBuffer& Buffer) -> bool {
+ Exists[Index] = !!Buffer;
+ if (ShouldExist)
+ {
+ CHECK(Buffer);
+ IoHash ValidateHash;
+ uint64_t ValidateRawSize;
+ CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Buffer), ValidateHash, ValidateRawSize);
+ CHECK(Compressed);
+ CHECK(ValidateHash == Hashes[Index]);
+ }
+ else
+ {
+ CHECK(!Buffer);
+ }
+ return true;
+ },
+ &ThreadPool,
+ 1u * 1248u);
+
+ CHECK_EQ(std::find(Exists.begin(), Exists.end(), !ShouldExist ? 1 : 0), Exists.end());
};
{
@@ -1901,7 +1964,7 @@ TEST_CASE("compactcas.iteratechunks")
const uint64_t kChunkSize = 1048 + 395;
const size_t kChunkCount = 63840;
- for (uint32_t N = 0; N < 4; N++)
+ for (uint32_t N = 0; N < 2; N++)
{
GcManager Gc;
CasContainerStrategy Cas(Gc);
@@ -1964,7 +2027,8 @@ TEST_CASE("compactcas.iteratechunks")
RwLock::ExclusiveLockScope __(InsertLock);
Hashes.insert(Hashes.end(), BatchHashes.begin(), BatchHashes.end());
}
- });
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
Offset += BatchCount;
}
WorkLatch.CountDown();
@@ -1998,82 +2062,84 @@ TEST_CASE("compactcas.iteratechunks")
for (size_t I = 0; I < 2; I++)
{
WorkLatch.AddCount(1);
- ThreadPool.ScheduleWork([&Cas, &Hashes, &BatchWorkerPool, &WorkLatch, I]() {
- auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
- std::vector<IoHash> PartialHashes;
- PartialHashes.reserve(Hashes.size() / 4);
- for (size_t Index = 0; Index < Hashes.size(); Index++)
- {
- size_t TestIndex = Index + I;
- if ((TestIndex % 7 == 1) || (TestIndex % 13 == 1) || (TestIndex % 17 == 1))
+ ThreadPool.ScheduleWork(
+ [&Cas, &Hashes, &BatchWorkerPool, &WorkLatch, I]() {
+ auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
+ std::vector<IoHash> PartialHashes;
+ PartialHashes.reserve(Hashes.size() / 4);
+ for (size_t Index = 0; Index < Hashes.size(); Index++)
{
- PartialHashes.push_back(Hashes[Index]);
+ size_t TestIndex = Index + I;
+ if ((TestIndex % 7 == 1) || (TestIndex % 13 == 1) || (TestIndex % 17 == 1))
+ {
+ PartialHashes.push_back(Hashes[Index]);
+ }
}
- }
- std::reverse(PartialHashes.begin(), PartialHashes.end());
+ std::reverse(PartialHashes.begin(), PartialHashes.end());
- std::vector<IoHash> NoFoundHashes;
- std::vector<size_t> NoFindIndexes;
-
- NoFoundHashes.reserve(9);
- for (size_t J = 0; J < 9; J++)
- {
- std::string Data = fmt::format("oh no, we don't exist {}", J + 1);
- NoFoundHashes.push_back(IoHash::HashBuffer(Data.data(), Data.length()));
- }
-
- NoFindIndexes.reserve(9);
-
- // Sprinkle in chunks that are not found!
- auto It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 0, NoFoundHashes[0]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 0 + 1, NoFoundHashes[1]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 1, NoFoundHashes[2]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 1 + 1, NoFoundHashes[3]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 2, NoFoundHashes[4]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 3, NoFoundHashes[5]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 3 + 1, NoFoundHashes[6]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 4, NoFoundHashes[7]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
- It = PartialHashes.insert(PartialHashes.end(), NoFoundHashes[8]);
- NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
-
- std::vector<std::atomic<bool>> FoundFlags(PartialHashes.size() + NoFoundHashes.size());
- std::vector<std::atomic<uint32_t>> FetchedCounts(PartialHashes.size() + NoFoundHashes.size());
-
- CHECK(Cas.IterateChunks(
- PartialHashes,
- [&PartialHashes, &FoundFlags, &FetchedCounts, &NoFindIndexes](size_t Index, const IoBuffer& Payload) {
- CHECK_EQ(NoFindIndexes.end(), std::find(NoFindIndexes.begin(), NoFindIndexes.end(), Index));
- uint32_t PreviousCount = FetchedCounts[Index].fetch_add(1);
- CHECK(PreviousCount == 0);
- FoundFlags[Index] = !!Payload;
- const IoHash& Hash = PartialHashes[Index];
- CHECK(Hash == IoHash::HashBuffer(Payload));
- return true;
- },
- &BatchWorkerPool,
- 2048u));
+ std::vector<IoHash> NoFoundHashes;
+ std::vector<size_t> NoFindIndexes;
- for (size_t FoundIndex = 0; FoundIndex < PartialHashes.size(); FoundIndex++)
- {
- CHECK(FetchedCounts[FoundIndex].load() <= 1);
- if (std::find(NoFindIndexes.begin(), NoFindIndexes.end(), FoundIndex) == NoFindIndexes.end())
+ NoFoundHashes.reserve(9);
+ for (size_t J = 0; J < 9; J++)
{
- CHECK(FoundFlags[FoundIndex]);
+ std::string Data = fmt::format("oh no, we don't exist {}", J + 1);
+ NoFoundHashes.push_back(IoHash::HashBuffer(Data.data(), Data.length()));
}
- else
+
+ NoFindIndexes.reserve(9);
+
+ // Sprinkle in chunks that are not found!
+ auto It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 0, NoFoundHashes[0]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 0 + 1, NoFoundHashes[1]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 1, NoFoundHashes[2]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 1 + 1, NoFoundHashes[3]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 2, NoFoundHashes[4]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 3, NoFoundHashes[5]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 3 + 1, NoFoundHashes[6]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.begin() + (PartialHashes.size() / 4) * 4, NoFoundHashes[7]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+ It = PartialHashes.insert(PartialHashes.end(), NoFoundHashes[8]);
+ NoFindIndexes.push_back(std::distance(PartialHashes.begin(), It));
+
+ std::vector<std::atomic<bool>> FoundFlags(PartialHashes.size() + NoFoundHashes.size());
+ std::vector<std::atomic<uint32_t>> FetchedCounts(PartialHashes.size() + NoFoundHashes.size());
+
+ CHECK(Cas.IterateChunks(
+ PartialHashes,
+ [&PartialHashes, &FoundFlags, &FetchedCounts, &NoFindIndexes](size_t Index, const IoBuffer& Payload) {
+ CHECK_EQ(NoFindIndexes.end(), std::find(NoFindIndexes.begin(), NoFindIndexes.end(), Index));
+ uint32_t PreviousCount = FetchedCounts[Index].fetch_add(1);
+ CHECK(PreviousCount == 0);
+ FoundFlags[Index] = !!Payload;
+ const IoHash& Hash = PartialHashes[Index];
+ CHECK(Hash == IoHash::HashBuffer(Payload));
+ return true;
+ },
+ &BatchWorkerPool,
+ 2048u));
+
+ for (size_t FoundIndex = 0; FoundIndex < PartialHashes.size(); FoundIndex++)
{
- CHECK(!FoundFlags[FoundIndex]);
+ CHECK(FetchedCounts[FoundIndex].load() <= 1);
+ if (std::find(NoFindIndexes.begin(), NoFindIndexes.end(), FoundIndex) == NoFindIndexes.end())
+ {
+ CHECK(FoundFlags[FoundIndex]);
+ }
+ else
+ {
+ CHECK(!FoundFlags[FoundIndex]);
+ }
}
- }
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
WorkLatch.CountDown();
WorkLatch.Wait();
diff --git a/src/zenstore/filecas.cpp b/src/zenstore/filecas.cpp
index 68644be2d..13437369f 100644
--- a/src/zenstore/filecas.cpp
+++ b/src/zenstore/filecas.cpp
@@ -9,6 +9,7 @@
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/memory/memory.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/string.h>
#include <zencore/testing.h>
@@ -20,7 +21,6 @@
#include <zencore/workthreadpool.h>
#include <zenstore/gc.h>
#include <zenstore/scrubcontext.h>
-#include <zenutil/parallelwork.h>
#if ZEN_WITH_TESTS
# include <zencore/compactbinarybuilder.h>
@@ -656,6 +656,12 @@ FileCasStrategy::IterateChunks(std::span<IoHash> ChunkHashes,
ZEN_ASSERT(ChunkIndex < ChunkHashes.size());
const IoHash& ChunkHash = ChunkHashes[ChunkIndex];
IoBuffer Payload = SafeOpenChunk(ChunkHash, ExpectedSize);
+ if (!Payload)
+ {
+ ShardingHelper Name(m_RootDirectory, ChunkHash);
+ const std::filesystem::path ChunkPath = Name.ShardedPath.ToPath();
+ ZEN_WARN("Failed to fetch chunk {} from '{}', Size {}", ChunkHash, ChunkPath, ExpectedSize);
+ }
if (!AsyncCallback(ChunkIndex, std::move(Payload)))
{
return false;
@@ -665,7 +671,7 @@ FileCasStrategy::IterateChunks(std::span<IoHash> ChunkHashes,
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
try
{
for (size_t Index = 0; Index < FoundChunkIndexes.size(); Index++)
@@ -754,6 +760,12 @@ FileCasStrategy::IterateChunks(std::function<void(const IoHash& Hash, IoBuffer&&
const IoHash& ChunkHash = RawHashes[Index];
const uint64_t ExpectedSize = ExpectedSizes[Index];
IoBuffer Payload = SafeOpenChunk(ChunkHash, ExpectedSize);
+ if (!Payload)
+ {
+ ShardingHelper Name(m_RootDirectory, ChunkHash);
+ const std::filesystem::path ChunkPath = Name.ShardedPath.ToPath();
+ ZEN_WARN("Failed to fetch chunk {} from '{}', Size {}", ChunkHash, ChunkPath, ExpectedSize);
+ }
Callback(ChunkHash, std::move(Payload));
}
}
diff --git a/src/zenstore/gc.cpp b/src/zenstore/gc.cpp
index a7ef401d5..050ee3443 100644
--- a/src/zenstore/gc.cpp
+++ b/src/zenstore/gc.cpp
@@ -543,6 +543,8 @@ FilterReferences(GcCtx& Ctx, std::string_view Context, std::vector<IoHash>& InOu
return false;
}
+ auto Log = [&Ctx]() { return Ctx.Logger; };
+
const bool Filter = Ctx.Settings.AttachmentRangeMax != IoHash::Max || Ctx.Settings.AttachmentRangeMin != IoHash::Zero;
size_t TotalCount = InOutReferences.size();
@@ -762,7 +764,7 @@ GcManager::CollectGarbage(const GcSettings& Settings)
}
catch (const std::bad_alloc& Ex)
{
- ZEN_ERROR("GCV2: Failed removing expired data for {}. Reason: '{}'", Owner->GetGcName(Ctx), Ex.what());
+ ZEN_WARN("GCV2: Failed removing expired data for {}. Reason: '{}'", Owner->GetGcName(Ctx), Ex.what());
SetCancelGC(true);
}
catch (const std::exception& Ex)
@@ -855,9 +857,9 @@ GcManager::CollectGarbage(const GcSettings& Settings)
}
catch (const std::bad_alloc& Ex)
{
- ZEN_ERROR("GCV2: Failed creating reference pruners for {}. Reason: '{}'",
- ReferenceStore->GetGcName(Ctx),
- Ex.what());
+ ZEN_WARN("GCV2: Failed creating reference pruners for {}. Reason: '{}'",
+ ReferenceStore->GetGcName(Ctx),
+ Ex.what());
SetCancelGC(true);
}
catch (const std::exception& Ex)
@@ -867,7 +869,8 @@ GcManager::CollectGarbage(const GcSettings& Settings)
Ex.what());
SetCancelGC(true);
}
- });
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
WorkLeft.CountDown();
WorkLeft.Wait();
@@ -966,9 +969,9 @@ GcManager::CollectGarbage(const GcSettings& Settings)
}
catch (const std::bad_alloc& Ex)
{
- ZEN_ERROR("GCV2: Failed creating reference checkers for {}. Reason: '{}'",
- Referencer->GetGcName(Ctx),
- Ex.what());
+ ZEN_WARN("GCV2: Failed creating reference checkers for {}. Reason: '{}'",
+ Referencer->GetGcName(Ctx),
+ Ex.what());
SetCancelGC(true);
}
catch (const std::exception& Ex)
@@ -979,7 +982,8 @@ GcManager::CollectGarbage(const GcSettings& Settings)
SetCancelGC(true);
}
}
- });
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
WorkLeft.CountDown();
WorkLeft.Wait();
@@ -1019,77 +1023,80 @@ GcManager::CollectGarbage(const GcSettings& Settings)
GcReferencer* Referencer = m_GcReferencers[Index];
std::pair<std::string, GcReferencerStats>* ReferemcerStats = &Result.ReferencerStats[Index];
WorkLeft.AddCount(1);
- ParallelWorkThreadPool.ScheduleWork([this,
- &Ctx,
- &WorkLeft,
- Referencer,
- Index,
- Result = &Result,
- ReferemcerStats,
- &ReferenceValidatorsLock,
- &ReferenceValidators]() {
- ZEN_MEMSCOPE(GetGcTag());
+ ParallelWorkThreadPool.ScheduleWork(
+ [this,
+ &Ctx,
+ &WorkLeft,
+ Referencer,
+ Index,
+ Result = &Result,
+ ReferemcerStats,
+ &ReferenceValidatorsLock,
+ &ReferenceValidators]() {
+ ZEN_MEMSCOPE(GetGcTag());
- auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); });
- std::vector<GcReferenceValidator*> Validators;
- auto __ = MakeGuard([&Validators]() {
- while (!Validators.empty())
- {
- delete Validators.back();
- Validators.pop_back();
- }
- });
- try
- {
+ auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); });
+ std::vector<GcReferenceValidator*> Validators;
+ auto __ = MakeGuard([&Validators]() {
+ while (!Validators.empty())
+ {
+ delete Validators.back();
+ Validators.pop_back();
+ }
+ });
+ try
{
- SCOPED_TIMER(ReferemcerStats->second.CreateReferenceValidatorsMS =
- std::chrono::milliseconds(Timer.GetElapsedTimeMs()););
- Validators = Referencer->CreateReferenceValidators(Ctx);
+ {
+ SCOPED_TIMER(ReferemcerStats->second.CreateReferenceValidatorsMS =
+ std::chrono::milliseconds(Timer.GetElapsedTimeMs()););
+ Validators = Referencer->CreateReferenceValidators(Ctx);
+ }
+ if (!Validators.empty())
+ {
+ RwLock::ExclusiveLockScope __(ReferenceValidatorsLock);
+ for (auto& ReferenceValidator : Validators)
+ {
+ size_t ReferencesStatsIndex = Result->ReferenceValidatorStats.size();
+ Result->ReferenceValidatorStats.push_back({ReferenceValidator->GetGcName(Ctx), {}});
+ ReferenceValidators.insert_or_assign(
+ std::unique_ptr<GcReferenceValidator>(ReferenceValidator),
+ ReferencesStatsIndex);
+ ReferenceValidator = nullptr;
+ }
+ }
}
- if (!Validators.empty())
+ catch (const std::system_error& Ex)
{
- RwLock::ExclusiveLockScope __(ReferenceValidatorsLock);
- for (auto& ReferenceValidator : Validators)
+ if (IsOOD(Ex) || IsOOM(Ex))
+ {
+ ZEN_WARN("GCV2: Failed creating reference validators for {}. Reason: '{}'",
+ Referencer->GetGcName(Ctx),
+ Ex.what());
+ }
+ else
{
- size_t ReferencesStatsIndex = Result->ReferenceValidatorStats.size();
- Result->ReferenceValidatorStats.push_back({ReferenceValidator->GetGcName(Ctx), {}});
- ReferenceValidators.insert_or_assign(std::unique_ptr<GcReferenceValidator>(ReferenceValidator),
- ReferencesStatsIndex);
- ReferenceValidator = nullptr;
+ ZEN_ERROR("GCV2: Failed creating reference validators for {}. Reason: '{}'",
+ Referencer->GetGcName(Ctx),
+ Ex.what());
}
+ SetCancelGC(true);
}
- }
- catch (const std::system_error& Ex)
- {
- if (IsOOD(Ex) || IsOOM(Ex))
+ catch (const std::bad_alloc& Ex)
{
ZEN_WARN("GCV2: Failed creating reference validators for {}. Reason: '{}'",
Referencer->GetGcName(Ctx),
Ex.what());
+ SetCancelGC(true);
}
- else
+ catch (const std::exception& Ex)
{
ZEN_ERROR("GCV2: Failed creating reference validators for {}. Reason: '{}'",
Referencer->GetGcName(Ctx),
Ex.what());
+ SetCancelGC(true);
}
- SetCancelGC(true);
- }
- catch (const std::bad_alloc& Ex)
- {
- ZEN_ERROR("GCV2: Failed creating reference validators for {}. Reason: '{}'",
- Referencer->GetGcName(Ctx),
- Ex.what());
- SetCancelGC(true);
- }
- catch (const std::exception& Ex)
- {
- ZEN_ERROR("GCV2: Failed creating reference validators for {}. Reason: '{}'",
- Referencer->GetGcName(Ctx),
- Ex.what());
- SetCancelGC(true);
- }
- });
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
WorkLeft.CountDown();
WorkLeft.Wait();
@@ -1141,7 +1148,7 @@ GcManager::CollectGarbage(const GcSettings& Settings)
}
catch (const std::bad_alloc& Ex)
{
- ZEN_ERROR("GCV2: Failed precaching for {}. Reason: '{}'", Checker->GetGcName(Ctx), Ex.what());
+ ZEN_WARN("GCV2: Failed precaching for {}. Reason: '{}'", Checker->GetGcName(Ctx), Ex.what());
SetCancelGC(true);
}
catch (const std::exception& Ex)
@@ -1219,47 +1226,49 @@ GcManager::CollectGarbage(const GcSettings& Settings)
size_t Index = It.second;
std::pair<std::string, GcReferencerStats>* Stats = &Result.ReferencerStats[Index];
WorkLeft.AddCount(1);
- LockedPhaseThreadPool.ScheduleWork([this, &Ctx, Checker, Index, Stats, &WorkLeft]() {
- ZEN_MEMSCOPE(GetGcTag());
+ LockedPhaseThreadPool.ScheduleWork(
+ [this, &Ctx, Checker, Index, Stats, &WorkLeft]() {
+ ZEN_MEMSCOPE(GetGcTag());
- auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); });
- try
- {
- SCOPED_TIMER(Stats->second.UpdateLockedStateMS =
- std::chrono::milliseconds(Timer.GetElapsedTimeMs()););
- Checker->UpdateLockedState(Ctx);
- }
- catch (const std::system_error& Ex)
- {
- if (IsOOD(Ex) || IsOOM(Ex))
+ auto _ = MakeGuard([&WorkLeft]() { WorkLeft.CountDown(); });
+ try
+ {
+ SCOPED_TIMER(Stats->second.UpdateLockedStateMS =
+ std::chrono::milliseconds(Timer.GetElapsedTimeMs()););
+ Checker->UpdateLockedState(Ctx);
+ }
+ catch (const std::system_error& Ex)
+ {
+ if (IsOOD(Ex) || IsOOM(Ex))
+ {
+ ZEN_WARN("GCV2: Failed Updating locked state for {}. Reason: '{}'",
+ Checker->GetGcName(Ctx),
+ Ex.what());
+ }
+ else
+ {
+ ZEN_ERROR("GCV2: Failed Updating locked state for {}. Reason: '{}'",
+ Checker->GetGcName(Ctx),
+ Ex.what());
+ }
+ SetCancelGC(true);
+ }
+ catch (const std::bad_alloc& Ex)
{
ZEN_WARN("GCV2: Failed Updating locked state for {}. Reason: '{}'",
Checker->GetGcName(Ctx),
Ex.what());
+ SetCancelGC(true);
}
- else
+ catch (const std::exception& Ex)
{
ZEN_ERROR("GCV2: Failed Updating locked state for {}. Reason: '{}'",
Checker->GetGcName(Ctx),
Ex.what());
+ SetCancelGC(true);
}
- SetCancelGC(true);
- }
- catch (const std::bad_alloc& Ex)
- {
- ZEN_WARN("GCV2: Failed Updating locked state for {}. Reason: '{}'",
- Checker->GetGcName(Ctx),
- Ex.what());
- SetCancelGC(true);
- }
- catch (const std::exception& Ex)
- {
- ZEN_ERROR("GCV2: Failed Updating locked state for {}. Reason: '{}'",
- Checker->GetGcName(Ctx),
- Ex.what());
- SetCancelGC(true);
- }
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
WorkLeft.CountDown();
WorkLeft.Wait();
@@ -1359,9 +1368,9 @@ GcManager::CollectGarbage(const GcSettings& Settings)
}
catch (const std::bad_alloc& Ex)
{
- ZEN_ERROR("GCV2: Failed removing unused data for {}. Reason: '{}'",
- Pruner->GetGcName(Ctx),
- Ex.what());
+ ZEN_WARN("GCV2: Failed removing unused data for {}. Reason: '{}'",
+ Pruner->GetGcName(Ctx),
+ Ex.what());
SetCancelGC(true);
}
catch (const std::exception& Ex)
@@ -1371,7 +1380,8 @@ GcManager::CollectGarbage(const GcSettings& Settings)
Ex.what());
SetCancelGC(true);
}
- });
+ },
+ WorkerThreadPool::EMode::EnableBacklog);
}
WorkLeft.CountDown();
WorkLeft.Wait();
@@ -1441,7 +1451,7 @@ GcManager::CollectGarbage(const GcSettings& Settings)
}
catch (const std::bad_alloc& Ex)
{
- ZEN_ERROR("GCV2: Failed compacting store {}. Reason: '{}'", Compactor->GetGcName(Ctx), Ex.what());
+ ZEN_WARN("GCV2: Failed compacting store {}. Reason: '{}'", Compactor->GetGcName(Ctx), Ex.what());
SetCancelGC(true);
}
catch (const std::exception& Ex)
@@ -1499,7 +1509,7 @@ GcManager::CollectGarbage(const GcSettings& Settings)
}
catch (const std::bad_alloc& Ex)
{
- ZEN_ERROR("GCV2: Failed validating referencer {}. Reason: '{}'", ReferenceValidator->GetGcName(Ctx), Ex.what());
+ ZEN_WARN("GCV2: Failed validating referencer {}. Reason: '{}'", ReferenceValidator->GetGcName(Ctx), Ex.what());
SetCancelGC(true);
}
catch (const std::exception& Ex)
@@ -2083,10 +2093,11 @@ GcScheduler::SchedulerThread()
{
ZEN_ASSERT(WaitTime.count() >= 0);
std::unique_lock Lock(m_GcMutex);
- while (!Timeout)
+ while (!Timeout && (Status() != GcSchedulerStatus::kStopped))
{
std::chrono::seconds ShortWait = Min(WaitTime, ShortWaitTime);
bool ShortTimeout = std::cv_status::timeout == m_GcSignal.wait_for(Lock, ShortWait);
+
if (ShortTimeout)
{
if (WaitTime > ShortWaitTime)
@@ -2848,7 +2859,7 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime,
{
ZEN_WARN("writing gc scheduler state ran out of disk space: '{}'", SystemError.what());
}
- else if (RetryCount == 0)
+ else if (RetryCount == 2)
{
ZEN_ERROR("writing gc scheduler state failed with system error exception: '{}' ({})",
SystemError.what(),
@@ -2867,7 +2878,7 @@ GcScheduler::CollectGarbage(const GcClock::TimePoint& CacheExpireTime,
}
catch (const std::exception& Ex)
{
- if (RetryCount == 0)
+ if (RetryCount == 2)
{
ZEN_ERROR("writing gc scheduler state failed with: '{}'", Ex.what());
}
diff --git a/src/zenstore/include/zenstore/blockstore.h b/src/zenstore/include/zenstore/blockstore.h
index fce05766f..4006f4275 100644
--- a/src/zenstore/include/zenstore/blockstore.h
+++ b/src/zenstore/include/zenstore/blockstore.h
@@ -102,12 +102,12 @@ struct BlockStoreFile : public RefCounted
IoBuffer GetMetaData() const;
private:
- std::filesystem::path GetMetaPath() const;
- void RemoveMeta();
- const std::filesystem::path m_Path;
- IoBuffer m_IoBuffer;
- BasicFile m_File;
- std::atomic<uint64_t> m_CachedFileSize = 0;
+ std::filesystem::path GetMetaPath() const;
+ void RemoveMeta();
+ const std::filesystem::path m_Path;
+ IoBuffer m_IoBuffer;
+ BasicFile m_File;
+ mutable std::atomic<uint64_t> m_CachedFileSize = 0;
};
class BlockStoreCompactState;
diff --git a/src/zenstore/include/zenstore/cache/cache.h b/src/zenstore/include/zenstore/cache/cache.h
new file mode 100644
index 000000000..4e72d1b05
--- /dev/null
+++ b/src/zenstore/include/zenstore/cache/cache.h
@@ -0,0 +1,52 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zenstore/cache/cachekey.h>
+#include <zenstore/cache/cachepolicy.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <fmt/format.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+struct CacheRequestContext
+{
+ Oid SessionId{Oid::Zero};
+ uint32_t RequestId{0};
+};
+
+std::optional<std::string> GetValidNamespaceName(std::string_view Name);
+std::optional<std::string> GetValidBucketName(std::string_view Name);
+std::optional<IoHash> GetValidIoHash(std::string_view Hash);
+
+struct HttpCacheRequestData
+{
+ std::optional<std::string> Namespace;
+ std::optional<std::string> Bucket;
+ std::optional<IoHash> HashKey;
+ std::optional<IoHash> ValueContentId;
+};
+
+bool HttpCacheRequestParseRelativeUri(std::string_view Key, std::string_view DefaultNamespace, HttpCacheRequestData& Data);
+
+// Temporarily public
+std::optional<std::string> GetCacheRequestNamespace(const CbObjectView& Params);
+bool GetCacheRequestCacheKey(const CbObjectView& KeyView, CacheKey& Key);
+
+} // namespace zen
+
+template<>
+struct fmt::formatter<zen::CacheRequestContext> : formatter<string_view>
+{
+ template<typename FormatContext>
+ auto format(const zen::CacheRequestContext& Context, FormatContext& ctx) const
+ {
+ zen::ExtendableStringBuilder<64> String;
+ Context.SessionId.ToString(String);
+ String << ".";
+ String << Context.RequestId;
+ return formatter<string_view>::format(String.ToView(), ctx);
+ }
+};
diff --git a/src/zenstore/include/zenstore/cache/cachedisklayer.h b/src/zenstore/include/zenstore/cache/cachedisklayer.h
index 49c52f847..10c61681b 100644
--- a/src/zenstore/include/zenstore/cache/cachedisklayer.h
+++ b/src/zenstore/include/zenstore/cache/cachedisklayer.h
@@ -4,6 +4,7 @@
#include "cacheshared.h"
+#include <zencore/compactbinary.h>
#include <zencore/stats.h>
#include <zenstore/accesstime.h>
#include <zenstore/blockstore.h>
@@ -180,7 +181,7 @@ public:
struct PutResult
{
zen::PutStatus Status;
- std::string Message;
+ CbObject Details;
};
explicit ZenCacheDiskLayer(GcManager& Gc, JobQueue& JobQueue, const std::filesystem::path& RootDir, const Configuration& Config);
diff --git a/src/zenutil/include/zenutil/cache/cachekey.h b/src/zenstore/include/zenstore/cache/cachekey.h
index 0ab05f4f1..ae79f2a3d 100644
--- a/src/zenutil/include/zenutil/cache/cachekey.h
+++ b/src/zenstore/include/zenstore/cache/cachekey.h
@@ -6,7 +6,7 @@
#include <zencore/string.h>
#include <zencore/uid.h>
-#include <zenutil/cache/cachepolicy.h>
+#include <zenstore/cache/cachepolicy.h>
namespace zen {
diff --git a/src/zenutil/include/zenutil/cache/cachepolicy.h b/src/zenstore/include/zenstore/cache/cachepolicy.h
index 7773cd3d1..7773cd3d1 100644
--- a/src/zenutil/include/zenutil/cache/cachepolicy.h
+++ b/src/zenstore/include/zenstore/cache/cachepolicy.h
diff --git a/src/zenstore/include/zenstore/cache/cacherpc.h b/src/zenstore/include/zenstore/cache/cacherpc.h
index 104746aba..eb40befa0 100644
--- a/src/zenstore/include/zenstore/cache/cacherpc.h
+++ b/src/zenstore/include/zenstore/cache/cacherpc.h
@@ -4,8 +4,9 @@
#include <zencore/iobuffer.h>
#include <zencore/logging.h>
+#include <zenstore/cache/cache.h>
#include <zenstore/cache/cacheshared.h>
-#include <zenutil/cache/cache.h>
+#include <zenstore/cache/structuredcachestore.h>
#include <atomic>
#include <string_view>
@@ -28,7 +29,6 @@ class CidStore;
class DiskWriteBlocker;
class HttpStructuredCacheService;
class UpstreamCacheClient;
-class ZenCacheStore;
enum class CachePolicy : uint32_t;
enum class RpcAcceptOptions : uint16_t;
@@ -101,7 +101,7 @@ private:
CbPackage HandleRpcGetCacheValues(const CacheRequestContext& Context, CbObjectView BatchRequest);
CbPackage HandleRpcGetCacheChunks(const CacheRequestContext& Context, RpcAcceptOptions AcceptOptions, CbObjectView BatchRequest);
- PutStatus PutCacheRecord(PutRequestData& Request, const CbPackage* Package);
+ ZenCacheStore::PutResult PutCacheRecord(PutRequestData& Request, const CbPackage* Package);
/** HandleRpcGetCacheChunks Helper: Parse the Body object into RecordValue Requests and Value Requests. */
bool ParseGetCacheChunksRequest(std::string& Namespace,
diff --git a/src/zenstore/include/zenstore/cache/structuredcachestore.h b/src/zenstore/include/zenstore/cache/structuredcachestore.h
index c51d7312c..1ba469431 100644
--- a/src/zenstore/include/zenstore/cache/structuredcachestore.h
+++ b/src/zenstore/include/zenstore/cache/structuredcachestore.h
@@ -5,9 +5,9 @@
#include <zencore/compactbinary.h>
#include <zencore/iohash.h>
#include <zencore/stats.h>
+#include <zenstore/cache/cache.h>
#include <zenstore/cache/cachedisklayer.h>
#include <zenstore/gc.h>
-#include <zenutil/cache/cache.h>
#include <zenutil/statsreporter.h>
#include <atomic>
diff --git a/src/zenstore/include/zenstore/cache/upstreamcacheclient.h b/src/zenstore/include/zenstore/cache/upstreamcacheclient.h
index 152031c3a..2f3b6b0d7 100644
--- a/src/zenstore/include/zenstore/cache/upstreamcacheclient.h
+++ b/src/zenstore/include/zenstore/cache/upstreamcacheclient.h
@@ -8,7 +8,7 @@
#include <zencore/iohash.h>
#include <zencore/stats.h>
#include <zencore/zencore.h>
-#include <zenutil/cache/cache.h>
+#include <zenstore/cache/cache.h>
#include <functional>
#include <memory>
diff --git a/src/zenserver/projectstore/oplogreferencedset.h b/src/zenstore/include/zenstore/oplogreferencedset.h
index 297fd29d5..dcc156060 100644
--- a/src/zenserver/projectstore/oplogreferencedset.h
+++ b/src/zenstore/include/zenstore/oplogreferencedset.h
@@ -6,7 +6,10 @@
#include <optional>
#include <string_view>
-#include <unordered_set>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <tsl/robin_set.h>
+ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
@@ -24,8 +27,12 @@ class IoBuffer;
class OplogReferencedSet
{
public:
- void Emplace(Oid OplogId);
- bool Contains(Oid OplogId, std::string_view OplogKey);
+ inline bool Contains(const Oid& OplogId) const { return Set.contains(OplogId); }
+ static inline bool IsNonPackage(std::string_view OplogKey)
+ {
+ // A referencedset always includes all non-package keys
+ return OplogKey.empty() || !OplogKey.starts_with('/');
+ }
void Clear();
static std::optional<OplogReferencedSet> LoadFromChunk(const IoBuffer& ChunkData);
@@ -33,7 +40,7 @@ public:
static constexpr std::string_view ReferencedSetOplogKey = "ReferencedSet";
private:
- std::unordered_set<Oid> Set;
+ tsl::robin_set<Oid, Oid::Hasher> Set;
};
} // namespace zen
diff --git a/src/zenserver/projectstore/projectstore.h b/src/zenstore/include/zenstore/projectstore.h
index 2595d7198..258be5930 100644
--- a/src/zenserver/projectstore/projectstore.h
+++ b/src/zenstore/include/zenstore/projectstore.h
@@ -2,10 +2,11 @@
#pragma once
-#include <zencore/compactbinary.h>
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinarypackage.h>
+#include <zencore/compositebuffer.h>
#include <zencore/uid.h>
#include <zencore/xxhash.h>
-#include <zenhttp/httpserver.h>
#include <zenstore/gc.h>
ZEN_THIRD_PARTY_INCLUDES_START
@@ -13,39 +14,13 @@ ZEN_THIRD_PARTY_INCLUDES_START
ZEN_THIRD_PARTY_INCLUDES_END
#include <map>
-#include <unordered_map>
+#include <variant>
namespace zen {
-class CbPackage;
class CidStore;
class AuthMgr;
class ScrubContext;
-class JobQueue;
-class OpenProcessCache;
-
-enum class HttpResponseCode;
-
-struct OplogEntry
-{
- uint32_t OpLsn;
- uint32_t OpCoreOffset; // note: Multiple of alignment!
- uint32_t OpCoreSize;
- uint32_t OpCoreHash; // Used as checksum
- Oid OpKeyHash;
- uint32_t Reserved;
-
- inline bool IsTombstone() const { return OpCoreOffset == 0 && OpCoreSize == 0 && OpLsn == 0; }
- inline void MakeTombstone() { OpLsn = OpCoreOffset = OpCoreSize = OpCoreHash = Reserved = 0; }
-};
-
-struct OplogEntryAddress
-{
- uint64_t Offset;
- uint64_t Size;
-};
-
-static_assert(IsPow2(sizeof(OplogEntry)));
/** Project Store
@@ -68,23 +43,67 @@ public:
{
};
- ProjectStore(CidStore& Store,
- std::filesystem::path BasePath,
- GcManager& Gc,
- JobQueue& JobQueue,
- OpenProcessCache& InOpenProcessCache,
- const Configuration& Config);
+ ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, const Configuration& Config);
~ProjectStore();
- struct Project;
+ struct LogSequenceNumber
+ {
+ uint32_t Number = 0u;
- struct Oplog
+ operator bool() const { return Number != 0u; };
+ LogSequenceNumber() = default;
+ explicit LogSequenceNumber(size_t InNumber) : Number(uint32_t(InNumber)) {}
+ operator size_t() const { return Number; };
+ inline auto operator<=>(const LogSequenceNumber& Other) const = default;
+
+ struct Hasher
+ {
+ size_t operator()(const LogSequenceNumber& v) const { return std::hash<uint32_t>()(v.Number); }
+ };
+ };
+
+ template<class V>
+ using LsnMap = tsl::robin_map<LogSequenceNumber, V, LogSequenceNumber::Hasher>;
+
+ struct OplogEntryAddress
{
- Oplog(std::string_view Id,
- Project* Project,
+ uint32_t Offset; // note: Multiple of m_OpsAlign!
+ uint32_t Size;
+ };
+
+ struct OplogEntry
+ {
+ LogSequenceNumber OpLsn;
+ OplogEntryAddress OpCoreAddress;
+ uint32_t OpCoreHash; // Used as checksum
+ Oid OpKeyHash;
+ uint32_t Reserved;
+
+ inline bool IsTombstone() const { return OpCoreAddress.Offset == 0 && OpCoreAddress.Size == 0 && OpLsn.Number; }
+ inline void MakeTombstone()
+ {
+ OpLsn = {};
+ OpCoreAddress.Offset = OpCoreAddress.Size = OpCoreHash = Reserved = 0;
+ }
+ };
+
+ static_assert(IsPow2(sizeof(OplogEntry)));
+
+ struct Oplog : public RefCounted
+ {
+ enum class EMode
+ {
+ kBasicReadOnly,
+ kFull
+ };
+
+ Oplog(const LoggerRef& Log,
+ std::string_view ProjectIdentifier,
+ std::string_view Id,
CidStore& Store,
- std::filesystem::path BasePath,
- const std::filesystem::path& MarkerPath);
+ const std::filesystem::path& BasePath,
+ const std::filesystem::path& MarkerPath,
+ EMode State);
~Oplog();
[[nodiscard]] static bool ExistsAt(const std::filesystem::path& BasePath);
@@ -94,6 +113,7 @@ public:
void Write();
void Update(const std::filesystem::path& MarkerPath);
bool Reset();
+ bool CanUnload();
struct ChunkInfo
{
@@ -107,54 +127,47 @@ public:
int32_t Count = -1;
};
- std::vector<ChunkInfo> GetAllChunksInfo();
+ std::vector<ChunkInfo> GetAllChunksInfo(const std::filesystem::path& ProjectRootDir);
void IterateChunkMap(std::function<void(const Oid&, const IoHash& Hash)>&& Fn);
void IterateFileMap(std::function<void(const Oid&, const std::string_view& ServerPath, const std::string_view& ClientPath)>&& Fn);
void IterateOplog(std::function<void(CbObjectView)>&& Fn, const Paging& EntryPaging);
- void IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Fn);
- void IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Fn, const Paging& EntryPaging);
+ void IterateOplogWithKey(std::function<void(LogSequenceNumber, const Oid&, CbObjectView)>&& Fn);
+ void IterateOplogWithKey(std::function<void(LogSequenceNumber, const Oid&, CbObjectView)>&& Fn, const Paging& EntryPaging);
void IterateOplogLocked(std::function<void(CbObjectView)>&& Fn, const Paging& EntryPaging);
size_t GetOplogEntryCount() const;
std::optional<CbObject> GetOpByKey(const Oid& Key);
- std::optional<CbObject> GetOpByIndex(uint32_t Index);
- std::optional<uint32_t> GetOpIndexByKey(const Oid& Key);
-
- IoBuffer FindChunk(const Oid& ChunkId, uint64_t* OptOutModificationTag);
- IoBuffer GetChunkByRawHash(const IoHash& RawHash);
- bool IterateChunks(std::span<IoHash> RawHashes,
- bool IncludeModTag,
- const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback,
- WorkerThreadPool* OptionalWorkerPool,
- uint64_t LargeSizeLimit);
- bool IterateChunks(std::span<Oid> ChunkIds,
- bool IncludeModTag,
- const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback,
- WorkerThreadPool* OptionalWorkerPool,
- uint64_t LargeSizeLimit);
- inline static const uint32_t kInvalidOp = ~0u;
+ std::optional<CbObject> GetOpByIndex(LogSequenceNumber Index);
+ LogSequenceNumber GetOpIndexByKey(const Oid& Key);
+
+ IoBuffer FindChunk(const std::filesystem::path& ProjectRootDir, const Oid& ChunkId, uint64_t* OptOutModificationTag);
+ IoBuffer GetChunkByRawHash(const IoHash& RawHash);
+ bool IterateChunks(std::span<IoHash> RawHashes,
+ bool IncludeModTag,
+ const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback,
+ WorkerThreadPool* OptionalWorkerPool,
+ uint64_t LargeSizeLimit);
+ bool IterateChunks(const std::filesystem::path& ProjectRootDir,
+ std::span<Oid> ChunkIds,
+ bool IncludeModTag,
+ const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback,
+ WorkerThreadPool* OptionalWorkerPool,
+ uint64_t LargeSizeLimit);
/** Persist a new oplog entry
*
- * Returns the oplog LSN assigned to the new entry, or kInvalidOp if the entry is rejected
+ * Returns the oplog LSN assigned to the new entry, or an invalid number if the entry is rejected
*/
- uint32_t AppendNewOplogEntry(CbPackage Op);
-
- uint32_t AppendNewOplogEntry(CbObjectView Core);
- std::vector<uint32_t> AppendNewOplogEntries(std::span<CbObjectView> Cores);
-
- enum UpdateType
- {
- kUpdateNewEntry,
- kUpdateReplay
- };
+ LogSequenceNumber AppendNewOplogEntry(CbPackage Op);
+ LogSequenceNumber AppendNewOplogEntry(CbObjectView Core);
+ std::vector<LogSequenceNumber> AppendNewOplogEntries(std::span<CbObjectView> Cores);
const std::string& OplogId() const { return m_OplogId; }
const std::filesystem::path& TempPath() const { return m_TempPath; }
const std::filesystem::path& MarkerPath() const { return m_MarkerPath; }
- LoggerRef Log() { return m_OuterProject->Log(); }
+ LoggerRef Log() const { return m_Log; }
void Flush();
void Scrub(ScrubContext& Ctx);
static uint64_t TotalSize(const std::filesystem::path& BasePath);
@@ -163,14 +176,12 @@ public:
std::size_t OplogCount() const
{
RwLock::SharedLockScope _(m_OplogLock);
- return m_LatestOpMap.size();
+ return m_OpToPayloadOffsetMap.size();
}
void ResetState();
bool PrepareForDelete(std::filesystem::path& OutRemoveDirectory);
- void AddChunkMappings(const std::unordered_map<Oid, IoHash, Oid::Hasher>& ChunkMappings);
-
void EnableUpdateCapture();
void DisableUpdateCapture();
void CaptureAddedAttachments(std::span<const IoHash> AttachmentHashes);
@@ -186,8 +197,8 @@ public:
void GetAttachmentsLocked(std::vector<IoHash>& OutAttachments, bool StoreMetaDataOnDisk);
- Project* GetOuterProject() const { return m_OuterProject; }
- void CompactIfUnusedExceeds(bool DryRun, uint32_t CompactUnusedThreshold, std::string_view LogPrefix);
+ std::string_view GetOuterProjectIdentifier() const { return m_OuterProjectId; }
+ void CompactIfUnusedExceeds(bool DryRun, uint32_t CompactUnusedThreshold, std::string_view LogPrefix);
static std::optional<CbObject> ReadStateFile(const std::filesystem::path& BasePath, std::function<LoggerRef()>&& Log);
@@ -208,8 +219,8 @@ public:
struct ValidationResult
{
uint32_t OpCount = 0;
- uint32_t LSNLow = 0;
- uint32_t LSNHigh = 0;
+ LogSequenceNumber LSNLow;
+ LogSequenceNumber LSNHigh;
std::vector<std::pair<Oid, FileMapping>> MissingFiles;
std::vector<std::pair<Oid, ChunkMapping>> MissingChunks;
std::vector<std::pair<Oid, ChunkMapping>> MissingMetas;
@@ -222,7 +233,9 @@ public:
}
};
- ValidationResult Validate(std::atomic_bool& IsCancelledFlag, WorkerThreadPool* OptionalWorkerPool);
+ ValidationResult Validate(const std::filesystem::path& ProjectRootDir,
+ std::atomic_bool& IsCancelledFlag,
+ WorkerThreadPool* OptionalWorkerPool);
private:
struct FileMapEntry
@@ -234,31 +247,60 @@ public:
template<class V>
using OidMap = tsl::robin_map<Oid, V, Oid::Hasher>;
- Project* m_OuterProject = nullptr;
+ LoggerRef m_Log;
+ const std::string m_OuterProjectId;
const std::string m_OplogId;
CidStore& m_CidStore;
const std::filesystem::path m_BasePath;
std::filesystem::path m_MarkerPath;
- std::filesystem::path m_TempPath;
- std::filesystem::path m_MetaPath;
-
- mutable RwLock m_OplogLock;
- OidMap<IoHash> m_ChunkMap; // output data chunk id -> CAS address
- OidMap<IoHash> m_MetaMap; // meta chunk id -> CAS address
- OidMap<FileMapEntry> m_FileMap; // file id -> file map entry
- int32_t m_ManifestVersion; // File system manifest version
- tsl::robin_map<uint32_t, OplogEntryAddress> m_OpAddressMap; // Index LSN -> op data in ops blob file
- OidMap<uint32_t> m_LatestOpMap; // op key -> latest op LSN for key
- std::atomic<bool> m_MetaValid = false;
+ const std::filesystem::path m_TempPath;
+ const std::filesystem::path m_MetaPath;
+
+ const EMode m_Mode;
+
+ mutable RwLock m_OplogLock;
+ OidMap<IoHash> m_ChunkMap; // output data chunk id -> CAS address
+ OidMap<IoHash> m_MetaMap; // meta chunk id -> CAS address
+ OidMap<FileMapEntry> m_FileMap; // file id -> file map entry
+ int32_t m_ManifestVersion; // File system manifest version
+
+ struct PayloadIndex
+ {
+ uint32_t Index = std::numeric_limits<uint32_t>::max();
+
+ operator bool() const { return Index != std::numeric_limits<uint32_t>::max(); };
+ PayloadIndex() = default;
+ explicit PayloadIndex(size_t InIndex) : Index(uint32_t(InIndex)) {}
+ operator size_t() const { return Index; };
+ inline auto operator<=>(const PayloadIndex& Other) const = default;
+
+ struct Hasher
+ {
+ size_t operator()(const PayloadIndex& v) const { return std::hash<uint32_t>()(v.Index); }
+ };
+ };
+
+ struct OplogPayload
+ {
+ LogSequenceNumber Lsn;
+ OplogEntryAddress Address;
+ };
+
+ OidMap<PayloadIndex> m_OpToPayloadOffsetMap;
+ std::vector<OplogPayload> m_OpLogPayloads;
+ std::unique_ptr<LsnMap<PayloadIndex>> m_LsnToPayloadOffsetMap;
+
+ std::atomic<bool> m_MetaValid = false;
uint32_t m_UpdateCaptureRefCounter = 0;
- std::unique_ptr<std::vector<uint32_t>> m_CapturedLSNs;
+ std::unique_ptr<std::vector<Oid>> m_CapturedOps;
std::unique_ptr<std::vector<IoHash>> m_CapturedAttachments;
std::unordered_set<IoHash, IoHash::Hasher> m_PendingPrepOpAttachments;
GcClock::TimePoint m_PendingPrepOpAttachmentsRetainEnd;
RefPtr<OplogStorage> m_Storage;
uint64_t m_LogFlushPosition = 0;
+ bool m_IsLegacySnapshot = false;
RefPtr<OplogStorage> GetStorage();
@@ -267,6 +309,7 @@ public:
uint32_t GetUnusedSpacePercentLocked() const;
void WriteIndexSnapshot();
void ReadIndexSnapshot();
+ void RefreshLsnToPayloadOffsetMap(RwLock::ExclusiveLockScope&);
struct OplogEntryMapping
{
@@ -283,7 +326,9 @@ public:
*
* Returns the oplog LSN assigned to the new entry, or kInvalidOp if the entry is rejected
*/
- uint32_t RegisterOplogEntry(RwLock::ExclusiveLockScope& OplogLock, const OplogEntryMapping& OpMapping, const OplogEntry& OpEntry);
+ LogSequenceNumber RegisterOplogEntry(RwLock::ExclusiveLockScope& OplogLock,
+ const OplogEntryMapping& OpMapping,
+ const OplogEntry& OpEntry);
void AddFileMapping(const RwLock::ExclusiveLockScope& OplogLock,
const Oid& FileId,
@@ -293,11 +338,15 @@ public:
void AddChunkMapping(const RwLock::ExclusiveLockScope& OplogLock, const Oid& ChunkId, const IoHash& Hash);
void AddMetaMapping(const RwLock::ExclusiveLockScope& OplogLock, const Oid& ChunkId, const IoHash& Hash);
void Compact(RwLock::ExclusiveLockScope& Lock, bool DryRun, bool RetainLSNs, std::string_view LogPrefix);
- void IterateCapturedLSNsLocked(std::function<bool(const CbObjectView& UpdateOp)>&& Callback);
+ void IterateCapturedOpsLocked(std::function<bool(const Oid& Key, LogSequenceNumber LSN, const CbObjectView& UpdateOp)>&& Callback);
+ std::vector<PayloadIndex> GetSortedOpPayloadRangeLocked(
+ const Paging& EntryPaging,
+ tsl::robin_map<PayloadIndex, Oid, PayloadIndex::Hasher>* OutOptionalReverseKeyMap);
friend class ProjectStoreOplogReferenceChecker;
friend class ProjectStoreReferenceChecker;
friend class ProjectStoreOplogReferenceValidator;
+ friend struct OplogStorage;
};
struct Project : public RefCounted
@@ -308,8 +357,10 @@ public:
std::filesystem::path ProjectRootDir;
std::filesystem::path ProjectFilePath;
- Oplog* NewOplog(std::string_view OplogId, const std::filesystem::path& MarkerPath);
- Oplog* OpenOplog(std::string_view OplogId, bool AllowCompact, bool VerifyPathOnDisk);
+ Ref<Oplog> NewOplog(std::string_view OplogId, const std::filesystem::path& MarkerPath);
+ Ref<Oplog> OpenOplog(std::string_view OplogId, bool AllowCompact, bool VerifyPathOnDisk);
+ Ref<Oplog> ReadOplog(std::string_view OplogId);
+ bool TryUnloadOplog(std::string_view OplogId);
bool DeleteOplog(std::string_view OplogId);
bool RemoveOplog(std::string_view OplogId, std::filesystem::path& OutDeletePath);
void IterateOplogs(std::function<void(const RwLock::SharedLockScope&, const Oplog&)>&& Fn) const;
@@ -361,8 +412,7 @@ public:
ProjectStore* m_ProjectStore;
CidStore& m_CidStore;
mutable RwLock m_ProjectLock;
- std::map<std::string, std::unique_ptr<Oplog>> m_Oplogs;
- std::vector<std::unique_ptr<Oplog>> m_DeletedOplogs;
+ std::map<std::string, Ref<Oplog>> m_Oplogs;
std::filesystem::path m_OplogStoragePath;
mutable RwLock m_LastAccessTimesLock;
mutable tsl::robin_map<std::string, GcClock::Tick> m_LastAccessTimes;
@@ -416,79 +466,71 @@ public:
virtual std::vector<RwLock::SharedLockScope> LockState(GcCtx& Ctx) override;
- CbArray GetProjectsList();
- std::pair<HttpResponseCode, std::string> GetProjectFiles(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::unordered_set<std::string>& WantedFieldNames,
- CbObject& OutPayload);
- std::pair<HttpResponseCode, std::string> GetProjectChunkInfos(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::unordered_set<std::string>& WantedFieldNames,
- CbObject& OutPayload);
- std::pair<HttpResponseCode, std::string> GetChunkInfo(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view ChunkId,
- CbObject& OutPayload);
- std::pair<HttpResponseCode, std::string> GetChunkRange(const std::string_view ProjectId,
- const std::string_view OplogId,
- const Oid ChunkId,
- uint64_t Offset,
- uint64_t Size,
- ZenContentType AcceptType,
- CompositeBuffer& OutChunk,
- ZenContentType& OutContentType,
- uint64_t* OptionalInOutModificationTag);
- std::pair<HttpResponseCode, std::string> GetChunkRange(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view ChunkId,
- uint64_t Offset,
- uint64_t Size,
- ZenContentType AcceptType,
- CompositeBuffer& OutChunk,
- ZenContentType& OutContentType,
- uint64_t* OptionalInOutModificationTag);
- std::pair<HttpResponseCode, std::string> GetChunk(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view Cid,
- IoBuffer& OutChunk,
- uint64_t* OptionalInOutModificationTag);
-
- std::pair<HttpResponseCode, std::string> PutChunk(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view Cid,
- ZenContentType ContentType,
- IoBuffer&& Chunk);
-
- std::pair<HttpResponseCode, std::string> WriteOplog(const std::string_view ProjectId,
- const std::string_view OplogId,
- IoBuffer&& Payload,
- CbObject& OutResponse);
-
- std::pair<HttpResponseCode, std::string> ReadOplog(const std::string_view ProjectId,
- const std::string_view OplogId,
- const HttpServerRequest::QueryParams& Params,
- CbObject& OutResponse);
-
- std::pair<HttpResponseCode, std::string> GetChunks(const std::string_view ProjectId,
- const std::string_view OplogId,
- const CbObject& RequestObject,
- CbPackage& OutResponsePackage);
-
- bool Rpc(HttpServerRequest& HttpReq,
- const std::string_view ProjectId,
- const std::string_view OplogId,
- IoBuffer&& Payload,
- AuthMgr& AuthManager);
-
- std::pair<HttpResponseCode, std::string> Export(Ref<ProjectStore::Project> Project,
- ProjectStore::Oplog& Oplog,
- CbObjectView&& Params,
- AuthMgr& AuthManager);
-
- std::pair<HttpResponseCode, std::string> Import(ProjectStore::Project& Project,
- ProjectStore::Oplog& Oplog,
- CbObjectView&& Params,
- AuthMgr& AuthManager);
+ CbArray GetProjectsList();
+ static CbObject GetProjectFiles(LoggerRef InLog,
+ Project& Project,
+ Oplog& Oplog,
+ const std::unordered_set<std::string>& WantedFieldNames);
+
+ static CbObject GetProjectChunkInfos(LoggerRef InLog,
+ Project& Project,
+ Oplog& Oplog,
+ const std::unordered_set<std::string>& WantedFieldNames);
+ static CbObject GetChunkInfo(LoggerRef InLog, Project& Project, Oplog& Oplog, const Oid& ChunkId);
+ struct GetChunkRangeResult
+ {
+ enum class EError : uint8_t
+ {
+ Ok,
+ NotFound,
+ NotModified,
+ MalformedContent,
+ OutOfRange
+ };
+ EError Error = EError(-1);
+ std::string ErrorDescription;
+ CompositeBuffer Chunk = CompositeBuffer();
+ IoHash RawHash = IoHash::Zero;
+ uint64_t RawSize = 0;
+ ZenContentType ContentType = ZenContentType::kUnknownContentType;
+ };
+ static GetChunkRangeResult GetChunkRange(LoggerRef InLog,
+ Project& Project,
+ Oplog& Oplog,
+ const Oid& ChunkId,
+ uint64_t Offset,
+ uint64_t Size,
+ ZenContentType AcceptType,
+ uint64_t* OptionalInOutModificationTag);
+ IoBuffer GetChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash);
+
+ IoBuffer GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const Oid& ChunkId);
+
+ IoBuffer GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const IoHash& Cid);
+
+ bool PutChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash, IoBuffer&& Chunk);
+
+ struct ChunkRequest
+ {
+ uint64_t Offset = 0;
+ uint64_t Size = (uint64_t)-1;
+ std::variant<IoHash, Oid> Id;
+ std::optional<uint64_t> ModTag;
+ bool SkipData = false;
+ };
+ struct ChunkResult
+ {
+ bool Exists = false;
+ IoBuffer ChunkBuffer;
+ uint64_t ModTag = 0;
+ };
+ std::vector<ChunkResult> GetChunks(Project& Project, Oplog& Oplog, std::span<const ChunkRequest> Requests);
+
+ std::vector<ProjectStore::ChunkRequest> ParseChunksRequests(Project& Project, Oplog& Oplog, const CbObject& Cb);
+ CbPackage WriteChunksRequestResponse(Project& Project,
+ Oplog& Oplog,
+ std::vector<ChunkRequest>&& Requests,
+ std::vector<ChunkResult>&& Results);
bool AreDiskWritesAllowed() const;
@@ -500,8 +542,6 @@ private:
LoggerRef m_Log;
GcManager& m_Gc;
CidStore& m_CidStore;
- JobQueue& m_JobQueue;
- OpenProcessCache& m_OpenProcessCache;
std::filesystem::path m_ProjectBasePath;
const Configuration m_Config;
mutable RwLock m_ProjectsLock;
@@ -517,8 +557,27 @@ private:
friend class ProjectStoreReferenceChecker;
};
+Oid ComputeOpKey(const CbObjectView& Op);
+
Oid OpKeyStringAsOid(std::string_view OpKey);
+template<typename T>
+Oid
+OpKeyStringAsOid(std::string_view OpKey, T& TmpBuffer)
+{
+ using namespace std::literals;
+
+ CbObjectWriter Writer;
+ Writer << "key"sv << OpKey;
+ Writer.Finalize();
+ TmpBuffer.resize(Writer.GetSaveSize());
+ MutableMemoryView SaveBuffer(MutableMemoryView(TmpBuffer.data(), TmpBuffer.size()));
+
+ const Oid OpId = ComputeOpKey(Writer.Save(SaveBuffer).AsObjectView());
+
+ return OpId;
+}
+
void prj_forcelink();
} // namespace zen
diff --git a/src/zenserver/vfs/vfsimpl.h b/src/zenstore/include/zenstore/vfsimpl.h
index c33df100b..22ca07a27 100644
--- a/src/zenserver/vfs/vfsimpl.h
+++ b/src/zenstore/include/zenstore/vfsimpl.h
@@ -2,20 +2,20 @@
#pragma once
-#include "vfsservice.h"
-
-#include "projectstore/projectstore.h"
-
#include <zencore/logging.h>
#include <zenvfs/vfs.h>
-#if ZEN_WITH_VFS
-
-# include <memory>
-# include <unordered_map>
+#include <memory>
+#include <unordered_map>
namespace zen {
+#if ZEN_WITH_VFS
+
+class ProjectStore;
+class ZenCacheStore;
+struct VfsServiceImpl;
+
struct VfsOplogDataSource : public VfsTreeDataSource
{
VfsOplogDataSource(std::string_view ProjectId, std::string_view OplogId, Ref<ProjectStore> InProjectStore);
@@ -47,14 +47,14 @@ private:
struct VfsServiceDataSource : public VfsTreeDataSource
{
- VfsServiceDataSource(VfsService::Impl* VfsImpl) : m_VfsImpl(VfsImpl) {}
+ VfsServiceDataSource(VfsServiceImpl* VfsImpl) : m_VfsImpl(VfsImpl) {}
virtual void ReadNamedData(std::string_view Path, void* Buffer, uint64_t ByteOffset, uint64_t ByteCount) override;
virtual void ReadChunkData(const Oid& ChunkId, void* Buffer, uint64_t ByteOffset, uint64_t ByteCount) override;
virtual void PopulateDirectory(std::string NodePath, VfsTreeNode& DirNode) override;
private:
- VfsService::Impl* m_VfsImpl = nullptr;
+ VfsServiceImpl* m_VfsImpl = nullptr;
RwLock m_Lock;
std::unordered_map<std::string, Ref<VfsOplogDataSource>> m_OplogSourceMap;
@@ -64,12 +64,15 @@ private:
Ref<VfsCacheDataSource> GetCacheDataSource(std::string_view NamespaceId, std::string_view BucketId);
};
+#endif // ZEN_WITH_VFS
+
//////////////////////////////////////////////////////////////////////////
-struct VfsService::Impl
+struct VfsServiceImpl
{
- Impl();
- ~Impl();
+#if ZEN_WITH_VFS
+ VfsServiceImpl();
+ ~VfsServiceImpl();
void Mount(std::string_view MountPoint);
void Unmount();
@@ -94,8 +97,7 @@ private:
void VfsThread();
friend struct VfsServiceDataSource;
+#endif // ZEN_WITH_VFS
};
} // namespace zen
-
-#endif
diff --git a/src/zenserver/projectstore/oplogreferencedset.cpp b/src/zenstore/oplogreferencedset.cpp
index c6bfa0b98..f34b80b1a 100644
--- a/src/zenserver/projectstore/oplogreferencedset.cpp
+++ b/src/zenstore/oplogreferencedset.cpp
@@ -1,8 +1,8 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "oplogreferencedset.h"
+#include <zenstore/oplogreferencedset.h>
-#include "projectstore.h"
+#include <zenstore/projectstore.h>
#include <zencore/iobuffer.h>
#include <zencore/logging.h>
@@ -63,7 +63,7 @@ OplogReferencedSet::LoadFromChunk(const IoBuffer& ChunkData)
constexpr uint64_t MinSupportedVersion = 1;
if (Version < MinSupportedVersion)
{
- ZEN_INFO("ReferencedSet is below the minimum supported version, ignoring it. Version: {}, minimum version: {}.",
+ ZEN_WARN("ReferencedSet is below the minimum supported version, ignoring it. Version: {}, minimum version: {}.",
Version,
MinSupportedVersion);
return std::optional<OplogReferencedSet>();
@@ -72,12 +72,14 @@ OplogReferencedSet::LoadFromChunk(const IoBuffer& ChunkData)
// Parse the remaining lines after the leading comment block.
ChunkText.remove_prefix(FirstNonComment ? FirstNonComment - ChunkText.data() : ChunkText.length());
+ eastl::fixed_vector<uint8_t, 256> TmpBuffer;
+
OplogReferencedSet Result;
- ForEachStrTok(ChunkText, '\n', [&Result, &TrimWhitespace](std::string_view Line) {
+ ForEachStrTok(ChunkText, '\n', [&Result, &TrimWhitespace, &TmpBuffer](std::string_view Line) {
Line = AsciiSet::TrimSuffixWith(AsciiSet::TrimPrefixWith(Line, TrimWhitespace), TrimWhitespace);
if (!Line.empty() && !Line.starts_with('#'))
{
- Result.Emplace(OpKeyStringAsOid(Line));
+ Result.Set.emplace(OpKeyStringAsOid(Line, TmpBuffer));
}
return true;
});
@@ -85,26 +87,9 @@ OplogReferencedSet::LoadFromChunk(const IoBuffer& ChunkData)
}
void
-OplogReferencedSet::Emplace(Oid OplogId)
-{
- Set.emplace(OplogId);
-}
-
-void
OplogReferencedSet::Clear()
{
Set.clear();
}
-bool
-OplogReferencedSet::Contains(Oid OplogId, std::string_view OplogKey)
-{
- // A referencedset always includes all non-package keys
- if (OplogKey.empty() || !OplogKey.starts_with("/"))
- {
- return true;
- }
- return Set.contains(OplogId);
-}
-
} // namespace zen
diff --git a/src/zenserver/projectstore/projectstore.cpp b/src/zenstore/projectstore.cpp
index a5ab24cfb..8ae74c8cf 100644
--- a/src/zenserver/projectstore/projectstore.cpp
+++ b/src/zenstore/projectstore.cpp
@@ -1,39 +1,28 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "projectstore.h"
+#include <zenstore/projectstore.h>
#include <zencore/assertfmt.h>
-#include <zencore/compactbinarybuilder.h>
-#include <zencore/compactbinarypackage.h>
#include <zencore/compactbinaryutil.h>
#include <zencore/compactbinaryvalidation.h>
+#include <zencore/except.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
-#include <zencore/jobqueue.h>
#include <zencore/logging.h>
#include <zencore/memory/llm.h>
+#include <zencore/parallelwork.h>
#include <zencore/scopeguard.h>
#include <zencore/stream.h>
#include <zencore/timer.h>
#include <zencore/trace.h>
-#include <zenhttp/packageformat.h>
#include <zenstore/caslog.h>
#include <zenstore/cidstore.h>
#include <zenstore/scrubcontext.h>
-#include <zenutil/cache/rpcrecording.h>
-#include <zenutil/openprocesscache.h>
-#include <zenutil/parallelwork.h>
-#include <zenutil/referencemetadata.h>
#include <zenutil/workerpools.h>
-#include "buildsremoteprojectstore.h"
-#include "fileremoteprojectstore.h"
-#include "jupiterremoteprojectstore.h"
-#include "remoteprojectstore.h"
-#include "zenremoteprojectstore.h"
+#include "referencemetadata.h"
ZEN_THIRD_PARTY_INCLUDES_START
-#include <cpr/cpr.h>
#include <tsl/robin_set.h>
#include <xxh3.h>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -41,6 +30,8 @@ ZEN_THIRD_PARTY_INCLUDES_END
#if ZEN_WITH_TESTS
# include <zencore/testing.h>
# include <zencore/testutils.h>
+
+# include <unordered_map>
#endif // ZEN_WITH_TESTS
namespace zen {
@@ -140,278 +131,68 @@ namespace {
return CheckWriteTime < ReferenceWriteTime;
}
- struct CreateRemoteStoreResult
- {
- std::shared_ptr<RemoteProjectStore> Store;
- std::string Description;
- };
- CreateRemoteStoreResult CreateRemoteStore(CbObjectView Params,
- AuthMgr& AuthManager,
- size_t MaxBlockSize,
- size_t MaxChunkEmbedSize,
- const std::filesystem::path& TempFilePath)
- {
- ZEN_MEMSCOPE(GetProjectstoreTag());
-
- using namespace std::literals;
-
- std::shared_ptr<RemoteProjectStore> RemoteStore;
-
- if (CbObjectView File = Params["file"sv].AsObjectView(); File)
- {
- std::filesystem::path FolderPath(File["path"sv].AsString());
- if (FolderPath.empty())
- {
- return {nullptr, "Missing file path"};
- }
- std::string_view Name(File["name"sv].AsString());
- if (Name.empty())
- {
- return {nullptr, "Missing file name"};
- }
- std::string_view OptionalBaseName(File["basename"sv].AsString());
- bool ForceDisableBlocks = File["disableblocks"sv].AsBool(false);
- bool ForceEnableTempBlocks = File["enabletempblocks"sv].AsBool(false);
-
- FileRemoteStoreOptions Options = {RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunkEmbedSize = MaxChunkEmbedSize},
- FolderPath,
- std::string(Name),
- std::string(OptionalBaseName),
- ForceDisableBlocks,
- ForceEnableTempBlocks};
- RemoteStore = CreateFileRemoteStore(Options);
- }
-
- if (CbObjectView Cloud = Params["cloud"sv].AsObjectView(); Cloud)
- {
- std::string_view CloudServiceUrl = Cloud["url"sv].AsString();
- if (CloudServiceUrl.empty())
- {
- return {nullptr, "Missing service url"};
- }
-
- std::string Url = cpr::util::urlDecode(std::string(CloudServiceUrl));
- std::string_view Namespace = Cloud["namespace"sv].AsString();
- if (Namespace.empty())
- {
- return {nullptr, "Missing namespace"};
- }
- std::string_view Bucket = Cloud["bucket"sv].AsString();
- if (Bucket.empty())
- {
- return {nullptr, "Missing bucket"};
- }
- std::string_view OpenIdProvider = Cloud["openid-provider"sv].AsString();
- std::string AccessToken = std::string(Cloud["access-token"sv].AsString());
- if (AccessToken.empty())
- {
- std::string_view AccessTokenEnvVariable = Cloud["access-token-env"].AsString();
- if (!AccessTokenEnvVariable.empty())
- {
- AccessToken = GetEnvVariable(AccessTokenEnvVariable);
- }
- }
- std::filesystem::path OidcExePath;
- if (std::string_view OidcExePathString = Cloud["oidc-exe-path"].AsString(); !OidcExePathString.empty())
- {
- std::filesystem::path OidcExePathMaybe(OidcExePathString);
- if (!IsFile(OidcExePathMaybe))
- {
- ZEN_WARN("Path to OidcToken executable '{}' can not be reached by server", OidcExePathString);
- OidcExePath = std::move(OidcExePathMaybe);
- }
- }
- std::string_view KeyParam = Cloud["key"sv].AsString();
- if (KeyParam.empty())
- {
- return {nullptr, "Missing key"};
- }
- if (KeyParam.length() != IoHash::StringLength)
- {
- return {nullptr, "Invalid key"};
- }
- IoHash Key = IoHash::FromHexString(KeyParam);
- if (Key == IoHash::Zero)
- {
- return {nullptr, "Invalid key string"};
- }
- IoHash BaseKey = IoHash::Zero;
- std::string_view BaseKeyParam = Cloud["basekey"sv].AsString();
- if (!BaseKeyParam.empty())
- {
- if (BaseKeyParam.length() != IoHash::StringLength)
- {
- return {nullptr, "Invalid base key"};
- }
- BaseKey = IoHash::FromHexString(BaseKeyParam);
- if (BaseKey == IoHash::Zero)
- {
- return {nullptr, "Invalid base key string"};
- }
- }
-
- bool ForceDisableBlocks = Cloud["disableblocks"sv].AsBool(false);
- bool ForceDisableTempBlocks = Cloud["disabletempblocks"sv].AsBool(false);
- bool AssumeHttp2 = Cloud["assumehttp2"sv].AsBool(false);
-
- JupiterRemoteStoreOptions Options = {RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunkEmbedSize = MaxChunkEmbedSize},
- Url,
- std::string(Namespace),
- std::string(Bucket),
- Key,
- BaseKey,
- std::string(OpenIdProvider),
- AccessToken,
- AuthManager,
- OidcExePath,
- ForceDisableBlocks,
- ForceDisableTempBlocks,
- AssumeHttp2};
- RemoteStore = CreateJupiterRemoteStore(Options, TempFilePath, /*Quiet*/ false);
- }
-
- if (CbObjectView Zen = Params["zen"sv].AsObjectView(); Zen)
- {
- std::string_view Url = Zen["url"sv].AsString();
- std::string_view Project = Zen["project"sv].AsString();
- if (Project.empty())
- {
- return {nullptr, "Missing project"};
- }
- std::string_view Oplog = Zen["oplog"sv].AsString();
- if (Oplog.empty())
- {
- return {nullptr, "Missing oplog"};
- }
- ZenRemoteStoreOptions Options = {RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunkEmbedSize = MaxChunkEmbedSize},
- std::string(Url),
- std::string(Project),
- std::string(Oplog)};
- RemoteStore = CreateZenRemoteStore(Options, TempFilePath);
- }
-
- if (CbObjectView Builds = Params["builds"sv].AsObjectView(); Builds)
- {
- std::string_view BuildsServiceUrl = Builds["url"sv].AsString();
- if (BuildsServiceUrl.empty())
- {
- return {nullptr, "Missing service url"};
- }
-
- std::string Url = cpr::util::urlDecode(std::string(BuildsServiceUrl));
- std::string_view Namespace = Builds["namespace"sv].AsString();
- if (Namespace.empty())
- {
- return {nullptr, "Missing namespace"};
- }
- std::string_view Bucket = Builds["bucket"sv].AsString();
- if (Bucket.empty())
- {
- return {nullptr, "Missing bucket"};
- }
- std::string_view OpenIdProvider = Builds["openid-provider"sv].AsString();
- std::string AccessToken = std::string(Builds["access-token"sv].AsString());
- if (AccessToken.empty())
- {
- std::string_view AccessTokenEnvVariable = Builds["access-token-env"].AsString();
- if (!AccessTokenEnvVariable.empty())
- {
- AccessToken = GetEnvVariable(AccessTokenEnvVariable);
- }
- }
- std::filesystem::path OidcExePath;
- if (std::string_view OidcExePathString = Builds["oidc-exe-path"].AsString(); !OidcExePathString.empty())
- {
- std::filesystem::path OidcExePathMaybe(OidcExePathString);
- if (!IsFile(OidcExePathMaybe))
- {
- ZEN_WARN("Path to OidcToken executable '{}' can not be reached by server", OidcExePathString);
- OidcExePath = std::move(OidcExePathMaybe);
- }
- }
- std::string_view BuildIdParam = Builds["buildsid"sv].AsString();
- if (BuildIdParam.empty())
- {
- return {nullptr, "Missing build id"};
- }
- if (BuildIdParam.length() != Oid::StringLength)
- {
- return {nullptr, "Invalid build id"};
- }
- Oid BuildId = Oid::FromHexString(BuildIdParam);
- if (BuildId == Oid::Zero)
- {
- return {nullptr, "Invalid build id string"};
- }
-
- bool ForceDisableBlocks = Builds["disableblocks"sv].AsBool(false);
- bool ForceDisableTempBlocks = Builds["disabletempblocks"sv].AsBool(false);
- bool AssumeHttp2 = Builds["assumehttp2"sv].AsBool(false);
-
- MemoryView MetaDataSection = Builds["metadata"sv].AsBinaryView();
- IoBuffer MetaData(IoBuffer::Wrap, MetaDataSection.GetData(), MetaDataSection.GetSize());
-
- BuildsRemoteStoreOptions Options = {RemoteStoreOptions{.MaxBlockSize = MaxBlockSize, .MaxChunkEmbedSize = MaxChunkEmbedSize},
- Url,
- std::string(Namespace),
- std::string(Bucket),
- BuildId,
- std::string(OpenIdProvider),
- AccessToken,
- AuthManager,
- OidcExePath,
- ForceDisableBlocks,
- ForceDisableTempBlocks,
- AssumeHttp2,
- MetaData};
- RemoteStore = CreateJupiterBuildsRemoteStore(Options, TempFilePath, /*Quiet*/ false);
- }
-
- if (!RemoteStore)
- {
- return {nullptr, "Unknown remote store type"};
- }
-
- return {std::move(RemoteStore), ""};
- }
-
- std::pair<HttpResponseCode, std::string> ConvertResult(const RemoteProjectStore::Result& Result)
+ std::pair<int32_t, int32_t> GetPagedRange(int32_t TotalSize, const ProjectStore::Oplog::Paging& EntryPaging)
{
- if (Result.ErrorCode == 0)
- {
- return {HttpResponseCode::OK, Result.Text};
- }
- return {static_cast<HttpResponseCode>(Result.ErrorCode),
- Result.Reason.empty() ? Result.Text
- : Result.Text.empty() ? Result.Reason
- : fmt::format("{}: {}", Result.Reason, Result.Text)};
+ int32_t Start = std::clamp(EntryPaging.Start, 0, TotalSize);
+ int32_t End = EntryPaging.Count < 0 ? TotalSize : (Start + std::min<int32_t>(EntryPaging.Count, TotalSize - Start));
+ return {Start, End};
}
#pragma pack(push)
#pragma pack(1)
struct OplogIndexHeader
{
+ OplogIndexHeader() {}
+
+ struct BodyV1
+ {
+ uint64_t LogPosition = 0;
+ uint32_t LSNCount = 0;
+ uint64_t KeyCount = 0;
+ uint32_t OpAddressMapCount = 0;
+ uint32_t LatestOpMapCount = 0;
+ uint64_t ChunkMapCount = 0;
+ uint64_t MetaMapCount = 0;
+ uint64_t FileMapCount = 0;
+ };
+
+ struct BodyV2
+ {
+ uint64_t LogPosition = 0;
+ uint64_t KeyCount = 0;
+ uint32_t OpPayloadIndexCount = 0;
+ uint32_t OpPayloadCount = 0;
+ uint32_t ChunkMapCount = 0;
+ uint32_t MetaMapCount = 0;
+ uint32_t FileMapCount = 0;
+ uint32_t MaxLSN = 0;
+ uint32_t NextOpCoreOffset = 0; // note: Multiple of oplog data alignment!
+ uint32_t Reserved[2] = {0, 0};
+ };
+
static constexpr uint32_t ExpectedMagic = 0x7569647a; // 'zidx';
- static constexpr uint32_t CurrentVersion = 1;
+ static constexpr uint32_t Version1 = 1;
+ static constexpr uint32_t Version2 = 2;
+ static constexpr uint32_t CurrentVersion = Version2;
static constexpr uint64_t DataAlignment = 8;
- uint32_t Magic = ExpectedMagic;
- uint32_t Version = CurrentVersion;
- uint64_t LogPosition = 0;
- uint32_t LSNCount = 0;
- uint64_t KeyCount = 0;
- uint32_t OpAddressMapCount = 0;
- uint32_t LatestOpMapCount = 0;
- uint64_t ChunkMapCount = 0;
- uint64_t MetaMapCount = 0;
- uint64_t FileMapCount = 0;
- uint32_t Checksum = 0;
+ uint32_t Magic = ExpectedMagic;
+ uint32_t Version = CurrentVersion;
+
+ union
+ {
+ BodyV1 V1;
+ BodyV2 V2;
+ };
+
+ uint32_t Checksum = 0;
static uint32_t ComputeChecksum(const OplogIndexHeader& Header)
{
return XXH32(&Header.Magic, sizeof(OplogIndexHeader) - sizeof(uint32_t), 0xC0C0'BABA);
}
};
+
#pragma pack(pop)
static_assert(sizeof(OplogIndexHeader) == 64);
@@ -486,20 +267,19 @@ struct ProjectStore::OplogStorage : public RefCounted
~OplogStorage()
{
- ZEN_INFO("oplog '{}/{}': closing oplog storage at {}",
- m_OwnerOplog->GetOuterProject()->Identifier,
- m_OwnerOplog->OplogId(),
- m_OplogStoragePath);
+ ZEN_DEBUG("oplog '{}/{}': closing oplog storage at {}",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ m_OplogStoragePath);
try
{
- Flush();
m_Oplog.Close();
m_OpBlobs.Close();
}
catch (const std::exception& Ex)
{
ZEN_WARN("oplog '{}/{}': flushing oplog at '{}' failed. Reason: '{}'",
- m_OwnerOplog->GetOuterProject()->Identifier,
+ m_OwnerOplog->GetOuterProjectIdentifier(),
m_OwnerOplog->OplogId(),
m_OplogStoragePath,
Ex.what());
@@ -545,33 +325,63 @@ struct ProjectStore::OplogStorage : public RefCounted
m_NextOpsOffset = RoundUp((NextOpFileOffset.Offset * m_OpsAlign) + NextOpFileOffset.Size, m_OpsAlign);
}
- void Open(bool IsCreate)
+ void SetMaxLSNAndNextOpOffset(uint32_t MaxLSN, uint32_t NextOpOffset)
+ {
+ m_MaxLsn.store(MaxLSN);
+ m_NextOpsOffset = NextOpOffset * m_OpsAlign;
+ }
+
+ uint32_t GetNextOpOffset() const { return gsl::narrow<uint32_t>(m_NextOpsOffset / m_OpsAlign); }
+
+ enum EMode
+ {
+ Create,
+ Read,
+ Write
+ };
+
+ void Open(EMode InMode)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("Store::OplogStorage::Open");
- if (IsCreate)
+ CasLogFile::Mode LogMode = CasLogFile::Mode::kRead;
+ BasicFile::Mode BlobsMode = BasicFile::Mode::kRead;
+
+ if (InMode == EMode::Create)
{
- ZEN_INFO("oplog '{}/{}': initializing storage at '{}'",
- m_OwnerOplog->GetOuterProject()->Identifier,
- m_OwnerOplog->OplogId(),
- m_OplogStoragePath);
+ ZEN_DEBUG("oplog '{}/{}': initializing storage at '{}'",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ m_OplogStoragePath);
DeleteDirectories(m_OplogStoragePath);
CreateDirectories(m_OplogStoragePath);
+
+ LogMode = CasLogFile::Mode::kTruncate;
+ BlobsMode = BasicFile::Mode::kTruncate;
}
- else
+ else if (InMode == EMode::Write)
{
- ZEN_INFO("oplog '{}/{}': opening storage at '{}'",
- m_OwnerOplog->GetOuterProject()->Identifier,
- m_OwnerOplog->OplogId(),
- m_OplogStoragePath);
+ LogMode = CasLogFile::Mode::kWrite;
+ BlobsMode = BasicFile::Mode::kWrite;
+ ZEN_DEBUG("oplog '{}/{}': opening storage at '{}'",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ m_OplogStoragePath);
+ }
+ else if (InMode == EMode::Read)
+ {
+ ZEN_DEBUG("oplog '{}/{}': opening read only storage at '{}'",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ m_OplogStoragePath);
}
- m_Oplog.Open(GetLogPath(m_OplogStoragePath), IsCreate ? CasLogFile::Mode::kTruncate : CasLogFile::Mode::kWrite);
+ m_Oplog.Open(GetLogPath(m_OplogStoragePath), LogMode);
m_Oplog.Initialize();
- m_OpBlobs.Open(GetBlobsPath(m_OplogStoragePath), IsCreate ? BasicFile::Mode::kTruncate : BasicFile::Mode::kWrite);
+ m_OpBlobs.Open(GetBlobsPath(m_OplogStoragePath), BlobsMode);
ZEN_ASSERT(IsPow2(m_OpsAlign));
ZEN_ASSERT(!(m_NextOpsOffset & (m_OpsAlign - 1)));
@@ -581,41 +391,43 @@ struct ProjectStore::OplogStorage : public RefCounted
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- const uint64_t OpFileOffset = LogEntry.OpCoreOffset * m_OpsAlign;
- const MemoryView OpBufferView = OpBlobsBuffer.MakeView(LogEntry.OpCoreSize, OpFileOffset);
- if (OpBufferView.GetSize() == LogEntry.OpCoreSize)
+ const uint64_t OpFileOffset = LogEntry.OpCoreAddress.Offset * m_OpsAlign;
+ const MemoryView OpBufferView = OpBlobsBuffer.MakeView(LogEntry.OpCoreAddress.Size, OpFileOffset);
+ if (OpBufferView.GetSize() == LogEntry.OpCoreAddress.Size)
{
return IoBuffer(IoBuffer::Wrap, OpBufferView.GetData(), OpBufferView.GetSize());
}
else
{
- IoBuffer OpBuffer(LogEntry.OpCoreSize);
- OpBlobsBuffer.Read((void*)OpBuffer.Data(), LogEntry.OpCoreSize, OpFileOffset);
+ IoBuffer OpBuffer(LogEntry.OpCoreAddress.Size);
+ OpBlobsBuffer.Read((void*)OpBuffer.Data(), LogEntry.OpCoreAddress.Size, OpFileOffset);
return OpBuffer;
}
}
- uint64_t GetEffectiveBlobsSize(std::span<const OplogEntryAddress> Addresses) const
+ uint64_t GetEffectiveBlobsSize(std::span<const Oplog::OplogPayload> Addresses) const
{
uint64_t EffectiveSize = 0;
- for (const OplogEntryAddress& Address : Addresses)
+ for (const Oplog::OplogPayload& OpPayload : Addresses)
{
- EffectiveSize += RoundUp(Address.Size, m_OpsAlign);
+ EffectiveSize += RoundUp(OpPayload.Address.Size, m_OpsAlign);
}
return EffectiveSize;
}
- void Compact(
- std::span<const uint32_t> LSNs,
- std::function<void(const Oid& OpKeyHash, uint32_t OldLSN, uint32_t NewLSN, const OplogEntryAddress& NewAddress)>&& Callback,
- bool RetainLSNs,
- bool DryRun)
+ void Compact(std::span<const ProjectStore::LogSequenceNumber> LSNs,
+ std::function<void(const Oid& OpKeyHash,
+ ProjectStore::LogSequenceNumber OldLSN,
+ ProjectStore::LogSequenceNumber NewLSN,
+ const OplogEntryAddress& NewAddress)>&& Callback,
+ bool RetainLSNs,
+ bool DryRun)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("Store::OplogStorage::Compact");
ZEN_INFO("oplog '{}/{}': compacting at '{}'",
- m_OwnerOplog->GetOuterProject()->Identifier,
+ m_OwnerOplog->GetOuterProjectIdentifier(),
m_OwnerOplog->OplogId(),
m_OplogStoragePath);
@@ -643,9 +455,9 @@ struct ProjectStore::OplogStorage : public RefCounted
std::vector<OplogEntry> Ops;
Ops.reserve(LSNs.size());
- tsl::robin_map<uint32_t, size_t> LSNToIndex;
+ ProjectStore::LsnMap<size_t> LSNToIndex;
LSNToIndex.reserve(LSNs.size());
- for (uint32_t LSN : LSNs)
+ for (ProjectStore::LogSequenceNumber LSN : LSNs)
{
LSNToIndex[LSN] = (size_t)-1;
}
@@ -671,10 +483,10 @@ struct ProjectStore::OplogStorage : public RefCounted
SkipEntryCount);
std::sort(Ops.begin(), Ops.end(), [&](const OplogEntry& Lhs, const OplogEntry& Rhs) {
- return Lhs.OpCoreOffset < Rhs.OpCoreOffset;
+ return Lhs.OpCoreAddress.Offset < Rhs.OpCoreAddress.Offset;
});
- std::vector<uint32_t> OldLSNs;
+ std::vector<ProjectStore::LogSequenceNumber> OldLSNs;
OldLSNs.reserve(Ops.size());
uint64_t OpWriteOffset = 0;
@@ -690,15 +502,15 @@ struct ProjectStore::OplogStorage : public RefCounted
IoBuffer OpBuffer = GetOpBuffer(OldBlobsBuffer, LogEntry);
if (RetainLSNs)
{
- MaxLSN = Max(MaxLSN, LogEntry.OpLsn);
+ MaxLSN = Max(MaxLSN, LogEntry.OpLsn.Number);
}
else
{
- LogEntry.OpLsn = ++MaxLSN;
+ LogEntry.OpLsn = ProjectStore::LogSequenceNumber(++MaxLSN);
}
- LogEntry.OpCoreOffset = gsl::narrow<uint32_t>(OpWriteOffset / m_OpsAlign);
- NewOpBlobsBuffer.Write(OpBuffer.GetData(), LogEntry.OpCoreSize, OpWriteOffset);
- OpWriteOffset = RoundUp((LogEntry.OpCoreOffset * m_OpsAlign) + LogEntry.OpCoreSize, m_OpsAlign);
+ LogEntry.OpCoreAddress.Offset = gsl::narrow<uint32_t>(OpWriteOffset / m_OpsAlign);
+ NewOpBlobsBuffer.Write(OpBuffer.GetData(), LogEntry.OpCoreAddress.Size, OpWriteOffset);
+ OpWriteOffset = RoundUp((LogEntry.OpCoreAddress.Offset * m_OpsAlign) + LogEntry.OpCoreAddress.Size, m_OpsAlign);
}
Oplog.Append(Ops);
}
@@ -746,14 +558,11 @@ struct ProjectStore::OplogStorage : public RefCounted
for (size_t Index = 0; Index < Ops.size(); Index++)
{
const OplogEntry& LogEntry = Ops[Index];
- Callback(LogEntry.OpKeyHash,
- OldLSNs[Index],
- LogEntry.OpLsn,
- OplogEntryAddress{.Offset = LogEntry.OpCoreOffset, .Size = LogEntry.OpCoreSize});
+ Callback(LogEntry.OpKeyHash, OldLSNs[Index], LogEntry.OpLsn, LogEntry.OpCoreAddress);
}
ZEN_INFO("oplog '{}/{}': compact completed in {} - Max LSN# {}, New size: {}, old size {}.",
- m_OwnerOplog->GetOuterProject()->Identifier,
+ m_OwnerOplog->GetOuterProjectIdentifier(),
m_OwnerOplog->OplogId(),
NiceTimeSpanMs(Timer.GetElapsedTimeMs()),
m_MaxLsn.load(),
@@ -783,10 +592,10 @@ struct ProjectStore::OplogStorage : public RefCounted
std::filesystem::path GetBlobsPath() const { return GetBlobsPath(m_OplogStoragePath); }
- void ReplayLog(std::function<void(CbObjectView, const OplogEntry&)>&& Handler, uint64_t SkipEntryCount = 0)
+ void ReadOplogEntriesFromLog(std::vector<OplogEntry>& OutOpLogEntries, uint64_t& OutInvalidEntries, uint64_t SkipEntryCount)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::OplogStorage::ReplayLog");
+ ZEN_TRACE_CPU("Store::OplogStorage::ReadOplogEntriesFromLog");
if (m_Oplog.GetLogCount() == SkipEntryCount)
{
@@ -797,11 +606,12 @@ struct ProjectStore::OplogStorage : public RefCounted
uint64_t OpsBlockSize = m_OpBlobs.FileSize();
- std::vector<OplogEntry> OpLogEntries;
- uint64_t InvalidEntries = 0;
-
{
tsl::robin_map<Oid, size_t, Oid::Hasher> LatestKeys;
+ if (m_Oplog.GetLogCount() > SkipEntryCount)
+ {
+ LatestKeys.reserve(m_Oplog.GetLogCount() - SkipEntryCount);
+ }
m_Oplog.Replay(
[&](const OplogEntry& LogEntry) {
@@ -810,34 +620,34 @@ struct ProjectStore::OplogStorage : public RefCounted
if (auto It = LatestKeys.find(LogEntry.OpKeyHash); It == LatestKeys.end())
{
ZEN_SCOPED_WARN("found tombstone referencing unknown key {}", LogEntry.OpKeyHash);
- ++InvalidEntries;
+ ++OutInvalidEntries;
return;
}
}
- else if (LogEntry.OpCoreSize == 0)
+ else if (LogEntry.OpCoreAddress.Size == 0)
{
ZEN_SCOPED_WARN("skipping zero size op {}", LogEntry.OpKeyHash);
- ++InvalidEntries;
+ ++OutInvalidEntries;
return;
}
- else if (LogEntry.OpLsn == 0)
+ else if (LogEntry.OpLsn.Number == 0)
{
ZEN_SCOPED_WARN("skipping zero lsn op {}", LogEntry.OpKeyHash);
- ++InvalidEntries;
+ ++OutInvalidEntries;
return;
}
- const uint64_t OpFileOffset = LogEntry.OpCoreOffset * m_OpsAlign;
- if ((OpFileOffset + LogEntry.OpCoreSize) > OpsBlockSize)
+ const uint64_t OpFileOffset = LogEntry.OpCoreAddress.Offset * m_OpsAlign;
+ if ((OpFileOffset + LogEntry.OpCoreAddress.Size) > OpsBlockSize)
{
ZEN_SCOPED_WARN("skipping out of bounds op {}", LogEntry.OpKeyHash);
- ++InvalidEntries;
+ ++OutInvalidEntries;
return;
}
if (auto It = LatestKeys.find(LogEntry.OpKeyHash); It != LatestKeys.end())
{
- OplogEntry& Entry = OpLogEntries[It->second];
+ OplogEntry& Entry = OutOpLogEntries[It->second];
if (LogEntry.IsTombstone() && Entry.IsTombstone())
{
@@ -848,17 +658,31 @@ struct ProjectStore::OplogStorage : public RefCounted
}
else
{
- const size_t OpIndex = OpLogEntries.size();
+ const size_t OpIndex = OutOpLogEntries.size();
LatestKeys[LogEntry.OpKeyHash] = OpIndex;
- OpLogEntries.push_back(LogEntry);
+ OutOpLogEntries.push_back(LogEntry);
}
},
SkipEntryCount);
}
+ }
+
+ void ReplayLog(std::function<void(CbObjectView, const OplogEntry&)>&& Handler, uint64_t SkipEntryCount = 0)
+ {
+ ZEN_MEMSCOPE(GetProjectstoreTag());
+ ZEN_TRACE_CPU("Store::OplogStorage::ReplayLog");
+
+ if (m_Oplog.GetLogCount() == SkipEntryCount)
+ {
+ return;
+ }
- std::sort(OpLogEntries.begin(), OpLogEntries.end(), [&](const OplogEntry& Lhs, const OplogEntry& Rhs) {
- return Lhs.OpCoreOffset < Rhs.OpCoreOffset;
- });
+ Stopwatch Timer;
+
+ std::vector<OplogEntry> OpLogEntries;
+ uint64_t InvalidEntries = 0;
+
+ ReadOplogEntriesFromLog(OpLogEntries, InvalidEntries, SkipEntryCount);
uint64_t TombstoneEntries = 0;
@@ -867,6 +691,10 @@ struct ProjectStore::OplogStorage : public RefCounted
uint32_t MaxOpLsn = m_MaxLsn;
uint64_t NextOpFileOffset = m_NextOpsOffset;
+ std::sort(OpLogEntries.begin(), OpLogEntries.end(), [&](const OplogEntry& Lhs, const OplogEntry& Rhs) {
+ return Lhs.OpCoreAddress.Offset < Rhs.OpCoreAddress.Offset;
+ });
+
for (const OplogEntry& LogEntry : OpLogEntries)
{
if (LogEntry.IsTombstone())
@@ -880,12 +708,12 @@ struct ProjectStore::OplogStorage : public RefCounted
// Verify checksum, ignore op data if incorrect
const uint32_t ExpectedOpCoreHash = LogEntry.OpCoreHash;
- const uint32_t OpCoreHash = uint32_t(XXH3_64bits(OpBuffer.GetData(), LogEntry.OpCoreSize) & 0xffffFFFF);
+ const uint32_t OpCoreHash = uint32_t(XXH3_64bits(OpBuffer.GetData(), LogEntry.OpCoreAddress.Size) & 0xffffFFFF);
if (OpCoreHash != ExpectedOpCoreHash)
{
ZEN_WARN("oplog '{}/{}': skipping bad checksum op - {}. Expected: {}, found: {}",
- m_OwnerOplog->GetOuterProject()->Identifier,
+ m_OwnerOplog->GetOuterProjectIdentifier(),
m_OwnerOplog->OplogId(),
LogEntry.OpKeyHash,
ExpectedOpCoreHash,
@@ -895,17 +723,31 @@ struct ProjectStore::OplogStorage : public RefCounted
Err != CbValidateError::None)
{
ZEN_WARN("oplog '{}/{}': skipping invalid format op - {}. Error: '{}'",
- m_OwnerOplog->GetOuterProject()->Identifier,
+ m_OwnerOplog->GetOuterProjectIdentifier(),
m_OwnerOplog->OplogId(),
LogEntry.OpKeyHash,
ToString(Err));
}
else
{
- Handler(CbObjectView(OpBuffer.GetData()), LogEntry);
- MaxOpLsn = Max(MaxOpLsn, LogEntry.OpLsn);
- const uint64_t EntryNextOpFileOffset = RoundUp((LogEntry.OpCoreOffset * m_OpsAlign) + LogEntry.OpCoreSize, m_OpsAlign);
- NextOpFileOffset = Max(NextOpFileOffset, EntryNextOpFileOffset);
+ CbObjectView OpView(OpBuffer.GetData());
+ if (OpView.GetSize() != OpBuffer.GetSize())
+ {
+ ZEN_WARN("oplog '{}/{}': skipping invalid format op - {}. Object payload size {} does not match op data size {}",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ LogEntry.OpKeyHash,
+ OpView.GetSize(),
+ OpBuffer.GetSize());
+ }
+ else
+ {
+ Handler(OpView, LogEntry);
+ MaxOpLsn = Max(MaxOpLsn, LogEntry.OpLsn.Number);
+ const uint64_t EntryNextOpFileOffset =
+ RoundUp((LogEntry.OpCoreAddress.Offset * m_OpsAlign) + LogEntry.OpCoreAddress.Size, m_OpsAlign);
+ NextOpFileOffset = Max(NextOpFileOffset, EntryNextOpFileOffset);
+ }
}
}
}
@@ -914,7 +756,7 @@ struct ProjectStore::OplogStorage : public RefCounted
m_NextOpsOffset = NextOpFileOffset;
ZEN_INFO("oplog '{}/{}': replay from '{}' completed in {} - Max LSN# {}, Next offset: {}, {} tombstones, {} invalid entries",
- m_OwnerOplog->GetOuterProject()->Identifier,
+ m_OwnerOplog->GetOuterProjectIdentifier(),
m_OwnerOplog->OplogId(),
m_OplogStoragePath,
NiceTimeSpanMs(Timer.GetElapsedTimeMs()),
@@ -924,25 +766,80 @@ struct ProjectStore::OplogStorage : public RefCounted
InvalidEntries);
}
- void ReplayLogEntries(const std::span<OplogEntryAddress> Entries, std::function<void(CbObjectView)>&& Handler)
+ void ReplayLogEntries(const std::span<const Oplog::OplogPayload> Entries,
+ const std::span<const Oplog::PayloadIndex> Order,
+ std::function<void(LogSequenceNumber Lsn, CbObjectView)>&& Handler)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("Store::OplogStorage::ReplayLogEntries");
BasicFileBuffer OpBlobsBuffer(m_OpBlobs, 65536);
- for (const OplogEntryAddress& Entry : Entries)
+ for (ProjectStore::Oplog::PayloadIndex EntryOffset : Order)
{
- const uint64_t OpFileOffset = Entry.Offset * m_OpsAlign;
- MemoryView OpBufferView = OpBlobsBuffer.MakeView(Entry.Size, OpFileOffset);
- if (OpBufferView.GetSize() == Entry.Size)
+ const Oplog::OplogPayload& Entry = Entries[EntryOffset];
+
+ const uint64_t OpFileOffset = Entry.Address.Offset * m_OpsAlign;
+ MemoryView OpBufferView = OpBlobsBuffer.MakeView(Entry.Address.Size, OpFileOffset);
+ if (OpBufferView.GetSize() == Entry.Address.Size)
{
- Handler(CbObjectView(OpBufferView.GetData()));
- continue;
+ if (CbValidateError Error = ValidateCompactBinary(OpBufferView, CbValidateMode::Default); Error == CbValidateError::None)
+ {
+ CbObjectView OpView(OpBufferView.GetData());
+ if (OpView.GetSize() != OpBufferView.GetSize())
+ {
+ ZEN_WARN("oplog '{}/{}': skipping invalid format op - {}. Object payload size {} does not match op data size {}",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ Entry.Lsn.Number,
+ OpView.GetSize(),
+ OpBufferView.GetSize());
+ }
+ else
+ {
+ Handler(Entry.Lsn, OpView);
+ }
+ }
+ else
+ {
+ ZEN_WARN("oplog '{}/{}': skipping invalid format op - {}. Validation error: {}",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ Entry.Lsn.Number,
+ ToString(Error));
+ }
+ }
+ else
+ {
+ IoBuffer OpBuffer(Entry.Address.Size);
+ OpBlobsBuffer.Read((void*)OpBuffer.Data(), Entry.Address.Size, OpFileOffset);
+ OpBufferView = OpBuffer.GetView();
+ if (CbValidateError Error = ValidateCompactBinary(OpBufferView, CbValidateMode::Default); Error == CbValidateError::None)
+ {
+ CbObjectView OpView(OpBuffer.Data());
+ if (OpView.GetSize() != OpBuffer.GetSize())
+ {
+ ZEN_WARN("oplog '{}/{}': skipping invalid format op - {}. Object payload size {} does not match op data size {}",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ Entry.Lsn.Number,
+ OpView.GetSize(),
+ OpBuffer.GetSize());
+ }
+ else
+ {
+ Handler(Entry.Lsn, OpView);
+ }
+ }
+ else
+ {
+ ZEN_WARN("oplog '{}/{}': skipping invalid format op - {}. Validation error: {}",
+ m_OwnerOplog->GetOuterProjectIdentifier(),
+ m_OwnerOplog->OplogId(),
+ Entry.Lsn.Number,
+ ToString(Error));
+ }
}
- IoBuffer OpBuffer(Entry.Size);
- OpBlobsBuffer.Read((void*)OpBuffer.Data(), Entry.Size, OpFileOffset);
- Handler(CbObjectView(OpBuffer.Data()));
}
}
@@ -993,8 +890,8 @@ struct ProjectStore::OplogStorage : public RefCounted
RwLock::ExclusiveLockScope Lock(m_RwLock);
const uint64_t WriteOffset = m_NextOpsOffset;
- const uint32_t OpLsn = ++m_MaxLsn;
- if (OpLsn == std::numeric_limits<uint32_t>::max())
+ const LogSequenceNumber OpLsn(++m_MaxLsn);
+ if (!OpLsn.Number)
{
ZEN_ERROR("Oplog count has exceeded available range for oplog {}", m_OwnerOplog->OplogId());
throw std::runtime_error(fmt::format("Oplog count has exceeded available range for oplog {}", m_OwnerOplog->OplogId()));
@@ -1004,11 +901,10 @@ struct ProjectStore::OplogStorage : public RefCounted
ZEN_ASSERT(IsMultipleOf(WriteOffset, m_OpsAlign));
- OplogEntry Entry = {.OpLsn = OpLsn,
- .OpCoreOffset = gsl::narrow_cast<uint32_t>(WriteOffset / m_OpsAlign),
- .OpCoreSize = uint32_t(WriteSize),
- .OpCoreHash = OpData.OpCoreHash,
- .OpKeyHash = OpData.KeyHash};
+ OplogEntry Entry = {.OpLsn = OpLsn,
+ .OpCoreAddress = {.Offset = gsl::narrow_cast<uint32_t>(WriteOffset / m_OpsAlign), .Size = uint32_t(WriteSize)},
+ .OpCoreHash = OpData.OpCoreHash,
+ .OpKeyHash = OpData.KeyHash};
m_OpBlobs.Write(OpData.Buffer.GetData(), WriteSize, WriteOffset);
m_Oplog.Append(Entry);
@@ -1023,7 +919,7 @@ struct ProjectStore::OplogStorage : public RefCounted
size_t OpCount = Ops.size();
std::vector<std::pair<uint64_t, uint64_t>> OffsetAndSizes;
- std::vector<uint32_t> OpLsns;
+ std::vector<LogSequenceNumber> OpLsns;
OffsetAndSizes.resize(OpCount);
OpLsns.resize(OpCount);
@@ -1042,8 +938,8 @@ struct ProjectStore::OplogStorage : public RefCounted
for (size_t OpIndex = 0; OpIndex < OpCount; OpIndex++)
{
OffsetAndSizes[OpIndex].first = WriteOffset - WriteStart;
- OpLsns[OpIndex] = ++m_MaxLsn;
- if (OpLsns[OpIndex] == std::numeric_limits<uint32_t>::max())
+ OpLsns[OpIndex] = LogSequenceNumber(++m_MaxLsn);
+ if (!OpLsns[OpIndex])
{
ZEN_ERROR("Oplog count has exceeded available range for oplog {}", m_OwnerOplog->OplogId());
throw std::runtime_error(fmt::format("Oplog count has exceeded available range for oplog {}", m_OwnerOplog->OplogId()));
@@ -1062,11 +958,12 @@ struct ProjectStore::OplogStorage : public RefCounted
{
MutableMemoryView WriteBufferView = WriteBuffer.GetMutableView().RightChop(OffsetAndSizes[OpIndex].first);
WriteBufferView.CopyFrom(Ops[OpIndex].Buffer);
- Entries[OpIndex] = {.OpLsn = OpLsns[OpIndex],
- .OpCoreOffset = gsl::narrow_cast<uint32_t>((WriteStart + OffsetAndSizes[OpIndex].first) / m_OpsAlign),
- .OpCoreSize = uint32_t(OffsetAndSizes[OpIndex].second),
- .OpCoreHash = Ops[OpIndex].OpCoreHash,
- .OpKeyHash = Ops[OpIndex].KeyHash};
+ Entries[OpIndex] = {
+ .OpLsn = OpLsns[OpIndex],
+ .OpCoreAddress = {.Offset = gsl::narrow_cast<uint32_t>((WriteStart + OffsetAndSizes[OpIndex].first) / m_OpsAlign),
+ .Size = uint32_t(OffsetAndSizes[OpIndex].second)},
+ .OpCoreHash = Ops[OpIndex].OpCoreHash,
+ .OpKeyHash = Ops[OpIndex].KeyHash};
}
m_OpBlobs.Write(WriteBuffer.GetData(), WriteBuffer.GetSize(), WriteStart);
@@ -1105,16 +1002,22 @@ private:
//////////////////////////////////////////////////////////////////////////
-ProjectStore::Oplog::Oplog(std::string_view Id,
- Project* Project,
+ProjectStore::Oplog::Oplog(const LoggerRef& InLog,
+ std::string_view ProjectIdentifier,
+ std::string_view Id,
CidStore& Store,
- std::filesystem::path BasePath,
- const std::filesystem::path& MarkerPath)
-: m_OuterProject(Project)
+ const std::filesystem::path& BasePath,
+ const std::filesystem::path& MarkerPath,
+ EMode State)
+: m_Log(InLog)
+, m_OuterProjectId(ProjectIdentifier)
, m_OplogId(Id)
, m_CidStore(Store)
, m_BasePath(BasePath)
, m_MarkerPath(MarkerPath)
+, m_TempPath(m_BasePath / std::string_view("temp"))
+, m_MetaPath(m_BasePath / std::string_view("ops.meta"))
+, m_Mode(State)
, m_MetaValid(false)
, m_PendingPrepOpAttachmentsRetainEnd(GcClock::Now())
{
@@ -1122,21 +1025,32 @@ ProjectStore::Oplog::Oplog(std::string_view Id,
using namespace std::literals;
- m_Storage = new OplogStorage(this, m_BasePath);
- bool StoreExists = m_Storage->Exists();
- if (StoreExists)
+ m_Storage = new OplogStorage(this, m_BasePath);
+ OplogStorage::EMode StorageMode = OplogStorage::EMode::Write;
+ if (m_Mode == EMode::kBasicReadOnly)
+ {
+ StorageMode = OplogStorage::EMode::Read;
+ }
+ else
{
- if (!m_Storage->IsValid())
+ bool StoreExists = m_Storage->Exists();
+ if (StoreExists)
{
- ZEN_WARN("Invalid oplog found at '{}'. Wiping state for oplog.", m_BasePath);
- m_Storage->WipeState();
- std::error_code DummyEc;
- RemoveFile(m_MetaPath, DummyEc);
+ if (!m_Storage->IsValid())
+ {
+ ZEN_WARN("Invalid oplog found at '{}'. Wiping state for oplog.", m_BasePath);
+ m_Storage->WipeState();
+ std::error_code DummyEc;
+ RemoveFile(m_MetaPath, DummyEc);
+ StoreExists = false;
+ }
+ }
+ if (!StoreExists)
+ {
+ StorageMode = OplogStorage::EMode::Create;
}
}
- m_Storage->Open(/* IsCreate */ !StoreExists);
- m_TempPath = m_BasePath / "temp"sv;
- m_MetaPath = m_BasePath / "ops.meta"sv;
+ m_Storage->Open(StorageMode);
m_MetaValid = !IsFileOlderThan(m_MetaPath, m_Storage->GetBlobsPath());
CleanDirectory(m_TempPath, /*ForceRemoveReadOnlyFiles*/ false);
@@ -1157,21 +1071,45 @@ ProjectStore::Oplog::Flush()
ZEN_TRACE_CPU("Oplog::Flush");
- RwLock::SharedLockScope Lock(m_OplogLock);
+ if (m_Mode == EMode::kFull)
+ {
+ RwLock::SharedLockScope Lock(m_OplogLock);
- ZEN_ASSERT(m_Storage);
+ if (!m_Storage)
+ {
+ return;
+ }
- m_Storage->Flush();
- if (!m_MetaValid)
- {
- std::error_code DummyEc;
- RemoveFile(m_MetaPath, DummyEc);
+ m_Storage->Flush();
+ if (!m_MetaValid)
+ {
+ std::error_code DummyEc;
+ RemoveFile(m_MetaPath, DummyEc);
+ }
+
+ uint64_t LogCount = m_Storage->LogCount();
+ if (m_LogFlushPosition != LogCount || m_IsLegacySnapshot)
+ {
+ WriteIndexSnapshot();
+ }
}
+}
- uint64_t LogCount = m_Storage->LogCount();
- if (m_LogFlushPosition != LogCount)
+void
+ProjectStore::Oplog::RefreshLsnToPayloadOffsetMap(RwLock::ExclusiveLockScope&)
+{
+ if (!m_LsnToPayloadOffsetMap)
{
- WriteIndexSnapshot();
+ m_LsnToPayloadOffsetMap = std::make_unique<LsnMap<PayloadIndex>>();
+ m_LsnToPayloadOffsetMap->reserve(m_OpLogPayloads.size());
+ for (uint32_t PayloadOffset = 0; PayloadOffset < m_OpLogPayloads.size(); PayloadOffset++)
+ {
+ const OplogPayload& Payload = m_OpLogPayloads[PayloadOffset];
+ if (Payload.Lsn.Number != 0 && Payload.Address.Size != 0)
+ {
+ m_LsnToPayloadOffsetMap->insert_or_assign(Payload.Lsn, PayloadIndex(PayloadOffset));
+ }
+ }
}
}
@@ -1180,63 +1118,79 @@ ProjectStore::Oplog::Scrub(ScrubContext& Ctx)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- std::vector<Oid> BadEntryKeys;
-
- using namespace std::literals;
+ ZEN_ASSERT(m_Mode == EMode::kFull);
- IterateOplogWithKey([&](uint32_t Lsn, const Oid& Key, CbObjectView Op) {
- ZEN_UNUSED(Lsn);
+ std::vector<std::pair<LogSequenceNumber, Oid>> BadEntries;
- std::vector<IoHash> Cids;
- Op.IterateAttachments([&](CbFieldView Visitor) { Cids.emplace_back(Visitor.AsAttachment()); });
+ using namespace std::literals;
+ IterateOplogWithKey([&](LogSequenceNumber Lsn, const Oid& Key, CbObjectView Op) {
{
const Oid KeyHash = ComputeOpKey(Op);
-
- ZEN_ASSERT_FORMAT(KeyHash == Key, "oplog data does not match information from index (op:{} != index:{})", KeyHash, Key);
+ if (KeyHash != Key)
+ {
+ BadEntries.push_back({Lsn, Key});
+ ZEN_WARN("Scrub: oplog data does not match information from index (op:{} != index:{})", KeyHash, Key);
+ return;
+ }
}
- for (const IoHash& Cid : Cids)
- {
- if (!m_CidStore.ContainsChunk(Cid))
+ // TODO: Should we really delete an Op because it points to a missing or malformed Cid chunk?
+
+ Op.IterateAttachments([&](CbFieldView Visitor) {
+ const IoHash Cid = Visitor.AsAttachment();
+ if (Ctx.IsBadCid(Cid))
{
- // oplog entry references a CAS chunk which is not
- // present
- BadEntryKeys.push_back(Key);
+ // oplog entry references a CAS chunk which has been flagged as bad
+ BadEntries.push_back({Lsn, Key});
return;
}
- if (Ctx.IsBadCid(Cid))
+ if (!m_CidStore.ContainsChunk(Cid))
{
- // oplog entry references a CAS chunk which has been
- // flagged as bad
- BadEntryKeys.push_back(Key);
+ // oplog entry references a CAS chunk which is not present
+ BadEntries.push_back({Lsn, Key});
return;
}
- }
+ });
});
- if (!BadEntryKeys.empty())
+ if (!BadEntries.empty())
{
if (Ctx.RunRecovery())
{
ZEN_WARN("oplog '{}/{}': scrubbing found {} bad ops in oplog @ '{}', these will be removed from the index",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
- BadEntryKeys.size(),
+ BadEntries.size(),
m_BasePath);
// Actually perform some clean-up
- RwLock::ExclusiveLockScope _(m_OplogLock);
- for (const auto& Key : BadEntryKeys)
+ RwLock::ExclusiveLockScope Lock(m_OplogLock);
+
+ RefreshLsnToPayloadOffsetMap(Lock);
+
+ for (const auto& BadEntry : BadEntries)
{
- if (auto It = m_LatestOpMap.find(Key); It != m_LatestOpMap.end())
+ const LogSequenceNumber BadLsn = BadEntry.first;
+ const Oid& BadKey = BadEntry.second;
+ if (auto It = m_OpToPayloadOffsetMap.find(BadKey); It != m_OpToPayloadOffsetMap.end())
+ {
+ OplogPayload& Payload = m_OpLogPayloads[It->second];
+ if (Payload.Lsn == BadLsn)
+ {
+ m_OpToPayloadOffsetMap.erase(It);
+ m_Storage->AppendTombstone(BadKey);
+ }
+ }
+ if (auto LsnIt = m_LsnToPayloadOffsetMap->find(BadLsn); LsnIt != m_LsnToPayloadOffsetMap->end())
{
- m_OpAddressMap.erase(It->second);
- m_LatestOpMap.erase(It);
+ const PayloadIndex LsnPayloadOffset = LsnIt->second;
+ OplogPayload& LsnPayload = m_OpLogPayloads[LsnPayloadOffset];
+ LsnPayload = {.Lsn = LogSequenceNumber(0), .Address = {.Offset = 0, .Size = 0}};
}
- m_Storage->AppendTombstone(Key);
+ m_LsnToPayloadOffsetMap->erase(BadLsn);
}
- if (!BadEntryKeys.empty())
+ if (!BadEntries.empty())
{
m_MetaValid = false;
}
@@ -1244,9 +1198,9 @@ ProjectStore::Oplog::Scrub(ScrubContext& Ctx)
else
{
ZEN_WARN("oplog '{}/{}': scrubbing found {} bad ops in oplog @ '{}' but no cleanup will be performed",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
- BadEntryKeys.size(),
+ BadEntries.size(),
m_BasePath);
}
}
@@ -1292,8 +1246,9 @@ ProjectStore::Oplog::ResetState()
m_ChunkMap.clear();
m_MetaMap.clear();
m_FileMap.clear();
- m_OpAddressMap.clear();
- m_LatestOpMap.clear();
+ m_OpToPayloadOffsetMap.clear();
+ m_OpLogPayloads.clear();
+ m_LsnToPayloadOffsetMap.reset();
m_Storage = {};
}
@@ -1304,14 +1259,15 @@ ProjectStore::Oplog::PrepareForDelete(std::filesystem::path& OutRemoveDirectory)
RwLock::ExclusiveLockScope _(m_OplogLock);
m_UpdateCaptureRefCounter = 0;
- m_CapturedLSNs.reset();
+ m_CapturedOps.reset();
m_CapturedAttachments.reset();
m_PendingPrepOpAttachments.clear();
m_ChunkMap.clear();
m_MetaMap.clear();
m_FileMap.clear();
- m_OpAddressMap.clear();
- m_LatestOpMap.clear();
+ m_OpToPayloadOffsetMap.clear();
+ m_OpLogPayloads.clear();
+ m_LsnToPayloadOffsetMap.reset();
m_Storage = {};
if (PrepareDirectoryDelete(m_BasePath, OutRemoveDirectory))
{
@@ -1344,7 +1300,11 @@ ProjectStore::Oplog::Read()
ZEN_TRACE_CPU("Oplog::Read");
ZEN_LOG_SCOPE("Oplog::Read '{}'", m_OplogId);
- ZEN_DEBUG("oplog '{}': reading config from '{}'", m_OuterProject->Identifier, m_OplogId, m_BasePath);
+ ZEN_DEBUG("oplog '{}/{}': reading from '{}'. State: {}",
+ m_OuterProjectId,
+ m_OplogId,
+ m_BasePath,
+ m_Mode == EMode::kFull ? "Full" : "BasicNoLookups");
std::optional<CbObject> Config = ReadStateFile(m_BasePath, [this]() { return Log(); });
if (Config.has_value())
@@ -1363,43 +1323,61 @@ ProjectStore::Oplog::Read()
RemoveFile(m_MetaPath, DummyEc);
}
+ ZEN_ASSERT(!m_LsnToPayloadOffsetMap);
+
ReadIndexSnapshot();
- m_Storage->ReplayLog(
- [&](CbObjectView Op, const OplogEntry& OpEntry) {
- // MaxLSN = Max(OpEntry.OpLsn, MaxLSN);
+ if (m_Mode == EMode::kFull)
+ {
+ m_Storage->ReplayLog(
+ [&](CbObjectView Op, const OplogEntry& OpEntry) {
+ const OplogEntryMapping OpMapping = GetMapping(Op);
+ // Update chunk id maps
+ for (const ChunkMapping& Chunk : OpMapping.Chunks)
+ {
+ m_ChunkMap.insert_or_assign(Chunk.Id, Chunk.Hash);
+ }
- const OplogEntryMapping OpMapping = GetMapping(Op);
- // Update chunk id maps
- for (const ChunkMapping& Chunk : OpMapping.Chunks)
- {
- m_ChunkMap.insert_or_assign(Chunk.Id, Chunk.Hash);
- }
+ for (const FileMapping& File : OpMapping.Files)
+ {
+ if (File.Hash != IoHash::Zero)
+ {
+ m_ChunkMap.insert_or_assign(File.Id, File.Hash);
+ }
+ m_FileMap.insert_or_assign(File.Id,
+ FileMapEntry{.ServerPath = File.Hash == IoHash::Zero ? File.ServerPath : std::string(),
+ .ClientPath = File.ClientPath});
+ }
- for (const FileMapping& File : OpMapping.Files)
- {
- if (File.Hash != IoHash::Zero)
+ for (const ChunkMapping& Meta : OpMapping.Meta)
{
- m_ChunkMap.insert_or_assign(File.Id, File.Hash);
+ m_MetaMap.insert_or_assign(Meta.Id, Meta.Hash);
}
- m_FileMap.insert_or_assign(
- File.Id,
- FileMapEntry{.ServerPath = File.Hash == IoHash::Zero ? File.ServerPath : std::string(), .ClientPath = File.ClientPath});
- }
- for (const ChunkMapping& Meta : OpMapping.Meta)
- {
- m_MetaMap.insert_or_assign(Meta.Id, Meta.Hash);
- }
+ const PayloadIndex PayloadOffset(m_OpLogPayloads.size());
- m_OpAddressMap.emplace(OpEntry.OpLsn, OplogEntryAddress{.Offset = OpEntry.OpCoreOffset, .Size = OpEntry.OpCoreSize});
- m_LatestOpMap[OpEntry.OpKeyHash] = OpEntry.OpLsn;
- },
- m_LogFlushPosition);
+ m_OpToPayloadOffsetMap.insert_or_assign(OpEntry.OpKeyHash, PayloadOffset);
+ m_OpLogPayloads.push_back({.Lsn = OpEntry.OpLsn, .Address = OpEntry.OpCoreAddress});
+ },
+ m_LogFlushPosition);
- if (m_Storage->LogCount() != m_LogFlushPosition)
+ if (m_Storage->LogCount() != m_LogFlushPosition || m_IsLegacySnapshot)
+ {
+ WriteIndexSnapshot();
+ }
+ }
+ else
{
- WriteIndexSnapshot();
+ std::vector<OplogEntry> OpLogEntries;
+ uint64_t InvalidEntries;
+ m_Storage->ReadOplogEntriesFromLog(OpLogEntries, InvalidEntries, m_LogFlushPosition);
+ for (const OplogEntry& OpEntry : OpLogEntries)
+ {
+ const PayloadIndex PayloadOffset(m_OpLogPayloads.size());
+
+ m_OpToPayloadOffsetMap.insert_or_assign(OpEntry.OpKeyHash, PayloadOffset);
+ m_OpLogPayloads.push_back({.Lsn = OpEntry.OpLsn, .Address = OpEntry.OpCoreAddress});
+ }
}
}
@@ -1407,6 +1385,7 @@ void
ProjectStore::Oplog::Write()
{
ZEN_MEMSCOPE(GetProjectstoreTag());
+ ZEN_ASSERT(m_Mode != EMode::kBasicReadOnly);
using namespace std::literals;
@@ -1420,7 +1399,7 @@ ProjectStore::Oplog::Write()
std::filesystem::path StateFilePath = m_BasePath / "oplog.zcb"sv;
- ZEN_INFO("oplog '{}/{}': persisting config to '{}'", m_OuterProject->Identifier, m_OplogId, StateFilePath);
+ ZEN_DEBUG("oplog '{}/{}': persisting config to '{}'", m_OuterProjectId, m_OplogId, StateFilePath);
TemporaryFile::SafeWriteFile(StateFilePath, Mem.GetView());
}
@@ -1439,7 +1418,7 @@ bool
ProjectStore::Oplog::Reset()
{
ZEN_MEMSCOPE(GetProjectstoreTag());
-
+ ZEN_ASSERT(m_Mode == EMode::kFull);
std::filesystem::path MovedDir;
{
@@ -1449,17 +1428,18 @@ ProjectStore::Oplog::Reset()
{
m_Storage = new OplogStorage(this, m_BasePath);
const bool StoreExists = m_Storage->Exists();
- m_Storage->Open(/* IsCreate */ !StoreExists);
+ m_Storage->Open(StoreExists ? OplogStorage::EMode::Write : OplogStorage::EMode::Create);
m_MetaValid = !IsFileOlderThan(m_MetaPath, m_Storage->GetBlobsPath());
return false;
}
m_ChunkMap.clear();
m_MetaMap.clear();
m_FileMap.clear();
- m_OpAddressMap.clear();
- m_LatestOpMap.clear();
+ m_OpToPayloadOffsetMap.clear();
+ m_OpLogPayloads.clear();
+ m_LsnToPayloadOffsetMap.reset();
m_Storage = new OplogStorage(this, m_BasePath);
- m_Storage->Open(true);
+ m_Storage->Open(OplogStorage::EMode::Create);
m_MetaValid = false;
CleanDirectory(m_TempPath, /*ForceRemoveReadOnlyFiles*/ false);
Write();
@@ -1472,6 +1452,30 @@ ProjectStore::Oplog::Reset()
return true;
}
+bool
+ProjectStore::Oplog::CanUnload()
+{
+ ZEN_MEMSCOPE(GetProjectstoreTag());
+
+ RwLock::SharedLockScope _(m_OplogLock);
+
+ uint64_t LogCount = m_Storage->LogCount();
+ if (m_LogFlushPosition != LogCount)
+ {
+ return false; // The oplog is not flushed so likely this is an active oplog
+ }
+
+ if (!m_PendingPrepOpAttachments.empty())
+ {
+ return false; // We have a pending oplog prep operation in flight
+ }
+ if (m_UpdateCaptureRefCounter > 0)
+ {
+ return false; // GC capture is enable for the oplog
+ }
+ return true;
+}
+
std::optional<CbObject>
ProjectStore::Oplog::ReadStateFile(const std::filesystem::path& BasePath, std::function<LoggerRef()>&& Log)
{
@@ -1482,7 +1486,7 @@ ProjectStore::Oplog::ReadStateFile(const std::filesystem::path& BasePath, std::f
std::filesystem::path StateFilePath = BasePath / "oplog.zcb"sv;
if (IsFile(StateFilePath))
{
- // ZEN_INFO("oplog '{}/{}': config read from '{}'", m_OuterProject->Identifier, m_OplogId, StateFilePath);
+ // ZEN_INFO("oplog '{}/{}': config read from '{}'", m_OuterProjectId, m_OplogId, StateFilePath);
BasicFile Blob;
Blob.Open(StateFilePath, BasicFile::Mode::kRead);
@@ -1503,7 +1507,9 @@ ProjectStore::Oplog::ReadStateFile(const std::filesystem::path& BasePath, std::f
}
ProjectStore::Oplog::ValidationResult
-ProjectStore::Oplog::Validate(std::atomic_bool& IsCancelledFlag, WorkerThreadPool* OptionalWorkerPool)
+ProjectStore::Oplog::Validate(const std::filesystem::path& ProjectRootDir,
+ std::atomic_bool& IsCancelledFlag,
+ WorkerThreadPool* OptionalWorkerPool)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
@@ -1522,7 +1528,7 @@ ProjectStore::Oplog::Validate(std::atomic_bool& IsCancelledFlag, WorkerThreadPoo
Keys.reserve(OpCount);
Mappings.reserve(OpCount);
- IterateOplogWithKey([&](uint32_t LSN, const Oid& Key, CbObjectView OpView) {
+ IterateOplogWithKey([&](LogSequenceNumber LSN, const Oid& Key, CbObjectView OpView) {
Result.LSNLow = Min(Result.LSNLow, LSN);
Result.LSNHigh = Max(Result.LSNHigh, LSN);
KeyHashes.push_back(Key);
@@ -1566,7 +1572,7 @@ ProjectStore::Oplog::Validate(std::atomic_bool& IsCancelledFlag, WorkerThreadPoo
{
if (File.Hash == IoHash::Zero)
{
- std::filesystem::path FilePath = m_OuterProject->RootDir / File.ServerPath;
+ std::filesystem::path FilePath = ProjectRootDir / File.ServerPath;
if (!IsFile(FilePath))
{
ResultLock.WithExclusiveLock([&]() { Result.MissingFiles.push_back({KeyHash, File}); });
@@ -1598,7 +1604,7 @@ ProjectStore::Oplog::Validate(std::atomic_bool& IsCancelledFlag, WorkerThreadPoo
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
try
{
for (uint32_t OpIndex = 0; !IsCancelledFlag && OpIndex < Result.OpCount; OpIndex++)
@@ -1649,12 +1655,14 @@ ProjectStore::Oplog::WriteIndexSnapshot()
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("Oplog::WriteIndexSnapshot");
- ZEN_DEBUG("oplog '{}/{}': write store snapshot at '{}'", m_OuterProject->Identifier, m_OplogId, m_BasePath);
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
+ ZEN_DEBUG("oplog '{}/{}': write store snapshot at '{}'", m_OuterProjectId, m_OplogId, m_BasePath);
uint64_t EntryCount = 0;
Stopwatch Timer;
const auto _ = MakeGuard([&] {
ZEN_INFO("oplog '{}/{}': wrote store snapshot for '{}' containing {} entries in {}",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
m_BasePath,
EntryCount,
@@ -1666,34 +1674,21 @@ ProjectStore::Oplog::WriteIndexSnapshot()
const fs::path IndexPath = m_BasePath / "ops.zidx";
try
{
- // Write the current state of the location map to a new index state
- std::vector<uint32_t> LSNEntries;
- std::vector<Oid> Keys;
- std::vector<OplogEntryAddress> AddressMapEntries;
- std::vector<uint32_t> LatestOpMapEntries;
- std::vector<IoHash> ChunkMapEntries;
- std::vector<IoHash> MetaMapEntries;
- std::vector<uint32_t> FilePathLengths;
- std::vector<std::string> FilePaths;
- uint64_t IndexLogPosition = 0;
+ std::vector<Oid> Keys;
+ std::vector<PayloadIndex> OpPayloadOffsets;
+ std::vector<IoHash> ChunkMapEntries;
+ std::vector<IoHash> MetaMapEntries;
+ std::vector<uint32_t> FilePathLengths;
+ std::vector<std::string> FilePaths;
+ uint64_t IndexLogPosition = 0;
{
IndexLogPosition = m_Storage->LogCount();
-
- Keys.reserve(m_LatestOpMap.size() + m_ChunkMap.size() + m_MetaMap.size() + m_FileMap.size());
-
- AddressMapEntries.reserve(m_OpAddressMap.size());
- LSNEntries.reserve(m_OpAddressMap.size());
- for (const auto& It : m_OpAddressMap)
+ Keys.reserve(m_OpToPayloadOffsetMap.size() + m_ChunkMap.size() + m_MetaMap.size() + m_FileMap.size());
+ OpPayloadOffsets.reserve(m_OpToPayloadOffsetMap.size());
+ for (const auto& Kv : m_OpToPayloadOffsetMap)
{
- LSNEntries.push_back(It.first);
- AddressMapEntries.push_back(It.second);
- }
-
- LatestOpMapEntries.reserve(m_LatestOpMap.size());
- for (const auto& It : m_LatestOpMap)
- {
- Keys.push_back(It.first);
- LatestOpMapEntries.push_back(It.second);
+ Keys.push_back(Kv.first);
+ OpPayloadOffsets.push_back(Kv.second);
}
ChunkMapEntries.reserve(m_ChunkMap.size());
@@ -1733,14 +1728,16 @@ ProjectStore::Oplog::WriteIndexSnapshot()
{
BasicFileWriter IndexFile(ObjectIndexFile, 65536u);
- OplogIndexHeader Header = {.LogPosition = IndexLogPosition,
- .LSNCount = gsl::narrow<uint32_t>(LSNEntries.size()),
- .KeyCount = gsl::narrow<uint64_t>(Keys.size()),
- .OpAddressMapCount = gsl::narrow<uint32_t>(AddressMapEntries.size()),
- .LatestOpMapCount = gsl::narrow<uint32_t>(LatestOpMapEntries.size()),
- .ChunkMapCount = gsl::narrow<uint64_t>(ChunkMapEntries.size()),
- .MetaMapCount = gsl::narrow<uint64_t>(MetaMapEntries.size()),
- .FileMapCount = gsl::narrow<uint64_t>(FilePathLengths.size() / 2)};
+ OplogIndexHeader Header;
+ Header.V2 = {.LogPosition = IndexLogPosition,
+ .KeyCount = gsl::narrow<uint64_t>(Keys.size()),
+ .OpPayloadIndexCount = gsl::narrow<uint32_t>(OpPayloadOffsets.size()),
+ .OpPayloadCount = gsl::narrow<uint32_t>(m_OpLogPayloads.size()),
+ .ChunkMapCount = gsl::narrow<uint32_t>(ChunkMapEntries.size()),
+ .MetaMapCount = gsl::narrow<uint32_t>(MetaMapEntries.size()),
+ .FileMapCount = gsl::narrow<uint32_t>(FilePathLengths.size() / 2),
+ .MaxLSN = m_Storage->MaxLSN(),
+ .NextOpCoreOffset = m_Storage->GetNextOpOffset()};
Header.Checksum = OplogIndexHeader::ComputeChecksum(Header);
@@ -1748,16 +1745,13 @@ ProjectStore::Oplog::WriteIndexSnapshot()
IndexFile.Write(&Header, sizeof(OplogIndexHeader), Offset);
Offset = IndexFile.AlignTo(OplogIndexHeader::DataAlignment);
- IndexFile.Write(LSNEntries.data(), LSNEntries.size() * sizeof(uint32_t), Offset);
- Offset = IndexFile.AlignTo(OplogIndexHeader::DataAlignment);
-
IndexFile.Write(Keys.data(), Keys.size() * sizeof(Oid), Offset);
Offset = IndexFile.AlignTo(OplogIndexHeader::DataAlignment);
- IndexFile.Write(AddressMapEntries.data(), AddressMapEntries.size() * sizeof(OplogEntryAddress), Offset);
+ IndexFile.Write(OpPayloadOffsets.data(), OpPayloadOffsets.size() * sizeof(PayloadIndex), Offset);
Offset = IndexFile.AlignTo(OplogIndexHeader::DataAlignment);
- IndexFile.Write(LatestOpMapEntries.data(), LatestOpMapEntries.size() * sizeof(uint32_t), Offset);
+ IndexFile.Write(m_OpLogPayloads.data(), m_OpLogPayloads.size() * sizeof(OplogPayload), Offset);
Offset = IndexFile.AlignTo(OplogIndexHeader::DataAlignment);
IndexFile.Write(ChunkMapEntries.data(), ChunkMapEntries.size() * sizeof(IoHash), Offset);
@@ -1775,6 +1769,7 @@ ProjectStore::Oplog::WriteIndexSnapshot()
Offset += FilePath.length();
}
}
+
ObjectIndexFile.Flush();
ObjectIndexFile.MoveTemporaryIntoPlace(IndexPath, Ec);
if (Ec)
@@ -1785,12 +1780,13 @@ ProjectStore::Oplog::WriteIndexSnapshot()
IndexPath,
Ec.message()));
}
- EntryCount = LSNEntries.size();
+ EntryCount = m_OpLogPayloads.size();
m_LogFlushPosition = IndexLogPosition;
+ m_IsLegacySnapshot = false;
}
catch (const std::exception& Err)
{
- ZEN_WARN("oplog '{}/{}': snapshot FAILED, reason: '{}'", m_OuterProject->Identifier, m_OplogId, Err.what());
+ ZEN_WARN("oplog '{}/{}': snapshot FAILED, reason: '{}'", m_OuterProjectId, m_OplogId, Err.what());
}
}
@@ -1806,12 +1802,12 @@ ProjectStore::Oplog::ReadIndexSnapshot()
uint64_t EntryCount = 0;
Stopwatch Timer;
const auto _ = MakeGuard([&] {
- ZEN_INFO("oplog '{}/{}': index read from '{}' containing {} entries in {}",
- m_OuterProject->Identifier,
- m_OplogId,
- IndexPath,
- EntryCount,
- NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+ ZEN_DEBUG("oplog '{}/{}': index read from '{}' containing {} entries in {}",
+ m_OuterProjectId,
+ m_OplogId,
+ IndexPath,
+ EntryCount,
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
});
try
@@ -1827,17 +1823,13 @@ ProjectStore::Oplog::ReadIndexSnapshot()
Offset += sizeof(OplogIndexHeader);
Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- if ((Header.Magic == OplogIndexHeader::ExpectedMagic) && (Header.Version == OplogIndexHeader::CurrentVersion) &&
- (Header.Checksum == OplogIndexHeader::ComputeChecksum(Header)))
+ if (Header.Magic == OplogIndexHeader::ExpectedMagic)
{
- uint32_t MaxLSN = 0;
- OplogEntryAddress LastOpAddress{.Offset = 0, .Size = 0};
-
uint32_t Checksum = OplogIndexHeader::ComputeChecksum(Header);
if (Header.Checksum != Checksum)
{
ZEN_WARN("oplog '{}/{}': skipping invalid index file '{}'. Checksum mismatch. Expected: {}, Found: {}",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
IndexPath,
Header.Checksum,
@@ -1845,127 +1837,246 @@ ProjectStore::Oplog::ReadIndexSnapshot()
return;
}
- if (Header.LatestOpMapCount + Header.ChunkMapCount + Header.MetaMapCount + Header.FileMapCount != Header.KeyCount)
+ if (Header.Version == OplogIndexHeader::Version1)
{
- ZEN_WARN("oplog '{}/{}': skipping invalid index file '{}'. Key count mismatch. Expected: {}, Found: {}",
- m_OuterProject->Identifier,
- m_OplogId,
- IndexPath,
- Header.LatestOpMapCount + Header.ChunkMapCount + Header.MetaMapCount + Header.FileMapCount,
- Header.KeyCount);
- return;
- }
+ uint32_t MaxLSN = 0;
+ OplogEntryAddress LastOpAddress{.Offset = 0, .Size = 0};
- std::vector<uint32_t> LSNEntries(Header.LSNCount);
- ObjectIndexFile.Read(LSNEntries.data(), LSNEntries.size() * sizeof(uint32_t), Offset);
- Offset += LSNEntries.size() * sizeof(uint32_t);
- Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- size_t LSNOffset = 0;
+ if (Header.V1.LatestOpMapCount + Header.V1.ChunkMapCount + Header.V1.MetaMapCount + Header.V1.FileMapCount !=
+ Header.V1.KeyCount)
+ {
+ ZEN_WARN("oplog '{}/{}': skipping invalid index file '{}'. Key count mismatch. Expected: {}, Found: {}",
+ m_OuterProjectId,
+ m_OplogId,
+ IndexPath,
+ Header.V1.LatestOpMapCount + Header.V1.ChunkMapCount + Header.V1.MetaMapCount + Header.V1.FileMapCount,
+ Header.V1.KeyCount);
+ return;
+ }
- std::vector<Oid> Keys(Header.KeyCount);
- ObjectIndexFile.Read(Keys.data(), Keys.size() * sizeof(Oid), Offset);
- Offset += Keys.size() * sizeof(Oid);
- Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- size_t KeyOffset = 0;
+ std::vector<uint32_t> LSNEntries(Header.V1.LSNCount);
+ ObjectIndexFile.Read(LSNEntries.data(), LSNEntries.size() * sizeof(uint32_t), Offset);
+ Offset += LSNEntries.size() * sizeof(uint32_t);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+ size_t LSNOffset = 0;
+
+ std::vector<Oid> Keys(Header.V1.KeyCount);
+ ObjectIndexFile.Read(Keys.data(), Keys.size() * sizeof(Oid), Offset);
+ Offset += Keys.size() * sizeof(Oid);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+ size_t KeyOffset = 0;
- {
- std::vector<OplogEntryAddress> AddressMapEntries(Header.OpAddressMapCount);
- ObjectIndexFile.Read(AddressMapEntries.data(), AddressMapEntries.size() * sizeof(OplogEntryAddress), Offset);
- Offset += AddressMapEntries.size() * sizeof(OplogEntryAddress);
- Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- m_OpAddressMap.reserve(AddressMapEntries.size());
- for (const OplogEntryAddress& Address : AddressMapEntries)
{
- m_OpAddressMap.insert_or_assign(LSNEntries[LSNOffset++], Address);
- if (Address.Offset > LastOpAddress.Offset)
+ std::vector<OplogEntryAddress> AddressMapEntries(Header.V1.OpAddressMapCount);
+ ObjectIndexFile.Read(AddressMapEntries.data(), AddressMapEntries.size() * sizeof(OplogEntryAddress), Offset);
+ Offset += AddressMapEntries.size() * sizeof(OplogEntryAddress);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+
+ tsl::robin_map<uint32_t, PayloadIndex> LsnToPayloadOffset;
+ LsnToPayloadOffset.reserve(AddressMapEntries.size());
+
+ m_OpLogPayloads.reserve(AddressMapEntries.size());
+ for (const OplogEntryAddress& Address : AddressMapEntries)
{
- LastOpAddress = Address;
+ const uint32_t LSN = LSNEntries[LSNOffset++];
+ LsnToPayloadOffset.insert_or_assign(LSN, PayloadIndex(m_OpLogPayloads.size()));
+
+ m_OpLogPayloads.push_back({.Lsn = LogSequenceNumber(LSN), .Address = Address});
+ if (Address.Offset > LastOpAddress.Offset)
+ {
+ LastOpAddress = Address;
+ }
+ MaxLSN = Max(MaxLSN, LSN);
+ }
+
+ {
+ std::vector<uint32_t> LatestOpMapEntries(Header.V1.LatestOpMapCount);
+ ObjectIndexFile.Read(LatestOpMapEntries.data(), LatestOpMapEntries.size() * sizeof(uint32_t), Offset);
+ Offset += LatestOpMapEntries.size() * sizeof(uint32_t);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+ m_OpToPayloadOffsetMap.reserve(LatestOpMapEntries.size());
+ for (uint32_t Lsn : LatestOpMapEntries)
+ {
+ if (auto It = LsnToPayloadOffset.find(Lsn); It != LsnToPayloadOffset.end())
+ {
+ m_OpToPayloadOffsetMap.insert_or_assign(Keys[KeyOffset++], It->second);
+ MaxLSN = Max(MaxLSN, Lsn);
+ }
+ }
}
}
- }
- {
- std::vector<uint32_t> LatestOpMapEntries(Header.LatestOpMapCount);
- ObjectIndexFile.Read(LatestOpMapEntries.data(), LatestOpMapEntries.size() * sizeof(uint32_t), Offset);
- Offset += LatestOpMapEntries.size() * sizeof(uint32_t);
- Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- m_LatestOpMap.reserve(LatestOpMapEntries.size());
- for (uint32_t LSN : LatestOpMapEntries)
+ if (m_Mode == EMode::kFull)
{
- m_LatestOpMap.insert_or_assign(Keys[KeyOffset++], LSN);
- MaxLSN = Max(MaxLSN, LSN);
+ {
+ std::vector<IoHash> ChunkMapEntries(Header.V1.ChunkMapCount);
+ ObjectIndexFile.Read(ChunkMapEntries.data(), ChunkMapEntries.size() * sizeof(IoHash), Offset);
+ Offset += ChunkMapEntries.size() * sizeof(IoHash);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+ m_ChunkMap.reserve(ChunkMapEntries.size());
+ for (const IoHash& ChunkId : ChunkMapEntries)
+ {
+ m_ChunkMap.insert_or_assign(Keys[KeyOffset++], ChunkId);
+ }
+ }
+ {
+ std::vector<IoHash> MetaMapEntries(Header.V1.MetaMapCount);
+ ObjectIndexFile.Read(MetaMapEntries.data(), MetaMapEntries.size() * sizeof(IoHash), Offset);
+ Offset += MetaMapEntries.size() * sizeof(IoHash);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+ m_MetaMap.reserve(MetaMapEntries.size());
+ for (const IoHash& ChunkId : MetaMapEntries)
+ {
+ m_MetaMap.insert_or_assign(Keys[KeyOffset++], ChunkId);
+ }
+ }
+ {
+ m_FileMap.reserve(Header.V1.FileMapCount);
+
+ std::vector<uint32_t> FilePathLengths(Header.V1.FileMapCount * 2);
+ ObjectIndexFile.Read(FilePathLengths.data(), FilePathLengths.size() * sizeof(uint32_t), Offset);
+ Offset += FilePathLengths.size() * sizeof(uint32_t);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+
+ BasicFileBuffer IndexFile(ObjectIndexFile, 65536);
+ auto ReadString([&IndexFile, &Offset](uint32_t Length) -> std::string {
+ MemoryView StringData = IndexFile.MakeView(Length, Offset);
+ if (StringData.GetSize() != Length)
+ {
+ throw std::runtime_error(fmt::format("Invalid format. Expected to read {} bytes but got {}",
+ Length,
+ uint32_t(StringData.GetSize())));
+ }
+ Offset += Length;
+ return std::string((const char*)StringData.GetData(), Length);
+ });
+ for (uint64_t FileLengthOffset = 0; FileLengthOffset < FilePathLengths.size();)
+ {
+ std::string ServerPath = ReadString(FilePathLengths[FileLengthOffset++]);
+ std::string ClientPath = ReadString(FilePathLengths[FileLengthOffset++]);
+ m_FileMap.insert_or_assign(
+ Keys[KeyOffset++],
+ FileMapEntry{.ServerPath = std::move(ServerPath), .ClientPath = std::move(ClientPath)});
+ }
+ }
}
+ m_LogFlushPosition = Header.V1.LogPosition;
+ m_Storage->SetMaxLSNAndNextWriteAddress(MaxLSN, LastOpAddress);
+ EntryCount = Header.V1.LSNCount;
+ m_IsLegacySnapshot = true;
}
+ else if (Header.Version == OplogIndexHeader::Version2)
{
- std::vector<IoHash> ChunkMapEntries(Header.ChunkMapCount);
- ObjectIndexFile.Read(ChunkMapEntries.data(), ChunkMapEntries.size() * sizeof(IoHash), Offset);
- Offset += ChunkMapEntries.size() * sizeof(IoHash);
+ std::vector<Oid> Keys(Header.V2.KeyCount);
+
+ ObjectIndexFile.Read(Keys.data(), Keys.size() * sizeof(Oid), Offset);
+ Offset += Keys.size() * sizeof(Oid);
Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- m_ChunkMap.reserve(ChunkMapEntries.size());
- for (const IoHash& ChunkId : ChunkMapEntries)
- {
- m_ChunkMap.insert_or_assign(Keys[KeyOffset++], ChunkId);
- }
- }
- {
- std::vector<IoHash> MetaMapEntries(Header.MetaMapCount);
- ObjectIndexFile.Read(MetaMapEntries.data(), MetaMapEntries.size() * sizeof(IoHash), Offset);
- Offset += MetaMapEntries.size() * sizeof(IoHash);
+
+ std::vector<PayloadIndex> OpPayloadOffsets(Header.V2.OpPayloadIndexCount);
+ ObjectIndexFile.Read(OpPayloadOffsets.data(), OpPayloadOffsets.size() * sizeof(PayloadIndex), Offset);
+ Offset += OpPayloadOffsets.size() * sizeof(PayloadIndex);
Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- m_MetaMap.reserve(MetaMapEntries.size());
- for (const IoHash& ChunkId : MetaMapEntries)
- {
- m_MetaMap.insert_or_assign(Keys[KeyOffset++], ChunkId);
- }
- }
- {
- m_FileMap.reserve(Header.FileMapCount);
- std::vector<uint32_t> FilePathLengths(Header.FileMapCount * 2);
- ObjectIndexFile.Read(FilePathLengths.data(), FilePathLengths.size() * sizeof(uint32_t), Offset);
- Offset += FilePathLengths.size() * sizeof(uint32_t);
+ m_OpLogPayloads.resize(Header.V2.OpPayloadCount);
+ ObjectIndexFile.Read(m_OpLogPayloads.data(), m_OpLogPayloads.size() * sizeof(OplogPayload), Offset);
+ Offset += m_OpLogPayloads.size() * sizeof(OplogPayload);
Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
- BasicFileBuffer IndexFile(ObjectIndexFile, 65536);
- auto ReadString([&IndexFile, &Offset](uint32_t Length) -> std::string {
- MemoryView StringData = IndexFile.MakeView(Length, Offset);
- if (StringData.GetSize() != Length)
- {
- throw std::runtime_error(fmt::format("Invalid format. Expected to read %u bytes but got %u",
- Length,
- uint32_t(StringData.GetSize())));
- }
- Offset += Length;
- return std::string((const char*)StringData.GetData(), Length);
- });
- for (uint64_t FileLengthOffset = 0; FileLengthOffset < FilePathLengths.size();)
+ size_t KeyOffset = 0;
+
+ m_OpToPayloadOffsetMap.reserve(Header.V2.OpPayloadIndexCount);
+ for (const PayloadIndex PayloadOffset : OpPayloadOffsets)
{
- std::string ServerPath = ReadString(FilePathLengths[FileLengthOffset++]);
- std::string ClientPath = ReadString(FilePathLengths[FileLengthOffset++]);
- m_FileMap.insert_or_assign(
- Keys[KeyOffset++],
- FileMapEntry{.ServerPath = std::move(ServerPath), .ClientPath = std::move(ClientPath)});
+ const Oid& Key = Keys[KeyOffset++];
+ ZEN_ASSERT_SLOW(PayloadOffset.Index < Header.V2.OpPayloadCount);
+ m_OpToPayloadOffsetMap.insert({Key, PayloadOffset});
+ }
+
+ if (m_Mode == EMode::kFull)
+ {
+ {
+ std::vector<IoHash> ChunkMapEntries(Header.V2.ChunkMapCount);
+ ObjectIndexFile.Read(ChunkMapEntries.data(), ChunkMapEntries.size() * sizeof(IoHash), Offset);
+ Offset += ChunkMapEntries.size() * sizeof(IoHash);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+ m_ChunkMap.reserve(ChunkMapEntries.size());
+ for (const IoHash& ChunkId : ChunkMapEntries)
+ {
+ m_ChunkMap.insert_or_assign(Keys[KeyOffset++], ChunkId);
+ }
+ }
+ {
+ std::vector<IoHash> MetaMapEntries(Header.V2.MetaMapCount);
+ ObjectIndexFile.Read(MetaMapEntries.data(), MetaMapEntries.size() * sizeof(IoHash), Offset);
+ Offset += MetaMapEntries.size() * sizeof(IoHash);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+ m_MetaMap.reserve(MetaMapEntries.size());
+ for (const IoHash& ChunkId : MetaMapEntries)
+ {
+ m_MetaMap.insert_or_assign(Keys[KeyOffset++], ChunkId);
+ }
+ }
+ {
+ m_FileMap.reserve(Header.V2.FileMapCount);
+
+ std::vector<uint32_t> FilePathLengths(Header.V2.FileMapCount * 2);
+ ObjectIndexFile.Read(FilePathLengths.data(), FilePathLengths.size() * sizeof(uint32_t), Offset);
+ Offset += FilePathLengths.size() * sizeof(uint32_t);
+ Offset = RoundUp(Offset, OplogIndexHeader::DataAlignment);
+
+ BasicFileBuffer IndexFile(ObjectIndexFile, 65536);
+ auto ReadString([&IndexFile, &Offset](uint32_t Length) -> std::string {
+ MemoryView StringData = IndexFile.MakeView(Length, Offset);
+ if (StringData.GetSize() != Length)
+ {
+ throw std::runtime_error(fmt::format("Invalid format. Expected to read {} bytes but got {}",
+ Length,
+ uint32_t(StringData.GetSize())));
+ }
+ Offset += Length;
+ return std::string((const char*)StringData.GetData(), Length);
+ });
+ for (uint64_t FileLengthOffset = 0; FileLengthOffset < FilePathLengths.size();)
+ {
+ std::string ServerPath = ReadString(FilePathLengths[FileLengthOffset++]);
+ std::string ClientPath = ReadString(FilePathLengths[FileLengthOffset++]);
+ m_FileMap.insert_or_assign(
+ Keys[KeyOffset++],
+ FileMapEntry{.ServerPath = std::move(ServerPath), .ClientPath = std::move(ClientPath)});
+ }
+ }
}
+ m_LogFlushPosition = Header.V2.LogPosition;
+ m_Storage->SetMaxLSNAndNextOpOffset(Header.V2.MaxLSN, Header.V2.NextOpCoreOffset);
+ EntryCount = Header.V2.OpPayloadCount;
+ m_IsLegacySnapshot = false;
+ }
+ else
+ {
+ ZEN_WARN("oplog '{}/{}': skipping invalid index file '{}'. Unsupported version number {}",
+ m_OuterProjectId,
+ m_OplogId,
+ IndexPath,
+ Header.Version);
+ return;
}
- m_LogFlushPosition = Header.LogPosition;
- m_Storage->SetMaxLSNAndNextWriteAddress(MaxLSN, LastOpAddress);
- EntryCount = Header.LSNCount;
}
else
{
- ZEN_WARN("oplog '{}/{}': skipping invalid index file '{}'", m_OuterProject->Identifier, m_OplogId, IndexPath);
+ ZEN_WARN("oplog '{}/{}': skipping invalid index file '{}'", m_OuterProjectId, m_OplogId, IndexPath);
}
}
}
catch (const std::exception& Ex)
{
- m_OpAddressMap.clear();
- m_LatestOpMap.clear();
+ m_OpToPayloadOffsetMap.clear();
+ m_OpLogPayloads.clear();
m_ChunkMap.clear();
m_MetaMap.clear();
m_FileMap.clear();
m_LogFlushPosition = 0;
ZEN_ERROR("oplog '{}/{}': failed reading index snapshot from '{}'. Reason: '{}'",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
IndexPath,
Ex.what());
@@ -1991,19 +2102,7 @@ ProjectStore::Oplog::GetUnusedSpacePercentLocked() const
return 0;
}
- std::vector<OplogEntryAddress> Addresses;
- {
- Addresses.reserve(m_LatestOpMap.size());
- for (auto It : m_LatestOpMap)
- {
- if (auto AddressIt = m_OpAddressMap.find(It.second); AddressIt != m_OpAddressMap.end())
- {
- Addresses.push_back(AddressIt->second);
- }
- }
- }
-
- const uint64_t EffectiveBlobsSize = m_Storage->GetEffectiveBlobsSize(std::move(Addresses));
+ const uint64_t EffectiveBlobsSize = m_Storage->GetEffectiveBlobsSize(m_OpLogPayloads);
if (EffectiveBlobsSize < ActualBlobsSize)
{
@@ -2023,35 +2122,42 @@ ProjectStore::Oplog::Compact(bool DryRun, bool RetainLSNs, std::string_view LogP
void
ProjectStore::Oplog::Compact(RwLock::ExclusiveLockScope&, bool DryRun, bool RetainLSNs, std::string_view LogPrefix)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
ZEN_MEMSCOPE(GetProjectstoreTag());
Stopwatch Timer;
- std::vector<uint32_t> LSNs;
- LSNs.reserve(m_LatestOpMap.size());
- for (auto It : m_LatestOpMap)
+ std::vector<LogSequenceNumber> LatestLSNs;
+ LatestLSNs.reserve(m_OpLogPayloads.size());
+ for (const auto& Kv : m_OpToPayloadOffsetMap)
{
- LSNs.push_back(It.second);
+ const OplogPayload& OpPayload = m_OpLogPayloads[Kv.second];
+ LatestLSNs.push_back(OpPayload.Lsn);
}
- tsl::robin_map<uint32_t, OplogEntryAddress> OpAddressMap; // Index LSN -> op data in ops blob file
- OidMap<uint32_t> LatestOpMap; // op key -> latest op LSN for key
+ std::vector<OplogPayload> OpPayloads;
+ OpPayloads.reserve(LatestLSNs.size());
+
+ OidMap<PayloadIndex> OpToPayloadOffsetMap;
+ OpToPayloadOffsetMap.reserve(LatestLSNs.size());
uint64_t PreSize = TotalSize();
m_Storage->Compact(
- LSNs,
- [&](const Oid& OpKeyHash, uint32_t, uint32_t NewLSN, const OplogEntryAddress& NewAddress) {
- LatestOpMap.insert_or_assign(OpKeyHash, NewLSN);
- OpAddressMap.insert_or_assign(NewLSN, NewAddress);
+ LatestLSNs,
+ [&](const Oid& OpKeyHash, LogSequenceNumber, LogSequenceNumber NewLsn, const OplogEntryAddress& NewAddress) {
+ OpToPayloadOffsetMap.insert({OpKeyHash, PayloadIndex(OpPayloads.size())});
+ OpPayloads.push_back({.Lsn = NewLsn, .Address = NewAddress});
},
RetainLSNs,
/*DryRun*/ DryRun);
if (!DryRun)
{
- m_OpAddressMap.swap(OpAddressMap);
- m_LatestOpMap.swap(LatestOpMap);
+ m_OpToPayloadOffsetMap.swap(OpToPayloadOffsetMap);
+ m_OpLogPayloads.swap(OpPayloads);
+ m_LsnToPayloadOffsetMap.reset();
WriteIndexSnapshot();
}
@@ -2060,7 +2166,7 @@ ProjectStore::Oplog::Compact(RwLock::ExclusiveLockScope&, bool DryRun, bool Reta
ZEN_INFO("{} oplog '{}/{}': Compacted in {}. New size: {}, freeing: {}",
LogPrefix,
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
NiceTimeSpanMs(Timer.GetElapsedTimeMs()),
NiceBytes(PostSize),
@@ -2070,6 +2176,7 @@ ProjectStore::Oplog::Compact(RwLock::ExclusiveLockScope&, bool DryRun, bool Reta
IoBuffer
ProjectStore::Oplog::GetChunkByRawHash(const IoHash& RawHash)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
return m_CidStore.FindChunkByCid(RawHash);
}
@@ -2080,6 +2187,7 @@ ProjectStore::Oplog::IterateChunks(std::span<IoHash> RawHashes,
WorkerThreadPool* OptionalWorkerPool,
uint64_t LargeSizeLimit)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
return m_CidStore.IterateChunks(
RawHashes,
[&](size_t Index, const IoBuffer& Payload) {
@@ -2090,12 +2198,14 @@ ProjectStore::Oplog::IterateChunks(std::span<IoHash> RawHashes,
}
bool
-ProjectStore::Oplog::IterateChunks(std::span<Oid> ChunkIds,
+ProjectStore::Oplog::IterateChunks(const std::filesystem::path& ProjectRootDir,
+ std::span<Oid> ChunkIds,
bool IncludeModTag,
const std::function<bool(size_t Index, const IoBuffer& Payload, uint64_t ModTag)>& AsyncCallback,
WorkerThreadPool* OptionalWorkerPool,
uint64_t LargeSizeLimit)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
ZEN_MEMSCOPE(GetProjectstoreTag());
std::vector<size_t> CidChunkIndexes;
@@ -2120,7 +2230,7 @@ ProjectStore::Oplog::IterateChunks(std::span<Oid> ChunkIds,
else if (auto FileIt = m_FileMap.find(ChunkId); FileIt != m_FileMap.end())
{
FileChunkIndexes.push_back(ChunkIndex);
- FileChunkPaths.emplace_back(m_OuterProject->RootDir / FileIt->second.ServerPath);
+ FileChunkPaths.emplace_back(ProjectRootDir / FileIt->second.ServerPath);
}
}
}
@@ -2128,7 +2238,7 @@ ProjectStore::Oplog::IterateChunks(std::span<Oid> ChunkIds,
{
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
try
{
for (size_t ChunkIndex = 0; ChunkIndex < FileChunkIndexes.size(); ChunkIndex++)
@@ -2165,7 +2275,7 @@ ProjectStore::Oplog::IterateChunks(std::span<Oid> ChunkIds,
catch (const std::exception& Ex)
{
ZEN_WARN("oplog '{}/{}': exception caught when iterating file chunk {}, path '{}'. Reason: '{}'",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
FileChunkIndex,
FilePath,
@@ -2235,8 +2345,10 @@ ProjectStore::Oplog::IterateChunks(std::span<Oid> ChunkIds,
}
IoBuffer
-ProjectStore::Oplog::FindChunk(const Oid& ChunkId, uint64_t* OptOutModificationTag)
+ProjectStore::Oplog::FindChunk(const std::filesystem::path& ProjectRootDir, const Oid& ChunkId, uint64_t* OptOutModificationTag)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
RwLock::SharedLockScope OplogLock(m_OplogLock);
if (!m_Storage)
{
@@ -2258,7 +2370,7 @@ ProjectStore::Oplog::FindChunk(const Oid& ChunkId, uint64_t* OptOutModificationT
if (auto FileIt = m_FileMap.find(ChunkId); FileIt != m_FileMap.end())
{
- std::filesystem::path FilePath = m_OuterProject->RootDir / FileIt->second.ServerPath;
+ std::filesystem::path FilePath = ProjectRootDir / FileIt->second.ServerPath;
OplogLock.ReleaseNow();
@@ -2291,8 +2403,10 @@ ProjectStore::Oplog::FindChunk(const Oid& ChunkId, uint64_t* OptOutModificationT
}
std::vector<ProjectStore::Oplog::ChunkInfo>
-ProjectStore::Oplog::GetAllChunksInfo()
+ProjectStore::Oplog::GetAllChunksInfo(const std::filesystem::path& ProjectRootDir)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
ZEN_MEMSCOPE(GetProjectstoreTag());
// First just capture all the chunk ids
@@ -2322,7 +2436,7 @@ ProjectStore::Oplog::GetAllChunksInfo()
for (ChunkInfo& Info : InfoArray)
{
- if (IoBuffer Chunk = FindChunk(Info.ChunkId, nullptr))
+ if (IoBuffer Chunk = FindChunk(ProjectRootDir, Info.ChunkId, nullptr))
{
Info.ChunkSize = Chunk.GetSize();
}
@@ -2334,6 +2448,8 @@ ProjectStore::Oplog::GetAllChunksInfo()
void
ProjectStore::Oplog::IterateChunkMap(std::function<void(const Oid&, const IoHash&)>&& Fn)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
RwLock::SharedLockScope _(m_OplogLock);
if (!m_Storage)
{
@@ -2350,6 +2466,8 @@ void
ProjectStore::Oplog::IterateFileMap(
std::function<void(const Oid&, const std::string_view& ServerPath, const std::string_view& ClientPath)>&& Fn)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
RwLock::SharedLockScope _(m_OplogLock);
if (!m_Storage)
{
@@ -2369,15 +2487,44 @@ ProjectStore::Oplog::IterateOplog(std::function<void(CbObjectView)>&& Handler, c
IterateOplogLocked(std::move(Handler), EntryPaging);
}
-template<typename ContainerElement>
-std::span<ContainerElement>
-CreateSpanFromPaging(std::vector<ContainerElement>& Container, const ProjectStore::Oplog::Paging& Paging)
+std::vector<ProjectStore::Oplog::PayloadIndex>
+ProjectStore::Oplog::GetSortedOpPayloadRangeLocked(const Paging& EntryPaging,
+ tsl::robin_map<PayloadIndex, Oid, PayloadIndex::Hasher>* OutOptionalReverseKeyMap)
{
- std::span<ContainerElement> Span(Container);
- int32_t Size = int32_t(Container.size());
- int32_t Start = std::clamp(Paging.Start, 0, Size);
- int32_t End = Paging.Count < 0 ? Size : (Start + std::min<int32_t>(Paging.Count, Size - Start));
- return Span.subspan(Start, End - Start);
+ std::pair<int32_t, int32_t> StartAndEnd = GetPagedRange(int32_t(m_OpToPayloadOffsetMap.size()), EntryPaging);
+ if (StartAndEnd.first == StartAndEnd.second)
+ {
+ return {};
+ }
+
+ const int32_t ReplayCount = StartAndEnd.second - StartAndEnd.first;
+
+ auto Start = m_OpToPayloadOffsetMap.cbegin();
+ std::advance(Start, StartAndEnd.first);
+
+ auto End = Start;
+ std::advance(End, ReplayCount);
+
+ std::vector<PayloadIndex> ReplayOrder;
+ ReplayOrder.reserve(ReplayCount);
+
+ for (auto It = Start; It != End; It++)
+ {
+ const PayloadIndex PayloadOffset = It->second;
+ if (OutOptionalReverseKeyMap != nullptr)
+ {
+ OutOptionalReverseKeyMap->insert_or_assign(PayloadOffset, It->first);
+ }
+ ReplayOrder.push_back(PayloadOffset);
+ }
+
+ std::sort(ReplayOrder.begin(), ReplayOrder.end(), [this](const PayloadIndex Lhs, const PayloadIndex Rhs) {
+ const OplogEntryAddress& LhsEntry = m_OpLogPayloads[Lhs].Address;
+ const OplogEntryAddress& RhsEntry = m_OpLogPayloads[Rhs].Address;
+ return LhsEntry.Offset < RhsEntry.Offset;
+ });
+
+ return ReplayOrder;
}
void
@@ -2390,23 +2537,44 @@ ProjectStore::Oplog::IterateOplogLocked(std::function<void(CbObjectView)>&& Hand
return;
}
- std::vector<OplogEntryAddress> Entries;
- Entries.reserve(m_LatestOpMap.size());
-
- for (const auto& Kv : m_LatestOpMap)
+ std::vector<PayloadIndex> ReplayOrder = GetSortedOpPayloadRangeLocked(EntryPaging, nullptr);
+ if (!ReplayOrder.empty())
{
- const auto AddressEntry = m_OpAddressMap.find(Kv.second);
- ZEN_ASSERT(AddressEntry != m_OpAddressMap.end());
-
- Entries.push_back(AddressEntry->second);
+ m_Storage->ReplayLogEntries(m_OpLogPayloads, ReplayOrder, [&](LogSequenceNumber, CbObjectView Op) { Handler(Op); });
}
+}
- std::sort(Entries.begin(), Entries.end(), [](const OplogEntryAddress& Lhs, const OplogEntryAddress& Rhs) {
- return Lhs.Offset < Rhs.Offset;
- });
+void
+ProjectStore::Oplog::IterateOplogWithKey(std::function<void(LogSequenceNumber, const Oid&, CbObjectView)>&& Handler)
+{
+ IterateOplogWithKey(std::move(Handler), Paging{});
+}
+
+void
+ProjectStore::Oplog::IterateOplogWithKey(std::function<void(LogSequenceNumber, const Oid&, CbObjectView)>&& Handler,
+ const Paging& EntryPaging)
+{
+ ZEN_MEMSCOPE(GetProjectstoreTag());
+
+ tsl::robin_map<PayloadIndex, Oid, PayloadIndex::Hasher> ReverseKeyMap;
+ std::vector<PayloadIndex> ReplayOrder;
- std::span<OplogEntryAddress> EntrySpan = CreateSpanFromPaging(Entries, EntryPaging);
- m_Storage->ReplayLogEntries(EntrySpan, [&](CbObjectView Op) { Handler(Op); });
+ {
+ RwLock::SharedLockScope _(m_OplogLock);
+ if (m_Storage)
+ {
+ ReplayOrder = GetSortedOpPayloadRangeLocked(EntryPaging, &ReverseKeyMap);
+ if (!ReplayOrder.empty())
+ {
+ uint32_t EntryIndex = 0;
+ m_Storage->ReplayLogEntries(m_OpLogPayloads, ReplayOrder, [&](LogSequenceNumber Lsn, CbObjectView Op) {
+ const PayloadIndex PayloadOffset = ReplayOrder[EntryIndex];
+ Handler(Lsn, ReverseKeyMap.at(PayloadOffset), Op);
+ EntryIndex++;
+ });
+ }
+ }
+ }
}
static constexpr uint32_t OplogMetaDataExpectedMagic = 0x6f'74'6d'62; // 'omta';
@@ -2476,7 +2644,7 @@ ProjectStore::Oplog::GetAttachmentsLocked(std::vector<IoHash>& OutAttachments, b
{
m_MetaValid = false;
ZEN_WARN("oplog '{}/{}': unable to set meta data meta path: '{}'. Reason: '{}'",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
MetaPath,
Ec.message());
@@ -2496,80 +2664,10 @@ ProjectStore::Oplog::GetOplogEntryCount() const
{
return 0;
}
- return m_LatestOpMap.size();
+ return m_OpToPayloadOffsetMap.size();
}
-void
-ProjectStore::Oplog::IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Handler)
-{
- IterateOplogWithKey(std::move(Handler), Paging{});
-}
-
-void
-ProjectStore::Oplog::IterateOplogWithKey(std::function<void(uint32_t, const Oid&, CbObjectView)>&& Handler, const Paging& EntryPaging)
-{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- RwLock::SharedLockScope _(m_OplogLock);
- if (!m_Storage)
- {
- return;
- }
-
- std::vector<OplogEntryAddress> SortedEntries;
- std::vector<Oid> SortedKeys;
- std::vector<uint32_t> SortedLSNs;
-
- {
- const auto TargetEntryCount = m_LatestOpMap.size();
-
- std::vector<size_t> EntryIndexes;
- std::vector<OplogEntryAddress> Entries;
- std::vector<Oid> Keys;
- std::vector<uint32_t> LSNs;
-
- Entries.reserve(TargetEntryCount);
- EntryIndexes.reserve(TargetEntryCount);
- Keys.reserve(TargetEntryCount);
- LSNs.reserve(TargetEntryCount);
-
- for (const auto& Kv : m_LatestOpMap)
- {
- const auto AddressEntry = m_OpAddressMap.find(Kv.second);
- ZEN_ASSERT(AddressEntry != m_OpAddressMap.end());
-
- Entries.push_back(AddressEntry->second);
- Keys.push_back(Kv.first);
- LSNs.push_back(Kv.second);
- EntryIndexes.push_back(EntryIndexes.size());
- }
-
- std::sort(EntryIndexes.begin(), EntryIndexes.end(), [&Entries](const size_t& Lhs, const size_t& Rhs) {
- const OplogEntryAddress& LhsEntry = Entries[Lhs];
- const OplogEntryAddress& RhsEntry = Entries[Rhs];
- return LhsEntry.Offset < RhsEntry.Offset;
- });
-
- SortedEntries.reserve(EntryIndexes.size());
- SortedKeys.reserve(EntryIndexes.size());
- SortedLSNs.reserve(EntryIndexes.size());
-
- for (size_t Index : EntryIndexes)
- {
- SortedEntries.push_back(Entries[Index]);
- SortedKeys.push_back(Keys[Index]);
- SortedLSNs.push_back(LSNs[Index]);
- }
- }
-
- std::span<OplogEntryAddress> EntrySpan = CreateSpanFromPaging(SortedEntries, EntryPaging);
- size_t EntryIndex = EntrySpan.empty() ? 0 : static_cast<size_t>(&EntrySpan.front() - &SortedEntries.front());
- m_Storage->ReplayLogEntries(EntrySpan, [&](CbObjectView Op) {
- Handler(SortedLSNs[EntryIndex], SortedKeys[EntryIndex], Op);
- EntryIndex++;
- });
-}
-
-std::optional<uint32_t>
+ProjectStore::LogSequenceNumber
ProjectStore::Oplog::GetOpIndexByKey(const Oid& Key)
{
RwLock::SharedLockScope _(m_OplogLock);
@@ -2577,9 +2675,9 @@ ProjectStore::Oplog::GetOpIndexByKey(const Oid& Key)
{
return {};
}
- if (const auto LatestOp = m_LatestOpMap.find(Key); LatestOp != m_LatestOpMap.end())
+ if (const auto LatestOp = m_OpToPayloadOffsetMap.find(Key); LatestOp != m_OpToPayloadOffsetMap.end())
{
- return LatestOp->second;
+ return m_OpLogPayloads[LatestOp->second].Lsn;
}
return {};
}
@@ -2593,42 +2691,45 @@ ProjectStore::Oplog::GetOpByKey(const Oid& Key)
return {};
}
- if (const auto LatestOp = m_LatestOpMap.find(Key); LatestOp != m_LatestOpMap.end())
+ if (const auto LatestOp = m_OpToPayloadOffsetMap.find(Key); LatestOp != m_OpToPayloadOffsetMap.end())
{
- const auto AddressEntry = m_OpAddressMap.find(LatestOp->second);
- ZEN_ASSERT(AddressEntry != m_OpAddressMap.end());
-
- return m_Storage->GetOp(AddressEntry->second);
+ return m_Storage->GetOp(m_OpLogPayloads[LatestOp->second].Address);
}
return {};
}
std::optional<CbObject>
-ProjectStore::Oplog::GetOpByIndex(uint32_t Index)
+ProjectStore::Oplog::GetOpByIndex(ProjectStore::LogSequenceNumber Index)
{
- RwLock::SharedLockScope _(m_OplogLock);
- if (!m_Storage)
{
- return {};
+ RwLock::SharedLockScope _(m_OplogLock);
+ if (!m_Storage)
+ {
+ return {};
+ }
+
+ if (m_LsnToPayloadOffsetMap)
+ {
+ if (auto It = m_LsnToPayloadOffsetMap->find(Index); It != m_LsnToPayloadOffsetMap->end())
+ {
+ return m_Storage->GetOp(m_OpLogPayloads[It->second].Address);
+ }
+ }
}
- if (const auto AddressEntryIt = m_OpAddressMap.find(Index); AddressEntryIt != m_OpAddressMap.end())
+ RwLock::ExclusiveLockScope Lock(m_OplogLock);
+ if (!m_Storage)
{
- return m_Storage->GetOp(AddressEntryIt->second);
+ return {};
}
- return {};
-}
-
-void
-ProjectStore::Oplog::AddChunkMappings(const std::unordered_map<Oid, IoHash, Oid::Hasher>& ChunkMappings)
-{
- RwLock::ExclusiveLockScope OplogLock(m_OplogLock);
- for (const auto& It : ChunkMappings)
+ RefreshLsnToPayloadOffsetMap(Lock);
+ if (auto It = m_LsnToPayloadOffsetMap->find(Index); It != m_LsnToPayloadOffsetMap->end())
{
- AddChunkMapping(OplogLock, It.first, It.second);
+ return m_Storage->GetOp(m_OpLogPayloads[It->second].Address);
}
+ return {};
}
void
@@ -2653,14 +2754,14 @@ ProjectStore::Oplog::EnableUpdateCapture()
m_OplogLock.WithExclusiveLock([&]() {
if (m_UpdateCaptureRefCounter == 0)
{
- ZEN_ASSERT(!m_CapturedLSNs);
+ ZEN_ASSERT(!m_CapturedOps);
ZEN_ASSERT(!m_CapturedAttachments);
- m_CapturedLSNs = std::make_unique<std::vector<uint32_t>>();
+ m_CapturedOps = std::make_unique<std::vector<Oid>>();
m_CapturedAttachments = std::make_unique<std::vector<IoHash>>();
}
else
{
- ZEN_ASSERT(m_CapturedLSNs);
+ ZEN_ASSERT(m_CapturedOps);
ZEN_ASSERT(m_CapturedAttachments);
}
m_UpdateCaptureRefCounter++;
@@ -2673,34 +2774,36 @@ ProjectStore::Oplog::DisableUpdateCapture()
ZEN_MEMSCOPE(GetProjectstoreTag());
m_OplogLock.WithExclusiveLock([&]() {
- ZEN_ASSERT(m_CapturedLSNs);
+ ZEN_ASSERT(m_CapturedOps);
ZEN_ASSERT(m_CapturedAttachments);
ZEN_ASSERT(m_UpdateCaptureRefCounter > 0);
m_UpdateCaptureRefCounter--;
if (m_UpdateCaptureRefCounter == 0)
{
- m_CapturedLSNs.reset();
+ m_CapturedOps.reset();
m_CapturedAttachments.reset();
}
});
}
void
-ProjectStore::Oplog::IterateCapturedLSNsLocked(std::function<bool(const CbObjectView& UpdateOp)>&& Callback)
+ProjectStore::Oplog::IterateCapturedOpsLocked(
+ std::function<bool(const Oid& Key, ProjectStore::LogSequenceNumber LSN, const CbObjectView& UpdateOp)>&& Callback)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- if (m_CapturedLSNs)
+ if (m_CapturedOps)
{
if (!m_Storage)
{
return;
}
- for (uint32_t UpdatedLSN : *m_CapturedLSNs)
+ for (const Oid& OpKey : *m_CapturedOps)
{
- if (const auto AddressEntryIt = m_OpAddressMap.find(UpdatedLSN); AddressEntryIt != m_OpAddressMap.end())
+ if (const auto AddressEntryIt = m_OpToPayloadOffsetMap.find(OpKey); AddressEntryIt != m_OpToPayloadOffsetMap.end())
{
- Callback(m_Storage->GetOp(AddressEntryIt->second));
+ const OplogPayload& OpPayload = m_OpLogPayloads[AddressEntryIt->second];
+ Callback(OpKey, OpPayload.Lsn, m_Storage->GetOp(OpPayload.Address));
}
}
}
@@ -2840,7 +2943,7 @@ ProjectStore::Oplog::GetMapping(CbObjectView Core)
Oid Id = PackageObj["id"sv].AsObjectId();
IoHash Hash = PackageObj["data"sv].AsBinaryAttachment();
Result.Chunks.emplace_back(ChunkMapping{Id, Hash});
- ZEN_DEBUG("oplog {}/{}: package data {} -> {}", m_OuterProject->Identifier, m_OplogId, Id, Hash);
+ ZEN_DEBUG("oplog {}/{}: package data {} -> {}", m_OuterProjectId, m_OplogId, Id, Hash);
continue;
}
if (FieldName == "bulkdata"sv)
@@ -2852,7 +2955,7 @@ ProjectStore::Oplog::GetMapping(CbObjectView Core)
Oid Id = BulkObj["id"sv].AsObjectId();
IoHash Hash = BulkObj["data"sv].AsBinaryAttachment();
Result.Chunks.emplace_back(ChunkMapping{Id, Hash});
- ZEN_DEBUG("oplog {}/{}: bulkdata {} -> {}", m_OuterProject->Identifier, m_OplogId, Id, Hash);
+ ZEN_DEBUG("oplog {}/{}: bulkdata {} -> {}", m_OuterProjectId, m_OplogId, Id, Hash);
}
continue;
}
@@ -2865,7 +2968,7 @@ ProjectStore::Oplog::GetMapping(CbObjectView Core)
Oid Id = PackageDataObj["id"sv].AsObjectId();
IoHash Hash = PackageDataObj["data"sv].AsBinaryAttachment();
Result.Chunks.emplace_back(ChunkMapping{Id, Hash});
- ZEN_DEBUG("oplog {}/{}: package {} -> {}", m_OuterProject->Identifier, m_OplogId, Id, Hash);
+ ZEN_DEBUG("oplog {}/{}: package {} -> {}", m_OuterProjectId, m_OplogId, Id, Hash);
}
continue;
}
@@ -2884,23 +2987,20 @@ ProjectStore::Oplog::GetMapping(CbObjectView Core)
if (ServerPath.empty() && Hash == IoHash::Zero)
{
ZEN_WARN("oplog {}/{}: invalid file for entry '{}', missing both 'serverpath' and 'data' fields",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
Id);
continue;
}
if (ClientPath.empty())
{
- ZEN_WARN("oplog {}/{}: invalid file for entry '{}', missing 'clientpath' field",
- m_OuterProject->Identifier,
- m_OplogId,
- Id);
+ ZEN_WARN("oplog {}/{}: invalid file for entry '{}', missing 'clientpath' field", m_OuterProjectId, m_OplogId, Id);
continue;
}
Result.Files.emplace_back(FileMapping{Id, Hash, std::string(ServerPath), std::string(ClientPath)});
ZEN_DEBUG("oplog {}/{}: file {} -> {}, ServerPath: {}, ClientPath: {}",
- m_OuterProject->Identifier,
+ m_OuterProjectId,
m_OplogId,
Id,
Hash,
@@ -2920,7 +3020,7 @@ ProjectStore::Oplog::GetMapping(CbObjectView Core)
IoHash Hash = MetaObj["data"sv].AsBinaryAttachment();
Result.Meta.emplace_back(ChunkMapping{Id, Hash});
auto NameString = MetaObj["name"sv].AsString();
- ZEN_DEBUG("oplog {}/{}: meta data ({}) {} -> {}", m_OuterProject->Identifier, m_OplogId, NameString, Id, Hash);
+ ZEN_DEBUG("oplog {}/{}: meta data ({}) {} -> {}", m_OuterProjectId, m_OplogId, NameString, Id, Hash);
}
continue;
}
@@ -2929,7 +3029,7 @@ ProjectStore::Oplog::GetMapping(CbObjectView Core)
return Result;
}
-uint32_t
+ProjectStore::LogSequenceNumber
ProjectStore::Oplog::RegisterOplogEntry(RwLock::ExclusiveLockScope& OplogLock,
const OplogEntryMapping& OpMapping,
const OplogEntry& OpEntry)
@@ -2956,20 +3056,28 @@ ProjectStore::Oplog::RegisterOplogEntry(RwLock::ExclusiveLockScope& OplogLock,
AddMetaMapping(OplogLock, Meta.Id, Meta.Hash);
}
- m_OpAddressMap.emplace(OpEntry.OpLsn, OplogEntryAddress{.Offset = OpEntry.OpCoreOffset, .Size = OpEntry.OpCoreSize});
- m_LatestOpMap[OpEntry.OpKeyHash] = OpEntry.OpLsn;
+ const PayloadIndex PayloadOffset(m_OpLogPayloads.size());
+ m_OpToPayloadOffsetMap.insert_or_assign(OpEntry.OpKeyHash, PayloadOffset);
+ m_OpLogPayloads.push_back({.Lsn = OpEntry.OpLsn, .Address = OpEntry.OpCoreAddress});
+
+ if (m_LsnToPayloadOffsetMap)
+ {
+ m_LsnToPayloadOffsetMap->insert_or_assign(OpEntry.OpLsn, PayloadOffset);
+ }
return OpEntry.OpLsn;
}
-uint32_t
+ProjectStore::LogSequenceNumber
ProjectStore::Oplog::AppendNewOplogEntry(CbPackage OpPackage)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("Store::Oplog::AppendNewOplogEntry");
- const CbObject& Core = OpPackage.GetObject();
- const uint32_t EntryId = AppendNewOplogEntry(Core);
- if (EntryId == 0xffffffffu)
+ const CbObject& Core = OpPackage.GetObject();
+ const ProjectStore::LogSequenceNumber EntryId = AppendNewOplogEntry(Core);
+ if (!EntryId)
{
// The oplog has been deleted so just drop this
return EntryId;
@@ -3014,7 +3122,7 @@ ProjectStore::Oplog::AppendNewOplogEntry(CbPackage OpPackage)
}
}
- ZEN_DEBUG("oplog entry #{} attachments: {} new, {} total", EntryId, NiceBytes(NewAttachmentBytes), NiceBytes(AttachmentBytes));
+ ZEN_DEBUG("oplog entry #{} attachments: {} new, {} total", EntryId.Number, NiceBytes(NewAttachmentBytes), NiceBytes(AttachmentBytes));
return EntryId;
}
@@ -3031,9 +3139,11 @@ ProjectStore::Oplog::GetStorage()
return Storage;
}
-uint32_t
+ProjectStore::LogSequenceNumber
ProjectStore::Oplog::AppendNewOplogEntry(CbObjectView Core)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("Store::Oplog::AppendNewOplogEntry");
@@ -3042,7 +3152,7 @@ ProjectStore::Oplog::AppendNewOplogEntry(CbObjectView Core)
RefPtr<OplogStorage> Storage = GetStorage();
if (!Storage)
{
- return 0xffffffffu;
+ return {};
}
OplogEntryMapping Mapping = GetMapping(Core);
@@ -3050,20 +3160,22 @@ ProjectStore::Oplog::AppendNewOplogEntry(CbObjectView Core)
const OplogEntry OpEntry = Storage->AppendOp(OpData);
- RwLock::ExclusiveLockScope OplogLock(m_OplogLock);
- const uint32_t EntryId = RegisterOplogEntry(OplogLock, Mapping, OpEntry);
- if (m_CapturedLSNs)
+ RwLock::ExclusiveLockScope OplogLock(m_OplogLock);
+ const ProjectStore::LogSequenceNumber EntryId = RegisterOplogEntry(OplogLock, Mapping, OpEntry);
+ if (m_CapturedOps)
{
- m_CapturedLSNs->push_back(EntryId);
+ m_CapturedOps->push_back(OpData.KeyHash);
}
m_MetaValid = false;
return EntryId;
}
-std::vector<uint32_t>
+std::vector<ProjectStore::LogSequenceNumber>
ProjectStore::Oplog::AppendNewOplogEntries(std::span<CbObjectView> Cores)
{
+ ZEN_ASSERT(m_Mode == EMode::kFull);
+
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("Store::Oplog::AppendNewOplogEntries");
@@ -3072,7 +3184,7 @@ ProjectStore::Oplog::AppendNewOplogEntries(std::span<CbObjectView> Cores)
RefPtr<OplogStorage> Storage = GetStorage();
if (!Storage)
{
- return std::vector<uint32_t>(Cores.size(), 0xffffffffu);
+ return std::vector<ProjectStore::LogSequenceNumber>(Cores.size(), LogSequenceNumber{});
}
size_t OpCount = Cores.size();
@@ -3090,21 +3202,21 @@ ProjectStore::Oplog::AppendNewOplogEntries(std::span<CbObjectView> Cores)
std::vector<OplogEntry> OpEntries = Storage->AppendOps(OpDatas);
- std::vector<uint32_t> EntryIds;
+ std::vector<ProjectStore::LogSequenceNumber> EntryIds;
EntryIds.resize(OpCount);
{
{
RwLock::ExclusiveLockScope OplogLock(m_OplogLock);
- if (m_CapturedLSNs)
+ if (m_CapturedOps)
{
- m_CapturedLSNs->reserve(m_CapturedLSNs->size() + OpCount);
+ m_CapturedOps->reserve(m_CapturedOps->size() + OpCount);
}
for (size_t OpIndex = 0; OpIndex < OpCount; OpIndex++)
{
EntryIds[OpIndex] = RegisterOplogEntry(OplogLock, Mappings[OpIndex], OpEntries[OpIndex]);
- if (m_CapturedLSNs)
+ if (m_CapturedOps)
{
- m_CapturedLSNs->push_back(EntryIds[OpIndex]);
+ m_CapturedOps->push_back(OpDatas[OpIndex].KeyHash);
}
}
}
@@ -3321,7 +3433,7 @@ ProjectStore::Project::BasePathForOplog(std::string_view OplogId) const
return m_OplogStoragePath / OplogId;
}
-ProjectStore::Oplog*
+Ref<ProjectStore::Oplog>
ProjectStore::Project::NewOplog(std::string_view OplogId, const std::filesystem::path& MarkerPath)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
@@ -3331,21 +3443,25 @@ ProjectStore::Project::NewOplog(std::string_view OplogId, const std::filesystem:
try
{
- ZEN_INFO("oplog '{}/{}': creating oplog at '{}'", Identifier, OplogId, OplogBasePath);
+ Stopwatch Timer;
- Oplog* Log = m_Oplogs
- .try_emplace(std::string{OplogId},
- std::make_unique<ProjectStore::Oplog>(OplogId, this, m_CidStore, OplogBasePath, MarkerPath))
- .first->second.get();
+ Ref<Oplog> NewLog(new Oplog(Log(), Identifier, OplogId, m_CidStore, OplogBasePath, MarkerPath, Oplog::EMode::kFull));
+ m_Oplogs.insert_or_assign(std::string{OplogId}, NewLog);
- Log->Write();
+ NewLog->Write();
+
+ ZEN_INFO("oplog '{}/{}': created oplog at '{}' in {}",
+ Identifier,
+ OplogId,
+ OplogBasePath,
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
if (m_CapturedOplogs)
{
m_CapturedOplogs->push_back(std::string(OplogId));
}
- return Log;
+ return NewLog;
}
catch (const std::exception&)
{
@@ -3355,11 +3471,55 @@ ProjectStore::Project::NewOplog(std::string_view OplogId, const std::filesystem:
m_Oplogs.erase(std::string{OplogId});
- return nullptr;
+ return {};
}
}
-ProjectStore::Oplog*
+Ref<ProjectStore::Oplog>
+ProjectStore::Project::ReadOplog(std::string_view OplogId)
+{
+ ZEN_MEMSCOPE(GetProjectstoreTag());
+ ZEN_TRACE_CPU("Store::OpenOplog");
+
+ std::filesystem::path OplogBasePath = BasePathForOplog(OplogId);
+
+ RwLock::SharedLockScope Lock(m_ProjectLock);
+
+ if (auto It = m_Oplogs.find(std::string{OplogId}); It != m_Oplogs.end())
+ {
+ if (Oplog::ExistsAt(OplogBasePath))
+ {
+ return It->second;
+ }
+ else
+ {
+ return {};
+ }
+ }
+
+ if (Oplog::ExistsAt(OplogBasePath))
+ {
+ Stopwatch Timer;
+
+ Ref<Oplog> ExistingLog(new Oplog(m_ProjectStore->Log(),
+ Identifier,
+ OplogId,
+ m_CidStore,
+ OplogBasePath,
+ std::filesystem::path{},
+ Oplog::EMode::kBasicReadOnly));
+
+ ExistingLog->Read();
+ Lock.ReleaseNow();
+
+ ZEN_INFO("oplog '{}/{}': read oplog at '{}' in {}", Identifier, OplogId, OplogBasePath, NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+
+ return ExistingLog;
+ }
+ return {};
+}
+
+Ref<ProjectStore::Oplog>
ProjectStore::Project::OpenOplog(std::string_view OplogId, bool AllowCompact, bool VerifyPathOnDisk)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
@@ -3394,7 +3554,7 @@ ProjectStore::Project::OpenOplog(std::string_view OplogId, bool AllowCompact, bo
if (!ReOpen)
{
- return OplogIt->second.get();
+ return OplogIt->second;
}
}
}
@@ -3404,30 +3564,40 @@ ProjectStore::Project::OpenOplog(std::string_view OplogId, bool AllowCompact, bo
RwLock::ExclusiveLockScope Lock(m_ProjectLock);
if (auto It = m_Oplogs.find(std::string{OplogId}); It != m_Oplogs.end())
{
- return It->second.get();
+ return It->second;
}
if (Oplog::ExistsAt(OplogBasePath))
{
try
{
- ZEN_INFO("oplog '{}/{}': opening oplog at '{}'", Identifier, OplogId, OplogBasePath);
+ Stopwatch Timer;
+
+ Ref<Oplog> ExistingLog(new Oplog(m_ProjectStore->Log(),
+ Identifier,
+ OplogId,
+ m_CidStore,
+ OplogBasePath,
+ std::filesystem::path{},
+ Oplog::EMode::kFull));
- Oplog* Log =
- m_Oplogs
- .try_emplace(std::string{OplogId},
- std::make_unique<ProjectStore::Oplog>(OplogId, this, m_CidStore, OplogBasePath, std::filesystem::path{}))
- .first->second.get();
- Log->Read();
+ m_Oplogs.insert_or_assign(std::string{OplogId}, ExistingLog);
+ ExistingLog->Read();
Lock.ReleaseNow();
+ ZEN_INFO("oplog '{}/{}': opened oplog at '{}' in {}",
+ Identifier,
+ OplogId,
+ OplogBasePath,
+ NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
+
if (AllowCompact)
{
const uint32_t CompactUnusedThreshold = 50;
- Log->CompactIfUnusedExceeds(/*DryRun*/ false,
- CompactUnusedThreshold,
- fmt::format("Compact on initial open of oplog {}/{}: ", Identifier, OplogId));
+ ExistingLog->CompactIfUnusedExceeds(/*DryRun*/ false,
+ CompactUnusedThreshold,
+ fmt::format("Compact on initial open of oplog {}/{}: ", Identifier, OplogId));
}
- return Log;
+ return ExistingLog;
}
catch (const std::exception& Ex)
{
@@ -3436,7 +3606,7 @@ ProjectStore::Project::OpenOplog(std::string_view OplogId, bool AllowCompact, bo
}
}
- return nullptr;
+ return {};
}
void
@@ -3460,6 +3630,27 @@ ProjectStore::Oplog::CompactIfUnusedExceeds(bool DryRun, uint32_t CompactUnusedT
}
bool
+ProjectStore::Project::TryUnloadOplog(std::string_view OplogId)
+{
+ ZEN_MEMSCOPE(GetProjectstoreTag());
+
+ RwLock::ExclusiveLockScope _(m_ProjectLock);
+ if (auto OplogIt = m_Oplogs.find(std::string(OplogId)); OplogIt != m_Oplogs.end())
+ {
+ Ref<Oplog>& Oplog = OplogIt->second;
+
+ if (Oplog->CanUnload())
+ {
+ m_Oplogs.erase(OplogIt);
+ return true;
+ }
+ return false;
+ }
+
+ return false;
+}
+
+bool
ProjectStore::Project::RemoveOplog(std::string_view OplogId, std::filesystem::path& OutDeletePath)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
@@ -3480,12 +3671,11 @@ ProjectStore::Project::RemoveOplog(std::string_view OplogId, std::filesystem::pa
}
else
{
- std::unique_ptr<Oplog>& Oplog = OplogIt->second;
+ Ref<Oplog>& Oplog = OplogIt->second;
if (!Oplog->PrepareForDelete(OutDeletePath))
{
return false;
}
- m_DeletedOplogs.emplace_back(std::move(Oplog));
m_Oplogs.erase(OplogIt);
}
}
@@ -3529,6 +3719,11 @@ ProjectStore::Project::ScanForOplogs() const
Oplogs.reserve(DirContent.Directories.size());
for (const std::filesystem::path& DirPath : DirContent.Directories)
{
+ std::string DirName = PathToUtf8(DirPath.filename());
+ if (DirName.starts_with("[dropped]"))
+ {
+ continue;
+ }
if (Oplog::ExistsAt(DirPath))
{
Oplogs.push_back(DirPath.filename().string());
@@ -3638,7 +3833,6 @@ ProjectStore::Project::PrepareForDelete(std::filesystem::path& OutDeletePath)
for (auto& It : m_Oplogs)
{
It.second->ResetState();
- m_DeletedOplogs.emplace_back(std::move(It.second));
}
m_Oplogs.clear();
@@ -3790,7 +3984,7 @@ ProjectStore::Project::IsExpired(const GcClock::TimePoint ExpireTime, std::strin
if (OplogIt != m_Oplogs.end())
{
Lock.ReleaseNow();
- return IsExpired(ExpireTime, *OplogIt->second.get());
+ return IsExpired(ExpireTime, *OplogIt->second);
}
}
@@ -3832,17 +4026,10 @@ ProjectStore::Project::LastOplogAccessTime(std::string_view Oplog) const
//////////////////////////////////////////////////////////////////////////
-ProjectStore::ProjectStore(CidStore& Store,
- std::filesystem::path BasePath,
- GcManager& Gc,
- JobQueue& JobQueue,
- OpenProcessCache& InOpenProcessCache,
- const Configuration& Config)
+ProjectStore::ProjectStore(CidStore& Store, std::filesystem::path BasePath, GcManager& Gc, const Configuration& Config)
: m_Log(logging::Get("project"))
, m_Gc(Gc)
, m_CidStore(Store)
-, m_JobQueue(JobQueue)
-, m_OpenProcessCache(InOpenProcessCache)
, m_ProjectBasePath(BasePath)
, m_Config(Config)
, m_DiskWriteBlocker(Gc.GetDiskWriteBlocker())
@@ -3923,21 +4110,24 @@ ProjectStore::Flush()
WorkerThreadPool& WorkerPool = GetSmallWorkerPool(EWorkloadType::Burst);
std::atomic<bool> AbortFlag;
std::atomic<bool> PauseFlag;
- ParallelWork Work(AbortFlag, PauseFlag);
+ ParallelWork Work(AbortFlag, PauseFlag, WorkerThreadPool::EMode::DisableBacklog);
try
{
for (const Ref<Project>& Project : Projects)
{
- Work.ScheduleWork(WorkerPool, [this, Project](std::atomic<bool>&) {
- try
- {
- Project->Flush();
- }
- catch (const std::exception& Ex)
- {
- ZEN_WARN("Exception while flushing project {}: {}", Project->Identifier, Ex.what());
- }
- });
+ Work.ScheduleWork(
+ WorkerPool,
+ [this, Project](std::atomic<bool>&) {
+ try
+ {
+ Project->Flush();
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_WARN("Exception while flushing project {}: {}", Project->Identifier, Ex.what());
+ }
+ },
+ 0);
}
}
catch (const std::exception& Ex)
@@ -4189,7 +4379,7 @@ ProjectStore::GetProjectsList()
CbWriter Response;
Response.BeginArray();
- IterateProjects([&Response](ProjectStore::Project& Prj) {
+ IterateProjects([&Response](Project& Prj) {
Response.BeginObject();
Response << "Id"sv << Prj.Identifier;
Response << "RootDir"sv << Prj.RootDir.string();
@@ -4202,31 +4392,13 @@ ProjectStore::GetProjectsList()
return Response.Save().AsArray();
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::GetProjectFiles(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::unordered_set<std::string>& WantedFieldNames,
- CbObject& OutPayload)
+CbObject
+ProjectStore::GetProjectFiles(LoggerRef InLog, Project& Project, Oplog& Oplog, const std::unordered_set<std::string>& WantedFieldNames)
{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::GetProjectFiles");
+ auto Log = [&InLog]() { return InLog; };
using namespace std::literals;
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("Project files request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
-
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
- if (!FoundLog)
- {
- return {HttpResponseCode::NotFound, fmt::format("Project files for unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
-
const bool WantsAllFields = WantedFieldNames.empty();
const bool WantsIdField = WantsAllFields || WantedFieldNames.contains("id");
@@ -4242,7 +4414,7 @@ ProjectStore::GetProjectFiles(const std::string_view ProjectId,
std::vector<uint64_t> RawSizes;
size_t Count = 0;
- FoundLog->IterateFileMap([&](const Oid& Id, const std::string_view& ServerPath, const std::string_view& ClientPath) {
+ Oplog.IterateFileMap([&](const Oid& Id, const std::string_view& ServerPath, const std::string_view& ClientPath) {
if (WantsIdField || WantsRawSizeField || WantsSizeField)
{
Ids.push_back(Id);
@@ -4268,7 +4440,8 @@ ProjectStore::GetProjectFiles(const std::string_view ProjectId,
RawSizes.resize(Ids.size(), (uint64_t)-1);
}
- FoundLog->IterateChunks(
+ Oplog.IterateChunks(
+ Project.RootDir,
Ids,
false,
[&](size_t Index, const IoBuffer& Payload, uint64_t /*ModTag*/) {
@@ -4291,8 +4464,8 @@ ProjectStore::GetProjectFiles(const std::string_view ProjectId,
else
{
ZEN_WARN("oplog '{}/{}': payload for project file info for id {} is not a valid compressed binary.",
- ProjectId,
- OplogId,
+ Project.Identifier,
+ Oplog.OplogId(),
Ids[Index]);
}
}
@@ -4315,14 +4488,17 @@ ProjectStore::GetProjectFiles(const std::string_view ProjectId,
}
else
{
- ZEN_WARN("oplog '{}/{}': failed getting payload for project file info for id {}.", ProjectId, OplogId, Ids[Index]);
+ ZEN_WARN("oplog '{}/{}': failed getting payload for project file info for id {}.",
+ Project.Identifier,
+ Oplog.OplogId(),
+ Ids[Index]);
}
}
catch (const std::exception& Ex)
{
ZEN_WARN("oplog '{}/{}': failed getting project file info for id {}. Reason: '{}'",
- ProjectId,
- OplogId,
+ Project.Identifier,
+ Oplog.OplogId(),
Ids[Index],
Ex.what());
}
@@ -4362,34 +4538,18 @@ ProjectStore::GetProjectFiles(const std::string_view ProjectId,
}
Response.EndArray();
- OutPayload = Response.Save();
- return {HttpResponseCode::OK, {}};
+ return Response.Save();
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::unordered_set<std::string>& WantedFieldNames,
- CbObject& OutPayload)
+CbObject
+ProjectStore::GetProjectChunkInfos(LoggerRef InLog, Project& Project, Oplog& Oplog, const std::unordered_set<std::string>& WantedFieldNames)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
ZEN_TRACE_CPU("ProjectStore::GetProjectChunkInfos");
- using namespace std::literals;
-
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
+ auto Log = [&InLog]() { return InLog; };
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
- if (!FoundLog)
- {
- return {HttpResponseCode::NotFound, fmt::format("unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
+ using namespace std::literals;
const bool WantsAllFields = WantedFieldNames.empty();
@@ -4404,7 +4564,7 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
std::vector<uint64_t> Sizes;
size_t Count = 0;
- size_t EstimatedCount = FoundLog->OplogCount();
+ size_t EstimatedCount = Oplog.OplogCount();
if (WantsIdField)
{
Ids.reserve(EstimatedCount);
@@ -4413,7 +4573,7 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
{
Hashes.reserve(EstimatedCount);
}
- FoundLog->IterateChunkMap([&](const Oid& Id, const IoHash& Hash) {
+ Oplog.IterateChunkMap([&](const Oid& Id, const IoHash& Hash) {
if (WantsIdField)
{
Ids.push_back(Id);
@@ -4429,15 +4589,15 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
{
if (WantsRawSizeField)
{
- RawSizes.resize(Hashes.size(), 0u);
+ RawSizes.resize(Hashes.size(), (uint64_t)-1);
}
if (WantsSizeField)
{
- Sizes.resize(Hashes.size(), 0u);
+ Sizes.resize(Hashes.size(), (uint64_t)-1);
}
WorkerThreadPool& WorkerPool = GetSmallWorkerPool(EWorkloadType::Burst); // GetSyncWorkerPool();
- (void)FoundLog->IterateChunks(
+ (void)Oplog.IterateChunks(
Hashes,
false,
[&](size_t Index, const IoBuffer& Payload, uint64_t /*ModTag*/) -> bool {
@@ -4449,24 +4609,27 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
{
if (WantsRawSizeField)
{
+ ZEN_ASSERT_SLOW(RawSizes[Index] == (uint64_t)-1);
IoHash _;
if (CompressedBuffer::ValidateCompressedHeader(Payload, _, RawSizes[Index]))
{
if (WantsSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
Sizes[Index] = Payload.GetSize();
}
}
else
{
ZEN_WARN("oplog '{}/{}': payload for project chunk for id {} is not a valid compressed binary.",
- ProjectId,
- OplogId,
+ Project.Identifier,
+ Oplog.OplogId(),
Ids[Index]);
}
}
else if (WantsSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
Sizes[Index] = Payload.GetSize();
}
}
@@ -4474,24 +4637,29 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
{
if (WantsSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
Sizes[Index] = Payload.GetSize();
}
if (WantsRawSizeField)
{
+ ZEN_ASSERT_SLOW(Sizes[Index] == (uint64_t)-1);
RawSizes[Index] = Payload.GetSize();
}
}
}
else
{
- ZEN_WARN("oplog '{}/{}': failed getting payload for chunk for id {}", ProjectId, OplogId, Ids[Index]);
+ ZEN_WARN("oplog '{}/{}': failed getting payload for chunk for id {}",
+ Project.Identifier,
+ Oplog.OplogId(),
+ Ids[Index]);
}
}
catch (const std::exception& Ex)
{
ZEN_WARN("oplog '{}/{}': failed getting project chunk info for id {}. Reason: '{}'",
- ProjectId,
- OplogId,
+ Project.Identifier,
+ Oplog.OplogId(),
Ids[Index],
Ex.what());
}
@@ -4529,110 +4697,63 @@ ProjectStore::GetProjectChunkInfos(const std::string_view ProjectId,
}
Response.EndArray();
- OutPayload = Response.Save();
- return {HttpResponseCode::OK, {}};
+ return Response.Save();
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::GetChunkInfo(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view ChunkId,
- CbObject& OutPayload)
+CbObject
+ProjectStore::GetChunkInfo(LoggerRef InLog, Project& Project, Oplog& Oplog, const Oid& ChunkId)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- using namespace std::literals;
-
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("Chunk info request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
+ ZEN_TRACE_CPU("ProjectStore::GetChunkInfo");
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
- if (!FoundLog)
- {
- return {HttpResponseCode::NotFound, fmt::format("Chunk info request for unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
-
- if (ChunkId.size() != 2 * sizeof(Oid::OidBits))
- {
- return {HttpResponseCode::BadRequest,
- fmt::format("Chunk info request for invalid chunk id '{}/{}'/'{}'", ProjectId, OplogId, ChunkId)};
- }
+ using namespace std::literals;
- const Oid Obj = Oid::FromHexString(ChunkId);
+ auto Log = [&InLog]() { return InLog; };
- IoBuffer Chunk = FoundLog->FindChunk(Obj, nullptr);
+ IoBuffer Chunk = Oplog.FindChunk(Project.RootDir, ChunkId, nullptr);
if (!Chunk)
{
- return {HttpResponseCode::NotFound, {}};
+ return {};
}
uint64_t ChunkSize = Chunk.GetSize();
- if (Chunk.GetContentType() == HttpContentType::kCompressedBinary)
+ if (Chunk.GetContentType() == ZenContentType::kCompressedBinary)
{
IoHash RawHash;
uint64_t RawSize;
bool IsCompressed = CompressedBuffer::ValidateCompressedHeader(Chunk, RawHash, RawSize);
if (!IsCompressed)
{
- return {HttpResponseCode::InternalServerError,
- fmt::format("Chunk info request for malformed chunk id '{}/{}'/'{}'", ProjectId, OplogId, ChunkId)};
+ throw std::runtime_error(
+ fmt::format("Chunk info request for malformed chunk id '{}/{}'/'{}'", Project.Identifier, Oplog.OplogId(), ChunkId));
}
ChunkSize = RawSize;
}
CbObjectWriter Response;
Response << "size"sv << ChunkSize;
- OutPayload = Response.Save();
- return {HttpResponseCode::OK, {}};
+ return Response.Save();
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::GetChunkRange(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view ChunkId,
- uint64_t Offset,
- uint64_t Size,
- ZenContentType AcceptType,
- CompositeBuffer& OutChunk,
- ZenContentType& OutContentType,
- uint64_t* OptionalInOutModificationTag)
+static ProjectStore::GetChunkRangeResult
+ExtractRange(IoBuffer&& Chunk, uint64_t Offset, uint64_t Size, ZenContentType AcceptType)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- if (ChunkId.size() != 2 * sizeof(Oid::OidBits))
- {
- return {HttpResponseCode::BadRequest, fmt::format("Chunk request for invalid chunk id '{}/{}'/'{}'", ProjectId, OplogId, ChunkId)};
- }
-
- const Oid Obj = Oid::FromHexString(ChunkId);
- return GetChunkRange(ProjectId, OplogId, Obj, Offset, Size, AcceptType, OutChunk, OutContentType, OptionalInOutModificationTag);
-}
+ ProjectStore::GetChunkRangeResult Result;
-static std::pair<HttpResponseCode, std::string>
-ExtractRange(IoBuffer&& Chunk,
- uint64_t Offset,
- uint64_t Size,
- ZenContentType AcceptType,
- ZenContentType& OutContentType,
- CompositeBuffer& OutChunk,
- IoHash& OutRawHash,
- uint64_t& OutRawSize)
-{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- OutContentType = Chunk.GetContentType();
+ Result.ContentType = Chunk.GetContentType();
- if (OutContentType == ZenContentType::kCompressedBinary)
+ if (Result.ContentType == ZenContentType::kCompressedBinary)
{
IoHash RawHash;
uint64_t RawSize;
CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(std::move(Chunk)), RawHash, RawSize);
if (!Compressed)
{
- return {HttpResponseCode::InternalServerError, "malformed compressed buffer"};
+ Result.Error = ProjectStore::GetChunkRangeResult::EError::MalformedContent;
+ Result.ErrorDescription = fmt::format("Malformed payload, not a compressed buffer");
+ return Result;
}
const bool IsFullRange = (Offset == 0) && ((Size == ~(0ull)) || (Size == RawSize));
@@ -4641,14 +4762,14 @@ ExtractRange(IoBuffer&& Chunk,
{
if (AcceptType == ZenContentType::kBinary)
{
- OutChunk = Compressed.DecompressToComposite();
- OutContentType = ZenContentType::kBinary;
+ Result.Chunk = Compressed.DecompressToComposite();
+ Result.ContentType = ZenContentType::kBinary;
}
else
{
- OutChunk = Compressed.GetCompressed();
+ Result.Chunk = Compressed.GetCompressed();
}
- OutRawSize = 0;
+ Result.RawSize = 0;
}
else
{
@@ -4666,27 +4787,29 @@ ExtractRange(IoBuffer&& Chunk,
if (Size == 0)
{
- return {HttpResponseCode::NotFound,
- fmt::format("Chunk request for range outside of compressed chunk. Offset: {}, Size: {}, ChunkSize: {}",
- Offset,
- Size,
- RawSize)};
+ Result.Error = ProjectStore::GetChunkRangeResult::EError::OutOfRange;
+ Result.ErrorDescription =
+ fmt::format("Chunk request for range outside of compressed chunk. Offset: {}, Size: {}, ChunkSize: {}",
+ Offset,
+ Size,
+ RawSize);
+ return Result;
}
if (AcceptType == ZenContentType::kBinary)
{
- OutChunk = CompositeBuffer(Compressed.Decompress(Offset, Size));
- OutContentType = ZenContentType::kBinary;
+ Result.Chunk = CompositeBuffer(Compressed.Decompress(Offset, Size));
+ Result.ContentType = ZenContentType::kBinary;
}
else
{
// Value will be a range of compressed blocks that covers the requested range
// The client will have to compensate for any offsets that do not land on an even block size multiple
- OutChunk = Compressed.GetRange(Offset, Size).GetCompressed();
+ Result.Chunk = Compressed.GetRange(Offset, Size).GetCompressed();
}
- OutRawSize = RawSize;
+ Result.RawSize = RawSize;
}
- OutRawHash = RawHash;
+ Result.RawHash = RawHash;
}
else
{
@@ -4695,8 +4818,8 @@ ExtractRange(IoBuffer&& Chunk,
const bool IsFullRange = (Offset == 0) && ((Size == ~(0ull)) || (Size == ChunkSize));
if (IsFullRange)
{
- OutChunk = CompositeBuffer(SharedBuffer(std::move(Chunk)));
- OutRawSize = 0;
+ Result.Chunk = CompositeBuffer(SharedBuffer(std::move(Chunk)));
+ Result.RawSize = 0;
}
else
{
@@ -4714,1150 +4837,467 @@ ExtractRange(IoBuffer&& Chunk,
if (Size == 0)
{
- return {HttpResponseCode::NotFound,
- fmt::format("Chunk request for range outside of chunk. Offset: {}, Size: {}, ChunkSize: {}", Offset, Size, Size)};
+ Result.Error = ProjectStore::GetChunkRangeResult::EError::OutOfRange;
+ Result.ErrorDescription =
+ fmt::format("Chunk request for range outside of compressed chunk. Offset: {}, Size: {}, ChunkSize: {}",
+ Offset,
+ Size,
+ ChunkSize);
+ return Result;
}
- OutChunk = CompositeBuffer(SharedBuffer(IoBuffer(std::move(Chunk), Offset, Size)));
- OutRawSize = ChunkSize;
+ Result.Chunk = CompositeBuffer(SharedBuffer(IoBuffer(std::move(Chunk), Offset, Size)));
+ Result.RawSize = ChunkSize;
}
}
- return {HttpResponseCode::OK, {}};
+ Result.Error = ProjectStore::GetChunkRangeResult::EError::Ok;
+ return Result;
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::GetChunkRange(const std::string_view ProjectId,
- const std::string_view OplogId,
- Oid ChunkId,
- uint64_t Offset,
- uint64_t Size,
- ZenContentType AcceptType,
- CompositeBuffer& OutChunk,
- ZenContentType& OutContentType,
- uint64_t* OptionalInOutModificationTag)
+ProjectStore::GetChunkRangeResult
+ProjectStore::GetChunkRange(LoggerRef InLog,
+ Project& Project,
+ Oplog& Oplog,
+ const Oid& ChunkId,
+ uint64_t Offset,
+ uint64_t Size,
+ ZenContentType AcceptType,
+ uint64_t* OptionalInOutModificationTag)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
- if (!FoundLog)
- {
- return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
+ ZEN_TRACE_CPU("ProjectStore::GetChunkRange");
+
+ auto Log = [&InLog]() { return InLog; };
uint64_t OldTag = OptionalInOutModificationTag == nullptr ? 0 : *OptionalInOutModificationTag;
- IoBuffer Chunk = FoundLog->FindChunk(ChunkId, OptionalInOutModificationTag);
+ IoBuffer Chunk = Oplog.FindChunk(Project.RootDir, ChunkId, OptionalInOutModificationTag);
if (!Chunk)
{
- return {HttpResponseCode::NotFound, {}};
+ return GetChunkRangeResult{.Error = GetChunkRangeResult::EError::NotFound,
+ .ErrorDescription = fmt::format("Chunk range request for chunk {}/{}/{} failed, payload not found",
+ Project.Identifier,
+ Oplog.OplogId(),
+ ChunkId)};
}
if (OptionalInOutModificationTag != nullptr && OldTag == *OptionalInOutModificationTag)
{
- return {HttpResponseCode::NotModified, {}};
+ return {.Error = GetChunkRangeResult::EError::NotModified};
}
- IoHash _;
- uint64_t __;
- std::pair<HttpResponseCode, std::string> Result =
- ExtractRange(std::move(Chunk), Offset, Size, AcceptType, OutContentType, OutChunk, /*OutRawHash*/ _, /*OutRawSize*/ __);
- if (Result.first != HttpResponseCode::OK)
- {
- return {Result.first,
- fmt::format("Chunk request for chunk {} in {}/{} failed. Reason: '{}'", ChunkId, ProjectId, OplogId, Result.second)};
- }
- return Result;
+ return ExtractRange(std::move(Chunk), Offset, Size, AcceptType);
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::GetChunk(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view Cid,
- IoBuffer& OutChunk,
- uint64_t* OptionalInOutModificationTag)
+IoBuffer
+ProjectStore::GetChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
+ ZEN_TRACE_CPU("ProjectStore::GetChunk");
+ ZEN_UNUSED(Project, Oplog);
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
- if (!FoundLog)
- {
- return {HttpResponseCode::NotFound, fmt::format("Chunk request for unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
+ IoBuffer Chunk = m_CidStore.FindChunkByCid(ChunkHash);
- if (Cid.length() != IoHash::StringLength)
+ if (!Chunk)
{
- return {HttpResponseCode::BadRequest, fmt::format("Chunk request for invalid chunk id '{}/{}'/'{}'", ProjectId, OplogId, Cid)};
+ return {};
}
- const IoHash Hash = IoHash::FromHexString(Cid);
- OutChunk = m_CidStore.FindChunkByCid(Hash);
+ Chunk.SetContentType(ZenContentType::kCompressedBinary);
+ return Chunk;
+}
+
+IoBuffer
+ProjectStore::GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const Oid& ChunkId)
+{
+ ZEN_MEMSCOPE(GetProjectstoreTag());
+ ZEN_TRACE_CPU("ProjectStore::GetChunk");
- if (!OutChunk)
+ Ref<Project> Project = OpenProject(ProjectId);
+ if (!Project)
{
- return {HttpResponseCode::NotFound, fmt::format("chunk - '{}' MISSING", Cid)};
+ return {};
}
-
- if (OptionalInOutModificationTag != nullptr)
+ Ref<Oplog> Oplog = Project->OpenOplog(OplogId, /*AllowCompact */ false, /*VerifyPathOnDisk*/ false);
+ if (!Oplog)
{
- uint64_t OldTag = *OptionalInOutModificationTag;
- *OptionalInOutModificationTag = GetModificationTagFromRawHash(Hash);
- if (*OptionalInOutModificationTag == OldTag)
- {
- return {HttpResponseCode::NotModified, {}};
- }
+ return {};
}
-
- OutChunk.SetContentType(ZenContentType::kCompressedBinary);
- return {HttpResponseCode::OK, {}};
+ return Oplog->FindChunk(Project->RootDir, ChunkId, /*OptOutModificationTag*/ nullptr);
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::PutChunk(const std::string_view ProjectId,
- const std::string_view OplogId,
- const std::string_view Cid,
- ZenContentType ContentType,
- IoBuffer&& Chunk)
+IoBuffer
+ProjectStore::GetChunk(const std::string_view ProjectId, const std::string_view OplogId, const IoHash& Cid)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("Chunk put request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
+ ZEN_TRACE_CPU("ProjectStore::GetChunk");
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ false);
- if (!FoundLog)
+ Ref<Project> Project = OpenProject(ProjectId);
+ if (!Project)
{
- return {HttpResponseCode::NotFound, fmt::format("Chunk put request for unknown oplog '{}/{}'", ProjectId, OplogId)};
+ return {};
}
- Project->TouchOplog(OplogId);
-
- if (Cid.length() != IoHash::StringLength)
+ Ref<Oplog> Oplog = Project->OpenOplog(OplogId, /*AllowCompact */ false, /*VerifyPathOnDisk*/ false);
+ if (!Oplog)
{
- return {HttpResponseCode::BadRequest, fmt::format("Chunk put request for invalid chunk hash '{}'", Cid)};
+ return {};
}
+ return m_CidStore.FindChunkByCid(Cid);
+}
- const IoHash Hash = IoHash::FromHexString(Cid);
+bool
+ProjectStore::PutChunk(Project& Project, Oplog& Oplog, const IoHash& ChunkHash, IoBuffer&& Chunk)
+{
+ ZEN_MEMSCOPE(GetProjectstoreTag());
+ ZEN_TRACE_CPU("ProjectStore::PutChunk");
- if (ContentType != HttpContentType::kCompressedBinary)
- {
- return {HttpResponseCode::BadRequest, fmt::format("Chunk request for invalid content type for chunk '{}'", Cid)};
- }
IoHash RawHash;
uint64_t RawSize;
CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Chunk), RawHash, RawSize);
- if (RawHash != Hash)
+ if (RawHash != ChunkHash)
{
- return {HttpResponseCode::BadRequest, fmt::format("Chunk request for invalid payload format for chunk '{}'", Cid)};
+ throw std::runtime_error(
+ fmt::format("Chunk request for invalid payload format for chunk {}/{}/{}", Project.Identifier, Oplog.OplogId(), ChunkHash));
}
- FoundLog->CaptureAddedAttachments(std::vector<IoHash>{Hash});
- CidStore::InsertResult Result = m_CidStore.AddChunk(Chunk, Hash);
- return {Result.New ? HttpResponseCode::Created : HttpResponseCode::OK, {}};
+ Oplog.CaptureAddedAttachments(std::vector<IoHash>{ChunkHash});
+ CidStore::InsertResult Result = m_CidStore.AddChunk(Chunk, ChunkHash);
+ return Result.New;
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::GetChunks(const std::string_view ProjectId,
- const std::string_view OplogId,
- const CbObject& RequestObject,
- CbPackage& OutResponsePackage)
+std::vector<ProjectStore::ChunkResult>
+ProjectStore::GetChunks(Project& Project, Oplog& Oplog, std::span<const ChunkRequest> Requests)
{
ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::GetChunks");
+ ZEN_TRACE_CPU("ProjectStore::GetChunks");
- using namespace std::literals;
+ ZEN_ASSERT(!Requests.empty());
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("getchunks rpc request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
+ std::vector<ProjectStore::ChunkResult> Results;
+ size_t RequestCount = Requests.size();
- ProjectStore::Oplog* FoundLog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ false);
- if (!FoundLog)
- {
- return {HttpResponseCode::NotFound, fmt::format("getchunks rpc request for unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
+ Results.resize(RequestCount);
- if (RequestObject["chunks"sv].IsArray())
+ if (RequestCount > 1)
{
- // Legacy full chunks only by rawhash
-
- CbArrayView ChunksArray = RequestObject["chunks"sv].AsArrayView();
+ std::vector<IoHash> ChunkRawHashes;
+ std::vector<size_t> ChunkRawHashesRequestIndex;
+ std::vector<Oid> ChunkIds;
+ std::vector<size_t> ChunkIdsRequestIndex;
- CbObjectWriter ResponseWriter;
- ResponseWriter.BeginArray("chunks"sv);
- for (CbFieldView FieldView : ChunksArray)
- {
- IoHash RawHash = FieldView.AsHash();
- IoBuffer ChunkBuffer = m_CidStore.FindChunkByCid(RawHash);
- if (ChunkBuffer)
- {
- CompressedBuffer Compressed = CompressedBuffer::FromCompressedNoValidate(std::move(ChunkBuffer));
- if (Compressed)
- {
- ResponseWriter.AddHash(RawHash);
- OutResponsePackage.AddAttachment(CbAttachment(std::move(Compressed), RawHash));
- }
- else
- {
- ZEN_WARN("oplog '{}/{}': invalid compressed binary in cas store for {}", ProjectId, OplogId, RawHash);
- }
- }
- }
- ResponseWriter.EndArray();
- OutResponsePackage.SetObject(ResponseWriter.Save());
- return {HttpResponseCode::OK, {}};
- }
- else if (auto RequestFieldView = RequestObject["Request"sv]; RequestFieldView.IsObject())
- {
- CbObjectView RequestView = RequestFieldView.AsObjectView();
- bool SkipData = RequestView["SkipData"].AsBool(false);
- CbArrayView ChunksArray = RequestView["Chunks"sv].AsArrayView();
+ ChunkRawHashes.reserve(RequestCount);
+ ChunkRawHashesRequestIndex.reserve(RequestCount);
+ ChunkIds.reserve(RequestCount);
+ ChunkIdsRequestIndex.reserve(RequestCount);
- struct Request
+ for (size_t RequestIndex = 0; RequestIndex < Requests.size(); RequestIndex++)
{
- struct InputData
- {
- uint64_t Offset = 0;
- uint64_t Size = (uint64_t)-1;
- std::variant<IoHash, Oid> Id;
- std::optional<uint64_t> ModTag;
- } Input;
- struct OutputData
+ const ChunkRequest& Request = Requests[RequestIndex];
+ if (Request.Id.index() == 0)
{
- bool Exists = false;
- IoBuffer ChunkBuffer;
- uint64_t ModTag = 0;
- } Output;
- };
-
- std::vector<Request> Requests;
- size_t RequestCount = ChunksArray.Num();
- if (RequestCount > 0)
- {
- Requests.reserve(RequestCount);
- std::vector<IoHash> ChunkRawHashes;
- std::vector<size_t> ChunkRawHashesRequestIndex;
- std::vector<Oid> ChunkIds;
- std::vector<size_t> ChunkIdsRequestIndex;
- bool DoBatch = RequestCount > 1;
- if (DoBatch)
- {
- ChunkRawHashes.reserve(RequestCount);
- ChunkRawHashesRequestIndex.reserve(RequestCount);
- ChunkIds.reserve(RequestCount);
- ChunkIdsRequestIndex.reserve(RequestCount);
+ ChunkRawHashes.push_back(std::get<IoHash>(Request.Id));
+ ChunkRawHashesRequestIndex.push_back(RequestIndex);
}
- for (CbFieldView FieldView : ChunksArray)
+ else
{
- CbObjectView ChunkObject = FieldView.AsObjectView();
- Request ChunkRequest = {
- .Input{.Offset = ChunkObject["Offset"sv].AsUInt64(0), .Size = ChunkObject["Size"sv].AsUInt64((uint64_t)-1)}};
- if (CbFieldView InputModificationTagView = ChunkObject.FindView("ModTag"); InputModificationTagView.IsInteger())
- {
- ChunkRequest.Input.ModTag = InputModificationTagView.AsUInt64();
- }
- if (CbFieldView RawHashView = ChunkObject.FindView("RawHash"sv); RawHashView.IsHash())
- {
- const IoHash ChunkHash = RawHashView.AsHash();
- ChunkRequest.Input.Id = ChunkHash;
- if (DoBatch)
- {
- ChunkRawHashes.push_back(ChunkHash);
- ChunkRawHashesRequestIndex.push_back(Requests.size());
- }
- }
- else if (CbFieldView IdView = ChunkObject.FindView("Oid"sv); IdView.IsObjectId())
- {
- const Oid ChunkId = IdView.AsObjectId();
- ChunkRequest.Input.Id = ChunkId;
- if (DoBatch)
- {
- ChunkIds.push_back(ChunkId);
- ChunkIdsRequestIndex.push_back(Requests.size());
- }
- }
- else
- {
- return {HttpResponseCode::BadRequest,
- fmt::format("oplog '{}/{}': malformed getchunks rpc request object, chunk request has no identifier",
- ProjectId,
- OplogId)};
- }
- Requests.emplace_back(std::move(ChunkRequest));
+ ChunkIds.push_back(std::get<Oid>(Request.Id));
+ ChunkIdsRequestIndex.push_back(RequestIndex);
}
+ }
- if (DoBatch)
- {
- WorkerThreadPool& WorkerPool = GetSmallWorkerPool(EWorkloadType::Burst); // GetSyncWorkerPool();
- if (!ChunkRawHashes.empty())
- {
- FoundLog->IterateChunks(
- ChunkRawHashes,
- true,
- [&](size_t Index, const IoBuffer& Payload, uint64_t ModTag) -> bool {
- if (Payload)
- {
- size_t RequestIndex = ChunkRawHashesRequestIndex[Index];
- Requests[RequestIndex].Output.Exists = true;
- if (!SkipData)
- {
- Requests[RequestIndex].Output.ChunkBuffer = Payload;
- Requests[RequestIndex].Output.ChunkBuffer.MakeOwned();
- }
- Requests[RequestIndex].Output.ModTag = ModTag;
- }
- return true;
- },
- &WorkerPool,
- 8u * 1024);
- }
- if (!ChunkIdsRequestIndex.empty())
- {
- FoundLog->IterateChunks(
- ChunkIds,
- true,
- [&](size_t Index, const IoBuffer& Payload, uint64_t ModTag) -> bool {
- if (Payload)
- {
- size_t RequestIndex = ChunkIdsRequestIndex[Index];
- Requests[RequestIndex].Output.Exists = true;
- if (!SkipData)
- {
- Requests[RequestIndex].Output.ChunkBuffer = Payload;
- Requests[RequestIndex].Output.ChunkBuffer.MakeOwned();
- }
- Requests[RequestIndex].Output.ModTag = ModTag;
- }
- return true;
- },
- &WorkerPool,
- 8u * 1024);
- }
- }
- else
- {
- Request& ChunkRequest = Requests.front();
- if (ChunkRequest.Input.Id.index() == 0)
- {
- const IoHash& ChunkHash = std::get<IoHash>(ChunkRequest.Input.Id);
- IoBuffer Payload = m_CidStore.FindChunkByCid(ChunkHash);
+ WorkerThreadPool& WorkerPool = GetSmallWorkerPool(EWorkloadType::Burst); // GetSyncWorkerPool();
+ if (!ChunkRawHashes.empty())
+ {
+ Oplog.IterateChunks(
+ ChunkRawHashes,
+ true,
+ [&](size_t Index, const IoBuffer& Payload, uint64_t ModTag) -> bool {
if (Payload)
{
- ChunkRequest.Output.Exists = true;
- ChunkRequest.Output.ModTag = GetModificationTagFromRawHash(ChunkHash);
- if (!SkipData)
+ size_t RequestIndex = ChunkRawHashesRequestIndex[Index];
+ const ChunkRequest& Request = Requests[RequestIndex];
+ ChunkResult& Result = Results[RequestIndex];
+ Result.Exists = true;
+ if (!Request.SkipData)
{
- ChunkRequest.Output.ChunkBuffer = Payload;
+ Result.ChunkBuffer = std::move(Payload);
+ Result.ChunkBuffer.MakeOwned();
}
+ Result.ModTag = ModTag;
}
- }
- else
- {
- const Oid& ChunkId = std::get<Oid>(ChunkRequest.Input.Id);
- uint64_t ModTag = 0;
- IoBuffer Payload = FoundLog->FindChunk(ChunkId, &ModTag);
+ return true;
+ },
+ &WorkerPool,
+ 8u * 1024);
+ }
+ if (!ChunkIdsRequestIndex.empty())
+ {
+ Oplog.IterateChunks(
+ Project.RootDir,
+ ChunkIds,
+ true,
+ [&](size_t Index, const IoBuffer& Payload, uint64_t ModTag) -> bool {
if (Payload)
{
- ChunkRequest.Output.Exists = true;
- ChunkRequest.Output.ModTag = ModTag;
- if (!SkipData)
+ size_t RequestIndex = ChunkIdsRequestIndex[Index];
+ const ChunkRequest& Request = Requests[RequestIndex];
+ ChunkResult& Result = Results[RequestIndex];
+ Result.Exists = true;
+ if (!Request.SkipData)
{
- ChunkRequest.Output.ChunkBuffer = Payload;
+ Result.ChunkBuffer = std::move(Payload);
+ Result.ChunkBuffer.MakeOwned();
}
+ Result.ModTag = ModTag;
}
- }
- }
+ return true;
+ },
+ &WorkerPool,
+ 8u * 1024);
}
+ }
+ else
+ {
+ const ChunkRequest& Request = Requests.front();
+ ChunkResult& Result = Results.front();
- CbObjectWriter ResponseWriter(32 + Requests.size() * 64u);
- ResponseWriter.BeginArray("Chunks"sv);
+ if (Request.Id.index() == 0)
{
- for (Request& ChunkRequest : Requests)
+ const IoHash& ChunkHash = std::get<IoHash>(Request.Id);
+ IoBuffer Payload = m_CidStore.FindChunkByCid(ChunkHash);
+ if (Payload)
{
- if (ChunkRequest.Output.Exists)
+ Result.Exists = true;
+ Result.ModTag = GetModificationTagFromRawHash(ChunkHash);
+ if (!Request.SkipData)
{
- ResponseWriter.BeginObject();
- {
- if (ChunkRequest.Input.Id.index() == 0)
- {
- const IoHash& RawHash = std::get<IoHash>(ChunkRequest.Input.Id);
- ResponseWriter.AddHash("Id", RawHash);
- }
- else
- {
- const Oid& Id = std::get<Oid>(ChunkRequest.Input.Id);
- ResponseWriter.AddObjectId("Id", Id);
- }
- if (!ChunkRequest.Input.ModTag.has_value() || ChunkRequest.Input.ModTag.value() != ChunkRequest.Output.ModTag)
- {
- ResponseWriter.AddInteger("ModTag", ChunkRequest.Output.ModTag);
- if (!SkipData)
- {
- CompositeBuffer ChunkRange;
- ZenContentType ContentType;
- IoHash FullChunkRawHash;
- uint64_t FullChunkSize = 0;
- auto ExtractRangeResult = ExtractRange(std::move(ChunkRequest.Output.ChunkBuffer),
- ChunkRequest.Input.Offset,
- ChunkRequest.Input.Size,
- ZenContentType::kCompressedBinary,
- ContentType,
- ChunkRange,
- FullChunkRawHash,
- FullChunkSize);
- if (ExtractRangeResult.first == HttpResponseCode::OK)
- {
- if (ContentType == ZenContentType::kCompressedBinary)
- {
- ZEN_ASSERT(FullChunkRawHash != IoHash::Zero);
- CompressedBuffer CompressedValue =
- CompressedBuffer::FromCompressedNoValidate(std::move(ChunkRange));
- ZEN_ASSERT(CompressedValue);
-
- if (FullChunkSize != 0)
- {
- // This really could use some thought so we don't send the same data if we get a request for
- // multiple ranges from the same chunk block
-
- uint64_t FragmentRawOffset = 0;
- OodleCompressor Compressor;
- OodleCompressionLevel CompressionLevel;
- uint64_t BlockSize = 0;
- if (CompressedValue.TryGetCompressParameters(Compressor, CompressionLevel, BlockSize))
- {
- if (BlockSize > 0)
- {
- FragmentRawOffset = (ChunkRequest.Input.Offset / BlockSize) * BlockSize;
- }
- else
- {
- FragmentRawOffset = ChunkRequest.Input.Offset;
- }
- uint64_t FragmentRawLength = CompressedValue.DecodeRawSize();
-
- IoHashStream FragmentHashStream;
- FragmentHashStream.Append(FullChunkRawHash.Hash, sizeof(FullChunkRawHash.Hash));
- FragmentHashStream.Append(&FragmentRawOffset, sizeof(FragmentRawOffset));
- FragmentHashStream.Append(&FragmentRawLength, sizeof(FragmentRawLength));
- IoHash FragmentHash = FragmentHashStream.GetHash();
-
- ResponseWriter.AddHash("FragmentHash", FragmentHash);
- ResponseWriter.AddInteger("FragmentOffset", FragmentRawOffset);
- ResponseWriter.AddInteger("RawSize", FullChunkSize);
- OutResponsePackage.AddAttachment(CbAttachment(CompressedValue, FragmentHash));
- }
- else
- {
- std::string ErrorString =
- "Failed to get compression parameters from partial compressed buffer";
- ResponseWriter.AddString("Error", ErrorString);
- ZEN_WARN("oplog '{}/{}': {}", ProjectId, OplogId, ErrorString);
- }
- }
- else
- {
- ResponseWriter.AddHash("RawHash"sv, FullChunkRawHash);
- OutResponsePackage.AddAttachment(CbAttachment(std::move(CompressedValue), FullChunkRawHash));
- }
- }
- else
- {
- IoHashStream HashStream;
- ZEN_ASSERT(ChunkRequest.Input.Id.index() == 1);
- const Oid& Id = std::get<Oid>(ChunkRequest.Input.Id);
- HashStream.Append(Id.OidBits, sizeof(Id.OidBits));
- HashStream.Append(&ChunkRequest.Input.Offset, sizeof(ChunkRequest.Input.Offset));
- HashStream.Append(&ChunkRequest.Input.Size, sizeof(ChunkRequest.Input.Size));
- IoHash Hash = HashStream.GetHash();
-
- ResponseWriter.AddHash("Hash"sv, Hash);
- if (FullChunkSize != 0)
- {
- ResponseWriter.AddInteger("Size", FullChunkSize);
- }
- OutResponsePackage.AddAttachment(CbAttachment(std::move(ChunkRange), Hash));
- }
- }
- else
- {
- std::string ErrorString = fmt::format("Failed fetchiong chunk range ({})", ExtractRangeResult.second);
- ResponseWriter.AddString("Error", ErrorString);
- ZEN_WARN("oplog '{}/{}': {}", ProjectId, OplogId, ErrorString);
- }
- }
- }
- }
- ResponseWriter.EndObject();
+ Result.ChunkBuffer = std::move(Payload);
+ Result.ChunkBuffer.MakeOwned();
}
}
}
- ResponseWriter.EndArray();
- OutResponsePackage.SetObject(ResponseWriter.Save());
- return {HttpResponseCode::OK, {}};
- }
- else
- {
- return {HttpResponseCode::BadRequest, fmt::format("oplog '{}/{}': malformed getchunks rpc request object", ProjectId, OplogId)};
- }
-}
-
-std::pair<HttpResponseCode, std::string>
-ProjectStore::WriteOplog(const std::string_view ProjectId, const std::string_view OplogId, IoBuffer&& Payload, CbObject& OutResponse)
-{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::WriteOplog");
-
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("Write oplog request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
-
- ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ false);
- if (!Oplog)
- {
- return {HttpResponseCode::NotFound, fmt::format("Write oplog request for unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
-
- CbObject ContainerObject = LoadCompactBinaryObject(Payload);
- if (!ContainerObject)
- {
- return {HttpResponseCode::BadRequest, "Invalid payload format"};
- }
-
- CidStore& ChunkStore = m_CidStore;
- RwLock AttachmentsLock;
- tsl::robin_set<IoHash, IoHash::Hasher> Attachments;
-
- auto HasAttachment = [&ChunkStore](const IoHash& RawHash) { return ChunkStore.ContainsChunk(RawHash); };
- auto OnNeedBlock = [&AttachmentsLock, &Attachments](const IoHash& BlockHash, const std::vector<IoHash>&& ChunkHashes) {
- RwLock::ExclusiveLockScope _(AttachmentsLock);
- if (BlockHash != IoHash::Zero)
- {
- Attachments.insert(BlockHash);
- }
- else
- {
- Attachments.insert(ChunkHashes.begin(), ChunkHashes.end());
- }
- };
- auto OnNeedAttachment = [&AttachmentsLock, &Attachments](const IoHash& RawHash) {
- RwLock::ExclusiveLockScope _(AttachmentsLock);
- Attachments.insert(RawHash);
- };
-
- auto OnChunkedAttachment = [](const ChunkedInfo&) {};
-
- auto OnReferencedAttachments = [&Oplog](std::span<IoHash> RawHashes) { Oplog->CaptureAddedAttachments(RawHashes); };
- // Make sure we retain any attachments we download before writing the oplog
- Oplog->EnableUpdateCapture();
- auto _ = MakeGuard([&Oplog]() { Oplog->DisableUpdateCapture(); });
-
- RemoteProjectStore::Result RemoteResult = SaveOplogContainer(*Oplog,
- ContainerObject,
- OnReferencedAttachments,
- HasAttachment,
- OnNeedBlock,
- OnNeedAttachment,
- OnChunkedAttachment,
- nullptr);
-
- if (RemoteResult.ErrorCode)
- {
- return ConvertResult(RemoteResult);
- }
-
- CbObjectWriter Cbo(1 + 1 + 5 + Attachments.size() * (1 + sizeof(IoHash::Hash)) + 1);
- Cbo.BeginArray("need");
- {
- for (const IoHash& Hash : Attachments)
+ else
{
- ZEN_DEBUG("Need attachment {}", Hash);
- Cbo << Hash;
+ const Oid& ChunkId = std::get<Oid>(Request.Id);
+ uint64_t ModTag = 0;
+ IoBuffer Payload = Oplog.FindChunk(Project.RootDir, ChunkId, &ModTag);
+ if (Payload)
+ {
+ Result.Exists = true;
+ Result.ModTag = ModTag;
+ if (!Request.SkipData)
+ {
+ Result.ChunkBuffer = std::move(Payload);
+ Result.ChunkBuffer.MakeOwned();
+ }
+ }
}
}
- Cbo.EndArray(); // "need"
-
- OutResponse = Cbo.Save();
- return {HttpResponseCode::OK, {}};
+ return Results;
}
-std::pair<HttpResponseCode, std::string>
-ProjectStore::ReadOplog(const std::string_view ProjectId,
- const std::string_view OplogId,
- const HttpServerRequest::QueryParams& Params,
- CbObject& OutResponse)
+std::vector<ProjectStore::ChunkRequest>
+ProjectStore::ParseChunksRequests(Project& Project, Oplog& Oplog, const CbObject& Cb)
{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::ReadOplog");
+ ZEN_TRACE_CPU("Store::Rpc::getchunks");
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
- {
- return {HttpResponseCode::NotFound, fmt::format("Read oplog request for unknown project '{}'", ProjectId)};
- }
- Project->TouchProject();
+ using namespace std::literals;
- ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ true, /*VerifyPathOnDisk*/ true);
- if (!Oplog)
- {
- return {HttpResponseCode::NotFound, fmt::format("Read oplog request for unknown oplog '{}/{}'", ProjectId, OplogId)};
- }
- Project->TouchOplog(OplogId);
+ std::vector<ChunkRequest> Requests;
- size_t MaxBlockSize = RemoteStoreOptions::DefaultMaxBlockSize;
- if (auto Param = Params.GetValue("maxblocksize"); Param.empty() == false)
+ if (auto RequestFieldView = Cb["Request"sv]; RequestFieldView.IsObject())
{
- if (auto Value = ParseInt<size_t>(Param))
- {
- MaxBlockSize = Value.value();
- }
- }
- size_t MaxChunkEmbedSize = RemoteStoreOptions::DefaultMaxChunkEmbedSize;
- if (auto Param = Params.GetValue("maxchunkembedsize"); Param.empty() == false)
- {
- if (auto Value = ParseInt<size_t>(Param))
- {
- MaxChunkEmbedSize = Value.value();
- }
- }
+ CbObjectView RequestView = RequestFieldView.AsObjectView();
+ bool SkipData = RequestView["SkipData"].AsBool(false);
+ CbArrayView ChunksArray = RequestView["Chunks"sv].AsArrayView();
- size_t ChunkFileSizeLimit = RemoteStoreOptions::DefaultChunkFileSizeLimit;
- if (auto Param = Params.GetValue("chunkfilesizelimit"); Param.empty() == false)
- {
- if (auto Value = ParseInt<size_t>(Param))
+ size_t RequestCount = ChunksArray.Num();
+ if (RequestCount > 0)
{
- ChunkFileSizeLimit = Value.value();
- }
- }
-
- CidStore& ChunkStore = m_CidStore;
-
- RemoteProjectStore::LoadContainerResult ContainerResult = BuildContainer(
- ChunkStore,
- *Project.Get(),
- *Oplog,
- MaxBlockSize,
- MaxChunkEmbedSize,
- ChunkFileSizeLimit,
- /* BuildBlocks */ false,
- /* IgnoreMissingAttachments */ false,
- /* AllowChunking*/ false,
- [](CompressedBuffer&&, ChunkBlockDescription&&) {},
- [](const IoHash&, TGetAttachmentBufferFunc&&) {},
- [](std::vector<std::pair<IoHash, FetchChunkFunc>>&&) {},
- /* EmbedLooseFiles*/ false);
-
- OutResponse = std::move(ContainerResult.ContainerObject);
- return ConvertResult(ContainerResult);
-}
-
-bool
-ProjectStore::Rpc(HttpServerRequest& HttpReq,
- const std::string_view ProjectId,
- const std::string_view OplogId,
- IoBuffer&& Payload,
- AuthMgr& AuthManager)
-{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::Rpc");
-
- using namespace std::literals;
- HttpContentType PayloadContentType = HttpReq.RequestContentType();
- CbPackage Package;
- CbObject Cb;
- switch (PayloadContentType)
- {
- case HttpContentType::kJSON:
- case HttpContentType::kUnknownContentType:
- case HttpContentType::kText:
- {
- std::string JsonText(reinterpret_cast<const char*>(Payload.GetData()), Payload.GetSize());
- Cb = LoadCompactBinaryFromJson(JsonText).AsObject();
- if (!Cb)
+ Requests.reserve(RequestCount);
+ for (CbFieldView FieldView : ChunksArray)
+ {
+ CbObjectView ChunkObject = FieldView.AsObjectView();
+ ChunkRequest ChunkRequest = {.Offset = ChunkObject["Offset"sv].AsUInt64(0),
+ .Size = ChunkObject["Size"sv].AsUInt64((uint64_t)-1),
+ .SkipData = SkipData};
+ if (CbFieldView InputModificationTagView = ChunkObject.FindView("ModTag"); InputModificationTagView.IsInteger())
{
- HttpReq.WriteResponse(HttpResponseCode::BadRequest,
- HttpContentType::kText,
- "Content format not supported, expected JSON format");
- return false;
+ ChunkRequest.ModTag = InputModificationTagView.AsUInt64();
}
+ if (CbFieldView RawHashView = ChunkObject.FindView("RawHash"sv); RawHashView.IsHash())
+ {
+ const IoHash ChunkHash = RawHashView.AsHash();
+ ChunkRequest.Id = ChunkHash;
+ }
+ else if (CbFieldView IdView = ChunkObject.FindView("Oid"sv); IdView.IsObjectId())
+ {
+ const Oid ChunkId = IdView.AsObjectId();
+ ChunkRequest.Id = ChunkId;
+ }
+ else
+ {
+ throw std::runtime_error(
+ fmt::format("oplog '{}/{}': malformed getchunks rpc request object, chunk request has no identifier",
+ Project.Identifier,
+ Oplog.OplogId()));
+ }
+ Requests.emplace_back(std::move(ChunkRequest));
}
- break;
- case HttpContentType::kCbObject:
- Cb = LoadCompactBinaryObject(Payload);
- if (!Cb)
- {
- HttpReq.WriteResponse(HttpResponseCode::BadRequest,
- HttpContentType::kText,
- "Content format not supported, expected compact binary format");
- return false;
- }
- break;
- case HttpContentType::kCbPackage:
- try
- {
- Package = ParsePackageMessage(Payload);
- Cb = Package.GetObject();
- }
- catch (const std::invalid_argument& ex)
- {
- HttpReq.WriteResponse(HttpResponseCode::BadRequest,
- HttpContentType::kText,
- fmt::format("Failed to parse package request, reason: '{}'", ex.what()));
- return false;
- }
- if (!Cb)
- {
- HttpReq.WriteResponse(HttpResponseCode::BadRequest,
- HttpContentType::kText,
- "Content format not supported, expected package message format");
- return false;
- }
- break;
- default:
- HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type");
- return false;
+ }
}
-
- Ref<ProjectStore::Project> Project = OpenProject(ProjectId);
- if (!Project)
+ else if (CbArrayView ChunksArray = Cb["chunks"sv].AsArrayView(); ChunksArray)
{
- HttpReq.WriteResponse(HttpResponseCode::NotFound,
- HttpContentType::kText,
- fmt::format("Rpc oplog request for unknown project '{}'", ProjectId));
- return true;
- }
- Project->TouchProject();
+ // Legacy full chunks only by rawhash
- std::string_view Method = Cb["method"sv].AsString();
+ size_t RequestCount = ChunksArray.Num();
- bool VerifyPathOnDisk = Method != "getchunks"sv;
+ Requests.reserve(RequestCount);
- ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, VerifyPathOnDisk);
- if (!Oplog)
- {
- HttpReq.WriteResponse(HttpResponseCode::NotFound,
- HttpContentType::kText,
- fmt::format("Rpc oplog request for unknown oplog '{}/{}'", ProjectId, OplogId));
- return true;
- }
- Project->TouchOplog(OplogId);
-
- if (Method == "import"sv)
- {
- if (!AreDiskWritesAllowed())
- {
- HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- return true;
- }
- std::pair<HttpResponseCode, std::string> Result = Import(*Project.Get(), *Oplog, Cb["params"sv].AsObjectView(), AuthManager);
- if (Result.second.empty())
+ std::vector<IoHash> Cids;
+ Cids.reserve(ChunksArray.Num());
+ for (CbFieldView FieldView : ChunksArray)
{
- HttpReq.WriteResponse(Result.first);
- return Result.first != HttpResponseCode::BadRequest;
+ Requests.push_back(ProjectStore::ChunkRequest{.Id = FieldView.AsHash()});
}
- HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- return true;
}
- else if (Method == "export"sv)
+ else
{
- std::pair<HttpResponseCode, std::string> Result = Export(Project, *Oplog, Cb["params"sv].AsObjectView(), AuthManager);
- HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- return true;
+ throw std::runtime_error(fmt::format("oplog '{}/{}': malformed getchunks rpc request object", Project.Identifier, Oplog.OplogId()));
}
- else if (Method == "getchunks"sv)
- {
- ZEN_TRACE_CPU("Store::Rpc::getchunks");
+ return Requests;
+}
- RpcAcceptOptions AcceptFlags = static_cast<RpcAcceptOptions>(Cb["AcceptFlags"sv].AsUInt16(0u));
- int32_t TargetProcessId = Cb["Pid"sv].AsInt32(0);
+CbPackage
+ProjectStore::WriteChunksRequestResponse(Project& Project,
+ Oplog& Oplog,
+ std::vector<ChunkRequest>&& Requests,
+ std::vector<ChunkResult>&& Results)
+{
+ using namespace std::literals;
- CbPackage ResponsePackage;
- std::pair<HttpResponseCode, std::string> Result = GetChunks(ProjectId, OplogId, Cb, ResponsePackage);
- if (Result.first == HttpResponseCode::OK)
- {
- void* TargetProcessHandle = nullptr;
- FormatFlags Flags = FormatFlags::kDefault;
- if (EnumHasAllFlags(AcceptFlags, RpcAcceptOptions::kAllowLocalReferences))
- {
- Flags |= FormatFlags::kAllowLocalReferences;
- if (!EnumHasAnyFlags(AcceptFlags, RpcAcceptOptions::kAllowPartialLocalReferences))
- {
- Flags |= FormatFlags::kDenyPartialLocalReferences;
- }
- TargetProcessHandle = m_OpenProcessCache.GetProcessHandle(HttpReq.SessionId(), TargetProcessId);
- }
+ CbPackage ResponsePackage;
- CompositeBuffer RpcResponseBuffer = FormatPackageMessageBuffer(ResponsePackage, Flags, TargetProcessHandle);
- HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kCbPackage, RpcResponseBuffer);
- }
- else
- {
- HttpReq.WriteResponse(Result.first, HttpContentType::kText, Result.second);
- }
- return true;
- }
- else if (Method == "putchunks"sv)
+ CbObjectWriter ResponseWriter(32 + Requests.size() * 64u);
+ ResponseWriter.BeginArray("Chunks"sv);
{
- ZEN_TRACE_CPU("Store::Rpc::putchunks");
- if (!AreDiskWritesAllowed())
+ for (size_t Index = 0; Index < Requests.size(); Index++)
{
- HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- return true;
- }
-
- std::span<const CbAttachment> Attachments = Package.GetAttachments();
- if (!Attachments.empty())
- {
- std::vector<IoBuffer> WriteAttachmentBuffers;
- std::vector<IoHash> WriteRawHashes;
-
- WriteAttachmentBuffers.reserve(Attachments.size());
- WriteRawHashes.reserve(Attachments.size());
-
- for (const CbAttachment& Attachment : Attachments)
+ const ChunkRequest& Request = Requests[Index];
+ ChunkResult& Result = Results[Index];
+ if (Result.Exists)
{
- IoHash RawHash = Attachment.GetHash();
- const CompressedBuffer& Compressed = Attachment.AsCompressedBinary();
- WriteAttachmentBuffers.push_back(Compressed.GetCompressed().Flatten().AsIoBuffer());
- WriteRawHashes.push_back(RawHash);
- }
-
- Oplog->CaptureAddedAttachments(WriteRawHashes);
- m_CidStore.AddChunks(WriteAttachmentBuffers, WriteRawHashes, CidStore::InsertMode::kCopyOnly);
- }
- HttpReq.WriteResponse(HttpResponseCode::OK);
- return true;
- }
- else if (Method == "snapshot"sv)
- {
- ZEN_TRACE_CPU("Store::Rpc::snapshot");
- if (!AreDiskWritesAllowed())
- {
- HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage);
- return true;
- }
-
- // Snapshot all referenced files. This brings the content of all
- // files into the CID store
-
- uint32_t OpCount = 0;
- uint64_t InlinedBytes = 0;
- uint64_t InlinedFiles = 0;
- uint64_t TotalBytes = 0;
- uint64_t TotalFiles = 0;
-
- std::vector<CbObject> NewOps;
- struct AddedChunk
- {
- IoBuffer Buffer;
- uint64_t RawSize = 0;
- };
- tsl::robin_map<IoHash, AddedChunk, IoHash::Hasher> AddedChunks;
-
- Oplog->IterateOplog(
- [&](CbObjectView Op) {
- bool OpRewritten = false;
- bool AllOk = true;
-
- CbWriter FilesArrayWriter;
- FilesArrayWriter.BeginArray("files"sv);
-
- for (CbFieldView& Field : Op["files"sv])
+ ResponseWriter.BeginObject();
{
- bool CopyField = true;
-
- if (CbObjectView View = Field.AsObjectView())
+ if (Request.Id.index() == 0)
{
- const IoHash DataHash = View["data"sv].AsHash();
-
- if (DataHash == IoHash::Zero)
+ const IoHash& RawHash = std::get<IoHash>(Request.Id);
+ ResponseWriter.AddHash("Id", RawHash);
+ }
+ else
+ {
+ const Oid& Id = std::get<Oid>(Request.Id);
+ ResponseWriter.AddObjectId("Id", Id);
+ }
+ if (!Request.ModTag.has_value() || Request.ModTag.value() != Result.ModTag)
+ {
+ ResponseWriter.AddInteger("ModTag", Result.ModTag);
+ if (!Request.SkipData)
{
- std::string_view ServerPath = View["serverpath"sv].AsString();
- std::filesystem::path FilePath = Project->RootDir / ServerPath;
- BasicFile DataFile;
- std::error_code Ec;
- DataFile.Open(FilePath, BasicFile::Mode::kRead, Ec);
-
- if (Ec)
- {
- // Error...
-
- ZEN_ERROR("unable to read data from file '{}': {}", FilePath, Ec.message());
-
- AllOk = false;
- }
- else
+ auto ExtractRangeResult = ExtractRange(std::move(Result.ChunkBuffer),
+ Request.Offset,
+ Request.Size,
+ ZenContentType::kCompressedBinary);
+ if (ExtractRangeResult.Error == GetChunkRangeResult::EError::Ok)
{
- // Read file contents into memory, compress and keep in map of chunks to add to Cid store
- IoBuffer FileIoBuffer = DataFile.ReadAll();
- CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer(std::move(FileIoBuffer)));
- const uint64_t RawSize = Compressed.DecodeRawSize();
- const IoHash RawHash = Compressed.DecodeRawHash();
- if (!AddedChunks.contains(RawHash))
+ if (ExtractRangeResult.ContentType == ZenContentType::kCompressedBinary)
{
- const std::filesystem::path TempChunkPath = Oplog->TempPath() / RawHash.ToHexString();
- BasicFile ChunkTempFile;
- ChunkTempFile.Open(TempChunkPath, BasicFile::Mode::kTruncateDelete);
- ChunkTempFile.Write(Compressed.GetCompressed(), 0, Ec);
- if (Ec)
+ ZEN_ASSERT(ExtractRangeResult.RawHash != IoHash::Zero);
+ CompressedBuffer CompressedValue =
+ CompressedBuffer::FromCompressedNoValidate(std::move(ExtractRangeResult.Chunk));
+ ZEN_ASSERT(CompressedValue);
+
+ if (ExtractRangeResult.RawSize != 0)
{
- Oid ChunkId = View["id"sv].AsObjectId();
- ZEN_ERROR("unable to write external file as compressed chunk '{}', id {}: {}",
- FilePath,
- ChunkId,
- Ec.message());
- AllOk = false;
+ // This really could use some thought so we don't send the same data if we get a request for
+ // multiple ranges from the same chunk block
+
+ uint64_t FragmentRawOffset = 0;
+ OodleCompressor Compressor;
+ OodleCompressionLevel CompressionLevel;
+ uint64_t BlockSize = 0;
+ if (CompressedValue.TryGetCompressParameters(Compressor, CompressionLevel, BlockSize))
+ {
+ if (BlockSize > 0)
+ {
+ FragmentRawOffset = (Request.Offset / BlockSize) * BlockSize;
+ }
+ else
+ {
+ FragmentRawOffset = Request.Offset;
+ }
+ uint64_t FragmentRawLength = CompressedValue.DecodeRawSize();
+
+ IoHashStream FragmentHashStream;
+ FragmentHashStream.Append(ExtractRangeResult.RawHash.Hash,
+ sizeof(ExtractRangeResult.RawHash.Hash));
+ FragmentHashStream.Append(&FragmentRawOffset, sizeof(FragmentRawOffset));
+ FragmentHashStream.Append(&FragmentRawLength, sizeof(FragmentRawLength));
+ IoHash FragmentHash = FragmentHashStream.GetHash();
+
+ ResponseWriter.AddHash("FragmentHash", FragmentHash);
+ ResponseWriter.AddInteger("FragmentOffset", FragmentRawOffset);
+ ResponseWriter.AddInteger("RawSize", ExtractRangeResult.RawSize);
+ ResponsePackage.AddAttachment(CbAttachment(CompressedValue, FragmentHash));
+ }
+ else
+ {
+ std::string ErrorString = "Failed to get compression parameters from partial compressed buffer";
+ ResponseWriter.AddString("Error", ErrorString);
+ ZEN_WARN("oplog '{}/{}': {}", Project.Identifier, Oplog.OplogId(), ErrorString);
+ }
}
else
{
- void* FileHandle = ChunkTempFile.Detach();
- IoBuffer ChunkBuffer(IoBuffer::File,
- FileHandle,
- 0,
- Compressed.GetCompressed().GetSize(),
- /*IsWholeFile*/ true);
- ChunkBuffer.SetDeleteOnClose(true);
- AddedChunks.insert_or_assign(RawHash,
- AddedChunk{.Buffer = std::move(ChunkBuffer), .RawSize = RawSize});
+ ResponseWriter.AddHash("RawHash"sv, ExtractRangeResult.RawHash);
+ ResponsePackage.AddAttachment(CbAttachment(std::move(CompressedValue), ExtractRangeResult.RawHash));
}
}
-
- TotalBytes += RawSize;
- ++TotalFiles;
-
- // Rewrite file array entry with new data reference
- CbObjectWriter Writer(View.GetSize());
- RewriteCbObject(Writer, View, [&](CbObjectWriter&, CbFieldView Field) -> bool {
- if (Field.GetName() == "data"sv)
+ else
+ {
+ IoHashStream HashStream;
+ ZEN_ASSERT(Request.Id.index() == 1);
+ const Oid& Id = std::get<Oid>(Request.Id);
+ HashStream.Append(Id.OidBits, sizeof(Id.OidBits));
+ HashStream.Append(&Request.Offset, sizeof(Request.Offset));
+ HashStream.Append(&Request.Size, sizeof(Request.Size));
+ IoHash Hash = HashStream.GetHash();
+
+ ResponseWriter.AddHash("Hash"sv, Hash);
+ if (ExtractRangeResult.RawSize != 0)
{
- // omit this field as we will write it explicitly ourselves
- return true;
+ ResponseWriter.AddInteger("Size", ExtractRangeResult.RawSize);
}
- return false;
- });
- Writer.AddBinaryAttachment("data"sv, RawHash);
-
- CbObject RewrittenOp = Writer.Save();
- FilesArrayWriter.AddObject(std::move(RewrittenOp));
- CopyField = false;
+ ResponsePackage.AddAttachment(CbAttachment(std::move(ExtractRangeResult.Chunk), Hash));
+ }
+ }
+ else
+ {
+ std::string ErrorString =
+ fmt::format("Failed fetching chunk range ({})", ExtractRangeResult.ErrorDescription);
+ ResponseWriter.AddString("Error", ErrorString);
+ ZEN_WARN("oplog '{}/{}': {}", Project.Identifier, Oplog.OplogId(), ErrorString);
}
}
}
-
- if (CopyField)
- {
- FilesArrayWriter.AddField(Field);
- }
- else
- {
- OpRewritten = true;
- }
- }
-
- if (OpRewritten && AllOk)
- {
- FilesArrayWriter.EndArray();
- CbArray FilesArray = FilesArrayWriter.Save().AsArray();
-
- CbObject RewrittenOp = RewriteCbObject(Op, [&](CbObjectWriter& NewWriter, CbFieldView Field) -> bool {
- if (Field.GetName() == "files"sv)
- {
- NewWriter.AddArray("files"sv, FilesArray);
-
- return true;
- }
-
- return false;
- });
-
- NewOps.push_back(std::move(RewrittenOp));
}
-
- OpCount++;
- },
- Oplog::Paging{});
-
- CbObjectWriter ResponseObj;
-
- // Persist rewritten oplog entries
- if (!NewOps.empty())
- {
- ResponseObj.BeginArray("rewritten_ops");
-
- for (CbObject& NewOp : NewOps)
- {
- uint32_t NewLsn = Oplog->AppendNewOplogEntry(std::move(NewOp));
-
- ZEN_DEBUG("appended rewritten op at LSN: {}", NewLsn);
-
- ResponseObj.AddInteger(NewLsn);
+ ResponseWriter.EndObject();
}
-
- ResponseObj.EndArray();
}
-
- // Ops that have moved chunks to a compressed buffer for storage in m_CidStore have been rewritten with references to the new
- // chunk(s). Make sure we add the chunks to m_CidStore, and do it after we update the oplog so GC doesn't think we have
- // unreferenced chunks.
- for (auto It : AddedChunks)
- {
- const IoHash& RawHash = It.first;
- AddedChunk& Chunk = It.second;
- CidStore::InsertResult Result = m_CidStore.AddChunk(Chunk.Buffer, RawHash);
- if (Result.New)
- {
- InlinedBytes += Chunk.RawSize;
- ++InlinedFiles;
- }
- }
-
- ResponseObj << "inlined_bytes" << InlinedBytes << "inlined_files" << InlinedFiles;
- ResponseObj << "total_bytes" << TotalBytes << "total_files" << TotalFiles;
-
- ZEN_INFO("oplog '{}/{}': rewrote {} oplog entries (out of {})", ProjectId, OplogId, NewOps.size(), OpCount);
-
- HttpReq.WriteResponse(HttpResponseCode::OK, ResponseObj.Save());
- return true;
}
- HttpReq.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("Unknown rpc method '{}'", Method));
- return true;
-}
-
-std::pair<HttpResponseCode, std::string>
-ProjectStore::Export(Ref<ProjectStore::Project> Project, ProjectStore::Oplog& Oplog, CbObjectView&& Params, AuthMgr& AuthManager)
-{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::Export");
-
- using namespace std::literals;
+ ResponseWriter.EndArray();
+ ResponsePackage.SetObject(ResponseWriter.Save());
- size_t MaxBlockSize = Params["maxblocksize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxBlockSize);
- size_t MaxChunkEmbedSize = Params["maxchunkembedsize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxChunkEmbedSize);
- size_t ChunkFileSizeLimit = Params["chunkfilesizelimit"sv].AsUInt64(RemoteStoreOptions::DefaultChunkFileSizeLimit);
- bool Force = Params["force"sv].AsBool(false);
- bool IgnoreMissingAttachments = Params["ignoremissingattachments"sv].AsBool(false);
- bool EmbedLooseFile = Params["embedloosefiles"sv].AsBool(false);
-
- CreateRemoteStoreResult RemoteStoreResult = CreateRemoteStore(Params, AuthManager, MaxBlockSize, MaxChunkEmbedSize, Oplog.TempPath());
-
- if (RemoteStoreResult.Store == nullptr)
- {
- return {HttpResponseCode::BadRequest, RemoteStoreResult.Description};
- }
- std::shared_ptr<RemoteProjectStore> RemoteStore = std::move(RemoteStoreResult.Store);
- RemoteProjectStore::RemoteStoreInfo StoreInfo = RemoteStore->GetInfo();
-
- JobId JobId = m_JobQueue.QueueJob(
- fmt::format("Export oplog '{}/{}'", Project->Identifier, Oplog.OplogId()),
- [this,
- ActualRemoteStore = std::move(RemoteStore),
- Project,
- OplogPtr = &Oplog,
- MaxBlockSize,
- MaxChunkEmbedSize,
- ChunkFileSizeLimit,
- EmbedLooseFile,
- Force,
- IgnoreMissingAttachments](JobContext& Context) {
- Context.ReportMessage(fmt::format("Saving oplog '{}/{}' to {}, maxblocksize {}, maxchunkembedsize {}",
- Project->Identifier,
- OplogPtr->OplogId(),
- ActualRemoteStore->GetInfo().Description,
- NiceBytes(MaxBlockSize),
- NiceBytes(MaxChunkEmbedSize)));
-
- RemoteProjectStore::Result Result = SaveOplog(m_CidStore,
- *ActualRemoteStore,
- *Project.Get(),
- *OplogPtr,
- MaxBlockSize,
- MaxChunkEmbedSize,
- ChunkFileSizeLimit,
- EmbedLooseFile,
- Force,
- IgnoreMissingAttachments,
- &Context);
- auto Response = ConvertResult(Result);
- ZEN_INFO("SaveOplog: Status: {} '{}'", ToString(Response.first), Response.second);
- if (!IsHttpSuccessCode(Response.first))
- {
- throw JobError(Response.second.empty() ? fmt::format("Status: {}", ToString(Response.first)) : Response.second,
- (int)Response.first);
- }
- });
-
- return {HttpResponseCode::Accepted, fmt::format("{}", JobId.Id)};
-}
-
-std::pair<HttpResponseCode, std::string>
-ProjectStore::Import(ProjectStore::Project& Project, ProjectStore::Oplog& Oplog, CbObjectView&& Params, AuthMgr& AuthManager)
-{
- ZEN_MEMSCOPE(GetProjectstoreTag());
- ZEN_TRACE_CPU("Store::Import");
-
- using namespace std::literals;
-
- size_t MaxBlockSize = Params["maxblocksize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxBlockSize);
- size_t MaxChunkEmbedSize = Params["maxchunkembedsize"sv].AsUInt64(RemoteStoreOptions::DefaultMaxChunkEmbedSize);
- bool Force = Params["force"sv].AsBool(false);
- bool IgnoreMissingAttachments = Params["ignoremissingattachments"sv].AsBool(false);
- bool CleanOplog = Params["clean"].AsBool(false);
-
- CreateRemoteStoreResult RemoteStoreResult = CreateRemoteStore(Params, AuthManager, MaxBlockSize, MaxChunkEmbedSize, Oplog.TempPath());
-
- if (RemoteStoreResult.Store == nullptr)
- {
- return {HttpResponseCode::BadRequest, RemoteStoreResult.Description};
- }
- std::shared_ptr<RemoteProjectStore> RemoteStore = std::move(RemoteStoreResult.Store);
- RemoteProjectStore::RemoteStoreInfo StoreInfo = RemoteStore->GetInfo();
-
- JobId JobId = m_JobQueue.QueueJob(
- fmt::format("Import oplog '{}/{}'", Project.Identifier, Oplog.OplogId()),
- [this,
- ChunkStore = &m_CidStore,
- ActualRemoteStore = std::move(RemoteStore),
- OplogPtr = &Oplog,
- Force,
- IgnoreMissingAttachments,
- CleanOplog](JobContext& Context) {
- Context.ReportMessage(fmt::format("Loading oplog '{}/{}' from {}",
- OplogPtr->GetOuterProject()->Identifier,
- OplogPtr->OplogId(),
- ActualRemoteStore->GetInfo().Description));
-
- RemoteProjectStore::Result Result =
- LoadOplog(m_CidStore, *ActualRemoteStore, *OplogPtr, Force, IgnoreMissingAttachments, CleanOplog, &Context);
- auto Response = ConvertResult(Result);
- ZEN_INFO("LoadOplog: Status: {} '{}'", ToString(Response.first), Response.second);
- if (!IsHttpSuccessCode(Response.first))
- {
- throw JobError(Response.second.empty() ? fmt::format("Status: {}", ToString(Response.first)) : Response.second,
- (int)Response.first);
- }
- });
-
- return {HttpResponseCode::Accepted, fmt::format("{}", JobId.Id)};
+ return ResponsePackage;
}
bool
@@ -5995,19 +5435,35 @@ public:
CompactOplogCount += OplogsToCompact.size();
for (const std::string& OplogId : OplogsToCompact)
{
- ProjectStore::Oplog* OpLog = nullptr;
+ Ref<ProjectStore::Oplog> OpLog;
{
RwLock::SharedLockScope __(Project->m_ProjectLock);
if (auto OpIt = Project->m_Oplogs.find(OplogId); OpIt != Project->m_Oplogs.end())
{
- OpLog = OpIt->second.get();
+ OpLog = OpIt->second;
}
else
{
+ Stopwatch OplogTimer;
std::filesystem::path OplogBasePath = Project->BasePathForOplog(OplogId);
- OpLog =
- new ProjectStore::Oplog(OplogId, Project.Get(), Project->m_CidStore, OplogBasePath, std::filesystem::path{});
+ OpLog = new ProjectStore::Oplog(
+ Project->Log(),
+ Project->Identifier,
+ OplogId,
+ Project->m_CidStore,
+ OplogBasePath,
+ std::filesystem::path{},
+ ProjectStore::Oplog::EMode::kFull); // We need it to be a full read so we can write a new index snapshot
OpLog->Read();
+ if (Ctx.Settings.Verbose)
+ {
+ ZEN_INFO("GCV2: projectstore [COMPACT] '{}': read oplog '{}/{}' at '{}' in {}",
+ m_BasePath,
+ Project->Identifier,
+ OplogId,
+ OplogBasePath,
+ NiceTimeSpanMs(OplogTimer.GetElapsedTimeMs()));
+ }
}
if (OpLog)
@@ -6024,11 +5480,6 @@ public:
Stats.RemovedDisk += FreedSize;
}
-
- if (auto OpIt = Project->m_Oplogs.find(OplogId); OpIt == Project->m_Oplogs.end())
- {
- delete OpLog;
- }
}
}
}
@@ -6117,6 +5568,16 @@ ProjectStore::RemoveExpiredData(GcCtx& Ctx, GcStats& Stats)
{
ExpiredOplogs.push_back(OplogId);
}
+ else if (!Project->IsOplogTouchedSince(GcClock::Now() - std::chrono::minutes(15), OplogId))
+ {
+ if (Project->TryUnloadOplog(OplogId))
+ {
+ ZEN_INFO("GCV2: projectstore [REMOVE EXPIRED] '{}': Unloaded oplog {}/{} due to inactivity",
+ m_ProjectBasePath,
+ Project->Identifier,
+ OplogId);
+ }
+ }
}
std::filesystem::path ProjectPath = BasePathForProject(Project->Identifier);
@@ -6208,7 +5669,7 @@ public:
Stopwatch Timer;
- std::vector<ProjectStore::Oplog*> AddedOplogs;
+ std::vector<Ref<ProjectStore::Oplog>> AddedOplogs;
const auto _ = MakeGuard([&] {
if (!Ctx.Settings.Verbose)
@@ -6230,7 +5691,7 @@ public:
ProjectStore::Project& Project = *It->second;
for (auto& OplogPair : Project.m_Oplogs)
{
- ProjectStore::Oplog* Oplog = OplogPair.second.get();
+ Ref<ProjectStore::Oplog> Oplog = OplogPair.second;
AddedOplogs.push_back(Oplog);
}
}
@@ -6244,13 +5705,13 @@ public:
{
if (auto It = Project.m_Oplogs.find(OplogName); It != Project.m_Oplogs.end())
{
- ProjectStore::Oplog* Oplog = It->second.get();
+ Ref<ProjectStore::Oplog> Oplog = It->second;
AddedOplogs.push_back(Oplog);
}
}
}
- for (ProjectStore::Oplog* Oplog : AddedOplogs)
+ for (const Ref<ProjectStore::Oplog>& Oplog : AddedOplogs)
{
size_t BaseReferenceCount = m_References.size();
@@ -6326,13 +5787,14 @@ public:
{
m_Project->DisableUpdateCapture();
- RwLock::SharedLockScope _(m_Project->m_ProjectLock);
- if (auto It = m_Project->m_Oplogs.find(m_OplogId); It != m_Project->m_Oplogs.end())
+ if (m_OplogHasUpdateCapture)
{
- ProjectStore::Oplog* Oplog = It->second.get();
- if (Oplog == m_OplogWithUpdateCapture)
+ RwLock::SharedLockScope _(m_Project->m_ProjectLock);
+ if (auto It = m_Project->m_Oplogs.find(m_OplogId); It != m_Project->m_Oplogs.end())
{
+ Ref<ProjectStore::Oplog> Oplog = It->second;
Oplog->DisableUpdateCapture();
+ m_OplogHasUpdateCapture = false;
}
}
}
@@ -6364,50 +5826,61 @@ public:
m_OplogId);
});
- ProjectStore::Oplog* Oplog = nullptr;
- auto __ = MakeGuard([this, &Oplog]() {
- if (Oplog != nullptr && m_OplogWithUpdateCapture == nullptr)
- {
- delete Oplog;
- }
- });
- m_OplogBasePath = m_Project->BasePathForOplog(m_OplogId);
-
- RwLock::SharedLockScope ___(m_Project->m_ProjectLock);
- if (auto It = m_Project->m_Oplogs.find(m_OplogId); It != m_Project->m_Oplogs.end())
- {
- It->second->EnableUpdateCapture();
- Oplog = It->second.get();
- m_OplogWithUpdateCapture = Oplog;
- }
- else if (ProjectStore::Oplog::ExistsAt(m_OplogBasePath))
- {
- Oplog = new ProjectStore::Oplog(m_OplogId, m_Project.Get(), m_Project->m_CidStore, m_OplogBasePath, std::filesystem::path{});
- Oplog->Read();
- }
- else
+ m_OplogBasePath = m_Project->BasePathForOplog(m_OplogId);
{
- return;
- }
+ Ref<ProjectStore::Oplog> Oplog;
- RwLock::SharedLockScope ____(Oplog->m_OplogLock);
- if (Ctx.IsCancelledFlag)
- {
- return;
- }
+ RwLock::SharedLockScope __(m_Project->m_ProjectLock);
+ if (auto It = m_Project->m_Oplogs.find(m_OplogId); It != m_Project->m_Oplogs.end())
+ {
+ Oplog = It->second;
+ Oplog->EnableUpdateCapture();
+ m_OplogHasUpdateCapture = true;
+ }
+ else if (ProjectStore::Oplog::ExistsAt(m_OplogBasePath))
+ {
+ Stopwatch OplogTimer;
+ Oplog = new ProjectStore::Oplog(m_Project->Log(),
+ m_Project->Identifier,
+ m_OplogId,
+ m_Project->m_CidStore,
+ m_OplogBasePath,
+ std::filesystem::path{},
+ ProjectStore::Oplog::EMode::kBasicReadOnly);
+ Oplog->Read();
+ if (Ctx.Settings.Verbose)
+ {
+ ZEN_INFO("GCV2: projectstore [PRECACHE] '{}': read oplog '{}/{}' in {}",
+ m_OplogBasePath,
+ m_Project->Identifier,
+ m_OplogId,
+ NiceTimeSpanMs(OplogTimer.GetElapsedTimeMs()));
+ }
+ }
+ else
+ {
+ return;
+ }
- GcClock::TimePoint CompactExpireTime = GcClock::Now() - std::chrono::minutes(30);
- if (!m_Project->IsOplogTouchedSince(CompactExpireTime, m_OplogId))
- {
- const uint32_t CompactUnusedThreshold = 25;
- if (Oplog->GetUnusedSpacePercent() >= CompactUnusedThreshold)
+ RwLock::SharedLockScope ___(Oplog->m_OplogLock);
+ if (Ctx.IsCancelledFlag)
{
- m_Project->AddOplogToCompact(m_OplogId);
+ return;
+ }
+
+ GcClock::TimePoint CompactExpireTime = GcClock::Now() - std::chrono::minutes(30);
+ if (!m_Project->IsOplogTouchedSince(CompactExpireTime, m_OplogId))
+ {
+ const uint32_t CompactUnusedThreshold = 25;
+ if (Oplog->GetUnusedSpacePercent() >= CompactUnusedThreshold)
+ {
+ m_Project->AddOplogToCompact(m_OplogId);
+ }
}
- }
- Oplog->GetAttachmentsLocked(m_References, Ctx.Settings.StoreProjectAttachmentMetaData);
- m_OplogAccessTime = m_Project->LastOplogAccessTime(m_OplogId);
+ Oplog->GetAttachmentsLocked(m_References, Ctx.Settings.StoreProjectAttachmentMetaData);
+ m_OplogAccessTime = m_Project->LastOplogAccessTime(m_OplogId);
+ }
FilterReferences(Ctx, fmt::format("projectstore [PRECACHE] '{}'", m_OplogBasePath), m_References);
}
@@ -6433,8 +5906,9 @@ public:
if (auto It = m_Project->m_Oplogs.find(m_OplogId); It != m_Project->m_Oplogs.end())
{
- ProjectStore::Oplog* Oplog = It->second.get();
- Oplog->IterateCapturedLSNsLocked([&](const CbObjectView& UpdateOp) -> bool {
+ Ref<ProjectStore::Oplog> Oplog = It->second;
+ Oplog->IterateCapturedOpsLocked([&](const Oid& Key, ProjectStore::LogSequenceNumber LSN, const CbObjectView& UpdateOp) -> bool {
+ ZEN_UNUSED(Key, LSN);
UpdateOp.IterateAttachments([&](CbFieldView Visitor) { m_AddedReferences.emplace_back(Visitor.AsAttachment()); });
return true;
});
@@ -6447,16 +5921,37 @@ public:
}
else if (m_Project->LastOplogAccessTime(m_OplogId) > m_OplogAccessTime && ProjectStore::Oplog::ExistsAt(m_OplogBasePath))
{
- ProjectStore::Oplog* Oplog =
- new ProjectStore::Oplog(m_OplogId, m_Project.Get(), m_Project->m_CidStore, m_OplogBasePath, std::filesystem::path{});
- auto __ = MakeGuard([Oplog]() {
- if (Oplog != nullptr)
+ Stopwatch OplogTimer;
+ {
+ Ref<ProjectStore::Oplog> Oplog(new ProjectStore::Oplog(m_Project->Log(),
+ m_Project->Identifier,
+ m_OplogId,
+ m_Project->m_CidStore,
+ m_OplogBasePath,
+ std::filesystem::path{},
+ ProjectStore::Oplog::EMode::kBasicReadOnly));
+ Oplog->Read();
+ if (Ctx.Settings.Verbose)
{
- delete Oplog;
+ ZEN_INFO("GCV2: projectstore [LOCKSTATE] '{}': read oplog '{}/{}' in {}",
+ m_OplogBasePath,
+ m_Project->Identifier,
+ m_OplogId,
+ NiceTimeSpanMs(OplogTimer.GetElapsedTimeMs()));
}
- });
- Oplog->Read();
- Oplog->GetAttachmentsLocked(m_AddedReferences, Ctx.Settings.StoreProjectAttachmentMetaData);
+
+ OplogTimer.Reset();
+
+ Oplog->GetAttachmentsLocked(m_AddedReferences, Ctx.Settings.StoreProjectAttachmentMetaData);
+ }
+ if (Ctx.Settings.Verbose)
+ {
+ ZEN_INFO("GCV2: projectstore [LOCKSTATE] '{}': read referenced attachments from oplog '{}/{}' in {}",
+ m_OplogBasePath,
+ m_Project->Identifier,
+ m_OplogId,
+ NiceTimeSpanMs(OplogTimer.GetElapsedTimeMs()));
+ }
}
FilterReferences(Ctx, fmt::format("projectstore [LOCKSTATE] '{}'", m_OplogBasePath), m_AddedReferences);
}
@@ -6495,7 +5990,7 @@ public:
Ref<ProjectStore::Project> m_Project;
std::string m_OplogId;
std::filesystem::path m_OplogBasePath;
- ProjectStore::Oplog* m_OplogWithUpdateCapture = nullptr;
+ bool m_OplogHasUpdateCapture = false;
std::vector<IoHash> m_References;
std::vector<IoHash> m_AddedReferences;
GcClock::TimePoint m_OplogAccessTime;
@@ -6608,7 +6103,7 @@ public:
ProjectStore::Oplog::ValidationResult Result;
Stopwatch Timer;
- const auto _ = MakeGuard([&] {
+ const auto _ = MakeGuard([&] {
if (!Ctx.Settings.Verbose)
{
return;
@@ -6619,42 +6114,52 @@ public:
m_OplogId,
NiceTimeSpanMs(Timer.GetElapsedTimeMs()),
Result.OpCount,
- Result.LSNLow,
- Result.LSNHigh,
+ Result.LSNLow.Number,
+ Result.LSNHigh.Number,
Status);
});
- ProjectStore::Oplog* TempOplog = nullptr;
- auto __ = MakeGuard([this, &TempOplog]() {
- if (TempOplog != nullptr)
- {
- delete TempOplog;
- }
- });
- ProjectStore::Oplog* Oplog = nullptr;
- Ref<ProjectStore::Project> Project = m_ProjectStore.OpenProject(m_ProjectId);
+ Ref<ProjectStore::Oplog> Oplog;
+ Ref<ProjectStore::Project> Project = m_ProjectStore.OpenProject(m_ProjectId);
if (Project)
{
- RwLock::SharedLockScope ___(Project->m_ProjectLock);
- if (auto It = Project->m_Oplogs.find(m_OplogId); It != Project->m_Oplogs.end())
- {
- Oplog = It->second.get();
- }
- else
{
- std::filesystem::path OplogBasePath = Project->BasePathForOplog(m_OplogId);
- TempOplog = new ProjectStore::Oplog(m_OplogId, Project.Get(), Project->m_CidStore, OplogBasePath, std::filesystem::path{});
- Oplog = TempOplog;
- Oplog->Read();
-
- if (Ctx.IsCancelledFlag)
+ RwLock::SharedLockScope __(Project->m_ProjectLock);
+ if (auto It = Project->m_Oplogs.find(m_OplogId); It != Project->m_Oplogs.end())
{
- return;
+ Oplog = It->second;
+ }
+ else
+ {
+ Stopwatch OplogTimer;
+
+ std::filesystem::path OplogBasePath = Project->BasePathForOplog(m_OplogId);
+ Oplog = Ref<ProjectStore::Oplog>(new ProjectStore::Oplog(Project->Log(),
+ Project->Identifier,
+ m_OplogId,
+ Project->m_CidStore,
+ OplogBasePath,
+ std::filesystem::path{},
+ ProjectStore::Oplog::EMode::kBasicReadOnly));
+ Oplog->Read();
+ if (Ctx.Settings.Verbose)
+ {
+ ZEN_INFO("GCV2: projectstore [VALIDATE] '{}': read oplog '{}/{}' in {}",
+ OplogBasePath,
+ Project->Identifier,
+ m_OplogId,
+ NiceTimeSpanMs(OplogTimer.GetElapsedTimeMs()));
+ }
+
+ if (Ctx.IsCancelledFlag)
+ {
+ return;
+ }
}
}
- if (Oplog != nullptr)
+ if (Oplog)
{
- Result = Oplog->Validate(Ctx.IsCancelledFlag, nullptr);
+ Result = Oplog->Validate(Project->RootDir, Ctx.IsCancelledFlag, nullptr);
if (Ctx.IsCancelledFlag)
{
return;
@@ -6691,6 +6196,9 @@ ProjectStore::CreateReferenceValidators(GcCtx& Ctx)
{
return {};
}
+
+ auto Log = [&Ctx]() { return Ctx.Logger; };
+
DiscoverProjects();
std::vector<std::pair<std::string, std::string>> Oplogs;
@@ -6721,14 +6229,8 @@ ProjectStore::CreateReferenceValidators(GcCtx& Ctx)
Oid
OpKeyStringAsOid(std::string_view OpKey)
{
- using namespace std::literals;
-
- CbObjectWriter Writer;
- Writer << "key"sv << OpKey;
-
- const Oid OpId = ComputeOpKey(Writer.Save());
-
- return OpId;
+ eastl::fixed_vector<uint8_t, 512> Buffer;
+ return OpKeyStringAsOid(OpKey, Buffer);
}
//////////////////////////////////////////////////////////////////////////
@@ -6738,14 +6240,14 @@ OpKeyStringAsOid(std::string_view OpKey)
namespace testutils {
using namespace std::literals;
- std::string OidAsString(const Oid& Id)
+ static std::string OidAsString(const Oid& Id)
{
StringBuilder<25> OidStringBuilder;
Id.ToString(OidStringBuilder);
return OidStringBuilder.ToString();
}
- CbPackage CreateBulkDataOplogPackage(const Oid& Id, const std::span<const std::pair<Oid, CompressedBuffer>>& Attachments)
+ static CbPackage CreateBulkDataOplogPackage(const Oid& Id, const std::span<const std::pair<Oid, CompressedBuffer>>& Attachments)
{
CbPackage Package;
CbObjectWriter Object;
@@ -6771,9 +6273,9 @@ namespace testutils {
return Package;
};
- CbPackage CreateFilesOplogPackage(const Oid& Id,
- const std::filesystem::path ProjectRootDir,
- const std::span<const std::pair<Oid, std::filesystem::path>>& Attachments)
+ static CbPackage CreateFilesOplogPackage(const Oid& Id,
+ const std::filesystem::path ProjectRootDir,
+ const std::span<const std::pair<Oid, std::filesystem::path>>& Attachments)
{
CbPackage Package;
CbObjectWriter Object;
@@ -6797,7 +6299,7 @@ namespace testutils {
return Package;
};
- std::vector<std::pair<Oid, CompressedBuffer>> CreateAttachments(
+ static std::vector<std::pair<Oid, CompressedBuffer>> CreateAttachments(
const std::span<const size_t>& Sizes,
OodleCompressionLevel CompressionLevel = OodleCompressionLevel::VeryFast,
uint64_t BlockSize = 0)
@@ -6813,7 +6315,7 @@ namespace testutils {
return Result;
}
- uint64_t GetCompressedOffset(const CompressedBuffer& Buffer, uint64_t RawOffset)
+ static uint64_t GetCompressedOffset(const CompressedBuffer& Buffer, uint64_t RawOffset)
{
if (RawOffset > 0)
{
@@ -6945,8 +6447,6 @@ TEST_CASE("project.store.create")
ScopedTemporaryDirectory TempDir;
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
GcManager Gc;
CidStore CidStore(Gc);
CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas", .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
@@ -6954,7 +6454,7 @@ TEST_CASE("project.store.create")
std::string_view ProjectName("proj1"sv);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
std::filesystem::path ProjectRootDir = TempDir.Path() / "game";
@@ -6976,15 +6476,13 @@ TEST_CASE("project.store.lifetimes")
ScopedTemporaryDirectory TempDir;
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
GcManager Gc;
CidStore CidStore(Gc);
CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas", .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
std::filesystem::path ProjectRootDir = TempDir.Path() / "game";
@@ -6996,145 +6494,19 @@ TEST_CASE("project.store.lifetimes")
EngineRootDir.string(),
ProjectRootDir.string(),
ProjectFilePath.string()));
- ProjectStore::Oplog* Oplog = Project->NewOplog("oplog1", {});
- CHECK(Oplog != nullptr);
+ Ref<ProjectStore::Oplog> Oplog = Project->NewOplog("oplog1", {});
+ CHECK(Oplog);
std::filesystem::path DeletePath;
CHECK(Project->PrepareForDelete(DeletePath));
CHECK(!DeletePath.empty());
- CHECK(Project->OpenOplog("oplog1", /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true) == nullptr);
+ CHECK(!Project->OpenOplog("oplog1", /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true));
// Oplog is now invalid, but pointer can still be accessed since we store old oplog pointers
CHECK(Oplog->OplogCount() == 0);
// Project is still valid since we have a Ref to it
CHECK(Project->Identifier == "proj1"sv);
}
-struct ExportForceDisableBlocksTrue_ForceTempBlocksFalse
-{
- static const bool ForceDisableBlocks = true;
- static const bool ForceEnableTempBlocks = false;
-};
-
-struct ExportForceDisableBlocksFalse_ForceTempBlocksFalse
-{
- static const bool ForceDisableBlocks = false;
- static const bool ForceEnableTempBlocks = false;
-};
-
-struct ExportForceDisableBlocksFalse_ForceTempBlocksTrue
-{
- static const bool ForceDisableBlocks = false;
- static const bool ForceEnableTempBlocks = true;
-};
-
-TEST_CASE_TEMPLATE("project.store.export",
- Settings,
- ExportForceDisableBlocksTrue_ForceTempBlocksFalse,
- ExportForceDisableBlocksFalse_ForceTempBlocksFalse,
- ExportForceDisableBlocksFalse_ForceTempBlocksTrue)
-{
- using namespace std::literals;
- using namespace testutils;
-
- ScopedTemporaryDirectory TempDir;
- ScopedTemporaryDirectory ExportDir;
-
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
- GcManager Gc;
- CidStore CidStore(Gc);
- CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas", .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
- CidStore.Initialize(CidConfig);
-
- std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
- std::filesystem::path RootDir = TempDir.Path() / "root";
- std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
- std::filesystem::path ProjectRootDir = TempDir.Path() / "game";
- std::filesystem::path ProjectFilePath = TempDir.Path() / "game" / "game.uproject";
-
- Ref<ProjectStore::Project> Project(ProjectStore.NewProject(BasePath / "proj1"sv,
- "proj1"sv,
- RootDir.string(),
- EngineRootDir.string(),
- ProjectRootDir.string(),
- ProjectFilePath.string()));
- ProjectStore::Oplog* Oplog = Project->NewOplog("oplog1", {});
- CHECK(Oplog != nullptr);
-
- Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), {}));
- Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{77})));
- Oplog->AppendNewOplogEntry(
- CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{7123, 583, 690, 99})));
- Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{55, 122})));
- Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(
- Oid::NewOid(),
- CreateAttachments(std::initializer_list<size_t>{256u * 1024u, 92u * 1024u}, OodleCompressionLevel::None)));
-
- FileRemoteStoreOptions Options = {
- RemoteStoreOptions{.MaxBlockSize = 64u * 1024, .MaxChunkEmbedSize = 32 * 1024u, .ChunkFileSizeLimit = 64u * 1024u},
- /*.FolderPath = */ ExportDir.Path(),
- /*.Name = */ std::string("oplog1"),
- /*OptionalBaseName = */ std::string(),
- /*.ForceDisableBlocks = */ Settings::ForceDisableBlocks,
- /*.ForceEnableTempBlocks = */ Settings::ForceEnableTempBlocks};
- std::shared_ptr<RemoteProjectStore> RemoteStore = CreateFileRemoteStore(Options);
- RemoteProjectStore::RemoteStoreInfo StoreInfo = RemoteStore->GetInfo();
-
- RemoteProjectStore::Result ExportResult = SaveOplog(CidStore,
- *RemoteStore,
- *Project.Get(),
- *Oplog,
- Options.MaxBlockSize,
- Options.MaxChunkEmbedSize,
- Options.ChunkFileSizeLimit,
- true,
- false,
- false,
- nullptr);
-
- CHECK(ExportResult.ErrorCode == 0);
-
- ProjectStore::Oplog* OplogImport = Project->NewOplog("oplog2", {});
- CHECK(OplogImport != nullptr);
-
- RemoteProjectStore::Result ImportResult = LoadOplog(CidStore,
- *RemoteStore,
- *OplogImport,
- /*Force*/ false,
- /*IgnoreMissingAttachments*/ false,
- /*CleanOplog*/ false,
- nullptr);
- CHECK(ImportResult.ErrorCode == 0);
-
- RemoteProjectStore::Result ImportForceResult = LoadOplog(CidStore,
- *RemoteStore,
- *OplogImport,
- /*Force*/ true,
- /*IgnoreMissingAttachments*/ false,
- /*CleanOplog*/ false,
- nullptr);
- CHECK(ImportForceResult.ErrorCode == 0);
-
- RemoteProjectStore::Result ImportCleanResult = LoadOplog(CidStore,
- *RemoteStore,
- *OplogImport,
- /*Force*/ false,
- /*IgnoreMissingAttachments*/ false,
- /*CleanOplog*/ true,
- nullptr);
- CHECK(ImportCleanResult.ErrorCode == 0);
-
- RemoteProjectStore::Result ImportForceCleanResult = LoadOplog(CidStore,
- *RemoteStore,
- *OplogImport,
- /*Force*/ true,
- /*IgnoreMissingAttachments*/ false,
- /*CleanOplog*/ true,
- nullptr);
- CHECK(ImportForceCleanResult.ErrorCode == 0);
-}
-
TEST_CASE("project.store.gc")
{
using namespace std::literals;
@@ -7142,15 +6514,13 @@ TEST_CASE("project.store.gc")
ScopedTemporaryDirectory TempDir;
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
GcManager Gc;
CidStore CidStore(Gc);
CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas", .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
@@ -7195,8 +6565,8 @@ TEST_CASE("project.store.gc")
EngineRootDir.string(),
Project1RootDir.string(),
Project1FilePath.string()));
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1", Project1OplogPath);
- CHECK(Oplog != nullptr);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1", Project1OplogPath);
+ CHECK(Oplog);
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), {}));
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{77})));
@@ -7213,8 +6583,8 @@ TEST_CASE("project.store.gc")
Project2RootDir.string(),
Project2FilePath.string()));
{
- ProjectStore::Oplog* Oplog = Project2->NewOplog("oplog2", Project2Oplog1Path);
- CHECK(Oplog != nullptr);
+ Ref<ProjectStore::Oplog> Oplog = Project2->NewOplog("oplog2", Project2Oplog1Path);
+ CHECK(Oplog);
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), {}));
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{177})));
@@ -7224,8 +6594,8 @@ TEST_CASE("project.store.gc")
CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{535, 221})));
}
{
- ProjectStore::Oplog* Oplog = Project2->NewOplog("oplog3", Project2Oplog2Path);
- CHECK(Oplog != nullptr);
+ Ref<ProjectStore::Oplog> Oplog = Project2->NewOplog("oplog3", Project2Oplog2Path);
+ CHECK(Oplog);
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), {}));
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), CreateAttachments(std::initializer_list<size_t>{137})));
@@ -7343,15 +6713,13 @@ TEST_CASE("project.store.gc.prep")
ScopedTemporaryDirectory TempDir;
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
GcManager Gc;
CidStore CidStore(Gc);
CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas", .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore";
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
std::filesystem::path RootDir = TempDir.Path() / "root";
std::filesystem::path EngineRootDir = TempDir.Path() / "engine";
@@ -7383,7 +6751,7 @@ TEST_CASE("project.store.gc.prep")
EngineRootDir.string(),
Project1RootDir.string(),
Project1FilePath.string()));
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), OpAttachments));
}
{
@@ -7414,13 +6782,13 @@ TEST_CASE("project.store.gc.prep")
{
// Make sure the chunks are stored but not the referencing op
Ref<ProjectStore::Project> Project1 = ProjectStore.OpenProject("proj1"sv);
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), OpAttachments));
Project1->DeleteOplog("oplog1"sv);
}
{
Ref<ProjectStore::Project> Project1 = ProjectStore.OpenProject("proj1"sv);
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
// Equivalent of a `prep` call with tracking of ops
CHECK(Oplog->CheckPendingChunkReferences(OpChunkHashes, std::chrono::hours(1)).empty());
@@ -7461,7 +6829,7 @@ TEST_CASE("project.store.gc.prep")
{
Ref<ProjectStore::Project> Project1 = ProjectStore.OpenProject("proj1"sv);
- ProjectStore::Oplog* Oplog = Project1->OpenOplog("oplog1"sv, true, true);
+ Ref<ProjectStore::Oplog> Oplog = Project1->OpenOplog("oplog1"sv, true, true);
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), OpAttachments));
Oplog->RemovePendingChunkReferences(OpChunkHashes);
CHECK(Oplog->GetPendingChunkReferencesLocked().size() == 0);
@@ -7495,15 +6863,16 @@ TEST_CASE("project.store.gc.prep")
{
// Make sure the chunks are stored but not the referencing op
Ref<ProjectStore::Project> Project1 = ProjectStore.OpenProject("proj1"sv);
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(Oid::NewOid(), OpAttachments));
Project1->DeleteOplog("oplog1"sv);
}
- // Caution - putting breakpoints and stepping through this part of the test likely makes it fails due to expiry time of pending chunks
+ // Caution - putting breakpoints and stepping through this part of the test likely makes it fails due to expiry time of pending
+ // chunks
{
Ref<ProjectStore::Project> Project1 = ProjectStore.OpenProject("proj1"sv);
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1"sv, Project1OplogPath);
CHECK(Oplog->CheckPendingChunkReferences(OpChunkHashes, std::chrono::milliseconds(100)).empty());
}
@@ -7522,8 +6891,8 @@ TEST_CASE("project.store.gc.prep")
}
Sleep(200);
- // This pass they should also be retained since our age retention has kept them alive and they will now be picked up and the retention
- // cleared
+ // This pass they should also be retained since our age retention has kept them alive and they will now be picked up and the
+ // retention cleared
{
GcSettings Settings = {.CacheExpireTime = GcClock::Now(),
.ProjectStoreExpireTime = GcClock::Now(),
@@ -7558,15 +6927,13 @@ TEST_CASE("project.store.rpc.getchunks")
ScopedTemporaryDirectory TempDir;
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
GcManager Gc;
CidStore CidStore(Gc);
CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas"sv, .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore"sv;
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
std::filesystem::path RootDir = TempDir.Path() / "root"sv;
std::filesystem::path EngineRootDir = TempDir.Path() / "engine"sv;
@@ -7590,8 +6957,8 @@ TEST_CASE("project.store.rpc.getchunks")
EngineRootDir.string(),
Project1RootDir.string(),
Project1FilePath.string()));
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1"sv, {});
- CHECK(Oplog != nullptr);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1"sv, {});
+ CHECK(Oplog);
Attachments[OpIds[0]] = {};
Attachments[OpIds[1]] = CreateAttachments(std::initializer_list<size_t>{77});
Attachments[OpIds[2]] =
@@ -7610,24 +6977,30 @@ TEST_CASE("project.store.rpc.getchunks")
Oplog->AppendNewOplogEntry(CreateFilesOplogPackage(FilesOpId, RootDir, FilesOpIdAttachments));
}
+ auto GetChunks = [](zen::ProjectStore& ProjectStore, ProjectStore::Project& Project, ProjectStore::Oplog& Oplog, const CbObject& Cb) {
+ std::vector<ProjectStore::ChunkRequest> Requests = ProjectStore.ParseChunksRequests(Project, Oplog, Cb);
+ std::vector<ProjectStore::ChunkResult> Results =
+ Requests.empty() ? std::vector<ProjectStore::ChunkResult>{} : ProjectStore.GetChunks(Project, Oplog, Requests);
+ return ProjectStore.WriteChunksRequestResponse(Project, Oplog, std::move(Requests), std::move(Results));
+ };
+
+ Ref<ProjectStore::Project> Project1 = ProjectStore.OpenProject("proj1"sv);
+ CHECK(Project1);
+ Ref<ProjectStore::Oplog> Oplog1 = Project1->OpenOplog("oplog1"sv, false, true);
+ CHECK(Oplog1);
// Invalid request
{
CbObjectWriter Request;
Request.BeginObject("WrongName"sv);
Request.EndObject();
CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv, "oplog1"sv, Request.Save(), Response);
- CHECK_EQ(HttpResponseCode::BadRequest, Result.first);
+ CHECK_THROWS(GetChunks(ProjectStore, *Project1, *Oplog1, Request.Save()));
}
// Empty request
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, std::vector<IoHash>{}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ false, std::vector<IoHash>{}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(0, Chunks.Num());
@@ -7635,23 +7008,15 @@ TEST_CASE("project.store.rpc.getchunks")
// Single non-existing chunk by RawHash
IoHash NotFoundIoHash = IoHash::Max;
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {NotFoundIoHash}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ false, {NotFoundIoHash}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(0, Chunks.Num());
}
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {NotFoundIoHash}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ true, {NotFoundIoHash}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(0, Chunks.Num());
@@ -7659,21 +7024,15 @@ TEST_CASE("project.store.rpc.getchunks")
// Single non-existing chunk by Id
Oid NotFoundId = Oid::NewOid();
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {NotFoundId}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ false, {NotFoundId}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(0, Chunks.Num());
}
{
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv, "oplog1"sv, testutils::BuildChunksRequest(/*SkipData*/ true, {NotFoundId}, {}, {}), Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ true, {NotFoundId}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(0, Chunks.Num());
@@ -7684,13 +7043,11 @@ TEST_CASE("project.store.rpc.getchunks")
IoHash FirstAttachmentHash = Attachments[OpIds[2]][1].second.DecodeRawHash();
uint64_t ResponseModTag = 0;
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentHash}, {}, {}),
- Response);
+ const CbPackage& Response = GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentHash}, {}, {}));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7710,13 +7067,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Fetch with matching ModTag
{
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentHash}, {}, {ResponseModTag}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentHash}, {}, {ResponseModTag}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7731,14 +7086,12 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Fetch with mismatching ModTag
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks(
- "proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentHash}, {}, {uint64_t(ResponseModTag + 1)}),
- Response);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentHash}, {}, {uint64_t(ResponseModTag + 1)}));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7758,12 +7111,10 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Fresh modtime query
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentHash}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response = GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentHash}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7778,13 +7129,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Modtime query with matching ModTag
{
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentHash}, {}, {ResponseModTag}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentHash}, {}, {ResponseModTag}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7799,13 +7148,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Modtime query with mismatching ModTag
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks(
- "proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentHash}, {}, {uint64_t(ResponseModTag + 1)}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentHash}, {}, {uint64_t(ResponseModTag + 1)}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7825,12 +7172,8 @@ TEST_CASE("project.store.rpc.getchunks")
uint64_t ResponseModTag = 0;
{
// Full chunk request
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {}));
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7850,13 +7193,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Partial chunk request
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {{130 * 1024, 8100}}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {{130 * 1024, 8100}}, {}));
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7881,13 +7222,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fetch with matching ModTag
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {ResponseModTag}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {ResponseModTag}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7902,13 +7241,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fetch with mismatching ModTag
- CbPackage Response;
- auto Result = ProjectStore.GetChunks(
- "proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}));
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7928,12 +7265,8 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Fresh modtime query
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7948,13 +7281,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Modtime query with matching ModTag
{
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {ResponseModTag}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {ResponseModTag}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7969,13 +7300,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Modtime query with mismatching ModTag
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks(
- "proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -7996,12 +7325,8 @@ TEST_CASE("project.store.rpc.getchunks")
uint64_t ResponseModTag = 0;
{
// Full chunk request
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {}));
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -8021,13 +7346,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Partial chunk request
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {{81823, 5434}}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {{81823, 5434}}, {}));
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -8049,13 +7372,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fetch with matching ModTag
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {ResponseModTag}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {ResponseModTag}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -8070,13 +7391,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fetch with mismatching ModTag
- CbPackage Response;
- auto Result = ProjectStore.GetChunks(
- "proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}));
CHECK_EQ(1, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -8096,12 +7415,8 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Fresh modtime query
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -8116,13 +7431,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Modtime query with matching ModTag
{
- CbPackage Response;
- auto Result =
- ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {ResponseModTag}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {ResponseModTag}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -8137,13 +7450,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
// Modtime query with mismatching ModTag
{
- CbPackage Response;
- auto Result = ProjectStore.GetChunks(
- "proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}),
- Response);
- CHECK_EQ(HttpResponseCode::OK, Result.first);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, {FirstAttachmentId}, {}, {uint64_t(ResponseModTag + 1)}));
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(1, Chunks.Num());
@@ -8169,13 +7480,9 @@ TEST_CASE("project.store.rpc.getchunks")
std::vector<uint64_t> ResponseModTags(3, 0);
{
// Fresh fetch
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, AttachmentHashes, {}, {}),
- Response);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ false, AttachmentHashes, {}, {}));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(3, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8202,13 +7509,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fetch with matching ModTag
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, AttachmentHashes, {}, ResponseModTags),
- Response);
+ const CbPackage& Response = GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, AttachmentHashes, {}, ResponseModTags));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8229,13 +7534,9 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fresh modtime query
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, AttachmentHashes, {}, {}),
- Response);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ true, AttachmentHashes, {}, {}));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8256,13 +7557,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Modtime query with matching ModTags
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, AttachmentHashes, {}, ResponseModTags),
- Response);
+ const CbPackage& Response = GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, AttachmentHashes, {}, ResponseModTags));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8288,13 +7587,12 @@ TEST_CASE("project.store.rpc.getchunks")
{
Tag++;
}
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, AttachmentHashes, {}, MismatchingModTags),
- Response);
+ const CbPackage& Response =
+ GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, AttachmentHashes, {}, MismatchingModTags));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8326,13 +7624,9 @@ TEST_CASE("project.store.rpc.getchunks")
std::vector<uint64_t> ResponseModTags(3, 0);
{
// Fresh fetch
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, AttachedIds, {}, {}),
- Response);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ false, AttachedIds, {}, {}));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(3, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8359,13 +7653,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fetch with matching ModTag
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ false, AttachedIds, {}, ResponseModTags),
- Response);
+ const CbPackage& Response = GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ false, AttachedIds, {}, ResponseModTags));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8386,13 +7678,9 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Fresh modtime query
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, AttachedIds, {}, {}),
- Response);
+ const CbPackage& Response =
+ GetChunks(ProjectStore, *Project1, *Oplog1, testutils::BuildChunksRequest(/*SkipData*/ true, AttachedIds, {}, {}));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8413,13 +7701,11 @@ TEST_CASE("project.store.rpc.getchunks")
}
{
// Modtime query with matching ModTags
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, AttachedIds, {}, ResponseModTags),
- Response);
+ const CbPackage& Response = GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, AttachedIds, {}, ResponseModTags));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8445,13 +7731,11 @@ TEST_CASE("project.store.rpc.getchunks")
{
Tag++;
}
- CbPackage Response;
- auto Result = ProjectStore.GetChunks("proj1"sv,
- "oplog1"sv,
- testutils::BuildChunksRequest(/*SkipData*/ true, AttachedIds, {}, MismatchingModTags),
- Response);
+ const CbPackage& Response = GetChunks(ProjectStore,
+ *Project1,
+ *Oplog1,
+ testutils::BuildChunksRequest(/*SkipData*/ true, AttachedIds, {}, MismatchingModTags));
- CHECK_EQ(HttpResponseCode::OK, Result.first);
CHECK_EQ(0, Response.GetAttachments().size());
CbArrayView Chunks = Response.GetObject()["Chunks"].AsArrayView();
CHECK_EQ(3, Chunks.Num());
@@ -8480,15 +7764,13 @@ TEST_CASE("project.store.partial.read")
ScopedTemporaryDirectory TempDir;
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
GcManager Gc;
CidStore CidStore(Gc);
CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas"sv, .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore"sv;
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
std::filesystem::path RootDir = TempDir.Path() / "root"sv;
std::filesystem::path EngineRootDir = TempDir.Path() / "engine"sv;
@@ -8510,8 +7792,8 @@ TEST_CASE("project.store.partial.read")
EngineRootDir.string(),
Project1RootDir.string(),
Project1FilePath.string()));
- ProjectStore::Oplog* Oplog = Project1->NewOplog("oplog1"sv, {});
- CHECK(Oplog != nullptr);
+ Ref<ProjectStore::Oplog> Oplog = Project1->NewOplog("oplog1"sv, {});
+ CHECK(Oplog);
Attachments[OpIds[0]] = {};
Attachments[OpIds[1]] = CreateAttachments(std::initializer_list<size_t>{77});
Attachments[OpIds[2]] = CreateAttachments(std::initializer_list<size_t>{7123, 9583, 690, 99});
@@ -8521,80 +7803,84 @@ TEST_CASE("project.store.partial.read")
Oplog->AppendNewOplogEntry(CreateBulkDataOplogPackage(It.first, It.second));
}
}
+ Ref<ProjectStore::Project> Project1 = ProjectStore.OpenProject("proj1"sv);
+ CHECK(Project1);
+ Ref<ProjectStore::Oplog> Oplog1 = Project1->OpenOplog("oplog1"sv, false, true);
+ CHECK(Oplog1);
{
uint64_t ModificationTag = 0;
- IoBuffer Chunk;
- CHECK(ProjectStore
- .GetChunk("proj1"sv, "oplog1"sv, Attachments[OpIds[1]][0].second.DecodeRawHash().ToHexString(), Chunk, &ModificationTag)
- .first == HttpResponseCode::OK);
+
+ auto Result = ProjectStore.GetChunkRange(Log(),
+ *Project1,
+ *Oplog1,
+ Attachments[OpIds[1]][0].first,
+ 0,
+ ~0ull,
+ ZenContentType::kCompressedBinary,
+ &ModificationTag);
+
+ CHECK_EQ(ProjectStore::GetChunkRangeResult::EError::Ok, Result.Error);
+
IoHash RawHash;
uint64_t RawSize;
- CompressedBuffer Attachment = CompressedBuffer::FromCompressed(SharedBuffer(Chunk), RawHash, RawSize);
+ CompressedBuffer Attachment = CompressedBuffer::FromCompressed(Result.Chunk, RawHash, RawSize);
CHECK(RawSize == Attachments[OpIds[1]][0].second.DecodeRawSize());
- CHECK(ModificationTag != 0);
- CHECK(ProjectStore
- .GetChunk("proj1"sv, "oplog1"sv, Attachments[OpIds[1]][0].second.DecodeRawHash().ToHexString(), Chunk, &ModificationTag)
- .first == HttpResponseCode::NotModified);
+ auto Result2 = ProjectStore.GetChunkRange(Log(),
+ *Project1,
+ *Oplog1,
+ Attachments[OpIds[1]][0].first,
+ 0,
+ ~0ull,
+ ZenContentType::kCompressedBinary,
+ &ModificationTag);
+ CHECK_EQ(ProjectStore::GetChunkRangeResult::EError::NotModified, Result2.Error);
}
{
uint64_t FullChunkModificationTag = 0;
{
- CompositeBuffer ChunkResult;
- HttpContentType ContentType;
- CHECK(ProjectStore
- .GetChunkRange("proj1"sv,
- "oplog1"sv,
- OidAsString(Attachments[OpIds[2]][1].first),
- 0,
- ~0ull,
- HttpContentType::kCompressedBinary,
- ChunkResult,
- ContentType,
- &FullChunkModificationTag)
- .first == HttpResponseCode::OK);
- CHECK(ChunkResult);
- CHECK(CompressedBuffer::FromCompressedNoValidate(std::move(ChunkResult)).DecodeRawSize() ==
+ auto Result = ProjectStore.GetChunkRange(Log(),
+ *Project1,
+ *Oplog1,
+ Attachments[OpIds[2]][1].first,
+ 0,
+ ~0ull,
+ ZenContentType::kCompressedBinary,
+ &FullChunkModificationTag);
+ CHECK_EQ(Result.Error, ProjectStore::GetChunkRangeResult::EError::Ok);
+ CHECK(Result.Chunk);
+ CHECK(CompressedBuffer::FromCompressedNoValidate(std::move(Result.Chunk)).DecodeRawSize() ==
Attachments[OpIds[2]][1].second.DecodeRawSize());
}
{
- CompositeBuffer ChunkResult;
- HttpContentType ContentType;
- CHECK(ProjectStore
- .GetChunkRange("proj1"sv,
- "oplog1"sv,
- OidAsString(Attachments[OpIds[2]][1].first),
- 0,
- ~0ull,
- HttpContentType::kCompressedBinary,
- ChunkResult,
- ContentType,
- &FullChunkModificationTag)
- .first == HttpResponseCode::NotModified);
- }
- }
- {
- CompositeBuffer PartialChunkResult;
- uint64_t PartialChunkModificationTag = 0;
- {
- CompositeBuffer ChunkResult;
- HttpContentType ContentType;
- CHECK(ProjectStore
- .GetChunkRange("proj1"sv,
- "oplog1"sv,
- OidAsString(Attachments[OpIds[2]][1].first),
- 5,
- 1773,
- HttpContentType::kCompressedBinary,
- PartialChunkResult,
- ContentType,
- &PartialChunkModificationTag)
- .first == HttpResponseCode::OK);
- CHECK(PartialChunkResult);
+ auto Result = ProjectStore.GetChunkRange(Log(),
+ *Project1,
+ *Oplog1,
+ Attachments[OpIds[2]][1].first,
+ 0,
+ ~0ull,
+ ZenContentType::kCompressedBinary,
+ &FullChunkModificationTag);
+ CHECK_EQ(Result.Error, ProjectStore::GetChunkRangeResult::EError::NotModified);
+ }
+ }
+ {
+ uint64_t PartialChunkModificationTag = 0;
+ {
+ auto Result = ProjectStore.GetChunkRange(Log(),
+ *Project1,
+ *Oplog1,
+ Attachments[OpIds[2]][1].first,
+ 5,
+ 1773,
+ ZenContentType::kCompressedBinary,
+ &PartialChunkModificationTag);
+ CHECK_EQ(Result.Error, ProjectStore::GetChunkRangeResult::EError::Ok);
+
IoHash PartialRawHash;
uint64_t PartialRawSize;
- CompressedBuffer PartialCompressedResult = CompressedBuffer::FromCompressed(PartialChunkResult, PartialRawHash, PartialRawSize);
+ CompressedBuffer PartialCompressedResult = CompressedBuffer::FromCompressed(Result.Chunk, PartialRawHash, PartialRawSize);
CHECK(PartialRawSize >= 1773);
uint64_t RawOffsetInPartialCompressed = GetCompressedOffset(PartialCompressedResult, 5);
@@ -8606,51 +7892,19 @@ TEST_CASE("project.store.partial.read")
}
{
- CompositeBuffer ChunkResult;
- HttpContentType ContentType;
- CHECK(ProjectStore
- .GetChunkRange("proj1"sv,
- "oplog1"sv,
- OidAsString(Attachments[OpIds[2]][1].first),
- 5,
- 1773,
- HttpContentType::kCompressedBinary,
- PartialChunkResult,
- ContentType,
- &PartialChunkModificationTag)
- .first == HttpResponseCode::NotModified);
+ auto Result = ProjectStore.GetChunkRange(Log(),
+ *Project1,
+ *Oplog1,
+ Attachments[OpIds[2]][1].first,
+ 0,
+ 1773,
+ ZenContentType::kCompressedBinary,
+ &PartialChunkModificationTag);
+ CHECK_EQ(Result.Error, ProjectStore::GetChunkRangeResult::EError::NotModified);
}
}
}
-TEST_CASE("project.store.block")
-{
- using namespace std::literals;
- using namespace testutils;
-
- std::vector<std::size_t> AttachmentSizes({7633, 6825, 5738, 8031, 7225, 566, 3656, 6006, 24, 3466, 1093, 4269, 2257, 3685, 3489,
- 7194, 6151, 5482, 6217, 3511, 6738, 5061, 7537, 2759, 1916, 8210, 2235, 4024, 1582, 5251,
- 491, 5464, 4607, 8135, 3767, 4045, 4415, 5007, 8876, 6761, 3359, 8526, 4097, 4855, 8225});
-
- std::vector<std::pair<Oid, CompressedBuffer>> AttachmentsWithId = CreateAttachments(AttachmentSizes);
- std::vector<std::pair<IoHash, FetchChunkFunc>> Chunks;
- Chunks.reserve(AttachmentSizes.size());
- for (const auto& It : AttachmentsWithId)
- {
- Chunks.push_back(
- std::make_pair(It.second.DecodeRawHash(), [Buffer = It.second](const IoHash&) -> std::pair<uint64_t, CompressedBuffer> {
- return {Buffer.DecodeRawSize(), Buffer};
- }));
- }
- ChunkBlockDescription Block;
- CompressedBuffer BlockBuffer = GenerateChunkBlock(std::move(Chunks), Block);
- uint64_t HeaderSize;
- CHECK(IterateChunkBlock(
- BlockBuffer.Decompress(),
- [](CompressedBuffer&&, const IoHash&) {},
- HeaderSize));
-}
-
TEST_CASE("project.store.iterateoplog")
{
using namespace std::literals;
@@ -8658,15 +7912,13 @@ TEST_CASE("project.store.iterateoplog")
ScopedTemporaryDirectory TempDir;
- auto JobQueue = MakeJobQueue(1, ""sv);
- OpenProcessCache ProcessCache;
GcManager Gc;
CidStore CidStore(Gc);
CidStoreConfiguration CidConfig = {.RootDirectory = TempDir.Path() / "cas"sv, .TinyValueThreshold = 1024, .HugeValueThreshold = 4096};
CidStore.Initialize(CidConfig);
std::filesystem::path BasePath = TempDir.Path() / "projectstore"sv;
- ProjectStore ProjectStore(CidStore, BasePath, Gc, *JobQueue, ProcessCache, ProjectStore::Configuration{});
+ ProjectStore ProjectStore(CidStore, BasePath, Gc, ProjectStore::Configuration{});
std::filesystem::path RootDir = TempDir.Path() / "root"sv;
std::filesystem::path EngineRootDir = TempDir.Path() / "enginesv";
@@ -8690,8 +7942,8 @@ TEST_CASE("project.store.iterateoplog")
EngineRootDir.string(),
ProjectRootDir.string(),
ProjectFilePath.string()));
- ProjectStore::Oplog* Oplog = TestProject->NewOplog("oplog"sv, ProjectOplogPath);
- CHECK(Oplog != nullptr);
+ Ref<ProjectStore::Oplog> Oplog = TestProject->NewOplog("oplog"sv, ProjectOplogPath);
+ CHECK(Oplog);
struct TestOidData
{
@@ -8715,7 +7967,7 @@ TEST_CASE("project.store.iterateoplog")
}
};
auto IncrementCount = [&Count](CbObjectView /* Op */) { ++Count; };
- auto MarkFound = [&TestOids, &Count](uint32_t /* LSN */, const Oid& /* InId */, CbObjectView Op) {
+ auto MarkFound = [&TestOids, &Count](ProjectStore::LogSequenceNumber /* LSN */, const Oid& /* InId */, CbObjectView Op) {
for (TestOidData& TestOid : TestOids)
{
if (Op["key"sv].AsString() == TestOid.Key)
diff --git a/src/zenutil/referencemetadata.cpp b/src/zenstore/referencemetadata.cpp
index bbcafcfba..e202b435b 100644
--- a/src/zenutil/referencemetadata.cpp
+++ b/src/zenstore/referencemetadata.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/referencemetadata.h>
+#include "referencemetadata.h"
//////////////////////////////////////////////////////////////////////////
#include <zencore/xxhash.h>
diff --git a/src/zenutil/include/zenutil/referencemetadata.h b/src/zenstore/referencemetadata.h
index 5160bfb8b..5160bfb8b 100644
--- a/src/zenutil/include/zenutil/referencemetadata.h
+++ b/src/zenstore/referencemetadata.h
diff --git a/src/zenserver/vfs/vfsimpl.cpp b/src/zenstore/vfsimpl.cpp
index 2bac6b756..0a918d452 100644
--- a/src/zenserver/vfs/vfsimpl.cpp
+++ b/src/zenstore/vfsimpl.cpp
@@ -1,13 +1,11 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "vfsimpl.h"
-#include "vfsservice.h"
-
-#include "projectstore/projectstore.h"
+#include <zenstore/vfsimpl.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zenstore/cache/structuredcachestore.h>
+#include <zenstore/projectstore.h>
#include <zenvfs/projfs.h>
#include <zenvfs/vfs.h>
@@ -38,24 +36,13 @@ VfsOplogDataSource::ReadNamedData(std::string_view Path, void* Buffer, uint64_t
void
VfsOplogDataSource::ReadChunkData(const Oid& ChunkId, void* Buffer, uint64_t ByteOffset, uint64_t ByteCount)
{
- CompositeBuffer ChunkBuffer;
- ZenContentType ContentType;
- auto Result = m_ProjectStore->GetChunkRange(m_ProjectId,
- m_OplogId,
- ChunkId,
- 0,
- ~0ull,
- ZenContentType::kCompressedBinary,
- /* out */ ChunkBuffer,
- /* out */ ContentType,
- /* OptionalInOutModificationTag */ nullptr);
-
- if (Result.first == HttpResponseCode::OK)
+ IoBuffer ChunkBuffer = m_ProjectStore->GetChunk(m_ProjectId, m_OplogId, ChunkId);
+ if (ChunkBuffer)
{
ZEN_ASSERT(ChunkBuffer.GetSize() >= ByteOffset);
ZEN_ASSERT(ChunkBuffer.GetSize() - ByteOffset >= ByteCount);
MutableMemoryView Target(Buffer, ByteCount);
- ChunkBuffer.CopyTo(Target, ByteOffset);
+ Target.CopyFrom(ChunkBuffer.GetView().Mid(ByteOffset, ByteCount));
}
}
@@ -117,17 +104,17 @@ VfsCacheDataSource::PopulateDirectory(std::string NodePath, VfsTreeNode& DirNode
//////////////////////////////////////////////////////////////////////////
-VfsService::Impl::Impl()
+VfsServiceImpl::VfsServiceImpl()
{
}
-VfsService::Impl::~Impl()
+VfsServiceImpl::~VfsServiceImpl()
{
Unmount();
}
void
-VfsService::Impl::Mount(std::string_view MountPoint)
+VfsServiceImpl::Mount(std::string_view MountPoint)
{
ZEN_INFO("VFS mount requested at '{}'", MountPoint);
@@ -149,7 +136,7 @@ VfsService::Impl::Mount(std::string_view MountPoint)
}
void
-VfsService::Impl::Unmount()
+VfsServiceImpl::Unmount()
{
if (m_MountpointPath.empty())
{
@@ -164,7 +151,7 @@ VfsService::Impl::Unmount()
}
void
-VfsService::Impl::AddService(Ref<ProjectStore>&& Ps)
+VfsServiceImpl::AddService(Ref<ProjectStore>&& Ps)
{
m_ProjectStore = std::move(Ps);
@@ -172,7 +159,7 @@ VfsService::Impl::AddService(Ref<ProjectStore>&& Ps)
}
void
-VfsService::Impl::AddService(Ref<ZenCacheStore>&& Z$)
+VfsServiceImpl::AddService(Ref<ZenCacheStore>&& Z$)
{
m_ZenCacheStore = std::move(Z$);
@@ -180,7 +167,7 @@ VfsService::Impl::AddService(Ref<ZenCacheStore>&& Z$)
}
void
-VfsService::Impl::RefreshVfs()
+VfsServiceImpl::RefreshVfs()
{
if (m_VfsHost && m_MountpointPath.empty())
{
@@ -195,7 +182,7 @@ VfsService::Impl::RefreshVfs()
if (!m_VfsHost && !m_MountpointPath.empty())
{
- m_VfsThread = std::thread(&VfsService::Impl::VfsThread, this);
+ m_VfsThread = std::thread(&VfsServiceImpl::VfsThread, this);
m_VfsThreadRunning.Wait();
// At this stage, m_VfsHost should be initialized
@@ -225,7 +212,7 @@ VfsService::Impl::RefreshVfs()
}
void
-VfsService::Impl::VfsThread()
+VfsServiceImpl::VfsThread()
{
SetCurrentThreadName("VFS");
@@ -363,12 +350,12 @@ VfsServiceDataSource::PopulateDirectory(std::string NodePath, VfsTreeNode& DirNo
// Oplog contents enumeration
- if (ProjectStore::Oplog* Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true))
+ if (Ref<ProjectStore::Oplog> Oplog = Project->OpenOplog(OplogId, /*AllowCompact*/ false, /*VerifyPathOnDisk*/ true))
{
Ref<VfsOplogDataSource> DataSource = GetOplogDataSource(ProjectId, OplogId);
// Get metadata for all chunks
- std::vector<ProjectStore::Oplog::ChunkInfo> ChunkInfos = Oplog->GetAllChunksInfo();
+ std::vector<ProjectStore::Oplog::ChunkInfo> ChunkInfos = Oplog->GetAllChunksInfo(Project->RootDir);
std::unordered_map<zen::Oid, uint64_t> ChunkSizes;
diff --git a/src/zenstore/workspaces.cpp b/src/zenstore/workspaces.cpp
index 0ca2adab2..4e7bd79a3 100644
--- a/src/zenstore/workspaces.cpp
+++ b/src/zenstore/workspaces.cpp
@@ -622,17 +622,19 @@ Workspaces::GetWorkspaceShareChunks(const Oid& WorkspaceId,
for (size_t Index = 0; Index < ChunkRequests.size(); Index++)
{
WorkLatch.AddCount(1);
- WorkerPool.ScheduleWork([&, Index]() {
- auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
- try
- {
- Chunks[Index] = GetOne(RootPath, *WorkspaceAndShare.second, ChunkRequests[Index]);
- }
- catch (const std::exception& Ex)
- {
- ZEN_WARN("Exception while fetching chunks, chunk {}: {}", ChunkRequests[Index].ChunkId, Ex.what());
- }
- });
+ WorkerPool.ScheduleWork(
+ [&, Index]() {
+ auto _ = MakeGuard([&WorkLatch]() { WorkLatch.CountDown(); });
+ try
+ {
+ Chunks[Index] = GetOne(RootPath, *WorkspaceAndShare.second, ChunkRequests[Index]);
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_WARN("Exception while fetching chunks, chunk {}: {}", ChunkRequests[Index].ChunkId, Ex.what());
+ }
+ },
+ WorkerThreadPool::EMode::DisableBacklog);
}
WorkLatch.CountDown();
WorkLatch.Wait();
diff --git a/src/zenstore/xmake.lua b/src/zenstore/xmake.lua
index 031a66829..cf5f30d09 100644
--- a/src/zenstore/xmake.lua
+++ b/src/zenstore/xmake.lua
@@ -6,6 +6,6 @@ target('zenstore')
add_headerfiles("**.h")
add_files("**.cpp")
add_includedirs("include", {public=true})
- add_deps("zencore", "zenutil")
+ add_deps("zencore", "zenutil", "zenvfs")
add_packages("vcpkg::robin-map")
add_packages("vcpkg::eastl", {public=true});
diff --git a/src/zenstore/zenstore.cpp b/src/zenstore/zenstore.cpp
index 654fb3510..c563cc202 100644
--- a/src/zenstore/zenstore.cpp
+++ b/src/zenstore/zenstore.cpp
@@ -6,7 +6,9 @@
# include <zenstore/blockstore.h>
# include <zenstore/buildstore/buildstore.h>
+# include <zenstore/cache/cachepolicy.h>
# include <zenstore/cache/structuredcachestore.h>
+# include <zenstore/projectstore.h>
# include <zenstore/workspaces.h>
# include <zenstore/gc.h>
# include <zenstore/hashkeyset.h>
@@ -21,6 +23,7 @@ void
zenstore_forcelinktests()
{
buildstore_forcelink();
+ cachepolicy_forcelink();
CAS_forcelink();
filecas_forcelink();
blockstore_forcelink();
@@ -29,6 +32,7 @@ zenstore_forcelinktests()
gc_forcelink();
hashkeyset_forcelink();
structured_cachestore_forcelink();
+ prj_forcelink();
}
} // namespace zen
diff --git a/src/zenutil-test/xmake.lua b/src/zenutil-test/xmake.lua
index 61a828f1c..061c79ddf 100644
--- a/src/zenutil-test/xmake.lua
+++ b/src/zenutil-test/xmake.lua
@@ -5,5 +5,5 @@ target("zenutil-test")
set_group("tests")
add_headerfiles("**.h")
add_files("*.cpp")
- add_deps("zenutil", "zencore")
+ add_deps("zenutil")
add_packages("vcpkg::doctest")
diff --git a/src/zenutil-test/zenutil-test.cpp b/src/zenutil-test/zenutil-test.cpp
index eb4a17918..3e3a11a01 100644
--- a/src/zenutil-test/zenutil-test.cpp
+++ b/src/zenutil-test/zenutil-test.cpp
@@ -23,7 +23,15 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
zen::IgnoreChildSignals();
# endif
- zen::TraceInit("zencore-test");
+# if ZEN_WITH_TRACE
+ zen::TraceInit("zenutil-test");
+ zen::TraceOptions TraceCommandlineOptions;
+ if (GetTraceOptionsFromCommandline(TraceCommandlineOptions))
+ {
+ TraceConfigure(TraceCommandlineOptions);
+ }
+# endif // ZEN_WITH_TRACE
+
zen::logging::InitializeLogging();
zen::MaximizeOpenFileCount();
diff --git a/src/zenutil/commandlineoptions.cpp b/src/zenutil/commandlineoptions.cpp
index afef7f6f2..040726c77 100644
--- a/src/zenutil/commandlineoptions.cpp
+++ b/src/zenutil/commandlineoptions.cpp
@@ -116,10 +116,15 @@ StripCommandlineQuotes(std::vector<std::string>& InOutArgs)
{
if (Arg.find('"', 1) == Arg.length() - 1)
{
- if (Arg.find(' ', 1) == std::string::npos)
- {
- Arg = Arg.substr(1, Arg.length() - 2);
- }
+ Arg = Arg.substr(1, Arg.length() - 2);
+ }
+ }
+ else if (Arg.ends_with("\""))
+ {
+ std::string::size_type EqualSign = Arg.find("=", 1);
+ if (EqualSign != std::string::npos && Arg[EqualSign + 1] == '\"')
+ {
+ Arg = Arg.substr(0, EqualSign + 1) + Arg.substr(EqualSign + 2, Arg.length() - (EqualSign + 2) - 1);
}
}
RawArgs.push_back(const_cast<char*>(Arg.c_str()));
@@ -209,12 +214,23 @@ TEST_CASE("CommandLine")
CHECK_EQ(v2Stripped[5], std::string("--namespace=ue.oplog"));
CHECK_EQ(v2Stripped[6], std::string("--bucket=citysample.packaged-build.fortnite-main.windows"));
CHECK_EQ(v2Stripped[7], std::string("c:\\just\\a\\path"));
- CHECK_EQ(v2Stripped[8], std::string("--access-token-path=\"C:\\Users\\dan.engelbrecht\\jupiter-token.json\""));
- CHECK_EQ(v2Stripped[9], std::string("\"D:\\Dev\\Spaced Folder\\Target\""));
- CHECK_EQ(v2Stripped[10], std::string("--alt-path=\"D:\\Dev\\Spaced Folder2\\Target\""));
+ CHECK_EQ(v2Stripped[8], std::string("--access-token-path=C:\\Users\\dan.engelbrecht\\jupiter-token.json"));
+ CHECK_EQ(v2Stripped[9], std::string("D:\\Dev\\Spaced Folder\\Target"));
+ CHECK_EQ(v2Stripped[10], std::string("--alt-path=D:\\Dev\\Spaced Folder2\\Target"));
CHECK_EQ(v2Stripped[11], std::string("07dn23ifiwesnvoasjncasab"));
CHECK_EQ(v2Stripped[12], std::string("--build-part-name"));
CHECK_EQ(v2Stripped[13], std::string("win64,linux,ps5"));
+
+ std::vector<std::string> v3 = ParseCommandLine(
+ "--tracehost \"127.0.0.1\" builds download --url=\"https://jupiter.devtools.epicgames.com\" --build-part-name=\"win64\"");
+ std::vector<char*> v3Stripped = StripCommandlineQuotes(v3);
+
+ CHECK_EQ(v3Stripped[0], std::string("--tracehost"));
+ CHECK_EQ(v3Stripped[1], std::string("127.0.0.1"));
+ CHECK_EQ(v3Stripped[2], std::string("builds"));
+ CHECK_EQ(v3Stripped[3], std::string("download"));
+ CHECK_EQ(v3Stripped[4], std::string("--url=https://jupiter.devtools.epicgames.com"));
+ CHECK_EQ(v3Stripped[5], std::string("--build-part-name=win64"));
}
#endif
diff --git a/src/zenutil/include/zenutil/cache/cache.h b/src/zenutil/include/zenutil/cache/cache.h
deleted file mode 100644
index 20299a667..000000000
--- a/src/zenutil/include/zenutil/cache/cache.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#pragma once
-
-#include <zenutil/cache/cachekey.h>
-#include <zenutil/cache/cachepolicy.h>
-
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <fmt/format.h>
-ZEN_THIRD_PARTY_INCLUDES_END
-
-namespace zen {
-
-struct CacheRequestContext
-{
- Oid SessionId{Oid::Zero};
- uint32_t RequestId{0};
-};
-
-} // namespace zen
-
-template<>
-struct fmt::formatter<zen::CacheRequestContext> : formatter<string_view>
-{
- template<typename FormatContext>
- auto format(const zen::CacheRequestContext& Context, FormatContext& ctx) const
- {
- zen::ExtendableStringBuilder<64> String;
- Context.SessionId.ToString(String);
- String << ".";
- String << Context.RequestId;
- return formatter<string_view>::format(String.ToView(), ctx);
- }
-};
diff --git a/src/zenutil/include/zenutil/cache/rpcrecording.h b/src/zenutil/include/zenutil/rpcrecording.h
index f1ad35413..f1ad35413 100644
--- a/src/zenutil/include/zenutil/cache/rpcrecording.h
+++ b/src/zenutil/include/zenutil/rpcrecording.h
diff --git a/src/zenutil/include/zenutil/windows/service.h b/src/zenutil/include/zenutil/windows/windowsservice.h
index ca0270a36..ca0270a36 100644
--- a/src/zenutil/include/zenutil/windows/service.h
+++ b/src/zenutil/include/zenutil/windows/windowsservice.h
diff --git a/src/zenutil/include/zenutil/zenserverprocess.h b/src/zenutil/include/zenutil/zenserverprocess.h
index 563970363..b6a3d70ff 100644
--- a/src/zenutil/include/zenutil/zenserverprocess.h
+++ b/src/zenutil/include/zenutil/zenserverprocess.h
@@ -80,8 +80,9 @@ struct ZenServerInstance
void EnableTermination() { m_Terminate = true; }
void DisableShutdownOnDestroy() { m_ShutdownOnDestroy = false; }
void Detach();
- inline int GetPid() { return m_Process.Pid(); }
+ inline int GetPid() const { return m_Process.Pid(); }
inline void SetOwnerPid(int Pid) { m_OwnerPid = Pid; }
+ void* GetProcessHandle() const { return m_Process.Handle(); }
bool IsRunning();
bool Terminate();
std::string GetLogOutput() const;
diff --git a/src/zenutil/logging.cpp b/src/zenutil/logging.cpp
index 8ff58ee73..806b96d52 100644
--- a/src/zenutil/logging.cpp
+++ b/src/zenutil/logging.cpp
@@ -42,6 +42,8 @@ InitializeLogging(const LoggingOptions& LogOptions)
FinishInitializeLogging(LogOptions);
}
+static std::terminate_handler OldTerminateHandler = nullptr;
+
void
BeginInitializeLogging(const LoggingOptions& LogOptions)
{
@@ -99,12 +101,43 @@ BeginInitializeLogging(const LoggingOptions& LogOptions)
}
}
- std::set_terminate([]() {
- void* Frames[8];
- uint32_t FrameCount = GetCallstack(2, 8, Frames);
- CallstackFrames* Callstack = CreateCallstack(FrameCount, Frames);
- ZEN_CRITICAL("Program exited abnormally via std::terminate()\n{}", CallstackToString(Callstack, " "));
- FreeCallstack(Callstack);
+ OldTerminateHandler = std::set_terminate([]() {
+ try
+ {
+ constexpr int SkipFrameCount = 4;
+ constexpr int FrameCount = 8;
+ uint8_t CallstackBuffer[CallstackRawMemorySize(SkipFrameCount, FrameCount)];
+ CallstackFrames* Callstack = GetCallstackRaw(&CallstackBuffer[0], SkipFrameCount, FrameCount);
+
+ fmt::basic_memory_buffer<char, 2048> Message;
+ auto Appender = fmt::appender(Message);
+ fmt::format_to(Appender, "Program exited abnormally via std::terminate()");
+
+ if (Callstack->FrameCount > 0)
+ {
+ fmt::format_to(Appender, "\n");
+
+ CallstackToStringRaw(Callstack, &Message, [](void* UserData, uint32_t FrameIndex, const char* FrameText) {
+ ZEN_UNUSED(FrameIndex);
+ fmt::basic_memory_buffer<char, 2048>* Message = (fmt::basic_memory_buffer<char, 2048>*)UserData;
+ auto Appender = fmt::appender(*Message);
+ fmt::format_to(Appender, " {}\n", FrameText);
+ });
+ }
+ Message.push_back('\0');
+
+ // We use direct ZEN_LOG here instead of ZEN_ERROR as we don't care about *this* code location in the log
+ ZEN_LOG(Log(), zen::logging::level::Critical, "{}", Message.data());
+ zen::logging::FlushLogging();
+ }
+ catch (const std::exception&)
+ {
+ // Ignore any exceptions in terminate callback
+ }
+ if (OldTerminateHandler)
+ {
+ OldTerminateHandler();
+ }
});
// Default
diff --git a/src/zenutil/cache/rpcrecording.cpp b/src/zenutil/rpcrecording.cpp
index 46e80f6b7..54f27dee7 100644
--- a/src/zenutil/cache/rpcrecording.cpp
+++ b/src/zenutil/rpcrecording.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <zenutil/cache/rpcrecording.h>
+#include <zenutil/rpcrecording.h>
#include <zencore/basicfile.h>
#include <zencore/compactbinarybuilder.h>
diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp
index e4a9a951e..103fdaa2f 100644
--- a/src/zenutil/service.cpp
+++ b/src/zenutil/service.cpp
@@ -10,7 +10,7 @@
#if ZEN_PLATFORM_WINDOWS
# include <zencore/windows.h>
-# include <zenutil/windows/service.h>
+# include <zenutil/windows/windowsservice.h>
#endif
#if ZEN_PLATFORM_MAC
# include <zencore/filesystem.h>
diff --git a/src/zenutil/windows/service.cpp b/src/zenutil/windows/windowsservice.cpp
index b76ed7a66..ebb88b018 100644
--- a/src/zenutil/windows/service.cpp
+++ b/src/zenutil/windows/windowsservice.cpp
@@ -3,7 +3,7 @@
#include <zencore/zencore.h>
#if ZEN_PLATFORM_WINDOWS
-# include <zenutil/windows/service.h>
+# include <zenutil/windows/windowsservice.h>
# include <zencore/except.h>
# include <zencore/thread.h>
diff --git a/src/zenutil/workerpools.cpp b/src/zenutil/workerpools.cpp
index 797034978..55c91e68e 100644
--- a/src/zenutil/workerpools.cpp
+++ b/src/zenutil/workerpools.cpp
@@ -27,19 +27,19 @@ namespace {
const std::string_view Name;
};
- WorkerPool BurstLargeWorkerPool = {.TreadCount = LargeWorkerThreadPoolTreadCount, .Name = "LargeThreadPool(burst)"};
- WorkerPool BackgroundLargeWorkerPool = {.TreadCount = LargeWorkerThreadPoolTreadCount, .Name = "LargeThreadPool(bkg)"};
+ WorkerPool BurstLargeWorkerPool = {.TreadCount = LargeWorkerThreadPoolTreadCount, .Name = "large"};
+ WorkerPool BackgroundLargeWorkerPool = {.TreadCount = LargeWorkerThreadPoolTreadCount, .Name = "large_bg"};
- WorkerPool BurstMediumWorkerPool = {.TreadCount = MediumWorkerThreadPoolTreadCount, .Name = "MediumThreadPool(burst)"};
- WorkerPool BackgroundMediumWorkerPool = {.TreadCount = MediumWorkerThreadPoolTreadCount, .Name = "MediumThreadPool(bkg)"};
+ WorkerPool BurstMediumWorkerPool = {.TreadCount = MediumWorkerThreadPoolTreadCount, .Name = "medium"};
+ WorkerPool BackgroundMediumWorkerPool = {.TreadCount = MediumWorkerThreadPoolTreadCount, .Name = "medium_bg"};
- WorkerPool BurstSmallWorkerPool = {.TreadCount = SmallWorkerThreadPoolTreadCount, .Name = "SmallThreadPool(burst)"};
- WorkerPool BackgroundSmallWorkerPool = {.TreadCount = SmallWorkerThreadPoolTreadCount, .Name = "SmallThreadPool(bkg)"};
+ WorkerPool BurstSmallWorkerPool = {.TreadCount = SmallWorkerThreadPoolTreadCount, .Name = "small"};
+ WorkerPool BackgroundSmallWorkerPool = {.TreadCount = SmallWorkerThreadPoolTreadCount, .Name = "small_bg"};
- WorkerPool BurstTinyWorkerPool = {.TreadCount = TinyWorkerThreadPoolTreadCount, .Name = "TinyThreadPool(burst)"};
- WorkerPool BackgroundTinyWorkerPool = {.TreadCount = TinyWorkerThreadPoolTreadCount, .Name = "TinyThreadPool(bkg)"};
+ WorkerPool BurstTinyWorkerPool = {.TreadCount = TinyWorkerThreadPoolTreadCount, .Name = "tiny"};
+ WorkerPool BackgroundTinyWorkerPool = {.TreadCount = TinyWorkerThreadPoolTreadCount, .Name = "tiny_bg"};
- WorkerPool SyncWorkerPool = {.TreadCount = 0, .Name = "SyncThreadPool"};
+ WorkerPool SyncWorkerPool = {.TreadCount = 0, .Name = "synctp"};
WorkerThreadPool& EnsurePoolPtr(WorkerPool& Pool)
{
diff --git a/src/zenutil/zenserverprocess.cpp b/src/zenutil/zenserverprocess.cpp
index 3f71c1357..6a93f0c63 100644
--- a/src/zenutil/zenserverprocess.cpp
+++ b/src/zenutil/zenserverprocess.cpp
@@ -656,7 +656,7 @@ ZenServerInstance::Shutdown()
}
else if (Ec)
{
- ZEN_WARN("Terminating zenserver proces as we failed to signal zenserver process {} ({}) to shut down. Reason: '{}'",
+ ZEN_WARN("Terminating zenserver process as we failed to signal zenserver process {} ({}) to shut down. Reason: '{}'",
m_Name,
m_Process.Pid(),
Ec.message());
@@ -915,7 +915,7 @@ ZenServerInstance::Detach()
uint16_t
ZenServerInstance::WaitUntilReady()
{
- while (m_ReadyEvent.Wait(100) == false)
+ while (m_ReadyEvent.Wait(10) == false)
{
if (!m_Process.IsValid())
{
@@ -938,7 +938,7 @@ bool
ZenServerInstance::WaitUntilReady(int Timeout)
{
int TimeoutLeftMS = Timeout;
- while (m_ReadyEvent.Wait(100) == false)
+ while (m_ReadyEvent.Wait(10) == false)
{
if (!m_Process.IsValid())
{
@@ -950,7 +950,7 @@ ZenServerInstance::WaitUntilReady(int Timeout)
ZEN_WARN("Wait abandoned by exited process");
return false;
}
- TimeoutLeftMS -= 100;
+ TimeoutLeftMS -= 10;
if ((TimeoutLeftMS <= 0))
{
ZEN_WARN("Wait abandoned due to timeout");
diff --git a/src/zenutil/zenutil.cpp b/src/zenutil/zenutil.cpp
index 37b229c49..51c1ee72e 100644
--- a/src/zenutil/zenutil.cpp
+++ b/src/zenutil/zenutil.cpp
@@ -4,11 +4,8 @@
#if ZEN_WITH_TESTS
-# include <zenutil/cache/cacherequests.h>
-# include <zenutil/cache/rpcrecording.h>
-# include <zenutil/chunkedfile.h>
+# include <zenutil/rpcrecording.h>
# include <zenutil/commandlineoptions.h>
-# include <zenutil/parallelwork.h>
# include <zenutil/wildcard.h>
namespace zen {
@@ -16,12 +13,8 @@ namespace zen {
void
zenutil_forcelinktests()
{
- cachepolicy_forcelink();
cache::rpcrecord_forcelink();
- cacherequests_forcelink();
- chunkedfile_forcelink();
commandlineoptions_forcelink();
- parallellwork_forcelink();
wildcard_forcelink();
}