Fix: use lru cache for active files in server (#177)

This commit is contained in:
Shiyu
2025-08-17 10:04:12 +08:00
committed by GitHub
parent 08b41f15c2
commit 744502033a
8 changed files with 368 additions and 148 deletions

View File

@@ -67,7 +67,7 @@ void Server::load_cache_info() {
}
/// Update the PCH info.
opening_files[*file].pch = std::move(info);
opening_files.get_or_add(*file)->pch = std::move(info);
}
}
@@ -80,11 +80,11 @@ void Server::save_cache_info() {
json["pchs"] = json::Array();
for(auto& [file, open_file]: opening_files) {
if(!open_file.pch) {
if(!open_file->pch) {
continue;
}
auto& pch = *open_file.pch;
auto& pch = *open_file->pch;
json::Object object;
object["file"] = file;
object["path"] = pch.path;
@@ -104,7 +104,11 @@ void Server::save_cache_info() {
return;
}
auto clean_up = llvm::make_scope_exit([&temp_path]() { llvm::sys::fs::remove(temp_path); });
auto clean_up = llvm::make_scope_exit([&temp_path]() {
if(auto errc = llvm::sys::fs::remove(temp_path); errc != std::error_code{}) {
log::warn("Fail to remove temporary file: {}", errc.message());
}
});
std::error_code EC;
llvm::raw_fd_ostream os(temp_path, EC, llvm::sys::fs::OF_None);
@@ -132,117 +136,120 @@ void Server::save_cache_info() {
log::info("Save cache info successfully");
}
namespace {
bool check_pch_update(llvm::StringRef content,
std::uint32_t bound,
CompilationDatabase::LookupInfo& info,
PCHInfo& pch) {
if(content.substr(0, bound) != pch.preamble) {
return true;
}
if(info.arguments != pch.arguments) {
return true;
}
/// Check deps.
for(auto& dep: pch.deps) {
fs::file_status status;
auto error = fs::status(dep, status, true);
if(error || std::chrono::duration_cast<std::chrono::milliseconds>(
status.getLastModificationTime().time_since_epoch())
.count() > pch.mtime) {
return true;
}
}
return false;
}
/// The actual PCH build task.
async::Task<bool> build_pch_task(CompilationDatabase::LookupInfo& info,
std::shared_ptr<OpenFile> open_file,
std::string path,
std::uint32_t bound,
std::string content,
std::shared_ptr<std::vector<Diagnostic>> diagnostics) {
if(!fs::exists(config::cache.dir)) {
auto error = fs::create_directories(config::cache.dir);
if(error) {
log::warn("Fail to create directory for PCH building: {}", config::cache.dir);
co_return false;
}
}
/// Everytime we build a new pch, the old diagnostics should be discarded.
diagnostics->clear();
CompilationParams params;
params.output_file = path::join(config::cache.dir, path::filename(path) + ".pch");
params.arguments = std::move(info.arguments);
params.diagnostics = diagnostics;
params.add_remapped_file(path, content, bound);
PCHInfo pch;
std::string command;
for(auto argument: params.arguments) {
command += " ";
command += argument;
}
log::info("Start building PCH for {}, command: [{}]", path, command);
std::string message;
std::vector<feature::DocumentLink> links;
bool success = co_await async::submit([&params, &pch, &message, &links] -> bool {
/// PCH file is written until destructing, Add a single block
/// for it.
auto unit = compile(params, pch);
if(!unit) {
message = std::move(unit.error());
return false;
}
links = feature::document_links(*unit);
/// TODO: index PCH file, etc
return true;
});
if(!success) {
log::warn("Building PCH fails for {}, Because: {}", path, message);
for(auto& diagnostic: *diagnostics) {
log::warn("{}", diagnostic.message);
}
co_return false;
}
log::info("Building PCH successfully for {}", path);
/// Update the built PCH info.
open_file->pch = std::move(pch);
open_file->pch_includes = std::move(links);
/// Resume waiters on this event.
open_file->pch_built_event.set();
open_file->pch_built_event.clear();
co_return true;
};
} // namespace
async::Task<bool> Server::build_pch(std::string file, std::string content) {
auto bound = compute_preamble_bound(content);
auto open_file = &opening_files[file];
auto info = database.get_command(file, true, true);
auto check_pch_update = [&content, &bound, &info](PCHInfo& pch) {
if(content.substr(0, bound) != pch.preamble) {
return true;
}
if(info.arguments != pch.arguments) {
return true;
}
/// Check deps.
for(auto& dep: pch.deps) {
fs::file_status status;
auto error = fs::status(dep, status, true);
if(error || std::chrono::duration_cast<std::chrono::milliseconds>(
status.getLastModificationTime().time_since_epoch())
.count() > pch.mtime) {
return true;
}
}
return false;
};
auto& open_file = opening_files.get_or_add(file);
/// Check update ...
if(open_file->pch && !check_pch_update(*open_file->pch)) {
if(open_file->pch && !check_pch_update(content, bound, info, *open_file->pch)) {
/// If not need update, return directly.
log::info("PCH is already up-to-date for {}", file);
co_return true;
}
/// The actual PCH build task.
constexpr static auto PCHBuildTask =
[](CompilationDatabase::LookupInfo& info,
OpenFile* open_file,
std::string path,
std::uint32_t bound,
std::string content,
std::shared_ptr<std::vector<Diagnostic>> diagnostics) -> async::Task<bool> {
if(!fs::exists(config::cache.dir)) {
auto error = fs::create_directories(config::cache.dir);
if(error) {
log::warn("Fail to create directory for PCH building: {}", config::cache.dir);
co_return false;
}
}
/// Everytime we build a new pch, the old diagnostics should be discarded.
diagnostics->clear();
CompilationParams params;
params.output_file = path::join(config::cache.dir, path::filename(path) + ".pch");
params.arguments = std::move(info.arguments);
params.diagnostics = diagnostics;
params.add_remapped_file(path, content, bound);
PCHInfo pch;
std::string command;
for(auto argument: params.arguments) {
command += " ";
command += argument;
}
log::info("Start building PCH for {}, command: [{}]", path, command);
std::string message;
std::vector<feature::DocumentLink> links;
bool success = co_await async::submit([&params, &pch, &message, &links] -> bool {
/// PCH file is written until destructing, Add a single block
/// for it.
auto unit = compile(params, pch);
if(!unit) {
message = std::move(unit.error());
return false;
}
links = feature::document_links(*unit);
/// TODO: index PCH file, etc
return true;
});
if(!success) {
log::warn("Building PCH fails for {}, Because: {}", path, message);
for(auto& diagnostic: *diagnostics) {
log::warn("{}", diagnostic.message);
}
co_return false;
}
log::info("Building PCH successfully for {}", path);
/// Update the built PCH info.
open_file->pch = std::move(pch);
open_file->pch_includes = std::move(links);
/// Resume waiters on this event.
open_file->pch_built_event.set();
open_file->pch_built_event.clear();
co_return true;
};
open_file = &opening_files[file];
/// If there is already an PCH build task, cancel it.
auto& task = open_file->pch_build_task;
if(!task.empty()) {
@@ -257,13 +264,10 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
}
/// Schedule the new building task.
task = PCHBuildTask(info, open_file, file, bound, std::move(content), open_file->diagnostics);
task = build_pch_task(info, open_file, file, bound, std::move(content), open_file->diagnostics);
if(co_await task) {
/// FIXME: At this point, task has already been finished, destroy it
/// directly.
/// FIXME: At this point, task has already been finished, destroy it directly.
task.release().destroy();
co_return true;
}
@@ -272,7 +276,7 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
}
async::Task<> Server::build_ast(std::string path, std::string content) {
auto file = &opening_files[path];
auto file = opening_files.get_or_add(path);
/// Try get the lock, the waiter on the lock will be resumed when
/// guard is destroyed.
@@ -284,12 +288,11 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
co_return;
}
auto pch = opening_files[path].pch;
auto pch = file->pch;
if(!pch) {
log::fatal("Expected PCH built at this point");
}
file = &opening_files[path];
CompilationParams params;
params.arguments = database.get_command(path, true, true).arguments;
params.add_remapped_file(path, content);
@@ -311,7 +314,6 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
/// FIXME: Index the source file.
/// co_await indexer.index(*ast);
file = &opening_files[path];
/// Update built AST info.
file->ast = std::make_shared<CompilationUnit>(std::move(*ast));
@@ -321,11 +323,11 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
log::info("Building AST successfully for {}", path);
}
async::Task<OpenFile*> Server::add_document(std::string path, std::string content) {
auto& openFile = opening_files[path];
openFile.content = content;
async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, std::string content) {
auto& openFile = opening_files.get_or_add(path);
openFile->content = content;
auto& task = openFile.ast_build_task;
auto& task = openFile->ast_build_task;
/// If there is already an AST build task, cancel it.
if(!task.empty()) {
@@ -343,12 +345,11 @@ async::Task<OpenFile*> Server::add_document(std::string path, std::string conten
task = build_ast(std::move(path), std::move(content));
task.schedule();
co_return &opening_files[path];
co_return openFile;
}
async::Task<> Server::publish_diagnostics(std::string path, OpenFile* file) {
async::Task<> Server::publish_diagnostics(std::string path, std::shared_ptr<OpenFile> file) {
auto guard = co_await file->ast_built_lock.try_lock();
file = &opening_files[path];
if(file->ast) {
auto diagnostics = feature::diagnostics(kind, mapping, *file->ast);
co_await notify("textDocument/publishDiagnostics",
@@ -363,7 +364,7 @@ async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto file = co_await add_document(path, std::move(params.textDocument.text));
if(file->diagnostics) {
co_await publish_diagnostics(path, file);
co_await publish_diagnostics(path, std::move(file));
}
co_return;
}
@@ -372,7 +373,7 @@ async::Task<> Server::on_did_change(proto::DidChangeTextDocumentParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto file = co_await add_document(path, std::move(params.contentChanges[0].text));
if(file->diagnostics) {
co_await publish_diagnostics(path, file);
co_await publish_diagnostics(path, std::move(file));
}
co_return;
}

View File

@@ -12,17 +12,15 @@ namespace clice {
async::Task<json::Value> Server::on_completion(proto::CompletionParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto opening_file = &opening_files[path];
auto content = opening_file->content;
auto offset = to_offset(kind, content, params.position);
auto opening_file = opening_files.get_or_add(path);
if(!opening_file->pch_build_task.empty()) {
co_await opening_file->pch_built_event;
}
opening_file = &opening_files[path];
auto& content = opening_file->content;
auto offset = to_offset(kind, content, params.position);
auto& pch = opening_file->pch;
{
/// Set compilation params ... .
CompilationParams params;
@@ -40,13 +38,10 @@ async::Task<json::Value> Server::on_completion(proto::CompletionParams params) {
async::Task<json::Value> Server::on_hover(proto::HoverParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto opening_file = &opening_files[path];
auto opening_file = opening_files.get_or_add(path);
auto guard = co_await opening_file->ast_built_lock.try_lock();
auto offset = to_offset(kind, opening_file->content, params.position);
opening_file = &opening_files[path];
auto ast = opening_file->ast;
if(!ast) {
co_return json::Value(nullptr);
@@ -65,11 +60,9 @@ async::Task<json::Value> Server::on_hover(proto::HoverParams params) {
async::Task<json::Value> Server::on_document_symbol(proto::DocumentSymbolParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto opening_file = &opening_files[path];
auto opening_file = opening_files.get_or_add(path);
auto guard = co_await opening_file->ast_built_lock.try_lock();
opening_file = &opening_files[path];
auto ast = opening_file->ast;
if(!ast) {
co_return json::Value(nullptr);
@@ -115,9 +108,7 @@ async::Task<json::Value> Server::on_document_symbol(proto::DocumentSymbolParams
async::Task<json::Value> Server::on_document_link(proto::DocumentLinkParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto opening_file = &opening_files[path];
opening_file = &opening_files[path];
auto opening_file = opening_files.get_or_add(path);
auto guard = co_await opening_file->ast_built_lock.try_lock();
auto ast = opening_file->ast;
@@ -146,10 +137,9 @@ async::Task<json::Value> Server::on_document_link(proto::DocumentLinkParams para
async::Task<json::Value> Server::on_folding_range(proto::FoldingRangeParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto opening_file = &opening_files[path];
auto opening_file = opening_files.get_or_add(path);
auto guard = co_await opening_file->ast_built_lock.try_lock();
opening_file = &opening_files[path];
auto ast = opening_file->ast;
if(!ast) {
co_return json::Value(nullptr);
@@ -185,12 +175,9 @@ async::Task<json::Value> Server::on_folding_range(proto::FoldingRangeParams para
async::Task<json::Value> Server::on_semantic_token(proto::SemanticTokensParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto opening_file = &opening_files[path];
auto opening_file = opening_files.get_or_add(path);
auto guard = co_await opening_file->ast_built_lock.try_lock();
opening_file = &opening_files[path];
auto ast = opening_file->ast;
if(!ast) {
co_return json::Value(nullptr);

View File

@@ -18,6 +18,9 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
/// Initialize configuration.
config::init(workspace);
/// Set server options.
opening_files.set_capability(config::server.max_active_file);
/// Load compile commands.json
for(auto& dir: config::server.compile_commands_dirs) {
auto content = fs::read(dir + "/compile_commands.json");

View File

@@ -3,6 +3,46 @@
namespace clice {
ActiveFileManager::ActiveFile& ActiveFileManager::lru_put_impl(llvm::StringRef path,
OpenFile file) {
/// If the file is not in the chain, create a new OpenFile.
if(items.size() >= capability) {
/// If the size exceeds the maximum size, remove the last element.
index.erase(items.back().first);
items.pop_back();
}
items.emplace_front(path, std::make_shared<OpenFile>(std::move(file)));
// fix the ownership of the StringRef of the path.
auto [added, _] = index.insert({path, items.begin()});
items.front().first = added->getKey();
return items.front().second;
}
ActiveFileManager::ActiveFile& ActiveFileManager::get_or_add(llvm::StringRef path) {
auto iter = index.find(path);
if(iter == index.end()) {
return lru_put_impl(path, OpenFile{});
}
// If the file is in the chain, move it to the front.
items.splice(items.begin(), items, iter->second);
return iter->second->second;
}
ActiveFileManager::ActiveFile& ActiveFileManager::add(llvm::StringRef path, OpenFile file) {
auto iter = index.find(path);
if(iter == index.end()) {
return lru_put_impl(path, std::move(file));
}
iter->second->second = std::make_shared<OpenFile>(std::move(file));
// If the file is in the chain, move it to the front.
items.splice(items.begin(), items, iter->second);
return iter->second->second;
}
async::Task<> Server::request(llvm::StringRef method, json::Value params) {
co_await async::net::write(json::Object{
{"jsonrpc", "2.0" },