First iteration of code completion off the main thread. Still one race condition but hopefully it shouldn't happen too often.

This commit is contained in:
Jacob Dufault 2017-04-16 18:22:59 -07:00
parent c6dead848e
commit 5e8e13380d
4 changed files with 206 additions and 135 deletions

32
src/atomic_object.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <algorithm>
#include <condition_variable>
#include <memory>
#include <mutex>
// A object which can be stored and taken from atomically.
template <class T>
struct AtomicObject {
void Set(std::unique_ptr<T> t) {
std::lock_guard<std::mutex> lock(mutex_);
value_ = std::move(t);
cv_.notify_one();
}
std::unique_ptr<T> Take() {
std::unique_lock<std::mutex> lock(mutex_);
while (!value_) {
// release lock as long as the wait and reaquire it afterwards.
cv_.wait(lock);
}
return std::move(value_);
}
private:
std::unique_ptr<T> value_;
mutable std::mutex mutex_;
std::condition_variable cv_;
};

View File

@ -4,6 +4,7 @@
#include "timer.h"
#include <algorithm>
#include <thread>
namespace {
unsigned Flags() {
@ -191,64 +192,28 @@ std::string BuildDetailString(CXCompletionString completion_string) {
return detail;
}
}
CompletionSession::CompletionSession(const CompilationEntry& file, WorkingFiles* working_files) : file(file) {
std::vector<CXUnsavedFile> unsaved = working_files->AsUnsavedFiles();
void CompletionMain(CompletionManager* completion_manager) {
while (true) {
std::unique_ptr<CompletionManager::CompletionRequest> request = completion_manager->completion_request.Take();
std::vector<std::string> args = file.args;
args.push_back("-x");
args.push_back("c++");
args.push_back("-fparse-all-comments");
std::string sent_args = "";
for (auto& arg : args)
sent_args += arg + ", ";
std::cerr << "Creating completion session with arguments " << sent_args << std::endl;
// TODO: I think we crash when there are syntax errors.
active_index = MakeUnique<clang::Index>(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/);
active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
std::cerr << "Done creating active; did_fail=" << active->did_fail << std::endl;
//if (active->did_fail) {
// std::cerr << "Failed to create translation unit; trying again..." << std::endl;
// active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
//}
// Despite requesting clang create a precompiled header on the first parse in
// Flags() via CXTranslationUnit_CreatePreambleOnFirstParse, it doesn't seem
// to do so. Immediately reparsing will create one which reduces
// clang_codeCompleteAt timing from 200ms to 20ms on simple files.
// TODO: figure out why this is crashing so much
if (!active->did_fail) {
std::cerr << "Start reparse" << std::endl;
active->ReparseTranslationUnit(unsaved);
std::cerr << "Done reparse" << std::endl;
}
}
CompletionSession::~CompletionSession() {}
void CompletionSession::Refresh(std::vector<CXUnsavedFile>& unsaved) {
// TODO: Do this off the code completion thread so we don't block completions.
active->ReparseTranslationUnit(unsaved);
}
CompletionManager::CompletionManager(Project* project, WorkingFiles* working_files) : project(project), working_files(working_files) {}
NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDocumentPositionParams& completion_location) {
NonElidedVector<lsCompletionItem> ls_result;
CompletionSession* session = GetOrOpenSession(completion_location.textDocument.uri.GetPath());
CompletionSession* session = completion_manager->GetOrOpenSession(request->location.textDocument.uri.GetPath());
unsigned line = completion_location.position.line + 1;
unsigned column = completion_location.position.character + 1;
unsigned line = request->location.position.line + 1;
unsigned column = request->location.position.character + 1;
std::cerr << std::endl;
//std::cerr << "Completing at " << line << ":" << column << std::endl;
std::cerr << "Completing at " << line << ":" << column << std::endl;
std::vector<CXUnsavedFile> unsaved = working_files->AsUnsavedFiles();
// TODO/FIXME
// TODO/FIXME
// TODO/FIXME NOT THREAD SAFE
// TODO/FIXME
// TODO/FIXME
std::vector<CXUnsavedFile> unsaved = completion_manager->working_files->AsUnsavedFiles();
Timer timer;
@ -262,7 +227,8 @@ NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDo
CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments);
if (!cx_results) {
std::cerr << "Code completion failed" << std::endl;
return ls_result;
request->on_complete(ls_result);
continue;
}
timer.ResetAndPrint("clangCodeCompleteAt");
std::cerr << "Got " << cx_results->NumResults << " results" << std::endl;
@ -323,12 +289,13 @@ NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDo
ls_result.push_back(ls_completion_item);
}
timer.ResetAndPrint("Building completion results");
timer.ResetAndPrint("Building " + std::to_string(ls_result.size()) + " completion results");
clang_disposeCodeCompleteResults(cx_results);
timer.ResetAndPrint("clang_disposeCodeCompleteResults ");
return ls_result;
request->on_complete(ls_result);
continue;
// we should probably main two translation units, one for
// serving current requests, and one that is reparsing (follow qtcreator)
@ -353,6 +320,64 @@ NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDo
// probably don't need
// - textDocument/willSave
}
}
} // namespace
CompletionSession::CompletionSession(const CompilationEntry& file, WorkingFiles* working_files) : file(file) {
std::vector<CXUnsavedFile> unsaved = working_files->AsUnsavedFiles();
std::vector<std::string> args = file.args;
args.push_back("-x");
args.push_back("c++");
args.push_back("-fparse-all-comments");
std::string sent_args = "";
for (auto& arg : args)
sent_args += arg + ", ";
std::cerr << "Creating completion session with arguments " << sent_args << std::endl;
// TODO: I think we crash when there are syntax errors.
active_index = MakeUnique<clang::Index>(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/);
active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
std::cerr << "Done creating active; did_fail=" << active->did_fail << std::endl;
//if (active->did_fail) {
// std::cerr << "Failed to create translation unit; trying again..." << std::endl;
// active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
//}
// Despite requesting clang create a precompiled header on the first parse in
// Flags() via CXTranslationUnit_CreatePreambleOnFirstParse, it doesn't seem
// to do so. Immediately reparsing will create one which reduces
// clang_codeCompleteAt timing from 200ms to 20ms on simple files.
// TODO: figure out why this is crashing so much
if (!active->did_fail) {
std::cerr << "Start reparse" << std::endl;
active->ReparseTranslationUnit(unsaved);
std::cerr << "Done reparse" << std::endl;
}
}
CompletionSession::~CompletionSession() {}
void CompletionSession::Refresh(std::vector<CXUnsavedFile>& unsaved) {
// TODO: Do this off the code completion thread so we don't block completions.
active->ReparseTranslationUnit(unsaved);
}
CompletionManager::CompletionManager(Project* project, WorkingFiles* working_files) : project(project), working_files(working_files) {
new std::thread([&]() {
CompletionMain(this);
});
}
void CompletionManager::CodeComplete(const lsTextDocumentPositionParams& completion_location, const OnComplete& on_complete) {
auto request = MakeUnique<CompletionRequest>();
request->location = completion_location;
request->on_complete = on_complete;
completion_request.Set(std::move(request));
}
CompletionSession* CompletionManager::GetOrOpenSession(const std::string& filename) {
// Try to find existing session.

View File

@ -1,3 +1,4 @@
#include "atomic_object.h"
#include "language_server_api.h"
#include "libclangmm/CompletionString.h"
#include "libclangmm/Index.h"
@ -7,6 +8,8 @@
#include <clang-c/Index.h>
#include <functional>
struct CompletionSession {
CompilationEntry file;
@ -33,12 +36,18 @@ struct CompletionManager {
Project* project;
WorkingFiles* working_files;
using OnComplete = std::function<void(NonElidedVector<lsCompletionItem> results)>;
struct CompletionRequest {
lsTextDocumentPositionParams location;
OnComplete on_complete;
};
AtomicObject<CompletionRequest> completion_request;
CompletionManager(Project* project, WorkingFiles* working_files);
// This all should run on complete_responder thread. This will internally run a child thread to
// reparse.
NonElidedVector<lsCompletionItem> CodeComplete(const lsTextDocumentPositionParams& completion_location);
// Start a code completion at the given location. |on_complete| will run when
// completion results are available. |on_complete| may run on any thread.
void CodeComplete(const lsTextDocumentPositionParams& completion_location, const OnComplete& on_complete);
private:
CompletionSession* GetOrOpenSession(const std::string& filename);
};

View File

@ -20,6 +20,7 @@
#include <rapidjson/ostreamwrapper.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
@ -999,13 +1000,6 @@ void IndexMain(
@ -1170,16 +1164,27 @@ void QueryDbMainLoop(
}
case IpcId::TextDocumentCompletion: {
// TODO: better performance
auto msg = static_cast<Ipc_TextDocumentComplete*>(message.get());
lsTextDocumentPositionParams params = msg->params;
CompletionManager::OnComplete callback = std::bind([](BaseIpcMessage* message, const NonElidedVector<lsCompletionItem>& results) {
auto msg = static_cast<Ipc_TextDocumentComplete*>(message);
auto ipc = IpcManager::instance();
Out_TextDocumentComplete response;
response.id = msg->id;
response.result.isIncomplete = false;
response.result.items = completion_manager->CodeComplete(msg->params);
response.result.items = results;
Timer timer;
ipc->SendOutMessageToClient(response);
timer.ResetAndPrint("Writing completion results");
delete message;
}, message.release(), std::placeholders::_1);
completion_manager->CodeComplete(params, std::move(callback));
break;
}