Browse Source

WIP

pull/2/head
Jacob Dufault 8 years ago
parent
commit
4cec26ae12
  1. 5
      command_line.cc
  2. 2
      doctest_impl.cc
  3. 124
      ipc.cc
  4. 6
      ipc.h
  5. 92
      platform_win.cc
  6. 120
      src/buffer.cc
  7. 30
      src/buffer.h
  8. 37
      src/message_queue.cc
  9. 75
      src/message_queue.h
  10. 39
      src/platform.cc
  11. 7
      src/platform.h
  12. 2
      src/platform_linux.cc
  13. 113
      src/platform_win.cc
  14. 100
      src/resizable_buffer.cc
  15. 21
      src/resizable_buffer.h
  16. 2
      test.cc

5
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 <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

@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT
#include "third_party/doctest/doctest/doctest.h"

124
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;
// 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);
}
// 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;
const int kBufferSize = 1024 * 1024 * 32; // number of chars/bytes (32mb) in the message buffer.
// 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);
}
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;
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";
char* payload_dest = reinterpret_cast<char*>(this) + sizeof(JsonMessage);
memcpy(payload_dest, payload, payload_size);
}
};
std::string NameToClientName(const std::string& name, int client_id) {
return name + "client" + std::to_string(client_id);
}
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

@ -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 {

92
platform_win.cc

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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();

7
platform.h → 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<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();

2
platform_linux.cc → 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<PlatformSharedMemory> CreatePlatformSharedMemory(const std::stri
std::string name2 = "/" + name;
return MakeUnique<PlatformSharedMemoryLinux>(name2);
}
#endif

113
src/platform_win.cc

@ -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

@ -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

@ -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

@ -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<std::string>& strs) {
for (const std::string& str : strs)

Loading…
Cancel
Save