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}
This commit is contained in:
Fangrui Song 2019-02-21 23:46:20 +08:00
parent a3f6885c5e
commit 7c2b115c28
5 changed files with 81 additions and 43 deletions

View File

@ -40,19 +40,37 @@ struct Config {
std::string compilationDatabaseCommand; std::string compilationDatabaseCommand;
// Directory containing compile_commands.json. // Directory containing compile_commands.json.
std::string compilationDatabaseDirectory; std::string compilationDatabaseDirectory;
// Cache directory for indexed files, either absolute or relative to the
// project root. struct Cache {
// If empty, cache will be stored in memory. // Cache directory for indexed files, either absolute or relative to the
std::string cacheDirectory = ".ccls-cache"; // project root.
// Cache serialization format. //
// // If empty, retainInMemory will be set to true and cache will be stored in
// "json" generates `cacheDirectory/.../xxx.json` files which can be pretty // memory.
// printed with jq. std::string directory = ".ccls-cache";
//
// "binary" uses a compact binary serialization format. // Cache serialization format.
// It is not schema-aware and you need to re-index whenever an internal struct //
// member has changed. // "json" generates $directory/.../xxx.json files which can be pretty
SerializeFormat cacheFormat = SerializeFormat::Binary; // 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 ServerCap {
struct DocumentOnTypeFormattingOptions { struct DocumentOnTypeFormattingOptions {
@ -292,6 +310,8 @@ struct Config {
int maxNum = 2000; int maxNum = 2000;
} xref; } xref;
}; };
REFLECT_STRUCT(Config::Cache, directory, format, retainInMemory,
useHierarchy);
REFLECT_STRUCT(Config::ServerCap::DocumentOnTypeFormattingOptions, REFLECT_STRUCT(Config::ServerCap::DocumentOnTypeFormattingOptions,
firstTriggerCharacter, moreTriggerCharacter); firstTriggerCharacter, moreTriggerCharacter);
REFLECT_STRUCT(Config::ServerCap::Workspace::WorkspaceFolders, supported, 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::WorkspaceSymbol, caseSensitivity, maxNum, sort);
REFLECT_STRUCT(Config::Xref, maxNum); REFLECT_STRUCT(Config::Xref, maxNum);
REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory, REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory,
cacheDirectory, cacheFormat, capabilities, clang, client, cache, capabilities, clang, client, codeLens, completion,
codeLens, completion, diagnostics, highlight, index, request, diagnostics, highlight, index, request, session, workspaceSymbol,
session, workspaceSymbol, xref); xref);
extern Config *g_config; extern Config *g_config;

View File

@ -293,12 +293,12 @@ void Initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply) {
Reflect(json_writer, *g_config); Reflect(json_writer, *g_config);
LOG_S(INFO) << "initializationOptions: " << output.GetString(); LOG_S(INFO) << "initializationOptions: " << output.GetString();
if (g_config->cacheDirectory.size()) { if (g_config->cache.directory.size()) {
SmallString<256> Path(g_config->cacheDirectory); SmallString<256> Path(g_config->cache.directory);
sys::fs::make_absolute(project_path, Path); sys::fs::make_absolute(project_path, Path);
// Use upper case for the Driver letter on Windows. // Use upper case for the Driver letter on Windows.
g_config->cacheDirectory = NormalizePath(Path.str()); g_config->cache.directory = NormalizePath(Path.str());
EnsureEndsInSlash(g_config->cacheDirectory); EnsureEndsInSlash(g_config->cache.directory);
} }
} }
@ -345,13 +345,16 @@ void Initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply) {
} }
if (param.workspaceFolders.empty()) if (param.workspaceFolders.empty())
g_config->workspaceFolders.push_back(project_path); 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) { for (const std::string &folder : g_config->workspaceFolders) {
// Create two cache directories for files inside and outside of the // Create two cache directories for files inside and outside of the
// project. // project.
std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1)); std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1));
sys::fs::create_directories(g_config->cacheDirectory + escaped); sys::fs::create_directories(g_config->cache.directory + escaped);
sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped); sys::fs::create_directories(g_config->cache.directory + '@' + escaped);
} }
idx::Init(); idx::Init();

View File

@ -46,8 +46,8 @@ void MessageHandler::workspace_didChangeWatchedFiles(
DidChangeWatchedFilesParam &param) { DidChangeWatchedFilesParam &param) {
for (auto &event : param.changes) { for (auto &event : param.changes) {
std::string path = event.uri.GetPath(); std::string path = event.uri.GetPath();
if ((g_config->cacheDirectory.size() && if ((g_config->cache.directory.size() &&
StringRef(path).startswith(g_config->cacheDirectory)) || StringRef(path).startswith(g_config->cache.directory)) ||
lookupExtension(path).first == LanguageId::Unknown) lookupExtension(path).first == LanguageId::Unknown)
return; return;
for (std::string cur = path; cur.size(); cur = sys::path::parent_path(cur)) for (std::string cur = path; cur.size(); cur = sys::path::parent_path(cur))

View File

@ -137,7 +137,7 @@ bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path,
}; };
std::string AppendSerializationFormat(const std::string &base) { std::string AppendSerializationFormat(const std::string &base) {
switch (g_config->cacheFormat) { switch (g_config->cache.format) {
case SerializeFormat::Binary: case SerializeFormat::Binary:
return base + ".blob"; return base + ".blob";
case SerializeFormat::Json: 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) for (auto &root : g_config->workspaceFolders)
if (StringRef(source_file).startswith(root)) { if (StringRef(src).startswith(root)) {
auto len = root.size(); auto len = root.size();
return g_config->cacheDirectory + return g_config->cache.directory +
EscapeFileName(root.substr(0, len - 1)) + '/' + 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( EscapeFileName(g_config->fallbackFolder.substr(
0, g_config->fallbackFolder.size() - 1)) + 0, g_config->fallbackFolder.size() - 1)) +
'/' + EscapeFileName(source_file); '/' + EscapeFileName(src);
} }
std::unique_ptr<IndexFile> RawCacheLoad(const std::string &path) { std::unique_ptr<IndexFile> RawCacheLoad(const std::string &path) {
if (g_config->cacheDirectory.empty()) { if (g_config->cache.retainInMemory) {
std::shared_lock lock(g_index_mutex); std::shared_lock lock(g_index_mutex);
auto it = g_index.find(path); auto it = g_index.find(path);
if (it == g_index.end()) if (it != g_index.end())
return std::make_unique<IndexFile>(it->second.index);
if (g_config->cache.directory.empty())
return nullptr; return nullptr;
return std::make_unique<IndexFile>(it->second.index);
} }
std::string cache_path = GetCachePath(path); std::string cache_path = GetCachePath(path);
@ -175,7 +184,7 @@ std::unique_ptr<IndexFile> RawCacheLoad(const std::string &path) {
if (!file_content || !serialized_indexed_content) if (!file_content || !serialized_indexed_content)
return nullptr; return nullptr;
return ccls::Deserialize(g_config->cacheFormat, path, return ccls::Deserialize(g_config->cache.format, path,
*serialized_indexed_content, *file_content, *serialized_indexed_content, *file_content,
IndexFile::kMajorVersion); IndexFile::kMajorVersion);
} }
@ -371,20 +380,25 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles,
prev = RawCacheLoad(path); prev = RawCacheLoad(path);
else else
prev.reset(); prev.reset();
if (g_config->cacheDirectory.empty()) { if (g_config->cache.retainInMemory) {
std::lock_guard lock(g_index_mutex); std::lock_guard lock(g_index_mutex);
auto it = g_index.insert_or_assign( auto it = g_index.insert_or_assign(
path, InMemoryIndexFile{curr->file_contents, *curr}); path, InMemoryIndexFile{curr->file_contents, *curr});
std::string().swap(it.first->second.index.file_contents); std::string().swap(it.first->second.index.file_contents);
} else { }
if (g_config->cache.directory.size()) {
std::string cache_path = GetCachePath(path); std::string cache_path = GetCachePath(path);
if (deleted) { if (deleted) {
(void)sys::fs::remove(cache_path); (void)sys::fs::remove(cache_path);
(void)sys::fs::remove(AppendSerializationFormat(cache_path)); (void)sys::fs::remove(AppendSerializationFormat(cache_path));
} else { } 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(cache_path, curr->file_contents);
WriteToFile(AppendSerializationFormat(cache_path), WriteToFile(AppendSerializationFormat(cache_path),
Serialize(g_config->cacheFormat, *curr)); Serialize(g_config->cache.format, *curr));
} }
} }
on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()), on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()),
@ -719,7 +733,7 @@ void Index(const std::string &path, const std::vector<const char *> &args,
} }
std::optional<std::string> LoadIndexedContent(const std::string &path) { std::optional<std::string> LoadIndexedContent(const std::string &path) {
if (g_config->cacheDirectory.empty()) { if (g_config->cache.directory.empty()) {
std::shared_lock lock(g_index_mutex); std::shared_lock lock(g_index_mutex);
auto it = g_index.find(path); auto it = g_index.find(path);
if (it == g_index.end()) if (it == g_index.end())

View File

@ -124,9 +124,10 @@ void EnsureEndsInSlash(std::string &path) {
std::string EscapeFileName(std::string path) { std::string EscapeFileName(std::string path) {
bool slash = path.size() && path.back() == '/'; bool slash = path.size() && path.back() == '/';
for (char &c : path) #ifdef _WIN32
if (c == '\\' || c == '/' || c == ':') std::replace(path.begin(), path.end(), ':', '@');
c = '@'; #endif
std::replace(path.begin(), path.end(), '/', '@');
if (slash) if (slash)
path += '/'; path += '/';
return path; return path;