mirror of
https://github.com/MaskRay/ccls.git
synced 2025-04-14 21:02:14 +00:00
Use condition variables instead of sleeping.
While tricky to do because we have multiple queues, this reduces a lot of unnecessary delay. e2e time goes down from 10-15ms on average to 0-3ms on average. Loading from cache is also nearly instant on the cquery codebase.
This commit is contained in:
parent
63908e3aa0
commit
1b2f5896dc
@ -80,8 +80,8 @@ struct IpcManager {
|
|||||||
static IpcManager* instance() {
|
static IpcManager* instance() {
|
||||||
return instance_;
|
return instance_;
|
||||||
}
|
}
|
||||||
static void CreateInstance() {
|
static void CreateInstance(MultiQueueWaiter* waiter) {
|
||||||
instance_ = new IpcManager();
|
instance_ = new IpcManager(waiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>> threaded_queue_for_client_;
|
std::unique_ptr<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>> threaded_queue_for_client_;
|
||||||
@ -114,9 +114,9 @@ struct IpcManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IpcManager() {
|
IpcManager(MultiQueueWaiter* waiter) {
|
||||||
threaded_queue_for_client_ = MakeUnique<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>();
|
threaded_queue_for_client_ = MakeUnique<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>(waiter);
|
||||||
threaded_queue_for_server_ = MakeUnique<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>();
|
threaded_queue_for_server_ = MakeUnique<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>(waiter);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1073,18 +1073,20 @@ bool IndexMain_DoCreateIndexUpdate(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndexJoinIndexUpdates(Index_OnIndexedQueue* queue_on_indexed) {
|
bool IndexMergeIndexUpdates(Index_OnIndexedQueue* queue_on_indexed) {
|
||||||
optional<Index_OnIndexed> root = queue_on_indexed->TryDequeue();
|
optional<Index_OnIndexed> root = queue_on_indexed->TryDequeue();
|
||||||
if (!root)
|
if (!root)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
|
bool did_merge = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
optional<Index_OnIndexed> to_join = queue_on_indexed->TryDequeue();
|
optional<Index_OnIndexed> to_join = queue_on_indexed->TryDequeue();
|
||||||
if (!to_join) {
|
if (!to_join) {
|
||||||
queue_on_indexed->Enqueue(std::move(*root));
|
queue_on_indexed->Enqueue(std::move(*root));
|
||||||
return;
|
return did_merge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
did_merge = true;
|
||||||
Timer time;
|
Timer time;
|
||||||
root->update.Merge(to_join->update);
|
root->update.Merge(to_join->update);
|
||||||
time.ResetAndPrint("[indexer] Joining two querydb updates");
|
time.ResetAndPrint("[indexer] Joining two querydb updates");
|
||||||
@ -1095,6 +1097,7 @@ void IndexMain(
|
|||||||
IndexerConfig* config,
|
IndexerConfig* config,
|
||||||
FileConsumer::SharedState* file_consumer_shared,
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
Project* project,
|
Project* project,
|
||||||
|
MultiQueueWaiter* waiter,
|
||||||
Index_DoIndexQueue* queue_do_index,
|
Index_DoIndexQueue* queue_do_index,
|
||||||
Index_DoIdMapQueue* queue_do_id_map,
|
Index_DoIdMapQueue* queue_do_id_map,
|
||||||
Index_OnIdMappedQueue* queue_on_id_mapped,
|
Index_OnIdMappedQueue* queue_on_id_mapped,
|
||||||
@ -1112,15 +1115,20 @@ void IndexMain(
|
|||||||
// index.
|
// index.
|
||||||
bool did_index = IndexMain_DoIndex(config, file_consumer_shared, project, queue_do_index, queue_do_id_map);
|
bool did_index = IndexMain_DoIndex(config, file_consumer_shared, project, queue_do_index, queue_do_id_map);
|
||||||
bool did_create_update = IndexMain_DoCreateIndexUpdate(queue_on_id_mapped, queue_on_indexed);
|
bool did_create_update = IndexMain_DoCreateIndexUpdate(queue_on_id_mapped, queue_on_indexed);
|
||||||
if (!did_index && !did_create_update) {
|
bool did_merge = false;
|
||||||
|
|
||||||
// Nothing to index and no index updates to create, so join some already
|
// Nothing to index and no index updates to create, so join some already
|
||||||
// created index updates to reduce work on querydb thread.
|
// created index updates to reduce work on querydb thread.
|
||||||
IndexJoinIndexUpdates(queue_on_indexed);
|
if (!did_index && !did_create_update)
|
||||||
|
did_merge = IndexMergeIndexUpdates(queue_on_indexed);
|
||||||
|
|
||||||
// TODO: use CV to wakeup?
|
// We didn't do any work, so wait for a notification.
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(25));
|
if (!did_index && !did_create_update && !did_merge)
|
||||||
}
|
waiter->Wait({
|
||||||
|
queue_do_index,
|
||||||
|
queue_on_id_mapped,
|
||||||
|
queue_on_indexed
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1173,9 +1181,10 @@ void IndexMain(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
void QueryDbMainLoop(
|
bool QueryDbMainLoop(
|
||||||
IndexerConfig* config,
|
IndexerConfig* config,
|
||||||
QueryDatabase* db,
|
QueryDatabase* db,
|
||||||
|
MultiQueueWaiter* waiter,
|
||||||
Index_DoIndexQueue* queue_do_index,
|
Index_DoIndexQueue* queue_do_index,
|
||||||
Index_DoIdMapQueue* queue_do_id_map,
|
Index_DoIdMapQueue* queue_do_id_map,
|
||||||
Index_OnIdMappedQueue* queue_on_id_mapped,
|
Index_OnIdMappedQueue* queue_on_id_mapped,
|
||||||
@ -1186,8 +1195,11 @@ void QueryDbMainLoop(
|
|||||||
CompletionManager* completion_manager) {
|
CompletionManager* completion_manager) {
|
||||||
IpcManager* ipc = IpcManager::instance();
|
IpcManager* ipc = IpcManager::instance();
|
||||||
|
|
||||||
|
bool did_work = false;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<BaseIpcMessage>> messages = ipc->GetMessages(IpcManager::Destination::Server);
|
std::vector<std::unique_ptr<BaseIpcMessage>> messages = ipc->GetMessages(IpcManager::Destination::Server);
|
||||||
for (auto& message : messages) {
|
for (auto& message : messages) {
|
||||||
|
did_work = true;
|
||||||
std::cerr << "[querydb] Processing message " << IpcIdToString(message->method_id) << std::endl;
|
std::cerr << "[querydb] Processing message " << IpcIdToString(message->method_id) << std::endl;
|
||||||
|
|
||||||
switch (message->method_id) {
|
switch (message->method_id) {
|
||||||
@ -1223,7 +1235,7 @@ void QueryDbMainLoop(
|
|||||||
std::cerr << "[querydb] Starting " << indexer_count << " indexers" << std::endl;
|
std::cerr << "[querydb] Starting " << indexer_count << " indexers" << std::endl;
|
||||||
for (int i = 0; i < indexer_count; ++i) {
|
for (int i = 0; i < indexer_count; ++i) {
|
||||||
new std::thread([&]() {
|
new std::thread([&]() {
|
||||||
IndexMain(config, file_consumer_shared, project, queue_do_index, queue_do_id_map, queue_on_id_mapped, queue_on_indexed);
|
IndexMain(config, file_consumer_shared, project, waiter, queue_do_index, queue_do_id_map, queue_on_id_mapped, queue_on_indexed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1738,6 +1750,7 @@ void QueryDbMainLoop(
|
|||||||
if (!request)
|
if (!request)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
did_work = true;
|
||||||
|
|
||||||
Index_OnIdMapped response;
|
Index_OnIdMapped response;
|
||||||
Timer time;
|
Timer time;
|
||||||
@ -1760,6 +1773,8 @@ void QueryDbMainLoop(
|
|||||||
if (!response)
|
if (!response)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
did_work = true;
|
||||||
|
|
||||||
Timer time;
|
Timer time;
|
||||||
|
|
||||||
for (auto& updated_file : response->update.files_def_update) {
|
for (auto& updated_file : response->update.files_def_update) {
|
||||||
@ -1787,14 +1802,16 @@ void QueryDbMainLoop(
|
|||||||
db->ApplyIndexUpdate(&response->update);
|
db->ApplyIndexUpdate(&response->update);
|
||||||
time.ResetAndPrint("[querydb] Applying index update");
|
time.ResetAndPrint("[querydb] Applying index update");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return did_work;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryDbMain(IndexerConfig* config) {
|
void QueryDbMain(IndexerConfig* config, MultiQueueWaiter* waiter) {
|
||||||
// Create queues.
|
// Create queues.
|
||||||
Index_DoIndexQueue queue_do_index;
|
Index_DoIndexQueue queue_do_index(waiter);
|
||||||
Index_DoIdMapQueue queue_do_id_map;
|
Index_DoIdMapQueue queue_do_id_map(waiter);
|
||||||
Index_OnIdMappedQueue queue_on_id_mapped;
|
Index_OnIdMappedQueue queue_on_id_mapped(waiter);
|
||||||
Index_OnIndexedQueue queue_on_indexed;
|
Index_OnIndexedQueue queue_on_indexed(waiter);
|
||||||
|
|
||||||
Project project;
|
Project project;
|
||||||
WorkingFiles working_files;
|
WorkingFiles working_files;
|
||||||
@ -1805,8 +1822,15 @@ void QueryDbMain(IndexerConfig* config) {
|
|||||||
SetCurrentThreadName("querydb");
|
SetCurrentThreadName("querydb");
|
||||||
QueryDatabase db;
|
QueryDatabase db;
|
||||||
while (true) {
|
while (true) {
|
||||||
QueryDbMainLoop(config, &db, &queue_do_index, &queue_do_id_map, &queue_on_id_mapped, &queue_on_indexed, &project, &file_consumer_shared, &working_files, &completion_manager);
|
bool did_work = QueryDbMainLoop(config, &db, waiter, &queue_do_index, &queue_do_id_map, &queue_on_id_mapped, &queue_on_indexed, &project, &file_consumer_shared, &working_files, &completion_manager);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
if (!did_work) {
|
||||||
|
IpcManager* ipc = IpcManager::instance();
|
||||||
|
waiter->Wait({
|
||||||
|
ipc->threaded_queue_for_server_.get(),
|
||||||
|
&queue_do_id_map,
|
||||||
|
&queue_on_indexed
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1978,34 +2002,42 @@ void LanguageServerStdinLoop(IndexerConfig* config, std::unordered_map<IpcId, Ti
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
void StdoutMainLoop(std::unordered_map<IpcId, Timer>* request_times) {
|
void StdoutMain(std::unordered_map<IpcId, Timer>* request_times, MultiQueueWaiter* waiter) {
|
||||||
|
SetCurrentThreadName("stdout");
|
||||||
IpcManager* ipc = IpcManager::instance();
|
IpcManager* ipc = IpcManager::instance();
|
||||||
|
|
||||||
std::vector<std::unique_ptr<BaseIpcMessage>> messages = ipc->GetMessages(IpcManager::Destination::Client);
|
while (true) {
|
||||||
for (auto& message : messages) {
|
std::vector<std::unique_ptr<BaseIpcMessage>> messages = ipc->GetMessages(IpcManager::Destination::Client);
|
||||||
std::cerr << "[stdout] Processing message " << IpcIdToString(message->method_id) << std::endl;
|
if (messages.empty()) {
|
||||||
|
waiter->Wait({ipc->threaded_queue_for_client_.get()});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (message->method_id) {
|
for (auto& message : messages) {
|
||||||
case IpcId::Cout: {
|
std::cerr << "[stdout] Processing message " << IpcIdToString(message->method_id) << std::endl;
|
||||||
auto msg = static_cast<Ipc_Cout*>(message.get());
|
|
||||||
|
|
||||||
Timer time = (*request_times)[msg->original_ipc_id];
|
switch (message->method_id) {
|
||||||
time.ResetAndPrint("[e2e] Running " + std::string(IpcIdToString(msg->original_ipc_id)));
|
case IpcId::Cout: {
|
||||||
|
auto msg = static_cast<Ipc_Cout*>(message.get());
|
||||||
|
|
||||||
std::cout << msg->content;
|
Timer time = (*request_times)[msg->original_ipc_id];
|
||||||
std::cout.flush();
|
time.ResetAndPrint("[e2e] Running " + std::string(IpcIdToString(msg->original_ipc_id)));
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
std::cout << msg->content;
|
||||||
std::cerr << "[stdout] Unhandled IPC message " << IpcIdToString(message->method_id) << std::endl;
|
std::cout.flush();
|
||||||
exit(1);
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
std::cerr << "[stdout] Unhandled IPC message " << IpcIdToString(message->method_id) << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LanguageServerMain(IndexerConfig* config) {
|
void LanguageServerMain(IndexerConfig* config, MultiQueueWaiter* waiter) {
|
||||||
std::unordered_map<IpcId, Timer> request_times;
|
std::unordered_map<IpcId, Timer> request_times;
|
||||||
|
|
||||||
// Start stdin reader. Reading from stdin is a blocking operation so this
|
// Start stdin reader. Reading from stdin is a blocking operation so this
|
||||||
@ -2015,17 +2047,13 @@ void LanguageServerMain(IndexerConfig* config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Start querydb thread. querydb will start indexer threads as needed.
|
// Start querydb thread. querydb will start indexer threads as needed.
|
||||||
new std::thread([&config]() {
|
new std::thread([&]() {
|
||||||
QueryDbMain(config);
|
QueryDbMain(config, waiter);
|
||||||
});
|
});
|
||||||
|
|
||||||
// We run a dedicated thread for writing to stdout because there can be an
|
// We run a dedicated thread for writing to stdout because there can be an
|
||||||
// unknown number of delays when output information.
|
// unknown number of delays when output information.
|
||||||
SetCurrentThreadName("stdout");
|
StdoutMain(&request_times, waiter);
|
||||||
while (true) {
|
|
||||||
StdoutMainLoop(&request_times);
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2079,7 +2107,8 @@ void LanguageServerMain(IndexerConfig* config) {
|
|||||||
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
IpcManager::CreateInstance();
|
MultiQueueWaiter waiter;
|
||||||
|
IpcManager::CreateInstance(&waiter);
|
||||||
|
|
||||||
//bool loop = true;
|
//bool loop = true;
|
||||||
//while (loop)
|
//while (loop)
|
||||||
@ -2107,7 +2136,7 @@ int main(int argc, char** argv) {
|
|||||||
else if (HasOption(options, "--language-server")) {
|
else if (HasOption(options, "--language-server")) {
|
||||||
//std::cerr << "Running language server" << std::endl;
|
//std::cerr << "Running language server" << std::endl;
|
||||||
IndexerConfig config;
|
IndexerConfig config;
|
||||||
LanguageServerMain(&config);
|
LanguageServerMain(&config, &waiter);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -3,29 +3,67 @@
|
|||||||
#include <optional.h>
|
#include <optional.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
|
||||||
// TODO: cleanup includes.
|
// TODO: cleanup includes.
|
||||||
|
|
||||||
|
struct BaseThreadQueue {
|
||||||
|
virtual bool IsEmpty() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MultiQueueWaiter {
|
||||||
|
std::mutex m;
|
||||||
|
std::condition_variable cv;
|
||||||
|
|
||||||
|
bool HasState(std::initializer_list<BaseThreadQueue*> queues) {
|
||||||
|
for (BaseThreadQueue* queue : queues) {
|
||||||
|
if (!queue->IsEmpty())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wait(std::initializer_list<BaseThreadQueue*> queues) {
|
||||||
|
// We cannot have a single condition variable wait on all of the different
|
||||||
|
// mutexes, so we have a global condition variable that every queue will
|
||||||
|
// notify. After it is notified we check to see if any of the queues have
|
||||||
|
// data; if they do, we return.
|
||||||
|
//
|
||||||
|
// We repoll every 5 seconds because it's not possible to atomically check
|
||||||
|
// the state of every queue and then setup the condition variable. So, if
|
||||||
|
// Wait() is called, HasState() returns false, and then in the time after
|
||||||
|
// HasState() is called data gets posted but before we begin waiting for
|
||||||
|
// the condition variable, we will miss the notification. The timeout of 5
|
||||||
|
// means that if this happens we will delay operation for 5 seconds.
|
||||||
|
|
||||||
|
while (!HasState(queues)) {
|
||||||
|
std::unique_lock<std::mutex> l(m);
|
||||||
|
cv.wait_for(l, std::chrono::seconds(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// A threadsafe-queue. http://stackoverflow.com/a/16075550
|
// A threadsafe-queue. http://stackoverflow.com/a/16075550
|
||||||
template <class T>
|
template <class T>
|
||||||
class ThreadedQueue {
|
struct ThreadedQueue : public BaseThreadQueue {
|
||||||
public:
|
public:
|
||||||
|
ThreadedQueue(MultiQueueWaiter* waiter) : waiter_(waiter) {}
|
||||||
|
|
||||||
// Add an element to the front of the queue.
|
// Add an element to the front of the queue.
|
||||||
void PriorityEnqueue(T&& t) {
|
void PriorityEnqueue(T&& t) {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
priority_.push(std::move(t));
|
priority_.push(std::move(t));
|
||||||
cv_.notify_one();
|
waiter_->cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an element to the queue.
|
// Add an element to the queue.
|
||||||
void Enqueue(T&& t) {
|
void Enqueue(T&& t) {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
queue_.push(std::move(t));
|
queue_.push(std::move(t));
|
||||||
cv_.notify_one();
|
waiter_->cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return all elements in the queue.
|
// Return all elements in the queue.
|
||||||
@ -45,6 +83,12 @@ public:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsEmpty() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return queue_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// Get the "front"-element.
|
// Get the "front"-element.
|
||||||
// If the queue is empty, wait untill an element is avaiable.
|
// If the queue is empty, wait untill an element is avaiable.
|
||||||
T Dequeue() {
|
T Dequeue() {
|
||||||
@ -64,6 +108,7 @@ public:
|
|||||||
queue_.pop();
|
queue_.pop();
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Get the first element from the queue without blocking. Returns a null
|
// Get the first element from the queue without blocking. Returns a null
|
||||||
// value if the queue is empty.
|
// value if the queue is empty.
|
||||||
@ -85,7 +130,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::queue<T> priority_;
|
std::queue<T> priority_;
|
||||||
std::queue<T> queue_;
|
|
||||||
mutable std::mutex mutex_;
|
mutable std::mutex mutex_;
|
||||||
std::condition_variable cv_;
|
std::queue<T> queue_;
|
||||||
|
MultiQueueWaiter* waiter_;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user