From 4cec26ae12cf4d63bbd44aab7560813a9d46493d Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Sun, 19 Mar 2017 15:06:22 -0700 Subject: [PATCH] WIP --- command_line.cc | 5 - doctest_impl.cc | 2 + ipc.cc | 122 ++++++++++++++------- ipc.h | 6 +- platform_win.cc | 92 ---------------- src/buffer.cc | 120 ++++++++++++++++++++ src/buffer.h | 30 +++++ src/message_queue.cc | 37 +++++++ src/message_queue.h | 75 +++++++++++++ src/platform.cc | 39 +++++++ platform.h => src/platform.h | 7 +- platform_linux.cc => src/platform_linux.cc | 2 + src/platform_win.cc | 113 +++++++++++++++++++ src/resizable_buffer.cc | 100 +++++++++++++++++ src/resizable_buffer.h | 21 ++++ test.cc | 2 +- 16 files changed, 629 insertions(+), 144 deletions(-) create mode 100644 doctest_impl.cc delete mode 100644 platform_win.cc create mode 100644 src/buffer.cc create mode 100644 src/buffer.h create mode 100644 src/message_queue.cc create mode 100644 src/message_queue.h create mode 100644 src/platform.cc rename platform.h => src/platform.h (82%) rename platform_linux.cc => src/platform_linux.cc (98%) create mode 100644 src/platform_win.cc create mode 100644 src/resizable_buffer.cc create mode 100644 src/resizable_buffer.h diff --git a/command_line.cc b/command_line.cc index 2b80efa3..9bd75628 100644 --- a/command_line.cc +++ b/command_line.cc @@ -13,7 +13,6 @@ #include "third_party/tiny-process-library/process.hpp" -#define DOCTEST_CONFIG_IMPLEMENT #include "third_party/doctest/doctest/doctest.h" #include @@ -1026,10 +1025,6 @@ void PreMain() { MessageRegistry::instance()->Register(); } -TEST_CASE("helo") { - CHECK(1 == 1); -} - int main(int argc, char** argv) { bool loop = false; while (loop) diff --git a/doctest_impl.cc b/doctest_impl.cc new file mode 100644 index 00000000..132c1b2e --- /dev/null +++ b/doctest_impl.cc @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT +#include "third_party/doctest/doctest/doctest.h" \ No newline at end of file diff --git a/ipc.cc b/ipc.cc index 9163d80d..9df2e21e 100644 --- a/ipc.cc +++ b/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(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(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(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(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(new char[shmem_size]); + local = std::unique_ptr(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(shared->shared, shmem_size, initialize_shared_memory); - local_buffer = MakeUnique(local.get(), shmem_size, true /*initialize*/); + shared_buffer = MakeUnique(shared->data, kBufferSize, initialize_shared_memory); + local_buffer = MakeUnique(local.get(), kBufferSize, true /*initialize*/); } IpcDirectionalChannel::~IpcDirectionalChannel() {} @@ -249,7 +253,7 @@ void IpcDispatch(PlatformMutex* mutex, std::function 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 writer(output); @@ -331,8 +335,8 @@ std::vector> IpcDirectionalChannel::TakeMessages() { // posting data as soon as possible. { std::unique_ptr 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> 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> IpcClient::TakeMessages() { return client_.TakeMessages(); } + + + + + +template +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>(IpcId::Test); + + IpcDirectionalChannel channel0("indexertestmemory", true /*initialize_shared_memory*/); + IpcDirectionalChannel channel1("indexertestmemory", false /*initialize_shared_memory*/); + + TestIpcMessage m; + m.data = "hey there"; + + channel0.PushMessage(&m); + std::vector> messages = channel1.TakeMessages(); + REQUIRE(messages.size() == 1); + REQUIRE(messages[0]->ipc_id == m.ipc_id); + REQUIRE(reinterpret_cast*>(messages[0].get())->data == m.data); +} +#endif \ No newline at end of file diff --git a/ipc.h b/ipc.h index 01d86d2d..66d88d30 100644 --- a/ipc.h +++ b/ipc.h @@ -9,7 +9,7 @@ #include #include -#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 { diff --git a/platform_win.cc b/platform_win.cc deleted file mode 100644 index 8e0d9a8b..00000000 --- a/platform_win.cc +++ /dev/null @@ -1,92 +0,0 @@ -#ifdef _MSC_VER -#include "platform.h" - -#include -#include -#include - -#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 CreatePlatformMutex(const std::string& name) { - return MakeUnique(name); -} - -std::unique_ptr CreatePlatformScopedMutexLock(PlatformMutex* mutex) { - return MakeUnique(static_cast(mutex)->raw_mutex); -} - -std::unique_ptr CreatePlatformSharedMemory(const std::string& name) { - return MakeUnique(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 -#include -#include - -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 diff --git a/src/buffer.cc b/src/buffer.cc new file mode 100644 index 00000000..cb575c39 --- /dev/null +++ b/src/buffer.cc @@ -0,0 +1,120 @@ +#include "buffer.h" + +#include + +#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 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 WaitForExclusiveAccess() override { + return MakeUnique(mutex_); + } + + std::mutex mutex_; +}; + +struct ScopedLockPlatform : public ScopedLock { + ScopedLockPlatform(PlatformMutex* mutex) + : guard(CreatePlatformScopedMutexLock(mutex)) {} + + std::unique_ptr 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 WaitForExclusiveAccess() override { + return MakeUnique(mutex_.get()); + } + + std::unique_ptr memory_; + std::unique_ptr mutex_; +}; + +} // namespace + +std::unique_ptr Buffer::Create(size_t capacity) { + return MakeUnique(capacity); +} + +std::unique_ptr Buffer::CreateSharedBuffer(const std::string& name, size_t capacity) { + return MakeUnique(name, capacity); +} + +TEST_SUITE("BufferLocal"); + +TEST_CASE("create") { + std::unique_ptr 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(b->data); + *data = 0; + + std::unique_ptr 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([&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(); \ No newline at end of file diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 00000000..1558d52b --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +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 Create(size_t capacity); + // Create a buffer pointing to memory shared across processes with the given + // capacity. + static std::unique_ptr 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 WaitForExclusiveAccess() = 0; + + void* data = nullptr; + size_t capacity = 0; +}; + diff --git a/src/message_queue.cc b/src/message_queue.cc new file mode 100644 index 00000000..30f526a3 --- /dev/null +++ b/src/message_queue.cc @@ -0,0 +1,37 @@ +#include "message_queue.h" + +#include + +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, 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(buffer_->data); +} \ No newline at end of file diff --git a/src/message_queue.h b/src/message_queue.h new file mode 100644 index 00000000..01227395 --- /dev/null +++ b/src/message_queue.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#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, 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> DequeueAll(); + + // Take the first available message from the queue. + std::unique_ptr DequeueFirst(); + +private: + struct BufferMetadata; + + BufferMetadata* Metadata(); + + std::unique_ptr buffer_; +}; + + +/* +// TODO: We convert IpcMessage <-> Message as a user-level operation. +// MessageQueue doesn't know about IpcMessage. +struct IpcMessage { + std::unique_ptr ToMessage(); + void BuildFromMessage(std::unique_ptr message); + + // Serialize/deserialize the message. + virtual void Serialize(Writer& writer) = 0; + virtual void Deserialize(Reader& reader) = 0; +}; +*/ \ No newline at end of file diff --git a/src/platform.cc b/src/platform.cc new file mode 100644 index 00000000..844c9115 --- /dev/null +++ b/src/platform.cc @@ -0,0 +1,39 @@ +#include "platform.h" + +#include + +#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(); diff --git a/platform.h b/src/platform.h similarity index 82% rename from platform.h rename to src/platform.h index 03cd70c6..d0213274 100644 --- a/platform.h +++ b/src/platform.h @@ -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 CreatePlatformMutex(const std::string& name); std::unique_ptr CreatePlatformScopedMutexLock(PlatformMutex* mutex); -std::unique_ptr CreatePlatformSharedMemory(const std::string& name); +std::unique_ptr CreatePlatformSharedMemory(const std::string& name, size_t size); std::string GetWorkingDirectory(); \ No newline at end of file diff --git a/platform_linux.cc b/src/platform_linux.cc similarity index 98% rename from platform_linux.cc rename to src/platform_linux.cc index 7fe08969..61183f9b 100644 --- a/platform_linux.cc +++ b/src/platform_linux.cc @@ -1,3 +1,4 @@ +#if defined(__linux__) || defined(__APPLE__) #include "platform.h" #include "utils.h" @@ -123,3 +124,4 @@ std::unique_ptr CreatePlatformSharedMemory(const std::stri std::string name2 = "/" + name; return MakeUnique(name2); } +#endif \ No newline at end of file diff --git a/src/platform_win.cc b/src/platform_win.cc new file mode 100644 index 00000000..5e74018b --- /dev/null +++ b/src/platform_win.cc @@ -0,0 +1,113 @@ +#if defined(_WIN32) +#include "platform.h" + +#include +#include +#include +#include + +#include "../utils.h" + +namespace { + +DWORD CheckForError(std::vector 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 CreatePlatformMutex(const std::string& name) { + return MakeUnique(name); +} + +std::unique_ptr CreatePlatformScopedMutexLock(PlatformMutex* mutex) { + return MakeUnique(static_cast(mutex)->raw_mutex); +} + +std::unique_ptr CreatePlatformSharedMemory(const std::string& name, size_t size) { + return MakeUnique(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 diff --git a/src/resizable_buffer.cc b/src/resizable_buffer.cc new file mode 100644 index 00000000..b159d4bd --- /dev/null +++ b/src/resizable_buffer.cc @@ -0,0 +1,100 @@ +#include "resizable_buffer.h" + +#include "../third_party/doctest/doctest/doctest.h" + +#include +#include +#include +#include + +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(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(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(); \ No newline at end of file diff --git a/src/resizable_buffer.h b/src/resizable_buffer.h new file mode 100644 index 00000000..04a9f77c --- /dev/null +++ b/src/resizable_buffer.h @@ -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_; +}; \ No newline at end of file diff --git a/test.cc b/test.cc index b74e7cfd..c19fea7d 100644 --- a/test.cc +++ b/test.cc @@ -3,7 +3,7 @@ #include "indexer.h" #include "serializer.h" #include "utils.h" -#include "platform.h" +#include "src/platform.h" void Write(const std::vector& strs) { for (const std::string& str : strs)