ccls/src/pipeline.cc

563 lines
17 KiB
C++
Raw Normal View History

2018-08-21 05:27:52 +00:00
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
2018-05-27 19:24:56 +00:00
#include "pipeline.hh"
#include "clang_complete.hh"
2017-12-29 16:29:47 +00:00
#include "config.h"
#include "include_complete.h"
2018-05-27 19:24:56 +00:00
#include "log.hh"
#include "lsp.h"
#include "message_handler.h"
2018-08-09 17:08:14 +00:00
#include "pipeline.hh"
#include "platform.h"
#include "project.h"
#include "query_utils.h"
2018-05-27 19:24:56 +00:00
#include <llvm/Support/Threading.h>
#include <llvm/Support/Timer.h>
2018-05-27 19:24:56 +00:00
using namespace llvm;
#include <chrono>
2018-09-10 06:46:13 +00:00
#include <mutex>
#include <shared_mutex>
#include <thread>
2018-07-03 22:47:43 +00:00
#ifndef _WIN32
#include <unistd.h>
#endif
void DiagnosticsPublisher::Init() {
frequencyMs_ = g_config->diagnostics.frequencyMs;
match_ = std::make_unique<GroupMatch>(g_config->diagnostics.whitelist,
g_config->diagnostics.blacklist);
}
2018-08-09 17:08:14 +00:00
void DiagnosticsPublisher::Publish(WorkingFiles *working_files,
std::string path,
std::vector<lsDiagnostic> diagnostics) {
2018-07-03 22:47:43 +00:00
bool good = true;
// Cache diagnostics so we can show fixits.
2018-08-09 17:08:14 +00:00
working_files->DoActionOnFile(path, [&](WorkingFile *working_file) {
2018-07-03 22:47:43 +00:00
if (working_file) {
good = working_file->diagnostics_.empty();
working_file->diagnostics_ = diagnostics;
2018-07-03 22:47:43 +00:00
}
});
int64_t now =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
2018-07-03 22:47:43 +00:00
if (frequencyMs_ >= 0 &&
(nextPublish_ <= now || (!good && diagnostics.empty())) &&
match_->IsMatch(path)) {
nextPublish_ = now + frequencyMs_;
Out_TextDocumentPublishDiagnostics out;
out.params.uri = lsDocumentUri::FromPath(path);
out.params.diagnostics = diagnostics;
2018-08-09 17:08:14 +00:00
ccls::pipeline::WriteStdout(kMethodType_TextDocumentPublishDiagnostics,
out);
}
}
namespace ccls::pipeline {
2018-09-18 01:03:59 +00:00
int64_t loaded_ts = 0, tick = 0;
namespace {
2018-05-28 00:50:02 +00:00
struct Index_Request {
std::string path;
2018-09-19 16:31:45 +00:00
std::vector<const char *> args;
IndexMode mode;
2018-05-28 00:50:02 +00:00
lsRequestId id;
2018-09-18 01:03:59 +00:00
int64_t ts = tick++;
2018-05-28 00:50:02 +00:00
};
struct Stdout_Request {
MethodType method;
std::string content;
};
2018-08-09 17:08:14 +00:00
MultiQueueWaiter *main_waiter;
MultiQueueWaiter *indexer_waiter;
MultiQueueWaiter *stdout_waiter;
ThreadedQueue<std::unique_ptr<InMessage>> *on_request;
ThreadedQueue<Index_Request> *index_request;
ThreadedQueue<IndexUpdate> *on_indexed;
ThreadedQueue<Stdout_Request> *for_stdout;
2018-05-28 00:50:02 +00:00
struct InMemoryIndexFile {
std::string content;
IndexFile index;
};
2018-09-10 06:46:13 +00:00
std::shared_mutex g_index_mutex;
std::unordered_map<std::string, InMemoryIndexFile> g_index;
2018-07-08 18:51:07 +00:00
bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path,
2018-09-19 16:31:45 +00:00
const std::vector<const char *> &args,
2018-07-08 18:51:07 +00:00
const std::optional<std::string> &from) {
{
std::lock_guard<std::mutex> lock(vfs->mutex);
if (prev->mtime < vfs->state[path].timestamp) {
LOG_S(INFO) << "timestamp changed for " << path
<< (from ? " (via " + *from + ")" : std::string());
return true;
}
2018-01-18 05:53:03 +00:00
}
2018-09-19 16:31:45 +00:00
bool changed = prev->args.size() != args.size();
for (size_t i = 0; !changed && i < args.size(); i++)
if (strcmp(prev->args[i], args[i]))
changed = true;
if (changed)
2018-08-09 17:08:14 +00:00
LOG_S(INFO) << "args changed for " << path
<< (from ? " (via " + *from + ")" : std::string());
2018-09-19 16:31:45 +00:00
return changed;
2018-01-18 05:53:03 +00:00
};
2018-01-18 05:48:09 +00:00
2018-08-09 17:08:14 +00:00
std::string AppendSerializationFormat(const std::string &base) {
2018-05-28 00:50:02 +00:00
switch (g_config->cacheFormat) {
2018-08-09 17:08:14 +00:00
case SerializeFormat::Binary:
return base + ".blob";
case SerializeFormat::Json:
return base + ".json";
2018-05-28 00:50:02 +00:00
}
}
2018-08-09 17:08:14 +00:00
std::string GetCachePath(const std::string &source_file) {
2018-05-28 00:50:02 +00:00
std::string cache_file;
auto len = g_config->projectRoot.size();
2018-05-28 00:50:02 +00:00
if (StartsWith(source_file, g_config->projectRoot)) {
cache_file = EscapeFileName(g_config->projectRoot.substr(0, len - 1)) + '/' +
2018-05-28 00:50:02 +00:00
EscapeFileName(source_file.substr(len));
} else {
cache_file = '@' +
EscapeFileName(g_config->projectRoot.substr(0, len - 1)) + '/' +
2018-05-28 00:50:02 +00:00
EscapeFileName(source_file);
}
return g_config->cacheDirectory + cache_file;
}
2018-08-09 17:08:14 +00:00
std::unique_ptr<IndexFile> RawCacheLoad(const std::string &path) {
if (g_config->cacheDirectory.empty()) {
2018-09-10 06:46:13 +00:00
std::shared_lock lock(g_index_mutex);
auto it = g_index.find(path);
if (it == g_index.end())
return nullptr;
return std::make_unique<IndexFile>(it->second.index);
}
2018-05-28 00:50:02 +00:00
std::string cache_path = GetCachePath(path);
std::optional<std::string> file_content = ReadContent(cache_path);
std::optional<std::string> serialized_indexed_content =
ReadContent(AppendSerializationFormat(cache_path));
if (!file_content || !serialized_indexed_content)
return nullptr;
return ccls::Deserialize(g_config->cacheFormat, path,
*serialized_indexed_content, *file_content,
IndexFile::kMajorVersion);
2018-05-28 00:50:02 +00:00
}
2018-09-08 23:00:14 +00:00
bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles,
Project *project, VFS *vfs, const GroupMatch &matcher) {
2018-09-18 01:03:59 +00:00
const int N_MUTEXES = 256;
static std::mutex mutexes[N_MUTEXES];
2018-05-28 00:50:02 +00:00
std::optional<Index_Request> opt_request = index_request->TryPopFront();
if (!opt_request)
return false;
2018-08-09 17:08:14 +00:00
auto &request = *opt_request;
bool loud = request.mode != IndexMode::OnChange;
2018-01-18 07:59:48 +00:00
2018-05-08 15:56:20 +00:00
// Dummy one to trigger refresh semantic highlight.
2018-05-08 07:35:32 +00:00
if (request.path.empty()) {
2018-05-08 15:56:20 +00:00
IndexUpdate dummy;
dummy.refresh = true;
2018-06-01 04:21:34 +00:00
on_indexed->PushBack(std::move(dummy), false);
2018-05-08 07:35:32 +00:00
return false;
}
2018-09-18 01:03:59 +00:00
if (!matcher.IsMatch(request.path)) {
LOG_IF_S(INFO, loud) << "skip " << request.path;
return false;
}
Project::Entry entry = project->FindCompilationEntryForFile(request.path);
if (request.args.size())
entry.args = request.args;
std::string path_to_index = entry.filename;
std::unique_ptr<IndexFile> prev;
2018-01-18 07:59:48 +00:00
std::optional<int64_t> write_time = LastWriteTime(path_to_index);
if (!write_time)
return true;
2018-09-18 01:03:59 +00:00
int reparse = vfs->Stamp(path_to_index, *write_time, -1);
if (request.path != path_to_index) {
std::optional<int64_t> mtime1 = LastWriteTime(request.path);
if (!mtime1)
return true;
2018-09-18 01:03:59 +00:00
if (vfs->Stamp(request.path, *mtime1, -1))
reparse = 1;
}
if (g_config->index.onChange)
reparse = 2;
2018-09-18 01:03:59 +00:00
if (!reparse)
return true;
2018-09-18 01:03:59 +00:00
if (reparse < 2) do {
std::unique_lock lock(
mutexes[std::hash<std::string>()(path_to_index) % N_MUTEXES]);
prev = RawCacheLoad(path_to_index);
if (!prev || CacheInvalid(vfs, prev.get(), path_to_index, entry.args,
std::nullopt))
break;
bool update = false;
for (const auto &dep : prev->dependencies)
if (auto mtime1 = LastWriteTime(dep.first.val().str())) {
if (dep.second < *mtime1)
update = true;
} else {
update = true;
}
2018-09-18 01:03:59 +00:00
int forDep = g_config->index.reparseForDependency;
if (update && (forDep > 1 || (forDep == 1 && request.ts < loaded_ts)))
break;
if (reparse < 2) {
LOG_S(INFO) << "load cache for " << path_to_index;
auto dependencies = prev->dependencies;
if (reparse) {
IndexUpdate update = IndexUpdate::CreateDelta(nullptr, prev.get());
on_indexed->PushBack(std::move(update),
request.mode != IndexMode::NonInteractive);
2018-09-18 01:03:59 +00:00
std::lock_guard lock1(vfs->mutex);
vfs->state[path_to_index].loaded = true;
}
lock.unlock();
for (const auto &dep : dependencies) {
std::string path = dep.first.val().str();
std::lock_guard lock1(
mutexes[std::hash<std::string>()(path) % N_MUTEXES]);
prev = RawCacheLoad(path);
if (!prev)
continue;
{
2018-09-18 01:03:59 +00:00
std::lock_guard lock2(vfs->mutex);
VFS::State &st = vfs->state[path];
if (st.loaded)
continue;
st.loaded = true;
st.timestamp = prev->mtime;
}
2018-09-18 01:03:59 +00:00
IndexUpdate update = IndexUpdate::CreateDelta(nullptr, prev.get());
on_indexed->PushBack(std::move(update),
request.mode != IndexMode::NonInteractive);
if (entry.id >= 0) {
2018-09-18 01:03:59 +00:00
std::lock_guard lock2(project->mutex_);
project->path_to_entry_index[path] = entry.id;
}
}
2018-09-18 01:03:59 +00:00
return true;
}
2018-09-18 01:03:59 +00:00
} while (0);
LOG_IF_S(INFO, loud) << "parse " << path_to_index;
std::vector<std::pair<std::string, std::string>> remapped;
if (g_config->index.onChange) {
std::string content = wfiles->GetContent(path_to_index);
if (content.size())
remapped.emplace_back(path_to_index, content);
}
auto indexes = idx::Index(completion, wfiles, vfs, entry.directory,
path_to_index, entry.args, remapped);
2018-01-20 07:56:49 +00:00
2018-04-02 07:22:12 +00:00
if (indexes.empty()) {
2018-04-16 19:36:02 +00:00
if (g_config->index.enabled && request.id.Valid()) {
2018-01-20 07:56:49 +00:00
Out_Error out;
out.id = request.id;
out.error.code = lsErrorCodes::InternalError;
out.error.message = "Failed to index " + path_to_index;
2018-05-28 00:50:02 +00:00
pipeline::WriteStdout(kMethodType_Unknown, out);
2018-01-20 07:56:49 +00:00
}
return true;
2018-01-20 07:56:49 +00:00
}
2018-08-09 17:08:14 +00:00
for (std::unique_ptr<IndexFile> &curr : indexes) {
std::string path = curr->path;
2018-09-18 01:03:59 +00:00
if (!matcher.IsMatch(path)) {
LOG_IF_S(INFO, loud) << "skip index for " << path;
continue;
}
LOG_IF_S(INFO, loud) << "store index for " << path << " (delta: " << !!prev
<< ")";
2018-09-18 01:03:59 +00:00
{
std::lock_guard lock(mutexes[std::hash<std::string>()(path) % N_MUTEXES]);
bool loaded;
{
std::lock_guard lock1(vfs->mutex);
loaded = vfs->state[path].loaded;
}
if (loaded)
prev = RawCacheLoad(path);
else
prev.reset();
if (g_config->cacheDirectory.empty()) {
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 {
std::string cache_path = GetCachePath(path);
WriteToFile(cache_path, curr->file_contents);
WriteToFile(AppendSerializationFormat(cache_path),
Serialize(g_config->cacheFormat, *curr));
}
on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()),
request.mode != IndexMode::NonInteractive);
{
std::lock_guard lock1(vfs->mutex);
vfs->state[path].loaded = true;
}
if (entry.id >= 0) {
std::lock_guard<std::mutex> lock(project->mutex_);
for (auto &dep : curr->dependencies)
project->path_to_entry_index[dep.first.val().str()] = entry.id;
2018-09-18 01:03:59 +00:00
}
}
}
return true;
}
2018-08-09 17:08:14 +00:00
} // namespace
2018-05-28 00:50:02 +00:00
void Init() {
main_waiter = new MultiQueueWaiter;
on_request = new ThreadedQueue<std::unique_ptr<InMessage>>(main_waiter);
2018-06-01 04:21:34 +00:00
on_indexed = new ThreadedQueue<IndexUpdate>(main_waiter);
2018-05-28 00:50:02 +00:00
indexer_waiter = new MultiQueueWaiter;
index_request = new ThreadedQueue<Index_Request>(indexer_waiter);
stdout_waiter = new MultiQueueWaiter;
for_stdout = new ThreadedQueue<Stdout_Request>(stdout_waiter);
}
2018-09-08 23:00:14 +00:00
void Indexer_Main(CompletionManager *completion, VFS *vfs, Project *project,
WorkingFiles *wfiles) {
GroupMatch matcher(g_config->index.whitelist, g_config->index.blacklist);
while (true)
2018-09-08 23:00:14 +00:00
if (!Indexer_Parse(completion, wfiles, project, vfs, matcher))
2018-05-28 00:50:02 +00:00
indexer_waiter->Wait(index_request);
2018-02-05 03:38:57 +00:00
}
2018-08-09 17:08:14 +00:00
void Main_OnIndexed(DB *db, SemanticHighlightSymbolCache *semantic_cache,
WorkingFiles *working_files, IndexUpdate *update) {
2018-06-01 04:21:34 +00:00
if (update->refresh) {
2018-08-09 17:08:14 +00:00
LOG_S(INFO)
<< "loaded project. Refresh semantic highlight for all working file.";
2018-05-08 07:35:32 +00:00
std::lock_guard<std::mutex> lock(working_files->files_mutex);
2018-08-09 17:08:14 +00:00
for (auto &f : working_files->files) {
2018-05-08 15:56:20 +00:00
std::string filename = LowerPathIfInsensitive(f->filename);
if (db->name2file_id.find(filename) == db->name2file_id.end())
continue;
2018-08-09 17:08:14 +00:00
QueryFile *file = &db->files[db->name2file_id[filename]];
2018-05-08 07:35:32 +00:00
EmitSemanticHighlighting(db, semantic_cache, f.get(), file);
}
return;
}
2018-07-03 22:47:43 +00:00
static Timer timer("apply", "apply index");
timer.startTimer();
2018-06-01 04:21:34 +00:00
db->ApplyIndexUpdate(update);
timer.stopTimer();
2018-02-05 03:38:57 +00:00
2018-07-08 19:49:27 +00:00
// Update indexed content, skipped ranges, and semantic highlighting.
2018-06-01 04:21:34 +00:00
if (update->files_def_update) {
2018-08-09 17:08:14 +00:00
auto &def_u = *update->files_def_update;
if (WorkingFile *wfile =
2018-06-01 04:21:34 +00:00
working_files->GetFileByFilename(def_u.first.path)) {
// FIXME With index.onChange: true, use buffer_content only for
// request.path
wfile->SetIndexContent(g_config->index.onChange ? wfile->buffer_content
: def_u.second);
EmitSkippedRanges(wfile, def_u.first.skipped_ranges);
EmitSemanticHighlighting(db, semantic_cache, wfile,
2018-06-01 04:21:34 +00:00
&db->files[update->file_id]);
2018-02-05 03:38:57 +00:00
}
}
}
2018-03-20 03:01:23 +00:00
void LaunchStdin() {
std::thread([]() {
2018-05-27 19:24:56 +00:00
set_thread_name("stdin");
while (true) {
std::unique_ptr<InMessage> message;
std::optional<std::string> err =
MessageRegistry::instance()->ReadMessageFromStdin(&message);
// Message parsing can fail if we don't recognize the method.
if (err) {
// The message may be partially deserialized.
// Emit an error ResponseMessage if |id| is available.
if (message) {
lsRequestId id = message->GetRequestId();
if (id.Valid()) {
Out_Error out;
out.id = id;
out.error.code = lsErrorCodes::InvalidParams;
out.error.message = std::move(*err);
2018-05-28 00:50:02 +00:00
WriteStdout(kMethodType_Unknown, out);
}
}
continue;
}
2018-02-05 03:38:57 +00:00
// Cache |method_id| so we can access it after moving |message|.
MethodType method_type = message->GetMethodType();
2018-05-28 00:50:02 +00:00
on_request->PushBack(std::move(message));
// If the message was to exit then querydb will take care of the actual
// exit. Stop reading from stdin since it might be detached.
if (method_type == kMethodType_Exit)
break;
}
2018-08-09 17:08:14 +00:00
})
.detach();
}
void LaunchStdout() {
std::thread([=]() {
2018-05-27 19:24:56 +00:00
set_thread_name("stdout");
while (true) {
2018-05-28 00:50:02 +00:00
std::vector<Stdout_Request> messages = for_stdout->DequeueAll();
if (messages.empty()) {
2018-05-28 00:50:02 +00:00
stdout_waiter->Wait(for_stdout);
continue;
}
2018-08-09 17:08:14 +00:00
for (auto &message : messages) {
2018-07-03 22:47:43 +00:00
#ifdef _WIN32
fwrite(message.content.c_str(), message.content.size(), 1, stdout);
fflush(stdout);
2018-07-03 22:47:43 +00:00
#else
write(1, message.content.c_str(), message.content.size());
#endif
}
}
2018-08-09 17:08:14 +00:00
})
.detach();
}
2018-05-28 00:50:02 +00:00
void MainLoop() {
Project project;
SemanticHighlightSymbolCache semantic_cache;
WorkingFiles working_files;
VFS vfs;
DiagnosticsPublisher diag_pub;
CompletionManager clang_complete(
&project, &working_files,
[&](std::string path, std::vector<lsDiagnostic> diagnostics) {
diag_pub.Publish(&working_files, path, diagnostics);
},
[](lsRequestId id) {
if (id.Valid()) {
Out_Error out;
out.id = id;
out.error.code = lsErrorCodes::InternalError;
2018-08-09 17:08:14 +00:00
out.error.message = "Dropping completion request; a newer request "
"has come in that will be serviced instead.";
2018-05-28 00:50:02 +00:00
pipeline::WriteStdout(kMethodType_Unknown, out);
}
});
IncludeComplete include_complete(&project);
DB db;
// Setup shared references.
2018-08-09 17:08:14 +00:00
for (MessageHandler *handler : *MessageHandler::message_handlers) {
handler->db = &db;
handler->waiter = indexer_waiter;
handler->project = &project;
handler->diag_pub = &diag_pub;
handler->vfs = &vfs;
handler->semantic_cache = &semantic_cache;
handler->working_files = &working_files;
handler->clang_complete = &clang_complete;
handler->include_complete = &include_complete;
}
while (true) {
2018-05-28 00:50:02 +00:00
std::vector<std::unique_ptr<InMessage>> messages = on_request->DequeueAll();
bool did_work = messages.size();
2018-08-09 17:08:14 +00:00
for (auto &message : messages) {
// TODO: Consider using std::unordered_map to lookup the handler
2018-08-09 17:08:14 +00:00
for (MessageHandler *handler : *MessageHandler::message_handlers) {
if (handler->GetMethodType() == message->GetMethodType()) {
handler->Run(std::move(message));
break;
}
}
if (message)
LOG_S(ERROR) << "No handler for " << message->GetMethodType();
}
for (int i = 80; i--;) {
2018-06-01 04:21:34 +00:00
std::optional<IndexUpdate> update = on_indexed->TryPopFront();
if (!update)
break;
did_work = true;
2018-06-01 04:21:34 +00:00
Main_OnIndexed(&db, &semantic_cache, &working_files, &*update);
}
if (!did_work) {
FreeUnusedMemory();
2018-05-28 00:50:02 +00:00
main_waiter->Wait(on_indexed, on_request);
}
}
}
2018-05-28 00:50:02 +00:00
2018-09-19 16:31:45 +00:00
void Index(const std::string &path, const std::vector<const char *> &args,
IndexMode mode, lsRequestId id) {
index_request->PushBack({path, args, mode, id}, mode != IndexMode::NonInteractive);
2018-05-28 00:50:02 +00:00
}
std::optional<int64_t> LastWriteTime(const std::string &path) {
sys::fs::file_status Status;
if (sys::fs::status(path, Status))
return {};
return sys::toTimeT(Status.getLastModificationTime());
}
std::optional<std::string> LoadIndexedContent(const std::string &path) {
if (g_config->cacheDirectory.empty()) {
2018-09-10 06:46:13 +00:00
std::shared_lock lock(g_index_mutex);
auto it = g_index.find(path);
if (it == g_index.end())
return {};
return it->second.content;
}
2018-05-28 00:50:02 +00:00
return ReadContent(GetCachePath(path));
}
2018-08-09 17:08:14 +00:00
void WriteStdout(MethodType method, lsBaseOutMessage &response) {
2018-05-28 00:50:02 +00:00
std::ostringstream sstream;
response.Write(sstream);
Stdout_Request out;
out.content = sstream.str();
out.method = method;
for_stdout->PushBack(std::move(out));
}
2018-08-09 17:08:14 +00:00
} // namespace ccls::pipeline