Jacob Dufault
8 years ago
6 changed files with 446 additions and 368 deletions
@ -0,0 +1,153 @@ |
|||
#include "ipc.h" |
|||
|
|||
namespace { |
|||
JsonMessage* as_message(char* ptr) { |
|||
return reinterpret_cast<JsonMessage*>(ptr); |
|||
} |
|||
} |
|||
|
|||
const char* JsonMessage::payload() { |
|||
return reinterpret_cast<const char*>(this) + sizeof(JsonMessage); |
|||
} |
|||
|
|||
void JsonMessage::SetPayload(size_t payload_size, const char* payload) { |
|||
char* payload_dest = reinterpret_cast<char*>(this) + sizeof(JsonMessage); |
|||
this->payload_size = payload_size; |
|||
memcpy(payload_dest, payload, payload_size); |
|||
} |
|||
|
|||
IpcMessage_IsAlive::IpcMessage_IsAlive() { |
|||
kind = JsonMessage::Kind::IsAlive; |
|||
} |
|||
|
|||
void IpcMessage_IsAlive::Serialize(Writer& writer) {} |
|||
|
|||
void IpcMessage_IsAlive::Deserialize(Reader& reader) {} |
|||
|
|||
IpcMessage_ImportIndex::IpcMessage_ImportIndex() { |
|||
kind = JsonMessage::Kind::ImportIndex; |
|||
} |
|||
|
|||
void IpcMessage_ImportIndex::Serialize(Writer& writer) { |
|||
writer.StartObject(); |
|||
::Serialize(writer, "path", path); |
|||
writer.EndObject(); |
|||
} |
|||
void IpcMessage_ImportIndex::Deserialize(Reader& reader) { |
|||
::Deserialize(reader, "path", path); |
|||
} |
|||
|
|||
IpcMessage_CreateIndex::IpcMessage_CreateIndex() { |
|||
kind = JsonMessage::Kind::CreateIndex; |
|||
} |
|||
|
|||
void IpcMessage_CreateIndex::Serialize(Writer& writer) { |
|||
writer.StartObject(); |
|||
::Serialize(writer, "path", path); |
|||
::Serialize(writer, "args", args); |
|||
writer.EndObject(); |
|||
} |
|||
void IpcMessage_CreateIndex::Deserialize(Reader& reader) { |
|||
::Deserialize(reader, "path", path); |
|||
::Deserialize(reader, "args", args); |
|||
} |
|||
|
|||
IpcMessageQueue::IpcMessageQueue(const std::string& name) { |
|||
local_block = new char[shmem_size]; |
|||
shared = CreatePlatformSharedMemory(name + "_memory"); |
|||
mutex = CreatePlatformMutex(name + "_mutex"); |
|||
} |
|||
|
|||
IpcMessageQueue::~IpcMessageQueue() { |
|||
delete[] local_block; |
|||
} |
|||
|
|||
void IpcMessageQueue::PushMessage(BaseIpcMessage* message) { |
|||
rapidjson::StringBuffer output; |
|||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(output); |
|||
writer.SetFormatOptions( |
|||
rapidjson::PrettyFormatOptions::kFormatSingleLineArray); |
|||
writer.SetIndent(' ', 2); |
|||
message->Serialize(writer); |
|||
|
|||
size_t payload_size = strlen(output.GetString()); |
|||
assert(payload_size < shmem_size && "Increase shared memory size, payload will never fit"); |
|||
|
|||
bool first = true; |
|||
bool did_log = false; |
|||
while (true) { |
|||
using namespace std::chrono_literals; |
|||
if (!first) { |
|||
if (!did_log) { |
|||
std::cout << "[info]: shmem full, waiting" << std::endl; // TODO: remove
|
|||
did_log = true; |
|||
} |
|||
std::this_thread::sleep_for(16ms); |
|||
} |
|||
first = false; |
|||
|
|||
std::unique_ptr<PlatformScopedMutexLock> lock = CreatePlatformScopedMutexLock(mutex.get()); |
|||
|
|||
// Try again later when there is room in shared memory.
|
|||
if ((*shared->shared_bytes_used + sizeof(JsonMessage) + payload_size) >= shmem_size) |
|||
continue; |
|||
|
|||
get_free_message()->kind = message->kind; |
|||
get_free_message()->SetPayload(payload_size, output.GetString()); |
|||
|
|||
*shared->shared_bytes_used += sizeof(JsonMessage) + get_free_message()->payload_size; |
|||
assert(*shared->shared_bytes_used < shmem_size); |
|||
get_free_message()->kind = JsonMessage::Kind::Invalid; |
|||
break; |
|||
} |
|||
|
|||
} |
|||
|
|||
std::vector<std::unique_ptr<BaseIpcMessage>> IpcMessageQueue::PopMessage() { |
|||
size_t remaining_bytes = 0; |
|||
// Move data from shared memory into a local buffer. Do this
|
|||
// before parsing the blocks so that other processes can begin
|
|||
// posting data as soon as possible.
|
|||
{ |
|||
std::unique_ptr<PlatformScopedMutexLock> lock = CreatePlatformScopedMutexLock(mutex.get()); |
|||
remaining_bytes = *shared->shared_bytes_used; |
|||
|
|||
memcpy(local_block, shared->shared_start, *shared->shared_bytes_used); |
|||
*shared->shared_bytes_used = 0; |
|||
get_free_message()->kind = JsonMessage::Kind::Invalid; |
|||
} |
|||
|
|||
std::vector<std::unique_ptr<BaseIpcMessage>> result; |
|||
|
|||
char* message = local_block; |
|||
while (remaining_bytes > 0) { |
|||
std::unique_ptr<BaseIpcMessage> base_message; |
|||
switch (as_message(message)->kind) { |
|||
case JsonMessage::Kind::IsAlive: |
|||
base_message = std::make_unique<IpcMessage_IsAlive>(); |
|||
break; |
|||
case JsonMessage::Kind::CreateIndex: |
|||
base_message = std::make_unique<IpcMessage_CreateIndex>(); |
|||
break; |
|||
case JsonMessage::Kind::ImportIndex: |
|||
base_message = std::make_unique<IpcMessage_ImportIndex>(); |
|||
break; |
|||
default: |
|||
assert(false); |
|||
} |
|||
|
|||
rapidjson::Document document; |
|||
document.Parse(as_message(message)->payload(), as_message(message)->payload_size); |
|||
bool has_error = document.HasParseError(); |
|||
auto error = document.GetParseError(); |
|||
|
|||
base_message->Deserialize(document); |
|||
|
|||
result.emplace_back(std::move(base_message)); |
|||
|
|||
remaining_bytes -= sizeof(JsonMessage) + as_message(message)->payload_size; |
|||
message = message + sizeof(JsonMessage) + as_message(message)->payload_size; |
|||
} |
|||
|
|||
return result; |
|||
} |
@ -0,0 +1,96 @@ |
|||
#pragma once |
|||
|
|||
#include <iostream> |
|||
#include <chrono> |
|||
#include <string> |
|||
#include <thread> |
|||
|
|||
#include <rapidjson/document.h> |
|||
#include <rapidjson/prettywriter.h> |
|||
|
|||
#include "platform.h" |
|||
#include "serializer.h" |
|||
|
|||
using Writer = rapidjson::PrettyWriter<rapidjson::StringBuffer>; |
|||
using Reader = rapidjson::Document; |
|||
|
|||
|
|||
// 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 { |
|||
enum class Kind { |
|||
Invalid, |
|||
IsAlive, |
|||
CreateIndex, |
|||
ImportIndex, |
|||
}; |
|||
|
|||
Kind kind; |
|||
size_t payload_size; |
|||
|
|||
const char* payload(); |
|||
void SetPayload(size_t payload_size, const char* payload); |
|||
}; |
|||
|
|||
struct BaseIpcMessage { |
|||
JsonMessage::Kind kind; |
|||
virtual ~BaseIpcMessage() {} |
|||
|
|||
virtual void Serialize(Writer& writer) = 0; |
|||
virtual void Deserialize(Reader& reader) = 0; |
|||
}; |
|||
|
|||
struct IpcMessage_IsAlive : public BaseIpcMessage { |
|||
IpcMessage_IsAlive(); |
|||
|
|||
// BaseIpcMessage:
|
|||
void Serialize(Writer& writer) override; |
|||
void Deserialize(Reader& reader) override; |
|||
}; |
|||
|
|||
struct IpcMessage_ImportIndex : public BaseIpcMessage { |
|||
std::string path; |
|||
|
|||
IpcMessage_ImportIndex(); |
|||
|
|||
// BaseMessage:
|
|||
void Serialize(Writer& writer) override; |
|||
void Deserialize(Reader& reader) override; |
|||
}; |
|||
|
|||
struct IpcMessage_CreateIndex : public BaseIpcMessage { |
|||
std::string path; |
|||
std::vector<std::string> args; |
|||
|
|||
IpcMessage_CreateIndex(); |
|||
|
|||
// BaseMessage:
|
|||
void Serialize(Writer& writer) override; |
|||
void Deserialize(Reader& reader) override; |
|||
}; |
|||
|
|||
struct IpcMessageQueue { |
|||
// NOTE: We keep all pointers in terms of char* so pointer arithmetic is
|
|||
// always relative to bytes.
|
|||
|
|||
explicit IpcMessageQueue(const std::string& name); |
|||
~IpcMessageQueue(); |
|||
|
|||
void PushMessage(BaseIpcMessage* message); |
|||
std::vector<std::unique_ptr<BaseIpcMessage>> PopMessage(); |
|||
|
|||
private: |
|||
JsonMessage* get_free_message() { |
|||
return reinterpret_cast<JsonMessage*>(shared->shared_start + *shared->shared_bytes_used); |
|||
} |
|||
|
|||
// Pointer to process shared memory and process shared mutex.
|
|||
std::unique_ptr<PlatformSharedMemory> shared; |
|||
std::unique_ptr<PlatformMutex> mutex; |
|||
|
|||
// Pointer to process-local memory.
|
|||
char* local_block; |
|||
}; |
@ -0,0 +1,23 @@ |
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <string> |
|||
|
|||
struct PlatformMutex { |
|||
virtual ~PlatformMutex() {} |
|||
}; |
|||
struct PlatformScopedMutexLock { |
|||
virtual ~PlatformScopedMutexLock() {} |
|||
}; |
|||
struct PlatformSharedMemory { |
|||
virtual ~PlatformSharedMemory() {} |
|||
|
|||
size_t* shared_bytes_used; |
|||
char* shared_start; |
|||
}; |
|||
|
|||
const int shmem_size = 1024; // number of chars/bytes (256kb)
|
|||
|
|||
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); |
@ -0,0 +1,71 @@ |
|||
#include "platform.h" |
|||
|
|||
#include <cassert> |
|||
#include <string> |
|||
#include <Windows.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_; |
|||
void* shared_start_real_; |
|||
|
|||
PlatformSharedMemoryWin(const std::string& name) { |
|||
shmem_ = CreateFileMapping( |
|||
INVALID_HANDLE_VALUE, |
|||
NULL, |
|||
PAGE_READWRITE, |
|||
0, |
|||
shmem_size, |
|||
name.c_str() |
|||
); |
|||
|
|||
shared_start_real_ = MapViewOfFile(shmem_, FILE_MAP_ALL_ACCESS, 0, 0, shmem_size); |
|||
|
|||
shared_bytes_used = reinterpret_cast<size_t*>(shared_start_real_); |
|||
*shared_bytes_used = 0; |
|||
shared_start = reinterpret_cast<char*>(shared_bytes_used + 1); |
|||
} |
|||
|
|||
~PlatformSharedMemoryWin() override { |
|||
UnmapViewOfFile(shared_start_real_); |
|||
} |
|||
}; |
|||
|
|||
|
|||
|
|||
std::unique_ptr<PlatformMutex> CreatePlatformMutex(const std::string& name) { |
|||
return std::make_unique<PlatformMutexWin>(name); |
|||
} |
|||
|
|||
std::unique_ptr<PlatformScopedMutexLock> CreatePlatformScopedMutexLock(PlatformMutex* mutex) { |
|||
return std::make_unique<PlatformScopedMutexLockWin>(static_cast<PlatformMutexWin*>(mutex)->raw_mutex); |
|||
} |
|||
|
|||
std::unique_ptr<PlatformSharedMemory> CreatePlatformSharedMemory(const std::string& name) { |
|||
return std::make_unique<PlatformSharedMemoryWin>(name); |
|||
} |
Loading…
Reference in new issue