This commit is contained in:
63
rediska/frontend/CMakeLists.txt
Normal file
63
rediska/frontend/CMakeLists.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
add_library(frontend STATIC
|
||||
# IntService.cpp
|
||||
# BoolService.cpp
|
||||
# FloatService.cpp
|
||||
# StringService.cpp
|
||||
CallData.cpp
|
||||
server.hpp
|
||||
RequestManager.hpp
|
||||
)
|
||||
|
||||
# Find packages
|
||||
find_package(Protobuf REQUIRED CONFIG)
|
||||
find_package(gRPC REQUIRED CONFIG)
|
||||
|
||||
set(PROTO_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../proto")
|
||||
|
||||
# Define the proto files
|
||||
set(PROTO_FILES
|
||||
"${PROTO_ROOT_DIR}/v1/collections/common.proto"
|
||||
"${PROTO_ROOT_DIR}/v1/collections/element_kind.proto"
|
||||
"${PROTO_ROOT_DIR}/v1/collections/list.proto"
|
||||
"${PROTO_ROOT_DIR}/v1/object/object.proto"
|
||||
"${PROTO_ROOT_DIR}/v1/primitives/bool.proto"
|
||||
"${PROTO_ROOT_DIR}/v1/primitives/float.proto"
|
||||
"${PROTO_ROOT_DIR}/v1/primitives/int.proto"
|
||||
"${PROTO_ROOT_DIR}/v1/primitives/string.proto"
|
||||
)
|
||||
|
||||
# This allows protobuf_generate to find them when inspecting the target.
|
||||
target_sources(frontend PRIVATE ${PROTO_FILES})
|
||||
|
||||
# Include directories
|
||||
# We include the binary dir (for generated .pb.h files) and the root (for imports)
|
||||
target_include_directories(frontend PUBLIC
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${PROTO_ROOT_DIR}"
|
||||
)
|
||||
|
||||
# Generate Standard Protobuf files (.pb.cc / .pb.h)
|
||||
# We use 'protobuf_generate' instead of the legacy 'protobuf_generate_cpp'.
|
||||
# It automatically adds the generated C++ files back into the 'frontend' target.
|
||||
protobuf_generate(
|
||||
TARGET frontend
|
||||
LANGUAGE cpp
|
||||
IMPORT_DIRS "${PROTO_ROOT_DIR}"
|
||||
PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
|
||||
# Generate gRPC specific files (.grpc.pb.cc / .grpc.pb.h)
|
||||
protobuf_generate(
|
||||
TARGET frontend
|
||||
LANGUAGE grpc
|
||||
GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc
|
||||
PLUGIN "protoc-gen-grpc=$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
|
||||
IMPORT_DIRS "${PROTO_ROOT_DIR}"
|
||||
PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
|
||||
target_link_libraries(frontend
|
||||
PUBLIC
|
||||
gRPC::grpc++
|
||||
protobuf::libprotobuf
|
||||
)
|
||||
336
rediska/frontend/CallData.cpp
Normal file
336
rediska/frontend/CallData.cpp
Normal file
@@ -0,0 +1,336 @@
|
||||
#include <functional>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "google/protobuf/empty.pb.h"
|
||||
|
||||
#include "v1/collections/list.grpc.pb.h"
|
||||
#include "v1/primitives/bool.grpc.pb.h"
|
||||
#include "v1/primitives/int.grpc.pb.h"
|
||||
#include "v1/primitives/string.grpc.pb.h"
|
||||
|
||||
#include "rediska/common/QueueMessage.hpp"
|
||||
#include "rediska/frontend/RequestManager.hpp"
|
||||
#include "rediska/frontend/server.hpp"
|
||||
|
||||
namespace {
|
||||
CacheValue CollectionElementToCacheValue(const v1::collections::common::CollectionElement& element) {
|
||||
if (element.has_integer())
|
||||
return static_cast<int64_t>(element.integer());
|
||||
if (element.has_floating_point())
|
||||
return element.floating_point();
|
||||
if (element.has_boolean())
|
||||
return element.boolean();
|
||||
if (element.has_str_or_obj())
|
||||
return element.str_or_obj();
|
||||
return std::string{};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
using BoolService = v1::primitives::boolean::BoolCacheService::AsyncService;
|
||||
using IntService = v1::primitives::integer::IntCacheService::AsyncService;
|
||||
using StringService = v1::primitives::str::StringCacheService::AsyncService;
|
||||
using ListService = v1::collections::list::ListCacheService::AsyncService;
|
||||
|
||||
struct BoolSetRequestManager : RequestManager<
|
||||
BoolSetRequestManager,
|
||||
v1::primitives::boolean::BoolSetRequest,
|
||||
grpc::ServerAsyncResponseWriter<google::protobuf::Empty>,
|
||||
BoolService,
|
||||
&BoolService::RequestSet> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::BOOLEAN;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::SET;
|
||||
msg.arguments = PrimitiveSetArgs{.value = CacheValue{this->request.value()}, .ttl_seconds = 0};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct BoolGetRequestManager : RequestManager<
|
||||
BoolGetRequestManager,
|
||||
v1::primitives::boolean::BoolGetRequest,
|
||||
grpc::ServerAsyncResponseWriter<v1::primitives::boolean::BoolGetResponse>,
|
||||
BoolService,
|
||||
&BoolService::RequestGet> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::BOOLEAN;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::GET;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct BoolDeleteRequestManager : RequestManager<
|
||||
BoolDeleteRequestManager,
|
||||
v1::primitives::boolean::BoolDeleteRequest,
|
||||
grpc::ServerAsyncResponseWriter<v1::primitives::boolean::BoolDeleteResponse>,
|
||||
BoolService,
|
||||
&BoolService::RequestDelete> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::BOOLEAN;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::DELETE;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct IntSetRequestManager : RequestManager<
|
||||
IntSetRequestManager,
|
||||
v1::primitives::integer::IntSetRequest,
|
||||
grpc::ServerAsyncResponseWriter<google::protobuf::Empty>,
|
||||
IntService,
|
||||
&IntService::RequestSet> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::INT;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::SET;
|
||||
msg.arguments = PrimitiveSetArgs{.value = CacheValue{this->request.value()}, .ttl_seconds = 0};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct IntGetRequestManager : RequestManager<
|
||||
IntGetRequestManager,
|
||||
v1::primitives::integer::IntGetRequest,
|
||||
grpc::ServerAsyncResponseWriter<v1::primitives::integer::IntGetResponse>,
|
||||
IntService,
|
||||
&IntService::RequestGet> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::INT;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::GET;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct IntDeleteRequestManager : RequestManager<
|
||||
IntDeleteRequestManager,
|
||||
v1::primitives::integer::IntDeleteRequest,
|
||||
grpc::ServerAsyncResponseWriter<v1::primitives::integer::IntDeleteResponse>,
|
||||
IntService,
|
||||
&IntService::RequestDelete> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::INT;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::DELETE;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct StringSetRequestManager : RequestManager<
|
||||
StringSetRequestManager,
|
||||
v1::primitives::str::StringSetRequest,
|
||||
grpc::ServerAsyncResponseWriter<google::protobuf::Empty>,
|
||||
StringService,
|
||||
&StringService::RequestSet> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::STRING;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::SET;
|
||||
msg.arguments = PrimitiveSetArgs{.value = CacheValue{this->request.value()}, .ttl_seconds = 0};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct StringGetRequestManager : RequestManager<
|
||||
StringGetRequestManager,
|
||||
v1::primitives::str::StringGetRequest,
|
||||
grpc::ServerAsyncResponseWriter<v1::primitives::str::StringGetResponse>,
|
||||
StringService,
|
||||
&StringService::RequestGet> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::STRING;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::GET;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct StringDeleteRequestManager : RequestManager<
|
||||
StringDeleteRequestManager,
|
||||
v1::primitives::str::StringDeleteRequest,
|
||||
grpc::ServerAsyncResponseWriter<v1::primitives::str::StringDeleteResponse>,
|
||||
StringService,
|
||||
&StringService::RequestDelete> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::STRING;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::DELETE;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct ListSetRequestManager : RequestManager<
|
||||
ListSetRequestManager,
|
||||
v1::collections::list::ListSetRequest,
|
||||
grpc::ServerAsyncResponseWriter<v1::collections::list::ListSetResponse>,
|
||||
ListService,
|
||||
&ListService::RequestSet> {
|
||||
using RequestManager::RequestManager;
|
||||
QueueMessage BuildMessage() {
|
||||
std::vector<CacheValue> values;
|
||||
values.reserve(static_cast<size_t>(this->request.elements_size()));
|
||||
for (const auto& el : this->request.elements())
|
||||
values.emplace_back(CollectionElementToCacheValue(el));
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::ARRAY;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::SET;
|
||||
msg.arguments = ListPushManyArgs{.values = std::move(values), .replace_entire_list = true};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct ListGetRequestManager : RequestManager<
|
||||
ListGetRequestManager,
|
||||
v1::collections::list::ListGetRequest,
|
||||
grpc::ServerAsyncWriter<v1::collections::list::ListGetResponse>,
|
||||
ListService,
|
||||
&ListService::RequestGet> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::ARRAY;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::GET;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
struct ListDeleteRequestManager : RequestManager<
|
||||
ListDeleteRequestManager,
|
||||
v1::collections::list::DeleteRequest,
|
||||
grpc::ServerAsyncResponseWriter<google::protobuf::Empty>,
|
||||
ListService,
|
||||
&ListService::RequestDelete> {
|
||||
using RequestManager::RequestManager;
|
||||
|
||||
QueueMessage BuildMessage() {
|
||||
QueueMessage msg;
|
||||
msg.type = CacheValueId::ARRAY;
|
||||
msg.key = this->request.id();
|
||||
msg.operation = OperationId::DELETE;
|
||||
msg.arguments = std::monostate{};
|
||||
msg.responder = this->TakeResponder();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
void RunFrontendServer(const std::string& address, std::function<void(QueueMessage)> callback_enqueue_message) {
|
||||
grpc::ServerBuilder builder;
|
||||
BoolService bool_service;
|
||||
IntService int_service;
|
||||
StringService string_service;
|
||||
ListService list_service;
|
||||
|
||||
builder.AddListeningPort(address, grpc::InsecureServerCredentials());
|
||||
builder.RegisterService(&bool_service);
|
||||
builder.RegisterService(&int_service);
|
||||
builder.RegisterService(&string_service);
|
||||
builder.RegisterService(&list_service);
|
||||
|
||||
std::unique_ptr<grpc::ServerCompletionQueue> cq = builder.AddCompletionQueue();
|
||||
std::unique_ptr<grpc::Server> server = builder.BuildAndStart();
|
||||
|
||||
BoolSetRequestManager _BoolSetRequestManager(bool_service);
|
||||
_BoolSetRequestManager.ListenForOne(*cq);
|
||||
BoolGetRequestManager _BoolGetRequestManager(bool_service);
|
||||
_BoolGetRequestManager.ListenForOne(*cq);
|
||||
BoolDeleteRequestManager _BoolDeleteRequestManager(bool_service);
|
||||
_BoolDeleteRequestManager.ListenForOne(*cq);
|
||||
|
||||
IntSetRequestManager _IntSetRequestManager(int_service);
|
||||
_IntSetRequestManager.ListenForOne(*cq);
|
||||
IntGetRequestManager _IntGetRequestManager(int_service);
|
||||
_IntGetRequestManager.ListenForOne(*cq);
|
||||
IntDeleteRequestManager _IntDeleteRequestManager(int_service);
|
||||
_IntDeleteRequestManager.ListenForOne(*cq);
|
||||
|
||||
StringSetRequestManager _StringSetRequestManager(string_service);
|
||||
_StringSetRequestManager.ListenForOne(*cq);
|
||||
StringGetRequestManager _StringGetRequestManager(string_service);
|
||||
_StringGetRequestManager.ListenForOne(*cq);
|
||||
StringDeleteRequestManager _StringDeleteRequestManager(string_service);
|
||||
_StringDeleteRequestManager.ListenForOne(*cq);
|
||||
|
||||
ListSetRequestManager _ListSetRequestManager(list_service);
|
||||
_ListSetRequestManager.ListenForOne(*cq);
|
||||
ListGetRequestManager _ListGetRequestManager(list_service);
|
||||
_ListGetRequestManager.ListenForOne(*cq);
|
||||
ListDeleteRequestManager _ListDeleteRequestManager(list_service);
|
||||
_ListDeleteRequestManager.ListenForOne(*cq);
|
||||
|
||||
void* tag;
|
||||
bool ok;
|
||||
while (cq->Next(&tag, &ok)) {
|
||||
if (!tag)
|
||||
continue;
|
||||
// taking ownership as per the manager api
|
||||
std::unique_ptr<EventVariant> event(static_cast<EventVariant*>(tag));
|
||||
|
||||
if (!ok) {
|
||||
// idk could happen, but we do take ownership of the event anyway
|
||||
(void)event;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* req = std::get_if<RequestEvent>(event.get())) {
|
||||
auto& manager = req->manager;
|
||||
if (auto msg = manager.ConsumeMessage(); msg.has_value()) {
|
||||
callback_enqueue_message(std::move(*msg));
|
||||
}
|
||||
manager.ListenForOne(*cq);
|
||||
} else if (auto* fin = std::get_if<FinishEvent>(event.get())) {
|
||||
// we just destroy
|
||||
std::cout << "Finished a request\n" << std::flush;
|
||||
(void)fin;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
rediska/frontend/RequestManager.hpp
Normal file
52
rediska/frontend/RequestManager.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/support/async_stream.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include "rediska/common/QueueMessage.hpp"
|
||||
|
||||
// One global context kept alive as long as the server lives.
|
||||
inline grpc::ServerContext& GlobalServerContext() {
|
||||
static grpc::ServerContext ctx;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// The thing recieved from the completion queue in case of a request.
|
||||
struct BaseRequestManager {
|
||||
virtual void ListenForOne(grpc::ServerCompletionQueue& cq) = 0;
|
||||
virtual std::optional<QueueMessage> ConsumeMessage() = 0;
|
||||
virtual ~BaseRequestManager() = default;
|
||||
};
|
||||
|
||||
// This is basically storage for an incoming request.
|
||||
template <typename Derived, typename RequestT, typename ResponderT, typename ServiceT, auto RequestMethod>
|
||||
class RequestManager : public BaseRequestManager {
|
||||
protected:
|
||||
RequestT request;
|
||||
std::unique_ptr<ResponderT> responder;
|
||||
ServiceT& service;
|
||||
|
||||
std::unique_ptr<grpc::internal::ServerAsyncStreamingInterface> TakeResponder() {
|
||||
return std::unique_ptr<grpc::internal::ServerAsyncStreamingInterface>(std::move(responder));
|
||||
}
|
||||
|
||||
public:
|
||||
explicit RequestManager(ServiceT& svc) : service(svc) { }
|
||||
|
||||
// Sets up listening for a new request.
|
||||
// You must ensure this object lives until the request is consumed, or grpc will be unhappy.
|
||||
// The tag you recieve from the queue is a EventVariant* that you must take ownership of.
|
||||
void ListenForOne(grpc::ServerCompletionQueue& cq) override {
|
||||
request = RequestT{};
|
||||
// FIXME: just leak this stupid shit for now idk how to fix I'm done with this stupid language
|
||||
auto* ctx = new grpc::ServerContext();
|
||||
responder = std::make_unique<ResponderT>(ctx);
|
||||
(service.*RequestMethod)(ctx, &request, responder.get(), &cq, &cq, std::make_unique<EventVariant>(RequestEvent{*this}).release());
|
||||
}
|
||||
|
||||
std::optional<QueueMessage> ConsumeMessage() override {
|
||||
QueueMessage msg = static_cast<Derived*>(this)->BuildMessage();
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
11
rediska/frontend/server.hpp
Normal file
11
rediska/frontend/server.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "rediska/common/QueueMessage.hpp"
|
||||
|
||||
// Starts the gRPC frontend server on the given address and blocks.
|
||||
// Each incoming request is transformed into a QueueMessage with its responder
|
||||
// moved inside; the provided callback decides when/how to finish it.
|
||||
void RunFrontendServer(const std::string& address,
|
||||
std::function<void(QueueMessage)> on_request);
|
||||
Reference in New Issue
Block a user