mirror of
https://github.com/MaskRay/ccls.git
synced 2025-02-20 15:40:57 +00:00
WIP
This commit is contained in:
parent
2f730efd41
commit
4cec26ae12
@ -13,7 +13,6 @@
|
||||
|
||||
#include "third_party/tiny-process-library/process.hpp"
|
||||
|
||||
#define DOCTEST_CONFIG_IMPLEMENT
|
||||
#include "third_party/doctest/doctest/doctest.h"
|
||||
|
||||
#include <rapidjson/istreamwrapper.h>
|
||||
@ -1026,10 +1025,6 @@ void PreMain() {
|
||||
MessageRegistry::instance()->Register<In_WorkspaceSymbolRequest>();
|
||||
}
|
||||
|
||||
TEST_CASE("helo") {
|
||||
CHECK(1 == 1);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
bool loop = false;
|
||||
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 "utils.h"
|
||||
|
||||
#include "third_party/doctest/doctest/doctest.h"
|
||||
|
||||
namespace {
|
||||
// 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.
|
||||
const int kMinimumPartialPayloadSize = 128;
|
||||
// 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.
|
||||
const int kMinimumPartialPayloadSize = 128;
|
||||
|
||||
// JSON-encoded message that is passed across shared memory.
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
const int kBufferSize = 1024 * 1024 * 32; // number of chars/bytes (32mb) in the message buffer.
|
||||
|
||||
void Setup(IpcId ipc_id, int partial_message_id, bool has_more_chunks, size_t payload_size, const char* payload) {
|
||||
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";
|
||||
// JSON-encoded message that is passed across shared memory.
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
|
||||
std::string NameToClientName(const std::string& name, int client_id) {
|
||||
return name + "client" + std::to_string(client_id);
|
||||
void Setup(IpcId ipc_id, int partial_message_id, bool has_more_chunks, size_t payload_size, const char* payload) {
|
||||
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;
|
||||
@ -206,14 +210,14 @@ void IpcDirectionalChannel::RemoveResizableBuffer(int id) {
|
||||
}
|
||||
|
||||
IpcDirectionalChannel::IpcDirectionalChannel(const std::string& name, bool initialize_shared_memory) {
|
||||
shared = CreatePlatformSharedMemory(name + "memory");
|
||||
shared = CreatePlatformSharedMemory(name + "memory", kBufferSize);
|
||||
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
|
||||
// buffer. We need to store if we "initialized".
|
||||
shared_buffer = MakeUnique<MessageBuffer>(shared->shared, shmem_size, initialize_shared_memory);
|
||||
local_buffer = MakeUnique<MessageBuffer>(local.get(), shmem_size, true /*initialize*/);
|
||||
shared_buffer = MakeUnique<MessageBuffer>(shared->data, kBufferSize, initialize_shared_memory);
|
||||
local_buffer = MakeUnique<MessageBuffer>(local.get(), kBufferSize, true /*initialize*/);
|
||||
}
|
||||
|
||||
IpcDirectionalChannel::~IpcDirectionalChannel() {}
|
||||
@ -249,7 +253,7 @@ void IpcDispatch(PlatformMutex* mutex, std::function<DispatchResult()> action) {
|
||||
|
||||
void IpcDirectionalChannel::PushMessage(IpcMessage* message) {
|
||||
assert(message->ipc_id != IpcId::Invalid);
|
||||
assert(shmem_size > sizeof(JsonMessage) + kMinimumPartialPayloadSize);
|
||||
assert(kBufferSize > sizeof(JsonMessage) + kMinimumPartialPayloadSize);
|
||||
|
||||
rapidjson::StringBuffer 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.
|
||||
{
|
||||
std::unique_ptr<PlatformScopedMutexLock> lock = CreatePlatformScopedMutexLock(mutex.get());
|
||||
assert(shared_buffer->metadata()->bytes_used <= shmem_size);
|
||||
memcpy(local.get(), shared->shared, sizeof(MessageBuffer::Metadata) + shared_buffer->metadata()->bytes_used);
|
||||
assert(shared_buffer->metadata()->bytes_used <= kBufferSize);
|
||||
memcpy(local.get(), shared->data, sizeof(MessageBuffer::Metadata) + shared_buffer->metadata()->bytes_used);
|
||||
shared_buffer->metadata()->bytes_used = 0;
|
||||
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)
|
||||
: 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) {
|
||||
server_.PushMessage(message);
|
||||
@ -392,3 +396,41 @@ void IpcClient::SendToServer(IpcMessage* message) {
|
||||
std::vector<std::unique_ptr<IpcMessage>> IpcClient::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/prettywriter.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "src/platform.h"
|
||||
#include "serializer.h"
|
||||
#include "utils.h"
|
||||
|
||||
@ -38,7 +38,9 @@ enum class IpcId : int {
|
||||
CodeLensResolveRequest,
|
||||
CodeLensResolveResponse,
|
||||
WorkspaceSymbolsRequest,
|
||||
WorkspaceSymbolsResponse
|
||||
WorkspaceSymbolsResponse,
|
||||
|
||||
Test
|
||||
};
|
||||
|
||||
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 {
|
||||
virtual ~PlatformSharedMemory() {}
|
||||
void* shared;
|
||||
void* data;
|
||||
size_t capacity;
|
||||
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<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();
|
@ -1,3 +1,4 @@
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#include "platform.h"
|
||||
|
||||
#include "utils.h"
|
||||
@ -123,3 +124,4 @@ std::unique_ptr<PlatformSharedMemory> CreatePlatformSharedMemory(const std::stri
|
||||
std::string name2 = "/" + name;
|
||||
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_;
|
||||
};
|
Loading…
Reference in New Issue
Block a user