diff --git a/.gitignore b/.gitignore index d56046f8a..96fd7c3fa 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,38 @@ TAGS *.capnp.h # LCP generated C++ & Cap'n Proto files -src/query/plan/operator.hpp -src/query/plan/operator.lcp.cpp +*.lcp.cpp +src/database/counters_rpc_messages.capnp +src/database/counters_rpc_messages.hpp +src/database/state_delta.capnp +src/database/state_delta.hpp +src/distributed/bfs_rpc_messages.capnp +src/distributed/bfs_rpc_messages.hpp +src/distributed/coordination_rpc_messages.capnp +src/distributed/coordination_rpc_messages.hpp +src/distributed/data_rpc_messages.capnp +src/distributed/data_rpc_messages.hpp +src/distributed/durability_rpc_messages.capnp +src/distributed/durability_rpc_messages.hpp +src/distributed/index_rpc_messages.capnp +src/distributed/index_rpc_messages.hpp +src/distributed/plan_rpc_messages.capnp +src/distributed/plan_rpc_messages.hpp +src/distributed/pull_produce_rpc_messages.capnp +src/distributed/pull_produce_rpc_messages.hpp +src/distributed/storage_gc_rpc_messages.capnp +src/distributed/storage_gc_rpc_messages.hpp +src/distributed/token_sharing_rpc_messages.capnp +src/distributed/token_sharing_rpc_messages.hpp +src/distributed/transactional_cache_cleaner_rpc_messages.capnp +src/distributed/transactional_cache_cleaner_rpc_messages.hpp +src/distributed/updates_rpc_messages.capnp +src/distributed/updates_rpc_messages.hpp src/query/plan/operator.capnp +src/query/plan/operator.hpp +src/stats/stats_rpc_messages.capnp +src/stats/stats_rpc_messages.hpp +src/storage/concurrent_id_mapper_rpc_messages.capnp +src/storage/concurrent_id_mapper_rpc_messages.hpp +src/transactions/engine_rpc_messages.capnp +src/transactions/engine_rpc_messages.hpp diff --git a/init b/init index 5d50345ee..8b09f86eb 100755 --- a/init +++ b/init @@ -111,6 +111,7 @@ fi mkdir -p ./build # quicklisp package manager for Common Lisp +# TODO: We should at some point cache or have a mirror of packages we use. quicklisp_install_dir="$HOME/quicklisp" if [[ -v QUICKLISP_HOME ]]; then quicklisp_install_dir="${QUICKLISP_HOME}" @@ -121,6 +122,7 @@ if [[ ! -f "${quicklisp_install_dir}/setup.lisp" ]]; then " (load \"${DIR}/quicklisp.lisp\") (quicklisp-quickstart:install :path \"${quicklisp_install_dir}\") + (ql:quickload :cl-ppcre :silent t) " | sbcl --script || exit 1 rm -rf quicklisp.lisp || exit 1 fi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e169c062b..c7ed82cc9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ set(memgraph_src_files distributed/data_rpc_server.cpp distributed/produce_rpc_server.cpp distributed/pull_rpc_clients.cpp + distributed/serialization.cpp distributed/updates_rpc_clients.cpp distributed/updates_rpc_server.cpp durability/paths.cpp @@ -70,6 +71,7 @@ set(memgraph_src_files transactions/engine_master.cpp transactions/engine_single_node.cpp transactions/engine_worker.cpp + transactions/snapshot.cpp ) # ----------------------------------------------------------------------------- @@ -96,23 +98,31 @@ set(lcp_src_files lisp/lcp.lisp ${lcp_exe}) # Use this function to add each lcp file to generation. This way each file is # standalone and we avoid recompiling everything. +# +# You may pass a CAPNP_SCHEMA <id> keyword argument to generate the Cap'n Proto +# serialization code from .lcp file. You still need to add the generated capnp +# file through `add_capnp` function. To generate the <id> use `capnp id` +# invocation, and specify it here. This preserves correct id information across +# multiple schema generations. If this wasn't the case, wrong typeId +# information will break RPC between different compilations of memgraph. +# # NOTE: memgraph_src_files and generated_lcp_files are globally updated. function(add_lcp lcp_file) - set(options CAPNP_SCHEMA) - cmake_parse_arguments(KW "${options}" "" "" ${ARGN}) + set(one_value_kwargs CAPNP_SCHEMA) + cmake_parse_arguments(KW "" "${one_value_kwargs}" "" ${ARGN}) string(REGEX REPLACE "\.lcp$" ".hpp" h_file "${CMAKE_CURRENT_SOURCE_DIR}/${lcp_file}") if (KW_CAPNP_SCHEMA) string(REGEX REPLACE "\.lcp$" ".capnp" capnp_file "${CMAKE_CURRENT_SOURCE_DIR}/${lcp_file}") - set(capnp_id_command ${CAPNP_EXE}) + set(capnp_id ${KW_CAPNP_SCHEMA}) set(capnp_depend capnproto-proj) set(cpp_file ${CMAKE_CURRENT_SOURCE_DIR}/${lcp_file}.cpp) # Update *global* memgraph_src_files set(memgraph_src_files ${memgraph_src_files} ${cpp_file} PARENT_SCOPE) endif() add_custom_command(OUTPUT ${h_file} ${cpp_file} ${capnp_file} - COMMAND ${lcp_exe} ${lcp_file} ${capnp_id_command} + COMMAND ${lcp_exe} ${lcp_file} ${capnp_id} VERBATIM DEPENDS ${lcp_file} ${lcp_src_files} ${capnp_depend} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) @@ -120,18 +130,54 @@ function(add_lcp lcp_file) set(generated_lcp_files ${generated_lcp_files} ${h_file} ${cpp_file} ${capnp_file} PARENT_SCOPE) endfunction(add_lcp) -add_lcp(query/plan/operator.lcp CAPNP_SCHEMA) +add_lcp(database/counters_rpc_messages.lcp CAPNP_SCHEMA @0x95a2c3ea3871e945) +add_capnp(database/counters_rpc_messages.capnp) +add_lcp(database/state_delta.lcp CAPNP_SCHEMA @0xdea01657b3563887) +add_capnp(database/state_delta.capnp) +add_lcp(distributed/bfs_rpc_messages.lcp CAPNP_SCHEMA @0x8e508640b09b6d2a) +add_capnp(distributed/bfs_rpc_messages.capnp) +add_lcp(distributed/coordination_rpc_messages.lcp CAPNP_SCHEMA @0x93df0c4703cf98fb) +add_capnp(distributed/coordination_rpc_messages.capnp) +add_lcp(distributed/data_rpc_messages.lcp CAPNP_SCHEMA @0xc1c8a341ba37aaf5) +add_capnp(distributed/data_rpc_messages.capnp) +add_lcp(distributed/durability_rpc_messages.lcp CAPNP_SCHEMA @0xf5e53bc271e2163d) +add_capnp(distributed/durability_rpc_messages.capnp) +add_lcp(distributed/index_rpc_messages.lcp CAPNP_SCHEMA @0xa8aab46862945bd6) +add_capnp(distributed/index_rpc_messages.capnp) +add_lcp(distributed/plan_rpc_messages.lcp CAPNP_SCHEMA @0xfcbc48dc9f106d28) +add_capnp(distributed/plan_rpc_messages.capnp) +add_lcp(distributed/pull_produce_rpc_messages.lcp CAPNP_SCHEMA @0xa78a9254a73685bd) +add_capnp(distributed/pull_produce_rpc_messages.capnp) +add_lcp(distributed/storage_gc_rpc_messages.lcp CAPNP_SCHEMA @0xd705663dfe36cf81) +add_capnp(distributed/storage_gc_rpc_messages.capnp) +add_lcp(distributed/token_sharing_rpc_messages.lcp CAPNP_SCHEMA @0x8f295db54ec4caec) +add_capnp(distributed/token_sharing_rpc_messages.capnp) +add_lcp(distributed/transactional_cache_cleaner_rpc_messages.lcp CAPNP_SCHEMA @0xe2be6183a1ff9e11) +add_capnp(distributed/transactional_cache_cleaner_rpc_messages.capnp) +add_lcp(distributed/updates_rpc_messages.lcp CAPNP_SCHEMA @0x82d5f38d73c7b53a) +add_capnp(distributed/updates_rpc_messages.capnp) +add_lcp(query/plan/operator.lcp CAPNP_SCHEMA @0xe5cae8d045d30c42) add_capnp(query/plan/operator.capnp) +add_lcp(stats/stats_rpc_messages.lcp CAPNP_SCHEMA @0xc19a87c81b9b4512) +add_capnp(stats/stats_rpc_messages.capnp) +add_lcp(storage/concurrent_id_mapper_rpc_messages.lcp CAPNP_SCHEMA @0xa6068dae93d225dd) +add_capnp(storage/concurrent_id_mapper_rpc_messages.capnp) +add_lcp(transactions/engine_rpc_messages.lcp CAPNP_SCHEMA @0xde02b7c49180cad5) +add_capnp(transactions/engine_rpc_messages.capnp) add_custom_target(generate_lcp DEPENDS ${generated_lcp_files}) # Registering capnp must come after registering lcp files. -add_capnp(query/frontend/semantic/symbol.capnp) -add_capnp(query/frontend/ast/ast.capnp) -add_capnp(utils/serialization.capnp) -add_capnp(storage/types.capnp) +add_capnp(communication/rpc/messages.capnp) +add_capnp(distributed/serialization.capnp) +add_capnp(durability/recovery.capnp) add_capnp(query/common.capnp) +add_capnp(query/frontend/ast/ast.capnp) +add_capnp(query/frontend/semantic/symbol.capnp) +add_capnp(storage/serialization.capnp) +add_capnp(transactions/common.capnp) +add_capnp(utils/serialization.capnp) add_custom_target(generate_capnp DEPENDS generate_lcp ${generated_capnp_files}) diff --git a/src/communication/raft/network_common.hpp b/src/communication/raft/network_common.hpp index 347b98df5..96ceeb0b0 100644 --- a/src/communication/raft/network_common.hpp +++ b/src/communication/raft/network_common.hpp @@ -1,8 +1,5 @@ #pragma once -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" - #include "communication/rpc/messages.hpp" #include "communication/raft/raft.hpp" @@ -11,38 +8,16 @@ namespace communication::raft { enum class RpcType { REQUEST_VOTE, APPEND_ENTRIES }; template <class State> -struct PeerRpcRequest : public rpc::Message { +struct PeerRpcRequest { RpcType type; RequestVoteRequest request_vote; AppendEntriesRequest<State> append_entries; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<rpc::Message>(*this); - ar &type; - ar &request_vote; - ar &append_entries; - } }; -struct PeerRpcReply : public rpc::Message { +struct PeerRpcReply { RpcType type; RequestVoteReply request_vote; AppendEntriesReply append_entries; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<rpc::Message>(*this); - ar &type; - ar &request_vote; - ar &append_entries; - } }; } // namespace communication::raft diff --git a/src/communication/raft/rpc.hpp b/src/communication/raft/rpc.hpp index 4dddf66d5..1cd5bb7f3 100644 --- a/src/communication/raft/rpc.hpp +++ b/src/communication/raft/rpc.hpp @@ -30,24 +30,27 @@ class RpcNetwork : public RaftNetworkInterface<State> { : server_(server), directory_(std::move(directory)) {} virtual void Start(RaftMember<State> &member) override { - server_.Register<PeerProtocol<State>>( - [&member](const PeerRpcRequest<State> &request) { - auto reply = std::make_unique<PeerRpcReply>(); - reply->type = request.type; - switch (request.type) { - case RpcType::REQUEST_VOTE: - reply->request_vote = member.OnRequestVote(request.request_vote); - break; - case RpcType::APPEND_ENTRIES: - reply->append_entries = - member.OnAppendEntries(request.append_entries); - break; - default: - LOG(ERROR) << "Unknown RPC type: " - << static_cast<int>(request.type); - } - return reply; - }); + // TODO: Serialize RPC via Cap'n Proto +// server_.Register<PeerProtocol<State>>( +// [&member](const auto &req_reader, auto *res_builder) { +// PeerRpcRequest<State> request; +// request.Load(req_reader); +// PeerRpcReply reply; +// reply.type = request.type; +// switch (request.type) { +// case RpcType::REQUEST_VOTE: +// reply.request_vote = member.OnRequestVote(request.request_vote); +// break; +// case RpcType::APPEND_ENTRIES: +// reply.append_entries = +// member.OnAppendEntries(request.append_entries); +// break; +// default: +// LOG(ERROR) << "Unknown RPC type: " +// << static_cast<int>(request.type); +// } +// reply.Save(res_builder); +// }); } virtual bool SendRequestVote(const MemberId &recipient, diff --git a/src/communication/rpc/client.cpp b/src/communication/rpc/client.cpp index aa4498a29..0d99740bf 100644 --- a/src/communication/rpc/client.cpp +++ b/src/communication/rpc/client.cpp @@ -1,12 +1,6 @@ #include <chrono> #include <thread> -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" -#include "boost/serialization/export.hpp" -#include "boost/serialization/unique_ptr.hpp" #include "gflags/gflags.h" #include "communication/rpc/client.hpp" @@ -19,7 +13,8 @@ namespace communication::rpc { Client::Client(const io::network::Endpoint &endpoint) : endpoint_(endpoint) {} -std::unique_ptr<Message> Client::Call(const Message &request) { +std::experimental::optional<::capnp::FlatArrayMessageReader> Client::Send( + ::capnp::MessageBuilder *message) { std::lock_guard<std::mutex> guard(mutex_); if (FLAGS_rpc_random_latency) { @@ -39,45 +34,37 @@ std::unique_ptr<Message> Client::Call(const Message &request) { if (!client_->Connect(endpoint_)) { LOG(ERROR) << "Couldn't connect to remote address " << endpoint_; client_ = std::experimental::nullopt; - return nullptr; + return std::experimental::nullopt; } } // Serialize and send request. - std::stringstream request_stream(std::ios_base::out | std::ios_base::binary); - { - boost::archive::binary_oarchive request_archive(request_stream); - // Serialize reference as pointer (to serialize the derived class). The - // request is read in protocol.cpp. - request_archive << &request; - // Archive destructor ensures everything is written. - } - - const std::string &request_buffer = request_stream.str(); - CHECK(request_buffer.size() <= std::numeric_limits<MessageSize>::max()) + auto request_words = ::capnp::messageToFlatArray(*message); + auto request_bytes = request_words.asBytes(); + CHECK(request_bytes.size() <= std::numeric_limits<MessageSize>::max()) << fmt::format( "Trying to send message of size {}, max message size is {}", - request_buffer.size(), std::numeric_limits<MessageSize>::max()); + request_bytes.size(), std::numeric_limits<MessageSize>::max()); - MessageSize request_data_size = request_buffer.size(); + MessageSize request_data_size = request_bytes.size(); if (!client_->Write(reinterpret_cast<uint8_t *>(&request_data_size), sizeof(MessageSize), true)) { LOG(ERROR) << "Couldn't send request size to " << client_->endpoint(); client_ = std::experimental::nullopt; - return nullptr; + return std::experimental::nullopt; } - if (!client_->Write(request_buffer)) { + if (!client_->Write(request_bytes.begin(), request_bytes.size())) { LOG(ERROR) << "Couldn't send request data to " << client_->endpoint(); client_ = std::experimental::nullopt; - return nullptr; + return std::experimental::nullopt; } // Receive response data size. if (!client_->Read(sizeof(MessageSize))) { LOG(ERROR) << "Couldn't get response from " << client_->endpoint(); client_ = std::experimental::nullopt; - return nullptr; + return std::experimental::nullopt; } MessageSize response_data_size = *reinterpret_cast<MessageSize *>(client_->GetData()); @@ -87,22 +74,19 @@ std::unique_ptr<Message> Client::Call(const Message &request) { if (!client_->Read(response_data_size)) { LOG(ERROR) << "Couldn't get response from " << client_->endpoint(); client_ = std::experimental::nullopt; - return nullptr; - } - - std::unique_ptr<Message> response; - { - std::stringstream response_stream(std::ios_base::in | - std::ios_base::binary); - response_stream.str(std::string(reinterpret_cast<char *>(client_->GetData()), - response_data_size)); - boost::archive::binary_iarchive response_archive(response_stream); - response_archive >> response; + return std::experimental::nullopt; } + // Read the response message. + auto data = ::kj::arrayPtr(client_->GetData(), response_data_size); + // Our data is word aligned and padded to 64bit because we use regular + // (non-packed) serialization of Cap'n Proto. So we can use reinterpret_cast. + auto data_words = + ::kj::arrayPtr(reinterpret_cast<::capnp::word *>(data.begin()), + reinterpret_cast<::capnp::word *>(data.end())); + ::capnp::FlatArrayMessageReader response_message(data_words.asConst()); client_->ShiftData(response_data_size); - - return response; + return std::experimental::make_optional(std::move(response_message)); } void Client::Abort() { diff --git a/src/communication/rpc/client.hpp b/src/communication/rpc/client.hpp index b5712028e..47d7cffb4 100644 --- a/src/communication/rpc/client.hpp +++ b/src/communication/rpc/client.hpp @@ -5,62 +5,85 @@ #include <mutex> #include <random> +#include <capnp/message.h> +#include <capnp/serialize.h> #include <glog/logging.h> #include "communication/client.hpp" +#include "communication/rpc/messages.capnp.h" #include "communication/rpc/messages.hpp" #include "io/network/endpoint.hpp" #include "utils/demangle.hpp" namespace communication::rpc { -// Client is thread safe, but it is recommended to use thread_local clients. +/// Client is thread safe, but it is recommended to use thread_local clients. class Client { public: - Client(const io::network::Endpoint &endpoint); + explicit Client(const io::network::Endpoint &endpoint); - // Call function can initiate only one request at the time. Function blocks - // until there is a response. If there was an error nullptr is returned. - template <typename TRequestResponse, typename... Args> - std::unique_ptr<typename TRequestResponse::Response> Call(Args &&... args) { - using Req = typename TRequestResponse::Request; - using Res = typename TRequestResponse::Response; - static_assert(std::is_base_of<Message, Req>::value, - "TRequestResponse::Request must be derived from Message"); - static_assert(std::is_base_of<Message, Res>::value, - "TRequestResponse::Response must be derived from Message"); - auto request = Req(std::forward<Args>(args)...); + /// Call function can initiate only one request at the time. Function blocks + /// until there is a response. If there was an error nullptr is returned. + template <class TRequestResponse, class... Args> + std::experimental::optional<typename TRequestResponse::Response> Call( + Args &&... args) { + return CallWithLoad<TRequestResponse>( + [](const auto &reader) { + typename TRequestResponse::Response response; + response.Load(reader); + return response; + }, + std::forward<Args>(args)...); + } - if (VLOG_IS_ON(12)) { - auto req_type = utils::Demangle(request.type_index().name()); - LOG(INFO) << "[RpcClient] sent " << (req_type ? req_type.value() : ""); + /// Same as `Call` but the first argument is a response loading function. + template <class TRequestResponse, class... Args> + std::experimental::optional<typename TRequestResponse::Response> CallWithLoad( + std::function<typename TRequestResponse::Response( + const typename TRequestResponse::Response::Capnp::Reader &)> + load, + Args &&... args) { + typename TRequestResponse::Request request(std::forward<Args>(args)...); + auto req_type = TRequestResponse::Request::TypeInfo; + VLOG(12) << "[RpcClient] sent " << req_type.name; + ::capnp::MallocMessageBuilder req_msg; + { + auto builder = req_msg.initRoot<capnp::Message>(); + builder.setTypeId(req_type.id); + auto data_builder = builder.initData(); + auto req_builder = + data_builder + .template initAs<typename TRequestResponse::Request::Capnp>(); + request.Save(&req_builder); } - - std::unique_ptr<Message> response = Call(request); - auto *real_response = dynamic_cast<Res *>(response.get()); - if (!real_response && response) { + auto maybe_response = Send(&req_msg); + if (!maybe_response) { + return std::experimental::nullopt; + } + auto res_msg = maybe_response->getRoot<capnp::Message>(); + auto res_type = TRequestResponse::Response::TypeInfo; + if (res_msg.getTypeId() != res_type.id) { // Since message_id was checked in private Call function, this means // something is very wrong (probably on the server side). LOG(ERROR) << "Message response was of unexpected type"; client_ = std::experimental::nullopt; - return nullptr; + return std::experimental::nullopt; } - if (VLOG_IS_ON(12) && response) { - auto res_type = utils::Demangle(response->type_index().name()); - LOG(INFO) << "[RpcClient] received " - << (res_type ? res_type.value() : ""); - } + VLOG(12) << "[RpcClient] received " << res_type.name; - response.release(); - return std::unique_ptr<Res>(real_response); + auto data_reader = + res_msg.getData() + .template getAs<typename TRequestResponse::Response::Capnp>(); + return std::experimental::make_optional(load(data_reader)); } - // Call this function from another thread to abort a pending RPC call. + /// Call this function from another thread to abort a pending RPC call. void Abort(); private: - std::unique_ptr<Message> Call(const Message &request); + std::experimental::optional<::capnp::FlatArrayMessageReader> Send( + ::capnp::MessageBuilder *message); io::network::Endpoint endpoint_; std::experimental::optional<communication::Client> client_; diff --git a/src/communication/rpc/client_pool.hpp b/src/communication/rpc/client_pool.hpp index dbdf23d64..bfd609abc 100644 --- a/src/communication/rpc/client_pool.hpp +++ b/src/communication/rpc/client_pool.hpp @@ -14,10 +14,33 @@ namespace communication::rpc { */ class ClientPool { public: - ClientPool(const io::network::Endpoint &endpoint) : endpoint_(endpoint) {} + explicit ClientPool(const io::network::Endpoint &endpoint) + : endpoint_(endpoint) {} - template <typename TRequestResponse, typename... Args> - std::unique_ptr<typename TRequestResponse::Response> Call(Args &&... args) { + template <class TRequestResponse, class... Args> + std::experimental::optional<typename TRequestResponse::Response> Call( + Args &&... args) { + return WithUnusedClient([&](const auto &client) { + return client->template Call<TRequestResponse>( + std::forward<Args>(args)...); + }); + }; + + template <class TRequestResponse, class... Args> + std::experimental::optional<typename TRequestResponse::Response> CallWithLoad( + std::function<typename TRequestResponse::Response( + const typename TRequestResponse::Response::Capnp::Reader &)> + load, + Args &&... args) { + return WithUnusedClient([&](const auto &client) { + return client->template CallWithLoad<TRequestResponse>( + load, std::forward<Args>(args)...); + }); + }; + + private: + template <class TFun> + auto WithUnusedClient(const TFun &fun) { std::unique_ptr<Client> client; std::unique_lock<std::mutex> lock(mutex_); @@ -29,14 +52,13 @@ class ClientPool { } lock.unlock(); - auto resp = client->Call<TRequestResponse>(std::forward<Args>(args)...); + auto res = fun(client); lock.lock(); unused_clients_.push(std::move(client)); - return resp; - }; + return res; + } - private: io::network::Endpoint endpoint_; std::mutex mutex_; diff --git a/src/communication/rpc/messages-inl.hpp b/src/communication/rpc/messages-inl.hpp deleted file mode 100644 index 59f93bff6..000000000 --- a/src/communication/rpc/messages-inl.hpp +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/export.hpp" - -#include "database/state_delta.hpp" -#include "distributed/bfs_rpc_messages.hpp" -#include "distributed/coordination_rpc_messages.hpp" -#include "distributed/data_rpc_messages.hpp" -#include "distributed/durability_rpc_messages.hpp" -#include "distributed/index_rpc_messages.hpp" -#include "distributed/plan_rpc_messages.hpp" -#include "distributed/pull_produce_rpc_messages.hpp" -#include "distributed/storage_gc_rpc_messages.hpp" -#include "distributed/transactional_cache_cleaner_rpc_messages.hpp" -#include "distributed/updates_rpc_messages.hpp" -#include "durability/recovery.hpp" -#include "stats/stats_rpc_messages.hpp" -#include "storage/concurrent_id_mapper_rpc_messages.hpp" -#include "transactions/engine_rpc_messages.hpp" - -#define ID_VALUE_EXPORT_BOOST_TYPE(type) \ - BOOST_CLASS_EXPORT(storage::type##IdReq); \ - BOOST_CLASS_EXPORT(storage::type##IdRes); \ - BOOST_CLASS_EXPORT(storage::Id##type##Req); \ - BOOST_CLASS_EXPORT(storage::Id##type##Res); - -ID_VALUE_EXPORT_BOOST_TYPE(Label) -ID_VALUE_EXPORT_BOOST_TYPE(EdgeType) -ID_VALUE_EXPORT_BOOST_TYPE(Property) - -#undef ID_VALUE_EXPORT_BOOST_TYPE - -// Distributed transaction engine. -BOOST_CLASS_EXPORT(tx::TxAndSnapshot); -BOOST_CLASS_EXPORT(tx::BeginReq); -BOOST_CLASS_EXPORT(tx::BeginRes); -BOOST_CLASS_EXPORT(tx::AdvanceReq); -BOOST_CLASS_EXPORT(tx::AdvanceRes); -BOOST_CLASS_EXPORT(tx::CommitReq); -BOOST_CLASS_EXPORT(tx::CommitRes); -BOOST_CLASS_EXPORT(tx::AbortReq); -BOOST_CLASS_EXPORT(tx::AbortRes); -BOOST_CLASS_EXPORT(tx::SnapshotReq); -BOOST_CLASS_EXPORT(tx::SnapshotRes); -BOOST_CLASS_EXPORT(tx::CommandReq); -BOOST_CLASS_EXPORT(tx::CommandRes); -BOOST_CLASS_EXPORT(tx::GcSnapshotReq); -BOOST_CLASS_EXPORT(tx::ClogInfoReq); -BOOST_CLASS_EXPORT(tx::ClogInfoRes); -BOOST_CLASS_EXPORT(tx::ActiveTransactionsReq); -BOOST_CLASS_EXPORT(tx::EnsureNextIdGreaterReq); -BOOST_CLASS_EXPORT(tx::EnsureNextIdGreaterRes); -BOOST_CLASS_EXPORT(tx::GlobalLastReq); -BOOST_CLASS_EXPORT(tx::GlobalLastRes); - -// Distributed coordination. -BOOST_CLASS_EXPORT(durability::RecoveryInfo); -BOOST_CLASS_EXPORT(distributed::RegisterWorkerReq); -BOOST_CLASS_EXPORT(distributed::RegisterWorkerRes); -BOOST_CLASS_EXPORT(distributed::ClusterDiscoveryReq); -BOOST_CLASS_EXPORT(distributed::ClusterDiscoveryRes); -BOOST_CLASS_EXPORT(distributed::StopWorkerReq); -BOOST_CLASS_EXPORT(distributed::StopWorkerRes); -BOOST_CLASS_EXPORT(distributed::NotifyWorkerRecoveredReq); -BOOST_CLASS_EXPORT(distributed::NotifyWorkerRecoveredRes); - -// Distributed data exchange. -BOOST_CLASS_EXPORT(distributed::EdgeReq); -BOOST_CLASS_EXPORT(distributed::EdgeRes); -BOOST_CLASS_EXPORT(distributed::VertexReq); -BOOST_CLASS_EXPORT(distributed::VertexRes); -BOOST_CLASS_EXPORT(distributed::TxGidPair); -BOOST_CLASS_EXPORT(distributed::VertexCountReq); -BOOST_CLASS_EXPORT(distributed::VertexCountRes); - -// Distributed plan exchange. -BOOST_CLASS_EXPORT(distributed::DispatchPlanReq); -BOOST_CLASS_EXPORT(distributed::DispatchPlanRes); -BOOST_CLASS_EXPORT(distributed::RemovePlanReq); -BOOST_CLASS_EXPORT(distributed::RemovePlanRes); - -// Pull. -BOOST_CLASS_EXPORT(distributed::PullReq); -BOOST_CLASS_EXPORT(distributed::PullRes); -BOOST_CLASS_EXPORT(distributed::TransactionCommandAdvancedReq); -BOOST_CLASS_EXPORT(distributed::TransactionCommandAdvancedRes); - -// Distributed indexes. -BOOST_CLASS_EXPORT(distributed::BuildIndexReq); -BOOST_CLASS_EXPORT(distributed::BuildIndexRes); -BOOST_CLASS_EXPORT(distributed::IndexLabelPropertyTx); - -// Token sharing. -BOOST_CLASS_EXPORT(distributed::TokenTransferReq); -BOOST_CLASS_EXPORT(distributed::TokenTransferRes); - -// Stats. -BOOST_CLASS_EXPORT(stats::StatsReq); -BOOST_CLASS_EXPORT(stats::StatsRes); -BOOST_CLASS_EXPORT(stats::BatchStatsReq); -BOOST_CLASS_EXPORT(stats::BatchStatsRes); - -// Updates. -BOOST_CLASS_EXPORT(database::StateDelta); -BOOST_CLASS_EXPORT(distributed::UpdateReq); -BOOST_CLASS_EXPORT(distributed::UpdateRes); -BOOST_CLASS_EXPORT(distributed::UpdateApplyReq); -BOOST_CLASS_EXPORT(distributed::UpdateApplyRes); - -// Creates. -BOOST_CLASS_EXPORT(distributed::CreateResult); -BOOST_CLASS_EXPORT(distributed::CreateVertexReq); -BOOST_CLASS_EXPORT(distributed::CreateVertexReqData); -BOOST_CLASS_EXPORT(distributed::CreateVertexRes); -BOOST_CLASS_EXPORT(distributed::CreateEdgeReqData); -BOOST_CLASS_EXPORT(distributed::CreateEdgeReq); -BOOST_CLASS_EXPORT(distributed::CreateEdgeRes); -BOOST_CLASS_EXPORT(distributed::AddInEdgeReqData); -BOOST_CLASS_EXPORT(distributed::AddInEdgeReq); -BOOST_CLASS_EXPORT(distributed::AddInEdgeRes); - -// Removes. -BOOST_CLASS_EXPORT(distributed::RemoveVertexReq); -BOOST_CLASS_EXPORT(distributed::RemoveVertexRes); -BOOST_CLASS_EXPORT(distributed::RemoveEdgeReq); -BOOST_CLASS_EXPORT(distributed::RemoveEdgeRes); -BOOST_CLASS_EXPORT(distributed::RemoveInEdgeData); -BOOST_CLASS_EXPORT(distributed::RemoveInEdgeReq); -BOOST_CLASS_EXPORT(distributed::RemoveInEdgeRes); - -// Durability -BOOST_CLASS_EXPORT(distributed::MakeSnapshotReq); -BOOST_CLASS_EXPORT(distributed::MakeSnapshotRes); - -// Storage Gc. -BOOST_CLASS_EXPORT(distributed::GcClearedStatusReq); -BOOST_CLASS_EXPORT(distributed::GcClearedStatusRes); - -// Transactional Cache Cleaner. -BOOST_CLASS_EXPORT(distributed::WaitOnTransactionEndReq); -BOOST_CLASS_EXPORT(distributed::WaitOnTransactionEndRes); - -// Cursor. -BOOST_CLASS_EXPORT(distributed::CreateBfsSubcursorReq); -BOOST_CLASS_EXPORT(distributed::CreateBfsSubcursorRes); -BOOST_CLASS_EXPORT(distributed::RegisterSubcursorsReq); -BOOST_CLASS_EXPORT(distributed::RegisterSubcursorsRes); -BOOST_CLASS_EXPORT(distributed::RemoveBfsSubcursorReq); -BOOST_CLASS_EXPORT(distributed::RemoveBfsSubcursorRes); -BOOST_CLASS_EXPORT(distributed::SetSourceReq); -BOOST_CLASS_EXPORT(distributed::SetSourceRes); -BOOST_CLASS_EXPORT(distributed::ExpandLevelReq); -BOOST_CLASS_EXPORT(distributed::ExpandLevelRes); -BOOST_CLASS_EXPORT(distributed::SubcursorPullReq); -BOOST_CLASS_EXPORT(distributed::SubcursorPullRes); -BOOST_CLASS_EXPORT(distributed::ExpandToRemoteVertexReq); -BOOST_CLASS_EXPORT(distributed::ExpandToRemoteVertexRes); -BOOST_CLASS_EXPORT(distributed::ReconstructPathReq); -BOOST_CLASS_EXPORT(distributed::ReconstructPathRes); -BOOST_CLASS_EXPORT(distributed::PrepareForExpandReq); -BOOST_CLASS_EXPORT(distributed::PrepareForExpandRes); diff --git a/src/communication/rpc/messages.capnp b/src/communication/rpc/messages.capnp new file mode 100644 index 000000000..507d52148 --- /dev/null +++ b/src/communication/rpc/messages.capnp @@ -0,0 +1,9 @@ +@0xd3832c9a1a3d8ec7; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("communication::rpc::capnp"); + +struct Message { + typeId @0 :UInt64; + data @1 :AnyPointer; +} diff --git a/src/communication/rpc/messages.hpp b/src/communication/rpc/messages.hpp index 604868350..982f9896e 100644 --- a/src/communication/rpc/messages.hpp +++ b/src/communication/rpc/messages.hpp @@ -1,38 +1,50 @@ #pragma once +#include <cstdint> #include <memory> -#include <type_traits> -#include <typeindex> - -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" namespace communication::rpc { using MessageSize = uint32_t; -/** - * Base class for messages. - */ -class Message { - public: - virtual ~Message() {} - - /** - * Run-time type identification that is used for callbacks. - * - * Warning: this works because of the virtual destructor, don't remove it from - * this class - */ - std::type_index type_index() const { return typeid(*this); } - - private: - friend boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &, unsigned int) {} +/// Type information on a RPC message. +/// Each message should have a static member `TypeInfo` with this information. +struct MessageType { + /// Unique ID for a message. + uint64_t id; + /// Pretty name of the type. + std::string name; }; +inline bool operator==(const MessageType &a, const MessageType &b) { + return a.id == b.id; +} +inline bool operator!=(const MessageType &a, const MessageType &b) { + return a.id != b.id; +} +inline bool operator<(const MessageType &a, const MessageType &b) { + return a.id < b.id; +} +inline bool operator<=(const MessageType &a, const MessageType &b) { + return a.id <= b.id; +} +inline bool operator>(const MessageType &a, const MessageType &b) { + return a.id > b.id; +} +inline bool operator>=(const MessageType &a, const MessageType &b) { + return a.id >= b.id; +} + +/// Each RPC is defined via this struct. +/// +/// `TRequest` and `TResponse` are required to be classes which have a static +/// member `TypeInfo` of `MessageType` type. This is used for proper +/// registration and deserialization of RPC types. Additionally, both `TRequest` +/// and `TResponse` are required to define a nested `Capnp` type, which +/// corresponds to the Cap'n Proto schema type, as well as defined the following +/// serialization functions: +/// * void Save(Capnp::Builder *, ...) const +/// * void Load(const Capnp::Reader &, ...) template <typename TRequest, typename TResponse> struct RequestResponse { using Request = TRequest; @@ -40,35 +52,3 @@ struct RequestResponse { }; } // namespace communication::rpc - -// RPC Pimp -#define RPC_NO_MEMBER_MESSAGE(name) \ - struct name : public communication::rpc::Message { \ - name() {} \ - \ - private: \ - friend class boost::serialization::access; \ - \ - template <class TArchive> \ - void serialize(TArchive &ar, unsigned int) { \ - ar &boost::serialization::base_object<communication::rpc::Message>( \ - *this); \ - } \ - } - -#define RPC_SINGLE_MEMBER_MESSAGE(name, type) \ - struct name : public communication::rpc::Message { \ - name() {} \ - name(const type &member) : member(member) {} \ - type member; \ - \ - private: \ - friend class boost::serialization::access; \ - \ - template <class TArchive> \ - void serialize(TArchive &ar, unsigned int) { \ - ar &boost::serialization::base_object<communication::rpc::Message>( \ - *this); \ - ar &member; \ - } \ - } diff --git a/src/communication/rpc/protocol.cpp b/src/communication/rpc/protocol.cpp index 937532e1e..f05788d49 100644 --- a/src/communication/rpc/protocol.cpp +++ b/src/communication/rpc/protocol.cpp @@ -1,11 +1,10 @@ #include <sstream> -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/unique_ptr.hpp" +#include "capnp/message.h" +#include "capnp/serialize.h" #include "fmt/format.h" -#include "communication/rpc/messages-inl.hpp" +#include "communication/rpc/messages.capnp.h" #include "communication/rpc/messages.hpp" #include "communication/rpc/protocol.hpp" #include "communication/rpc/server.hpp" @@ -28,65 +27,51 @@ void Session::Execute() { if (input_stream_.size() < request_size) return; // Read the request message. - std::unique_ptr<Message> request([this, request_len]() { - Message *req_ptr = nullptr; - std::stringstream stream(std::ios_base::in | std::ios_base::binary); - stream.str(std::string( - reinterpret_cast<char *>(input_stream_.data() + sizeof(MessageSize)), - request_len)); - boost::archive::binary_iarchive archive(stream); - // Sent from client.cpp - archive >> req_ptr; - return req_ptr; - }()); + auto data = + ::kj::arrayPtr(input_stream_.data() + sizeof(request_len), request_len); + // Our data is word aligned and padded to 64bit because we use regular + // (non-packed) serialization of Cap'n Proto. So we can use reinterpret_cast. + auto data_words = + ::kj::arrayPtr(reinterpret_cast<::capnp::word *>(data.begin()), + reinterpret_cast<::capnp::word *>(data.end())); + ::capnp::FlatArrayMessageReader request_message(data_words.asConst()); + auto request = request_message.getRoot<capnp::Message>(); input_stream_.Shift(sizeof(MessageSize) + request_len); auto callbacks_accessor = server_.callbacks_.access(); - auto it = callbacks_accessor.find(request->type_index()); + auto it = callbacks_accessor.find(request.getTypeId()); if (it == callbacks_accessor.end()) { // Throw exception to close the socket and cleanup the session. throw SessionException( "Session trying to execute an unregistered RPC call!"); } - if (VLOG_IS_ON(12)) { - auto req_type = utils::Demangle(request->type_index().name()); - LOG(INFO) << "[RpcServer] received " << (req_type ? req_type.value() : ""); - } + VLOG(12) << "[RpcServer] received " << it->second.req_type.name; - std::unique_ptr<Message> response = it->second(*(request.get())); - - if (!response) { - throw SessionException("Trying to send nullptr instead of message"); - } + ::capnp::MallocMessageBuilder response_message; + // callback fills the message data + auto response_builder = response_message.initRoot<capnp::Message>(); + it->second.callback(request, &response_builder); // Serialize and send response - std::stringstream stream(std::ios_base::out | std::ios_base::binary); - { - boost::archive::binary_oarchive archive(stream); - archive << response; - // Archive destructor ensures everything is written. - } - - const std::string &buffer = stream.str(); - if (buffer.size() > std::numeric_limits<MessageSize>::max()) { + auto response_words = ::capnp::messageToFlatArray(response_message); + auto response_bytes = response_words.asBytes(); + if (response_bytes.size() > std::numeric_limits<MessageSize>::max()) { throw SessionException(fmt::format( "Trying to send response of size {}, max response size is {}", - buffer.size(), std::numeric_limits<MessageSize>::max())); + response_bytes.size(), std::numeric_limits<MessageSize>::max())); } - MessageSize input_stream_size = buffer.size(); + MessageSize input_stream_size = response_bytes.size(); if (!output_stream_.Write(reinterpret_cast<uint8_t *>(&input_stream_size), sizeof(MessageSize), true)) { throw SessionException("Couldn't send response size!"); } - if (!output_stream_.Write(buffer)) { + if (!output_stream_.Write(response_bytes.begin(), response_bytes.size())) { throw SessionException("Couldn't send response data!"); } - if (VLOG_IS_ON(12)) { - auto res_type = utils::Demangle(response->type_index().name()); - LOG(INFO) << "[RpcServer] sent " << (res_type ? res_type.value() : ""); - } + VLOG(12) << "[RpcServer] sent " << it->second.res_type.name; } + } // namespace communication::rpc diff --git a/src/communication/rpc/server.cpp b/src/communication/rpc/server.cpp index 19eade72c..5304a698c 100644 --- a/src/communication/rpc/server.cpp +++ b/src/communication/rpc/server.cpp @@ -1,10 +1,3 @@ -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" -#include "boost/serialization/export.hpp" -#include "boost/serialization/unique_ptr.hpp" - #include "communication/rpc/server.hpp" namespace communication::rpc { diff --git a/src/communication/rpc/server.hpp b/src/communication/rpc/server.hpp index 100a0f0eb..a39a25dd3 100644 --- a/src/communication/rpc/server.hpp +++ b/src/communication/rpc/server.hpp @@ -1,9 +1,11 @@ #pragma once -#include <type_traits> #include <unordered_map> #include <vector> +#include "capnp/any.h" + +#include "communication/rpc/messages.capnp.h" #include "communication/rpc/messages.hpp" #include "communication/rpc/protocol.hpp" #include "communication/server.hpp" @@ -27,57 +29,53 @@ class Server { const io::network::Endpoint &endpoint() const; - template <typename TRequestResponse> - void Register( - std::function<std::unique_ptr<typename TRequestResponse::Response>( - const typename TRequestResponse::Request &)> - callback) { - static_assert( - std::is_base_of<Message, typename TRequestResponse::Request>::value, - "TRequestResponse::Request must be derived from Message"); - static_assert( - std::is_base_of<Message, typename TRequestResponse::Response>::value, - "TRequestResponse::Response must be derived from Message"); + template <class TRequestResponse> + void Register(std::function< + void(const typename TRequestResponse::Request::Capnp::Reader &, + typename TRequestResponse::Response::Capnp::Builder *)> + callback) { + RpcCallback rpc; + rpc.req_type = TRequestResponse::Request::TypeInfo; + rpc.res_type = TRequestResponse::Response::TypeInfo; + rpc.callback = [callback = callback](const auto &reader, auto *builder) { + auto req_data = + reader.getData() + .template getAs<typename TRequestResponse::Request::Capnp>(); + builder->setTypeId(TRequestResponse::Response::TypeInfo.id); + auto data_builder = builder->initData(); + auto res_builder = + data_builder + .template initAs<typename TRequestResponse::Response::Capnp>(); + callback(req_data, &res_builder); + }; auto callbacks_accessor = callbacks_.access(); - auto got = callbacks_accessor.insert( - typeid(typename TRequestResponse::Request), - [callback = callback](const Message &base_message) { - const auto &message = - dynamic_cast<const typename TRequestResponse::Request &>( - base_message); - return callback(message); - }); + auto got = + callbacks_accessor.insert(TRequestResponse::Request::TypeInfo.id, rpc); CHECK(got.second) << "Callback for that message type already registered"; - if (VLOG_IS_ON(12)) { - auto req_type = - utils::Demangle(typeid(typename TRequestResponse::Request).name()); - auto res_type = - utils::Demangle(typeid(typename TRequestResponse::Response).name()); - LOG(INFO) << "[RpcServer] register " << (req_type ? req_type.value() : "") - << " -> " << (res_type ? res_type.value() : ""); - } + VLOG(12) << "[RpcServer] register " << rpc.req_type.name << " -> " + << rpc.res_type.name; } template <typename TRequestResponse> void UnRegister() { - static_assert( - std::is_base_of<Message, typename TRequestResponse::Request>::value, - "TRequestResponse::Request must be derived from Message"); - static_assert( - std::is_base_of<Message, typename TRequestResponse::Response>::value, - "TRequestResponse::Response must be derived from Message"); + const MessageType &type = TRequestResponse::Request::TypeInfo; auto callbacks_accessor = callbacks_.access(); - auto deleted = - callbacks_accessor.remove(typeid(typename TRequestResponse::Request)); + auto deleted = callbacks_accessor.remove(type.id); CHECK(deleted) << "Trying to remove unknown message type callback"; } private: friend class Session; - ConcurrentMap<std::type_index, - std::function<std::unique_ptr<Message>(const Message &)>> - callbacks_; + struct RpcCallback { + MessageType req_type; + std::function<void(const capnp::Message::Reader &, + capnp::Message::Builder *)> + callback; + MessageType res_type; + }; + + ConcurrentMap<uint64_t, RpcCallback> callbacks_; std::mutex mutex_; communication::Server<Session, Server> server_; diff --git a/src/database/counters.cpp b/src/database/counters.cpp index 5ee380ddc..97814b31d 100644 --- a/src/database/counters.cpp +++ b/src/database/counters.cpp @@ -1,23 +1,9 @@ #include "database/counters.hpp" -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/export.hpp" -#include "boost/serialization/utility.hpp" +#include "database/counters_rpc_messages.hpp" namespace database { -RPC_SINGLE_MEMBER_MESSAGE(CountersGetReq, std::string); -RPC_SINGLE_MEMBER_MESSAGE(CountersGetRes, int64_t); -using CountersGetRpc = - communication::rpc::RequestResponse<CountersGetReq, CountersGetRes>; - -using CountersSetReqData = std::pair<std::string, int64_t>; -RPC_SINGLE_MEMBER_MESSAGE(CountersSetReq, CountersSetReqData); -RPC_NO_MEMBER_MESSAGE(CountersSetRes); -using CountersSetRpc = - communication::rpc::RequestResponse<CountersSetReq, CountersSetRes>; - int64_t SingleNodeCounters::Get(const std::string &name) { return counters_.access() .emplace(name, std::make_tuple(name), std::make_tuple(0)) @@ -32,13 +18,16 @@ void SingleNodeCounters::Set(const std::string &name, int64_t value) { MasterCounters::MasterCounters(communication::rpc::Server &server) : rpc_server_(server) { - rpc_server_.Register<CountersGetRpc>([this](const CountersGetReq &req) { - return std::make_unique<CountersGetRes>(Get(req.member)); - }); - rpc_server_.Register<CountersSetRpc>([this](const CountersSetReq &req) { - Set(req.member.first, req.member.second); - return std::make_unique<CountersSetRes>(); - }); + rpc_server_.Register<CountersGetRpc>( + [this](const auto &req_reader, auto *res_builder) { + CountersGetRes res(Get(req_reader.getName())); + res.Save(res_builder); + }); + rpc_server_.Register<CountersSetRpc>( + [this](const auto &req_reader, auto *res_builder) { + Set(req_reader.getName(), req_reader.getValue()); + return std::make_unique<CountersSetRes>(); + }); } WorkerCounters::WorkerCounters( @@ -48,18 +37,12 @@ WorkerCounters::WorkerCounters( int64_t WorkerCounters::Get(const std::string &name) { auto response = master_client_pool_.Call<CountersGetRpc>(name); CHECK(response) << "CountersGetRpc failed"; - return response->member; + return response->value; } void WorkerCounters::Set(const std::string &name, int64_t value) { - auto response = - master_client_pool_.Call<CountersSetRpc>(CountersSetReqData{name, value}); + auto response = master_client_pool_.Call<CountersSetRpc>(name, value); CHECK(response) << "CountersSetRpc failed"; } } // namespace database - -BOOST_CLASS_EXPORT(database::CountersGetReq); -BOOST_CLASS_EXPORT(database::CountersGetRes); -BOOST_CLASS_EXPORT(database::CountersSetReq); -BOOST_CLASS_EXPORT(database::CountersSetRes); diff --git a/src/database/counters.hpp b/src/database/counters.hpp index c5661107b..1da76d7bd 100644 --- a/src/database/counters.hpp +++ b/src/database/counters.hpp @@ -5,7 +5,6 @@ #include <string> #include "communication/rpc/client_pool.hpp" -#include "communication/rpc/messages.hpp" #include "communication/rpc/server.hpp" #include "data_structures/concurrent/concurrent_map.hpp" @@ -45,7 +44,7 @@ class SingleNodeCounters : public Counters { /** Implementation for distributed master. */ class MasterCounters : public SingleNodeCounters { public: - MasterCounters(communication::rpc::Server &server); + explicit MasterCounters(communication::rpc::Server &server); private: communication::rpc::Server &rpc_server_; @@ -54,7 +53,7 @@ class MasterCounters : public SingleNodeCounters { /** Implementation for distributed worker. */ class WorkerCounters : public Counters { public: - WorkerCounters(communication::rpc::ClientPool &master_client_pool); + explicit WorkerCounters(communication::rpc::ClientPool &master_client_pool); int64_t Get(const std::string &name) override; void Set(const std::string &name, int64_t value) override; diff --git a/src/database/counters_rpc_messages.lcp b/src/database/counters_rpc_messages.lcp new file mode 100644 index 000000000..9b1834b83 --- /dev/null +++ b/src/database/counters_rpc_messages.lcp @@ -0,0 +1,23 @@ +#>cpp +#pragma once + +#include <string> + +#include "communication/rpc/messages.hpp" +#include "database/counters_rpc_messages.capnp.h" +cpp<# + +(lcp:namespace database) + +(lcp:capnp-namespace "database") + +(lcp:define-rpc counters-get + (:request ((name "std::string"))) + (:response ((value :int64_t)))) + +(lcp:define-rpc counters-set + (:request ((name "std::string") + (value :int64_t))) + (:response ())) + +(lcp:pop-namespace) ;; database diff --git a/src/database/state_delta.hpp b/src/database/state_delta.hpp deleted file mode 100644 index 7c87e9c58..000000000 --- a/src/database/state_delta.hpp +++ /dev/null @@ -1,183 +0,0 @@ -#pragma once - -#include "communication/bolt/v1/decoder/decoder.hpp" -#include "communication/bolt/v1/encoder/primitive_encoder.hpp" -#include "durability/hashed_file_reader.hpp" -#include "durability/hashed_file_writer.hpp" -#include "storage/address_types.hpp" -#include "storage/gid.hpp" -#include "storage/property_value.hpp" -#include "utils/serialization.hpp" - -namespace database { -/** Describes single change to the database state. Used for durability (WAL) and - * state communication over network in HA and for distributed remote storage - * changes. - * - * Labels, Properties and EdgeTypes are stored both as values (integers) and - * strings (their names). The values are used when applying deltas in a running - * database. Names are used when recovering the database as it's not guaranteed - * that after recovery the old name<->value mapping will be preserved. - * - * TODO: ensure the mapping is preserved after recovery and don't save strings - * in StateDeltas. */ -struct StateDelta { - /** Defines StateDelta type. For each type the comment indicates which values - * need to be stored. All deltas have the transaction_id member, so that's - * omitted in the comment. */ - enum class Type { - TRANSACTION_BEGIN, - TRANSACTION_COMMIT, - TRANSACTION_ABORT, - CREATE_VERTEX, // vertex_id - CREATE_EDGE, // edge_id, from_vertex_id, to_vertex_id, edge_type, - // edge_type_name - ADD_OUT_EDGE, // vertex_id, edge_address, vertex_to_address, edge_type - REMOVE_OUT_EDGE, // vertex_id, edge_address - ADD_IN_EDGE, // vertex_id, edge_address, vertex_from_address, edge_type - REMOVE_IN_EDGE, // vertex_id, edge_address - SET_PROPERTY_VERTEX, // vertex_id, property, property_name, property_value - SET_PROPERTY_EDGE, // edge_id, property, property_name, property_value - // remove property is done by setting a PropertyValue::Null - ADD_LABEL, // vertex_id, label, label_name - REMOVE_LABEL, // vertex_id, label, label_name - REMOVE_VERTEX, // vertex_id, check_empty - REMOVE_EDGE, // edge_id - BUILD_INDEX // label, label_name, property, property_name - }; - - StateDelta() = default; - StateDelta(const enum Type &type, tx::TransactionId tx_id) - : type(type), transaction_id(tx_id) {} - - /** Attempts to decode a StateDelta from the given decoder. Returns the - * decoded value if successful, otherwise returns nullopt. */ - static std::experimental::optional<StateDelta> Decode( - HashedFileReader &reader, - communication::bolt::Decoder<HashedFileReader> &decoder); - - /** Encodes the delta using primitive encoder, and writes out the new hash - * with delta to the writer */ - void Encode( - HashedFileWriter &writer, - communication::bolt::PrimitiveEncoder<HashedFileWriter> &encoder) const; - - static StateDelta TxBegin(tx::TransactionId tx_id); - static StateDelta TxCommit(tx::TransactionId tx_id); - static StateDelta TxAbort(tx::TransactionId tx_id); - static StateDelta CreateVertex(tx::TransactionId tx_id, - gid::Gid vertex_id); - static StateDelta CreateEdge(tx::TransactionId tx_id, gid::Gid edge_id, - gid::Gid vertex_from_id, gid::Gid vertex_to_id, - storage::EdgeType edge_type, - const std::string &edge_type_name); - static StateDelta AddOutEdge(tx::TransactionId tx_id, gid::Gid vertex_id, - storage::VertexAddress vertex_to_address, - storage::EdgeAddress edge_address, - storage::EdgeType edge_type); - static StateDelta RemoveOutEdge(tx::TransactionId tx_id, - gid::Gid vertex_id, - storage::EdgeAddress edge_address); - static StateDelta AddInEdge(tx::TransactionId tx_id, gid::Gid vertex_id, - storage::VertexAddress vertex_from_address, - storage::EdgeAddress edge_address, - storage::EdgeType edge_type); - static StateDelta RemoveInEdge(tx::TransactionId tx_id, gid::Gid vertex_id, - storage::EdgeAddress edge_address); - static StateDelta PropsSetVertex(tx::TransactionId tx_id, - gid::Gid vertex_id, - storage::Property property, - const std::string &property_name, - const PropertyValue &value); - static StateDelta PropsSetEdge(tx::TransactionId tx_id, gid::Gid edge_id, - storage::Property property, - const std::string &property_name, - const PropertyValue &value); - static StateDelta AddLabel(tx::TransactionId tx_id, gid::Gid vertex_id, - storage::Label label, - const std::string &label_name); - static StateDelta RemoveLabel(tx::TransactionId tx_id, gid::Gid vertex_id, - storage::Label label, - const std::string &label_name); - static StateDelta RemoveVertex(tx::TransactionId tx_id, gid::Gid vertex_id, - bool check_empty); - static StateDelta RemoveEdge(tx::TransactionId tx_id, gid::Gid edge_id); - static StateDelta BuildIndex(tx::TransactionId tx_id, storage::Label label, - const std::string &label_name, - storage::Property property, - const std::string &property_name); - - /// Applies CRUD delta to database accessor. Fails on other types of deltas - void Apply(GraphDbAccessor &dba) const; - - // Members valid for every delta. - enum Type type; - tx::TransactionId transaction_id; - - // Members valid only for some deltas, see StateDelta::Type comments above. - // TODO: when preparing the WAL for distributed, most likely remove Gids and - // only keep addresses. - gid::Gid vertex_id; - gid::Gid edge_id; - storage::EdgeAddress edge_address; - gid::Gid vertex_from_id; - storage::VertexAddress vertex_from_address; - gid::Gid vertex_to_id; - storage::VertexAddress vertex_to_address; - storage::EdgeType edge_type; - std::string edge_type_name; - storage::Property property; - std::string property_name; - PropertyValue value = PropertyValue::Null; - storage::Label label; - std::string label_name; - bool check_empty; - - private: - friend class boost::serialization::access; - BOOST_SERIALIZATION_SPLIT_MEMBER(); - template <class TArchive> - void save(TArchive &ar, const unsigned int) const { - ar &type; - ar &transaction_id; - ar &vertex_id; - ar &edge_id; - ar &edge_address; - ar &vertex_from_id; - ar &vertex_from_address; - ar &vertex_to_id; - ar &vertex_to_address; - ar &edge_type; - ar &edge_type_name; - ar &property; - ar &property_name; - utils::SaveTypedValue(ar, value); - ar &label; - ar &label_name; - ar &check_empty; - } - - template <class TArchive> - void load(TArchive &ar, const unsigned int) { - ar &type; - ar &transaction_id; - ar &vertex_id; - ar &edge_id; - ar &edge_address; - ar &vertex_from_id; - ar &vertex_from_address; - ar &vertex_to_id; - ar &vertex_to_address; - ar &edge_type; - ar &edge_type_name; - ar &property; - ar &property_name; - query::TypedValue tv; - utils::LoadTypedValue(ar, tv); - value = tv; - ar &label; - ar &label_name; - ar &check_empty; - } -}; -} // namespace database diff --git a/src/database/state_delta.lcp b/src/database/state_delta.lcp new file mode 100644 index 000000000..f395f4c3c --- /dev/null +++ b/src/database/state_delta.lcp @@ -0,0 +1,179 @@ +#>cpp +#pragma once + +#include "communication/bolt/v1/decoder/decoder.hpp" +#include "communication/bolt/v1/encoder/primitive_encoder.hpp" +#include "database/state_delta.capnp.h" +#include "durability/hashed_file_reader.hpp" +#include "durability/hashed_file_writer.hpp" +#include "storage/address_types.hpp" +#include "storage/gid.hpp" +#include "storage/property_value.hpp" +#include "utils/serialization.hpp" +cpp<# + +(lcp:namespace database) + +(lcp:capnp-namespace "database") + +(lcp:capnp-import 'storage "/storage/serialization.capnp") +(lcp:capnp-import 'dis "/distributed/serialization.capnp") + +(lcp:capnp-type-conversion "tx::TransactionId" "UInt64") +(lcp:capnp-type-conversion "gid::Gid" "UInt64") +(lcp:capnp-type-conversion "storage::Label" "Storage.Common") +(lcp:capnp-type-conversion "storage::EdgeType" "Storage.Common") +(lcp:capnp-type-conversion "storage::Property" "Storage.Common") +(lcp:capnp-type-conversion "storage::EdgeAddress" "Storage.Address") +(lcp:capnp-type-conversion "storage::VertexAddress" "Storage.Address") + +(lcp:define-struct state-delta () + ( + ;; Members valid for every delta. + (type "Type" :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::StateDelta::Type" "Type") + :capnp-load (lcp:capnp-load-enum "capnp::StateDelta::Type" "Type")) + (transaction-id "tx::TransactionId") + ;; Members valid only for some deltas, see StateDelta::Type comments above. + ;; TODO: when preparing the WAL for distributed, most likely remove Gids and + ;; only keep addresses. + (vertex-id "gid::Gid") + (edge-id "gid::Gid") + (edge-address "storage::EdgeAddress") + (vertex-from-id "gid::Gid") + (vertex-from-address "storage::VertexAddress") + (vertex-to-id "gid::Gid") + (vertex-to-address "storage::VertexAddress") + (edge-type "storage::EdgeType") + (edge-type-name "std::string") + (property "storage::Property") + (property-name "std::string") + (value "PropertyValue" :initval "PropertyValue::Null" + :save-fun #>cpp utils::SaveTypedValue(ar, value); cpp<# + :load-fun + #>cpp + query::TypedValue tv; + utils::LoadTypedValue(ar, tv); + value = tv; + cpp<# + :capnp-type "Dis.TypedValue" + :capnp-save + (lambda (builder member) + #>cpp + utils::SaveCapnpTypedValue(${member}, &${builder}); + cpp<#) + :capnp-load + (lambda (reader member) + #>cpp + query::TypedValue tv; + utils::LoadCapnpTypedValue(${reader}, &tv); + ${member} = tv; + cpp<#)) + (label "storage::Label") + (label-name "std::string") + (check-empty :bool)) + (:documentation + "Describes single change to the database state. Used for durability (WAL) and +state communication over network in HA and for distributed remote storage +changes. + +Labels, Properties and EdgeTypes are stored both as values (integers) and +strings (their names). The values are used when applying deltas in a running +database. Names are used when recovering the database as it's not guaranteed +that after recovery the old name<->value mapping will be preserved. + +TODO: ensure the mapping is preserved after recovery and don't save strings +in StateDeltas.") + (:public + (lcp:define-enum type + (transaction-begin + transaction-commit + transaction-abort + create-vertex ;; vertex_id + create-edge ;; edge_id, from_vertex_id, to_vertex_id, edge_type, edge_type_name + add-out-edge ;; vertex_id, edge_address, vertex_to_address, edge_type + remove-out-edge ;; vertex_id, edge_address + add-in-edge ;; vertex_id, edge_address, vertex_from_address, edge_type + remove-in-edge ;; vertex_id, edge_address + set-property-vertex ;; vertex_id, property, property_name, property_value + set-property-edge ;; edge_id, property, property_name, property_value + ;; remove property is done by setting a PropertyValue::Null + add-label ;; vertex_id, label, label_name + remove-label ;; vertex_id, label, label_name + remove-vertex ;; vertex_id, check_empty + remove-edge ;; edge_id + build-index ;; label, label_name, property, property_name + ) + (:documentation + "Defines StateDelta type. For each type the comment indicates which values +need to be stored. All deltas have the transaction_id member, so that's +omitted in the comment.") + (:serialize :capnp)) + #>cpp + StateDelta() = default; + StateDelta(const enum Type &type, tx::TransactionId tx_id) + : type(type), transaction_id(tx_id) {} + + /** Attempts to decode a StateDelta from the given decoder. Returns the + * decoded value if successful, otherwise returns nullopt. */ + static std::experimental::optional<StateDelta> Decode( + HashedFileReader &reader, + communication::bolt::Decoder<HashedFileReader> &decoder); + + /** Encodes the delta using primitive encoder, and writes out the new hash + * with delta to the writer */ + void Encode( + HashedFileWriter &writer, + communication::bolt::PrimitiveEncoder<HashedFileWriter> &encoder) const; + + static StateDelta TxBegin(tx::TransactionId tx_id); + static StateDelta TxCommit(tx::TransactionId tx_id); + static StateDelta TxAbort(tx::TransactionId tx_id); + static StateDelta CreateVertex(tx::TransactionId tx_id, + gid::Gid vertex_id); + static StateDelta CreateEdge(tx::TransactionId tx_id, gid::Gid edge_id, + gid::Gid vertex_from_id, gid::Gid vertex_to_id, + storage::EdgeType edge_type, + const std::string &edge_type_name); + static StateDelta AddOutEdge(tx::TransactionId tx_id, gid::Gid vertex_id, + storage::VertexAddress vertex_to_address, + storage::EdgeAddress edge_address, + storage::EdgeType edge_type); + static StateDelta RemoveOutEdge(tx::TransactionId tx_id, + gid::Gid vertex_id, + storage::EdgeAddress edge_address); + static StateDelta AddInEdge(tx::TransactionId tx_id, gid::Gid vertex_id, + storage::VertexAddress vertex_from_address, + storage::EdgeAddress edge_address, + storage::EdgeType edge_type); + static StateDelta RemoveInEdge(tx::TransactionId tx_id, gid::Gid vertex_id, + storage::EdgeAddress edge_address); + static StateDelta PropsSetVertex(tx::TransactionId tx_id, + gid::Gid vertex_id, + storage::Property property, + const std::string &property_name, + const PropertyValue &value); + static StateDelta PropsSetEdge(tx::TransactionId tx_id, gid::Gid edge_id, + storage::Property property, + const std::string &property_name, + const PropertyValue &value); + static StateDelta AddLabel(tx::TransactionId tx_id, gid::Gid vertex_id, + storage::Label label, + const std::string &label_name); + static StateDelta RemoveLabel(tx::TransactionId tx_id, gid::Gid vertex_id, + storage::Label label, + const std::string &label_name); + static StateDelta RemoveVertex(tx::TransactionId tx_id, gid::Gid vertex_id, + bool check_empty); + static StateDelta RemoveEdge(tx::TransactionId tx_id, gid::Gid edge_id); + static StateDelta BuildIndex(tx::TransactionId tx_id, storage::Label label, + const std::string &label_name, + storage::Property property, + const std::string &property_name); + + /// Applies CRUD delta to database accessor. Fails on other types of deltas + void Apply(GraphDbAccessor &dba) const; + cpp<#) + (:serialize :capnp)) + +(lcp:pop-namespace) ;; database diff --git a/src/database/storage_gc_master.hpp b/src/database/storage_gc_master.hpp index 02b9d513b..81d5635ac 100644 --- a/src/database/storage_gc_master.hpp +++ b/src/database/storage_gc_master.hpp @@ -17,10 +17,11 @@ class StorageGcMaster : public StorageGc { rpc_server_(rpc_server), coordination_(coordination) { rpc_server_.Register<distributed::RanLocalGcRpc>( - [this](const distributed::GcClearedStatusReq &req) { + [this](const auto &req_reader, auto *res_builder) { + distributed::RanLocalGcReq req; + req.Load(req_reader); std::unique_lock<std::mutex> lock(worker_safe_transaction_mutex_); worker_safe_transaction_[req.worker_id] = req.local_oldest_active; - return std::make_unique<distributed::GcClearedStatusRes>(); }); } diff --git a/src/distributed/bfs_rpc_clients.cpp b/src/distributed/bfs_rpc_clients.cpp index b6ccdb98f..c0a29d9eb 100644 --- a/src/distributed/bfs_rpc_clients.cpp +++ b/src/distributed/bfs_rpc_clients.cpp @@ -10,11 +10,11 @@ BfsRpcClients::BfsRpcClients( distributed::RpcWorkerClients *clients) : db_(db), subcursor_storage_(subcursor_storage), clients_(clients) {} -std::unordered_map<int, int64_t> BfsRpcClients::CreateBfsSubcursors( +std::unordered_map<int16_t, int64_t> BfsRpcClients::CreateBfsSubcursors( tx::TransactionId tx_id, query::EdgeAtom::Direction direction, const std::vector<storage::EdgeType> &edge_types, query::GraphView graph_view) { - auto futures = clients_->ExecuteOnWorkers<std::pair<int, int64_t>>( + auto futures = clients_->ExecuteOnWorkers<std::pair<int16_t, int64_t>>( db_->WorkerId(), [tx_id, direction, &edge_types, graph_view](int worker_id, auto &client) { auto res = client.template Call<CreateBfsSubcursorRpc>( @@ -22,7 +22,7 @@ std::unordered_map<int, int64_t> BfsRpcClients::CreateBfsSubcursors( CHECK(res) << "CreateBfsSubcursor RPC failed!"; return std::make_pair(worker_id, res->member); }); - std::unordered_map<int, int64_t> subcursor_ids; + std::unordered_map<int16_t, int64_t> subcursor_ids; subcursor_ids.emplace( db_->WorkerId(), subcursor_storage_->Create(tx_id, direction, edge_types, graph_view)); @@ -34,7 +34,7 @@ std::unordered_map<int, int64_t> BfsRpcClients::CreateBfsSubcursors( } void BfsRpcClients::RegisterSubcursors( - const std::unordered_map<int, int64_t> &subcursor_ids) { + const std::unordered_map<int16_t, int64_t> &subcursor_ids) { auto futures = clients_->ExecuteOnWorkers<void>( db_->WorkerId(), [&subcursor_ids](int worker_id, auto &client) { auto res = client.template Call<RegisterSubcursorsRpc>(subcursor_ids); @@ -45,7 +45,7 @@ void BfsRpcClients::RegisterSubcursors( } void BfsRpcClients::RemoveBfsSubcursors( - const std::unordered_map<int, int64_t> &subcursor_ids) { + const std::unordered_map<int16_t, int64_t> &subcursor_ids) { auto futures = clients_->ExecuteOnWorkers<void>( db_->WorkerId(), [&subcursor_ids](int worker_id, auto &client) { auto res = client.template Call<RemoveBfsSubcursorRpc>( @@ -56,7 +56,7 @@ void BfsRpcClients::RemoveBfsSubcursors( } std::experimental::optional<VertexAccessor> BfsRpcClients::Pull( - int worker_id, int64_t subcursor_id, database::GraphDbAccessor *dba) { + int16_t worker_id, int64_t subcursor_id, database::GraphDbAccessor *dba) { if (worker_id == db_->WorkerId()) { return subcursor_storage_->Get(subcursor_id)->Pull(); } @@ -75,7 +75,7 @@ std::experimental::optional<VertexAccessor> BfsRpcClients::Pull( } bool BfsRpcClients::ExpandLevel( - const std::unordered_map<int, int64_t> &subcursor_ids) { + const std::unordered_map<int16_t, int64_t> &subcursor_ids) { auto futures = clients_->ExecuteOnWorkers<bool>( db_->WorkerId(), [&subcursor_ids](int worker_id, auto &client) { auto res = @@ -92,7 +92,7 @@ bool BfsRpcClients::ExpandLevel( } void BfsRpcClients::SetSource( - const std::unordered_map<int, int64_t> &subcursor_ids, + const std::unordered_map<int16_t, int64_t> &subcursor_ids, storage::VertexAddress source_address) { CHECK(source_address.is_remote()) << "SetSource should be called with global address"; @@ -109,8 +109,8 @@ void BfsRpcClients::SetSource( } bool BfsRpcClients::ExpandToRemoteVertex( - const std::unordered_map<int, int64_t> &subcursor_ids, EdgeAccessor edge, - VertexAccessor vertex) { + const std::unordered_map<int16_t, int64_t> &subcursor_ids, + EdgeAccessor edge, VertexAccessor vertex) { CHECK(!vertex.is_local()) << "ExpandToRemoteVertex should not be called with local vertex"; int worker_id = vertex.address().worker_id(); @@ -137,7 +137,7 @@ PathSegment BuildPathSegment(ReconstructPathRes *res, } PathSegment BfsRpcClients::ReconstructPath( - const std::unordered_map<int, int64_t> &subcursor_ids, + const std::unordered_map<int16_t, int64_t> &subcursor_ids, storage::VertexAddress vertex, database::GraphDbAccessor *dba) { int worker_id = vertex.worker_id(); if (worker_id == db_->WorkerId()) { @@ -147,11 +147,11 @@ PathSegment BfsRpcClients::ReconstructPath( auto res = clients_->GetClientPool(worker_id).Call<ReconstructPathRpc>( subcursor_ids.at(worker_id), vertex); - return BuildPathSegment(res.get(), dba); + return BuildPathSegment(&res.value(), dba); } PathSegment BfsRpcClients::ReconstructPath( - const std::unordered_map<int, int64_t> &subcursor_ids, + const std::unordered_map<int16_t, int64_t> &subcursor_ids, storage::EdgeAddress edge, database::GraphDbAccessor *dba) { int worker_id = edge.worker_id(); if (worker_id == db_->WorkerId()) { @@ -160,11 +160,11 @@ PathSegment BfsRpcClients::ReconstructPath( } auto res = clients_->GetClientPool(worker_id).Call<ReconstructPathRpc>( subcursor_ids.at(worker_id), edge); - return BuildPathSegment(res.get(), dba); + return BuildPathSegment(&res.value(), dba); } void BfsRpcClients::PrepareForExpand( - const std::unordered_map<int, int64_t> &subcursor_ids, bool clear) { + const std::unordered_map<int16_t, int64_t> &subcursor_ids, bool clear) { auto res = clients_->ExecuteOnWorkers<void>( db_->WorkerId(), [clear, &subcursor_ids](int worker_id, auto &client) { auto res = client.template Call<PrepareForExpandRpc>( diff --git a/src/distributed/bfs_rpc_clients.hpp b/src/distributed/bfs_rpc_clients.hpp index 41cce6cd1..a60acdf29 100644 --- a/src/distributed/bfs_rpc_clients.hpp +++ b/src/distributed/bfs_rpc_clients.hpp @@ -19,39 +19,39 @@ class BfsRpcClients { distributed::BfsSubcursorStorage *subcursor_storage, distributed::RpcWorkerClients *clients); - std::unordered_map<int, int64_t> CreateBfsSubcursors( + std::unordered_map<int16_t, int64_t> CreateBfsSubcursors( tx::TransactionId tx_id, query::EdgeAtom::Direction direction, const std::vector<storage::EdgeType> &edge_types, query::GraphView graph_view); void RegisterSubcursors( - const std::unordered_map<int, int64_t> &subcursor_ids); + const std::unordered_map<int16_t, int64_t> &subcursor_ids); void RemoveBfsSubcursors( - const std::unordered_map<int, int64_t> &subcursor_ids); + const std::unordered_map<int16_t, int64_t> &subcursor_ids); std::experimental::optional<VertexAccessor> Pull( - int worker_id, int64_t subcursor_id, database::GraphDbAccessor *dba); + int16_t worker_id, int64_t subcursor_id, database::GraphDbAccessor *dba); - bool ExpandLevel(const std::unordered_map<int, int64_t> &subcursor_ids); + bool ExpandLevel(const std::unordered_map<int16_t, int64_t> &subcursor_ids); - void SetSource(const std::unordered_map<int, int64_t> &subcursor_ids, + void SetSource(const std::unordered_map<int16_t, int64_t> &subcursor_ids, storage::VertexAddress source_address); bool ExpandToRemoteVertex( - const std::unordered_map<int, int64_t> &subcursor_ids, EdgeAccessor edge, - VertexAccessor vertex); + const std::unordered_map<int16_t, int64_t> &subcursor_ids, + EdgeAccessor edge, VertexAccessor vertex); PathSegment ReconstructPath( - const std::unordered_map<int, int64_t> &subcursor_ids, + const std::unordered_map<int16_t, int64_t> &subcursor_ids, storage::EdgeAddress edge, database::GraphDbAccessor *dba); PathSegment ReconstructPath( - const std::unordered_map<int, int64_t> &subcursor_ids, + const std::unordered_map<int16_t, int64_t> &subcursor_ids, storage::VertexAddress vertex, database::GraphDbAccessor *dba); - void PrepareForExpand(const std::unordered_map<int, int64_t> &subcursor_ids, - bool clear); + void PrepareForExpand( + const std::unordered_map<int16_t, int64_t> &subcursor_ids, bool clear); private: database::GraphDb *db_; diff --git a/src/distributed/bfs_rpc_messages.hpp b/src/distributed/bfs_rpc_messages.hpp deleted file mode 100644 index 23f0e6d94..000000000 --- a/src/distributed/bfs_rpc_messages.hpp +++ /dev/null @@ -1,319 +0,0 @@ -#pragma once - -#include <tuple> - -#include "communication/rpc/messages.hpp" -#include "distributed/bfs_subcursor.hpp" -#include "query/plan/operator.hpp" -#include "transactions/type.hpp" -#include "utils/serialization.hpp" - -namespace distributed { - -template <class TElement> -struct SerializedGraphElement { - using AddressT = storage::Address<mvcc::VersionList<TElement>>; - using AccessorT = RecordAccessor<TElement>; - - SerializedGraphElement(AddressT global_address, TElement *old_element_input, - TElement *new_element_input, int worker_id) - : global_address(global_address), - old_element_input(old_element_input), - old_element_output(nullptr), - new_element_input(new_element_input), - new_element_output(nullptr), - worker_id(worker_id) { - CHECK(global_address.is_remote()) - << "Only global addresses should be used with SerializedGraphElement"; - } - - SerializedGraphElement(const AccessorT &accessor) - : SerializedGraphElement(accessor.GlobalAddress(), accessor.GetOld(), - accessor.GetNew(), - accessor.db_accessor().db().WorkerId()) {} - - SerializedGraphElement() {} - - AddressT global_address; - TElement *old_element_input; - std::unique_ptr<TElement> old_element_output; - TElement *new_element_input; - std::unique_ptr<TElement> new_element_output; - int worker_id; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void save(TArchive &ar, unsigned int) const { - ar << global_address; - if (old_element_input) { - ar << true; - SaveElement(ar, *old_element_input, worker_id); - } else { - ar << false; - } - if (new_element_input) { - ar << true; - SaveElement(ar, *new_element_input, worker_id); - } else { - ar << false; - } - } - - template <class TArchive> - void load(TArchive &ar, unsigned int) { - ar >> global_address; - static_assert(std::is_same<TElement, Vertex>::value || - std::is_same<TElement, Edge>::value, - "TElement should be either Vertex or Edge"); - bool has_old; - ar >> has_old; - if (has_old) { - if constexpr (std::is_same<TElement, Vertex>::value) { - old_element_output = std::move(LoadVertex(ar)); - } else { - old_element_output = std::move(LoadEdge(ar)); - } - } - bool has_new; - ar >> has_new; - if (has_new) { - if constexpr (std::is_same<TElement, Vertex>::value) { - new_element_output = std::move(LoadVertex(ar)); - } else { - new_element_output = std::move(LoadEdge(ar)); - } - } - } - - BOOST_SERIALIZATION_SPLIT_MEMBER() -}; // namespace distributed - -using SerializedVertex = SerializedGraphElement<Vertex>; -using SerializedEdge = SerializedGraphElement<Edge>; - -struct CreateBfsSubcursorReq : public communication::rpc::Message { - tx::TransactionId tx_id; - query::EdgeAtom::Direction direction; - std::vector<storage::EdgeType> edge_types; - query::GraphView graph_view; - - CreateBfsSubcursorReq(tx::TransactionId tx_id, - query::EdgeAtom::Direction direction, - std::vector<storage::EdgeType> edge_types, - query::GraphView graph_view) - : tx_id(tx_id), - direction(direction), - edge_types(std::move(edge_types)), - graph_view(graph_view) {} - - private: - friend class boost::serialization::access; - - CreateBfsSubcursorReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &tx_id &direction &graph_view; - } -}; -RPC_SINGLE_MEMBER_MESSAGE(CreateBfsSubcursorRes, int64_t); - -using CreateBfsSubcursorRpc = - communication::rpc::RequestResponse<CreateBfsSubcursorReq, - CreateBfsSubcursorRes>; - -struct RegisterSubcursorsReq : public communication::rpc::Message { - std::unordered_map<int, int64_t> subcursor_ids; - - RegisterSubcursorsReq(std::unordered_map<int, int64_t> subcursor_ids) - : subcursor_ids(std::move(subcursor_ids)) {} - - private: - friend class boost::serialization::access; - - RegisterSubcursorsReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &subcursor_ids; - } -}; -RPC_NO_MEMBER_MESSAGE(RegisterSubcursorsRes); - -using RegisterSubcursorsRpc = - communication::rpc::RequestResponse<RegisterSubcursorsReq, - RegisterSubcursorsRes>; - -RPC_SINGLE_MEMBER_MESSAGE(RemoveBfsSubcursorReq, int64_t); -RPC_NO_MEMBER_MESSAGE(RemoveBfsSubcursorRes); - -using RemoveBfsSubcursorRpc = - communication::rpc::RequestResponse<RemoveBfsSubcursorReq, - RemoveBfsSubcursorRes>; - -RPC_SINGLE_MEMBER_MESSAGE(ExpandLevelReq, int64_t); -RPC_SINGLE_MEMBER_MESSAGE(ExpandLevelRes, bool); - -using ExpandLevelRpc = - communication::rpc::RequestResponse<ExpandLevelReq, ExpandLevelRes>; - -RPC_SINGLE_MEMBER_MESSAGE(SubcursorPullReq, int64_t); - -struct SubcursorPullRes : public communication::rpc::Message { - SubcursorPullRes(const VertexAccessor &vertex) - : vertex(std::experimental::in_place, vertex) {} - - SubcursorPullRes() : vertex(std::experimental::nullopt) {} - - std::experimental::optional<SerializedVertex> vertex; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &vertex; - } -}; - -using SubcursorPullRpc = - communication::rpc::RequestResponse<SubcursorPullReq, SubcursorPullRes>; - -struct SetSourceReq : public communication::rpc::Message { - int64_t subcursor_id; - storage::VertexAddress source; - - SetSourceReq(int64_t subcursor_id, storage::VertexAddress source) - : subcursor_id(subcursor_id), source(source) {} - - private: - friend class boost::serialization::access; - - SetSourceReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &subcursor_id &source; - } -}; -RPC_NO_MEMBER_MESSAGE(SetSourceRes); - -using SetSourceRpc = - communication::rpc::RequestResponse<SetSourceReq, SetSourceRes>; - -struct ExpandToRemoteVertexReq : public communication::rpc::Message { - int64_t subcursor_id; - storage::EdgeAddress edge; - storage::VertexAddress vertex; - - ExpandToRemoteVertexReq(int64_t subcursor_id, storage::EdgeAddress edge, - storage::VertexAddress vertex) - : subcursor_id(subcursor_id), edge(edge), vertex(vertex) {} - - private: - friend class boost::serialization::access; - - ExpandToRemoteVertexReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &subcursor_id &edge &vertex; - } -}; -RPC_SINGLE_MEMBER_MESSAGE(ExpandToRemoteVertexRes, bool); - -using ExpandToRemoteVertexRpc = - communication::rpc::RequestResponse<ExpandToRemoteVertexReq, - ExpandToRemoteVertexRes>; - -struct ReconstructPathReq : public communication::rpc::Message { - int64_t subcursor_id; - std::experimental::optional<storage::VertexAddress> vertex; - std::experimental::optional<storage::EdgeAddress> edge; - - ReconstructPathReq(int64_t subcursor_id, storage::VertexAddress vertex) - : subcursor_id(subcursor_id), - vertex(vertex), - edge(std::experimental::nullopt) {} - - ReconstructPathReq(int64_t subcursor_id, storage::EdgeAddress edge) - : subcursor_id(subcursor_id), - vertex(std::experimental::nullopt), - edge(edge) {} - - private: - friend class boost::serialization::access; - - ReconstructPathReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &subcursor_id &vertex &edge; - } -}; - -struct ReconstructPathRes : public communication::rpc::Message { - int64_t subcursor_id; - std::vector<SerializedEdge> edges; - std::experimental::optional<storage::VertexAddress> next_vertex; - std::experimental::optional<storage::EdgeAddress> next_edge; - - ReconstructPathRes( - const std::vector<EdgeAccessor> &edge_accessors, - std::experimental::optional<storage::VertexAddress> next_vertex, - std::experimental::optional<storage::EdgeAddress> next_edge) - : next_vertex(std::move(next_vertex)), next_edge(std::move(next_edge)) { - CHECK(!static_cast<bool>(next_vertex) || !static_cast<bool>(next_edge)) - << "At most one of `next_vertex` and `next_edge` should be set"; - for (const auto &edge : edge_accessors) { - edges.emplace_back(edge); - } - } - - private: - friend class boost::serialization::access; - - ReconstructPathRes() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &edges &next_vertex &next_edge; - } -}; - -using ReconstructPathRpc = - communication::rpc::RequestResponse<ReconstructPathReq, ReconstructPathRes>; - -struct PrepareForExpandReq : public communication::rpc::Message { - int64_t subcursor_id; - bool clear; - - PrepareForExpandReq(int64_t subcursor_id, bool clear) - : subcursor_id(subcursor_id), clear(clear) {} - - private: - friend class boost::serialization::access; - - PrepareForExpandReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &subcursor_id &clear; - } -}; -RPC_NO_MEMBER_MESSAGE(PrepareForExpandRes); - -using PrepareForExpandRpc = - communication::rpc::RequestResponse<PrepareForExpandReq, - PrepareForExpandRes>; -} // namespace distributed diff --git a/src/distributed/bfs_rpc_messages.lcp b/src/distributed/bfs_rpc_messages.lcp new file mode 100644 index 000000000..4cb7c42b7 --- /dev/null +++ b/src/distributed/bfs_rpc_messages.lcp @@ -0,0 +1,280 @@ +#>cpp +#pragma once + +#include <tuple> + +#include "communication/rpc/messages.hpp" +#include "distributed/bfs_rpc_messages.capnp.h" +#include "distributed/bfs_subcursor.hpp" +#include "query/plan/operator.hpp" +#include "transactions/type.hpp" +#include "utils/serialization.hpp" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:capnp-import 'ast "/query/frontend/ast/ast.capnp") +(lcp:capnp-import 'dis "/distributed/serialization.capnp") +(lcp:capnp-import 'query "/query/common.capnp") +(lcp:capnp-import 'storage "/storage/serialization.capnp") +(lcp:capnp-import 'utils "/utils/serialization.capnp") + +(lcp:capnp-type-conversion "storage::EdgeAddress" "Storage.Address") +(lcp:capnp-type-conversion "storage::VertexAddress" "Storage.Address") + +(defun save-element (builder member) + #>cpp + if (${member}) { + if constexpr (std::is_same<TElement, Vertex>::value) { + auto builder = ${builder}.initVertex(); + SaveVertex(*${member}, &builder, worker_id); + } else { + auto builder = ${builder}.initEdge(); + SaveEdge(*${member}, &builder, worker_id); + } + } else { + ${builder}.setNull(); + } + cpp<#) + +(defun load-element (reader member) + (let ((output-member (cl-ppcre:regex-replace "input$" member "output"))) + #>cpp + if (!${reader}.isNull()) { + if constexpr (std::is_same<TElement, Vertex>::value) { + const auto reader = ${reader}.getVertex(); + ${output-member} = LoadVertex(reader); + } else { + const auto reader = ${reader}.getEdge(); + ${output-member} = LoadEdge(reader); + } + } + cpp<#)) + +(lcp:define-struct (serialized-graph-element t-element) () + ((global-address "storage::Address<mvcc::VersionList<TElement>>" + :capnp-type "Storage.Address") + (old-element-input "TElement *" + :save-fun + "if (old_element_input) { + ar << true; + SaveElement(ar, *old_element_input, worker_id); + } else { + ar << false; + }" + :load-fun "" + :capnp-type '((null "Void") (vertex "Dis.Vertex") (edge "Dis.Edge")) + :capnp-save #'save-element :capnp-load #'load-element) + (old-element-output "std::unique_ptr<TElement>" + :save-fun "" + :load-fun + "bool has_old; + ar >> has_old; + if (has_old) { + if constexpr (std::is_same<TElement, Vertex>::value) { + old_element_output = std::move(LoadVertex(ar)); + } else { + old_element_output = std::move(LoadEdge(ar)); + } + }" + :capnp-save :dont-save) + (new-element-input "TElement *" + :save-fun + "if (new_element_input) { + ar << true; + SaveElement(ar, *new_element_input, worker_id); + } else { + ar << false; + }" + :load-fun "" + :capnp-type '((null "Void") (vertex "Dis.Vertex") (edge "Dis.Edge")) + :capnp-save #'save-element :capnp-load #'load-element) + (new-element-output "std::unique_ptr<TElement>" + :save-fun "" + :load-fun + "bool has_new; + ar >> has_new; + if (has_new) { + if constexpr (std::is_same<TElement, Vertex>::value) { + new_element_output = std::move(LoadVertex(ar)); + } else { + new_element_output = std::move(LoadEdge(ar)); + } + }" + :capnp-save :dont-save) + (worker-id :int16_t :save-fun "" :load-fun "" :capnp-save :dont-save)) + (:public + #>cpp + SerializedGraphElement(storage::Address<mvcc::VersionList<TElement>> global_address, + TElement *old_element_input, TElement *new_element_input, + int16_t worker_id) + : global_address(global_address), + old_element_input(old_element_input), + old_element_output(nullptr), + new_element_input(new_element_input), + new_element_output(nullptr), + worker_id(worker_id) { + CHECK(global_address.is_remote()) + << "Only global addresses should be used with SerializedGraphElement"; + } + + SerializedGraphElement(const RecordAccessor<TElement> &accessor) + : SerializedGraphElement(accessor.GlobalAddress(), accessor.GetOld(), + accessor.GetNew(), + accessor.db_accessor().db().WorkerId()) {} + + SerializedGraphElement() {} + cpp<#) + (:serialize :capnp :type-args '(vertex edge))) + +#>cpp +using SerializedVertex = SerializedGraphElement<Vertex>; +using SerializedEdge = SerializedGraphElement<Edge>; +cpp<# + +(lcp:define-rpc create-bfs-subcursor + (:request + ((tx-id "tx::TransactionId" :capnp-type "UInt64") + (direction "query::EdgeAtom::Direction" + :capnp-type "Ast.EdgeAtom.Direction" :capnp-init nil + :capnp-save (lcp:capnp-save-enum "::query::capnp::EdgeAtom::Direction" + "query::EdgeAtom::Direction" + '(in out both)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::EdgeAtom::Direction" + "query::EdgeAtom::Direction" + '(in out both))) + ;; TODO(mtomic): Why isn't edge-types serialized? + (edge-types "std::vector<storage::EdgeType>" + :save-fun "" :load-fun "" :capnp-save :dont-save) + (graph-view "query::GraphView" + :capnp-type "Query.GraphView" :capnp-init nil + :capnp-save (lcp:capnp-save-enum "::query::capnp::GraphView" + "query::GraphView" + '(old new)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::GraphView" + "query::GraphView" + '(old new))))) + (:response ((member :int64_t)))) + +(lcp:define-rpc register-subcursors + (:request ((subcursor-ids "std::unordered_map<int16_t, int64_t>" + :capnp-type "Utils.Map(Utils.BoxInt16, Utils.BoxInt64)" + :capnp-save + (lambda (builder member) + #>cpp + utils::SaveMap<utils::capnp::BoxInt16, utils::capnp::BoxInt64>( + ${member}, &${builder}, + [](auto *builder, const auto &entry) { + auto key_builder = builder->initKey(); + key_builder.setValue(entry.first); + auto value_builder = builder->initValue(); + value_builder.setValue(entry.second); + }); + cpp<#) + :capnp-load + (lambda (reader member) + #>cpp + utils::LoadMap<utils::capnp::BoxInt16, utils::capnp::BoxInt64>( + &${member}, ${reader}, + [](const auto &reader) { + int16_t key = reader.getKey().getValue(); + int64_t value = reader.getValue().getValue(); + return std::make_pair(key, value); + }); + cpp<#)))) + (:response ())) + +(lcp:define-rpc remove-bfs-subcursor + (:request ((member :int64_t))) + (:response ())) + +(lcp:define-rpc expand-level + (:request ((member :int64_t))) + (:response ((member :bool)))) + +(lcp:define-rpc subcursor-pull + (:request ((member :int64_t))) + (:response ((vertex "std::experimental::optional<SerializedVertex>" :initarg :move + :capnp-type "Utils.Optional(SerializedGraphElement)" + :capnp-save (lcp:capnp-save-optional "capnp::SerializedGraphElement" "SerializedVertex") + :capnp-load (lcp:capnp-load-optional "capnp::SerializedGraphElement" "SerializedVertex"))))) +(lcp:define-rpc set-source + (:request + ((subcursor-id :int64_t) + (source "storage::VertexAddress"))) + (:response ())) + +(lcp:define-rpc expand-to-remote-vertex + (:request + ((subcursor-id :int64_t) + (edge "storage::EdgeAddress") + (vertex "storage::VertexAddress"))) + (:response ((member :bool)))) + +(lcp:define-rpc reconstruct-path + (:request + ((subcursor-id :int64_t) + (vertex "std::experimental::optional<storage::VertexAddress>" + :capnp-save (lcp:capnp-save-optional "storage::capnp::Address" "storage::VertexAddress") + :capnp-load (lcp:capnp-load-optional "storage::capnp::Address" "storage::VertexAddress")) + (edge "std::experimental::optional<storage::EdgeAddress>" + :capnp-save (lcp:capnp-save-optional "storage::capnp::Address" "storage::EdgeAddress") + :capnp-load (lcp:capnp-load-optional "storage::capnp::Address" "storage::EdgeAddress"))) + (:public + #>cpp + using Capnp = capnp::ReconstructPathReq; + static const communication::rpc::MessageType TypeInfo; + + ReconstructPathReq() {} + + ReconstructPathReq(int64_t subcursor_id, storage::VertexAddress vertex) + : subcursor_id(subcursor_id), + vertex(vertex), + edge(std::experimental::nullopt) {} + + ReconstructPathReq(int64_t subcursor_id, storage::EdgeAddress edge) + : subcursor_id(subcursor_id), + vertex(std::experimental::nullopt), + edge(edge) {} + cpp<#)) + (:response + ((subcursor-id :int64_t ;; TODO(mtomic): Unused? + :save-fun "" :load-fun "" :capnp-save :dont-save) + (edges "std::vector<SerializedEdge>" :capnp-type "List(SerializedGraphElement)" + :capnp-save (lcp:capnp-save-vector "capnp::SerializedGraphElement" "SerializedEdge") + :capnp-load (lcp:capnp-load-vector "capnp::SerializedGraphElement" "SerializedEdge")) + (next-vertex "std::experimental::optional<storage::VertexAddress>" + :capnp-save (lcp:capnp-save-optional "storage::capnp::Address" "storage::VertexAddress") + :capnp-load (lcp:capnp-load-optional "storage::capnp::Address" "storage::VertexAddress")) + (next-edge "std::experimental::optional<storage::EdgeAddress>" + :capnp-save (lcp:capnp-save-optional "storage::capnp::Address" "storage::EdgeAddress") + :capnp-load (lcp:capnp-load-optional "storage::capnp::Address" "storage::EdgeAddress"))) + (:public + #>cpp + using Capnp = capnp::ReconstructPathRes; + static const communication::rpc::MessageType TypeInfo; + + ReconstructPathRes() {} + + ReconstructPathRes( + const std::vector<EdgeAccessor> &edge_accessors, + std::experimental::optional<storage::VertexAddress> next_vertex, + std::experimental::optional<storage::EdgeAddress> next_edge) + : next_vertex(std::move(next_vertex)), next_edge(std::move(next_edge)) { + CHECK(!static_cast<bool>(next_vertex) || !static_cast<bool>(next_edge)) + << "At most one of `next_vertex` and `next_edge` should be set"; + for (const auto &edge : edge_accessors) { + edges.emplace_back(edge); + } + } + cpp<#))) + +(lcp:define-rpc prepare-for-expand + (:request + ((subcursor-id :int64_t) + (clear :bool))) + (:response ())) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/bfs_rpc_server.hpp b/src/distributed/bfs_rpc_server.hpp index ff9b38230..2c6832030 100644 --- a/src/distributed/bfs_rpc_server.hpp +++ b/src/distributed/bfs_rpc_server.hpp @@ -20,52 +20,78 @@ class BfsRpcServer { BfsSubcursorStorage *subcursor_storage) : db_(db), server_(server), subcursor_storage_(subcursor_storage) { server_->Register<CreateBfsSubcursorRpc>( - [this](const CreateBfsSubcursorReq &req) { - return std::make_unique<CreateBfsSubcursorRes>( - subcursor_storage_->Create(req.tx_id, req.direction, - req.edge_types, req.graph_view)); + [this](const auto &req_reader, auto *res_builder) { + CreateBfsSubcursorReq req; + req.Load(req_reader); + CreateBfsSubcursorRes res(subcursor_storage_->Create( + req.tx_id, req.direction, req.edge_types, req.graph_view)); + res.Save(res_builder); }); server_->Register<RegisterSubcursorsRpc>( - [this](const RegisterSubcursorsReq &req) { + [this](const auto &req_reader, auto *res_builder) { + RegisterSubcursorsReq req; + req.Load(req_reader); subcursor_storage_->Get(req.subcursor_ids.at(db_->WorkerId())) ->RegisterSubcursors(req.subcursor_ids); - return std::make_unique<RegisterSubcursorsRes>(); + RegisterSubcursorsRes res; + res.Save(res_builder); }); server_->Register<RemoveBfsSubcursorRpc>( - [this](const RemoveBfsSubcursorReq &req) { + [this](const auto &req_reader, auto *res_builder) { + RemoveBfsSubcursorReq req; + req.Load(req_reader); subcursor_storage_->Erase(req.member); - return std::make_unique<RemoveBfsSubcursorRes>(); + RemoveBfsSubcursorRes res; + res.Save(res_builder); }); - server_->Register<SetSourceRpc>([this](const SetSourceReq &req) { - subcursor_storage_->Get(req.subcursor_id)->SetSource(req.source); - return std::make_unique<SetSourceRes>(); + server_->Register<SetSourceRpc>( + [this](const auto &req_reader, auto *res_builder) { + SetSourceReq req; + req.Load(req_reader); + subcursor_storage_->Get(req.subcursor_id)->SetSource(req.source); + SetSourceRes res; + res.Save(res_builder); + }); + + server_->Register<ExpandLevelRpc>([this](const auto &req_reader, + auto *res_builder) { + ExpandLevelReq req; + req.Load(req_reader); + ExpandLevelRes res(subcursor_storage_->Get(req.member)->ExpandLevel()); + res.Save(res_builder); }); - server_->Register<ExpandLevelRpc>([this](const ExpandLevelReq &req) { - return std::make_unique<ExpandLevelRes>( - subcursor_storage_->Get(req.member)->ExpandLevel()); - }); - - server_->Register<SubcursorPullRpc>([this](const SubcursorPullReq &req) { - auto vertex = subcursor_storage_->Get(req.member)->Pull(); - if (!vertex) { - return std::make_unique<SubcursorPullRes>(); - } - return std::make_unique<SubcursorPullRes>(*vertex); - }); + server_->Register<SubcursorPullRpc>( + [this](const auto &req_reader, auto *res_builder) { + SubcursorPullReq req; + req.Load(req_reader); + auto vertex = subcursor_storage_->Get(req.member)->Pull(); + if (!vertex) { + SubcursorPullRes res; + res.Save(res_builder); + return; + } + SubcursorPullRes res(*vertex); + res.Save(res_builder); + }); server_->Register<ExpandToRemoteVertexRpc>( - [this](const ExpandToRemoteVertexReq &req) { - return std::make_unique<ExpandToRemoteVertexRes>( + [this](const auto &req_reader, auto *res_builder) { + ExpandToRemoteVertexReq req; + req.Load(req_reader); + ExpandToRemoteVertexRes res( subcursor_storage_->Get(req.subcursor_id) ->ExpandToLocalVertex(req.edge, req.vertex)); + res.Save(res_builder); }); - server_->Register<ReconstructPathRpc>([this]( - const ReconstructPathReq &req) { + server_->Register<ReconstructPathRpc>([this](const auto &req_reader, + auto *res_builder) { + ReconstructPathReq req; + req.Load(req_reader); auto subcursor = subcursor_storage_->Get(req.subcursor_id); PathSegment result; if (req.vertex) { @@ -75,14 +101,18 @@ class BfsRpcServer { } else { LOG(FATAL) << "`edge` or `vertex` should be set in ReconstructPathReq"; } - return std::make_unique<ReconstructPathRes>( - result.edges, result.next_vertex, result.next_edge); + ReconstructPathRes res(result.edges, result.next_vertex, + result.next_edge); + res.Save(res_builder); }); - server_->Register<PrepareForExpandRpc>([this]( - const PrepareForExpandReq &req) { + server_->Register<PrepareForExpandRpc>([this](const auto &req_reader, + auto *res_builder) { + PrepareForExpandReq req; + req.Load(req_reader); subcursor_storage_->Get(req.subcursor_id)->PrepareForExpand(req.clear); - return std::make_unique<PrepareForExpandRes>(); + PrepareForExpandRes res; + res.Save(res_builder); }); } diff --git a/src/distributed/bfs_subcursor.hpp b/src/distributed/bfs_subcursor.hpp index d92e40692..7959e537d 100644 --- a/src/distributed/bfs_subcursor.hpp +++ b/src/distributed/bfs_subcursor.hpp @@ -35,7 +35,7 @@ class ExpandBfsSubcursor { query::GraphView graph_view); // Stores subcursor ids of other workers. - void RegisterSubcursors(std::unordered_map<int, int64_t> subcursor_ids) { + void RegisterSubcursors(std::unordered_map<int16_t, int64_t> subcursor_ids) { subcursor_ids_ = std::move(subcursor_ids); } @@ -91,7 +91,7 @@ class ExpandBfsSubcursor { database::GraphDbAccessor dba_; /// IDs of subcursors on other workers, used when sending RPCs. - std::unordered_map<int, int64_t> subcursor_ids_; + std::unordered_map<int16_t, int64_t> subcursor_ids_; query::EdgeAtom::Direction direction_; std::vector<storage::EdgeType> edge_types_; diff --git a/src/distributed/cluster_discovery_master.cpp b/src/distributed/cluster_discovery_master.cpp index 53e48d208..9c03a1e6f 100644 --- a/src/distributed/cluster_discovery_master.cpp +++ b/src/distributed/cluster_discovery_master.cpp @@ -11,7 +11,10 @@ ClusterDiscoveryMaster::ClusterDiscoveryMaster( : server_(server), coordination_(coordination), rpc_worker_clients_(rpc_worker_clients) { - server_.Register<RegisterWorkerRpc>([this](const RegisterWorkerReq &req) { + server_.Register<RegisterWorkerRpc>([this](const auto &req_reader, + auto *res_builder) { + RegisterWorkerReq req; + req.Load(req_reader); bool registration_successful = this->coordination_.RegisterWorker(req.desired_worker_id, req.endpoint); @@ -24,15 +27,15 @@ ClusterDiscoveryMaster::ClusterDiscoveryMaster( }); } - return std::make_unique<RegisterWorkerRes>( - registration_successful, this->coordination_.RecoveryInfo(), - this->coordination_.GetWorkers()); + RegisterWorkerRes res(registration_successful, + this->coordination_.RecoveryInfo(), + this->coordination_.GetWorkers()); + res.Save(res_builder); }); server_.Register<NotifyWorkerRecoveredRpc>( - [this](const NotifyWorkerRecoveredReq &req) { - this->coordination_.WorkerRecovered(req.member); - return std::make_unique<NotifyWorkerRecoveredRes>(); + [this](const auto &req_reader, auto *res_builder) { + this->coordination_.WorkerRecovered(req_reader.getMember()); }); } diff --git a/src/distributed/cluster_discovery_worker.cpp b/src/distributed/cluster_discovery_worker.cpp index 7184c3ab4..85746797c 100644 --- a/src/distributed/cluster_discovery_worker.cpp +++ b/src/distributed/cluster_discovery_worker.cpp @@ -8,10 +8,12 @@ ClusterDiscoveryWorker::ClusterDiscoveryWorker( Server &server, WorkerCoordination &coordination, communication::rpc::ClientPool &client_pool) : server_(server), coordination_(coordination), client_pool_(client_pool) { - server_.Register<ClusterDiscoveryRpc>([this](const ClusterDiscoveryReq &req) { - this->coordination_.RegisterWorker(req.worker_id, req.endpoint); - return std::make_unique<ClusterDiscoveryRes>(); - }); + server_.Register<ClusterDiscoveryRpc>( + [this](const auto &req_reader, auto *res_builder) { + ClusterDiscoveryReq req; + req.Load(req_reader); + this->coordination_.RegisterWorker(req.worker_id, req.endpoint); + }); } void ClusterDiscoveryWorker::RegisterWorker(int worker_id) { diff --git a/src/distributed/coordination_rpc_messages.hpp b/src/distributed/coordination_rpc_messages.hpp deleted file mode 100644 index 4bd94b5fc..000000000 --- a/src/distributed/coordination_rpc_messages.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include <experimental/optional> -#include <unordered_map> - -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" -#include "boost/serialization/unordered_map.hpp" - -#include "communication/rpc/messages.hpp" -#include "durability/recovery.hpp" -#include "io/network/endpoint.hpp" - -namespace distributed { - -using communication::rpc::Message; -using Endpoint = io::network::Endpoint; - -struct RegisterWorkerReq : public Message { - // Set desired_worker_id to -1 to get an automatically assigned ID. - RegisterWorkerReq(int desired_worker_id, const Endpoint &endpoint) - : desired_worker_id(desired_worker_id), endpoint(endpoint) {} - int desired_worker_id; - Endpoint endpoint; - - private: - friend class boost::serialization::access; - RegisterWorkerReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar &desired_worker_id; - ar &endpoint; - } -}; - -struct RegisterWorkerRes : public Message { - RegisterWorkerRes( - bool registration_successful, - std::experimental::optional<durability::RecoveryInfo> recovery_info, - std::unordered_map<int, Endpoint> workers) - : registration_successful(registration_successful), - recovery_info(recovery_info), - workers(std::move(workers)) {} - - bool registration_successful; - std::experimental::optional<durability::RecoveryInfo> recovery_info; - std::unordered_map<int, Endpoint> workers; - - private: - friend class boost::serialization::access; - RegisterWorkerRes() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar ®istration_successful; - ar &recovery_info; - ar &workers; - } -}; - -struct ClusterDiscoveryReq : public Message { - ClusterDiscoveryReq(int worker_id, Endpoint endpoint) - : worker_id(worker_id), endpoint(endpoint) {} - - int worker_id; - Endpoint endpoint; - - private: - friend class boost::serialization::access; - ClusterDiscoveryReq() {} - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar &worker_id; - ar &endpoint; - } -}; - -RPC_NO_MEMBER_MESSAGE(ClusterDiscoveryRes); - -RPC_NO_MEMBER_MESSAGE(StopWorkerReq); -RPC_NO_MEMBER_MESSAGE(StopWorkerRes); - -RPC_SINGLE_MEMBER_MESSAGE(NotifyWorkerRecoveredReq, int); -RPC_NO_MEMBER_MESSAGE(NotifyWorkerRecoveredRes); - -using RegisterWorkerRpc = - communication::rpc::RequestResponse<RegisterWorkerReq, RegisterWorkerRes>; -using StopWorkerRpc = - communication::rpc::RequestResponse<StopWorkerReq, StopWorkerRes>; -using NotifyWorkerRecoveredRpc = - communication::rpc::RequestResponse<NotifyWorkerRecoveredReq, - NotifyWorkerRecoveredRes>; -using ClusterDiscoveryRpc = - communication::rpc::RequestResponse<ClusterDiscoveryReq, - ClusterDiscoveryRes>; -} // namespace distributed diff --git a/src/distributed/coordination_rpc_messages.lcp b/src/distributed/coordination_rpc_messages.lcp new file mode 100644 index 000000000..8237740cb --- /dev/null +++ b/src/distributed/coordination_rpc_messages.lcp @@ -0,0 +1,72 @@ +#>cpp +#pragma once + +#include <experimental/optional> +#include <unordered_map> + +#include "communication/rpc/messages.hpp" +#include "distributed/coordination_rpc_messages.capnp.h" +#include "durability/recovery.hpp" +#include "io/network/endpoint.hpp" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:capnp-import 'dur "/durability/recovery.capnp") +(lcp:capnp-import 'io "/io/network/endpoint.capnp") +(lcp:capnp-import 'utils "/utils/serialization.capnp") + +(lcp:define-rpc register-worker + (:request + ((desired-worker-id :int16_t) + (endpoint "io::network::Endpoint" :capnp-type "Io.Endpoint"))) + (:response + ((registration-successful :bool) + (recovery-info "std::experimental::optional<durability::RecoveryInfo>" + :capnp-type "Utils.Optional(Dur.RecoveryInfo)" + :capnp-save (lcp:capnp-save-optional "durability::capnp::RecoveryInfo" + "durability::RecoveryInfo") + :capnp-load (lcp:capnp-load-optional "durability::capnp::RecoveryInfo" + "durability::RecoveryInfo")) + (workers "std::unordered_map<int, io::network::Endpoint>" + :capnp-type "Utils.Map(Utils.BoxInt16, Io.Endpoint)" + :capnp-save + (lambda (builder member) + #>cpp + utils::SaveMap<utils::capnp::BoxInt16, io::network::capnp::Endpoint>(${member}, &${builder}, + [](auto *builder, const auto &entry) { + auto key_builder = builder->initKey(); + key_builder.setValue(entry.first); + auto value_builder = builder->initValue(); + entry.second.Save(&value_builder); + }); + cpp<#) + :capnp-load + (lambda (reader member) + #>cpp + utils::LoadMap<utils::capnp::BoxInt16, io::network::capnp::Endpoint>(&${member}, ${reader}, + [](const auto &reader) { + io::network::Endpoint value; + value.Load(reader.getValue()); + return std::make_pair(reader.getKey().getValue(), value); + }); + cpp<#))))) + +(lcp:define-rpc cluster-discovery + (:request + ((worker-id :int16_t) + (endpoint "io::network::Endpoint" :capnp-type "Io.Endpoint"))) + (:response ())) + +(lcp:define-rpc stop-worker + (:request ()) + (:response ())) + +(lcp:define-rpc notify-worker-recovered + (:request ((member :int64_t))) + (:response ())) + +(lcp:pop-namespace) ;; distributed + diff --git a/src/distributed/coordination_worker.cpp b/src/distributed/coordination_worker.cpp index 4ae35a923..a094a20c4 100644 --- a/src/distributed/coordination_worker.cpp +++ b/src/distributed/coordination_worker.cpp @@ -27,19 +27,18 @@ void WorkerCoordination::WaitForShutdown() { std::condition_variable cv; bool shutdown = false; - server_.Register<StopWorkerRpc>([&](const StopWorkerReq &) { + server_.Register<StopWorkerRpc>([&](const auto &req_reader, auto *res_builder) { std::unique_lock<std::mutex> lk(mutex); shutdown = true; lk.unlock(); cv.notify_one(); - return std::make_unique<StopWorkerRes>(); }); std::unique_lock<std::mutex> lk(mutex); cv.wait(lk, [&shutdown] { return shutdown; }); } -Endpoint WorkerCoordination::GetEndpoint(int worker_id) { +io::network::Endpoint WorkerCoordination::GetEndpoint(int worker_id) { std::lock_guard<std::mutex> guard(lock_); return Coordination::GetEndpoint(worker_id); } diff --git a/src/distributed/data_rpc_clients.cpp b/src/distributed/data_rpc_clients.cpp index 5ee6fae8a..ac3ffa4ff 100644 --- a/src/distributed/data_rpc_clients.cpp +++ b/src/distributed/data_rpc_clients.cpp @@ -14,7 +14,7 @@ std::unique_ptr<Edge> DataRpcClients::RemoteElement(int worker_id, auto response = clients_.GetClientPool(worker_id).Call<EdgeRpc>(TxGidPair{tx_id, gid}); CHECK(response) << "EdgeRpc failed"; - return std::move(response->name_output_); + return std::move(response->edge_output); } template <> @@ -24,7 +24,7 @@ std::unique_ptr<Vertex> DataRpcClients::RemoteElement(int worker_id, auto response = clients_.GetClientPool(worker_id).Call<VertexRpc>(TxGidPair{tx_id, gid}); CHECK(response) << "VertexRpc failed"; - return std::move(response->name_output_); + return std::move(response->vertex_output); } std::unordered_map<int, int64_t> DataRpcClients::VertexCounts( diff --git a/src/distributed/data_rpc_messages.hpp b/src/distributed/data_rpc_messages.hpp deleted file mode 100644 index 60d7a79e8..000000000 --- a/src/distributed/data_rpc_messages.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include <memory> -#include <string> - -#include "communication/rpc/messages.hpp" -#include "distributed/serialization.hpp" -#include "storage/edge.hpp" -#include "storage/gid.hpp" -#include "storage/vertex.hpp" -#include "transactions/type.hpp" - -namespace distributed { - -struct TxGidPair { - tx::TransactionId tx_id; - gid::Gid gid; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &tx_id; - ar &gid; - } -}; - -#define MAKE_RESPONSE(type, name) \ - class type##Res : public communication::rpc::Message { \ - public: \ - type##Res() {} \ - type##Res(const type *name, int worker_id) \ - : name_input_(name), worker_id_(worker_id) {} \ - \ - template <class TArchive> \ - void save(TArchive &ar, unsigned int) const { \ - ar << boost::serialization::base_object< \ - const communication::rpc::Message>(*this); \ - Save##type(ar, *name_input_, worker_id_); \ - } \ - \ - template <class TArchive> \ - void load(TArchive &ar, unsigned int) { \ - ar >> boost::serialization::base_object<communication::rpc::Message>( \ - *this); \ - auto v = Load##type(ar); \ - v.swap(name_output_); \ - } \ - BOOST_SERIALIZATION_SPLIT_MEMBER() \ - \ - const type *name_input_; \ - int worker_id_; \ - std::unique_ptr<type> name_output_; \ - }; - -MAKE_RESPONSE(Vertex, vertex) -MAKE_RESPONSE(Edge, edge) - -#undef MAKE_RESPONSE - -RPC_SINGLE_MEMBER_MESSAGE(VertexReq, TxGidPair); -RPC_SINGLE_MEMBER_MESSAGE(EdgeReq, TxGidPair); -RPC_SINGLE_MEMBER_MESSAGE(VertexCountReq, tx::TransactionId); -RPC_SINGLE_MEMBER_MESSAGE(VertexCountRes, int64_t); - -using VertexRpc = communication::rpc::RequestResponse<VertexReq, VertexRes>; -using EdgeRpc = communication::rpc::RequestResponse<EdgeReq, EdgeRes>; -using VertexCountRpc = - communication::rpc::RequestResponse<VertexCountReq, VertexCountRes>; - -} // namespace distributed diff --git a/src/distributed/data_rpc_messages.lcp b/src/distributed/data_rpc_messages.lcp new file mode 100644 index 000000000..5f0f1ca3f --- /dev/null +++ b/src/distributed/data_rpc_messages.lcp @@ -0,0 +1,76 @@ +#>cpp +#pragma once + +#include <memory> +#include <string> + +#include "communication/rpc/messages.hpp" +#include "distributed/data_rpc_messages.capnp.h" +#include "distributed/serialization.hpp" +#include "storage/edge.hpp" +#include "storage/gid.hpp" +#include "storage/vertex.hpp" +#include "transactions/type.hpp" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:capnp-import 'utils "/utils/serialization.capnp") +(lcp:capnp-import 'dist "/distributed/serialization.capnp") + +(lcp:define-struct tx-gid-pair () + ((tx-id "tx::TransactionId" :capnp-type "UInt64") + (gid "gid::Gid" :capnp-type "UInt64")) + (:serialize :capnp)) + +(lcp:define-rpc vertex + (:request ((member "TxGidPair"))) + (:response + ((vertex-input "const Vertex *" + :save-fun "SaveVertex(ar, *vertex_input, worker_id);" :load-fun "" + :capnp-type "Dist.Vertex" + :capnp-save + (lambda (builder member) + #>cpp + SaveVertex(*${member}, &${builder}, worker_id); + cpp<#) + :capnp-load + (lambda (reader member) + (declare (ignore member)) + #>cpp + vertex_output = LoadVertex<const capnp::Vertex::Reader>(${reader}); + cpp<#)) + (worker-id :int64_t :save-fun "" :load-fun "" :capnp-save :dont-save) + (vertex-output "std::unique_ptr<Vertex>" :initarg nil + :save-fun "" :load-fun "vertex_output = LoadVertex(ar);" + :capnp-save :dont-save)))) + +(lcp:define-rpc edge + (:request ((member "TxGidPair"))) + (:response + ((edge-input "const Edge *" + :save-fun "SaveEdge(ar, *edge_input, worker_id);" :load-fun "" + :capnp-type "Dist.Edge" + :capnp-save + (lambda (builder member) + #>cpp + SaveEdge(*${member}, &${builder}, worker_id); + cpp<#) + :capnp-load + (lambda (reader member) + (declare (ignore member)) + #>cpp + edge_output = LoadEdge<const capnp::Edge::Reader>(${reader}); + cpp<#)) + (worker-id :int64_t :save-fun "" :load-fun "" :capnp-save :dont-save) + (edge-output "std::unique_ptr<Edge>" :initarg nil + :save-fun "" :load-fun "edge_output = LoadEdge(ar);" + :capnp-save :dont-save)))) + +(lcp:define-rpc vertex-count + (:request ((member "tx::TransactionId" :capnp-type "UInt64"))) + (:response ((member :int64_t)))) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/data_rpc_server.cpp b/src/distributed/data_rpc_server.cpp index 70e67e8f1..62c09ce4c 100644 --- a/src/distributed/data_rpc_server.cpp +++ b/src/distributed/data_rpc_server.cpp @@ -10,27 +10,34 @@ DataRpcServer::DataRpcServer(database::GraphDb &db, communication::rpc::Server &server) : db_(db), rpc_server_(server) { rpc_server_.Register<VertexRpc>( - [this](const VertexReq &req) { - database::GraphDbAccessor dba(db_, req.member.tx_id); - auto vertex = dba.FindVertex(req.member.gid, false); + [this](const auto &req_reader, auto *res_builder) { + database::GraphDbAccessor dba(db_, req_reader.getMember().getTxId()); + auto vertex = dba.FindVertex(req_reader.getMember().getGid(), false); CHECK(vertex.GetOld()) << "Old record must exist when sending vertex by RPC"; - return std::make_unique<VertexRes>(vertex.GetOld(), db_.WorkerId()); + VertexRes response(vertex.GetOld(), db_.WorkerId()); + response.Save(res_builder); }); - rpc_server_.Register<EdgeRpc>([this](const EdgeReq &req) { - database::GraphDbAccessor dba(db_, req.member.tx_id); - auto edge = dba.FindEdge(req.member.gid, false); + rpc_server_.Register<EdgeRpc>([this](const auto &req_reader, + auto *res_builder) { + database::GraphDbAccessor dba(db_, req_reader.getMember().getTxId()); + auto edge = dba.FindEdge(req_reader.getMember().getGid(), false); CHECK(edge.GetOld()) << "Old record must exist when sending edge by RPC"; - return std::make_unique<EdgeRes>(edge.GetOld(), db_.WorkerId()); + EdgeRes response(edge.GetOld(), db_.WorkerId()); + response.Save(res_builder); }); - rpc_server_.Register<VertexCountRpc>([this](const VertexCountReq &req) { - database::GraphDbAccessor dba(db_, req.member); - int64_t size = 0; - for (auto vertex : dba.Vertices(false)) ++size; - return std::make_unique<VertexCountRes>(size); - }); + rpc_server_.Register<VertexCountRpc>( + [this](const auto &req_reader, auto *res_builder) { + VertexCountReq req; + req.Load(req_reader); + database::GraphDbAccessor dba(db_, req.member); + int64_t size = 0; + for (auto vertex : dba.Vertices(false)) ++size; + VertexCountRes res(size); + res.Save(res_builder); + }); } } // namespace distributed diff --git a/src/distributed/durability_rpc_clients.cpp b/src/distributed/durability_rpc_clients.cpp index 866f63efb..660965cba 100644 --- a/src/distributed/durability_rpc_clients.cpp +++ b/src/distributed/durability_rpc_clients.cpp @@ -10,7 +10,7 @@ utils::Future<bool> DurabilityRpcClients::MakeSnapshot(tx::TransactionId tx) { auto futures = clients_.ExecuteOnWorkers<bool>( 0, [tx](int worker_id, communication::rpc::ClientPool &client_pool) { auto res = client_pool.Call<MakeSnapshotRpc>(tx); - if (res == nullptr) return false; + if (!res) return false; return res->member; }); diff --git a/src/distributed/durability_rpc_messages.hpp b/src/distributed/durability_rpc_messages.hpp deleted file mode 100644 index baf147814..000000000 --- a/src/distributed/durability_rpc_messages.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" - -#include "communication/rpc/messages.hpp" -#include "transactions/transaction.hpp" - -namespace distributed { - -RPC_SINGLE_MEMBER_MESSAGE(MakeSnapshotReq, tx::TransactionId); -RPC_SINGLE_MEMBER_MESSAGE(MakeSnapshotRes, bool); - -using MakeSnapshotRpc = - communication::rpc::RequestResponse<MakeSnapshotReq, MakeSnapshotRes>; - -} // namespace distributed diff --git a/src/distributed/durability_rpc_messages.lcp b/src/distributed/durability_rpc_messages.lcp new file mode 100644 index 000000000..9027569f1 --- /dev/null +++ b/src/distributed/durability_rpc_messages.lcp @@ -0,0 +1,20 @@ +#>cpp +#pragma once + +#include "boost/serialization/access.hpp" +#include "boost/serialization/base_object.hpp" + +#include "communication/rpc/messages.hpp" +#include "distributed/durability_rpc_messages.capnp.h" +#include "transactions/transaction.hpp" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:define-rpc make-snapshot + (:request ((member "tx::TransactionId" :capnp-type "UInt64"))) + (:response ((member :bool)))) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/durability_rpc_server.cpp b/src/distributed/durability_rpc_server.cpp index 801b59b16..031dc73dc 100644 --- a/src/distributed/durability_rpc_server.cpp +++ b/src/distributed/durability_rpc_server.cpp @@ -9,10 +9,12 @@ namespace distributed { DurabilityRpcServer::DurabilityRpcServer(database::GraphDb &db, communication::rpc::Server &server) : db_(db), rpc_server_(server) { - rpc_server_.Register<MakeSnapshotRpc>([this](const MakeSnapshotReq &req) { - database::GraphDbAccessor dba(this->db_, req.member); - return std::make_unique<MakeSnapshotRes>(this->db_.MakeSnapshot(dba)); - }); + rpc_server_.Register<MakeSnapshotRpc>( + [this](const auto &req_reader, auto *res_builder) { + database::GraphDbAccessor dba(this->db_, req_reader.getMember()); + MakeSnapshotRes res(this->db_.MakeSnapshot(dba)); + res.Save(res_builder); + }); } } // namespace distributed diff --git a/src/distributed/index_rpc_messages.hpp b/src/distributed/index_rpc_messages.hpp deleted file mode 100644 index 3f9ebf321..000000000 --- a/src/distributed/index_rpc_messages.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include <memory> -#include <string> - -#include "communication/rpc/messages.hpp" -#include "distributed/serialization.hpp" - -namespace distributed { - -struct IndexLabelPropertyTx { - storage::Label label; - storage::Property property; - tx::TransactionId tx_id; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &label; - ar &property; - ar &tx_id; - } -}; - -RPC_SINGLE_MEMBER_MESSAGE(BuildIndexReq, IndexLabelPropertyTx); -RPC_NO_MEMBER_MESSAGE(BuildIndexRes); - -using BuildIndexRpc = - communication::rpc::RequestResponse<BuildIndexReq, BuildIndexRes>; -} // namespace distributed diff --git a/src/distributed/index_rpc_messages.lcp b/src/distributed/index_rpc_messages.lcp new file mode 100644 index 000000000..d1573b53a --- /dev/null +++ b/src/distributed/index_rpc_messages.lcp @@ -0,0 +1,25 @@ +#>cpp +#pragma once + +#include <memory> +#include <string> + +#include "communication/rpc/messages.hpp" +#include "distributed/serialization.hpp" +#include "distributed/index_rpc_messages.capnp.h" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:capnp-import 'storage "/storage/serialization.capnp") + +(lcp:define-rpc build-index + (:request + ((label "storage::Label" :capnp-type "Storage.Common") + (property "storage::Property" :capnp-type "Storage.Common") + (tx-id "tx::TransactionId" :capnp-type "UInt64"))) + (:response ())) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/index_rpc_server.cpp b/src/distributed/index_rpc_server.cpp index 6964ebcc6..a88b0595d 100644 --- a/src/distributed/index_rpc_server.cpp +++ b/src/distributed/index_rpc_server.cpp @@ -7,27 +7,27 @@ namespace distributed { IndexRpcServer::IndexRpcServer(database::GraphDb &db, communication::rpc::Server &server) : db_(db), rpc_server_(server) { - rpc_server_.Register<BuildIndexRpc>([this](const BuildIndexReq &req) { + rpc_server_.Register<BuildIndexRpc>( + [this](const auto &req_reader, auto *res_builder) { + BuildIndexReq req; + req.Load(req_reader); + database::LabelPropertyIndex::Key key{req.label, req.property}; + database::GraphDbAccessor dba(db_, req.tx_id); - database::LabelPropertyIndex::Key key{req.member.label, - req.member.property}; - database::GraphDbAccessor dba(db_, req.member.tx_id); - - if (db_.storage().label_property_index_.CreateIndex(key) == false) { - // If we are a distributed worker we just have to wait till the index - // (which should be in progress of being created) is created so that our - // return guarantess that the index has been built - this assumes that - // no worker thread that is creating an index will fail - while (!dba.LabelPropertyIndexExists(key.label_, key.property_)) { - // TODO reconsider this constant, currently rule-of-thumb chosen - std::this_thread::sleep_for(std::chrono::microseconds(100)); - } - } else { - dba.PopulateIndex(key); - dba.EnableIndex(key); - } - return std::make_unique<BuildIndexRes>(); - }); + if (db_.storage().label_property_index_.CreateIndex(key) == false) { + // If we are a distributed worker we just have to wait till the index + // (which should be in progress of being created) is created so that + // our return guarantess that the index has been built - this assumes + // that no worker thread that is creating an index will fail + while (!dba.LabelPropertyIndexExists(key.label_, key.property_)) { + // TODO reconsider this constant, currently rule-of-thumb chosen + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + } else { + dba.PopulateIndex(key); + dba.EnableIndex(key); + } + }); } } // namespace distributed diff --git a/src/distributed/plan_consumer.cpp b/src/distributed/plan_consumer.cpp index 9e83b5785..fa48f2ce2 100644 --- a/src/distributed/plan_consumer.cpp +++ b/src/distributed/plan_consumer.cpp @@ -4,19 +4,21 @@ namespace distributed { PlanConsumer::PlanConsumer(communication::rpc::Server &server) : server_(server) { - server_.Register<DistributedPlanRpc>([this](const DispatchPlanReq &req) { - plan_cache_.access().insert( - req.plan_id_, - std::make_unique<PlanPack>( - req.plan_, req.symbol_table_, - std::move(const_cast<DispatchPlanReq &>(req).storage_))); - return std::make_unique<DispatchPlanRes>(); - }); + server_.Register<DispatchPlanRpc>( + [this](const auto &req_reader, auto *res_builder) { + DispatchPlanReq req; + req.Load(req_reader); + plan_cache_.access().insert( + req.plan_id, std::make_unique<PlanPack>(req.plan, req.symbol_table, + std::move(req.storage))); + DispatchPlanRes res; + res.Save(res_builder); + }); - server_.Register<RemovePlanRpc>([this](const RemovePlanReq &req) { - plan_cache_.access().remove(req.member); - return std::make_unique<RemovePlanRes>(); - }); + server_.Register<RemovePlanRpc>( + [this](const auto &req_reader, auto *res_builder) { + plan_cache_.access().remove(req_reader.getMember()); + }); } PlanConsumer::PlanPack &PlanConsumer::PlanForId(int64_t plan_id) const { diff --git a/src/distributed/plan_consumer.hpp b/src/distributed/plan_consumer.hpp index 0155805e4..933e50a99 100644 --- a/src/distributed/plan_consumer.hpp +++ b/src/distributed/plan_consumer.hpp @@ -16,14 +16,14 @@ class PlanConsumer { public: struct PlanPack { PlanPack(std::shared_ptr<query::plan::LogicalOperator> plan, - SymbolTable symbol_table, AstTreeStorage storage) + query::SymbolTable symbol_table, query::AstTreeStorage storage) : plan(plan), symbol_table(std::move(symbol_table)), storage(std::move(storage)) {} std::shared_ptr<query::plan::LogicalOperator> plan; - SymbolTable symbol_table; - const AstTreeStorage storage; + query::SymbolTable symbol_table; + const query::AstTreeStorage storage; }; explicit PlanConsumer(communication::rpc::Server &server); diff --git a/src/distributed/plan_dispatcher.cpp b/src/distributed/plan_dispatcher.cpp index 72ae13418..bd1b34429 100644 --- a/src/distributed/plan_dispatcher.cpp +++ b/src/distributed/plan_dispatcher.cpp @@ -6,13 +6,13 @@ PlanDispatcher::PlanDispatcher(RpcWorkerClients &clients) : clients_(clients) {} void PlanDispatcher::DispatchPlan( int64_t plan_id, std::shared_ptr<query::plan::LogicalOperator> plan, - const SymbolTable &symbol_table) { + const query::SymbolTable &symbol_table) { auto futures = clients_.ExecuteOnWorkers<void>( 0, [plan_id, plan, symbol_table]( int worker_id, communication::rpc::ClientPool &client_pool) { auto result = - client_pool.Call<DistributedPlanRpc>(plan_id, plan, symbol_table); - CHECK(result) << "DistributedPlanRpc failed"; + client_pool.Call<DispatchPlanRpc>(plan_id, plan, symbol_table); + CHECK(result) << "DispatchPlanRpc failed"; }); for (auto &future : futures) { diff --git a/src/distributed/plan_dispatcher.hpp b/src/distributed/plan_dispatcher.hpp index 9e2105b31..c8763f7e3 100644 --- a/src/distributed/plan_dispatcher.hpp +++ b/src/distributed/plan_dispatcher.hpp @@ -18,7 +18,7 @@ class PlanDispatcher { /** Dispatch a plan to all workers and wait for their acknowledgement. */ void DispatchPlan(int64_t plan_id, std::shared_ptr<query::plan::LogicalOperator> plan, - const SymbolTable &symbol_table); + const query::SymbolTable &symbol_table); /** Remove a plan from all workers and wait for their acknowledgement. */ void RemovePlan(int64_t plan_id); diff --git a/src/distributed/plan_rpc_messages.hpp b/src/distributed/plan_rpc_messages.hpp deleted file mode 100644 index 506365481..000000000 --- a/src/distributed/plan_rpc_messages.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" - -#include "communication/rpc/messages.hpp" -#include "query/frontend/ast/ast.hpp" -#include "query/frontend/semantic/symbol_table.hpp" -#include "query/plan/operator.hpp" - -namespace distributed { - -using communication::rpc::Message; -using SymbolTable = query::SymbolTable; -using AstTreeStorage = query::AstTreeStorage; - -struct DispatchPlanReq : public Message { - DispatchPlanReq() {} - DispatchPlanReq(int64_t plan_id, - std::shared_ptr<query::plan::LogicalOperator> plan, - SymbolTable symbol_table) - - : plan_id_(plan_id), plan_(plan), symbol_table_(symbol_table) {} - int64_t plan_id_; - std::shared_ptr<query::plan::LogicalOperator> plan_; - SymbolTable symbol_table_; - AstTreeStorage storage_; - - private: - friend class boost::serialization::access; - - BOOST_SERIALIZATION_SPLIT_MEMBER(); - - template <class TArchive> - void save(TArchive &ar, const unsigned int) const { - ar &boost::serialization::base_object<Message>(*this); - ar &plan_id_; - ar &plan_; - ar &symbol_table_; - } - - template <class TArchive> - void load(TArchive &ar, const unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar &plan_id_; - ar &plan_; - ar &symbol_table_; - storage_ = std::move( - ar.template get_helper<AstTreeStorage>(AstTreeStorage::kHelperId)); - } -}; - -RPC_NO_MEMBER_MESSAGE(DispatchPlanRes); - -using DistributedPlanRpc = - communication::rpc::RequestResponse<DispatchPlanReq, DispatchPlanRes>; - -RPC_SINGLE_MEMBER_MESSAGE(RemovePlanReq, int64_t); -RPC_NO_MEMBER_MESSAGE(RemovePlanRes); -using RemovePlanRpc = - communication::rpc::RequestResponse<RemovePlanReq, RemovePlanRes>; - -} // namespace distributed diff --git a/src/distributed/plan_rpc_messages.lcp b/src/distributed/plan_rpc_messages.lcp new file mode 100644 index 000000000..b55227308 --- /dev/null +++ b/src/distributed/plan_rpc_messages.lcp @@ -0,0 +1,59 @@ +#>cpp +#pragma once + +#include "communication/rpc/messages.hpp" +#include "query/frontend/ast/ast.hpp" +#include "query/frontend/semantic/symbol_table.hpp" +#include "query/plan/operator.hpp" + +#include "distributed/plan_rpc_messages.capnp.h" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:capnp-import 'utils "/utils/serialization.capnp") +(lcp:capnp-import 'plan "/query/plan/operator.capnp") +(lcp:capnp-import 'sem "/query/frontend/semantic/symbol.capnp") + +(defun load-plan (reader member) + #>cpp + query::plan::LogicalOperator::LoadHelper helper; + ${member} = utils::LoadSharedPtr<query::plan::capnp::LogicalOperator, query::plan::LogicalOperator>( + ${reader}, [&helper](const auto &reader) { + auto op = query::plan::LogicalOperator::Construct(reader); + op->Load(reader, &helper); + return op.release(); + }, &helper.loaded_ops); + storage = std::move(helper.ast_storage); + cpp<#) + +(defun save-plan (builder member) + #>cpp + query::plan::LogicalOperator::SaveHelper helper; + utils::SaveSharedPtr<query::plan::capnp::LogicalOperator, query::plan::LogicalOperator>( + ${member}, &${builder}, + [&helper](auto *builder, const auto &val) { + val.Save(builder, &helper); + }, &helper.saved_ops); + cpp<#) + +(lcp:define-rpc dispatch-plan + (:request + ((plan-id :int64_t) + (plan "std::shared_ptr<query::plan::LogicalOperator>" + :capnp-type "Utils.SharedPtr(Plan.LogicalOperator)" + :capnp-save #'save-plan :capnp-load #'load-plan) + (symbol-table "query::SymbolTable" :capnp-type "Sem.SymbolTable") + (storage "query::AstTreeStorage" :initarg nil + :save-fun "" + :load-fun "storage = std::move(ar.template get_helper<query::AstTreeStorage>(query::AstTreeStorage::kHelperId));" + :capnp-save :dont-save))) + (:response ())) + +(lcp:define-rpc remove-plan + (:request ((member :int64_t))) + (:response ())) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/produce_rpc_server.cpp b/src/distributed/produce_rpc_server.cpp index 1b3d3a2e7..466bbe30e 100644 --- a/src/distributed/produce_rpc_server.cpp +++ b/src/distributed/produce_rpc_server.cpp @@ -96,15 +96,22 @@ ProduceRpcServer::ProduceRpcServer( produce_rpc_server_(server), plan_consumer_(plan_consumer), tx_engine_(tx_engine) { - produce_rpc_server_.Register<PullRpc>([this](const PullReq &req) { - return std::make_unique<PullRes>(Pull(req)); - }); + produce_rpc_server_.Register<PullRpc>( + [this](const auto &req_reader, auto *res_builder) { + PullReq req; + req.Load(req_reader); + PullRes res(Pull(req)); + res.Save(res_builder); + }); produce_rpc_server_.Register<TransactionCommandAdvancedRpc>( - [this](const TransactionCommandAdvancedReq &req) { + [this](const auto &req_reader, auto *res_builder) { + TransactionCommandAdvancedReq req; + req.Load(req_reader); tx_engine_.UpdateCommand(req.member); db_.data_manager().ClearCacheForSingleTransaction(req.member); - return std::make_unique<TransactionCommandAdvancedRes>(); + TransactionCommandAdvancedRes res; + res.Save(res_builder); }); } @@ -145,22 +152,22 @@ ProduceRpcServer::OngoingProduce &ProduceRpcServer::GetOngoingProduce( PullResData ProduceRpcServer::Pull(const PullReq &req) { auto &ongoing_produce = GetOngoingProduce(req); - PullResData result{db_.WorkerId(), req.send_old, req.send_new}; - result.state_and_frames.pull_state = PullState::CURSOR_IN_PROGRESS; + PullResData result(db_.WorkerId(), req.send_old, req.send_new); + result.pull_state = PullState::CURSOR_IN_PROGRESS; if (req.accumulate) { - result.state_and_frames.pull_state = ongoing_produce.Accumulate(); + result.pull_state = ongoing_produce.Accumulate(); // If an error ocurred, we need to return that error. - if (result.state_and_frames.pull_state != PullState::CURSOR_EXHAUSTED) { + if (result.pull_state != PullState::CURSOR_EXHAUSTED) { return result; } } for (int i = 0; i < req.batch_size; ++i) { auto pull_result = ongoing_produce.Pull(); - result.state_and_frames.pull_state = pull_result.second; + result.pull_state = pull_result.second; if (pull_result.second != PullState::CURSOR_IN_PROGRESS) break; - result.state_and_frames.frames.emplace_back(std::move(pull_result.first)); + result.frames.emplace_back(std::move(pull_result.first)); } return result; diff --git a/src/distributed/pull_produce_rpc_messages.hpp b/src/distributed/pull_produce_rpc_messages.hpp deleted file mode 100644 index e2f41ff83..000000000 --- a/src/distributed/pull_produce_rpc_messages.hpp +++ /dev/null @@ -1,381 +0,0 @@ -#pragma once - -#include <cstdint> -#include <functional> -#include <string> - -#include "boost/serialization/utility.hpp" -#include "boost/serialization/vector.hpp" - -#include "communication/rpc/messages.hpp" -#include "distributed/serialization.hpp" -#include "query/frontend/semantic/symbol.hpp" -#include "query/parameters.hpp" -#include "storage/address_types.hpp" -#include "transactions/type.hpp" -#include "utils/serialization.hpp" - -namespace distributed { - -/// The default number of results returned via RPC from remote execution to the -/// master that requested it. -constexpr int kDefaultBatchSize = 20; - -/// Returnd along with a batch of results in the remote-pull RPC. Indicates the -/// state of execution on the worker. -enum class PullState { - CURSOR_EXHAUSTED, - CURSOR_IN_PROGRESS, - SERIALIZATION_ERROR, - LOCK_TIMEOUT_ERROR, - UPDATE_DELETED_ERROR, - RECONSTRUCTION_ERROR, - UNABLE_TO_DELETE_VERTEX_ERROR, - HINTED_ABORT_ERROR, - QUERY_ERROR -}; - -struct PullReq : public communication::rpc::Message { - PullReq() {} - PullReq(tx::TransactionId tx_id, tx::Snapshot tx_snapshot, int64_t plan_id, - tx::CommandId command_id, const Parameters ¶ms, - std::vector<query::Symbol> symbols, bool accumulate, int batch_size, - bool send_old, bool send_new) - : tx_id(tx_id), - tx_snapshot(tx_snapshot), - plan_id(plan_id), - command_id(command_id), - params(params), - symbols(symbols), - accumulate(accumulate), - batch_size(batch_size), - send_old(send_old), - send_new(send_new) {} - - tx::TransactionId tx_id; - tx::Snapshot tx_snapshot; - int64_t plan_id; - tx::CommandId command_id; - Parameters params; - std::vector<query::Symbol> symbols; - bool accumulate; - int batch_size; - // Indicates which of (old, new) records of a graph element should be sent. - bool send_old; - bool send_new; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void save(TArchive &ar, unsigned int) const { - ar << boost::serialization::base_object<communication::rpc::Message>(*this); - ar << tx_id; - ar << tx_snapshot; - ar << plan_id; - ar << command_id; - ar << params.size(); - for (auto &kv : params) { - ar << kv.first; - // Params never contain a vertex/edge, so save plan TypedValue. - utils::SaveTypedValue(ar, kv.second); - } - ar << symbols; - ar << accumulate; - ar << batch_size; - ar << send_old; - ar << send_new; - } - - template <class TArchive> - void load(TArchive &ar, unsigned int) { - ar >> boost::serialization::base_object<communication::rpc::Message>(*this); - ar >> tx_id; - ar >> tx_snapshot; - ar >> plan_id; - ar >> command_id; - size_t params_size; - ar >> params_size; - for (size_t i = 0; i < params_size; ++i) { - int token_pos; - ar >> token_pos; - query::TypedValue param; - // Params never contain a vertex/edge, so load plan TypedValue. - utils::LoadTypedValue(ar, param); - params.Add(token_pos, param); - } - ar >> symbols; - ar >> accumulate; - ar >> batch_size; - ar >> send_old; - ar >> send_new; - } - BOOST_SERIALIZATION_SPLIT_MEMBER() -}; - -/// The data returned to the end consumer (the Pull operator). Contains -/// only the relevant parts of the response, ready for use. -struct PullData { - PullState pull_state; - std::vector<std::vector<query::TypedValue>> frames; -}; - -/// The data of the remote pull response. Post-processing is required after -/// deserialization to initialize Vertex/Edge typed values in the frames -/// (possibly encapsulated in lists/maps) to their proper values. This requires -/// a GraphDbAccessor and therefore can't be done as part of deserialization. -/// -/// TODO - make it possible to inject a &GraphDbAcessor from the Pull -/// layer -/// all the way into RPC data deserialization to remove the requirement for -/// post-processing. The current approach of holding references to parts of the -/// frame (potentially embedded in lists/maps) is too error-prone. -struct PullResData { - private: - // Temp cache for deserialized vertices and edges. These objects are created - // during deserialization. They are used immediatelly after during - // post-processing. The vertex/edge data ownership gets transfered to the - // Cache, and the `element_in_frame` reference is used to set the - // appropriate accessor to the appropriate value. Not used on side that - // generates the response. - template <typename TRecord> - struct GraphElementData { - using AddressT = storage::Address<mvcc::VersionList<TRecord>>; - using PtrT = std::unique_ptr<TRecord>; - - GraphElementData(AddressT address, PtrT old_record, PtrT new_record, - query::TypedValue *element_in_frame) - : global_address(address), - old_record(std::move(old_record)), - new_record(std::move(new_record)), - element_in_frame(element_in_frame) {} - - storage::Address<mvcc::VersionList<TRecord>> global_address; - std::unique_ptr<TRecord> old_record; - std::unique_ptr<TRecord> new_record; - // The position in frame is optional. This same structure is used for - // deserializing path elements, in which case the vertex/edge in question is - // not directly part of the frame. - query::TypedValue *element_in_frame; - }; - - // Same like `GraphElementData`, but for paths. - struct PathData { - PathData(query::TypedValue &path_in_frame) : path_in_frame(path_in_frame) {} - std::vector<GraphElementData<Vertex>> vertices; - std::vector<GraphElementData<Edge>> edges; - query::TypedValue &path_in_frame; - }; - - public: - PullResData() {} // Default constructor required for serialization. - PullResData(int worker_id, bool send_old, bool send_new) - : worker_id(worker_id), send_old(send_old), send_new(send_new) {} - - PullResData(const PullResData &) = delete; - PullResData &operator=(const PullResData &) = delete; - PullResData(PullResData &&) = default; - PullResData &operator=(PullResData &&) = default; - - PullData state_and_frames; - // Id of the worker on which the response is created, used for serializing - // vertices (converting local to global addresses). - int worker_id; - // Indicates which of (old, new) records of a graph element should be sent. - bool send_old; - bool send_new; - - // Temporary caches used between deserialization and post-processing - // (transfering the ownership of this data to a Cache). - std::vector<GraphElementData<Vertex>> vertices; - std::vector<GraphElementData<Edge>> edges; - std::vector<PathData> paths; - - /// Saves a typed value that is a vertex/edge/path. - template <class TArchive> - void SaveGraphElement(TArchive &ar, const query::TypedValue &value) const { - // Helper template function for storing a vertex or an edge. - auto save_element = [&ar, this](auto element_accessor) { - ar << element_accessor.GlobalAddress().raw(); - - // If both old and new are null, we need to reconstruct. - if (!(element_accessor.GetOld() || element_accessor.GetNew())) { - bool result = element_accessor.Reconstruct(); - CHECK(result) << "Attempting to serialize an element not visible to " - "current transaction."; - } - auto *old_rec = element_accessor.GetOld(); - if (send_old && old_rec) { - ar << true; - distributed::SaveElement(ar, *old_rec, worker_id); - } else { - ar << false; - } - if (send_new) { - // Must call SwitchNew as that will trigger a potentially necesary - // Reconstruct. - element_accessor.SwitchNew(); - auto *new_rec = element_accessor.GetNew(); - if (new_rec) { - ar << true; - distributed::SaveElement(ar, *new_rec, worker_id); - } else { - ar << false; - } - } else { - ar << false; - } - }; - switch (value.type()) { - case query::TypedValue::Type::Vertex: - save_element(value.ValueVertex()); - break; - case query::TypedValue::Type::Edge: - save_element(value.ValueEdge()); - break; - case query::TypedValue::Type::Path: { - auto &path = value.ValuePath(); - ar << path.size(); - save_element(path.vertices()[0]); - for (size_t i = 0; i < path.size(); ++i) { - save_element(path.edges()[i]); - save_element(path.vertices()[i + 1]); - } - break; - } - default: - LOG(FATAL) << "Unsupported graph element type: " << value.type(); - } - } - - /// Loads a typed value that is a vertex/edge/path. Part of the - /// deserialization process, populates the temporary data caches which are - /// processed later. - template <class TArchive> - void LoadGraphElement(TArchive &ar, query::TypedValue::Type type, - query::TypedValue &value) { - auto load_edge = [](auto &ar) { - bool exists; - ar >> exists; - return exists ? LoadEdge(ar) : nullptr; - }; - auto load_vertex = [](auto &ar) { - bool exists; - ar >> exists; - return exists ? LoadVertex(ar) : nullptr; - }; - - switch (type) { - case query::TypedValue::Type::Vertex: { - storage::VertexAddress::StorageT address; - ar >> address; - vertices.emplace_back(storage::VertexAddress(address), load_vertex(ar), - load_vertex(ar), &value); - break; - } - case query::TypedValue::Type::Edge: { - storage::VertexAddress::StorageT address; - ar >> address; - edges.emplace_back(storage::EdgeAddress(address), load_edge(ar), - load_edge(ar), &value); - break; - } - case query::TypedValue::Type::Path: { - size_t path_size; - ar >> path_size; - - paths.emplace_back(value); - auto &path_data = paths.back(); - - storage::VertexAddress::StorageT vertex_address; - storage::EdgeAddress::StorageT edge_address; - ar >> vertex_address; - path_data.vertices.emplace_back(storage::VertexAddress(vertex_address), - load_vertex(ar), load_vertex(ar), - nullptr); - for (size_t i = 0; i < path_size; ++i) { - ar >> edge_address; - path_data.edges.emplace_back(storage::EdgeAddress(edge_address), - load_edge(ar), load_edge(ar), nullptr); - ar >> vertex_address; - path_data.vertices.emplace_back( - storage::VertexAddress(vertex_address), load_vertex(ar), - load_vertex(ar), nullptr); - } - break; - } - default: - LOG(FATAL) << "Unsupported graph element type: " << type; - } - } -}; - -class PullRes : public communication::rpc::Message { - public: - PullRes() {} - PullRes(PullResData data) : data(std::move(data)) {} - - PullResData data; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void save(TArchive &ar, unsigned int) const { - ar << boost::serialization::base_object<communication::rpc::Message>(*this); - ar << data.state_and_frames.pull_state; - ar << data.state_and_frames.frames.size(); - // We need to indicate how many values are in each frame. - // Assume all the frames have an equal number of elements. - ar << (data.state_and_frames.frames.size() == 0 - ? 0 - : data.state_and_frames.frames[0].size()); - for (const auto &frame : data.state_and_frames.frames) - for (const auto &value : frame) { - utils::SaveTypedValue<TArchive>( - ar, value, [this](TArchive &ar, const query::TypedValue &value) { - data.SaveGraphElement(ar, value); - }); - } - } - - template <class TArchive> - void load(TArchive &ar, unsigned int) { - ar >> boost::serialization::base_object<communication::rpc::Message>(*this); - ar >> data.state_and_frames.pull_state; - size_t frame_count; - ar >> frame_count; - data.state_and_frames.frames.reserve(frame_count); - size_t frame_size; - ar >> frame_size; - for (size_t i = 0; i < frame_count; ++i) { - data.state_and_frames.frames.emplace_back(); - auto ¤t_frame = data.state_and_frames.frames.back(); - current_frame.reserve(frame_size); - for (size_t j = 0; j < frame_size; ++j) { - current_frame.emplace_back(); - utils::LoadTypedValue<TArchive>( - ar, current_frame.back(), - [this](TArchive &ar, query::TypedValue::TypedValue::Type type, - query::TypedValue &value) { - data.LoadGraphElement(ar, type, value); - }); - } - } - } - BOOST_SERIALIZATION_SPLIT_MEMBER() -}; - -using PullRpc = communication::rpc::RequestResponse<PullReq, PullRes>; - -// TODO make a separate RPC for the continuation of an existing pull, as an -// optimization not to have to send the full PullReqData pack every -// time. - -RPC_SINGLE_MEMBER_MESSAGE(TransactionCommandAdvancedReq, tx::TransactionId); -RPC_NO_MEMBER_MESSAGE(TransactionCommandAdvancedRes); -using TransactionCommandAdvancedRpc = - communication::rpc::RequestResponse<TransactionCommandAdvancedReq, - TransactionCommandAdvancedRes>; - -} // namespace distributed diff --git a/src/distributed/pull_produce_rpc_messages.lcp b/src/distributed/pull_produce_rpc_messages.lcp new file mode 100644 index 000000000..849121140 --- /dev/null +++ b/src/distributed/pull_produce_rpc_messages.lcp @@ -0,0 +1,547 @@ +#>cpp +#pragma once + +#include <cstdint> +#include <functional> +#include <string> + +#include "communication/rpc/messages.hpp" +#include "distributed/pull_produce_rpc_messages.capnp.h" +#include "distributed/serialization.hpp" +#include "query/frontend/semantic/symbol.hpp" +#include "query/parameters.hpp" +#include "storage/address_types.hpp" +#include "transactions/type.hpp" +#include "utils/serialization.hpp" +cpp<# + +(lcp:in-impl + #>cpp + #include "database/graph_db_accessor.hpp" + #include "distributed/data_manager.hpp" + cpp<#) + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:capnp-import 'dis "/distributed/serialization.capnp") +(lcp:capnp-import 'sem "/query/frontend/semantic/symbol.capnp") +(lcp:capnp-import 'tx "/transactions/common.capnp") +(lcp:capnp-import 'utils "/utils/serialization.capnp") + +(lcp:capnp-type-conversion "tx::CommandId" "UInt32") +(lcp:capnp-type-conversion "tx::Snapshot" "Tx.Snapshot") +(lcp:capnp-type-conversion "tx::TransactionId" "UInt64") + +#>cpp +/// The default number of results returned via RPC from remote execution to the +/// master that requested it. +constexpr int kDefaultBatchSize = 20; +cpp<# + +(lcp:define-enum pull-state + (cursor-exhausted + cursor-in-progress + serialization-error + lock-timeout-error + update-deleted-error + reconstruction-error + unable-to-delete-vertex-error + hinted-abort-error + query-error) + (:documentation "Returned along with a batch of results in the remote-pull +RPC. Indicates the state of execution on the worker.") + (:serialize)) + +(lcp:define-struct pull-data () + ((pull-state "PullState") + (frames "std::vector<std::vector<query::TypedValue>>")) + (:documentation + "The data returned to the end consumer (the Pull operator). Contains only +the relevant parts of the response, ready for use.")) + +(lcp:define-struct pull-res-data () + ((pull-state "PullState" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::PullState" "PullState") + :capnp-load (lcp:capnp-load-enum "capnp::PullState" "PullState")) + (frames "std::vector<std::vector<query::TypedValue>>" + :capnp-type "List(List(Dis.TypedValue))" + :capnp-save + (lambda (builder member) + #>cpp + for (size_t frame_i = 0; frame_i < ${member}.size(); ++frame_i) { + const auto &frame = ${member}[frame_i]; + auto frame_builder = ${builder}.init(frame_i, frame.size()); + for (size_t val_i = 0; val_i < frame.size(); ++val_i) { + const auto &value = frame[val_i]; + auto value_builder = frame_builder[val_i]; + utils::SaveCapnpTypedValue( + value, &value_builder, + [this](const auto &value, auto *builder) { + this->SaveGraphElement(value, builder); + }); + } + } + cpp<#) + :capnp-load + (lambda (reader member) + #>cpp + ${member}.reserve(${reader}.size()); + for (const auto &frame_reader : ${reader}) { + std::vector<query::TypedValue> current_frame; + current_frame.reserve(frame_reader.size()); + for (const auto &value_reader : frame_reader) { + query::TypedValue value; + utils::LoadCapnpTypedValue( + value_reader, &value, + [this, dba](const auto &reader, auto *value) { + this->LoadGraphElement(dba, reader, value); + }); + current_frame.emplace_back(value); + } + ${member}.emplace_back(current_frame); + } + cpp<#)) + (worker-id :int16_t :capnp-save :dont-save + :documentation + "Id of the worker on which the response is created, used for +serializing vertices (converting local to global addresses). Indicates which +of (old, new) records of a graph element should be sent.") + (send-old :bool :capnp-save :dont-save) + (send-new :bool :capnp-save :dont-save) + ;; Temporary caches used between deserialization and post-processing + ;; (transfering the ownership of this data to a Cache). + (vertices "std::vector<GraphElementData<Vertex>>" :capnp-save :dont-save) + (edges "std::vector<GraphElementData<Edge>>" :capnp-save :dont-save) + (paths "std::vector<PathData>" :capnp-save :dont-save)) + (:documentation + "The data of the remote pull response. Post-processing is required after +deserialization to initialize Vertex/Edge typed values in the frames (possibly +encapsulated in lists/maps) to their proper values. This requires a +GraphDbAccessor and therefore can't be done as part of deserialization. + +TODO - make it possible to inject a &GraphDbAcessor from the Pull layer all +the way into RPC data deserialization to remove the requirement for +post-processing. The current approach of holding references to parts of the +frame (potentially embedded in lists/maps) is too error-prone.") + (:public + #>cpp + private: + cpp<# + (lcp:define-struct (graph-element-data t-record) () + ((global-address "storage::Address<mvcc::VersionList<TRecord>>") + (old-record "std::unique_ptr<TRecord>") + (new-record "std::unique_ptr<TRecord>") + (element-in-frame + "query::TypedValue *" + :documentation + "The position in frame is optional. This same structure is used for +deserializing path elements, in which case the vertex/edge in question is not +directly part of the frame.")) + (:documentation + "Temp cache for deserialized vertices and edges. These objects are +created during deserialization. They are used immediatelly after during +post-processing. The vertex/edge data ownership gets transfered to the Cache, +and the `element_in_frame` reference is used to set the appropriate accessor +to the appropriate value. Not used on side that generates the response.") + (:public + #>cpp + GraphElementData(storage::Address<mvcc::VersionList<TRecord>> address, + std::unique_ptr<TRecord> old_record, std::unique_ptr<TRecord> new_record, + query::TypedValue *element_in_frame) + : global_address(address), + old_record(std::move(old_record)), + new_record(std::move(new_record)), + element_in_frame(element_in_frame) {} + cpp<#)) + (lcp:define-struct path-data () + ((vertices "std::vector<GraphElementData<Vertex>>") + (edges "std::vector<GraphElementData<Edge>>") + (path-in-frame "query::TypedValue *")) + (:public + #>cpp + PathData(query::TypedValue *path_in_frame) : path_in_frame(path_in_frame) {} + cpp<#) + (:documentation "Same like `GraphElementData`, but for paths.")) + #>cpp + public: + PullResData() {} // Default constructor required for serialization. + PullResData(int worker_id, bool send_old, bool send_new) + : worker_id(worker_id), send_old(send_old), send_new(send_new) {} + + PullResData(const PullResData &) = delete; + PullResData &operator=(const PullResData &) = delete; + PullResData(PullResData &&) = default; + PullResData &operator=(PullResData &&) = default; + + + /// Saves a typed value that is a vertex/edge/path. + template <class TArchive> + void SaveGraphElement(TArchive &ar, const query::TypedValue &value) const { + // Helper template function for storing a vertex or an edge. + auto save_element = [&ar, this](auto element_accessor) { + ar << element_accessor.GlobalAddress().raw(); + + // If both old and new are null, we need to reconstruct. + if (!(element_accessor.GetOld() || element_accessor.GetNew())) { + bool result = element_accessor.Reconstruct(); + CHECK(result) << "Attempting to serialize an element not visible to " + "current transaction."; + } + auto *old_rec = element_accessor.GetOld(); + if (send_old && old_rec) { + ar << true; + distributed::SaveElement(ar, *old_rec, worker_id); + } else { + ar << false; + } + if (send_new) { + // Must call SwitchNew as that will trigger a potentially necesary + // Reconstruct. + element_accessor.SwitchNew(); + auto *new_rec = element_accessor.GetNew(); + if (new_rec) { + ar << true; + distributed::SaveElement(ar, *new_rec, worker_id); + } else { + ar << false; + } + } else { + ar << false; + } + }; + switch (value.type()) { + case query::TypedValue::Type::Vertex: + save_element(value.ValueVertex()); + break; + case query::TypedValue::Type::Edge: + save_element(value.ValueEdge()); + break; + case query::TypedValue::Type::Path: { + auto &path = value.ValuePath(); + ar << path.size(); + save_element(path.vertices()[0]); + for (size_t i = 0; i < path.size(); ++i) { + save_element(path.edges()[i]); + save_element(path.vertices()[i + 1]); + } + break; + } + default: + LOG(FATAL) << "Unsupported graph element type: " << value.type(); + } + } + + /// Loads a typed value that is a vertex/edge/path. Part of the + /// deserialization process, populates the temporary data caches which are + /// processed later. + template <class TArchive> + void LoadGraphElement(TArchive &ar, query::TypedValue::Type type, + query::TypedValue &value) { + auto load_edge = [](auto &ar) { + bool exists; + ar >> exists; + return exists ? LoadEdge(ar) : nullptr; + }; + auto load_vertex = [](auto &ar) { + bool exists; + ar >> exists; + return exists ? LoadVertex(ar) : nullptr; + }; + + switch (type) { + case query::TypedValue::Type::Vertex: { + storage::VertexAddress::StorageT address; + ar >> address; + vertices.emplace_back(storage::VertexAddress(address), load_vertex(ar), + load_vertex(ar), &value); + break; + } + case query::TypedValue::Type::Edge: { + storage::VertexAddress::StorageT address; + ar >> address; + edges.emplace_back(storage::EdgeAddress(address), load_edge(ar), + load_edge(ar), &value); + break; + } + case query::TypedValue::Type::Path: { + size_t path_size; + ar >> path_size; + + paths.emplace_back(&value); + auto &path_data = paths.back(); + + storage::VertexAddress::StorageT vertex_address; + storage::EdgeAddress::StorageT edge_address; + ar >> vertex_address; + path_data.vertices.emplace_back(storage::VertexAddress(vertex_address), + load_vertex(ar), load_vertex(ar), + nullptr); + for (size_t i = 0; i < path_size; ++i) { + ar >> edge_address; + path_data.edges.emplace_back(storage::EdgeAddress(edge_address), + load_edge(ar), load_edge(ar), nullptr); + ar >> vertex_address; + path_data.vertices.emplace_back( + storage::VertexAddress(vertex_address), load_vertex(ar), + load_vertex(ar), nullptr); + } + break; + } + default: + LOG(FATAL) << "Unsupported graph element type: " << type; + } + } + cpp<#) + (:private + #>cpp + void SaveGraphElement(const query::TypedValue &, + distributed::capnp::TypedValue::Builder *) const; + void LoadGraphElement(database::GraphDbAccessor *, + const distributed::capnp::TypedValue::Reader &, + query::TypedValue *); + cpp<#) + (:serialize :capnp :load-args '((dba "database::GraphDbAccessor *")))) + +(lcp:in-impl + #>cpp + void PullResData::SaveGraphElement( + const query::TypedValue &value, + distributed::capnp::TypedValue::Builder *builder) const { + auto save_element = [this](auto accessor, auto *builder) { + builder->setAddress(accessor.GlobalAddress().raw()); + // If both old and new are null, we need to reconstruct + if (!(accessor.GetOld() || accessor.GetNew())) { + bool result = accessor.Reconstruct(); + CHECK(result) << "Attempting to serialize an element not visible to " + "current transaction."; + } + auto *old_rec = accessor.GetOld(); + if (send_old && old_rec) { + auto old_builder = builder->initOld(); + distributed::SaveElement(*old_rec, &old_builder, worker_id); + } + if (send_new) { + // Must call SwitchNew as that will trigger a potentially necesary + // Reconstruct. + accessor.SwitchNew(); + auto *new_rec = accessor.GetNew(); + if (new_rec) { + auto new_builder = builder->initNew(); + distributed::SaveElement(*new_rec, &new_builder, worker_id); + } + } + }; + switch (value.type()) { + case query::TypedValue::Type::Vertex: { + auto vertex_builder = builder->initVertex(); + save_element(value.ValueVertex(), &vertex_builder); + break; + } + case query::TypedValue::Type::Edge: { + auto edge_builder = builder->initEdge(); + save_element(value.ValueEdge(), &edge_builder); + break; + } + case query::TypedValue::Type::Path: { + const auto &path = value.ValuePath(); + auto path_builder = builder->initPath(); + auto vertices_builder = path_builder.initVertices(path.vertices().size()); + for (size_t i = 0; i < path.vertices().size(); ++i) { + auto vertex_builder = vertices_builder[i]; + save_element(path.vertices()[i], &vertex_builder); + } + auto edges_builder = path_builder.initEdges(path.edges().size()); + for (size_t i = 0; i < path.edges().size(); ++i) { + auto edge_builder = edges_builder[i]; + save_element(path.edges()[i], &edge_builder); + } + break; + } + default: + LOG(FATAL) << "Unsupported graph element type: " << value.type(); + } + } + +void PullResData::LoadGraphElement( + database::GraphDbAccessor *dba, + const distributed::capnp::TypedValue::Reader &reader, + query::TypedValue *value) { + auto load_vertex = [dba](const auto &vertex_reader) { + storage::VertexAddress global_address(vertex_reader.getAddress()); + auto old_record = + vertex_reader.hasOld() + ? distributed::LoadVertex<const distributed::capnp::Vertex::Reader>( + vertex_reader.getOld()) + : nullptr; + auto new_record = + vertex_reader.hasNew() + ? distributed::LoadVertex<const distributed::capnp::Vertex::Reader>( + vertex_reader.getNew()) + : nullptr; + dba->db() + .data_manager() + .Elements<Vertex>(dba->transaction_id()) + .emplace(global_address.gid(), std::move(old_record), + std::move(new_record)); + return VertexAccessor(global_address, *dba); + }; + auto load_edge = [dba](const auto &edge_reader) { + storage::EdgeAddress global_address(edge_reader.getAddress()); + auto old_record = + edge_reader.hasOld() + ? distributed::LoadEdge<const distributed::capnp::Edge::Reader>( + edge_reader.getOld()) + : nullptr; + auto new_record = + edge_reader.hasNew() + ? distributed::LoadEdge<const distributed::capnp::Edge::Reader>( + edge_reader.getNew()) + : nullptr; + dba->db() + .data_manager() + .Elements<Edge>(dba->transaction_id()) + .emplace(global_address.gid(), std::move(old_record), + std::move(new_record)); + return EdgeAccessor(global_address, *dba); + }; + switch (reader.which()) { + case distributed::capnp::TypedValue::VERTEX: + *value = load_vertex(reader.getVertex()); + break; + case distributed::capnp::TypedValue::EDGE: + *value = load_edge(reader.getEdge()); + break; + case distributed::capnp::TypedValue::PATH: { + auto vertices_reader = reader.getPath().getVertices(); + auto edges_reader = reader.getPath().getEdges(); + query::Path path(load_vertex(vertices_reader[0])); + for (size_t i = 0; i < edges_reader.size(); ++i) { + path.Expand(load_edge(edges_reader[i])); + path.Expand(load_vertex(vertices_reader[i + 1])); + } + *value = path; + break; + } + default: + LOG(FATAL) << "Unsupported graph element type."; + } +} + + cpp<#) + +(lcp:define-rpc pull + (:request + ((tx-id "tx::TransactionId") + (tx-snapshot "tx::Snapshot") + (plan-id :int64_t) + (command-id "tx::CommandId") + (params "Parameters" + :save-fun + " + ar << params.size(); + for (auto &kv : params) { + ar << kv.first; + // Params never contain a vertex/edge, so save plan TypedValue. + utils::SaveTypedValue(ar, kv.second); + } + " + :load-fun + " + size_t params_size; + ar >> params_size; + for (size_t i = 0; i < params_size; ++i) { + int token_pos; + ar >> token_pos; + query::TypedValue param; + // Params never contain a vertex/edge, so load plan TypedValue. + utils::LoadTypedValue(ar, param); + params.Add(token_pos, param); + } + " + :capnp-type "Utils.Map(Utils.BoxInt64, Dis.TypedValue)" + :capnp-save + (lambda (builder member) + #>cpp + auto entries_builder = ${builder}.initEntries(${member}.size()); + size_t i = 0; + for (auto &entry : params) { + auto builder = entries_builder[i]; + auto key_builder = builder.initKey(); + key_builder.setValue(entry.first); + auto value_builder = builder.initValue(); + utils::SaveCapnpTypedValue(entry.second, &value_builder); + ++i; + } + cpp<#) + :capnp-load + (lambda (reader member) + #>cpp + for (const auto &entry_reader : ${reader}.getEntries()) { + query::TypedValue value; + utils::LoadCapnpTypedValue(entry_reader.getValue(), &value); + ${member}.Add(entry_reader.getKey().getValue(), value); + } + cpp<#)) + (symbols "std::vector<query::Symbol>" + :capnp-type "List(Sem.Symbol)" + :capnp-save (lcp:capnp-save-vector "query::capnp::Symbol" "query::Symbol") + :capnp-load (lcp:capnp-load-vector "query::capnp::Symbol" "query::Symbol")) + (accumulate :bool) + (batch-size :int64_t) + ;; Indicates which of (old, new) records of a graph element should be sent. + (send-old :bool) + (send-new :bool))) + (:response + ((data "PullResData" :initarg :move + :save-fun + " + ar << data.pull_state; + ar << data.frames.size(); + // We need to indicate how many values are in each frame. + // Assume all the frames have an equal number of elements. + ar << (data.frames.size() == 0 ? 0 : data.frames[0].size()); + for (const auto &frame : data.frames) { + for (const auto &value : frame) { + utils::SaveTypedValue<TArchive>( + ar, value, [this](TArchive &ar, const query::TypedValue &value) { + data.SaveGraphElement(ar, value); + }); + } + } + " + :load-fun + " + ar >> data.pull_state; + size_t frame_count; + ar >> frame_count; + data.frames.reserve(frame_count); + size_t frame_size; + ar >> frame_size; + for (size_t i = 0; i < frame_count; ++i) { + data.frames.emplace_back(); + auto ¤t_frame = data.frames.back(); + current_frame.reserve(frame_size); + for (size_t j = 0; j < frame_size; ++j) { + current_frame.emplace_back(); + utils::LoadTypedValue<TArchive>( + ar, current_frame.back(), + [this](TArchive &ar, query::TypedValue::TypedValue::Type type, + query::TypedValue &value) { + data.LoadGraphElement(ar, type, value); + }); + } + } + ")) + (:serialize :capnp :base t :load-args '((dba "database::GraphDbAccessor *"))))) + +;; TODO make a separate RPC for the continuation of an existing pull, as an +;; optimization not to have to send the full PullReqData pack every time. + +(lcp:define-rpc transaction-command-advanced + (:request ((member "tx::TransactionId"))) + (:response ())) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/pull_rpc_clients.cpp b/src/distributed/pull_rpc_clients.cpp index fc2903546..8652a3830 100644 --- a/src/distributed/pull_rpc_clients.cpp +++ b/src/distributed/pull_rpc_clients.cpp @@ -12,53 +12,21 @@ utils::Future<PullData> PullRpcClients::Pull( tx::CommandId command_id, const Parameters ¶ms, const std::vector<query::Symbol> &symbols, bool accumulate, int batch_size) { - return clients_.ExecuteOnWorker<PullData>( - worker_id, [&dba, plan_id, command_id, params, symbols, accumulate, - batch_size](int worker_id, ClientPool &client_pool) { - auto result = client_pool.Call<PullRpc>( - dba.transaction_id(), dba.transaction().snapshot(), plan_id, - command_id, params, symbols, accumulate, batch_size, true, true); - - auto handle_vertex = [&dba](auto &v) { - dba.db() - .data_manager() - .Elements<Vertex>(dba.transaction_id()) - .emplace(v.global_address.gid(), std::move(v.old_record), - std::move(v.new_record)); - if (v.element_in_frame) { - VertexAccessor va(v.global_address, dba); - *v.element_in_frame = va; - } - }; - auto handle_edge = [&dba](auto &e) { - dba.db() - .data_manager() - .Elements<Edge>(dba.transaction_id()) - .emplace(e.global_address.gid(), std::move(e.old_record), - std::move(e.new_record)); - if (e.element_in_frame) { - EdgeAccessor ea(e.global_address, dba); - *e.element_in_frame = ea; - } - }; - for (auto &v : result->data.vertices) handle_vertex(v); - for (auto &e : result->data.edges) handle_edge(e); - for (auto &p : result->data.paths) { - handle_vertex(p.vertices[0]); - p.path_in_frame = - query::Path(VertexAccessor(p.vertices[0].global_address, dba)); - query::Path &path_in_frame = p.path_in_frame.ValuePath(); - for (size_t i = 0; i < p.edges.size(); ++i) { - handle_edge(p.edges[i]); - path_in_frame.Expand(EdgeAccessor(p.edges[i].global_address, dba)); - handle_vertex(p.vertices[i + 1]); - path_in_frame.Expand( - VertexAccessor(p.vertices[i + 1].global_address, dba)); - } - } - - return std::move(result->data.state_and_frames); - }); + return clients_.ExecuteOnWorker< + PullData>(worker_id, [&dba, plan_id, command_id, params, symbols, + accumulate, batch_size](int worker_id, + ClientPool &client_pool) { + auto load_pull_res = [&dba](const auto &res_reader) { + PullRes res; + res.Load(res_reader, &dba); + return res; + }; + auto result = client_pool.CallWithLoad<PullRpc>( + load_pull_res, dba.transaction_id(), dba.transaction().snapshot(), + plan_id, command_id, params, symbols, accumulate, batch_size, true, + true); + return PullData{result->data.pull_state, std::move(result->data.frames)}; + }); } std::vector<utils::Future<void>> diff --git a/src/distributed/rpc_worker_clients.hpp b/src/distributed/rpc_worker_clients.hpp index 5b7c5b043..9fd4cc55a 100644 --- a/src/distributed/rpc_worker_clients.hpp +++ b/src/distributed/rpc_worker_clients.hpp @@ -91,9 +91,8 @@ class IndexRpcClients { worker_id, [label, property, transaction_id]( int worker_id, communication::rpc::ClientPool &client_pool) { - return client_pool.Call<BuildIndexRpc>( - distributed::IndexLabelPropertyTx{ - label, property, transaction_id}) != nullptr; + return static_cast<bool>( + client_pool.Call<BuildIndexRpc>(label, property, transaction_id)); }); } diff --git a/src/distributed/serialization.capnp b/src/distributed/serialization.capnp new file mode 100644 index 000000000..4f51247c1 --- /dev/null +++ b/src/distributed/serialization.capnp @@ -0,0 +1,71 @@ +@0xccb448f0b998d9c8; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("distributed::capnp"); + +struct Address { + gid @0 :UInt64; + workerId @1 :Int16; +} + +struct PropertyValue { + id @0 :UInt16; + value @1 :TypedValue; +} + +struct Edge { + from @0 :Address; + to @1 :Address; + typeId @2 :UInt16; + properties @3 :List(PropertyValue); +} + +struct Vertex { + outEdges @0 :List(EdgeEntry); + inEdges @1 :List(EdgeEntry); + labelIds @2 :List(UInt16); + properties @3 :List(PropertyValue); + + struct EdgeEntry { + vertexAddress @0 :Address; + edgeAddress @1 :Address; + edgeTypeId @2 :UInt16; + } +} + +struct TypedValue { + union { + nullType @0 :Void; + bool @1 :Bool; + integer @2 :Int64; + double @3 :Float64; + string @4 :Text; + list @5 :List(TypedValue); + map @6 :List(Entry); + vertex @7 :VertexAccessor; + edge @8 :EdgeAccessor; + path @9 :Path; + } + + struct Entry { + key @0 :Text; + value @1 :TypedValue; + } + + struct VertexAccessor { + address @0 :UInt64; + old @1 :Vertex; + new @2: Vertex; + } + + struct EdgeAccessor { + address @0 :UInt64; + old @1 :Edge; + new @2: Edge; + } + + struct Path { + vertices @0 :List(VertexAccessor); + edges @1 :List(EdgeAccessor); + } +} diff --git a/src/distributed/serialization.cpp b/src/distributed/serialization.cpp new file mode 100644 index 000000000..e8c74f831 --- /dev/null +++ b/src/distributed/serialization.cpp @@ -0,0 +1,120 @@ +#include "distributed/serialization.hpp" + +namespace { + +template <class TAddress> +void SaveAddress(TAddress address, + distributed::capnp::Address::Builder *builder, + int16_t worker_id) { + builder->setGid(address.is_local() ? address.local()->gid_ : address.gid()); + builder->setWorkerId(address.is_local() ? worker_id : address.worker_id()); +} + +storage::VertexAddress LoadVertexAddress( + const distributed::capnp::Address::Reader &reader) { + return {reader.getGid(), reader.getWorkerId()}; +} + +storage::EdgeAddress LoadEdgeAddress( + const distributed::capnp::Address::Reader &reader) { + return {reader.getGid(), reader.getWorkerId()}; +} + +void SaveProperties( + const PropertyValueStore &props, + ::capnp::List<distributed::capnp::PropertyValue>::Builder *builder) { + int64_t i = 0; + for (const auto &kv : props) { + auto prop_builder = (*builder)[i]; + prop_builder.setId(kv.first.Id()); + auto value_builder = prop_builder.initValue(); + utils::SaveCapnpTypedValue(kv.second, &value_builder); + ++i; + } +} + +PropertyValueStore LoadProperties( + const ::capnp::List<distributed::capnp::PropertyValue>::Reader &reader) { + PropertyValueStore props; + for (const auto &prop_reader : reader) { + query::TypedValue value; + utils::LoadCapnpTypedValue(prop_reader.getValue(), &value); + props.set(storage::Property(prop_reader.getId()), value); + } + return props; +} + +} // namespace + +namespace distributed { + +void SaveVertex(const Vertex &vertex, capnp::Vertex::Builder *builder, + int16_t worker_id) { + auto save_edges = [worker_id](const auto &edges, auto *edges_builder) { + int64_t i = 0; + for (const auto &edge : edges) { + auto edge_builder = (*edges_builder)[i]; + auto vertex_addr_builder = edge_builder.initVertexAddress(); + SaveAddress(edge.vertex, &vertex_addr_builder, worker_id); + auto edge_addr_builder = edge_builder.initEdgeAddress(); + SaveAddress(edge.edge, &edge_addr_builder, worker_id); + edge_builder.setEdgeTypeId(edge.edge_type.Id()); + ++i; + } + }; + auto out_builder = builder->initOutEdges(vertex.out_.size()); + save_edges(vertex.out_, &out_builder); + auto in_builder = builder->initInEdges(vertex.in_.size()); + save_edges(vertex.in_, &in_builder); + auto labels_builder = builder->initLabelIds(vertex.labels_.size()); + for (size_t i = 0; i < vertex.labels_.size(); ++i) { + labels_builder.set(i, vertex.labels_[i].Id()); + } + auto properties_builder = builder->initProperties(vertex.properties_.size()); + SaveProperties(vertex.properties_, &properties_builder); +} + +template <> +std::unique_ptr<Vertex> LoadVertex(const capnp::Vertex::Reader &reader) { + auto vertex = std::make_unique<Vertex>(); + auto load_edges = [](const auto &edges_reader) { + Edges edges; + for (const auto &edge_reader : edges_reader) { + auto vertex_address = LoadVertexAddress(edge_reader.getVertexAddress()); + auto edge_address = LoadEdgeAddress(edge_reader.getEdgeAddress()); + storage::EdgeType edge_type(edge_reader.getEdgeTypeId()); + edges.emplace(vertex_address, edge_address, edge_type); + } + return edges; + }; + vertex->out_ = load_edges(reader.getOutEdges()); + vertex->in_ = load_edges(reader.getInEdges()); + for (const auto &label_id : reader.getLabelIds()) { + vertex->labels_.emplace_back(label_id); + } + vertex->properties_ = LoadProperties(reader.getProperties()); + return vertex; +} + +void SaveEdge(const Edge &edge, capnp::Edge::Builder *builder, + int16_t worker_id) { + auto from_builder = builder->initFrom(); + SaveAddress(edge.from_, &from_builder, worker_id); + auto to_builder = builder->initTo(); + SaveAddress(edge.to_, &to_builder, worker_id); + builder->setTypeId(edge.edge_type_.Id()); + auto properties_builder = builder->initProperties(edge.properties_.size()); + SaveProperties(edge.properties_, &properties_builder); +} + +template <> +std::unique_ptr<Edge> LoadEdge(const capnp::Edge::Reader &reader) { + auto from = LoadVertexAddress(reader.getFrom()); + auto to = LoadVertexAddress(reader.getTo()); + auto edge = + std::make_unique<Edge>(from, to, storage::EdgeType{reader.getTypeId()}); + edge->properties_ = LoadProperties(reader.getProperties()); + return edge; +} + +} // namespace distributed diff --git a/src/distributed/serialization.hpp b/src/distributed/serialization.hpp index 468b9f55b..463c3cea5 100644 --- a/src/distributed/serialization.hpp +++ b/src/distributed/serialization.hpp @@ -4,6 +4,7 @@ #include <memory> #include <vector> +#include "distributed/serialization.capnp.h" #include "storage/address_types.hpp" #include "storage/edge.hpp" #include "storage/types.hpp" @@ -38,6 +39,9 @@ void SaveProperties(TArchive &ar, const PropertyValueStore &props) { } } // namespace impl +void SaveVertex(const Vertex &vertex, capnp::Vertex::Builder *builder, + int16_t worker_id); + /** * Saves the given vertex into the given Boost archive. * @@ -68,6 +72,9 @@ void SaveVertex(TArchive &ar, const Vertex &vertex, int worker_id) { impl::SaveProperties(ar, vertex.properties_); } +void SaveEdge(const Edge &edge, capnp::Edge::Builder *builder, + int16_t worker_id); + /** * Saves the given edge into the given Boost archive. * @@ -85,6 +92,18 @@ void SaveEdge(TArchive &ar, const Edge &edge, int worker_id) { impl::SaveProperties(ar, edge.properties_); } +/// Alias for `SaveEdge` allowing for param type resolution. +inline void SaveElement(const Edge &record, capnp::Edge::Builder *builder, + int16_t worker_id) { + return SaveEdge(record, builder, worker_id); +} + +/// Alias for `SaveVertex` allowing for param type resolution. +inline void SaveElement(const Vertex &record, capnp::Vertex::Builder *builder, + int16_t worker_id) { + return SaveVertex(record, builder, worker_id); +} + /// Alias for `SaveEdge` allowing for param type resolution. template <typename TArchive> void SaveElement(TArchive &ar, const Edge &record, int worker_id) { @@ -163,6 +182,9 @@ std::unique_ptr<Vertex> LoadVertex(TArchive &ar) { return vertex; } +template <> +std::unique_ptr<Vertex> LoadVertex(const capnp::Vertex::Reader &reader); + /** * Loads an Edge from the given archive and returns it. * @@ -181,4 +203,7 @@ std::unique_ptr<Edge> LoadEdge(TArchive &ar) { return edge; } +template <> +std::unique_ptr<Edge> LoadEdge(const capnp::Edge::Reader &reader); + } // namespace distributed diff --git a/src/distributed/storage_gc_rpc_messages.hpp b/src/distributed/storage_gc_rpc_messages.hpp deleted file mode 100644 index 716993ede..000000000 --- a/src/distributed/storage_gc_rpc_messages.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" - -#include "communication/rpc/messages.hpp" -#include "io/network/endpoint.hpp" -#include "transactions/transaction.hpp" - -namespace distributed { - -using communication::rpc::Message; -using Endpoint = io::network::Endpoint; - -struct GcClearedStatusReq : public Message { - GcClearedStatusReq() {} - GcClearedStatusReq(tx::TransactionId local_oldest_active, int worker_id) - : local_oldest_active(local_oldest_active), worker_id(worker_id) {} - - tx::TransactionId local_oldest_active; - int worker_id; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar &local_oldest_active; - ar &worker_id; - } -}; - -RPC_NO_MEMBER_MESSAGE(GcClearedStatusRes); - -using RanLocalGcRpc = - communication::rpc::RequestResponse<GcClearedStatusReq, GcClearedStatusRes>; - -} // namespace distributed diff --git a/src/distributed/storage_gc_rpc_messages.lcp b/src/distributed/storage_gc_rpc_messages.lcp new file mode 100644 index 000000000..a6bff3311 --- /dev/null +++ b/src/distributed/storage_gc_rpc_messages.lcp @@ -0,0 +1,20 @@ +#>cpp +#pragma once + +#include "communication/rpc/messages.hpp" +#include "distributed/storage_gc_rpc_messages.capnp.h" +#include "io/network/endpoint.hpp" +#include "transactions/transaction.hpp" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:define-rpc ran-local-gc + (:request + ((local-oldest-active "tx::TransactionId" :capnp-type "UInt64") + (worker-id :int16_t))) + (:response ())) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/token_sharing_rpc_messages.hpp b/src/distributed/token_sharing_rpc_messages.hpp deleted file mode 100644 index a5ed70636..000000000 --- a/src/distributed/token_sharing_rpc_messages.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include <memory> -#include <string> - -#include "communication/rpc/messages.hpp" -#include "distributed/serialization.hpp" - -namespace distributed { - -RPC_NO_MEMBER_MESSAGE(TokenTransferReq); -RPC_NO_MEMBER_MESSAGE(TokenTransferRes); - -using TokenTransferRpc = - communication::rpc::RequestResponse<TokenTransferReq, TokenTransferRes>; -} // namespace distributed diff --git a/src/distributed/token_sharing_rpc_messages.lcp b/src/distributed/token_sharing_rpc_messages.lcp new file mode 100644 index 000000000..6c3450d05 --- /dev/null +++ b/src/distributed/token_sharing_rpc_messages.lcp @@ -0,0 +1,20 @@ +#>cpp +#pragma once + +#include <memory> +#include <string> + +#include "communication/rpc/messages.hpp" +#include "distributed/serialization.hpp" +#include "distributed/token_sharing_rpc_messages.capnp.h" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:define-rpc token-transfer + (:request ()) + (:response ())) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/token_sharing_rpc_server.hpp b/src/distributed/token_sharing_rpc_server.hpp index ead29d56e..a3d8c3fe0 100644 --- a/src/distributed/token_sharing_rpc_server.hpp +++ b/src/distributed/token_sharing_rpc_server.hpp @@ -29,10 +29,7 @@ class TokenSharingRpcServer { clients_(clients), dgp_(db) { server_->Register<distributed::TokenTransferRpc>( - [this](const distributed::TokenTransferReq &req) { - token_ = true; - return std::make_unique<distributed::TokenTransferRes>(); - }); + [this](const auto &req_reader, auto *res_builder) { token_ = true; }); runner_ = std::thread([this]() { while (true) { diff --git a/src/distributed/transactional_cache_cleaner.hpp b/src/distributed/transactional_cache_cleaner.hpp index 4644023a1..98e6007fd 100644 --- a/src/distributed/transactional_cache_cleaner.hpp +++ b/src/distributed/transactional_cache_cleaner.hpp @@ -72,11 +72,10 @@ class WorkerTransactionalCacheCleaner : public TransactionalCacheCleaner { rpc_server_(server), produce_server_(produce_server) { Register(tx_engine); - rpc_server_.Register<WaitOnTransactionEndRpc>( - [this](const WaitOnTransactionEndReq &req) { - produce_server_.FinishAndClearOngoingProducePlans(req.member); - return std::make_unique<WaitOnTransactionEndRes>(); - }); + rpc_server_.Register<WaitOnTransactionEndRpc>([this](const auto &req_reader, + auto *res_builder) { + produce_server_.FinishAndClearOngoingProducePlans(req_reader.getMember()); + }); } private: diff --git a/src/distributed/transactional_cache_cleaner_rpc_messages.hpp b/src/distributed/transactional_cache_cleaner_rpc_messages.hpp deleted file mode 100644 index a949ae828..000000000 --- a/src/distributed/transactional_cache_cleaner_rpc_messages.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "communication/rpc/messages.hpp" -#include "transactions/type.hpp" - -namespace distributed { - -RPC_SINGLE_MEMBER_MESSAGE(WaitOnTransactionEndReq, tx::TransactionId); -RPC_NO_MEMBER_MESSAGE(WaitOnTransactionEndRes); -using WaitOnTransactionEndRpc = - communication::rpc::RequestResponse<WaitOnTransactionEndReq, - WaitOnTransactionEndRes>; -}; diff --git a/src/distributed/transactional_cache_cleaner_rpc_messages.lcp b/src/distributed/transactional_cache_cleaner_rpc_messages.lcp new file mode 100644 index 000000000..7580bd8e3 --- /dev/null +++ b/src/distributed/transactional_cache_cleaner_rpc_messages.lcp @@ -0,0 +1,17 @@ +#>cpp +#pragma once + +#include "distributed/transactional_cache_cleaner_rpc_messages.capnp.h" +#include "communication/rpc/messages.hpp" +#include "transactions/type.hpp" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:define-rpc wait-on-transaction-end + (:request ((member "tx::TransactionId" :capnp-type "UInt64"))) + (:response ())) + +(lcp:pop-namespace) diff --git a/src/distributed/updates_rpc_clients.cpp b/src/distributed/updates_rpc_clients.cpp index 42e5f8ef7..0f0b61f20 100644 --- a/src/distributed/updates_rpc_clients.cpp +++ b/src/distributed/updates_rpc_clients.cpp @@ -50,7 +50,6 @@ storage::EdgeAddress UpdatesRpcClients::CreateEdge( tx::TransactionId tx_id, VertexAccessor &from, VertexAccessor &to, storage::EdgeType edge_type) { CHECK(from.address().is_remote()) << "In CreateEdge `from` must be remote"; - int from_worker = from.address().worker_id(); auto res = worker_clients_.GetClientPool(from_worker) .Call<CreateEdgeRpc>(CreateEdgeReqData{ diff --git a/src/distributed/updates_rpc_messages.hpp b/src/distributed/updates_rpc_messages.hpp deleted file mode 100644 index 098a13696..000000000 --- a/src/distributed/updates_rpc_messages.hpp +++ /dev/null @@ -1,203 +0,0 @@ -#pragma once - -#include <unordered_map> - -#include "boost/serialization/vector.hpp" - -#include "communication/rpc/messages.hpp" -#include "database/state_delta.hpp" -#include "storage/address_types.hpp" -#include "storage/gid.hpp" -#include "transactions/type.hpp" -#include "utils/serialization.hpp" - -namespace distributed { - -/// The result of sending or applying a deferred update to a worker. -enum class UpdateResult { - DONE, - SERIALIZATION_ERROR, - LOCK_TIMEOUT_ERROR, - UPDATE_DELETED_ERROR, - UNABLE_TO_DELETE_VERTEX_ERROR -}; - -RPC_SINGLE_MEMBER_MESSAGE(UpdateReq, database::StateDelta); -RPC_SINGLE_MEMBER_MESSAGE(UpdateRes, UpdateResult); -using UpdateRpc = communication::rpc::RequestResponse<UpdateReq, UpdateRes>; - -RPC_SINGLE_MEMBER_MESSAGE(UpdateApplyReq, tx::TransactionId); -RPC_SINGLE_MEMBER_MESSAGE(UpdateApplyRes, UpdateResult); -using UpdateApplyRpc = - communication::rpc::RequestResponse<UpdateApplyReq, UpdateApplyRes>; - -struct CreateResult { - UpdateResult result; - // Only valid if creation was successful. - gid::Gid gid; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &result; - ar &gid; - } -}; - -struct CreateVertexReqData { - tx::TransactionId tx_id; - std::vector<storage::Label> labels; - std::unordered_map<storage::Property, query::TypedValue> properties; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void save(TArchive &ar, unsigned int) const { - ar << tx_id; - ar << labels; - ar << properties.size(); - for (auto &kv : properties) { - ar << kv.first; - utils::SaveTypedValue(ar, kv.second); - } - } - - template <class TArchive> - void load(TArchive &ar, unsigned int) { - ar >> tx_id; - ar >> labels; - size_t props_size; - ar >> props_size; - for (size_t i = 0; i < props_size; ++i) { - storage::Property p; - ar >> p; - query::TypedValue tv; - utils::LoadTypedValue(ar, tv); - properties.emplace(p, std::move(tv)); - } - } - BOOST_SERIALIZATION_SPLIT_MEMBER() -}; - -RPC_SINGLE_MEMBER_MESSAGE(CreateVertexReq, CreateVertexReqData); -RPC_SINGLE_MEMBER_MESSAGE(CreateVertexRes, CreateResult); -using CreateVertexRpc = - communication::rpc::RequestResponse<CreateVertexReq, CreateVertexRes>; - -struct CreateEdgeReqData { - gid::Gid from; - storage::VertexAddress to; - storage::EdgeType edge_type; - tx::TransactionId tx_id; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &from; - ar &to; - ar &edge_type; - ar &tx_id; - } -}; - -RPC_SINGLE_MEMBER_MESSAGE(CreateEdgeReq, CreateEdgeReqData); -RPC_SINGLE_MEMBER_MESSAGE(CreateEdgeRes, CreateResult); -using CreateEdgeRpc = - communication::rpc::RequestResponse<CreateEdgeReq, CreateEdgeRes>; - -struct AddInEdgeReqData { - storage::VertexAddress from; - storage::EdgeAddress edge_address; - gid::Gid to; - storage::EdgeType edge_type; - tx::TransactionId tx_id; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &from; - ar &edge_address; - ar &to; - ar &edge_type; - ar &tx_id; - } -}; - -RPC_SINGLE_MEMBER_MESSAGE(AddInEdgeReq, AddInEdgeReqData); -RPC_SINGLE_MEMBER_MESSAGE(AddInEdgeRes, UpdateResult); -using AddInEdgeRpc = - communication::rpc::RequestResponse<AddInEdgeReq, AddInEdgeRes>; - -struct RemoveVertexReqData { - gid::Gid gid; - tx::TransactionId tx_id; - bool check_empty; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &gid; - ar &tx_id; - ar &check_empty; - } -}; - -RPC_SINGLE_MEMBER_MESSAGE(RemoveVertexReq, RemoveVertexReqData); -RPC_SINGLE_MEMBER_MESSAGE(RemoveVertexRes, UpdateResult); -using RemoveVertexRpc = - communication::rpc::RequestResponse<RemoveVertexReq, RemoveVertexRes>; - -struct RemoveEdgeData { - tx::TransactionId tx_id; - gid::Gid edge_id; - gid::Gid vertex_from_id; - storage::VertexAddress vertex_to_address; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &tx_id; - ar &edge_id; - ar &vertex_from_id; - ar &vertex_to_address; - } -}; - -RPC_SINGLE_MEMBER_MESSAGE(RemoveEdgeReq, RemoveEdgeData); -RPC_SINGLE_MEMBER_MESSAGE(RemoveEdgeRes, UpdateResult); -using RemoveEdgeRpc = - communication::rpc::RequestResponse<RemoveEdgeReq, RemoveEdgeRes>; - -struct RemoveInEdgeData { - tx::TransactionId tx_id; - gid::Gid vertex; - storage::EdgeAddress edge_address; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &tx_id; - ar &vertex; - ar &edge_address; - } -}; - -RPC_SINGLE_MEMBER_MESSAGE(RemoveInEdgeReq, RemoveInEdgeData); -RPC_SINGLE_MEMBER_MESSAGE(RemoveInEdgeRes, UpdateResult); -using RemoveInEdgeRpc = - communication::rpc::RequestResponse<RemoveInEdgeReq, RemoveInEdgeRes>; - -} // namespace distributed diff --git a/src/distributed/updates_rpc_messages.lcp b/src/distributed/updates_rpc_messages.lcp new file mode 100644 index 000000000..e9be24b4f --- /dev/null +++ b/src/distributed/updates_rpc_messages.lcp @@ -0,0 +1,187 @@ +#>cpp +#pragma once + +#include <unordered_map> + +#include "communication/rpc/messages.hpp" +#include "database/state_delta.hpp" +#include "distributed/updates_rpc_messages.capnp.h" +#include "storage/address_types.hpp" +#include "storage/gid.hpp" +#include "transactions/type.hpp" +#include "utils/serialization.hpp" +cpp<# + +(lcp:namespace distributed) + +(lcp:capnp-namespace "distributed") + +(lcp:capnp-import 'db "/database/state_delta.capnp") +(lcp:capnp-import 'dis "/distributed/serialization.capnp") +(lcp:capnp-import 'storage "/storage/serialization.capnp") +(lcp:capnp-import 'utils "/utils/serialization.capnp") + +(lcp:capnp-type-conversion "tx::TransactionId" "UInt64") +(lcp:capnp-type-conversion "gid::Gid" "UInt64") +(lcp:capnp-type-conversion "storage::Label" "Storage.Common") +(lcp:capnp-type-conversion "storage::EdgeType" "Storage.Common") +(lcp:capnp-type-conversion "storage::Property" "Storage.Common") +(lcp:capnp-type-conversion "storage::EdgeAddress" "Storage.Address") +(lcp:capnp-type-conversion "storage::VertexAddress" "Storage.Address") + +(lcp:define-enum update-result + (done + serialization-error + lock-timeout-error + update-deleted-error + unable-to-delete-vertex-error) + (:documentation "The result of sending or applying a deferred update to a worker.") + (:serialize)) + +(lcp:define-rpc update + (:request ((member "database::StateDelta" :capnp-type "Db.StateDelta"))) + (:response ((member "UpdateResult" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::UpdateResult" "UpdateResult") + :capnp-load (lcp:capnp-load-enum "capnp::UpdateResult" "UpdateResult"))))) + +(lcp:define-rpc update-apply + (:request ((member "tx::TransactionId"))) + (:response ((member "UpdateResult" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::UpdateResult" "UpdateResult") + :capnp-load (lcp:capnp-load-enum "capnp::UpdateResult" "UpdateResult"))))) + +(lcp:define-struct create-result () + ((result "UpdateResult" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::UpdateResult" "UpdateResult") + :capnp-load (lcp:capnp-load-enum "capnp::UpdateResult" "UpdateResult")) + (gid "gid::Gid" :documentation "Only valid if creation was successful.")) + (:serialize :boost :capnp)) + +(lcp:define-struct create-vertex-req-data () + ((tx-id "tx::TransactionId") + (labels "std::vector<storage::Label>" + :capnp-save (lcp:capnp-save-vector "storage::capnp::Common" "storage::Label") + :capnp-load (lcp:capnp-load-vector "storage::capnp::Common" "storage::Label")) + (properties "std::unordered_map<storage::Property, query::TypedValue>" + :save-fun + #>cpp + ar << properties.size(); + for (auto &kv : properties) { + ar << kv.first; + utils::SaveTypedValue(ar, kv.second); + } + cpp<# + :load-fun + #>cpp + size_t props_size; + ar >> props_size; + for (size_t i = 0; i < props_size; ++i) { + storage::Property p; + ar >> p; + query::TypedValue tv; + utils::LoadTypedValue(ar, tv); + properties.emplace(p, std::move(tv)); + } + cpp<# + :capnp-type "Utils.Map(Storage.Common, Dis.TypedValue)" + :capnp-save + (lambda (builder member) + #>cpp + utils::SaveMap<storage::capnp::Common, capnp::TypedValue>( + ${member}, &${builder}, + [](auto *builder, const auto &entry) { + auto key_builder = builder->initKey(); + entry.first.Save(&key_builder); + auto value_builder = builder->initValue(); + utils::SaveCapnpTypedValue(entry.second, &value_builder); + }); + cpp<#) + :capnp-load + (lambda (reader member) + #>cpp + utils::LoadMap<storage::capnp::Common, capnp::TypedValue>( + &${member}, ${reader}, + [](const auto &reader) { + storage::Property prop; + prop.Load(reader.getKey()); + query::TypedValue value; + utils::LoadCapnpTypedValue(reader.getValue(), &value); + return std::make_pair(prop, value); + }); + cpp<#))) + (:serialize :capnp)) + +(lcp:define-rpc create-vertex + (:request ((member "CreateVertexReqData"))) + (:response ((member "CreateResult")))) + +(lcp:define-struct create-edge-req-data () + ((from "gid::Gid") + (to "storage::VertexAddress") + (edge-type "storage::EdgeType") + (tx-id "tx::TransactionId")) + (:serialize :capnp)) + +(lcp:define-rpc create-edge + (:request ((member "CreateEdgeReqData"))) + (:response ((member "CreateResult")))) + +(lcp:define-struct add-in-edge-req-data () + ((from "storage::VertexAddress") + (edge-address "storage::EdgeAddress") + (to "gid::Gid") + (edge-type "storage::EdgeType") + (tx-id "tx::TransactionId")) + (:serialize :capnp)) + +(lcp:define-rpc add-in-edge + (:request ((member "AddInEdgeReqData"))) + (:response ((member "UpdateResult" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::UpdateResult" "UpdateResult") + :capnp-load (lcp:capnp-load-enum "capnp::UpdateResult" "UpdateResult"))))) + +(lcp:define-struct remove-vertex-req-data () + ((gid "gid::Gid") + (tx-id "tx::TransactionId") + (check-empty :bool)) + (:serialize :capnp)) + +(lcp:define-rpc remove-vertex + (:request ((member "RemoveVertexReqData"))) + (:response ((member "UpdateResult" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::UpdateResult" "UpdateResult") + :capnp-load (lcp:capnp-load-enum "capnp::UpdateResult" "UpdateResult"))))) + +(lcp:define-struct remove-edge-data () + ((tx-id "tx::TransactionId") + (edge-id "gid::Gid") + (vertex-from-id "gid::Gid") + (vertex-to-address "storage::VertexAddress")) + (:serialize :capnp)) + +(lcp:define-rpc remove-edge + (:request ((member "RemoveEdgeData"))) + (:response ((member "UpdateResult" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::UpdateResult" "UpdateResult") + :capnp-load (lcp:capnp-load-enum "capnp::UpdateResult" "UpdateResult"))))) + +(lcp:define-struct remove-in-edge-data () + ((tx-id "tx::TransactionId") + (vertex "gid::Gid") + (edge-address "storage::EdgeAddress")) + (:serialize :capnp)) + +(lcp:define-rpc remove-in-edge + (:request ((member "RemoveInEdgeData"))) + (:response ((member "UpdateResult" + :capnp-init nil + :capnp-save (lcp:capnp-save-enum "capnp::UpdateResult" "UpdateResult") + :capnp-load (lcp:capnp-load-enum "capnp::UpdateResult" "UpdateResult"))))) + +(lcp:pop-namespace) ;; distributed diff --git a/src/distributed/updates_rpc_server.cpp b/src/distributed/updates_rpc_server.cpp index d3b0d9044..106d2d8f5 100644 --- a/src/distributed/updates_rpc_server.cpp +++ b/src/distributed/updates_rpc_server.cpp @@ -175,7 +175,9 @@ UpdateResult UpdatesRpcServer::TransactionUpdates<TRecordAccessor>::Apply() { UpdatesRpcServer::UpdatesRpcServer(database::GraphDb &db, communication::rpc::Server &server) : db_(db) { - server.Register<UpdateRpc>([this](const UpdateReq &req) { + server.Register<UpdateRpc>([this](const auto &req_reader, auto *res_builder) { + UpdateReq req; + req.Load(req_reader); using DeltaType = database::StateDelta::Type; auto &delta = req.member; switch (delta.type) { @@ -183,74 +185,106 @@ UpdatesRpcServer::UpdatesRpcServer(database::GraphDb &db, case DeltaType::ADD_LABEL: case DeltaType::REMOVE_LABEL: case database::StateDelta::Type::REMOVE_OUT_EDGE: - case database::StateDelta::Type::REMOVE_IN_EDGE: - return std::make_unique<UpdateRes>( + case database::StateDelta::Type::REMOVE_IN_EDGE: { + UpdateRes res( GetUpdates(vertex_updates_, delta.transaction_id).Emplace(delta)); - case DeltaType::SET_PROPERTY_EDGE: - return std::make_unique<UpdateRes>( + res.Save(res_builder); + return; + } + case DeltaType::SET_PROPERTY_EDGE: { + UpdateRes res( GetUpdates(edge_updates_, delta.transaction_id).Emplace(delta)); + res.Save(res_builder); + return; + } default: LOG(FATAL) << "Can't perform a remote update with delta type: " << static_cast<int>(req.member.type); } }); - server.Register<UpdateApplyRpc>([this](const UpdateApplyReq &req) { - return std::make_unique<UpdateApplyRes>(Apply(req.member)); - }); + server.Register<UpdateApplyRpc>( + [this](const auto &req_reader, auto *res_builder) { + UpdateApplyReq req; + req.Load(req_reader); + UpdateApplyRes res(Apply(req.member)); + res.Save(res_builder); + }); - server.Register<CreateVertexRpc>([this](const CreateVertexReq &req) { + server.Register<CreateVertexRpc>([this](const auto &req_reader, + auto *res_builder) { + CreateVertexReq req; + req.Load(req_reader); gid::Gid gid = GetUpdates(vertex_updates_, req.member.tx_id) .CreateVertex(req.member.labels, req.member.properties); - return std::make_unique<CreateVertexRes>( - CreateResult{UpdateResult::DONE, gid}); + CreateVertexRes res(CreateResult{UpdateResult::DONE, gid}); + res.Save(res_builder); }); - server.Register<CreateEdgeRpc>([this](const CreateEdgeReq &req) { + server.Register<CreateEdgeRpc>( + [this](const auto &req_reader, auto *res_builder) { + CreateEdgeReq req; + req.Load(req_reader); + auto data = req.member; + auto creation_result = CreateEdge(data); + + // If `from` and `to` are both on this worker, we handle it in this + // RPC call. Do it only if CreateEdge succeeded. + if (creation_result.result == UpdateResult::DONE && + data.to.worker_id() == db_.WorkerId()) { + auto to_delta = database::StateDelta::AddInEdge( + data.tx_id, data.to.gid(), {data.from, db_.WorkerId()}, + {creation_result.gid, db_.WorkerId()}, data.edge_type); + creation_result.result = + GetUpdates(vertex_updates_, data.tx_id).Emplace(to_delta); + } + + CreateEdgeRes res(creation_result); + res.Save(res_builder); + }); + + server.Register<AddInEdgeRpc>( + [this](const auto &req_reader, auto *res_builder) { + AddInEdgeReq req; + req.Load(req_reader); + auto to_delta = database::StateDelta::AddInEdge( + req.member.tx_id, req.member.to, req.member.from, + req.member.edge_address, req.member.edge_type); + auto result = + GetUpdates(vertex_updates_, req.member.tx_id).Emplace(to_delta); + AddInEdgeRes res(result); + res.Save(res_builder); + }); + + server.Register<RemoveVertexRpc>( + [this](const auto &req_reader, auto *res_builder) { + RemoveVertexReq req; + req.Load(req_reader); + auto to_delta = database::StateDelta::RemoveVertex( + req.member.tx_id, req.member.gid, req.member.check_empty); + auto result = + GetUpdates(vertex_updates_, req.member.tx_id).Emplace(to_delta); + RemoveVertexRes res(result); + res.Save(res_builder); + }); + + server.Register<RemoveEdgeRpc>( + [this](const auto &req_reader, auto *res_builder) { + RemoveEdgeReq req; + req.Load(req_reader); + RemoveEdgeRes res(RemoveEdge(req.member)); + res.Save(res_builder); + }); + + server.Register<RemoveInEdgeRpc>([this](const auto &req_reader, + auto *res_builder) { + RemoveInEdgeReq req; + req.Load(req_reader); auto data = req.member; - auto creation_result = CreateEdge(data); - - // If `from` and `to` are both on this worker, we handle it in this - // RPC call. Do it only if CreateEdge succeeded. - if (creation_result.result == UpdateResult::DONE && - data.to.worker_id() == db_.WorkerId()) { - auto to_delta = database::StateDelta::AddInEdge( - data.tx_id, data.to.gid(), {data.from, db_.WorkerId()}, - {creation_result.gid, db_.WorkerId()}, data.edge_type); - creation_result.result = - GetUpdates(vertex_updates_, data.tx_id).Emplace(to_delta); - } - - return std::make_unique<CreateEdgeRes>(creation_result); - }); - - server.Register<AddInEdgeRpc>([this](const AddInEdgeReq &req) { - auto to_delta = database::StateDelta::AddInEdge( - req.member.tx_id, req.member.to, req.member.from, - req.member.edge_address, req.member.edge_type); - auto result = - GetUpdates(vertex_updates_, req.member.tx_id).Emplace(to_delta); - return std::make_unique<AddInEdgeRes>(result); - }); - - server.Register<RemoveVertexRpc>([this](const RemoveVertexReq &req) { - auto to_delta = database::StateDelta::RemoveVertex( - req.member.tx_id, req.member.gid, req.member.check_empty); - auto result = - GetUpdates(vertex_updates_, req.member.tx_id).Emplace(to_delta); - return std::make_unique<RemoveVertexRes>(result); - }); - - server.Register<RemoveEdgeRpc>([this](const RemoveEdgeReq &req) { - return std::make_unique<RemoveEdgeRes>(RemoveEdge(req.member)); - }); - - server.Register<RemoveInEdgeRpc>([this](const RemoveInEdgeReq &req) { - auto data = req.member; - return std::make_unique<RemoveInEdgeRes>( - GetUpdates(vertex_updates_, data.tx_id) - .Emplace(database::StateDelta::RemoveInEdge(data.tx_id, data.vertex, - data.edge_address))); + RemoveInEdgeRes res(GetUpdates(vertex_updates_, data.tx_id) + .Emplace(database::StateDelta::RemoveInEdge( + data.tx_id, data.vertex, data.edge_address))); + res.Save(res_builder); }); } diff --git a/src/durability/recovery.capnp b/src/durability/recovery.capnp new file mode 100644 index 000000000..243b295c6 --- /dev/null +++ b/src/durability/recovery.capnp @@ -0,0 +1,9 @@ +@0xb3d70bc0576218f3; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("durability::capnp"); + +struct RecoveryInfo { + snapshotTxId @0 :UInt64; + maxWalTxId @1 :UInt64; +} diff --git a/src/durability/recovery.cpp b/src/durability/recovery.cpp index 3e0b5e930..b09607dea 100644 --- a/src/durability/recovery.cpp +++ b/src/durability/recovery.cpp @@ -1,5 +1,6 @@ #include "durability/recovery.hpp" +#include <experimental/filesystem> #include <limits> #include <unordered_map> @@ -16,6 +17,8 @@ #include "transactions/type.hpp" #include "utils/algorithm.hpp" +namespace fs = std::experimental::filesystem; + namespace durability { bool ReadSnapshotSummary(HashedFileReader &buffer, int64_t &vertex_count, diff --git a/src/durability/recovery.hpp b/src/durability/recovery.hpp index ccb8b5f28..87cfe6c11 100644 --- a/src/durability/recovery.hpp +++ b/src/durability/recovery.hpp @@ -1,16 +1,14 @@ #pragma once -#include <experimental/filesystem> #include <experimental/optional> #include <unordered_map> #include "database/graph_db.hpp" #include "durability/hashed_file_reader.hpp" +#include "durability/recovery.capnp.h" #include "storage/vertex_accessor.hpp" #include "transactions/type.hpp" -namespace fs = std::experimental::filesystem; - namespace durability { /// Stores info on what was (or needs to be) recovered from durability. @@ -28,6 +26,16 @@ struct RecoveryInfo { } bool operator!=(const RecoveryInfo &other) const { return !(*this == other); } + void Save(capnp::RecoveryInfo::Builder *builder) const { + builder->setSnapshotTxId(snapshot_tx_id); + builder->setMaxWalTxId(max_wal_tx_id); + } + + void Load(const capnp::RecoveryInfo::Reader &reader) { + snapshot_tx_id = reader.getSnapshotTxId(); + max_wal_tx_id = reader.getMaxWalTxId(); + } + private: friend class boost::serialization::access; diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 6c96a87ad..10cb3263a 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -4,7 +4,29 @@ set(io_src_files network/socket.cpp network/utils.cpp) +# Use this function to add each capnp file to generation. This way each file is +# standalone and we avoid recompiling everything. +# NOTE: io_src_files and io_capnp_files are globally updated. +# TODO: This is duplicated from src/CMakeLists.txt, find a good way to +# generalize this on per subdirectory basis. +function(add_capnp capnp_src_file) + set(cpp_file ${CMAKE_CURRENT_SOURCE_DIR}/${capnp_src_file}.c++) + set(h_file ${CMAKE_CURRENT_SOURCE_DIR}/${capnp_src_file}.h) + add_custom_command(OUTPUT ${cpp_file} ${h_file} + COMMAND ${CAPNP_EXE} compile -o${CAPNP_CXX_EXE} ${capnp_src_file} -I ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${capnp_src_file} capnproto-proj + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + # Update *global* io_capnp_files + set(io_capnp_files ${io_capnp_files} ${cpp_file} ${h_file} PARENT_SCOPE) + # Update *global* io_src_files + set(io_src_files ${io_src_files} ${cpp_file} PARENT_SCOPE) +endfunction(add_capnp) + +add_capnp(network/endpoint.capnp) + +add_custom_target(generate_io_capnp DEPENDS ${io_capnp_files}) + add_library(mg-io STATIC ${io_src_files}) target_link_libraries(mg-io stdc++fs Threads::Threads fmt glog mg-utils) -# TODO: Remove this dependency when we switch to capnp -target_link_libraries(mg-io ${Boost_SERIALIZATION_LIBRARY_RELEASE}) +target_link_libraries(mg-io capnp kj) +add_dependencies(mg-io generate_io_capnp) diff --git a/src/io/network/endpoint.capnp b/src/io/network/endpoint.capnp new file mode 100644 index 000000000..bc58b2869 --- /dev/null +++ b/src/io/network/endpoint.capnp @@ -0,0 +1,10 @@ +@0x93c2449a1e02365a; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("io::network::capnp"); + +struct Endpoint { + address @0 :Text; + port @1 :UInt16; + family @2 :UInt8; +} diff --git a/src/io/network/endpoint.cpp b/src/io/network/endpoint.cpp index 9761d1b60..c1f94bb22 100644 --- a/src/io/network/endpoint.cpp +++ b/src/io/network/endpoint.cpp @@ -24,6 +24,18 @@ Endpoint::Endpoint(const std::string &address, uint16_t port) CHECK(family_ != 0) << "Not a valid IPv4 or IPv6 address: " << address; } +void Endpoint::Save(capnp::Endpoint::Builder *builder) const { + builder->setAddress(address_); + builder->setPort(port_); + builder->setFamily(family_); +} + +void Endpoint::Load(const capnp::Endpoint::Reader &reader) { + address_ = reader.getAddress(); + port_ = reader.getPort(); + family_ = reader.getFamily(); +} + bool Endpoint::operator==(const Endpoint &other) const { return address_ == other.address_ && port_ == other.port_ && family_ == other.family_; diff --git a/src/io/network/endpoint.hpp b/src/io/network/endpoint.hpp index 5c7e8a477..bc17ccfd3 100644 --- a/src/io/network/endpoint.hpp +++ b/src/io/network/endpoint.hpp @@ -5,8 +5,7 @@ #include <iostream> #include <string> -#include "boost/serialization/access.hpp" - +#include "io/network/endpoint.capnp.h" #include "utils/exceptions.hpp" namespace io::network { @@ -28,16 +27,10 @@ class Endpoint { bool operator==(const Endpoint &other) const; friend std::ostream &operator<<(std::ostream &os, const Endpoint &endpoint); + void Save(capnp::Endpoint::Builder *builder) const; + void Load(const capnp::Endpoint::Reader &reader); + private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &address_; - ar &port_; - ar &family_; - } - std::string address_; uint16_t port_{0}; unsigned char family_{0}; diff --git a/src/lisp/lcp.lisp b/src/lisp/lcp.lisp index 54f097aca..84736c9fe 100644 --- a/src/lisp/lcp.lisp +++ b/src/lisp/lcp.lisp @@ -3,11 +3,20 @@ (:export #:define-class #:define-struct #:define-enum + #:define-rpc + #:cpp-list + #:in-impl #:namespace #:pop-namespace #:capnp-namespace #:capnp-import #:capnp-type-conversion + #:capnp-save-optional + #:capnp-load-optional + #:capnp-save-vector + #:capnp-load-vector + #:capnp-save-enum + #:capnp-load-enum #:process-file)) (in-package #:lcp) @@ -75,21 +84,57 @@ (eval-when (:compile-toplevel :load-toplevel :execute) (set-dispatch-macro-character #\# #\> #'|#>-reader|)) -(deftype cpp-primitive-type () - `(member :bool :int :int32_t :int64_t :uint :uint32_t :uint64_t :float :double)) +(defclass cpp-type () + ((documentation :type (or null string) :initarg :documentation :initform nil + :reader cpp-type-documentation + :documentation "Documentation string for this C++ type.") + (namespace :type list :initarg :ns :initarg :namespace :initform nil + :reader cpp-type-namespace + :documentation "A list of symbols or strings defining the full + namespace. A single symbol may refer to a `CPP-CLASS' which + encloses this type.") + (enclosing-class :type (or null symbol) :initarg :enclosing-class + :initform nil :accessor cpp-type-enclosing-class) + (name :type (or symbol string) :initarg :name :reader cpp-type-base-name + :documentation "Base name of this type.") + (type-params :type list :initarg :type-params :initform nil + :reader cpp-type-type-params + :documentation "List of template parameters that are needed to + instantiate a concrete type. For example, in `template + <TValue> class vector`, 'TValue' is type parameter.") + (type-args :type list :initarg :type-args :initform nil + :reader cpp-type-type-args + :documentation "List of already applied template arguments. For + example in `std::vector<int>`, 'int' is a type argument.")) + (:documentation "Base class for meta information on C++ types.")) -(defun cpp-primitive-type-p (type) - (member type '(:bool :int :int32_t :int64_t :uint :uint32_t :uint64_t :float :double))) +(defgeneric cpp-type-name (cpp-type) + (:documentation "Get C++ style type name from `CPP-TYPE' as a string.")) -;; TODO: Rename this to cpp-type and set it as the base class/struct for all -;; the other cpp type metainformation (`CPP-CLASS', `CPP-ENUM' ...) -(defstruct cpp-type-decl - namespace - name - type-args) +(defmethod cpp-type-name ((cpp-type string)) + "Return CPP-TYPE string as is." + cpp-type) + +(defmethod cpp-type-name ((cpp-type cpp-type)) + "Return `CPP-TYPE' name as PascalCase or if string, as is." + (cpp-type-name (cpp-type-base-name cpp-type))) + +(deftype cpp-primitive-type-keywords () + "List of keywords that specify a primitive type in C++." + `(member :bool :int :int16_t :int32_t :int64_t :uint :uint32_t :uint64_t :float :double)) + +(defmethod cpp-type-name ((cpp-type symbol)) + "Return PascalCase of CPP-TYPE symbol or lowercase if it is a primitive type." + (if (typep cpp-type 'cpp-primitive-type-keywords) + (string-downcase (string cpp-type)) + (remove #\- (string-capitalize (string cpp-type))))) + +(defclass cpp-primitive-type (cpp-type) + ((name :type cpp-primitive-type-keywords)) + (:documentation "Represents a primitive type in C++.")) (defun parse-cpp-type-declaration (type-decl) - "Parse C++ type from TYPE-DECL string and return CPP-TYPE-DECL. + "Parse C++ type from TYPE-DECL string and return CPP-TYPE. For example: @@ -97,14 +142,14 @@ For example: produces: -;; (cpp-type-decl +;; (cpp-type ;; :name pair -;; :type-args ((cpp-type-decl +;; :type-args ((cpp-type ;; :name MyClass -;; :type-args ((cpp-type-decl :name function -;; :type-args (cpp-type-decl :name void(int, bool))) -;; (cpp-type-decl :name double))) -;; (cpp-type-decl :name char)))" +;; :type-args ((cpp-type :name function +;; :type-args (cpp-type :name void(int, bool))) +;; (cpp-type :name double))) +;; (cpp-type :name char)))" (declare (type string type-decl)) ;; C++ type can be declared as follows: ;; namespace::namespace::type<type-arg, type-arg> * @@ -140,31 +185,33 @@ produces: (subseq template arg-start (1- match-end))) type-args) (setf arg-start (1+ match-end))))))) - (make-cpp-type-decl :namespace (when (cdr namespace-split) - (butlast namespace-split)) - :name name - :type-args (reverse type-args))))) + (make-instance 'cpp-type + :ns (when (cdr namespace-split) + (butlast namespace-split)) + :name name + :type-args (reverse type-args))))) -(defun string<-cpp-type-decl (type-decl) - (declare (type cpp-type-decl type-decl)) +(defun cpp-type-decl (cpp-type) + (declare (type cpp-type cpp-type)) + ;; TODO: Merge this and cpp-class-full-name (with-output-to-string (s) - (format s "~{~A::~}" (cpp-type-decl-namespace type-decl)) - (write-string (cpp-type-decl-name type-decl) s) - (when (cpp-type-decl-type-args type-decl) - (format s "<~{~A~^, ~}>" - (mapcar #'string<-cpp-type-decl (cpp-type-decl-type-args type-decl)))))) + (format s "~{~A::~}" (cpp-type-namespace cpp-type)) + (write-string (cpp-type-name cpp-type) s) + (when (cpp-type-type-args cpp-type) + (format s "<~{~A~^, ~}>" (mapcar #'cpp-type-decl (cpp-type-type-args cpp-type)))))) -(defstruct cpp-enum - "Meta information on a C++ enum." - (symbol nil :type symbol :read-only t) - (documentation nil :type (or null string) :read-only t) - (values nil :read-only t) - (enclosing-class nil :type (or null symbol) :read-only t)) +(defclass cpp-enum (cpp-type) + ((values :type list :initarg :values :initform nil :reader cpp-enum-values) + ;; If true, generate the schema for this enum. + (capnp-schema :type boolean :initarg :capnp-schema :initform nil + :reader cpp-enum-capnp-schema)) + (:documentation "Meta information on a C++ enum.")) (defstruct cpp-member "Meta information on a C++ class (or struct) member variable." (symbol nil :type symbol :read-only t) - (type nil :type (or cpp-primitive-type string) :read-only t) + (type nil :type (or cpp-primitive-type-keywords string) :read-only t) + (initarg nil :type symbol :read-only t) (initval nil :type (or null string integer float) :read-only t) (scope :private :type (member :public :protected :private) :read-only t) ;; TODO: Support giving a name for reader function. @@ -174,9 +221,11 @@ produces: ;; args: (archive member-name) and needs to return C++ code. (save-fun nil :type (or null string raw-cpp function) :read-only t) (load-fun nil :type (or null string raw-cpp function) :read-only t) - (capnp-type nil :type (or null string) :read-only t) + ;; CAPNP-TYPE may be a string specifying the type, or a list of + ;; (member-symbol "capnp-type") specifying a union type. + (capnp-type nil :type (or null string list) :read-only t) (capnp-init t :type boolean :read-only t) - (capnp-save nil :type (or null function) :read-only t) + (capnp-save nil :type (or null function (eql :dont-save)) :read-only t) (capnp-load nil :type (or null function) :read-only t)) (defstruct capnp-opts @@ -193,47 +242,44 @@ produces: ;; as a composition. (inherit-compose nil :read-only t)) -(defstruct cpp-class - "Meta information on a C++ class (or struct)." - (structp nil :type boolean :read-only t) - (name nil :type symbol :read-only t) - (super-classes nil :read-only t) - (type-params nil :read-only t) - (documentation "" :type (or null string) :read-only t) - (members nil :read-only t) - ;; Custom C++ code in 3 scopes. May be a list of C++ meta information or a - ;; single element. - (public nil :read-only t) - (protected nil :read-only t) - (private nil) - (capnp-opts nil :type (or null capnp-opts) :read-only t) - (namespace nil :read-only t) - (inner-types nil) - (enclosing-class nil :type (or null symbol))) - -(defun cpp-type-name (name) - "Get C++ style type name from NAME as a string." - (typecase name - (cpp-primitive-type (string-downcase (string name))) - (symbol (remove #\- (string-capitalize (string name)))) - (string name) - (otherwise (error "Unkown conversion to C++ type for ~S" (type-of name))))) +(defclass cpp-class (cpp-type) + ((structp :type boolean :initarg :structp :initform nil + :reader cpp-class-structp) + (super-classes :initarg :super-classes :initform nil + :reader cpp-class-super-classes) + (members :initarg :members :initform nil :reader cpp-class-members) + ;; Custom C++ code in 3 scopes. May be a list of C++ meta information or a + ;; single element. + (public :initarg :public :initform nil :reader cpp-class-public) + (protected :initarg :protected :initform nil :reader cpp-class-protected) + (private :initarg :private :initform nil :accessor cpp-class-private) + (capnp-opts :type (or null capnp-opts) :initarg :capnp-opts :initform nil + :reader cpp-class-capnp-opts) + (inner-types :initarg :inner-types :initform nil :reader cpp-class-inner-types)) + (:documentation "Meta information on a C++ class (or struct).")) (defvar *cpp-classes* nil "List of defined classes from LCP file") +(defvar *cpp-enums* nil "List of defined enums from LCP file") (defun find-cpp-class (cpp-class-name) "Find CPP-CLASS in *CPP-CLASSES* by CPP-CLASS-NAME" (declare (type (or symbol string) cpp-class-name)) + ;; TODO: Find by full name (if (stringp cpp-class-name) - (find cpp-class-name *cpp-classes* - :key (lambda (class) (cpp-type-name (cpp-class-name class))) - :test #'string=) - (find cpp-class-name *cpp-classes* :key #'cpp-class-name))) + (find cpp-class-name *cpp-classes* :key #'cpp-type-name :test #'string=) + (find cpp-class-name *cpp-classes* :key #'cpp-type-base-name))) + +(defun find-cpp-enum (cpp-enum-name) + "Find CPP-ENUM in *CPP-ENUMS* by CPP-ENUM-NAME" + (declare (type (or symbol string) cpp-enum-name)) + (if (stringp cpp-enum-name) + (find cpp-enum-name *cpp-enums* :key #'cpp-type-name :test #'string=) + (find cpp-enum-name *cpp-enums* :key #'cpp-type-base-name))) (defun direct-subclasses-of (cpp-class) "Find direct subclasses of CPP-CLASS from *CPP-CLASSES*" (declare (type (or symbol cpp-class) cpp-class)) - (let ((name (if (symbolp cpp-class) cpp-class (cpp-class-name cpp-class)))) + (let ((name (if (symbolp cpp-class) cpp-class (cpp-type-base-name cpp-class)))) (reverse ;; reverse to get them in definition order (remove-if (lambda (subclass) (not (member name (cpp-class-super-classes subclass)))) @@ -244,9 +290,7 @@ produces: (declare (type string documentation)) (format nil "/// ~A" (cl-ppcre:regex-replace-all - (string #\Newline) - documentation - (format nil "~%/// ")))) + (string #\Newline) documentation (format nil "~%/// ")))) (defun cpp-variable-name (symbol) "Get C++ style name of SYMBOL as a string." @@ -270,9 +314,9 @@ produces: "Get C++ style `CPP-ENUM' definition as a string." (declare (type cpp-enum cpp-enum)) (with-output-to-string (s) - (when (cpp-enum-documentation cpp-enum) - (write-line (cpp-documentation (cpp-enum-documentation cpp-enum)) s)) - (format s "enum class ~A {~%" (cpp-type-name (cpp-enum-symbol cpp-enum))) + (when (cpp-type-documentation cpp-enum) + (write-line (cpp-documentation (cpp-type-documentation cpp-enum)) s)) + (format s "enum class ~A {~%" (cpp-type-name cpp-enum)) (format s "~{ ~A~^,~%~}~%" (mapcar #'cpp-constant-name (cpp-enum-values cpp-enum))) (write-line "};" s))) @@ -292,7 +336,7 @@ produces: (defun cpp-member-reader-definition (cpp-member) "Get C++ style `CPP-MEMBER' getter (reader) function." (declare (type cpp-member cpp-member)) - (if (cpp-primitive-type-p (cpp-member-type cpp-member)) + (if (typep (cpp-member-type cpp-member) 'cpp-primitive-type-keywords) (format nil "auto ~A() const { return ~A; }" (cpp-member-name cpp-member :struct t) (cpp-member-name cpp-member)) (format nil "const auto &~A() const { return ~A; }" (cpp-member-name cpp-member :struct t) (cpp-member-name cpp-member)))) @@ -312,20 +356,20 @@ NIL, returns a string." (cpp-member-declaration member :struct (cpp-class-structp cpp-class)))) (with-output-to-string (s) (terpri s) - (when (cpp-class-documentation cpp-class) - (write-line (cpp-documentation (cpp-class-documentation cpp-class)) s)) - (when (cpp-class-type-params cpp-class) - (cpp-template (cpp-class-type-params cpp-class) s)) + (when (cpp-type-documentation cpp-class) + (write-line (cpp-documentation (cpp-type-documentation cpp-class)) s)) + (when (cpp-type-type-params cpp-class) + (cpp-template (cpp-type-type-params cpp-class) s)) (if (cpp-class-structp cpp-class) (write-string "struct " s) (write-string "class " s)) - (format s "~A" (cpp-type-name (cpp-class-name cpp-class))) + (format s "~A" (cpp-type-name cpp-class)) (when (cpp-class-super-classes cpp-class) (format s " : ~{public ~A~^, ~}" (mapcar #'cpp-type-name (cpp-class-super-classes cpp-class)))) (write-line " {" s) - (let ((reader-members (remove-if (lambda (m) (not (cpp-member-reader m))) - (cpp-class-members cpp-class)))) + (let ((reader-members (remove-if (complement #'cpp-member-reader) + (cpp-class-members cpp-class)))) (when (or (cpp-class-public cpp-class) (cpp-class-members-scoped :public) reader-members) (unless (cpp-class-structp cpp-class) (write-line " public:" s)) @@ -337,9 +381,9 @@ NIL, returns a string." (let ((save (capnp-save-declaration cpp-class)) (construct (capnp-construct-declaration cpp-class)) (load (capnp-load-declaration cpp-class))) - (when save (format s " ~A;~%" save)) - (when construct (format s " ~A;~%" construct)) - (when load (format s " ~A;~%" load)))) + (when save (format s " ~A;~2%" save)) + (when construct (format s " ~A;~2%" construct)) + (when load (format s " ~A;~2%" load)))) (when (or (cpp-class-protected cpp-class) (cpp-class-members-scoped :protected)) (write-line " protected:" s) (format s "~{~A~%~}" (mapcar #'cpp-code (cpp-class-protected cpp-class))) @@ -360,14 +404,14 @@ NIL, returns a string." (let (enclosing) (loop for class = cpp-class - then (find-cpp-class (cpp-class-enclosing-class class)) + then (find-cpp-class (cpp-type-enclosing-class class)) while class - do (push (cpp-type-name (cpp-class-name class)) enclosing)) + do (push (cpp-type-name class) enclosing)) enclosing))) (let* ((full-name (format nil "~{~A~^::~}" (enclosing-classes class))) - (type-args (if (or (not type-args) (not (cpp-class-type-params class))) + (type-args (if (or (not type-args) (not (cpp-type-type-params class))) "" - (format nil "<~{~A~^, ~}>" (mapcar #'cpp-type-name (cpp-class-type-params class)))))) + (format nil "<~{~A~^, ~}>" (mapcar #'cpp-type-name (cpp-type-type-params class)))))) (concatenate 'string full-name type-args)))) (defun cpp-method-declaration (class method-name @@ -380,18 +424,17 @@ declaration to be used outside of class definition. Remaining keys are flags which generate the corresponding C++ keywords." (declare (type cpp-class class) (type string method-name)) - (let* ((type-params (cpp-class-type-params class)) + (let* ((type-params (cpp-type-type-params class)) (template (if (or inline (not type-params)) "" (cpp-template type-params))) (static/virtual (cond ((and inline static) "static") ((and inline virtual) "virtual") (t ""))) (namespace (if inline "" (format nil "~A::" (cpp-class-full-name class)))) - (args (format nil "~{~A~^, ~}" + (args (format nil "~:{~A ~A~:^, ~}" (mapcar (lambda (name-and-type) - (format nil "~A ~A" - (cpp-type-name (second name-and-type)) - (cpp-variable-name (first name-and-type)))) + (list (cpp-type-name (second name-and-type)) + (cpp-variable-name (first name-and-type)))) args))) (const (if const "const" "")) (override (if (and override inline) "override" ""))) @@ -401,6 +444,15 @@ which generate the corresponding C++ keywords." ${returns} ${namespace}${method-name}(${args}) ${const} ${override} cpp<#))) +(defstruct cpp-list + values) + +(defun cpp-list (&rest args) + (make-cpp-list + :values (remove-if (lambda (a) + (not (typep a '(or raw-cpp cpp-type cpp-list)))) + args))) + (defun cpp-code (cpp) "Get a C++ string from given CPP meta information." (typecase cpp @@ -408,6 +460,7 @@ which generate the corresponding C++ keywords." (cpp-class (cpp-class-definition cpp)) (cpp-enum (cpp-enum-definition cpp)) (string cpp) + (cpp-list (format nil "~{~A~^~%~}" (mapcar #'cpp-code (cpp-list-values cpp)))) (null "") (otherwise (error "Unknown conversion to C++ for ~S" (type-of cpp))))) @@ -530,7 +583,7 @@ which generate the corresponding C++ keywords." "Get direct subclasses of CPP-CLASS which should be modeled as a union in Cap'n Proto schema." (declare (type (or symbol cpp-class) cpp-class)) - (let ((class-name (if (symbolp cpp-class) cpp-class (cpp-class-name cpp-class)))) + (let ((class-name (if (symbolp cpp-class) cpp-class (cpp-type-base-name cpp-class)))) (remove-if (lambda (subclass) (member class-name (capnp-opts-inherit-compose (cpp-class-capnp-opts subclass)))) @@ -544,10 +597,11 @@ CPP-CLASS." (let* ((class (if (symbolp cpp-class) (find-cpp-class cpp-class) cpp-class)) (capnp-opts (cpp-class-capnp-opts class)) union compose) - (dolist (parent (cpp-class-super-classes class)) - (if (member parent (capnp-opts-inherit-compose capnp-opts)) - (push parent compose) - (push parent union))) + (when (not (capnp-opts-base capnp-opts)) + (dolist (parent (cpp-class-super-classes class)) + (if (member parent (capnp-opts-inherit-compose capnp-opts)) + (push parent compose) + (push parent union)))) (values union compose))) (defun capnp-union-parents-rec (cpp-class) @@ -560,7 +614,7 @@ encoded as union inheritance in Cap'n Proto." (and opts (capnp-opts-base opts)))) (rec (class) (declare (type cpp-class class)) - (cons (cpp-class-name class) + (cons (cpp-type-base-name class) ;; Continue to supers only if this isn't marked as capnp base class. (when (and (not (capnp-base-p class)) (capnp-union-and-compose-parents class)) @@ -580,45 +634,72 @@ encoded as union inheritance in Cap'n Proto." (push (cons cpp-type capnp-type) *capnp-type-converters*)) (defun capnp-type<-cpp-type (cpp-type) - (typecase cpp-type - (cpp-primitive-type - (when (member cpp-type '(:int :uint)) - (error "Unable to get Capnp type for integer without specified width.")) - ;; Delete the _t suffix - (cl-ppcre:regex-replace "_t$" (string-downcase cpp-type :start 1) "")) - (string - (let ((type-decl (parse-cpp-type-declaration cpp-type))) - (cond - ((string= "shared_ptr" (cpp-type-decl-name type-decl)) - (let ((class (find-cpp-class - ;; TODO: Use full type - (cpp-type-decl-name (first (cpp-type-decl-type-args type-decl)))))) - (unless class - (error "Unable to determine base type for '~A'; use :capnp-type" - cpp-type)) - (let* ((parents (capnp-union-parents-rec class)) - (top-parent (if parents (car (last parents)) (cpp-class-name class)))) - (format nil "Utils.SharedPtr(~A)" (cpp-type-name top-parent))))) - ((string= "vector" (cpp-type-decl-name type-decl)) - (format nil "List(~A)" - (capnp-type<-cpp-type - (string<-cpp-type-decl (first (cpp-type-decl-type-args type-decl)))))) - ((string= "optional" (cpp-type-decl-name type-decl)) - (format nil "Utils.Optional(~A)" - (capnp-type<-cpp-type - (string<-cpp-type-decl (first (cpp-type-decl-type-args type-decl)))))) - ((assoc cpp-type *capnp-type-converters* :test #'string=) - (cdr (assoc cpp-type *capnp-type-converters* :test #'string=))) - (t (cpp-type-name cpp-type))))) - ;; Capnp only accepts uppercase first letter in types (PascalCase), so - ;; this is the same as our conversion to C++ type name. - (otherwise (cpp-type-name cpp-type)))) + (flet ((convert-primitive-type (name) + (when (member name '(:int :uint)) + (error "Unable to get Capnp type for integer without specified width.")) + (case name + (:bool "Bool") + (:float "Float32") + (:double "Float64") + (otherwise + (let ((pos-of-i (position #\I (string name)))) + ;; Delete the _t suffix + (cl-ppcre:regex-replace + "_t$" (string-downcase name :start (1+ pos-of-i)) "")))))) + (typecase cpp-type + (cpp-primitive-type-keywords (convert-primitive-type cpp-type)) + (cpp-primitive-type (convert-primitive-type (cpp-type-base-name cpp-type))) + (string + (let ((type (parse-cpp-type-declaration cpp-type))) + (cond + ((string= "string" (cpp-type-base-name type)) + "Text") + ((string= "shared_ptr" (cpp-type-base-name type)) + (let ((class (find-cpp-class + ;; TODO: Use full type + (cpp-type-base-name (first (cpp-type-type-args type)))))) + (unless class + (error "Unable to determine base type for '~A'; use :capnp-type" + cpp-type)) + (let* ((parents (capnp-union-parents-rec class)) + (top-parent (if parents (car (last parents)) (cpp-type-base-name class)))) + (format nil "Utils.SharedPtr(~A)" (cpp-type-name top-parent))))) + ((string= "vector" (cpp-type-base-name type)) + (format nil "List(~A)" + (capnp-type<-cpp-type + (cpp-type-decl (first (cpp-type-type-args type)))))) + ((string= "optional" (cpp-type-base-name type)) + (format nil "Utils.Optional(~A)" + (capnp-type<-cpp-type (cpp-type-decl (first (cpp-type-type-args type)))))) + ((assoc cpp-type *capnp-type-converters* :test #'string=) + (cdr (assoc cpp-type *capnp-type-converters* :test #'string=))) + (t (cpp-type-name cpp-type))))) + ;; Capnp only accepts uppercase first letter in types (PascalCase), so + ;; this is the same as our conversion to C++ type name. + (otherwise (cpp-type-name cpp-type))))) + +(defun capnp-type-of-member (member) + (declare (type cpp-member member)) + (if (cpp-member-capnp-type member) + (cpp-member-capnp-type member) + (capnp-type<-cpp-type (cpp-member-type member)))) + +(defun capnp-primitive-type-p (capnp-type) + (declare (type (or list string) capnp-type)) + (and (stringp capnp-type) + (member capnp-type + '("Bool" + "Int8" "Int16" "Int32" "Int64" + "UInt8" "UInt16" "UInt32" "UInt64" + "Float32" "Float64" + "Text" "Void") + :test #'string=))) (defun capnp-schema-for-enum (cpp-enum) "Generate Cap'n Proto serialization schema for CPP-ENUM" (declare (type cpp-enum cpp-enum)) (with-output-to-string (s) - (format s "enum ~A {~%" (cpp-type-name (cpp-enum-symbol cpp-enum))) + (format s "enum ~A {~%" (cpp-type-name cpp-enum)) (loop for val in (cpp-enum-values cpp-enum) and field-number from 0 do (format s " ~A @~A;~%" (string-downcase (cpp-type-name val) :end 1) @@ -630,14 +711,14 @@ encoded as union inheritance in Cap'n Proto." (declare (type (or cpp-class cpp-enum symbol) cpp-class)) (when (null cpp-class) (return-from capnp-schema)) - (when (cpp-enum-p cpp-class) + (when (typep cpp-class 'cpp-enum) (return-from capnp-schema (capnp-schema-for-enum cpp-class))) - (let ((class-name (if (symbolp cpp-class) cpp-class (cpp-class-name cpp-class))) - (members (when (cpp-class-p cpp-class) (cpp-class-members cpp-class))) - (inner-types (when (cpp-class-p cpp-class) (cpp-class-inner-types cpp-class))) + (let ((class-name (if (symbolp cpp-class) cpp-class (cpp-type-base-name cpp-class))) + (members (when (typep cpp-class 'cpp-class) (cpp-class-members cpp-class))) + (inner-types (when (typep cpp-class 'cpp-class) (cpp-class-inner-types cpp-class))) (union-subclasses (capnp-union-subclasses cpp-class)) - (type-params (when (cpp-class-p cpp-class) (cpp-class-type-params cpp-class))) - (capnp-type-args (when (cpp-class-p cpp-class) + (type-params (when (typep cpp-class 'cpp-class) (cpp-type-type-params cpp-class))) + (capnp-type-args (when (typep cpp-class 'cpp-class) (capnp-opts-type-args (cpp-class-capnp-opts cpp-class)))) (field-number 0)) (when (and type-params (not capnp-type-args)) @@ -667,22 +748,40 @@ encoded as union inheritance in Cap'n Proto." (incf field-number)) (write-line " }" s)) (dolist (member members) - (format s " ~A @~A :~A;~%" - (field-name<-symbol (cpp-member-symbol member)) - field-number - (if (cpp-member-capnp-type member) - (cpp-member-capnp-type member) - (capnp-type<-cpp-type (cpp-member-type member)))) - (incf field-number)) + (unless (eq :dont-save (cpp-member-capnp-save member)) + (let ((capnp-type (capnp-type-of-member member)) + (field-name (field-name<-symbol (cpp-member-symbol member)))) + (if (stringp capnp-type) + (progn + (format s " ~A @~A :~A;~%" + field-name field-number capnp-type) + (incf field-number)) + ;; capnp-type is a list specifying a union type + (progn + (format s " ~A :union {~%" field-name) + (dolist (union-member capnp-type) + (format s " ~A @~A :~A;~%" + (field-name<-symbol (first union-member)) + field-number (second union-member)) + (incf field-number)) + (write-line " }" s)))))) (dolist (inner inner-types) - (write-line (capnp-schema inner) s)) + (when (or (and (typep inner 'cpp-class) (cpp-class-capnp-opts inner)) + (and (typep inner 'cpp-enum) (cpp-enum-capnp-schema inner))) + (write-line (capnp-schema inner) s))) (when union-subclasses (write-line " union {" s) + (when union-parents + ;; Allow instantiating classes in the middle of inheritance + ;; hierarchy. + (format s " ~A @~A :Void;~%" + (field-name<-symbol class-name) field-number) + (incf field-number)) (dolist (subclass union-subclasses) (format s " ~A @~A :~A;~%" - (field-name<-symbol (cpp-class-name subclass)) + (field-name<-symbol (cpp-type-base-name subclass)) field-number - (capnp-type<-cpp-type (cpp-class-name subclass))) + (capnp-type<-cpp-type (cpp-type-base-name subclass))) (incf field-number)) (write-line " }" s)) (write-line "}" s)))))) @@ -744,7 +843,7 @@ encoded as union inheritance in Cap'n Proto." "Get additional arguments to Save/Load function for CPP-CLASS." (declare (type cpp-class cpp-class) (type (member :save :load) save-or-load)) - (loop for parent in (cons (cpp-class-name cpp-class) (capnp-union-parents-rec cpp-class)) + (loop for parent in (cons (cpp-type-base-name cpp-class) (capnp-union-parents-rec cpp-class)) for opts = (cpp-class-capnp-opts (find-cpp-class parent)) for args = (ecase save-or-load (:save (capnp-opts-save-args opts)) @@ -758,8 +857,8 @@ used for outside definition." (declare (type cpp-class cpp-class)) (let* ((parents (capnp-union-parents-rec cpp-class)) (top-parent-class (if parents - (cpp-class-full-name (find-cpp-class (car (last parents)))) - (cpp-class-full-name cpp-class))) + (cpp-class-full-name (find-cpp-class (car (last parents))) :type-args nil) + (cpp-class-full-name cpp-class :type-args nil))) (builder-arg (list (if parents 'base-builder 'builder) (format nil "capnp::~A::Builder *" top-parent-class)))) @@ -772,7 +871,7 @@ used for outside definition." "Generate the default call to save for member." (declare (type string member-name member-type member-builder)) (let* ((type (parse-cpp-type-declaration member-type)) - (type-name (cpp-type-decl-name type))) + (type-name (cpp-type-base-name type))) (when (member type-name '("unique_ptr" "shared_ptr" "vector") :test #'string=) (error "Use a custom :capnp-save function for ~A ~A" type-name member-name)) (let* ((cpp-class (find-cpp-class type-name)) ;; TODO: full type-name search @@ -803,11 +902,15 @@ used for outside definition." (cpp-type-name first-parent) (parent-args first-parent))) (when (or compose-parents (cpp-class-members cpp-class)) (format s " auto ~A_builder = base_builder->~{get~A().~}init~A();~%" - (cpp-variable-name (cpp-class-name cpp-class)) + (cpp-variable-name (cpp-type-base-name cpp-class)) (mapcar #'cpp-type-name (cdr (reverse parents))) - (cpp-type-name (cpp-class-name cpp-class))) + (cpp-type-name cpp-class)) (format s " auto *builder = &~A_builder;~%" - (cpp-variable-name (cpp-class-name cpp-class)))))) + (cpp-variable-name (cpp-type-base-name cpp-class)))) + (when (capnp-union-subclasses cpp-class) + ;; We are in the middle of inheritance hierarchy, so set our + ;; union Void field. + (format s " builder->set~A();" (cpp-type-name cpp-class))))) ;; Now handle composite inheritance calls. (dolist (parent compose-parents) (write-line "{" s) @@ -818,39 +921,40 @@ used for outside definition." (write-line "}" s)))) ;; Set the template instantiations (when (and (capnp-opts-type-args (cpp-class-capnp-opts cpp-class)) - (/= 1 (list-length (cpp-class-type-params cpp-class)))) - (error "Don't know how to save templated class ~A" (cpp-class-name cpp-class))) - (let ((type-param (first (cpp-class-type-params cpp-class)))) + (/= 1 (list-length (cpp-type-type-params cpp-class)))) + (error "Don't know how to save templated class ~A" (cpp-type-base-name cpp-class))) + (let ((type-param (first (cpp-type-type-params cpp-class)))) (dolist (type-arg (capnp-opts-type-args (cpp-class-capnp-opts cpp-class))) (format s " if (std::is_same<~A, ~A>::value) { builder->set~A(); }" (cpp-type-name type-arg) (cpp-type-name type-param) (cpp-type-name type-arg)))) (dolist (member (cpp-class-members cpp-class)) - (let ((member-name (cpp-member-name member :struct (cpp-class-structp cpp-class))) - (member-builder (format nil "~A_builder" (cpp-member-name member :struct t))) - (capnp-name (cpp-type-name (cpp-member-symbol member)))) - (cond - ((cpp-primitive-type-p (cpp-member-type member)) - (format s " builder->set~A(~A);~%" capnp-name member-name)) - (t - (write-line "{" s) ;; Enclose larger save code in new scope - (let ((size (if (string= "vector" (cpp-type-decl-name - (parse-cpp-type-declaration - (cpp-member-type member)))) - (format nil "~A.size()" member-name) - ""))) - (if (cpp-member-capnp-init member) - (format s " auto ~A = builder->init~A(~A);~%" - member-builder capnp-name size) - (setf member-builder "builder"))) - (if (cpp-member-capnp-save member) - (format s " ~A~%" - (cpp-code (funcall (cpp-member-capnp-save member) - member-builder member-name))) - (write-line (capnp-save-default member-name - (cpp-member-type member) - member-builder) - s)) - (write-line "}" s))))) + (unless (eq :dont-save (cpp-member-capnp-save member)) + (let ((member-name (cpp-member-name member :struct (cpp-class-structp cpp-class))) + (member-builder (format nil "~A_builder" (cpp-member-name member :struct t))) + (capnp-name (cpp-type-name (cpp-member-symbol member)))) + (cond + ((capnp-primitive-type-p (capnp-type-of-member member)) + (format s " builder->set~A(~A);~%" capnp-name member-name)) + (t + (write-line "{" s) ;; Enclose larger save code in new scope + (let ((size (if (string= "vector" (cpp-type-base-name + (parse-cpp-type-declaration + (cpp-member-type member)))) + (format nil "~A.size()" member-name) + ""))) + (if (cpp-member-capnp-init member) + (format s " auto ~A = builder->init~A(~A);~%" + member-builder capnp-name size) + (setf member-builder "builder"))) + (if (cpp-member-capnp-save member) + (format s " ~A~%" + (cpp-code (funcall (cpp-member-capnp-save member) + member-builder member-name))) + (write-line (capnp-save-default member-name + (cpp-member-type member) + member-builder) + s)) + (write-line "}" s)))))) (write-line "}" s))) ;;; Capnp C++ deserialization code generation @@ -887,7 +991,7 @@ used for outside definition." (let ((construct-declaration (capnp-construct-declaration cpp-class :inline nil))) (unless construct-declaration (return-from capnp-construct-code)) - (let ((class-name (cpp-class-name cpp-class)) + (let ((class-name (cpp-type-base-name cpp-class)) (union-subclasses (capnp-union-subclasses cpp-class))) (with-output-to-string (s) (format s "~A {~%" construct-declaration) @@ -898,11 +1002,17 @@ used for outside definition." ;; Inheritance, so forward the Construct. (progn (write-line " switch (reader.which()) {" s) + (when (capnp-union-and-compose-parents cpp-class) + ;; We are in the middle of the hierarchy, so allow + ;; constructing us. + (format s " case capnp::~A::~A: return std::unique_ptr<~A>(new ~A());~%" + (cpp-type-name class-name) (cpp-constant-name class-name) + (cpp-type-name class-name) (cpp-type-name class-name))) (dolist (subclass union-subclasses) (format s " case capnp::~A::~A:~%" (cpp-type-name class-name) - (cpp-constant-name (cpp-class-name subclass))) - (let ((subclass-name (cpp-type-name (cpp-class-name subclass)))) + (cpp-constant-name (cpp-type-base-name subclass))) + (let ((subclass-name (cpp-type-name (cpp-type-base-name subclass)))) (if (capnp-opts-type-args (cpp-class-capnp-opts subclass)) ;; Handle template instantiation (progn @@ -923,7 +1033,7 @@ used for outside definition." "Generate default load call for member." (declare (type string member-name member-type member-reader)) (let* ((type (parse-cpp-type-declaration member-type)) - (type-name (cpp-type-decl-name type))) + (type-name (cpp-type-base-name type))) (when (member type-name '("unique_ptr" "shared_ptr" "vector") :test #'string=) (error "Use a custom :capnp-load function for ~A ~A" type-name member-name)) (let* ((cpp-class (find-cpp-class type-name)) ;; TODO: full type-name search @@ -941,8 +1051,8 @@ used for outside definition." (declare (type cpp-class cpp-class)) (let* ((parents (capnp-union-parents-rec cpp-class)) (top-parent-class (if parents - (cpp-class-full-name (find-cpp-class (car (last parents)))) - (cpp-class-full-name cpp-class))) + (cpp-class-full-name (find-cpp-class (car (last parents))) :type-args nil) + (cpp-class-full-name cpp-class :type-args nil))) (reader-arg (list (if parents 'base-reader 'reader) (format nil "const capnp::~A::Reader &" top-parent-class)))) @@ -972,7 +1082,7 @@ used for outside definition." (when (or compose-parents (cpp-class-members cpp-class)) (format s " auto reader = base_reader.~{get~A().~}get~A();~%" (mapcar #'cpp-type-name (cdr (reverse parents))) - (cpp-type-name (cpp-class-name cpp-class)))) + (cpp-type-name cpp-class))) ;; Now handle composite inheritance calls. (dolist (parent compose-parents) (write-line "{" s) @@ -982,25 +1092,27 @@ used for outside definition." (cpp-type-name parent) (cpp-variable-name parent) (parent-args parent)) (write-line "}" s)))))) (dolist (member (cpp-class-members cpp-class)) - (let ((member-name (cpp-member-name member :struct (cpp-class-structp cpp-class))) - (member-reader (format nil "~A_reader" (cpp-member-name member :struct t))) - (capnp-name (cpp-type-name (cpp-member-symbol member)))) - (cond - ((cpp-primitive-type-p (cpp-member-type member)) - (format s " ~A = reader.get~A();~%" member-name capnp-name)) - (t - (write-line "{" s) ;; Enclose larger load code in new scope - (if (cpp-member-capnp-init member) - (format s " auto ~A = reader.get~A();~%" member-reader capnp-name) - (setf member-reader "reader")) - (if (cpp-member-capnp-load member) - (format s " ~A~%" - (cpp-code (funcall (cpp-member-capnp-load member) - member-reader member-name))) - (write-line (capnp-load-default member-name - (cpp-member-type member) - member-reader) s)) - (write-line "}" s))))) + (unless (and (eq :dont-save (cpp-member-capnp-save member)) + (not (cpp-member-capnp-load member))) + (let ((member-name (cpp-member-name member :struct (cpp-class-structp cpp-class))) + (member-reader (format nil "~A_reader" (cpp-member-name member :struct t))) + (capnp-name (cpp-type-name (cpp-member-symbol member)))) + (cond + ((capnp-primitive-type-p (capnp-type-of-member member)) + (format s " ~A = reader.get~A();~%" member-name capnp-name)) + (t + (write-line "{" s) ;; Enclose larger load code in new scope + (if (cpp-member-capnp-init member) + (format s " auto ~A = reader.get~A();~%" member-reader capnp-name) + (setf member-reader "reader")) + (if (cpp-member-capnp-load member) + (format s " ~A~%" + (cpp-code (funcall (cpp-member-capnp-load member) + member-reader member-name))) + (write-line (capnp-load-default member-name + (cpp-member-type member) + member-reader) s)) + (write-line "}" s)))))) (write-line "}" s))) (defvar *capnp-imports* nil @@ -1021,6 +1133,109 @@ used for outside definition." (declare (type string namespace)) (setf *capnp-namespace* namespace)) +(defun capnp-save-optional (capnp-type cpp-type &optional lambda-code) + "Generate the C++ code calling utils::SaveOptional. CAPNP-TYPE and CPP-TYPE +are passed as template parameters, while the optional LAMBDA-CODE is used to +save the value inside the std::optional." + (declare (type string capnp-type cpp-type) + (type (or null string) lambda-code)) + (let ((lambda-code (if lambda-code + lambda-code + "[](auto *builder, const auto &val) { val.Save(builder); }"))) + (lambda (builder member) + #>cpp + utils::SaveOptional<${capnp-type}, ${cpp-type}>(${member}, &${builder}, ${lambda-code}); + cpp<#))) + +(defun capnp-load-optional (capnp-type cpp-type &optional lambda-code) + "Generate the C++ code calling utils::LoadOptional. CAPNP-TYPE and CPP-TYPE +are passed as template parameters, while the optional LAMBDA-CODE is used to +load the value of std::optional." + (declare (type string capnp-type cpp-type) + (type (or null string) lambda-code)) + (let ((lambda-code (if lambda-code + lambda-code + (format nil + "[](const auto &reader) { ~A val; val.Load(reader); return val; }" + cpp-type)))) + (lambda (reader member) + #>cpp + ${member} = utils::LoadOptional<${capnp-type}, ${cpp-type}>(${reader}, ${lambda-code}); + cpp<#))) + +(defun capnp-save-vector (capnp-type cpp-type &optional lambda-code) + "Generate the C++ code calling utils::SaveVector. CAPNP-TYPE and CPP-TYPE +are passed as template parameters, while LAMBDA-CODE is used to save each +element." + (declare (type string capnp-type cpp-type) + (type (or null string) lambda-code)) + (let ((lambda-code (if lambda-code + lambda-code + "[](auto *builder, const auto &val) { val.Save(builder); }"))) + (lambda (builder member-name) + #>cpp + utils::SaveVector<${capnp-type}, ${cpp-type}>(${member-name}, &${builder}, ${lambda-code}); + cpp<#))) + +(defun capnp-load-vector (capnp-type cpp-type &optional lambda-code) + "Generate the C++ code calling utils::LoadVector. CAPNP-TYPE and CPP-TYPE +are passed as template parameters, while LAMBDA-CODE is used to load each +element." + (declare (type string capnp-type cpp-type) + (type (or null string) lambda-code)) + (let ((lambda-code (if lambda-code + lambda-code + (format nil + "[](const auto &reader) { ~A val; val.Load(reader); return val; }" + cpp-type)))) + (lambda (reader member-name) + #>cpp + utils::LoadVector<${capnp-type}, ${cpp-type}>(&${member-name}, ${reader}, ${lambda-code}); + cpp<#))) + +(defun capnp-save-enum (capnp-type cpp-type &optional enum-values) + "Generate C++ code for saving the enum specified by CPP-TYPE by converting +the values to CAPNP-TYPE. If ENUM-VALUES are not specified, tries to find the +CPP-TYPE among defined enums." + (declare (type string capnp-type) + (type (or symbol string) cpp-type)) + (lambda (builder member) + (let* ((enum-values (if enum-values + enum-values + (cpp-enum-values (find-cpp-enum cpp-type)))) + (member-setter (remove #\_ (string-capitalize member))) + (cases (mapcar (lambda (value-symbol) + (let ((value (cl-ppcre:regex-replace-all "-" (string value-symbol) "_"))) + #>cpp + case ${cpp-type}::${value}: + ${builder}->set${member-setter}(${capnp-type}::${value}); + break; + cpp<#)) + enum-values))) + (format nil "switch (~A) {~%~{~A~%~}}" member (mapcar #'raw-cpp-string cases))))) + +(defun capnp-load-enum (capnp-type cpp-type &optional enum-values) + "Generate C++ code for loading the enum specified by CPP-TYPE by converting +the values from CAPNP-TYPE. If ENUM-VALUES are not specified, tries to find the +CPP-TYPE among defined enums." + (declare (type string capnp-type) + (type (or symbol string) cpp-type)) + (lambda (reader member) + (let* ((enum-values (if enum-values + enum-values + (cpp-enum-values (find-cpp-enum cpp-type)))) + (member-getter (remove #\_ (string-capitalize member))) + (cases (mapcar (lambda (value-symbol) + (let ((value (cl-ppcre:regex-replace-all "-" (string value-symbol) "_"))) + #>cpp + case ${capnp-type}::${value}: + ${member} = ${cpp-type}::${value}; + break; + cpp<#)) + enum-values))) + (format nil "switch (~A.get~A()) {~%~{~A~%~}}" + reader member-getter (mapcar #'raw-cpp-string cases))))) + (defvar *cpp-namespaces* nil "Stack of C++ namespaces we are generating the code in.") @@ -1037,30 +1252,39 @@ used for outside definition." (pop *cpp-namespaces*) #>cpp } cpp<#) +(defvar *cpp-impl* nil "List of (namespace . C++ code) pairs that should be + written in the implementation (.cpp) file.") + +(defun in-impl (&rest args) + (let ((namespaces (reverse *cpp-namespaces*))) + (setf *cpp-impl* + (append *cpp-impl* (mapcar (lambda (cpp) (cons namespaces cpp)) + args))))) + (defvar *cpp-inner-types* nil "List of cpp types defined inside an enclosing class or struct") (defvar *cpp-enclosing-class* nil "Symbol name of the `CPP-CLASS' inside which inner types are defined.") -(defmacro define-enum (name maybe-documentation &rest values) - "Define a C++ enum. Documentation is optional. Syntax is: +(defmacro define-enum (name values &rest options) + "Define a C++ enum. Documentation is optional. The only options are + :documentation and :serialize. Syntax is: ;; (define-enum name -;; [documentation-string] -;; value1 value2 ...)" - (declare (type symbol name) - (type (or string symbol) maybe-documentation)) - (let ((documentation (when (stringp maybe-documentation) maybe-documentation)) - (all-values (if (symbolp maybe-documentation) - (cons maybe-documentation values) - values)) +;; (value1 value2 ...) +;; (:enum-option option-value)*)" + (declare (type symbol name)) + (let ((documentation (second (assoc :documentation options))) (enum (gensym (format nil "ENUM-~A" name)))) - `(let ((,enum (make-cpp-enum :symbol ',name + `(let ((,enum (make-instance 'cpp-enum + :name ',name :documentation ,documentation - :values ',all-values - :enclosing-class *cpp-enclosing-class*))) + :values ',values + :enclosing-class *cpp-enclosing-class* + :capnp-schema ',(assoc :serialize options)))) (prog1 ,enum + (push ,enum *cpp-enums*) (push ,enum *cpp-inner-types*))))) (defmacro define-class (name super-classes slots &rest options) @@ -1147,26 +1371,27 @@ Generates C++: `(let ((,class (let ((*cpp-inner-types* nil) (*cpp-enclosing-class* ',class-name)) - (make-cpp-class :name ',class-name :super-classes ',super-classes - :type-params ',type-params - :structp ,(second (assoc :structp options)) - :members (list ,@members) - :documentation ,(second (assoc :documentation options)) - :public (list ,@(cdr (assoc :public options))) - :protected (list ,@(cdr (assoc :protected options))) - :private (list ,@(cdr (assoc :private options))) - :capnp-opts ,(when (member :capnp serialize) - `(make-capnp-opts ,@(cdr (member :capnp serialize)))) - :namespace (reverse *cpp-namespaces*) - ;; Set inner types at the end. This works - ;; because CL standard specifies order of - ;; evaluation from left to right. - :inner-types *cpp-inner-types*)))) + (make-instance 'cpp-class + :name ',class-name :super-classes ',super-classes + :type-params ',type-params + :structp ,(second (assoc :structp options)) + :members (list ,@members) + :documentation ,(second (assoc :documentation options)) + :public (list ,@(cdr (assoc :public options))) + :protected (list ,@(cdr (assoc :protected options))) + :private (list ,@(cdr (assoc :private options))) + :capnp-opts ,(when (member :capnp serialize) + `(make-capnp-opts ,@(cdr (member :capnp serialize)))) + :namespace (reverse *cpp-namespaces*) + ;; Set inner types at the end. This works + ;; because CL standard specifies order of + ;; evaluation from left to right. + :inner-types *cpp-inner-types*)))) (prog1 ,class (push ,class *cpp-classes*) ;; Set the parent's inner types (push ,class *cpp-inner-types*) - (setf (cpp-class-enclosing-class ,class) *cpp-enclosing-class*) + (setf (cpp-type-enclosing-class ,class) *cpp-enclosing-class*) ,(when (eq :boost (car serialize)) `(setf (cpp-class-private ,class) (append (cpp-class-private ,class) (boost-serialization ,class)))))))))) @@ -1174,6 +1399,73 @@ Generates C++: (defmacro define-struct (name super-classes slots &rest options) `(define-class ,name ,super-classes ,slots (:structp t) ,@options)) +(defmacro define-rpc (name request response) + (declare (type list request response)) + (assert (eq :request (car request))) + (assert (eq :response (car response))) + (flet ((decl-type-info (class-name) + #>cpp + using Capnp = capnp::${class-name}; + static const communication::rpc::MessageType TypeInfo; + cpp<#) + (def-type-info (class-name) + #>cpp + const communication::rpc::MessageType + ${class-name}::TypeInfo{::capnp::typeId<${class-name}::Capnp>(), "${class-name}"}; + cpp<#) + (def-constructor (class-name members) + (let ((full-constructor + (let ((init-members (remove-if (lambda (slot-def) + ;; TODO: proper initarg + (let ((initarg (member :initarg slot-def))) + (and initarg (null (second initarg))))) + members))) + (with-output-to-string (s) + (when init-members + (format s "~A ~A(~:{~A ~A~:^, ~}) : ~:{~A(~A)~:^, ~} {}" + (if (= 1 (list-length init-members)) "explicit" "") + class-name + (mapcar (lambda (member) + (list (cpp-type-name (second member)) + (cpp-variable-name (first member)))) + init-members) + (mapcar (lambda (member) + (let ((var (cpp-variable-name (first member))) + (movep (eq :move (second (member :initarg member))))) + (list var (if movep + (format nil "std::move(~A)" var) + var)))) + init-members))))))) + #>cpp + ${class-name}() {} + ${full-constructor} + cpp<#))) + (let* ((req-sym (intern (format nil "~A-~A" name 'req))) + (req-name (cpp-type-name req-sym)) + (res-sym (intern (format nil "~A-~A" name 'res))) + (res-name (cpp-type-name res-sym)) + (rpc-name (format nil "~ARpc" (cpp-type-name name))) + (rpc-decl + #>cpp + using ${rpc-name} = communication::rpc::RequestResponse<${req-name}, ${res-name}>; + cpp<#)) + `(cpp-list + (define-struct ,req-sym () + ,@(cdr request) + (:public + ,(decl-type-info req-name) + ,(def-constructor req-name (second request))) + (:serialize :capnp :base t)) + (in-impl ,(def-type-info req-name)) + (define-struct ,res-sym () + ,@(cdr response) + (:public + ,(decl-type-info res-name) + ,(def-constructor res-name (second response))) + (:serialize :capnp :base t)) + (in-impl ,(def-type-info res-name)) + ,rpc-decl)))) + (defun read-lcp (filepath) "Read the FILEPATH and return a list of C++ meta information that should be formatted and output." @@ -1185,12 +1477,12 @@ formatted and output." for res = (handler-case (eval form) (error (err) (file-position in-stream 0) ;; start of stream - (error "~%~A:~A: error:~%~%~A~%~%in:~%~%~A" + (error "~%~A:~A: error:~2%~A~2%in:~2%~A" (uiop:native-namestring filepath) (count-newlines in-stream :stop-position (1+ stream-pos)) err form))) do (setf stream-pos (file-position in-stream)) - when (typep res '(or raw-cpp cpp-class cpp-enum)) + when (typep res '(or raw-cpp cpp-type cpp-list)) collect res) (end-of-file () (file-position in-stream 0) ;; start of stream @@ -1199,58 +1491,55 @@ formatted and output." (count-newlines in-stream :stop-position (1+ stream-pos)))))))) -(defun generate-capnp (cpp-classes &key capnp-file capnp-id cpp-file hpp-file lcp-file) - "Generate Cap'n Proto serialization code for given CPP-CLASSES. The schema +(defun generate-capnp (cpp-types &key capnp-file capnp-id cpp-out lcp-file) + "Generate Cap'n Proto serialization code for given CPP-TYPES. The schema is written to CAPNP-FILE using the CAPNP-ID. The C++ serialization code is -written to CPP-FILE. This source file will include the provided HPP-FILE. +written to CPP-OUT stream. This source file will include the provided HPP-FILE. Original LCP-FILE is used just to insert a comment about the source of the code generation." (with-open-file (out capnp-file :direction :output :if-exists :supersede) - (format out "# Autogenerated using LCP from '~A'~%~%" lcp-file) - (format out "~A;~%~%" capnp-id) + (format out "# Autogenerated using LCP from '~A'~%# DO NOT EDIT!~2%" lcp-file) + (format out "~A;~2%" capnp-id) (write-line "using Cxx = import \"/capnp/c++.capnp\";" out) - (format out "$Cxx.namespace(\"~A::capnp\");~%~%" *capnp-namespace*) + (format out "$Cxx.namespace(\"~A::capnp\");~2%" *capnp-namespace*) (dolist (capnp-import *capnp-imports* (terpri out)) (format out "using ~A = import ~S;~%" (remove #\- (string-capitalize (car capnp-import))) (cdr capnp-import))) - (dolist (cpp-class cpp-classes) + (dolist (cpp-type cpp-types) ;; Generate schema only for top level classes, inner classes are handled ;; inside the generation of the enclosing class. - (unless (cpp-class-enclosing-class cpp-class) - (let ((schema (capnp-schema cpp-class))) + (unless (cpp-type-enclosing-class cpp-type) + (let ((schema (capnp-schema cpp-type))) (when schema (write-line schema out)))))) ;; Now generate the save/load C++ code in the cpp file. - (with-open-file (out cpp-file :direction :output :if-exists :supersede) - (format out "// Autogenerated using LCP from '~A'~%~%" lcp-file) - (write-line "#include \"utils/serialization.hpp\"" out) - (format out "#include \"~A\"~%~%" hpp-file) - (let (open-namespaces) - (dolist (cpp-class cpp-classes) - ;; Check if we need to open or close namespaces - (loop for namespace in (cpp-class-namespace cpp-class) - with unmatched = open-namespaces do - (if (string= namespace (car unmatched)) - (setf unmatched (cdr unmatched)) - (progn - (dolist (to-close unmatched) - (declare (ignore to-close)) - (write-line "}" out)) - (format out "namespace ~A {~%~%" namespace)))) - (setf open-namespaces (cpp-class-namespace cpp-class)) - ;; Output the serialization code - (format out "// Serialize code for ~A~%~%" - (cpp-type-name (cpp-class-name cpp-class))) - (let ((save-code (capnp-save-code cpp-class)) - (construct-code (capnp-construct-code cpp-class)) - (load-code (capnp-load-code cpp-class))) - (when save-code (write-line save-code out)) - (when construct-code (write-line construct-code out)) - (when load-code (write-line load-code out)))) - ;; Close remaining namespaces - (dolist (to-close open-namespaces) - (declare (ignore to-close)) - (write-line "}" out))))) + (write-line "// Autogenerated Cap'n Proto serialization code" cpp-out) + (write-line "#include \"utils/serialization.hpp\"" cpp-out) + (let (open-namespaces) + (dolist (cpp-class (remove-if (lambda (cpp-type) (not (typep cpp-type 'cpp-class))) cpp-types)) + ;; Check if we need to open or close namespaces + (loop for namespace in (cpp-type-namespace cpp-class) + with unmatched = open-namespaces do + (if (string= namespace (car unmatched)) + (setf unmatched (cdr unmatched)) + (progn + (dolist (to-close unmatched) + (declare (ignore to-close)) + (format cpp-out "~%}")) + (format cpp-out "namespace ~A {~2%" namespace)))) + (setf open-namespaces (cpp-type-namespace cpp-class)) + ;; Output the serialization code + (format cpp-out "// Serialize code for ~A~2%" (cpp-type-name cpp-class)) + (let ((save-code (capnp-save-code cpp-class)) + (construct-code (capnp-construct-code cpp-class)) + (load-code (capnp-load-code cpp-class))) + (when save-code (write-line save-code cpp-out)) + (when construct-code (write-line construct-code cpp-out)) + (when load-code (write-line load-code cpp-out)))) + ;; Close remaining namespaces + (dolist (to-close open-namespaces) + (declare (ignore to-close)) + (format cpp-out "~%}")))) (defun process-file (lcp-file &key capnp-id) "Process a LCP-FILE and write the output to .hpp file in the same directory. @@ -1270,24 +1559,52 @@ file." (*capnp-imports* nil) (*capnp-type-converters* nil) (*cpp-inner-types* nil) + (*cpp-impl*) ;; Don't reset *cpp-classes* if we want to have support for ;; procesing multiple files. ;; (*cpp-classes* nil) + ;; (*cpp-enums* nil) ) ;; First read and evaluate the whole file, then output the evaluated ;; cpp-code. This allows us to generate code which may rely on ;; evaluation done after the code definition. (with-open-file (out hpp-file :direction :output :if-exists :supersede) - (format out "// Autogenerated using LCP from '~A'~%~%" lcp-file) + (format out "// Autogenerated using LCP from '~A'~%// DO NOT EDIT!~2%" lcp-file) (dolist (res (read-lcp lcp-file)) (write-line (cpp-code res) out))) (when *cpp-namespaces* (error "Unclosed namespaces: ~A" (reverse *cpp-namespaces*))) ;; If we have a capnp-id, generate the schema - (when capnp-id - (let ((classes-for-capnp - (remove-if (complement #'cpp-class-capnp-opts) *cpp-classes*))) - (when classes-for-capnp - (generate-capnp classes-for-capnp :capnp-file capnp-file :capnp-id capnp-id - :cpp-file cpp-file :hpp-file (file-namestring hpp-file) - :lcp-file lcp-file))))))) + (let ((types-for-capnp (when capnp-id + (append (remove-if (complement #'cpp-class-capnp-opts) *cpp-classes*) + (remove-if (complement #'cpp-enum-capnp-schema) *cpp-enums*))))) + ;; When we have either capnp or C++ code for the .cpp file, generate the .cpp file + (when (or *cpp-impl* types-for-capnp) + (with-open-file (out cpp-file :direction :output :if-exists :supersede) + (format out "// Autogenerated using LCP from '~A'~%// DO NOT EDIT!~2%" lcp-file) + (format out "#include \"~A\"~2%" (file-namestring hpp-file)) + ;; First output the C++ code from the user + (let (open-namespaces) + (dolist (cpp *cpp-impl*) + (destructuring-bind (namespaces . code) cpp + ;; Check if we need to open or close namespaces + (loop for namespace in namespaces + with unmatched = open-namespaces do + (if (string= namespace (car unmatched)) + (setf unmatched (cdr unmatched)) + (progn + (dolist (to-close unmatched) + (declare (ignore to-close)) + (format out "~%}")) + (format out "namespace ~A {~2%" namespace)))) + (setf open-namespaces namespaces) + ;; Output the code + (write-line (cpp-code code) out))) + ;; Close remaining namespaces + (dolist (to-close open-namespaces) + (declare (ignore to-close)) + (format out "~%}"))) + ;; Now output the capnp code + (when types-for-capnp + (generate-capnp types-for-capnp :capnp-file capnp-file :capnp-id capnp-id + :cpp-out out :lcp-file lcp-file)))))))) diff --git a/src/query/frontend/ast/ast.capnp b/src/query/frontend/ast/ast.capnp index 0f996e2a0..4269cc6c7 100644 --- a/src/query/frontend/ast/ast.capnp +++ b/src/query/frontend/ast/ast.capnp @@ -3,8 +3,8 @@ using Cxx = import "/capnp/c++.capnp"; $Cxx.namespace("query::capnp"); -using Utils = import "/utils/serialization.capnp"; -using Storage = import "/storage/types.capnp"; +using Dis = import "/distributed/serialization.capnp"; +using Storage = import "/storage/serialization.capnp"; using Symbols = import "/query/frontend/semantic/symbol.capnp"; struct Tree { @@ -227,7 +227,7 @@ struct BaseLiteral { struct PrimitiveLiteral { tokenPosition @0 :Int32; - value @1 :Utils.TypedValue; + value @1 :Dis.TypedValue; } struct ListLiteral { diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp index 154f59007..99cd17095 100644 --- a/src/query/frontend/ast/ast.cpp +++ b/src/query/frontend/ast/ast.cpp @@ -249,7 +249,7 @@ void PrimitiveLiteral::Save(capnp::BaseLiteral::Builder *base_literal_builder, auto primitive_literal_builder = base_literal_builder->initPrimitiveLiteral(); primitive_literal_builder.setTokenPosition(token_position_); auto typed_value_builder = primitive_literal_builder.getValue(); - utils::SaveCapnpTypedValue(value_, typed_value_builder); + utils::SaveCapnpTypedValue(value_, &typed_value_builder); } void PrimitiveLiteral::Load(const capnp::Tree::Reader &reader, @@ -259,7 +259,7 @@ void PrimitiveLiteral::Load(const capnp::Tree::Reader &reader, auto pl_reader = reader.getExpression().getBaseLiteral().getPrimitiveLiteral(); auto typed_value_reader = pl_reader.getValue(); - utils::LoadCapnpTypedValue(value_, typed_value_reader); + utils::LoadCapnpTypedValue(typed_value_reader, &value_); token_position_ = pl_reader.getTokenPosition(); } diff --git a/src/query/frontend/semantic/symbol.capnp b/src/query/frontend/semantic/symbol.capnp index 7d955391a..076ea08cb 100644 --- a/src/query/frontend/semantic/symbol.capnp +++ b/src/query/frontend/semantic/symbol.capnp @@ -19,3 +19,13 @@ struct Symbol { userDeclared @3 :Bool; tokenPosition @4 :Int32; } + +struct SymbolTable { + position @0 :Int32; + table @1 :List(Entry); + + struct Entry { + key @0 :Int32; + val @1 :Symbol; + } +} diff --git a/src/query/frontend/semantic/symbol_table.hpp b/src/query/frontend/semantic/symbol_table.hpp index 0499d4979..852572ff6 100644 --- a/src/query/frontend/semantic/symbol_table.hpp +++ b/src/query/frontend/semantic/symbol_table.hpp @@ -7,6 +7,7 @@ #include "boost/serialization/serialization.hpp" #include "query/frontend/ast/ast.hpp" +#include "query/frontend/semantic/symbol.capnp.h" #include "query/frontend/semantic/symbol.hpp" namespace query { @@ -30,6 +31,29 @@ class SymbolTable final { const auto &table() const { return table_; } + void Save(capnp::SymbolTable::Builder *builder) const { + builder->setPosition(position_); + auto list_builder = builder->initTable(table_.size()); + size_t i = 0; + for (const auto &entry : table_) { + auto entry_builder = list_builder[i++]; + entry_builder.setKey(entry.first); + auto sym_builder = entry_builder.initVal(); + entry.second.Save(&sym_builder); + } + } + + void Load(const capnp::SymbolTable::Reader &reader) { + position_ = reader.getPosition(); + table_.clear(); + for (const auto &entry_reader : reader.getTable()) { + int key = entry_reader.getKey(); + Symbol val; + val.Load(entry_reader.getVal()); + table_[key] = val; + } + } + private: int position_{0}; std::map<int, Symbol> table_; diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 42ce06778..13f1cd87e 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -1345,10 +1345,10 @@ class DistributedExpandBfsCursor : public query::plan::Cursor { int current_depth_{-1}; // Map from worker IDs to their corresponding subcursors. - std::unordered_map<int, int64_t> subcursor_ids_; + std::unordered_map<int16_t, int64_t> subcursor_ids_; // Next worker master should try pulling from. - std::unordered_map<int, int64_t>::iterator pull_pos_; + std::unordered_map<int16_t, int64_t>::iterator pull_pos_; }; class ExpandWeightedShortestPathCursor : public query::plan::Cursor { diff --git a/src/query/plan/operator.lcp b/src/query/plan/operator.lcp index d6b2f9279..cc087ed51 100644 --- a/src/query/plan/operator.lcp +++ b/src/query/plan/operator.lcp @@ -140,7 +140,7 @@ cpp<# (lcp:capnp-namespace "query::plan") (lcp:capnp-import 'utils "/utils/serialization.capnp") -(lcp:capnp-import 'storage "/storage/types.capnp") +(lcp:capnp-import 'storage "/storage/serialization.capnp") (lcp:capnp-import 'ast "/query/frontend/ast/ast.capnp") (lcp:capnp-import 'semantic "/query/frontend/semantic/symbol.capnp") (lcp:capnp-import 'common "/query/common.capnp") @@ -274,73 +274,6 @@ cpp<# LoadPointers(${archive}, ${member-name}); cpp<#) -(defun capnp-save-vector (capnp-type cpp-type &optional lambda-code) - (let ((lambda-code (if lambda-code - lambda-code - "[](auto *builder, const auto &val) { val.Save(builder); }"))) - (lambda (builder member-name) - #>cpp - utils::SaveVector<${capnp-type}, ${cpp-type}>(${member-name}, &${builder}, ${lambda-code}); - cpp<#))) - -(defun capnp-load-vector (capnp-type cpp-type &optional lambda-code) - (let ((lambda-code (if lambda-code - lambda-code - (format nil - "[](const auto &reader) { ~A val; val.Load(reader); return val; }" - cpp-type)))) - (lambda (reader member-name) - #>cpp - utils::LoadVector<${capnp-type}, ${cpp-type}>(&${member-name}, ${reader}, ${lambda-code}); - cpp<#))) - -(defun capnp-save-optional (capnp-type cpp-type &optional lambda-code) - (let ((lambda-code (if lambda-code - lambda-code - "[](auto *builder, const auto &val) { val.Save(builder); }"))) - (lambda (builder member) - #>cpp - utils::SaveOptional<${capnp-type}, ${cpp-type}>(${member}, &${builder}, ${lambda-code}); - cpp<#))) - -(defun capnp-load-optional (capnp-type cpp-type &optional lambda-code) - (let ((lambda-code (if lambda-code - lambda-code - (format nil - "[](const auto &reader) { ~A val; val.Load(reader); return val; }" - cpp-type)))) - (lambda (reader member) - #>cpp - ${member} = utils::LoadOptional<${capnp-type}, ${cpp-type}>(${reader}, ${lambda-code}); - cpp<#))) - -(defun capnp-save-enum (capnp-type cpp-type enum-values) - (lambda (builder member) - (let* ((member-setter (remove #\_ (string-capitalize member))) - (cases (mapcar (lambda (value-symbol) - (let ((value (cl-ppcre:regex-replace-all "-" (string value-symbol) "_"))) - #>cpp - case ${cpp-type}::${value}: - ${builder}->set${member-setter}(${capnp-type}::${value}); - break; - cpp<#)) - enum-values))) - (format nil "switch (~A) {~%~{~A~%~}}" member (mapcar #'lcp::raw-cpp-string cases))))) - -(defun capnp-load-enum (capnp-type cpp-type enum-values) - (lambda (reader member) - (let* ((member-getter (remove #\_ (string-capitalize member))) - (cases (mapcar (lambda (value-symbol) - (let ((value (cl-ppcre:regex-replace-all "-" (string value-symbol) "_"))) - #>cpp - case ${capnp-type}::${value}: - ${member} = ${cpp-type}::${value}; - break; - cpp<#)) - enum-values))) - (format nil "switch (~A.get~A()) {~%~{~A~%~}}" - reader member-getter (mapcar #'lcp::raw-cpp-string cases))))) - (defun save-ast-pointer (builder member) (let ((member-getter (remove #\_ (string-capitalize member)))) #>cpp @@ -361,19 +294,19 @@ cpp<# cpp<#))) (defun save-ast-vector (ast-type) - (capnp-save-vector "::query::capnp::Tree" ast-type - "[helper](auto *builder, const auto &val) { - val->Save(builder, &helper->saved_ast_uids); - }")) + (lcp:capnp-save-vector "::query::capnp::Tree" ast-type + "[helper](auto *builder, const auto &val) { + val->Save(builder, &helper->saved_ast_uids); + }")) (defun load-ast-vector (ast-type) - (capnp-load-vector "::query::capnp::Tree" ast-type - (format - nil - "[helper](const auto &reader) { - // We expect the unsafe downcast via static_cast to work. - return static_cast<~A>(helper->ast_storage.Load(reader, &helper->loaded_ast_uids)); - }" ast-type))) + (lcp:capnp-load-vector "::query::capnp::Tree" ast-type + (format + nil + "[helper](const auto &reader) { + // We expect the unsafe downcast via static_cast to work. + return static_cast<~A>(helper->ast_storage.Load(reader, &helper->loaded_ast_uids)); + }" ast-type))) (defun save-operator-pointer (builder member-name) #>cpp @@ -586,10 +519,10 @@ chained in cases when longer paths need creating. (output-symbol "Symbol" :reader t :scope :protected) (graph-view "GraphView" :reader t :scope :protected :capnp-init nil - :capnp-save (capnp-save-enum "::query::capnp::GraphView" "GraphView" - '(old new)) - :capnp-load (capnp-load-enum "::query::capnp::GraphView" "GraphView" - '(old new)) + :capnp-save (lcp:capnp-save-enum "::query::capnp::GraphView" "GraphView" + '(old new)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::GraphView" "GraphView" + '(old new)) :documentation "Controls which graph state is used to produce vertices. @@ -661,8 +594,8 @@ given label. auto value_builder = builder->initValue(); bound.value()->Save(&value_builder, &helper->saved_ast_uids); }")) - (funcall (capnp-save-optional "::utils::capnp::Bound<::query::capnp::Tree>" "Bound" - save-bound) + (funcall (lcp:capnp-save-optional "::utils::capnp::Bound<::query::capnp::Tree>" "Bound" + save-bound) builder member))) (defun load-optional-bound (reader member) @@ -673,8 +606,8 @@ given label. auto *value = static_cast<Expression*>(helper->ast_storage.Load(reader.getValue(), &helper->loaded_ast_uids)); return Bound(value, type); }")) - (funcall (capnp-load-optional "::utils::capnp::Bound<::query::capnp::Tree>" "Bound" - load-bound) + (funcall (lcp:capnp-load-optional "::utils::capnp::Bound<::query::capnp::Tree>" "Bound" + load-bound) reader member))) (lcp:define-class scan-all-by-label-property-range (scan-all) @@ -805,13 +738,13 @@ property value. (edge-symbol "Symbol" :reader t :scope :protected) (direction "EdgeAtom::Direction" :reader t :scope :protected :capnp-type "Ast.EdgeAtom.Direction" :capnp-init nil - :capnp-save (capnp-save-enum "::query::capnp::EdgeAtom::Direction" "EdgeAtom::Direction" - '(in out both)) - :capnp-load (capnp-load-enum "::query::capnp::EdgeAtom::Direction" "EdgeAtom::Direction" - '(in out both))) + :capnp-save (lcp:capnp-save-enum "::query::capnp::EdgeAtom::Direction" "EdgeAtom::Direction" + '(in out both)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::EdgeAtom::Direction" "EdgeAtom::Direction" + '(in out both))) (edge-types "std::vector<storage::EdgeType>" :reader t :scope :protected - :capnp-save (capnp-save-vector "::storage::capnp::Common" "storage::EdgeType") - :capnp-load (capnp-load-vector "::storage::capnp::Common" "storage::EdgeType")) + :capnp-save (lcp:capnp-save-vector "::storage::capnp::Common" "storage::EdgeType") + :capnp-load (lcp:capnp-load-vector "::storage::capnp::Common" "storage::EdgeType")) ;; the input op and the symbol under which the op's result ;; can be found in the frame (input "std::shared_ptr<LogicalOperator>" :scope :protected @@ -823,10 +756,10 @@ property value. been expanded and should be just validated in the frame.") (graph-view "GraphView" :reader t :scope :protected :capnp-init nil - :capnp-save (capnp-save-enum "::query::capnp::GraphView" "GraphView" - '(old new)) - :capnp-load (capnp-load-enum "::query::capnp::GraphView" "GraphView" - '(old new)) + :capnp-save (lcp:capnp-save-enum "::query::capnp::GraphView" "GraphView" + '(old new)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::GraphView" "GraphView" + '(old new)) :documentation "from which state the input node should get expanded")) (:documentation @@ -969,10 +902,10 @@ pulled.") (lcp:define-class expand-variable (logical-operator expand-common) ((type "EdgeAtom::Type" :reader t :capnp-type "Ast.EdgeAtom.Type" :capnp-init nil - :capnp-save (capnp-save-enum "::query::capnp::EdgeAtom::Type" "EdgeAtom::Type" - '(single depth-first breadth-first weighted-shortest-path)) - :capnp-load (capnp-load-enum "::query::capnp::EdgeAtom::Type" "EdgeAtom::Type" - '(single depth-first breadth-first weighted-shortest-path))) + :capnp-save (lcp:capnp-save-enum "::query::capnp::EdgeAtom::Type" "EdgeAtom::Type" + '(single depth-first breadth-first weighted-shortest-path)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::EdgeAtom::Type" "EdgeAtom::Type" + '(single depth-first breadth-first weighted-shortest-path))) (is-reverse :bool :documentation "True if the path should be written as expanding from node_symbol to input_symbol.") (lower-bound "Expression *" :save-fun #'save-pointer :load-fun #'load-pointer @@ -985,15 +918,15 @@ pulled.") :documentation "Optional upper bound of the variable length expansion, defaults are (1, inf)") (filter-lambda "Lambda") (weight-lambda "std::experimental::optional<Lambda>" - :capnp-save (capnp-save-optional + :capnp-save (lcp:capnp-save-optional "capnp::ExpandVariable::Lambda" "Lambda" "[helper](auto *builder, const auto &val) { val.Save(builder, helper); }") - :capnp-load (capnp-load-optional + :capnp-load (lcp:capnp-load-optional "capnp::ExpandVariable::Lambda" "Lambda" "[helper](const auto &reader) { Lambda val; val.Load(reader, helper); return val; }")) (total-weight "std::experimental::optional<Symbol>" - :capnp-save (capnp-save-optional "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-optional "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-optional "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-optional "::query::capnp::Symbol" "Symbol"))) (:documentation "Variable-length expansion operator. For a node existing in the frame it expands a variable number of edges and places them @@ -1088,8 +1021,8 @@ pulled.") :capnp-load #'load-operator-pointer) (path-symbol "Symbol" :reader t) (path-elements "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "Constructs a named path from it's elements and places it on the frame.") (:public @@ -1324,8 +1257,8 @@ can be stored (a TypedValue that can be converted to PropertyValue).") :capnp-save #'save-ast-pointer :capnp-load (load-ast-pointer "Expression *") :save-fun #'save-pointer :load-fun #'load-pointer) (op "Op" :capnp-init nil - :capnp-save (capnp-save-enum "capnp::SetProperties::Op" "Op" '(update replace)) - :capnp-load (capnp-load-enum "capnp::SetProperties::Op" "Op" '(update replace)))) + :capnp-save (lcp:capnp-save-enum "capnp::SetProperties::Op" "Op") + :capnp-load (lcp:capnp-load-enum "capnp::SetProperties::Op" "Op"))) (:documentation "Logical op for setting the whole properties set on a vertex or an edge. @@ -1336,12 +1269,13 @@ Supports setting (replacing the whole properties set with another) and updating.") (:public (lcp:define-enum op - "Defines how setting the properties works. + (update replace) + (:documentation "Defines how setting the properties works. @c UPDATE means that the current property set is augmented with additional ones (existing props of the same name are replaced), while @c REPLACE means -that the old props are discarded and replaced with new ones." - update replace) +that the old props are discarded and replaced with new ones.") + (:serialize :capnp)) #>cpp SetProperties(const std::shared_ptr<LogicalOperator> &input, @@ -1390,8 +1324,8 @@ that the old props are discarded and replaced with new ones." :capnp-load #'load-operator-pointer) (input-symbol "Symbol") (labels "std::vector<storage::Label>" - :capnp-save (capnp-save-vector "::storage::capnp::Common" "storage::Label") - :capnp-load (capnp-load-vector "::storage::capnp::Common" "storage::Label"))) + :capnp-save (lcp:capnp-save-vector "::storage::capnp::Common" "storage::Label") + :capnp-load (lcp:capnp-load-vector "::storage::capnp::Common" "storage::Label"))) (:documentation "Logical operator for setting an arbitrary number of labels on a Vertex. @@ -1478,8 +1412,8 @@ It does NOT remove labels that are already set on that Vertex.") :capnp-load #'load-operator-pointer) (input-symbol "Symbol") (labels "std::vector<storage::Label>" - :capnp-save (capnp-save-vector "::storage::capnp::Common" "storage::Label") - :capnp-load (capnp-load-vector "::storage::capnp::Common" "storage::Label"))) + :capnp-save (lcp:capnp-save-vector "::storage::capnp::Common" "storage::Label") + :capnp-load (lcp:capnp-load-vector "::storage::capnp::Common" "storage::Label"))) (:documentation "Logical operator for removing an arbitrary number of labels on a Vertex. @@ -1522,8 +1456,8 @@ If a label does not exist on a Vertex, nothing happens.") :capnp-load #'load-operator-pointer) (expand-symbol "Symbol") (previous-symbols "std::vector<Symbol>" - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "Filter whose Pull returns true only when the given expand_symbol frame value (the latest expansion) is not @@ -1585,8 +1519,8 @@ between edges and an edge lists).") :capnp-save #'save-operator-pointer :capnp-load #'load-operator-pointer) (symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol")) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol")) (advance-command :bool :reader t)) (:documentation "Pulls everything from the input before passing it through. @@ -1667,10 +1601,10 @@ cpp<# :capnp-save #'save-operator-pointer :capnp-load #'load-operator-pointer) (aggregations "std::vector<Element>" :reader t - :capnp-save (capnp-save-vector + :capnp-save (lcp:capnp-save-vector "capnp::Aggregate::Element" "Element" "[helper](auto *builder, const auto &val) { val.Save(builder, helper); }") - :capnp-load (capnp-load-vector + :capnp-load (lcp:capnp-load-vector "capnp::Aggregate::Element" "Element" "[helper](const auto &reader) { Element val; val.Load(reader, helper); return val; }")) (group-by "std::vector<Expression *>" :reader t @@ -1679,8 +1613,8 @@ cpp<# :capnp-load (load-ast-vector "Expression *") :save-fun #'save-pointers :load-fun #'load-pointers) (remember "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "Performs an arbitrary number of aggregations of data from the given input grouped by the given criteria. @@ -1706,10 +1640,10 @@ elements are in an undefined state after aggregation.") :capnp-save #'save-ast-pointer :capnp-load (load-ast-pointer "Expression *") :save-fun #'save-pointer :load-fun #'load-pointer) (op "Aggregation::Op" :capnp-type "Ast.Aggregation.Op" - :capnp-init nil :capnp-save (capnp-save-enum "::query::capnp::Aggregation::Op" "Aggregation::Op" - '(count min max sum avg collect-list collect-map)) - :capnp-load (capnp-load-enum "::query::capnp::Aggregation::Op" "Aggregation::Op" - '(count min max sum avg collect-list collect-map))) + :capnp-init nil :capnp-save (lcp:capnp-save-enum "::query::capnp::Aggregation::Op" "Aggregation::Op" + '(count min max sum avg collect-list collect-map)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::Aggregation::Op" "Aggregation::Op" + '(count min max sum avg collect-list collect-map))) (output-sym "Symbol")) (:documentation "An aggregation element, contains: @@ -1949,8 +1883,8 @@ input should be performed).") :capnp-load (load-ast-vector "Expression *") :save-fun #'save-pointers :load-fun #'load-pointers) (output-symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "Logical operator for ordering (sorting) results. @@ -2080,8 +2014,8 @@ documentation.") :capnp-save #'save-operator-pointer :capnp-load #'load-operator-pointer) (optional-symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "Optional operator. Used for optional match. For every successful Pull from the input branch a Pull from the optional @@ -2186,8 +2120,8 @@ Input is optional (unwind can be the first clause in a query).") :capnp-save #'save-operator-pointer :capnp-load #'load-operator-pointer) (value-symbols "std::vector<Symbol>" - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "Ensures that only distinct rows are yielded. This implementation accepts a vector of Symbols @@ -2272,14 +2206,14 @@ case the index already exists, nothing happens.") :capnp-save #'save-operator-pointer :capnp-load #'load-operator-pointer) (union-symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol")) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol")) (left-symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol")) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol")) (right-symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "A logical operator that applies UNION operator on inputs and places the result on the frame. @@ -2326,8 +2260,8 @@ vectors of symbols used by each of the inputs.") :capnp-load #'load-operator-pointer) (plan-id :int64_t :initval 0 :reader t) (symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "An operator in distributed Memgraph that yields both local and remote (from other workers) frames. Obtaining remote frames is done through RPC calls to @@ -2424,14 +2358,14 @@ Logic of the synchronize operator is: :capnp-save #'save-operator-pointer :capnp-load #'load-operator-pointer) (left-symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol")) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol")) (right-op "std::shared_ptr<LogicalOperator>" :reader t :capnp-save #'save-operator-pointer :capnp-load #'load-operator-pointer) (right-symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol"))) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol"))) (:documentation "Operator for producing a Cartesian product from 2 input branches") (:public @@ -2464,8 +2398,8 @@ Logic of the synchronize operator is: :capnp-load #'load-operator-pointer) (plan-id :int64_t :initval 0 :reader t) (symbols "std::vector<Symbol>" :reader t - :capnp-save (capnp-save-vector "::query::capnp::Symbol" "Symbol") - :capnp-load (capnp-load-vector "::query::capnp::Symbol" "Symbol")) + :capnp-save (lcp:capnp-save-vector "::query::capnp::Symbol" "Symbol") + :capnp-load (lcp:capnp-load-vector "::query::capnp::Symbol" "Symbol")) (order-by "std::vector<Expression *>" :reader t :capnp-type "List(Ast.Tree)" :capnp-save (save-ast-vector "Expression *") diff --git a/src/stats/stats_rpc_messages.hpp b/src/stats/stats_rpc_messages.hpp deleted file mode 100644 index b5106097c..000000000 --- a/src/stats/stats_rpc_messages.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" -#include "boost/serialization/string.hpp" -#include "boost/serialization/utility.hpp" -#include "boost/serialization/vector.hpp" - -#include "communication/rpc/messages.hpp" -#include "utils/timestamp.hpp" - -namespace stats { - -struct StatsReq : public communication::rpc::Message { - StatsReq() {} - StatsReq(std::string metric_path, - std::vector<std::pair<std::string, std::string>> tags, double value) - : metric_path(metric_path), - tags(tags), - value(value), - timestamp(utils::Timestamp::Now().SecSinceTheEpoch()) {} - - std::string metric_path; - std::vector<std::pair<std::string, std::string>> tags; - double value; - uint64_t timestamp; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &metric_path &tags &value ×tamp; - } -}; - -RPC_NO_MEMBER_MESSAGE(StatsRes); - -struct BatchStatsReq : public communication::rpc::Message { - BatchStatsReq() {} - explicit BatchStatsReq(std::vector<StatsReq> requests) : requests(requests) {} - - std::vector<StatsReq> requests; - - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<communication::rpc::Message>(*this); - ar &requests; - } -}; - -RPC_NO_MEMBER_MESSAGE(BatchStatsRes); - -using StatsRpc = communication::rpc::RequestResponse<StatsReq, StatsRes>; -using BatchStatsRpc = - communication::rpc::RequestResponse<BatchStatsReq, BatchStatsRes>; - -} // namespace stats diff --git a/src/stats/stats_rpc_messages.lcp b/src/stats/stats_rpc_messages.lcp new file mode 100644 index 000000000..87e2fcac4 --- /dev/null +++ b/src/stats/stats_rpc_messages.lcp @@ -0,0 +1,50 @@ +#>cpp +#pragma once + +#include "communication/rpc/messages.hpp" +#include "stats/stats_rpc_messages.capnp.h" +#include "utils/serialization.hpp" +#include "utils/timestamp.hpp" +cpp<# + +(lcp:namespace stats) + +(lcp:capnp-namespace "stats") + +(lcp:capnp-import 'utils "/utils/serialization.capnp") + +(lcp:define-rpc stats + (:request + ((metric-path "std::string") + (tags "std::vector<std::pair<std::string, std::string>>" + :capnp-type "List(Utils.Pair(Text, Text))" + :capnp-save + (lcp:capnp-save-vector + "utils::capnp::Pair<::capnp::Text, ::capnp::Text>" + "std::pair<std::string, std::string>" + "[](auto *builder, const auto &pair) { + builder->setFirst(pair.first); + builder->setSecond(pair.second); + }") + :capnp-load + (lcp:capnp-load-vector + "utils::capnp::Pair<::capnp::Text, ::capnp::Text>" + "std::pair<std::string, std::string>" + "[](const auto &reader) { + std::string first = reader.getFirst(); + std::string second = reader.getSecond(); + return std::make_pair(first, second); + }")) + (value :double) + (timestamp :uint64_t :initarg nil))) + (:response ())) + +(lcp:define-rpc batch-stats + (:request + ((requests "std::vector<StatsReq>" + :capnp-type "List(StatsReq)" + :capnp-save (lcp:capnp-save-vector "capnp::StatsReq" "StatsReq") + :capnp-load (lcp:capnp-load-vector "capnp::StatsReq" "StatsReq")))) + (:response ())) + +(lcp:pop-namespace) ;; stats diff --git a/src/storage/address.hpp b/src/storage/address.hpp index 93e83dcba..ff6d7bd8a 100644 --- a/src/storage/address.hpp +++ b/src/storage/address.hpp @@ -2,9 +2,9 @@ #include <cstdint> -#include "boost/serialization/access.hpp" #include "glog/logging.h" +#include "storage/serialization.capnp.h" #include "storage/gid.hpp" namespace storage { @@ -89,13 +89,16 @@ class Address { return storage_ == other.storage_; } + void Save(capnp::Address::Builder *builder) const { + builder->setStorage(storage_); + } + + void Load(const capnp::Address::Reader &reader) { + storage_ = reader.getStorage(); + } + private: StorageT storage_{0}; - - friend class boost::serialization::access; - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &storage_; - } }; + } // namespace storage diff --git a/src/storage/concurrent_id_mapper_master.cpp b/src/storage/concurrent_id_mapper_master.cpp index d8df45f5a..aa4799581 100644 --- a/src/storage/concurrent_id_mapper_master.cpp +++ b/src/storage/concurrent_id_mapper_master.cpp @@ -10,16 +10,24 @@ namespace { template <typename TId> void RegisterRpc(MasterConcurrentIdMapper<TId> &mapper, communication::rpc::Server &rpc_server); -#define ID_VALUE_RPC_CALLS(type) \ - template <> \ - void RegisterRpc<type>(MasterConcurrentIdMapper<type> & mapper, \ - communication::rpc::Server & rpc_server) { \ - rpc_server.Register<type##IdRpc>([&mapper](const type##IdReq &req) { \ - return std::make_unique<type##IdRes>(mapper.value_to_id(req.member)); \ - }); \ - rpc_server.Register<Id##type##Rpc>([&mapper](const Id##type##Req &req) { \ - return std::make_unique<Id##type##Res>(mapper.id_to_value(req.member)); \ - }); \ +#define ID_VALUE_RPC_CALLS(type) \ + template <> \ + void RegisterRpc<type>(MasterConcurrentIdMapper<type> & mapper, \ + communication::rpc::Server & rpc_server) { \ + rpc_server.Register<type##IdRpc>( \ + [&mapper](const auto &req_reader, auto *res_builder) { \ + type##IdReq req; \ + req.Load(req_reader); \ + type##IdRes res(mapper.value_to_id(req.member)); \ + res.Save(res_builder); \ + }); \ + rpc_server.Register<Id##type##Rpc>( \ + [&mapper](const auto &req_reader, auto *res_builder) { \ + Id##type##Req req; \ + req.Load(req_reader); \ + Id##type##Res res(mapper.id_to_value(req.member)); \ + res.Save(res_builder); \ + }); \ } using namespace storage; diff --git a/src/storage/concurrent_id_mapper_rpc_messages.hpp b/src/storage/concurrent_id_mapper_rpc_messages.hpp deleted file mode 100644 index 06e1d7f87..000000000 --- a/src/storage/concurrent_id_mapper_rpc_messages.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include <chrono> - -#include "communication/rpc/messages.hpp" -#include "storage/types.hpp" -#include "transactions/commit_log.hpp" -#include "transactions/snapshot.hpp" -#include "transactions/type.hpp" - -namespace storage { - -#define ID_VALUE_RPC(type) \ - RPC_SINGLE_MEMBER_MESSAGE(type##IdReq, std::string); \ - RPC_SINGLE_MEMBER_MESSAGE(type##IdRes, storage::type); \ - using type##IdRpc = \ - communication::rpc::RequestResponse<type##IdReq, type##IdRes>; \ - RPC_SINGLE_MEMBER_MESSAGE(Id##type##Req, storage::type); \ - RPC_SINGLE_MEMBER_MESSAGE(Id##type##Res, std::string); \ - using Id##type##Rpc = \ - communication::rpc::RequestResponse<Id##type##Req, Id##type##Res>; - -ID_VALUE_RPC(Label) -ID_VALUE_RPC(EdgeType) -ID_VALUE_RPC(Property) - -#undef ID_VALUE_RPC - -} // namespace storage diff --git a/src/storage/concurrent_id_mapper_rpc_messages.lcp b/src/storage/concurrent_id_mapper_rpc_messages.lcp new file mode 100644 index 000000000..439cf3d8d --- /dev/null +++ b/src/storage/concurrent_id_mapper_rpc_messages.lcp @@ -0,0 +1,44 @@ +#>cpp +#pragma once + +#include <chrono> + +#include "communication/rpc/messages.hpp" +#include "storage/concurrent_id_mapper_rpc_messages.capnp.h" +#include "storage/types.hpp" +#include "transactions/commit_log.hpp" +#include "transactions/snapshot.hpp" +#include "transactions/type.hpp" +cpp<# + +(lcp:namespace storage) + +(lcp:capnp-namespace "storage") + +(lcp:capnp-import 's "/storage/serialization.capnp") + +(lcp:define-rpc label-id + (:request ((member "std::string"))) + (:response ((member "Label" :capnp-type "S.Common")))) + +(lcp:define-rpc id-label + (:request ((member "Label" :capnp-type "S.Common"))) + (:response ((member "std::string")))) + +(lcp:define-rpc edge-type-id + (:request ((member "std::string"))) + (:response ((member "EdgeType" :capnp-type "S.Common")))) + +(lcp:define-rpc id-edge-type + (:request ((member "EdgeType" :capnp-type "S.Common"))) + (:response ((member "std::string")))) + +(lcp:define-rpc property-id + (:request ((member "std::string"))) + (:response ((member "Property" :capnp-type "S.Common")))) + +(lcp:define-rpc id-property + (:request ((member "Property" :capnp-type "S.Common"))) + (:response ((member "std::string")))) + +(lcp:pop-namespace) diff --git a/src/storage/types.capnp b/src/storage/serialization.capnp similarity index 86% rename from src/storage/types.capnp rename to src/storage/serialization.capnp index 08b81673c..8140fe015 100644 --- a/src/storage/types.capnp +++ b/src/storage/serialization.capnp @@ -15,3 +15,7 @@ struct Common { struct Label {} struct EdgeType {} struct Property {} + +struct Address { + storage @0 :UInt64; +} diff --git a/src/storage/types.hpp b/src/storage/types.hpp index 6121a43ce..b7801fc00 100644 --- a/src/storage/types.hpp +++ b/src/storage/types.hpp @@ -6,8 +6,8 @@ #include "boost/serialization/base_object.hpp" #include "glog/logging.h" -#include "types.capnp.h" +#include "storage/serialization.capnp.h" #include "utils/total_ordering.hpp" namespace storage { diff --git a/src/transactions/commit_log.hpp b/src/transactions/commit_log.hpp index e446f59a0..6081cc2f7 100644 --- a/src/transactions/commit_log.hpp +++ b/src/transactions/commit_log.hpp @@ -1,9 +1,8 @@ #pragma once -#include "boost/serialization/access.hpp" - #include "data_structures/bitset/dynamic_bitset.hpp" -#include "type.hpp" +#include "transactions/common.capnp.h" +#include "transactions/type.hpp" namespace tx { @@ -57,14 +56,15 @@ class CommitLog { operator uint8_t() const { return flags_; } - private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &flags_; + void Save(capnp::CommitLogInfo::Builder *builder) const { + builder->setFlags(flags_); } + void Load(const capnp::CommitLogInfo::Reader &reader) { + flags_ = reader.getFlags(); + } + + private: uint8_t flags_{0}; }; diff --git a/src/transactions/common.capnp b/src/transactions/common.capnp new file mode 100644 index 000000000..f9999efe4 --- /dev/null +++ b/src/transactions/common.capnp @@ -0,0 +1,12 @@ +@0xcdbe169866471033; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("tx::capnp"); + +struct Snapshot { + transactionIds @0 :List(UInt64); +} + +struct CommitLogInfo { + flags @0 :UInt8; +} diff --git a/src/transactions/engine.hpp b/src/transactions/engine.hpp index 04cb93ba3..00888a154 100644 --- a/src/transactions/engine.hpp +++ b/src/transactions/engine.hpp @@ -6,6 +6,7 @@ #include "data_structures/concurrent/concurrent_map.hpp" #include "transactions/commit_log.hpp" +#include "transactions/snapshot.hpp" #include "transactions/transaction.hpp" #include "transactions/type.hpp" diff --git a/src/transactions/engine_master.cpp b/src/transactions/engine_master.cpp index 5f984b486..6a081b893 100644 --- a/src/transactions/engine_master.cpp +++ b/src/transactions/engine_master.cpp @@ -15,61 +15,74 @@ MasterEngine::MasterEngine(communication::rpc::Server &server, : SingleNodeEngine(wal), rpc_server_(server), ongoing_produce_joiner_(rpc_worker_clients) { - rpc_server_.Register<BeginRpc>([this](const BeginReq &) { - auto tx = Begin(); - return std::make_unique<BeginRes>(TxAndSnapshot{tx->id_, tx->snapshot()}); - }); - - rpc_server_.Register<AdvanceRpc>([this](const AdvanceReq &req) { - return std::make_unique<AdvanceRes>(Advance(req.member)); - }); - - rpc_server_.Register<CommitRpc>([this](const CommitReq &req) { - Commit(*RunningTransaction(req.member)); - return std::make_unique<CommitRes>(); - }); - - rpc_server_.Register<AbortRpc>([this](const AbortReq &req) { - Abort(*RunningTransaction(req.member)); - return std::make_unique<AbortRes>(); - }); - - rpc_server_.Register<SnapshotRpc>([this](const SnapshotReq &req) { - // It is guaranteed that the Worker will not be requesting this for a - // transaction that's done, and that there are no race conditions here. - return std::make_unique<SnapshotRes>( - RunningTransaction(req.member)->snapshot()); - }); - - rpc_server_.Register<CommandRpc>([this](const CommandReq &req) { - // It is guaranteed that the Worker will not be requesting this for a - // transaction that's done, and that there are no race conditions here. - return std::make_unique<CommandRes>(RunningTransaction(req.member)->cid()); - }); - - rpc_server_.Register<GcSnapshotRpc>( - [this](const communication::rpc::Message &) { - return std::make_unique<SnapshotRes>(GlobalGcSnapshot()); + rpc_server_.Register<BeginRpc>( + [this](const auto &req_reader, auto *res_builder) { + auto tx = this->Begin(); + BeginRes res(TxAndSnapshot{tx->id_, tx->snapshot()}); + res.Save(res_builder); }); - rpc_server_.Register<ClogInfoRpc>([this](const ClogInfoReq &req) { - return std::make_unique<ClogInfoRes>(Info(req.member)); - }); + rpc_server_.Register<AdvanceRpc>( + [this](const auto &req_reader, auto *res_builder) { + AdvanceRes res(this->Advance(req_reader.getMember())); + res.Save(res_builder); + }); + + rpc_server_.Register<CommitRpc>( + [this](const auto &req_reader, auto *res_builder) { + this->Commit(*this->RunningTransaction(req_reader.getMember())); + }); + + rpc_server_.Register<AbortRpc>( + [this](const auto &req_reader, auto *res_builder) { + this->Abort(*this->RunningTransaction(req_reader.getMember())); + }); + + rpc_server_.Register<SnapshotRpc>( + [this](const auto &req_reader, auto *res_builder) { + // It is guaranteed that the Worker will not be requesting this for a + // transaction that's done, and that there are no race conditions here. + SnapshotRes res( + this->RunningTransaction(req_reader.getMember())->snapshot()); + res.Save(res_builder); + }); + + rpc_server_.Register<CommandRpc>( + [this](const auto &req_reader, auto *res_builder) { + // It is guaranteed that the Worker will not be requesting this for a + // transaction that's done, and that there are no race conditions here. + CommandRes res(this->RunningTransaction(req_reader.getMember())->cid()); + res.Save(res_builder); + }); + + rpc_server_.Register<GcSnapshotRpc>( + [this](const auto &req_reader, auto *res_builder) { + GcSnapshotRes res(this->GlobalGcSnapshot()); + res.Save(res_builder); + }); + + rpc_server_.Register<ClogInfoRpc>( + [this](const auto &req_reader, auto *res_builder) { + ClogInfoRes res(this->Info(req_reader.getMember())); + res.Save(res_builder); + }); rpc_server_.Register<ActiveTransactionsRpc>( - [this](const communication::rpc::Message &) { - return std::make_unique<SnapshotRes>(GlobalActiveTransactions()); + [this](const auto &req_reader, auto *res_builder) { + ActiveTransactionsRes res(this->GlobalActiveTransactions()); + res.Save(res_builder); }); rpc_server_.Register<EnsureNextIdGreaterRpc>( - [this](const EnsureNextIdGreaterReq &req) { - EnsureNextIdGreater(req.member); - return std::make_unique<EnsureNextIdGreaterRes>(); + [this](const auto &req_reader, auto *res_builder) { + this->EnsureNextIdGreater(req_reader.getMember()); }); - rpc_server_.Register<GlobalLastRpc>([this](const GlobalLastReq &) { - return std::make_unique<GlobalLastRes>(GlobalLast()); - }); + rpc_server_.Register<GlobalLastRpc>( + [this](const auto &req_reader, auto *res_builder) { + GlobalLastRes res(this->GlobalLast()); + res.Save(res_builder); + }); } void MasterEngine::Commit(const Transaction &t) { diff --git a/src/transactions/engine_rpc_messages.hpp b/src/transactions/engine_rpc_messages.hpp deleted file mode 100644 index 9f948813c..000000000 --- a/src/transactions/engine_rpc_messages.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include "communication/rpc/messages.hpp" -#include "transactions/commit_log.hpp" -#include "transactions/snapshot.hpp" -#include "transactions/type.hpp" - -namespace tx { - -RPC_NO_MEMBER_MESSAGE(BeginReq); -struct TxAndSnapshot { - TransactionId tx_id; - Snapshot snapshot; - - private: - friend class boost::serialization::access; - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &tx_id; - ar &snapshot; - } -}; -RPC_SINGLE_MEMBER_MESSAGE(BeginRes, TxAndSnapshot); -using BeginRpc = communication::rpc::RequestResponse<BeginReq, BeginRes>; - -RPC_SINGLE_MEMBER_MESSAGE(AdvanceReq, TransactionId); -RPC_SINGLE_MEMBER_MESSAGE(AdvanceRes, CommandId); -using AdvanceRpc = communication::rpc::RequestResponse<AdvanceReq, AdvanceRes>; - -RPC_SINGLE_MEMBER_MESSAGE(CommitReq, TransactionId); -RPC_NO_MEMBER_MESSAGE(CommitRes); -using CommitRpc = communication::rpc::RequestResponse<CommitReq, CommitRes>; - -RPC_SINGLE_MEMBER_MESSAGE(AbortReq, TransactionId); -RPC_NO_MEMBER_MESSAGE(AbortRes); -using AbortRpc = communication::rpc::RequestResponse<AbortReq, AbortRes>; - -RPC_SINGLE_MEMBER_MESSAGE(SnapshotReq, TransactionId); -RPC_SINGLE_MEMBER_MESSAGE(SnapshotRes, Snapshot); -using SnapshotRpc = - communication::rpc::RequestResponse<SnapshotReq, SnapshotRes>; - -RPC_SINGLE_MEMBER_MESSAGE(CommandReq, TransactionId); -RPC_SINGLE_MEMBER_MESSAGE(CommandRes, CommandId); -using CommandRpc = communication::rpc::RequestResponse<CommandReq, CommandRes>; - -RPC_NO_MEMBER_MESSAGE(GcSnapshotReq); -using GcSnapshotRpc = - communication::rpc::RequestResponse<GcSnapshotReq, SnapshotRes>; - -RPC_SINGLE_MEMBER_MESSAGE(ClogInfoReq, TransactionId); -RPC_SINGLE_MEMBER_MESSAGE(ClogInfoRes, CommitLog::Info); -using ClogInfoRpc = - communication::rpc::RequestResponse<ClogInfoReq, ClogInfoRes>; - -RPC_NO_MEMBER_MESSAGE(ActiveTransactionsReq); -using ActiveTransactionsRpc = - communication::rpc::RequestResponse<ActiveTransactionsReq, SnapshotRes>; - -RPC_SINGLE_MEMBER_MESSAGE(EnsureNextIdGreaterReq, TransactionId); -RPC_NO_MEMBER_MESSAGE(EnsureNextIdGreaterRes); -using EnsureNextIdGreaterRpc = - communication::rpc::RequestResponse<EnsureNextIdGreaterReq, - EnsureNextIdGreaterRes>; - -RPC_NO_MEMBER_MESSAGE(GlobalLastReq); -RPC_SINGLE_MEMBER_MESSAGE(GlobalLastRes, TransactionId); -using GlobalLastRpc = - communication::rpc::RequestResponse<GlobalLastReq, GlobalLastRes>; -} // namespace tx diff --git a/src/transactions/engine_rpc_messages.lcp b/src/transactions/engine_rpc_messages.lcp new file mode 100644 index 000000000..839ad6c57 --- /dev/null +++ b/src/transactions/engine_rpc_messages.lcp @@ -0,0 +1,69 @@ +#>cpp +#pragma once + +#include "communication/rpc/messages.hpp" +#include "transactions/commit_log.hpp" +#include "transactions/engine_rpc_messages.capnp.h" +#include "transactions/snapshot.hpp" +#include "transactions/type.hpp" +cpp<# + +(lcp:namespace tx) + +(lcp:capnp-namespace "tx") + +(lcp:capnp-import 'tx "/transactions/common.capnp") +(lcp:capnp-type-conversion "TransactionId" "UInt64") +(lcp:capnp-type-conversion "CommandId" "UInt32") +(lcp:capnp-type-conversion "Snapshot" "Tx.Snapshot") + +(lcp:define-struct tx-and-snapshot () + ((tx-id "TransactionId") + (snapshot "Snapshot")) + (:serialize :capnp)) + +(lcp:define-rpc begin + (:request ()) + (:response ((member "TxAndSnapshot")))) + +(lcp:define-rpc advance + (:request ((member "TransactionId"))) + (:response ((member "CommandId")))) + +(lcp:define-rpc commit + (:request ((member "TransactionId"))) + (:response ())) + +(lcp:define-rpc abort + (:request ((member "TransactionId"))) + (:response ())) + +(lcp:define-rpc snapshot + (:request ((member "TransactionId"))) + (:response ((member "Snapshot")))) + +(lcp:define-rpc command + (:request ((member "TransactionId"))) + (:response ((member "CommandId")))) + +(lcp:define-rpc gc-snapshot + (:request ()) + (:response ((member "Snapshot")))) + +(lcp:define-rpc clog-info + (:request ((member "TransactionId"))) + (:response ((member "CommitLog::Info" :capnp-type "Tx.CommitLogInfo")))) + +(lcp:define-rpc active-transactions + (:request ()) + (:response ((member "Snapshot")))) + +(lcp:define-rpc ensure-next-id-greater + (:request ((member "TransactionId"))) + (:response ())) + +(lcp:define-rpc global-last + (:request ()) + (:response ((member "TransactionId")))) + +(lcp:pop-namespace) ;; tx diff --git a/src/transactions/snapshot.cpp b/src/transactions/snapshot.cpp new file mode 100644 index 000000000..134259566 --- /dev/null +++ b/src/transactions/snapshot.cpp @@ -0,0 +1,16 @@ +#include "transactions/snapshot.hpp" + +#include "utils/serialization.hpp" + +namespace tx { + +void Snapshot::Save(capnp::Snapshot::Builder *builder) const { + auto list_builder = builder->initTransactionIds(transaction_ids_.size()); + utils::SaveVector(transaction_ids_, &list_builder); +} + +void Snapshot::Load(const capnp::Snapshot::Reader &reader) { + utils::LoadVector(&transaction_ids_, reader.getTransactionIds()); +} + +} // namespace tx diff --git a/src/transactions/snapshot.hpp b/src/transactions/snapshot.hpp index 3cc1ca0d6..bb2549282 100644 --- a/src/transactions/snapshot.hpp +++ b/src/transactions/snapshot.hpp @@ -4,10 +4,8 @@ #include <iostream> #include <vector> -#include "boost/serialization/access.hpp" -#include "boost/serialization/vector.hpp" - #include "glog/logging.h" +#include "transactions/common.capnp.h" #include "transactions/type.hpp" #include "utils/algorithm.hpp" @@ -86,14 +84,11 @@ class Snapshot { return stream; } + void Save(capnp::Snapshot::Builder *builder) const; + void Load(const capnp::Snapshot::Reader &reader); + private: - friend class boost::serialization::access; - - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &transaction_ids_; - } - std::vector<TransactionId> transaction_ids_; }; + } // namespace tx diff --git a/src/utils/serialization.capnp b/src/utils/serialization.capnp index 8dfb73cd2..a25111477 100644 --- a/src/utils/serialization.capnp +++ b/src/utils/serialization.capnp @@ -1,10 +1,15 @@ @0xe7647d63b36c2c65; using Cxx = import "/capnp/c++.capnp"; + $Cxx.namespace("utils::capnp"); +# Primitive type wrappers + +struct BoxInt16 { + value @0 :Int16; +} -# Primitive types wrappers. struct BoxInt32 { value @0 :Int32; } @@ -13,12 +18,16 @@ struct BoxInt64 { value @0 :Int64; } +struct BoxUInt16 { + value @0 :UInt16; +} + struct BoxUInt32 { value @0 :UInt32; } struct BoxUInt64 { - value @0 :UInt32; + value @0 :UInt64; } struct BoxFloat32 { @@ -33,8 +42,7 @@ struct BoxBool { value @0 :Bool; } - -# CPP Types. +# C++ STL types struct Optional(T) { union { @@ -62,28 +70,22 @@ struct SharedPtr(T) { } } -# Our types - -struct TypedValue { - union { - nullType @0 :Void; - bool @1 :Bool; - integer @2 :Int64; - double @3 :Float64; - string @4 :Text; - list @5 :List(TypedValue); - map @6 :List(Entry); - # TODO vertex accessor - # TODO edge accessor - # TODO path - } +struct Map(K, V) { + entries @0 :List(Entry); struct Entry { - key @0 :Text; - value @1 :TypedValue; + key @0 :K; + value @1 :V; } } +struct Pair(First, Second) { + first @0 :First; + second @1 :Second; +} + +# Our types + struct Bound(T) { type @0 :Type; value @1 :T; diff --git a/src/utils/serialization.hpp b/src/utils/serialization.hpp index fb8d51e02..aead65a8b 100644 --- a/src/utils/serialization.hpp +++ b/src/utils/serialization.hpp @@ -2,12 +2,15 @@ #include <experimental/optional> +#include "boost/serialization/optional.hpp" +#include "boost/serialization/serialization.hpp" #include "boost/serialization/split_free.hpp" + +#include "distributed/serialization.capnp.h" #include "query/typed_value.hpp" #include "storage/edge.hpp" #include "storage/vertex.hpp" #include "utils/exceptions.hpp" - #include "utils/serialization.capnp.h" namespace boost::serialization { @@ -63,64 +66,114 @@ void load(TArchive &ar, std::experimental::optional<T> &opt, namespace utils { -inline void SaveCapnpTypedValue(const query::TypedValue &value, - capnp::TypedValue::Builder &builder) { +inline void SaveCapnpTypedValue( + const query::TypedValue &value, + distributed::capnp::TypedValue::Builder *builder, + std::function<void(const query::TypedValue &, + distributed::capnp::TypedValue::Builder *)> + save_graph_element = nullptr) { switch (value.type()) { case query::TypedValue::Type::Null: - builder.setNullType(); + builder->setNullType(); return; case query::TypedValue::Type::Bool: - builder.setBool(value.Value<bool>()); + builder->setBool(value.Value<bool>()); return; case query::TypedValue::Type::Int: - builder.setInteger(value.Value<int64_t>()); + builder->setInteger(value.Value<int64_t>()); return; case query::TypedValue::Type::Double: - builder.setDouble(value.Value<double>()); + builder->setDouble(value.Value<double>()); return; case query::TypedValue::Type::String: - builder.setString(value.Value<std::string>()); + builder->setString(value.Value<std::string>()); return; - case query::TypedValue::Type::List: - case query::TypedValue::Type::Map: + case query::TypedValue::Type::List: { + const auto &values = value.Value<std::vector<query::TypedValue>>(); + auto list_builder = builder->initList(values.size()); + for (size_t i = 0; i < values.size(); ++i) { + auto value_builder = list_builder[i]; + SaveCapnpTypedValue(values[i], &value_builder, save_graph_element); + } + return; + } + case query::TypedValue::Type::Map: { + const auto &map = value.Value<std::map<std::string, query::TypedValue>>(); + auto map_builder = builder->initMap(map.size()); + size_t i = 0; + for (const auto &kv : map) { + auto kv_builder = map_builder[i]; + kv_builder.setKey(kv.first); + auto value_builder = kv_builder.initValue(); + SaveCapnpTypedValue(kv.second, &value_builder, save_graph_element); + ++i; + } + return; + } case query::TypedValue::Type::Vertex: case query::TypedValue::Type::Edge: case query::TypedValue::Type::Path: - throw utils::NotYetImplemented("Capnp serialize typed value"); + if (save_graph_element) { + save_graph_element(value, builder); + } else { + throw utils::BasicException( + "Unable to serialize TypedValue of type: {}", value.type()); + } } } -inline void LoadCapnpTypedValue(query::TypedValue &value, - capnp::TypedValue::Reader &reader) { +inline void LoadCapnpTypedValue( + const distributed::capnp::TypedValue::Reader &reader, + query::TypedValue *value, + std::function<void(const distributed::capnp::TypedValue::Reader &, + query::TypedValue *)> + load_graph_element = nullptr) { switch (reader.which()) { - case capnp::TypedValue::BOOL: - value = reader.getBool(); + case distributed::capnp::TypedValue::NULL_TYPE: + *value = query::TypedValue::Null; return; - case capnp::TypedValue::DOUBLE: - value = reader.getDouble(); + case distributed::capnp::TypedValue::BOOL: + *value = reader.getBool(); return; - // case capnp::TypedValue::EDGE: - // // TODO - // return; - case capnp::TypedValue::INTEGER: - value = reader.getInteger(); + case distributed::capnp::TypedValue::INTEGER: + *value = reader.getInteger(); return; - case capnp::TypedValue::LIST: - throw utils::NotYetImplemented("Capnp deserialize typed value"); - case capnp::TypedValue::MAP: - throw utils::NotYetImplemented("Capnp deserialize typed value"); - case capnp::TypedValue::NULL_TYPE: - value = query::TypedValue::Null; + case distributed::capnp::TypedValue::DOUBLE: + *value = reader.getDouble(); return; - // case query::capnp::TypedValue::PATH: - // // TODO - // return; - case capnp::TypedValue::STRING: - value = reader.getString().cStr(); + case distributed::capnp::TypedValue::STRING: + *value = reader.getString().cStr(); return; - // case query::capnp::TypedValue::VERTEX: - // // TODO - // return; + case distributed::capnp::TypedValue::LIST: { + std::vector<query::TypedValue> list; + list.reserve(reader.getList().size()); + for (const auto &value_reader : reader.getList()) { + list.emplace_back(); + LoadCapnpTypedValue(value_reader, &list.back(), load_graph_element); + } + *value = list; + return; + } + case distributed::capnp::TypedValue::MAP: { + std::map<std::string, query::TypedValue> map; + for (const auto &kv_reader : reader.getMap()) { + auto key = kv_reader.getKey().cStr(); + LoadCapnpTypedValue(kv_reader.getValue(), &map[key], + load_graph_element); + } + *value = map; + return; + } + case distributed::capnp::TypedValue::VERTEX: + case distributed::capnp::TypedValue::EDGE: + case distributed::capnp::TypedValue::PATH: + if (load_graph_element) { + load_graph_element(reader, value); + } else { + throw utils::BasicException( + "Unexpected TypedValue type '{}' when loading from archive", + reader.which()); + } } } @@ -161,6 +214,34 @@ inline void LoadVector( } } +template <class TCapnpKey, class TCapnpValue, class TMap> +void SaveMap(const TMap &map, + typename capnp::Map<TCapnpKey, TCapnpValue>::Builder *map_builder, + std::function<void( + typename capnp::Map<TCapnpKey, TCapnpValue>::Entry::Builder *, + const typename TMap::value_type &)> + save) { + auto entries_builder = map_builder->initEntries(map.size()); + size_t i = 0; + for (const auto &entry : map) { + auto entry_builder = entries_builder[i]; + save(&entry_builder, entry); + ++i; + } +} + +template <class TCapnpKey, class TCapnpValue, class TMap> +void LoadMap( + TMap *map, + const typename capnp::Map<TCapnpKey, TCapnpValue>::Reader &map_reader, + std::function<typename TMap::value_type( + const typename capnp::Map<TCapnpKey, TCapnpValue>::Entry::Reader &)> + load) { + for (const auto &entry_reader : map_reader.getEntries()) { + map->insert(load(entry_reader)); + } +} + template <typename TCapnp, typename T> inline void SaveOptional( const std::experimental::optional<T> &data, diff --git a/src/utils/typed_value.capnp b/src/utils/typed_value.capnp deleted file mode 100644 index cc977cd48..000000000 --- a/src/utils/typed_value.capnp +++ /dev/null @@ -1,25 +0,0 @@ -@0xd229a9c0f7e55750; - -using Cxx = import "/capnp/c++.capnp"; -$Cxx.namespace("query::capnp"); - - -struct TypedValue { - union { - nullType @0 :Void; - bool @1 :Bool; - integer @2 :Int64; - double @3 :Float64; - string @4 :Text; - list @5 :List(TypedValue); - map @6 :List(Entry); - # TODO vertex accessor - # TODO edge accessor - # TODO path - } - - struct Entry { - key @0 :Text; - value @1 :TypedValue; - } -} diff --git a/tests/distributed/raft/example_client.cpp b/tests/distributed/raft/example_client.cpp index b9afeeb5e..a6f547918 100644 --- a/tests/distributed/raft/example_client.cpp +++ b/tests/distributed/raft/example_client.cpp @@ -34,14 +34,15 @@ int main(int argc, char **argv) { // in correct order. for (int i = 1; i <= 100; ++i) { LOG(INFO) << fmt::format("Apennding value: {}", i); - auto result_tuple = client.Call<AppendEntry>(i); - if (!result_tuple) { - LOG(INFO) << "Request unsuccessful"; - // Try to resend value - --i; - } else { - LOG(INFO) << fmt::format("Appended value: {}", i); - } + // TODO: Serialize RPC via Cap'n Proto + // auto result_tuple = client.Call<AppendEntry>(i); + // if (!result_tuple) { + // LOG(INFO) << "Request unsuccessful"; + // // Try to resend value + // --i; + // } else { + // LOG(INFO) << fmt::format("Appended value: {}", i); + // } } return 0; diff --git a/tests/distributed/raft/example_server.cpp b/tests/distributed/raft/example_server.cpp index 72c4b18c6..1ec3cd00f 100644 --- a/tests/distributed/raft/example_server.cpp +++ b/tests/distributed/raft/example_server.cpp @@ -56,13 +56,17 @@ int main(int argc, char **argv) { << "Unable to register SIGINT handler!"; // Example callback. - server.Register<AppendEntry>([&log](const AppendEntryReq &request) { - log << request.val << std::endl; - log.flush(); - LOG(INFO) << fmt::format("AppendEntry: {}", request.val); - return std::make_unique<AppendEntryRes>(200, FLAGS_interface, - stol(FLAGS_port)); - }); + // TODO: Serialize RPC via Cap'n Proto + // server.Register<AppendEntry>( + // [&log](const auto &req_reader, auto *res_builder) { + // AppendEntryReq request; + // request.Load(req_reader); + // log << request.val << std::endl; + // log.flush(); + // LOG(INFO) << fmt::format("AppendEntry: {}", request.val); + // AppendEntryRes res(200, FLAGS_interface, stol(FLAGS_port)); + // res.Save(res_builder); + // }); LOG(INFO) << "Raft RPC server started"; // Sleep until shutdown detected. diff --git a/tests/distributed/raft/messages.hpp b/tests/distributed/raft/messages.hpp index 5cb33f3f3..e79f5e68a 100644 --- a/tests/distributed/raft/messages.hpp +++ b/tests/distributed/raft/messages.hpp @@ -1,30 +1,14 @@ -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/base_object.hpp" - -#include "boost/serialization/export.hpp" #include "communication/rpc/messages.hpp" -using boost::serialization::base_object; -using communication::rpc::Message; using namespace communication::rpc; -struct AppendEntryReq : public Message { +struct AppendEntryReq { AppendEntryReq() {} explicit AppendEntryReq(int val) : val(val) {} int val; - - private: - friend class boost::serialization::access; - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &base_object<Message>(*this); - ar &val; - } }; -BOOST_CLASS_EXPORT(AppendEntryReq); -struct AppendEntryRes : public Message { +struct AppendEntryRes { AppendEntryRes() {} AppendEntryRes(int status, std::string interface, uint16_t port) : status(status), interface(interface), port(port) {} @@ -32,16 +16,6 @@ struct AppendEntryRes : public Message { std::string interface; uint16_t port; - private: - friend class boost::serialization::access; - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &base_object<Message>(*this); - ar &status; - ar &interface; - ar &port; - } }; -BOOST_CLASS_EXPORT(AppendEntryRes); using AppendEntry = RequestResponse<AppendEntryReq, AppendEntryRes>; diff --git a/tests/macro_benchmark/clients/card_fraud_client.cpp b/tests/macro_benchmark/clients/card_fraud_client.cpp index 3a70a64c9..08ba98cb3 100644 --- a/tests/macro_benchmark/clients/card_fraud_client.cpp +++ b/tests/macro_benchmark/clients/card_fraud_client.cpp @@ -11,15 +11,6 @@ #include "long_running_common.hpp" -// TODO(mtomic): this sucks but I don't know a different way to make it work -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/export.hpp" -BOOST_CLASS_EXPORT(stats::StatsReq); -BOOST_CLASS_EXPORT(stats::StatsRes); -BOOST_CLASS_EXPORT(stats::BatchStatsReq); -BOOST_CLASS_EXPORT(stats::BatchStatsRes); - std::atomic<int64_t> num_pos; std::atomic<int64_t> num_cards; std::atomic<int64_t> num_transactions; diff --git a/tests/manual/raft_rpc.cpp b/tests/manual/raft_rpc.cpp index c423acd8c..428b3bceb 100644 --- a/tests/manual/raft_rpc.cpp +++ b/tests/manual/raft_rpc.cpp @@ -1,11 +1,3 @@ -#include "boost/serialization/export.hpp" - -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/archive/text_iarchive.hpp" -#include "boost/archive/text_oarchive.hpp" -#include "boost/serialization/export.hpp" - #include "communication/raft/rpc.hpp" #include "communication/raft/storage/file.hpp" #include "communication/raft/test_utils.hpp" @@ -22,9 +14,6 @@ using raft::test_utils::DummyState; DEFINE_string(member_id, "", "id of Raft member"); DEFINE_string(log_dir, "", "Raft log directory"); -BOOST_CLASS_EXPORT(raft::PeerRpcReply); -BOOST_CLASS_EXPORT(raft::PeerRpcRequest<DummyState>); - /* Start cluster members with: * ./raft_rpc --member-id a --log-dir a_log * ./raft_rpc --member-id b --log-dir b_log @@ -43,18 +32,19 @@ int main(int argc, char *argv[]) { {"c", Endpoint("127.0.0.1", 12347)}}; communication::rpc::Server server(directory[FLAGS_member_id]); - RpcNetwork<DummyState> network(server, directory); - raft::SimpleFileStorage<DummyState> storage(FLAGS_log_dir); + // TODO: Serialize RPC via Cap'n Proto + // RpcNetwork<DummyState> network(server, directory); + // raft::SimpleFileStorage<DummyState> storage(FLAGS_log_dir); - raft::RaftConfig config{{"a", "b", "c"}, 150ms, 300ms, 70ms, 30ms}; + // raft::RaftConfig config{{"a", "b", "c"}, 150ms, 300ms, 70ms, 30ms}; - { - raft::RaftMember<DummyState> raft_member(network, storage, FLAGS_member_id, - config); - while (true) { - continue; - } - } + // { + // raft::RaftMember<DummyState> raft_member(network, storage, FLAGS_member_id, + // config); + // while (true) { + // continue; + // } + // } return 0; } diff --git a/tests/unit/rpc.cpp b/tests/unit/rpc.cpp index d22bbeee5..09de7a552 100644 --- a/tests/unit/rpc.cpp +++ b/tests/unit/rpc.cpp @@ -1,12 +1,6 @@ #include <thread> -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/archive/text_iarchive.hpp" -#include "boost/archive/text_oarchive.hpp" -#include "boost/serialization/access.hpp" -#include "boost/serialization/base_object.hpp" -#include "boost/serialization/export.hpp" +#include "capnp/serialize.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -19,63 +13,85 @@ using namespace communication::rpc; using namespace std::literals::chrono_literals; -struct SumReq : public Message { +struct SumReq { + using Capnp = ::capnp::AnyPointer; + static const MessageType TypeInfo; + + SumReq() {} // Needed for serialization. SumReq(int x, int y) : x(x), y(y) {} int x; int y; - private: - friend class boost::serialization::access; - SumReq() {} // Needed for serialization. + void Save(::capnp::AnyPointer::Builder *builder) const { + auto list_builder = builder->initAs<::capnp::List<int>>(2); + list_builder.set(0, x); + list_builder.set(1, y); + } - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar &x; - ar &y; + void Load(const ::capnp::AnyPointer::Reader &reader) { + auto list_reader = reader.getAs<::capnp::List<int>>(); + x = list_reader[0]; + y = list_reader[1]; } }; -BOOST_CLASS_EXPORT(SumReq); -struct SumRes : public Message { +const MessageType SumReq::TypeInfo{0, "SumReq"}; + +struct SumRes { + using Capnp = ::capnp::AnyPointer; + static const MessageType TypeInfo; + + SumRes() {} // Needed for serialization. SumRes(int sum) : sum(sum) {} + int sum; - private: - friend class boost::serialization::access; - SumRes() {} // Needed for serialization. + void Save(::capnp::AnyPointer::Builder *builder) const { + auto list_builder = builder->initAs<::capnp::List<int>>(1); + list_builder.set(0, sum); + } - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar ∑ + void Load(const ::capnp::AnyPointer::Reader &reader) { + auto list_reader = reader.getAs<::capnp::List<int>>(); + sum = list_reader[0]; } }; -BOOST_CLASS_EXPORT(SumRes); + +const MessageType SumRes::TypeInfo{1, "SumRes"}; + using Sum = RequestResponse<SumReq, SumRes>; -struct EchoMessage : public Message { +struct EchoMessage { + using Capnp = ::capnp::AnyPointer; + static const MessageType TypeInfo; + + EchoMessage() {} // Needed for serialization. EchoMessage(const std::string &data) : data(data) {} + std::string data; - private: - friend class boost::serialization::access; - EchoMessage() {} // Needed for serialization. + void Save(::capnp::AnyPointer::Builder *builder) const { + auto list_builder = builder->initAs<::capnp::List<::capnp::Text>>(1); + list_builder.set(0, data); + } - template <class TArchive> - void serialize(TArchive &ar, unsigned int) { - ar &boost::serialization::base_object<Message>(*this); - ar &data; + void Load(const ::capnp::AnyPointer::Reader &reader) { + auto list_reader = reader.getAs<::capnp::List<::capnp::Text>>(); + data = list_reader[0]; } }; -BOOST_CLASS_EXPORT(EchoMessage); + +const MessageType EchoMessage::TypeInfo{2, "EchoMessage"}; using Echo = RequestResponse<EchoMessage, EchoMessage>; TEST(Rpc, Call) { Server server({"127.0.0.1", 0}); - server.Register<Sum>([](const SumReq &request) { - return std::make_unique<SumRes>(request.x + request.y); + server.Register<Sum>([](const auto &req_reader, auto *res_builder) { + SumReq req; + req.Load(req_reader); + SumRes res(req.x + req.y); + res.Save(res_builder); }); std::this_thread::sleep_for(100ms); @@ -87,9 +103,12 @@ TEST(Rpc, Call) { TEST(Rpc, Abort) { Server server({"127.0.0.1", 0}); - server.Register<Sum>([](const SumReq &request) { + server.Register<Sum>([](const auto &req_reader, auto *res_builder) { + SumReq req; + req.Load(req_reader); std::this_thread::sleep_for(500ms); - return std::make_unique<SumRes>(request.x + request.y); + SumRes res(req.x + req.y); + res.Save(res_builder); }); std::this_thread::sleep_for(100ms); @@ -111,9 +130,12 @@ TEST(Rpc, Abort) { TEST(Rpc, ClientPool) { Server server({"127.0.0.1", 0}); - server.Register<Sum>([](const SumReq &request) { + server.Register<Sum>([](const auto &req_reader, auto *res_builder) { + SumReq req; + req.Load(req_reader); std::this_thread::sleep_for(100ms); - return std::make_unique<SumRes>(request.x + request.y); + SumRes res(req.x + req.y); + res.Save(res_builder); }); std::this_thread::sleep_for(100ms); @@ -161,8 +183,10 @@ TEST(Rpc, ClientPool) { TEST(Rpc, LargeMessage) { Server server({"127.0.0.1", 0}); - server.Register<Echo>([](const EchoMessage &request) { - return std::make_unique<EchoMessage>(request.data); + server.Register<Echo>([](const auto &req_reader, auto *res_builder) { + EchoMessage res; + res.Load(req_reader); + res.Save(res_builder); }); std::this_thread::sleep_for(100ms); diff --git a/tests/unit/rpc_worker_clients.cpp b/tests/unit/rpc_worker_clients.cpp index 7ca8fa7da..368ecabc5 100644 --- a/tests/unit/rpc_worker_clients.cpp +++ b/tests/unit/rpc_worker_clients.cpp @@ -1,8 +1,6 @@ #include <mutex> -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/export.hpp" +#include "capnp/serialize.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -18,17 +16,35 @@ namespace distributed { -RPC_NO_MEMBER_MESSAGE(IncrementCounterReq); -RPC_NO_MEMBER_MESSAGE(IncrementCounterRes); +struct IncrementCounterReq { + using Capnp = ::capnp::AnyPointer; + static const communication::rpc::MessageType TypeInfo; + + void Save(::capnp::AnyPointer::Builder *) const {} + + void Load(const ::capnp::AnyPointer::Reader &) {} +}; + +const communication::rpc::MessageType IncrementCounterReq::TypeInfo{ + 0, "IncrementCounterReq"}; + +struct IncrementCounterRes { + using Capnp = ::capnp::AnyPointer; + static const communication::rpc::MessageType TypeInfo; + + void Save(::capnp::AnyPointer::Builder *) const {} + + void Load(const ::capnp::AnyPointer::Reader &) {} +}; + +const communication::rpc::MessageType IncrementCounterRes::TypeInfo{ + 1, "IncrementCounterRes"}; using IncrementCounterRpc = communication::rpc::RequestResponse<IncrementCounterReq, IncrementCounterRes>; }; // namespace distributed -BOOST_CLASS_EXPORT(distributed::IncrementCounterReq); -BOOST_CLASS_EXPORT(distributed::IncrementCounterRes); - class RpcWorkerClientsTest : public ::testing::Test { protected: const io::network::Endpoint kLocalHost{"127.0.0.1", 0}; @@ -51,10 +67,9 @@ class RpcWorkerClientsTest : public ::testing::Test { cluster_discovery_.back()->RegisterWorker(i); workers_server_.back()->Register<distributed::IncrementCounterRpc>( - [this, i](const distributed::IncrementCounterReq &) { + [this, i](const auto &req_reader, auto *res_builder) { std::unique_lock<std::mutex> lock(mutex_); workers_cnt_[i]++; - return std::make_unique<distributed::IncrementCounterRes>(); }); } } diff --git a/tests/unit/serialization.cpp b/tests/unit/serialization.cpp index 83ab7f6e8..ac255efe9 100644 --- a/tests/unit/serialization.cpp +++ b/tests/unit/serialization.cpp @@ -361,3 +361,30 @@ TEST(Serialization, CapnpVectorNonCopyable) { EXPECT_EQ(*elements[0], 5); EXPECT_EQ(*elements[1], 10); } + +TEST(Serialization, CapnpMap) { + std::map<std::string, std::string> map{{"my_key", "my_value"}, + {"other_key", "other_value"}}; + ::capnp::MallocMessageBuilder message; + { + auto map_builder = + message.initRoot<utils::capnp::Map<capnp::Text, capnp::Text>>(); + utils::SaveMap<capnp::Text, capnp::Text>( + map, &map_builder, [](auto *entry_builder, const auto &entry) { + entry_builder->setKey(entry.first); + entry_builder->setValue(entry.second); + }); + } + std::map<std::string, std::string> new_map; + { + auto map_reader = + message.getRoot<utils::capnp::Map<capnp::Text, capnp::Text>>(); + utils::LoadMap<capnp::Text, capnp::Text>( + &new_map, map_reader, [](const auto &entry_reader) { + std::string key = entry_reader.getKey(); + std::string value = entry_reader.getValue(); + return std::make_pair(key, value); + }); + } + EXPECT_EQ(new_map, map); +} diff --git a/tools/lcp b/tools/lcp index 38d40a07a..284798ab9 100755 --- a/tools/lcp +++ b/tools/lcp @@ -22,7 +22,7 @@ fi capnp="" if [[ $# -eq 2 ]]; then - capnp=":capnp-id \"$($2 id)\"" + capnp=":capnp-id \"$2\"" fi echo \ @@ -37,6 +37,6 @@ filename=`basename $lcp_file .lcp` hpp_file="$(dirname $lcp_file)/$filename.hpp" clang-format -style=file -i $hpp_file -if [[ $# -eq 2 ]]; then +if [[ $# -eq 2 && -w "$lcp_file.cpp" ]]; then clang-format -style=file -i "$lcp_file.cpp" fi diff --git a/tools/src/mg_statsd/main.cpp b/tools/src/mg_statsd/main.cpp index 4bc68bceb..dbf337dfa 100644 --- a/tools/src/mg_statsd/main.cpp +++ b/tools/src/mg_statsd/main.cpp @@ -41,23 +41,31 @@ int main(int argc, char *argv[]) { << "Failed to connect to Graphite"; graphite_socket.SetKeepAlive(); - server.Register<stats::StatsRpc>([&](const stats::StatsReq &req) { - LOG(INFO) << "StatsRpc::Received"; - std::string data = GraphiteFormat(req); - graphite_socket.Write(data); - return std::make_unique<stats::StatsRes>(); - }); + server.Register<stats::StatsRpc>( + [&](const auto &req_reader, auto *res_builder) { + stats::StatsReq req; + req.Load(req_reader); + LOG(INFO) << "StatsRpc::Received"; + std::string data = GraphiteFormat(req); + graphite_socket.Write(data); + stats::StatsRes res; + res.Save(res_builder); + }); - server.Register<stats::BatchStatsRpc>([&](const stats::BatchStatsReq &req) { - // TODO(mtomic): batching? - LOG(INFO) << fmt::format("BatchStatsRpc::Received: {}", - req.requests.size()); - for (size_t i = 0; i < req.requests.size(); ++i) { - std::string data = GraphiteFormat(req.requests[i]); - graphite_socket.Write(data, i + 1 < req.requests.size()); - } - return std::make_unique<stats::BatchStatsRes>(); - }); + server.Register<stats::BatchStatsRpc>( + [&](const auto &req_reader, auto *res_builder) { + // TODO(mtomic): batching? + stats::BatchStatsReq req; + req.Load(req_reader); + LOG(INFO) << fmt::format("BatchStatsRpc::Received: {}", + req.requests.size()); + for (size_t i = 0; i < req.requests.size(); ++i) { + std::string data = GraphiteFormat(req.requests[i]); + graphite_socket.Write(data, i + 1 < req.requests.size()); + } + stats::BatchStatsRes res; + res.Save(res_builder); + }); std::this_thread::sleep_until(std::chrono::system_clock::time_point::max()); diff --git a/tools/tests/statsd/mg_statsd_client.cpp b/tools/tests/statsd/mg_statsd_client.cpp index 778417724..f96343236 100644 --- a/tools/tests/statsd/mg_statsd_client.cpp +++ b/tools/tests/statsd/mg_statsd_client.cpp @@ -7,13 +7,6 @@ // TODO (buda): move this logic to a unit test -// TODO (mtomic): This is a hack. I don't know a better way to make this work. -#include "boost/archive/binary_iarchive.hpp" -#include "boost/archive/binary_oarchive.hpp" -#include "boost/serialization/export.hpp" -BOOST_CLASS_EXPORT(stats::StatsReq); -BOOST_CLASS_EXPORT(stats::StatsRes); - bool parse_input(const std::string &s, std::string &metric_path, std::vector<std::pair<std::string, std::string>> &tags, double &value) {