From 7c2b115c2817d63212d2ed5b3993313faf6273d3 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Thu, 21 Feb 2019 23:46:20 +0800 Subject: [PATCH] Add cache.{inMemoryAfterLoad,useHierarchy} cache.useHierarchy: store cache files as $directory/a/b/c.cc.blob to work around NAME_MAX limitation. cache.inMemoryAfterLoad: during incremental updates, the removed file index is taken from the in-memory copy. This avoids cache corruption if the index file is changed after the initial load, which may happen when several language clients open the same project and share the same cache directory. Also rename cacheDirectory cacheFormat to cache.{directory,format} --- src/config.hh | 52 ++++++++++++++++++++++++++------------ src/messages/initialize.cc | 17 ++++++++----- src/messages/workspace.cc | 4 +-- src/pipeline.cc | 44 +++++++++++++++++++++----------- src/utils.cc | 7 ++--- 5 files changed, 81 insertions(+), 43 deletions(-) diff --git a/src/config.hh b/src/config.hh index 3e9aa793..f57ec398 100644 --- a/src/config.hh +++ b/src/config.hh @@ -40,19 +40,37 @@ struct Config { std::string compilationDatabaseCommand; // Directory containing compile_commands.json. std::string compilationDatabaseDirectory; - // Cache directory for indexed files, either absolute or relative to the - // project root. - // If empty, cache will be stored in memory. - std::string cacheDirectory = ".ccls-cache"; - // Cache serialization format. - // - // "json" generates `cacheDirectory/.../xxx.json` files which can be pretty - // printed with jq. - // - // "binary" uses a compact binary serialization format. - // It is not schema-aware and you need to re-index whenever an internal struct - // member has changed. - SerializeFormat cacheFormat = SerializeFormat::Binary; + + struct Cache { + // Cache directory for indexed files, either absolute or relative to the + // project root. + // + // If empty, retainInMemory will be set to true and cache will be stored in + // memory. + std::string directory = ".ccls-cache"; + + // Cache serialization format. + // + // "json" generates $directory/.../xxx.json files which can be pretty + // printed with jq. + // + // "binary" uses a compact binary serialization format. + // It is not schema-aware and you need to re-index whenever an internal + // struct member has changed. + SerializeFormat format = SerializeFormat::Binary; + + // If true, keep a copy of file index in memory (which increases memory + // usage). During incremental updates, the removed file index will be taken + // from the in-memory copy, instead of the on-disk file. + bool retainInMemory = true; + + // If false, store cache files as $directory/@a@b/c.cc.blob + // + // If true, $directory/a/b/c.cc.blob. If cache.directory is absolute, make + // sure different projects use different cache.directory as they would have + // conflicting cache files for system headers. + bool useHierarchy = false; + } cache; struct ServerCap { struct DocumentOnTypeFormattingOptions { @@ -292,6 +310,8 @@ struct Config { int maxNum = 2000; } xref; }; +REFLECT_STRUCT(Config::Cache, directory, format, retainInMemory, + useHierarchy); REFLECT_STRUCT(Config::ServerCap::DocumentOnTypeFormattingOptions, firstTriggerCharacter, moreTriggerCharacter); REFLECT_STRUCT(Config::ServerCap::Workspace::WorkspaceFolders, supported, @@ -321,9 +341,9 @@ REFLECT_STRUCT(Config::Session, maxNum); REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort); REFLECT_STRUCT(Config::Xref, maxNum); REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory, - cacheDirectory, cacheFormat, capabilities, clang, client, - codeLens, completion, diagnostics, highlight, index, request, - session, workspaceSymbol, xref); + cache, capabilities, clang, client, codeLens, completion, + diagnostics, highlight, index, request, session, workspaceSymbol, + xref); extern Config *g_config; diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 707798d0..e9b9c218 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -293,12 +293,12 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { Reflect(json_writer, *g_config); LOG_S(INFO) << "initializationOptions: " << output.GetString(); - if (g_config->cacheDirectory.size()) { - SmallString<256> Path(g_config->cacheDirectory); + if (g_config->cache.directory.size()) { + SmallString<256> Path(g_config->cache.directory); sys::fs::make_absolute(project_path, Path); // Use upper case for the Driver letter on Windows. - g_config->cacheDirectory = NormalizePath(Path.str()); - EnsureEndsInSlash(g_config->cacheDirectory); + g_config->cache.directory = NormalizePath(Path.str()); + EnsureEndsInSlash(g_config->cache.directory); } } @@ -345,13 +345,16 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { } if (param.workspaceFolders.empty()) g_config->workspaceFolders.push_back(project_path); - if (g_config->cacheDirectory.size()) + + if (g_config->cache.directory.empty()) + g_config->cache.retainInMemory = true; + else if (!g_config->cache.useHierarchy) for (const std::string &folder : g_config->workspaceFolders) { // Create two cache directories for files inside and outside of the // project. std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1)); - sys::fs::create_directories(g_config->cacheDirectory + escaped); - sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped); + sys::fs::create_directories(g_config->cache.directory + escaped); + sys::fs::create_directories(g_config->cache.directory + '@' + escaped); } idx::Init(); diff --git a/src/messages/workspace.cc b/src/messages/workspace.cc index 5435d98f..67e3e8a7 100644 --- a/src/messages/workspace.cc +++ b/src/messages/workspace.cc @@ -46,8 +46,8 @@ void MessageHandler::workspace_didChangeWatchedFiles( DidChangeWatchedFilesParam ¶m) { for (auto &event : param.changes) { std::string path = event.uri.GetPath(); - if ((g_config->cacheDirectory.size() && - StringRef(path).startswith(g_config->cacheDirectory)) || + if ((g_config->cache.directory.size() && + StringRef(path).startswith(g_config->cache.directory)) || lookupExtension(path).first == LanguageId::Unknown) return; for (std::string cur = path; cur.size(); cur = sys::path::parent_path(cur)) diff --git a/src/pipeline.cc b/src/pipeline.cc index fa8a3fa8..55ceed5b 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -137,7 +137,7 @@ bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, }; std::string AppendSerializationFormat(const std::string &base) { - switch (g_config->cacheFormat) { + switch (g_config->cache.format) { case SerializeFormat::Binary: return base + ".blob"; case SerializeFormat::Json: @@ -145,27 +145,36 @@ std::string AppendSerializationFormat(const std::string &base) { } } -std::string GetCachePath(const std::string &source_file) { +std::string GetCachePath(const std::string &src) { + if (g_config->cache.useHierarchy) { + std::string ret = + g_config->cache.directory + (src[0] == '/' ? src.substr(1) : src); +#ifdef _WIN32 + std::replace(src.begin(), src.end(), ':', '@'); +#endif + return ret; + } for (auto &root : g_config->workspaceFolders) - if (StringRef(source_file).startswith(root)) { + if (StringRef(src).startswith(root)) { auto len = root.size(); - return g_config->cacheDirectory + + return g_config->cache.directory + EscapeFileName(root.substr(0, len - 1)) + '/' + - EscapeFileName(source_file.substr(len)); + EscapeFileName(src.substr(len)); } - return g_config->cacheDirectory + '@' + + return g_config->cache.directory + '@' + EscapeFileName(g_config->fallbackFolder.substr( 0, g_config->fallbackFolder.size() - 1)) + - '/' + EscapeFileName(source_file); + '/' + EscapeFileName(src); } std::unique_ptr RawCacheLoad(const std::string &path) { - if (g_config->cacheDirectory.empty()) { + if (g_config->cache.retainInMemory) { std::shared_lock lock(g_index_mutex); auto it = g_index.find(path); - if (it == g_index.end()) + if (it != g_index.end()) + return std::make_unique(it->second.index); + if (g_config->cache.directory.empty()) return nullptr; - return std::make_unique(it->second.index); } std::string cache_path = GetCachePath(path); @@ -175,7 +184,7 @@ std::unique_ptr RawCacheLoad(const std::string &path) { if (!file_content || !serialized_indexed_content) return nullptr; - return ccls::Deserialize(g_config->cacheFormat, path, + return ccls::Deserialize(g_config->cache.format, path, *serialized_indexed_content, *file_content, IndexFile::kMajorVersion); } @@ -371,20 +380,25 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, prev = RawCacheLoad(path); else prev.reset(); - if (g_config->cacheDirectory.empty()) { + if (g_config->cache.retainInMemory) { std::lock_guard lock(g_index_mutex); auto it = g_index.insert_or_assign( path, InMemoryIndexFile{curr->file_contents, *curr}); std::string().swap(it.first->second.index.file_contents); - } else { + } + if (g_config->cache.directory.size()) { std::string cache_path = GetCachePath(path); if (deleted) { (void)sys::fs::remove(cache_path); (void)sys::fs::remove(AppendSerializationFormat(cache_path)); } else { + if (g_config->cache.useHierarchy) + sys::fs::create_directories( + sys::path::parent_path(cache_path, sys::path::Style::posix), + true); WriteToFile(cache_path, curr->file_contents); WriteToFile(AppendSerializationFormat(cache_path), - Serialize(g_config->cacheFormat, *curr)); + Serialize(g_config->cache.format, *curr)); } } on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()), @@ -719,7 +733,7 @@ void Index(const std::string &path, const std::vector &args, } std::optional LoadIndexedContent(const std::string &path) { - if (g_config->cacheDirectory.empty()) { + if (g_config->cache.directory.empty()) { std::shared_lock lock(g_index_mutex); auto it = g_index.find(path); if (it == g_index.end()) diff --git a/src/utils.cc b/src/utils.cc index 48e9c0d0..f37ac58e 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -124,9 +124,10 @@ void EnsureEndsInSlash(std::string &path) { std::string EscapeFileName(std::string path) { bool slash = path.size() && path.back() == '/'; - for (char &c : path) - if (c == '\\' || c == '/' || c == ':') - c = '@'; +#ifdef _WIN32 + std::replace(path.begin(), path.end(), ':', '@'); +#endif + std::replace(path.begin(), path.end(), '/', '@'); if (slash) path += '/'; return path;