mirror of
https://github.com/MaskRay/ccls.git
synced 2025-02-21 16:09:40 +00:00
WIP
This commit is contained in:
parent
2f730efd41
commit
4cec26ae12
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
#include "third_party/tiny-process-library/process.hpp"
|
#include "third_party/tiny-process-library/process.hpp"
|
||||||
|
|
||||||
#define DOCTEST_CONFIG_IMPLEMENT
|
|
||||||
#include "third_party/doctest/doctest/doctest.h"
|
#include "third_party/doctest/doctest/doctest.h"
|
||||||
|
|
||||||
#include <rapidjson/istreamwrapper.h>
|
#include <rapidjson/istreamwrapper.h>
|
||||||
@ -1026,10 +1025,6 @@ void PreMain() {
|
|||||||
MessageRegistry::instance()->Register<In_WorkspaceSymbolRequest>();
|
MessageRegistry::instance()->Register<In_WorkspaceSymbolRequest>();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("helo") {
|
|
||||||
CHECK(1 == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
bool loop = false;
|
bool loop = false;
|
||||||
while (loop)
|
while (loop)
|
||||||
|
2
doctest_impl.cc
Normal file
2
doctest_impl.cc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#define DOCTEST_CONFIG_IMPLEMENT
|
||||||
|
#include "third_party/doctest/doctest/doctest.h"
|
122
ipc.cc
122
ipc.cc
@ -2,44 +2,48 @@
|
|||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include "third_party/doctest/doctest/doctest.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// The absolute smallest partial payload we should send. This must be >0, ie, 1 is the
|
// The absolute smallest partial payload we should send. This must be >0, ie, 1 is the
|
||||||
// minimum. Keep a reasonably high value so we don't send needlessly send tiny payloads.
|
// minimum. Keep a reasonably high value so we don't send needlessly send tiny payloads.
|
||||||
const int kMinimumPartialPayloadSize = 128;
|
const int kMinimumPartialPayloadSize = 128;
|
||||||
|
|
||||||
// JSON-encoded message that is passed across shared memory.
|
const int kBufferSize = 1024 * 1024 * 32; // number of chars/bytes (32mb) in the message buffer.
|
||||||
//
|
|
||||||
// Messages are funky objects. They contain potentially variable amounts of
|
|
||||||
// data and are passed between processes. This means that they need to be
|
|
||||||
// fully relocatable, ie, it is possible to memmove them in memory to a
|
|
||||||
// completely different address.
|
|
||||||
struct JsonMessage {
|
|
||||||
IpcId ipc_id;
|
|
||||||
int partial_message_id;
|
|
||||||
bool has_more_chunks;
|
|
||||||
size_t payload_size;
|
|
||||||
void* payload() {
|
|
||||||
return reinterpret_cast<char*>(this) + sizeof(JsonMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Setup(IpcId ipc_id, int partial_message_id, bool has_more_chunks, size_t payload_size, const char* payload) {
|
// JSON-encoded message that is passed across shared memory.
|
||||||
this->ipc_id = ipc_id;
|
//
|
||||||
this->partial_message_id = partial_message_id;
|
// Messages are funky objects. They contain potentially variable amounts of
|
||||||
this->has_more_chunks = has_more_chunks;
|
// data and are passed between processes. This means that they need to be
|
||||||
this->payload_size = payload_size;
|
// fully relocatable, ie, it is possible to memmove them in memory to a
|
||||||
|
// completely different address.
|
||||||
char* payload_dest = reinterpret_cast<char*>(this) + sizeof(JsonMessage);
|
struct JsonMessage {
|
||||||
memcpy(payload_dest, payload, payload_size);
|
IpcId ipc_id;
|
||||||
}
|
int partial_message_id;
|
||||||
};
|
bool has_more_chunks;
|
||||||
|
size_t payload_size;
|
||||||
std::string NameToServerName(const std::string& name) {
|
void* payload() {
|
||||||
return name + "server";
|
return reinterpret_cast<char*>(this) + sizeof(JsonMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NameToClientName(const std::string& name, int client_id) {
|
void Setup(IpcId ipc_id, int partial_message_id, bool has_more_chunks, size_t payload_size, const char* payload) {
|
||||||
return name + "client" + std::to_string(client_id);
|
this->ipc_id = ipc_id;
|
||||||
|
this->partial_message_id = partial_message_id;
|
||||||
|
this->has_more_chunks = has_more_chunks;
|
||||||
|
this->payload_size = payload_size;
|
||||||
|
|
||||||
|
char* payload_dest = reinterpret_cast<char*>(this) + sizeof(JsonMessage);
|
||||||
|
memcpy(payload_dest, payload, payload_size);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string NameToServerName(const std::string& name) {
|
||||||
|
return name + "server";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NameToClientName(const std::string& name, int client_id) {
|
||||||
|
return name + "client" + std::to_string(client_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcRegistry* IpcRegistry::instance_ = nullptr;
|
IpcRegistry* IpcRegistry::instance_ = nullptr;
|
||||||
@ -206,14 +210,14 @@ void IpcDirectionalChannel::RemoveResizableBuffer(int id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IpcDirectionalChannel::IpcDirectionalChannel(const std::string& name, bool initialize_shared_memory) {
|
IpcDirectionalChannel::IpcDirectionalChannel(const std::string& name, bool initialize_shared_memory) {
|
||||||
shared = CreatePlatformSharedMemory(name + "memory");
|
shared = CreatePlatformSharedMemory(name + "memory", kBufferSize);
|
||||||
mutex = CreatePlatformMutex(name + "mutex");
|
mutex = CreatePlatformMutex(name + "mutex");
|
||||||
local = std::unique_ptr<char>(new char[shmem_size]);
|
local = std::unique_ptr<char>(new char[kBufferSize]);
|
||||||
|
|
||||||
// TODO: connecting a client will allocate reset shared state on the
|
// TODO: connecting a client will allocate reset shared state on the
|
||||||
// buffer. We need to store if we "initialized".
|
// buffer. We need to store if we "initialized".
|
||||||
shared_buffer = MakeUnique<MessageBuffer>(shared->shared, shmem_size, initialize_shared_memory);
|
shared_buffer = MakeUnique<MessageBuffer>(shared->data, kBufferSize, initialize_shared_memory);
|
||||||
local_buffer = MakeUnique<MessageBuffer>(local.get(), shmem_size, true /*initialize*/);
|
local_buffer = MakeUnique<MessageBuffer>(local.get(), kBufferSize, true /*initialize*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcDirectionalChannel::~IpcDirectionalChannel() {}
|
IpcDirectionalChannel::~IpcDirectionalChannel() {}
|
||||||
@ -249,7 +253,7 @@ void IpcDispatch(PlatformMutex* mutex, std::function<DispatchResult()> action) {
|
|||||||
|
|
||||||
void IpcDirectionalChannel::PushMessage(IpcMessage* message) {
|
void IpcDirectionalChannel::PushMessage(IpcMessage* message) {
|
||||||
assert(message->ipc_id != IpcId::Invalid);
|
assert(message->ipc_id != IpcId::Invalid);
|
||||||
assert(shmem_size > sizeof(JsonMessage) + kMinimumPartialPayloadSize);
|
assert(kBufferSize > sizeof(JsonMessage) + kMinimumPartialPayloadSize);
|
||||||
|
|
||||||
rapidjson::StringBuffer output;
|
rapidjson::StringBuffer output;
|
||||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(output);
|
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(output);
|
||||||
@ -331,8 +335,8 @@ std::vector<std::unique_ptr<IpcMessage>> IpcDirectionalChannel::TakeMessages() {
|
|||||||
// posting data as soon as possible.
|
// posting data as soon as possible.
|
||||||
{
|
{
|
||||||
std::unique_ptr<PlatformScopedMutexLock> lock = CreatePlatformScopedMutexLock(mutex.get());
|
std::unique_ptr<PlatformScopedMutexLock> lock = CreatePlatformScopedMutexLock(mutex.get());
|
||||||
assert(shared_buffer->metadata()->bytes_used <= shmem_size);
|
assert(shared_buffer->metadata()->bytes_used <= kBufferSize);
|
||||||
memcpy(local.get(), shared->shared, sizeof(MessageBuffer::Metadata) + shared_buffer->metadata()->bytes_used);
|
memcpy(local.get(), shared->data, sizeof(MessageBuffer::Metadata) + shared_buffer->metadata()->bytes_used);
|
||||||
shared_buffer->metadata()->bytes_used = 0;
|
shared_buffer->metadata()->bytes_used = 0;
|
||||||
shared_buffer->free_message()->ipc_id = IpcId::Invalid;
|
shared_buffer->free_message()->ipc_id = IpcId::Invalid;
|
||||||
}
|
}
|
||||||
@ -383,7 +387,7 @@ std::vector<std::unique_ptr<IpcMessage>> IpcServer::TakeMessages() {
|
|||||||
|
|
||||||
IpcClient::IpcClient(const std::string& name, int client_id)
|
IpcClient::IpcClient(const std::string& name, int client_id)
|
||||||
: server_(NameToServerName(name), false /*initialize_shared_memory*/),
|
: server_(NameToServerName(name), false /*initialize_shared_memory*/),
|
||||||
client_(NameToClientName(name, client_id), false /*initialize_shared_memory*/) {}
|
client_(NameToClientName(name, client_id), false /*initialize_shared_memory*/) {}
|
||||||
|
|
||||||
void IpcClient::SendToServer(IpcMessage* message) {
|
void IpcClient::SendToServer(IpcMessage* message) {
|
||||||
server_.PushMessage(message);
|
server_.PushMessage(message);
|
||||||
@ -392,3 +396,41 @@ void IpcClient::SendToServer(IpcMessage* message) {
|
|||||||
std::vector<std::unique_ptr<IpcMessage>> IpcClient::TakeMessages() {
|
std::vector<std::unique_ptr<IpcMessage>> IpcClient::TakeMessages() {
|
||||||
return client_.TakeMessages();
|
return client_.TakeMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct TestIpcMessage : IpcMessage {
|
||||||
|
T data;
|
||||||
|
|
||||||
|
TestIpcMessage() : IpcMessage(IpcId::Test) {}
|
||||||
|
~TestIpcMessage() override {}
|
||||||
|
|
||||||
|
// IpcMessage:
|
||||||
|
void Serialize(Writer& writer) override {
|
||||||
|
Reflect(writer, data);
|
||||||
|
}
|
||||||
|
void Deserialize(Reader& reader) override {
|
||||||
|
Reflect(reader, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if false
|
||||||
|
TEST_CASE("foo") {
|
||||||
|
IpcRegistry::instance()->Register<TestIpcMessage<std::string>>(IpcId::Test);
|
||||||
|
|
||||||
|
IpcDirectionalChannel channel0("indexertestmemory", true /*initialize_shared_memory*/);
|
||||||
|
IpcDirectionalChannel channel1("indexertestmemory", false /*initialize_shared_memory*/);
|
||||||
|
|
||||||
|
TestIpcMessage<std::string> m;
|
||||||
|
m.data = "hey there";
|
||||||
|
|
||||||
|
channel0.PushMessage(&m);
|
||||||
|
std::vector<std::unique_ptr<IpcMessage>> messages = channel1.TakeMessages();
|
||||||
|
REQUIRE(messages.size() == 1);
|
||||||
|
REQUIRE(messages[0]->ipc_id == m.ipc_id);
|
||||||
|
REQUIRE(reinterpret_cast<TestIpcMessage<std::string>*>(messages[0].get())->data == m.data);
|
||||||
|
}
|
||||||
|
#endif
|
6
ipc.h
6
ipc.h
@ -9,7 +9,7 @@
|
|||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/prettywriter.h>
|
#include <rapidjson/prettywriter.h>
|
||||||
|
|
||||||
#include "platform.h"
|
#include "src/platform.h"
|
||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
@ -38,7 +38,9 @@ enum class IpcId : int {
|
|||||||
CodeLensResolveRequest,
|
CodeLensResolveRequest,
|
||||||
CodeLensResolveResponse,
|
CodeLensResolveResponse,
|
||||||
WorkspaceSymbolsRequest,
|
WorkspaceSymbolsRequest,
|
||||||
WorkspaceSymbolsResponse
|
WorkspaceSymbolsResponse,
|
||||||
|
|
||||||
|
Test
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
#ifdef _MSC_VER
|
|
||||||
#include "platform.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <string>
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
struct PlatformMutexWin : public PlatformMutex {
|
|
||||||
HANDLE raw_mutex = INVALID_HANDLE_VALUE;
|
|
||||||
|
|
||||||
PlatformMutexWin(const std::string& name) {
|
|
||||||
raw_mutex = CreateMutex(nullptr, false /*initial_owner*/, name.c_str());
|
|
||||||
assert(GetLastError() != ERROR_INVALID_HANDLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
~PlatformMutexWin() override {
|
|
||||||
ReleaseMutex(raw_mutex);
|
|
||||||
raw_mutex = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PlatformScopedMutexLockWin : public PlatformScopedMutexLock {
|
|
||||||
HANDLE raw_mutex;
|
|
||||||
|
|
||||||
PlatformScopedMutexLockWin(HANDLE raw_mutex) : raw_mutex(raw_mutex) {
|
|
||||||
WaitForSingleObject(raw_mutex, INFINITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
~PlatformScopedMutexLockWin() override {
|
|
||||||
ReleaseMutex(raw_mutex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PlatformSharedMemoryWin : public PlatformSharedMemory {
|
|
||||||
HANDLE shmem_;
|
|
||||||
|
|
||||||
PlatformSharedMemoryWin(const std::string& name) {
|
|
||||||
this->name = name;
|
|
||||||
|
|
||||||
shmem_ = CreateFileMapping(
|
|
||||||
INVALID_HANDLE_VALUE,
|
|
||||||
NULL,
|
|
||||||
PAGE_READWRITE,
|
|
||||||
0,
|
|
||||||
shmem_size,
|
|
||||||
name.c_str()
|
|
||||||
);
|
|
||||||
|
|
||||||
shared = MapViewOfFile(shmem_, FILE_MAP_ALL_ACCESS, 0, 0, shmem_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
~PlatformSharedMemoryWin() override {
|
|
||||||
UnmapViewOfFile(shared);
|
|
||||||
shared = nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<PlatformMutex> CreatePlatformMutex(const std::string& name) {
|
|
||||||
return MakeUnique<PlatformMutexWin>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<PlatformScopedMutexLock> CreatePlatformScopedMutexLock(PlatformMutex* mutex) {
|
|
||||||
return MakeUnique<PlatformScopedMutexLockWin>(static_cast<PlatformMutexWin*>(mutex)->raw_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<PlatformSharedMemory> CreatePlatformSharedMemory(const std::string& name) {
|
|
||||||
return MakeUnique<PlatformSharedMemoryWin>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// See http://stackoverflow.com/a/19535628
|
|
||||||
std::string GetWorkingDirectory() {
|
|
||||||
char result[MAX_PATH];
|
|
||||||
return std::string(result, GetModuleFileName(NULL, result, MAX_PATH));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// linux
|
|
||||||
#include <string>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
std::string getexepath() {
|
|
||||||
char result[ PATH_MAX ];
|
|
||||||
ssize_t count = readlink( "/proc/self/exe", result, PATH_MAX );
|
|
||||||
return std::string( result, (count > 0) ? count : 0 );
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
#endif
|
|
120
src/buffer.cc
Normal file
120
src/buffer.cc
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "../utils.h"
|
||||||
|
#include "../third_party/doctest/doctest/doctest.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct ScopedLockLocal : public ScopedLock {
|
||||||
|
ScopedLockLocal(std::mutex& mutex) : guard(mutex) {}
|
||||||
|
std::lock_guard<std::mutex> guard;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BufferLocal : public Buffer {
|
||||||
|
explicit BufferLocal(size_t capacity) {
|
||||||
|
this->data = malloc(capacity);
|
||||||
|
this->capacity = capacity;
|
||||||
|
}
|
||||||
|
~BufferLocal() override {
|
||||||
|
free(data);
|
||||||
|
data = nullptr;
|
||||||
|
capacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ScopedLock> WaitForExclusiveAccess() override {
|
||||||
|
return MakeUnique<ScopedLockLocal>(mutex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScopedLockPlatform : public ScopedLock {
|
||||||
|
ScopedLockPlatform(PlatformMutex* mutex)
|
||||||
|
: guard(CreatePlatformScopedMutexLock(mutex)) {}
|
||||||
|
|
||||||
|
std::unique_ptr<PlatformScopedMutexLock> guard;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BufferPlatform : public Buffer {
|
||||||
|
explicit BufferPlatform(const std::string& name, size_t capacity)
|
||||||
|
: memory_(CreatePlatformSharedMemory(name + "_mem", capacity)),
|
||||||
|
mutex_(CreatePlatformMutex(name + "_mtx")) {
|
||||||
|
this->data = memory_->data;
|
||||||
|
this->capacity = memory_->capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
~BufferPlatform() override {
|
||||||
|
data = nullptr;
|
||||||
|
capacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ScopedLock> WaitForExclusiveAccess() override {
|
||||||
|
return MakeUnique<ScopedLockPlatform>(mutex_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PlatformSharedMemory> memory_;
|
||||||
|
std::unique_ptr<PlatformMutex> mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<Buffer> Buffer::Create(size_t capacity) {
|
||||||
|
return MakeUnique<BufferLocal>(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Buffer> Buffer::CreateSharedBuffer(const std::string& name, size_t capacity) {
|
||||||
|
return MakeUnique<BufferPlatform>(name, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE("BufferLocal");
|
||||||
|
|
||||||
|
TEST_CASE("create") {
|
||||||
|
std::unique_ptr<Buffer> b = Buffer::Create(24);
|
||||||
|
REQUIRE(b->data);
|
||||||
|
REQUIRE(b->capacity == 24);
|
||||||
|
|
||||||
|
b = Buffer::CreateSharedBuffer("indexertest", 24);
|
||||||
|
REQUIRE(b->data);
|
||||||
|
REQUIRE(b->capacity == 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lock") {
|
||||||
|
auto buffers = {
|
||||||
|
Buffer::Create(sizeof(int)),
|
||||||
|
Buffer::CreateSharedBuffer("indexertest", sizeof(int))
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& b : buffers) {
|
||||||
|
int* data = reinterpret_cast<int*>(b->data);
|
||||||
|
*data = 0;
|
||||||
|
|
||||||
|
std::unique_ptr<std::thread> thread;
|
||||||
|
{
|
||||||
|
auto lock = b->WaitForExclusiveAccess();
|
||||||
|
*data = 1;
|
||||||
|
|
||||||
|
// Start a second thread, wait until it has attempted to acquire a lock.
|
||||||
|
volatile bool did_read = false;
|
||||||
|
thread = MakeUnique<std::thread>([&did_read, &b, &data]() {
|
||||||
|
did_read = true;
|
||||||
|
auto l = b->WaitForExclusiveAccess();
|
||||||
|
*data = 2;
|
||||||
|
});
|
||||||
|
while (!did_read)
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
|
||||||
|
// Verify lock acquisition is waiting.
|
||||||
|
REQUIRE(*data == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for thread to acquire lock, verify it writes to data.
|
||||||
|
thread->join();
|
||||||
|
REQUIRE(*data == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
30
src/buffer.h
Normal file
30
src/buffer.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct ScopedLock {
|
||||||
|
virtual ~ScopedLock() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Points to a generic block of memory. Note that |data| is relocatable, ie,
|
||||||
|
// multiple Buffer instantations may point to the same underlying block of
|
||||||
|
// memory but the data pointer has different values.
|
||||||
|
struct Buffer {
|
||||||
|
// Create a new buffer of the given capacity using process-local memory.
|
||||||
|
static std::unique_ptr<Buffer> Create(size_t capacity);
|
||||||
|
// Create a buffer pointing to memory shared across processes with the given
|
||||||
|
// capacity.
|
||||||
|
static std::unique_ptr<Buffer> CreateSharedBuffer(const std::string& name,
|
||||||
|
size_t capacity);
|
||||||
|
|
||||||
|
virtual ~Buffer() = default;
|
||||||
|
|
||||||
|
// Acquire a lock on the buffer, ie, become the only code that can read or
|
||||||
|
// write to it. The lock lasts so long as the returned object is alive.
|
||||||
|
virtual std::unique_ptr<ScopedLock> WaitForExclusiveAccess() = 0;
|
||||||
|
|
||||||
|
void* data = nullptr;
|
||||||
|
size_t capacity = 0;
|
||||||
|
};
|
||||||
|
|
37
src/message_queue.cc
Normal file
37
src/message_queue.cc
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "message_queue.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
struct MessageQueue::BufferMetadata {
|
||||||
|
// Total number of used bytes exluding the sizeof this metadata object.
|
||||||
|
void set_total_messages_byte_count(size_t used_bytes) {
|
||||||
|
total_message_bytes_ = used_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The total number of bytes in use.
|
||||||
|
size_t total_bytes_used_including_metadata() {
|
||||||
|
return total_message_bytes_ + sizeof(BufferMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The total number of bytes currently used for messages. This does not
|
||||||
|
// include the sizeof the buffer metadata.
|
||||||
|
size_t total_message_bytes() {
|
||||||
|
return total_message_bytes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t total_message_bytes_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
MessageQueue::MessageQueue(std::unique_ptr<Buffer> buffer, bool buffer_has_data) : buffer_(std::move(buffer)) {
|
||||||
|
if (!buffer_has_data)
|
||||||
|
new(buffer_->data) BufferMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageQueue::Enqueue(const Message& message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageQueue::BufferMetadata* MessageQueue::Metadata() {
|
||||||
|
return reinterpret_cast<BufferMetadata*>(buffer_->data);
|
||||||
|
}
|
75
src/message_queue.h
Normal file
75
src/message_queue.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
struct Message {
|
||||||
|
// Unique message identifier.
|
||||||
|
uint8_t message_id;
|
||||||
|
|
||||||
|
// Total size of the message (including metadata that this object stores).
|
||||||
|
size_t total_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A MessageQueue is a FIFO container storing messages in an arbitrary memory
|
||||||
|
// buffer.
|
||||||
|
// - Multiple separate MessageQueues instantiations can point to the
|
||||||
|
// same underlying buffer
|
||||||
|
// - Buffer is fully relocatable, ie, it can have multiple different
|
||||||
|
// addresses (as is the case for memory shared across processes).
|
||||||
|
struct MessageQueue {
|
||||||
|
// Create a new MessageQueue using |buffer| as the backing data storage.
|
||||||
|
// This does *not* take ownership over the memory stored in |buffer|.
|
||||||
|
//
|
||||||
|
// If |buffer_has_data| is true, then it is assumed that |buffer| contains
|
||||||
|
// data and has already been initialized. It is a perfectly acceptable
|
||||||
|
// use-case to have multiple completely separate MessageQueue
|
||||||
|
// instantiations pointing to the same memory.
|
||||||
|
explicit MessageQueue(std::unique_ptr<Buffer> buffer, bool buffer_has_data);
|
||||||
|
MessageQueue(const MessageQueue&) = delete;
|
||||||
|
|
||||||
|
// Enqueue a message to the queue. This will wait until there is room in
|
||||||
|
// queue. If the message is too large to fit into the queue, this will
|
||||||
|
// wait until the message has been fully sent, which may involve multiple
|
||||||
|
// IPC roundtrips (ie, Enqueue -> DequeueAll -> Enqueue) - so this method
|
||||||
|
// may take a long time to run.
|
||||||
|
//
|
||||||
|
// TODO: Consider copying message memory to a temporary buffer and running
|
||||||
|
// enqueues on a worker thread.
|
||||||
|
void Enqueue(const Message& message);
|
||||||
|
|
||||||
|
// Take all messages from the queue.
|
||||||
|
//
|
||||||
|
// note:
|
||||||
|
// We could make this allocation free by returning raw pointers to the
|
||||||
|
// internal process-local buffer, but that is pretty haphazard and likely
|
||||||
|
// to cause a very confusing crash. The extra memory allocations here from
|
||||||
|
// unique_ptr going to make a performance difference.
|
||||||
|
std::vector<std::unique_ptr<Message>> DequeueAll();
|
||||||
|
|
||||||
|
// Take the first available message from the queue.
|
||||||
|
std::unique_ptr<Message> DequeueFirst();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct BufferMetadata;
|
||||||
|
|
||||||
|
BufferMetadata* Metadata();
|
||||||
|
|
||||||
|
std::unique_ptr<Buffer> buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// TODO: We convert IpcMessage <-> Message as a user-level operation.
|
||||||
|
// MessageQueue doesn't know about IpcMessage.
|
||||||
|
struct IpcMessage {
|
||||||
|
std::unique_ptr<Message> ToMessage();
|
||||||
|
void BuildFromMessage(std::unique_ptr<Message> message);
|
||||||
|
|
||||||
|
// Serialize/deserialize the message.
|
||||||
|
virtual void Serialize(Writer& writer) = 0;
|
||||||
|
virtual void Deserialize(Reader& reader) = 0;
|
||||||
|
};
|
||||||
|
*/
|
39
src/platform.cc
Normal file
39
src/platform.cc
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "../third_party/doctest/doctest/doctest.h"
|
||||||
|
|
||||||
|
|
||||||
|
TEST_SUITE("Platform");
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("Mutex lock/unlock (single process)") {
|
||||||
|
auto m1 = CreatePlatformMutex("indexer-platformmutexttest");
|
||||||
|
auto l1 = CreatePlatformScopedMutexLock(m1.get());
|
||||||
|
auto m2 = CreatePlatformMutex("indexer-platformmutexttest");
|
||||||
|
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
volatile bool did_run = false;
|
||||||
|
std::thread t([&]() {
|
||||||
|
did_run = true;
|
||||||
|
auto l2 = CreatePlatformScopedMutexLock(m2.get());
|
||||||
|
value = 1;
|
||||||
|
});
|
||||||
|
while (!did_run)
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
|
||||||
|
// Other thread has had a chance to run, but it should not have
|
||||||
|
// written to value yet (ie, it should be waiting).
|
||||||
|
REQUIRE(value == 0);
|
||||||
|
|
||||||
|
// Release the lock, wait for other thread to finish. Verify it
|
||||||
|
// wrote the expected value.
|
||||||
|
l1.reset();
|
||||||
|
t.join();
|
||||||
|
REQUIRE(value == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
@ -11,14 +11,13 @@ struct PlatformScopedMutexLock {
|
|||||||
};
|
};
|
||||||
struct PlatformSharedMemory {
|
struct PlatformSharedMemory {
|
||||||
virtual ~PlatformSharedMemory() {}
|
virtual ~PlatformSharedMemory() {}
|
||||||
void* shared;
|
void* data;
|
||||||
|
size_t capacity;
|
||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const int shmem_size = 1024 * 1024 * 32; // number of chars/bytes (32mb)
|
|
||||||
|
|
||||||
std::unique_ptr<PlatformMutex> CreatePlatformMutex(const std::string& name);
|
std::unique_ptr<PlatformMutex> CreatePlatformMutex(const std::string& name);
|
||||||
std::unique_ptr<PlatformScopedMutexLock> CreatePlatformScopedMutexLock(PlatformMutex* mutex);
|
std::unique_ptr<PlatformScopedMutexLock> CreatePlatformScopedMutexLock(PlatformMutex* mutex);
|
||||||
std::unique_ptr<PlatformSharedMemory> CreatePlatformSharedMemory(const std::string& name);
|
std::unique_ptr<PlatformSharedMemory> CreatePlatformSharedMemory(const std::string& name, size_t size);
|
||||||
|
|
||||||
std::string GetWorkingDirectory();
|
std::string GetWorkingDirectory();
|
@ -1,3 +1,4 @@
|
|||||||
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@ -123,3 +124,4 @@ std::unique_ptr<PlatformSharedMemory> CreatePlatformSharedMemory(const std::stri
|
|||||||
std::string name2 = "/" + name;
|
std::string name2 = "/" + name;
|
||||||
return MakeUnique<PlatformSharedMemoryLinux>(name2);
|
return MakeUnique<PlatformSharedMemoryLinux>(name2);
|
||||||
}
|
}
|
||||||
|
#endif
|
113
src/platform_win.cc
Normal file
113
src/platform_win.cc
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#if defined(_WIN32)
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include "../utils.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
DWORD CheckForError(std::vector<DWORD> allow) {
|
||||||
|
DWORD error = GetLastError();
|
||||||
|
if (error == ERROR_SUCCESS || std::find(allow.begin(), allow.end(), error) != allow.end())
|
||||||
|
return error;
|
||||||
|
|
||||||
|
// See http://stackoverflow.com/a/17387176
|
||||||
|
LPSTR message_buffer = nullptr;
|
||||||
|
size_t size = FormatMessageA(
|
||||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message_buffer, 0, NULL);
|
||||||
|
std::string message(message_buffer, size);
|
||||||
|
LocalFree(message_buffer);
|
||||||
|
|
||||||
|
std::cerr << "Windows error code=" << error << ", message=" << message << std::endl;
|
||||||
|
assert(false); // debugger break
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlatformMutexWin : public PlatformMutex {
|
||||||
|
HANDLE raw_mutex = INVALID_HANDLE_VALUE;
|
||||||
|
|
||||||
|
PlatformMutexWin(const std::string& name) {
|
||||||
|
std::cerr << "[win] Creating mutex with name " << name << std::endl;
|
||||||
|
raw_mutex = CreateMutex(nullptr, false /*initial_owner*/, name.c_str());
|
||||||
|
CheckForError({ ERROR_ALREADY_EXISTS });
|
||||||
|
}
|
||||||
|
|
||||||
|
~PlatformMutexWin() override {
|
||||||
|
CloseHandle(raw_mutex);
|
||||||
|
CheckForError({} /*allow*/);
|
||||||
|
raw_mutex = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlatformScopedMutexLockWin : public PlatformScopedMutexLock {
|
||||||
|
HANDLE raw_mutex;
|
||||||
|
|
||||||
|
PlatformScopedMutexLockWin(HANDLE raw_mutex) : raw_mutex(raw_mutex) {
|
||||||
|
DWORD result = WaitForSingleObject(raw_mutex, INFINITE);
|
||||||
|
assert(result != WAIT_FAILED);
|
||||||
|
CheckForError({} /*allow*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
~PlatformScopedMutexLockWin() override {
|
||||||
|
ReleaseMutex(raw_mutex);
|
||||||
|
CheckForError({} /*allow*/);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlatformSharedMemoryWin : public PlatformSharedMemory {
|
||||||
|
HANDLE shmem_;
|
||||||
|
|
||||||
|
PlatformSharedMemoryWin(const std::string& name, size_t capacity) {
|
||||||
|
std::cerr << "[win] Creating shared memory with name " << name << " and capacity " << capacity << std::endl;
|
||||||
|
this->name = name;
|
||||||
|
|
||||||
|
shmem_ = CreateFileMapping(
|
||||||
|
INVALID_HANDLE_VALUE,
|
||||||
|
NULL,
|
||||||
|
PAGE_READWRITE,
|
||||||
|
0,
|
||||||
|
capacity,
|
||||||
|
name.c_str()
|
||||||
|
);
|
||||||
|
CheckForError({ ERROR_ALREADY_EXISTS } /*allow*/);
|
||||||
|
|
||||||
|
data = MapViewOfFile(shmem_, FILE_MAP_ALL_ACCESS, 0, 0, capacity);
|
||||||
|
CheckForError({ ERROR_ALREADY_EXISTS } /*allow*/);
|
||||||
|
|
||||||
|
this->capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
~PlatformSharedMemoryWin() override {
|
||||||
|
UnmapViewOfFile(data);
|
||||||
|
CheckForError({} /*allow*/);
|
||||||
|
|
||||||
|
data = nullptr;
|
||||||
|
capacity = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<PlatformMutex> CreatePlatformMutex(const std::string& name) {
|
||||||
|
return MakeUnique<PlatformMutexWin>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PlatformScopedMutexLock> CreatePlatformScopedMutexLock(PlatformMutex* mutex) {
|
||||||
|
return MakeUnique<PlatformScopedMutexLockWin>(static_cast<PlatformMutexWin*>(mutex)->raw_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PlatformSharedMemory> CreatePlatformSharedMemory(const std::string& name, size_t size) {
|
||||||
|
return MakeUnique<PlatformSharedMemoryWin>(name, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See http://stackoverflow.com/a/19535628
|
||||||
|
std::string GetWorkingDirectory() {
|
||||||
|
char result[MAX_PATH];
|
||||||
|
return std::string(result, GetModuleFileName(NULL, result, MAX_PATH));
|
||||||
|
}
|
||||||
|
#endif
|
100
src/resizable_buffer.cc
Normal file
100
src/resizable_buffer.cc
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#include "resizable_buffer.h"
|
||||||
|
|
||||||
|
#include "../third_party/doctest/doctest/doctest.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const size_t kInitialCapacity = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizableBuffer::ResizableBuffer() {
|
||||||
|
buffer = malloc(kInitialCapacity);
|
||||||
|
size = 0;
|
||||||
|
capacity_ = kInitialCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizableBuffer::~ResizableBuffer() {
|
||||||
|
free(buffer);
|
||||||
|
size = 0;
|
||||||
|
capacity_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizableBuffer::Append(void* content, size_t content_size) {
|
||||||
|
assert(capacity_ >= 0);
|
||||||
|
|
||||||
|
size_t new_size = size + content_size;
|
||||||
|
|
||||||
|
// Grow buffer capacity if needed.
|
||||||
|
if (new_size >= capacity_) {
|
||||||
|
size_t new_capacity = capacity_ * 2;
|
||||||
|
while (new_size >= new_capacity)
|
||||||
|
new_capacity *= 2;
|
||||||
|
void* new_memory = malloc(new_capacity);
|
||||||
|
assert(size < capacity_);
|
||||||
|
memcpy(new_memory, buffer, size);
|
||||||
|
free(buffer);
|
||||||
|
buffer = new_memory;
|
||||||
|
capacity_ = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append new content into memory.
|
||||||
|
memcpy(reinterpret_cast<uint8_t*>(buffer) + size, content, content_size);
|
||||||
|
size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizableBuffer::Reset() {
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE("ResizableBuffer");
|
||||||
|
|
||||||
|
TEST_CASE("buffer starts with zero size") {
|
||||||
|
ResizableBuffer b;
|
||||||
|
REQUIRE(b.buffer);
|
||||||
|
REQUIRE(b.size == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("append and reset") {
|
||||||
|
int content = 1;
|
||||||
|
ResizableBuffer b;
|
||||||
|
|
||||||
|
b.Append(&content, sizeof(content));
|
||||||
|
REQUIRE(b.size == sizeof(content));
|
||||||
|
|
||||||
|
b.Append(&content, sizeof(content));
|
||||||
|
REQUIRE(b.size == (2 * sizeof(content)));
|
||||||
|
|
||||||
|
b.Reset();
|
||||||
|
REQUIRE(b.size == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("appended content is copied into buffer w/ resize") {
|
||||||
|
int content = 0;
|
||||||
|
ResizableBuffer b;
|
||||||
|
|
||||||
|
// go past kInitialCapacity to verify resize works too
|
||||||
|
while (b.size < kInitialCapacity * 2) {
|
||||||
|
b.Append(&content, sizeof(content));
|
||||||
|
content += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < content; ++i)
|
||||||
|
REQUIRE(i == *(reinterpret_cast<int*>(b.buffer) + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("reset does not reallocate") {
|
||||||
|
ResizableBuffer b;
|
||||||
|
|
||||||
|
while (b.size < kInitialCapacity)
|
||||||
|
b.Append(&b, sizeof(b));
|
||||||
|
|
||||||
|
void* buffer = b.buffer;
|
||||||
|
b.Reset();
|
||||||
|
REQUIRE(b.buffer == buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
21
src/resizable_buffer.h
Normal file
21
src/resizable_buffer.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Points to a generic block of memory that can be resized. This class owns
|
||||||
|
// and has the only pointer to the underlying memory buffer.
|
||||||
|
struct ResizableBuffer {
|
||||||
|
ResizableBuffer();
|
||||||
|
ResizableBuffer(const ResizableBuffer&) = delete;
|
||||||
|
~ResizableBuffer();
|
||||||
|
|
||||||
|
void Append(void* content, size_t content_size);
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
// Buffer content.
|
||||||
|
void* buffer;
|
||||||
|
// Number of bytes in |buffer|. Note that the actual buffer may be larger
|
||||||
|
// than |size|.
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t capacity_;
|
||||||
|
};
|
2
test.cc
2
test.cc
@ -3,7 +3,7 @@
|
|||||||
#include "indexer.h"
|
#include "indexer.h"
|
||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "platform.h"
|
#include "src/platform.h"
|
||||||
|
|
||||||
void Write(const std::vector<std::string>& strs) {
|
void Write(const std::vector<std::string>& strs) {
|
||||||
for (const std::string& str : strs)
|
for (const std::string& str : strs)
|
||||||
|
Loading…
Reference in New Issue
Block a user