From 69b3180b4f48d0d61e3cdd2e4c1ea75846082d84 Mon Sep 17 00:00:00 2001 From: Edward Tremel Date: Wed, 15 Oct 2025 14:26:33 -0400 Subject: [PATCH] Moved ServiceClient to its own set of files The service.hpp and service_impl.hpp files were getting very long and unwieldy due to the large number of different classes they contained. The ServiceClient class in particular is the longest, and most often needs to be consulted by users since it defines the API clients interact with, so I moved it to its own set of header/impl files. Also, in a minor fix, removed a reference to the nonexistant service_server_api.hpp from the documentation comments at the top of service.hpp. This seems to refer to a much older design for Casacade (where there was something called an 'onData()' function on servers) and is confusing for users. --- .../detail/_user_defined_logic_interface.hpp | 5 +- .../cascade/detail/service_client_impl.hpp | 2141 +++++++++++++++++ include/cascade/detail/service_impl.hpp | 2117 +--------------- include/cascade/service.hpp | 1419 +---------- include/cascade/service_client.hpp | 1386 +++++++++++ include/cascade/service_client_api.hpp | 43 +- src/service/server.cpp | 1 + src/service/server.hpp | 9 +- 8 files changed, 3588 insertions(+), 3533 deletions(-) create mode 100644 include/cascade/detail/service_client_impl.hpp create mode 100644 include/cascade/service_client.hpp diff --git a/include/cascade/detail/_user_defined_logic_interface.hpp b/include/cascade/detail/_user_defined_logic_interface.hpp index ed7d7cf0..ac638a7b 100644 --- a/include/cascade/detail/_user_defined_logic_interface.hpp +++ b/include/cascade/detail/_user_defined_logic_interface.hpp @@ -1,6 +1,7 @@ #pragma once #include #include "../service.hpp" +#include "../service_client.hpp" #include "../service_types.hpp" #include #include @@ -72,7 +73,7 @@ void release(ICascadeContext* ctxt); * | +------------------------------+ * | | * DefaultOffCriticalDataPathObserver - * + * * Please derive your own ocdpo from DefaultOffCriticalDataPathObserver, and override the virtual methods defined in * IDefaultOffCriticalDataPathObserver */ @@ -94,7 +95,7 @@ using emit_func_t = std::function +#include +#include +#include +#include + +namespace derecho { +namespace cascade { + +#ifdef ENABLE_EVALUATION +#define LOG_SERVICE_CLIENT_TIMESTAMP(tag, msgid) \ + TimestampLogger::log(tag, this->get_my_id(), msgid); +#else +#define LOG_SERVICE_CLIENT_TIMESTAMP(tag, msgid) +#endif + +template +template +void SubgroupNotificationHandler::initialize(derecho::ExternalClientCaller& subgroup_caller) { + dbg_default_trace("SubgroupNotificationHandler(this={:x}) is initialized for SubgroupType:{}", + reinterpret_cast(this), typeid(SubgroupType).name()); + subgroup_caller.register_notification_handler( + [this](const derecho::NotificationMessage& msg) { + dbg_default_trace("subgroup notification handler is triggered with this={:x}, msg type={}, size={} bytes", + reinterpret_cast(this), msg.message_type, msg.size); + (*this)(msg); + }); +} + +template +void SubgroupNotificationHandler::operator()(const derecho::NotificationMessage& msg) { + dbg_default_trace("SubgroupNotificationHandler(this={:x}) is triggered with message_type={:x}, size={} bytes", + reinterpret_cast(this), msg.message_type, msg.size); + if(msg.message_type != CASCADE_NOTIFICATION_MESSAGE_TYPE) { + return; + } + mutils::deserialize_and_run(nullptr, msg.body, + [this](const CascadeNotificationMessage& cascade_message) -> void { + dbg_default_trace("Handling cascade_message: {}. size={} bytes", + cascade_message.object_pool_pathname, cascade_message.blob.size); + std::lock_guard lck(*object_pool_notification_handlers_mutex); + // call default handler + if(object_pool_notification_handlers.find("") != object_pool_notification_handlers.cend()) { + if(object_pool_notification_handlers.at("").has_value()) { + (*object_pool_notification_handlers.at(""))(cascade_message.blob); + } + } + // call object pool handler + if(object_pool_notification_handlers.find(cascade_message.object_pool_pathname) != object_pool_notification_handlers.cend()) { + if(object_pool_notification_handlers.at(cascade_message.object_pool_pathname).has_value()) { + (*object_pool_notification_handlers.at(cascade_message.object_pool_pathname))(cascade_message.blob); + } + } + }); +} + +template +std::unique_ptr client_stub_factory() { + return std::make_unique(); +} + +template +ServiceClient::ServiceClient(derecho::Group, CascadeTypes...>* _group_ptr) + : external_group_ptr(nullptr), + group_ptr(_group_ptr) { + if(group_ptr == nullptr) { + this->external_group_ptr = std::make_unique, CascadeTypes...>>( + client_stub_factory>, + client_stub_factory...); + } +} + +template +bool ServiceClient::is_external_client() const { + return (group_ptr == nullptr) && (external_group_ptr != nullptr); +} + +template +node_id_t ServiceClient::get_my_id() const { + if(!is_external_client()) { + return group_ptr->get_my_id(); + } else { + return external_group_ptr->get_my_id(); + } +} + +template +std::vector ServiceClient::get_members() const { + if(!is_external_client()) { + return group_ptr->get_members(); + } else { + return external_group_ptr->get_members(); + } +} + +template +template +std::vector ServiceClient::get_shard_members(uint32_t subgroup_index, uint32_t shard_index) const { + if(!is_external_client()) { + std::vector> subgroup_members = group_ptr->template get_subgroup_members(subgroup_index); + if(subgroup_members.size() > shard_index) { + return subgroup_members[shard_index]; + } else { + return {}; + } + } else { + return external_group_ptr->template get_shard_members(subgroup_index, shard_index); + } +} + +template +template +std::vector ServiceClient::type_recursive_get_shard_members( + uint32_t type_index, uint32_t subgroup_index, uint32_t shard_index) const { + if(type_index == 0) { + return this->template get_shard_members(subgroup_index, shard_index); + } else { + return this->template type_recursive_get_shard_members(type_index - 1, subgroup_index, shard_index); + } +} + +template +template +std::vector ServiceClient::type_recursive_get_shard_members( + uint32_t type_index, uint32_t subgroup_index, uint32_t shard_index) const { + if(type_index == 0) { + return this->template get_shard_members(subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); + } +} + +template +std::vector ServiceClient::get_shard_members( + const std::string& object_pool_pathname, uint32_t shard_index) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + return this->template type_recursive_get_shard_members(opm.subgroup_type_index, opm.subgroup_index, shard_index); +} + +template +template +std::vector> ServiceClient::get_subgroup_members(uint32_t subgroup_index) const { + if(!is_external_client()) { + return group_ptr->template get_subgroup_members(subgroup_index); + } else { + return external_group_ptr->template get_subgroup_members(subgroup_index); + } +} + +template +template +std::vector> ServiceClient::type_recursive_get_subgroup_members( + uint32_t type_index, uint32_t subgroup_index) const { + if(type_index == 0) { + return this->template get_subgroup_members(subgroup_index); + } else { + return this->template type_recursive_get_subgroup_members(type_index - 1, subgroup_index); + } +} + +template +template +std::vector> ServiceClient::type_recursive_get_subgroup_members( + uint32_t type_index, uint32_t subgroup_index) const { + if(type_index == 0) { + return this->template get_subgroup_members(subgroup_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); + } +} + +template +std::vector> ServiceClient::get_subgroup_members( + const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + return this->template type_recursive_get_subgroup_members(opm.subgroup_type_index, opm.subgroup_index); +} + +template +template +uint32_t ServiceClient::get_number_of_subgroups() const { + if(!is_external_client()) { + return group_ptr->template get_num_subgroups(); + } else { + return external_group_ptr->template get_number_of_subgroups(); + } +} + +template +template +uint32_t ServiceClient::get_number_of_shards(uint32_t subgroup_index) const { + if(!is_external_client()) { + return group_ptr->template get_subgroup_members(subgroup_index).size(); + } else { + return external_group_ptr->template get_number_of_shards(subgroup_index); + } +} + +template +template +uint32_t ServiceClient::type_recursive_get_number_of_shards( + uint32_t type_index, uint32_t subgroup_index) const { + if(type_index == 0) { + return this->template get_number_of_shards(subgroup_index); + } else { + return this->template type_recursive_get_number_of_shards(type_index - 1, subgroup_index); + } +} + +template +template +uint32_t ServiceClient::type_recursive_get_number_of_shards( + uint32_t type_index, uint32_t subgroup_index) const { + if(type_index == 0) { + return this->template get_number_of_shards(subgroup_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); + } +} + +template +uint32_t ServiceClient::get_number_of_shards( + uint32_t subgroup_type_index, uint32_t subgroup_index) const { + return type_recursive_get_number_of_shards(subgroup_type_index, subgroup_index); +} + +template +uint32_t ServiceClient::get_number_of_shards( + const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + return get_number_of_shards(opm.subgroup_type_index, opm.subgroup_index); +} + +template +template +int32_t ServiceClient::get_my_shard(uint32_t subgroup_index) const { + if(!is_external_client()) { + return group_ptr->template get_my_shard(subgroup_index); + } else { + return -1; + } +} + +template +template +int32_t ServiceClient::type_recursive_get_my_shard( + uint32_t type_index, uint32_t subgroup_index) const { + if(type_index == 0) { + return this->template get_my_shard(subgroup_index); + } else { + return this->template type_recursive_get_number_of_shards(type_index - 1, subgroup_index); + } +} + +template +template +int32_t ServiceClient::type_recursive_get_my_shard( + uint32_t type_index, uint32_t subgroup_index) const { + if(type_index == 0) { + return this->template get_my_shard(subgroup_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); + } +} + +template +int32_t ServiceClient::get_my_shard( + uint32_t subgroup_type_index, uint32_t subgroup_index) const { + return this->template type_recursive_get_my_shard(subgroup_type_index, subgroup_index); +} + +template +int32_t ServiceClient::get_my_shard( + const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + return get_my_shard(opm.subgroup_type_index, opm.subgroup_index); +} + +template +template +void ServiceClient::set_member_selection_policy(uint32_t subgroup_index, uint32_t shard_index, + ShardMemberSelectionPolicy policy, node_id_t user_specified_node_id) { + // write lock policies + std::unique_lock wlck(this->member_selection_policies_mutex); + // update map + this->member_selection_policies[std::make_tuple(std::type_index(typeid(SubgroupType)), subgroup_index, shard_index)] + = std::make_tuple(policy, user_specified_node_id); +} + +template +template +std::tuple ServiceClient::get_member_selection_policy( + uint32_t subgroup_index, uint32_t shard_index) const { + // read lock policies + std::shared_lock rlck(this->member_selection_policies_mutex); + auto key = std::make_tuple(std::type_index(typeid(SubgroupType)), subgroup_index, shard_index); + // read map + if(member_selection_policies.find(key) != member_selection_policies.end()) { + return member_selection_policies.at(key); + } else { + return std::make_tuple(DEFAULT_SHARD_MEMBER_SELECTION_POLICY, INVALID_NODE_ID); + } +} + +template +template +void ServiceClient::refresh_member_cache_entry(uint32_t subgroup_index, + uint32_t shard_index) { + auto key = std::make_tuple(std::type_index(typeid(SubgroupType)), subgroup_index, shard_index); + auto members = get_shard_members(subgroup_index, shard_index); + std::shared_lock rlck(member_cache_mutex); + if(member_cache.find(key) == member_cache.end()) { + rlck.unlock(); + std::unique_lock wlck(member_cache_mutex); + member_cache[key] = members; + } else { + member_cache[key].swap(members); + } +} + +template +template +std::tuple ServiceClient::key_to_shard( + const KeyType& key, + bool check_object_location) { + auto pair = find_object_pool_and_affinity_set_by_key(key); + + auto& opm = std::get<0>(pair); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to identify the object_pool from key:" + key); + } + auto& affinity_set = std::get<1>(pair); + + return std::tuple{opm.subgroup_type_index, opm.subgroup_index, + opm.key_to_shard_index(key, affinity_set, + get_number_of_shards(opm.subgroup_type_index, opm.subgroup_index), + check_object_location)}; +} + +template +ServiceClient::ObjectPoolMetadataCacheEntry::ObjectPoolMetadataCacheEntry( + const ObjectPoolMetadata& _opm) : opm(_opm), database(nullptr) { + if(opm.affinity_set_regex.size() > 0) { + hs_compile_error_t* compile_err; + if(hs_compile(opm.affinity_set_regex.c_str(), HS_FLAG_DOTALL | HS_FLAG_SOM_LEFTMOST, HS_MODE_BLOCK, NULL, &database, + &compile_err) + != HS_SUCCESS) { + hs_free_compile_error(compile_err); + dbg_default_error("Compilation of affinity set regex:" + opm.affinity_set_regex + " failed with message:" + + compile_err->message); + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + + ": compilation of affinity_set_regex:" + + opm.affinity_set_regex + + " failed with message:" + + compile_err->message); + } + } +} + +template +ServiceClient::ObjectPoolMetadataCacheEntry::~ObjectPoolMetadataCacheEntry() { + if(this->database != nullptr) { + hs_free_database(database); + this->database = nullptr; + } +} + +template +inline std::string ServiceClient::ObjectPoolMetadataCacheEntry::to_affinity_set( + const std::string& key_string) { + if(key_string.size() > 0 && this->opm.affinity_set_regex.size() > 0) { + if(scratch == nullptr) { + if(hs_alloc_scratch(database, &scratch) != HS_SUCCESS) { + hs_free_database(database); + dbg_default_error("failed to allocate hyperscan scratch space."); + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + + " failed to allocate hyperscan scratch space."); + } + } + + struct hs_scan_ctxt { + unsigned long long from = 0; + unsigned long long to = 0; + } ctxt; + + hs_scan(database, key_string.c_str(), key_string.size(), HS_FLAG_SOM_LEFTMOST, scratch, [](unsigned int /*id*/, unsigned long long from, unsigned long long to, unsigned int /*flags*/, void* ctxt) -> int { + struct hs_scan_ctxt* p_hs_ctxt = static_cast(ctxt); + p_hs_ctxt->from = from; + p_hs_ctxt->to = to; + return 0; // do the longest match + }, + &ctxt); + if(ctxt.to > ctxt.from) { + return key_string.substr(ctxt.from, (ctxt.to - ctxt.from)); + } + } + + return key_string; +} + +template +thread_local hs_scratch_t* ServiceClient::ObjectPoolMetadataCacheEntry::scratch = nullptr; + +template +template +node_id_t ServiceClient::pick_member_by_policy(uint32_t subgroup_index, + uint32_t shard_index, + const KeyTypeForHashing& key_for_hashing, + bool retry) { + ShardMemberSelectionPolicy policy; + node_id_t last_specified_node_id_or_index; + + std::tie(policy, last_specified_node_id_or_index) = get_member_selection_policy(subgroup_index, shard_index); + + if(policy == ShardMemberSelectionPolicy::UserSpecified) { + return last_specified_node_id_or_index; + } + + auto key = std::make_tuple(std::type_index(typeid(SubgroupType)), subgroup_index, shard_index); + + if(member_cache.find(key) == member_cache.end() || retry) { + refresh_member_cache_entry(subgroup_index, shard_index); + } + + std::shared_lock rlck(member_cache_mutex); + + node_id_t node_id = last_specified_node_id_or_index; + + switch(policy) { + case ShardMemberSelectionPolicy::FirstMember: + node_id = member_cache.at(key).front(); + break; + case ShardMemberSelectionPolicy::LastMember: + node_id = member_cache.at(key).back(); + break; + case ShardMemberSelectionPolicy::Random: + node_id = member_cache.at(key)[get_time() % member_cache.at(key).size()]; // use time as random source. + break; + case ShardMemberSelectionPolicy::FixedRandom: + if(node_id == INVALID_NODE_ID || retry) { + node_id = member_cache.at(key)[get_time() % member_cache.at(key).size()]; // use time as random source. + } + break; + case ShardMemberSelectionPolicy::RoundRobin: { + std::shared_lock rlck(member_selection_policies_mutex); + node_id = static_cast(node_id + 1) % member_cache.at(key).size(); + auto new_policy = std::make_tuple(ShardMemberSelectionPolicy::RoundRobin, node_id); + member_selection_policies[key].swap(new_policy); + } + node_id = member_cache.at(key)[node_id]; + break; + case ShardMemberSelectionPolicy::KeyHashing: { + uint64_t hash = 0; + if constexpr(std::is_integral_v) { + hash = static_cast(key_for_hashing); + } else if constexpr(std::is_convertible_v) { + hash = std::hash{}(std::string(key_for_hashing)); + } else { + dbg_default_warn("Key type {} is neither integral nor string, falling back to FirstMember policy. {}:{}", + typeid(KeyTypeForHashing).name(), __FILE__, __LINE__); + } + node_id = member_cache.at(key)[hash % member_cache.at(key).size()]; + } break; + default: + throw derecho::derecho_exception("Unknown member selection policy:" + std::to_string(static_cast(policy))); + } + + return node_id; +} + +template +template +derecho::rpc::QueryResults ServiceClient::put( + const typename SubgroupType::ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_PUT_START, + (std::is_base_of::value ? value.get_message_id() : 0)); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + // ordered put as a shard member + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + return subgroup_handle.template ordered_send(value, as_trigger); + } else { + // p2p put + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, value.get_key_ref()); + try { + // as a subgroup member + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, value, as_trigger); + } catch(derecho::invalid_subgroup_exception& ex) { + // as an external caller + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, value, as_trigger); + } + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, value.get_key_ref()); + return caller.template p2p_send(node_id, value, as_trigger); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_put( + uint32_t type_index, + const ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger) { + if(type_index == 0) { + return this->template put(value, subgroup_index, shard_index, as_trigger); + } else { + return this->template type_recursive_put(type_index - 1, value, subgroup_index, shard_index, as_trigger); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_put( + uint32_t type_index, + const ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger) { + if(type_index == 0) { + return this->template put(value, subgroup_index, shard_index, as_trigger); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::put( + const ObjectType& value, bool as_trigger) { + // STEP 1 - get key + if constexpr(!std::is_base_of_v, ObjectType>) { + throw derecho::derecho_exception(std::string("ServiceClient<>::put() only support object of type ICascadeObject,but we get ") + typeid(ObjectType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(value.get_key_ref()); + + // STEP 3 - call recursive put + return this->template type_recursive_put(subgroup_type_index, value, subgroup_index, shard_index, as_trigger); +} + +template +template +void ServiceClient::put_and_forget( + const typename SubgroupType::ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_PUT_AND_FORGET_START, + (std::is_base_of::value ? value.get_message_id() : 0)); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + // do ordered put as a shard member (Replicated). + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + subgroup_handle.template ordered_send(value, as_trigger); + } else { + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, value.get_key_ref()); + // do p2p put + try { + // as a subgroup member + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + subgroup_handle.template p2p_send(node_id, value, as_trigger); + } catch(derecho::invalid_subgroup_exception& ex) { + // as an external caller + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + subgroup_handle.template p2p_send(node_id, value, as_trigger); + } + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, value.get_key_ref()); + caller.template p2p_send(node_id, value, as_trigger); + } +} + +template +template +void ServiceClient::type_recursive_put_and_forget( + uint32_t type_index, + const ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger) { + if(type_index == 0) { + put_and_forget(value, subgroup_index, shard_index, as_trigger); + } else { + type_recursive_put_and_forget(type_index - 1, value, subgroup_index, shard_index, as_trigger); + } +} + +template +template +void ServiceClient::type_recursive_put_and_forget( + uint32_t type_index, + const ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger) { + if(type_index == 0) { + put_and_forget(value, subgroup_index, shard_index, as_trigger); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +void ServiceClient::put_and_forget(const ObjectType& value, bool as_trigger) { + // STEP 1 - get key + if constexpr(!std::is_base_of_v, ObjectType>) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports object of type ICascadeObject,but we get ") + typeid(ObjectType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(value.get_key_ref()); + + // STEP 3 - call recursive put_and_forget + this->template type_recursive_put_and_forget(subgroup_type_index, value, subgroup_index, shard_index, as_trigger); +} + +template +template +derecho::rpc::QueryResults ServiceClient::trigger_put( + const typename SubgroupType::ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_TRIGGER_PUT_START, + (std::is_base_of::value ? value.get_message_id() : 0)); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, value.get_key_ref()); + dbg_default_trace("trigger_put to node {}", node_id); + return subgroup_handle.template p2p_send(node_id, value); + } else { + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, value.get_key_ref()); + dbg_default_trace("trigger_put to node {}", node_id); + return subgroup_handle.template p2p_send(node_id, value); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, value.get_key_ref()); + dbg_default_trace("trigger_put to node {}", node_id); + return caller.template p2p_send(node_id, value); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_trigger_put( + uint32_t type_index, + const ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return trigger_put(value, subgroup_index, shard_index); + } else { + return type_recursive_trigger_put(type_index - 1, value, subgroup_index, shard_index); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_trigger_put( + uint32_t type_index, + const ObjectType& value, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return trigger_put(value, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::trigger_put( + const ObjectType& value) { + // STEP 1 - get key + if constexpr(!std::is_base_of_v, ObjectType>) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports object of type ICascadeObject,but we get ") + typeid(ObjectType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(value.get_key_ref()); + + // STEP 3 - call recursive trigger_put + return this->template type_recursive_trigger_put(subgroup_type_index, value, subgroup_index, shard_index); +} + +template +template +void ServiceClient::collective_trigger_put( + const typename SubgroupType::ObjectType& value, + uint32_t subgroup_index, + std::unordered_map>>& nodes_and_futures) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_COLLECTIVE_TRIGGER_PUT_START, + (std::is_base_of::value ? value.get_message_id() : 0)); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + if(group_ptr->template get_my_shard(subgroup_index) != -1) { + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + for(auto& kv : nodes_and_futures) { + nodes_and_futures[kv.first] = std::make_unique>( + std::move(subgroup_handle.template p2p_send(kv.first, value))); + } + } else { + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + for(auto& kv : nodes_and_futures) { + nodes_and_futures[kv.first] = std::make_unique>( + std::move(subgroup_handle.template p2p_send(kv.first, value))); + } + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + for(auto& kv : nodes_and_futures) { + nodes_and_futures[kv.first] = std::make_unique>( + std::move(caller.template p2p_send(kv.first, value))); + } + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::remove( + const typename SubgroupType::KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_REMOVE_START, 0); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + // do ordered remove as a member (Replicated). + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + return subgroup_handle.template ordered_send(key); + } else { + // do p2p remove + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + try { + // as a subgroup member + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key); + } catch(derecho::invalid_subgroup_exception& ex) { + // as an external caller + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key); + } + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + return caller.template p2p_send(node_id, key); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_remove( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template remove(key, subgroup_index, shard_index); + } else { + return this->template type_recursive_remove(type_index - 1, key, subgroup_index, shard_index); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_remove( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template remove(key, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::remove( + const KeyType& key) { + // STEP 1 - get key + if constexpr(!std::is_convertible_v) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(key); + + // STEP 3 - call recursive remove + return this->template type_recursive_remove(subgroup_type_index, key, subgroup_index, shard_index); +} + +template +template +derecho::rpc::QueryResults ServiceClient::get( + const typename SubgroupType::KeyType& key, + const persistent::version_t& version, + bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_GET_START, 0); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + try { + // do p2p get as a subgroup member + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + // local get + auto obj = subgroup_handle.get_ref().get(key, version, stable); + auto pending_results = std::make_shared>(); + pending_results->fulfill_map({node_id}); + pending_results->set_value(node_id, obj); + auto query_results = pending_results->get_future(); + return std::move(*query_results); + } + return subgroup_handle.template p2p_send(node_id, key, version, stable, false); + } catch(derecho::invalid_subgroup_exception& ex) { + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key, version, stable, false); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + return caller.template p2p_send(node_id, key, version, stable, false); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::multi_get( + const typename SubgroupType::KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_MULTI_GET_START, 0); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + try { + // do p2p multi_get as a subgroup member. + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, key); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p multi_get as an external caller. + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + return caller.template p2p_send(node_id, key); + } +} + +template +template +auto ServiceClient::type_recursive_get( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get(key, version, stable, subgroup_index, shard_index); + } else { + return this->template type_recursive_get(type_index - 1, key, version, stable, subgroup_index, shard_index); + } +} + +template +template +auto ServiceClient::type_recursive_get( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get(key, version, stable, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +auto ServiceClient::get( + // const std::decay_t>& key, + const KeyType& key, + const persistent::version_t& version, + bool stable) { + // STEP 1 - get key + if constexpr(!std::is_convertible_v) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(key); + + // STEP 3 - call recursive get + return this->template type_recursive_get(subgroup_type_index, key, version, stable, subgroup_index, shard_index); +} + +template +template +auto ServiceClient::type_recursive_multi_get( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template multi_get(key, subgroup_index, shard_index); + } else { + return this->template type_recursive_multi_get(type_index - 1, key, subgroup_index, shard_index); + } +} + +template +template +auto ServiceClient::type_recursive_multi_get( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template multi_get(key, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +auto ServiceClient::multi_get( + const KeyType& key) { + // STEP 1 - get key + if constexpr(!std::is_convertible_v) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(key); + + // STEP 3 - call recursive get + return this->template type_recursive_multi_get(subgroup_type_index, key, subgroup_index, shard_index); +} + +template +template +derecho::rpc::QueryResults ServiceClient::get_by_time( + const typename SubgroupType::KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + try { + // do p2p get_by_time + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + // as a shard member. + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, key, ts_us, stable); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p get_by_time as an external caller + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key, ts_us, stable); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + return caller.template p2p_send(node_id, key, ts_us, stable); + } +} + +template +template +auto ServiceClient::type_recursive_get_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get_by_time(key, ts_us, stable, subgroup_index, shard_index); + } else { + return this->template type_recursive_get_by_time(type_index - 1, key, ts_us, stable, subgroup_index, shard_index); + } +} + +template +template +auto ServiceClient::type_recursive_get_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get_by_time(key, ts_us, stable, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +auto ServiceClient::get_by_time( + const KeyType& key, + const uint64_t& ts_us, + const bool stable) { + // STEP 1 - get key + if constexpr(!std::is_convertible_v) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(key); + + // STEP 3 - call recursive get_by_time + return this->template type_recursive_get_by_time(subgroup_type_index, key, ts_us, stable, subgroup_index, shard_index); +} + +template +template +derecho::rpc::QueryResults ServiceClient::get_size( + const typename SubgroupType::KeyType& key, + const persistent::version_t& version, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_GET_SIZE_START, 0); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + try { + // do p2p get_size as a subgroup_member + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + // as a shard member. + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, key, version, stable, false); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p get_size as an external caller + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key, version, stable, false); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + return caller.template p2p_send(node_id, key, version, stable, false); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_get_size( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get_size(key, version, stable, subgroup_index, shard_index); + } else { + return this->template type_recursive_get_size(type_index - 1, key, version, stable, subgroup_index, shard_index); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_get_size( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get_size(key, version, stable, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::get_size( + const KeyType& key, + const persistent::version_t& version, + const bool stable) { + // STEP 1 - verify the keys + if constexpr(!std::is_convertible_v) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(key); + + // STEP 3 - call recursive get + return this->template type_recursive_get_size(subgroup_type_index, key, version, stable, subgroup_index, shard_index); +} + +template +template +derecho::rpc::QueryResults ServiceClient::multi_get_size( + const typename SubgroupType::KeyType& key, + uint32_t subgroup_index, uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_MULTI_GET_SIZE_START, 0); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + try { + // do p2p multi_get_size as a subgroup member. + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, key); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p multi_get_size as an external caller. + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + return caller.template p2p_send(node_id, key); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_multi_get_size( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template multi_get_size(key, subgroup_index, shard_index); + } else { + return this->template type_recursive_multi_get_size(type_index - 1, key, subgroup_index, shard_index); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_multi_get_size( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template multi_get_size(key, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::multi_get_size(const KeyType& key) { + // STEP 1 - verify the keys + if constexpr(!std::is_convertible_v) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(key); + + // STEP 3 - call recursive get + return this->template type_recursive_multi_get_size(subgroup_type_index, key, subgroup_index, shard_index); +} + +template +template +derecho::rpc::QueryResults ServiceClient::get_size_by_time( + const typename SubgroupType::KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + try { + // do p2p get_size_by_time as a subgroup member. + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, key, ts_us, stable); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p get_size_by_time as an external caller. + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, key, ts_us, stable); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, key); + return caller.template p2p_send(node_id, key, ts_us, stable); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_get_size_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get_size_by_time(key, ts_us, stable, subgroup_index, shard_index); + } else { + return this->template type_recursive_get_size_by_time(type_index - 1, key, ts_us, stable, subgroup_index, shard_index); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::type_recursive_get_size_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(type_index == 0) { + return this->template get_size_by_time(key, ts_us, stable, subgroup_index, shard_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::get_size_by_time( + const KeyType& key, + const uint64_t& ts_us, + const bool stable) { + // STEP 1 - verify the keys + if constexpr(!std::is_convertible_v) { + throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); + } + + // STEP 2 - get shard + uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(key); + + // STEP 3 - call recursive get + return this->template type_recursive_get_size_by_time(subgroup_type_index, key, ts_us, stable, subgroup_index, shard_index); +} + +template +template +derecho::rpc::QueryResults> ServiceClient::list_keys( + const persistent::version_t& version, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_LIST_KEYS_START, 0); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + try { + // do p2p list_keys as a subgroup member. + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, "", version, stable); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p list_keys as an external client. + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, "", version, stable); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + return caller.template p2p_send(node_id, "", version, stable); + } +} + +template +template +auto ServiceClient::type_recursive_list_keys( + uint32_t type_index, + const persistent::version_t& version, + const bool stable, + const std::string& object_pool_pathname) { + if(type_index == 0) { + return this->template __list_keys(version, stable, object_pool_pathname); + } else { + return this->template type_recursive_list_keys(type_index - 1, version, stable, object_pool_pathname); + } +} + +template +template +auto ServiceClient::type_recursive_list_keys( + uint32_t type_index, + const persistent::version_t& version, + const bool stable, + const std::string& object_pool_pathname) { + if(type_index == 0) { + return this->template __list_keys(version, stable, object_pool_pathname); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +std::vector>>> ServiceClient::__list_keys( + const persistent::version_t& version, + const bool stable, + const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + uint32_t subgroup_index = opm.subgroup_index; + uint32_t shards = get_number_of_shards(subgroup_index); + std::vector>>> result; + for(uint32_t shard_index = 0; shard_index < shards; shard_index++) { + if(!is_external_client()) { + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + try { + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + auto shard_keys = subgroup_handle.template p2p_send(node_id, object_pool_pathname, version, stable); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } catch(derecho::invalid_subgroup_exception& ex) { + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + auto shard_keys = subgroup_handle.template p2p_send(node_id, object_pool_pathname, version, stable); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + auto shard_keys = caller.template p2p_send(node_id, object_pool_pathname, version, stable); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } + } + return result; +} + +template +auto ServiceClient::list_keys( + const persistent::version_t& version, + const bool stable, + const std::string& object_pool_pathname) { + volatile uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(object_pool_pathname + "/_"); + return this->template type_recursive_list_keys(subgroup_type_index, version, stable, object_pool_pathname); +} + +template +inline ReturnType wait_for_future(derecho::rpc::QueryResults& result) { + // iterate through ReplyMap + for(auto& reply_future : result.get()) { + ReturnType reply = static_cast(reply_future.second.get()); + return reply; + } + return ReturnType(); +} + +template +template +std::vector ServiceClient::wait_list_keys( + std::vector>>>& future) { + std::vector result; + // iterate over each shard's Query result + for(auto& query_result : future) { + std::vector reply = wait_for_future>(*(query_result.get())); + std::move(reply.begin(), reply.end(), std::back_inserter(result)); + } + return result; +} + +template +template +derecho::rpc::QueryResults> ServiceClient::multi_list_keys( + uint32_t subgroup_index, + uint32_t shard_index) { + LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_MULTI_LIST_KEYS_START, 0); + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + try { + // do p2p multi_list_keys as a subgroup member. + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, ""); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p multi_list_keys as an external client. + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + return subgroup_handle.template p2p_send(node_id, ""); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + return caller.template p2p_send(node_id, ""); + } +} + +template +template +auto ServiceClient::type_recursive_multi_list_keys( + uint32_t type_index, + const std::string& object_pool_pathname) { + if(type_index == 0) { + return this->template __multi_list_keys(object_pool_pathname); + } else { + return this->template type_recursive_multi_list_keys(type_index - 1, object_pool_pathname); + } +} + +template +template +auto ServiceClient::type_recursive_multi_list_keys( + uint32_t type_index, + const std::string& object_pool_pathname) { + if(type_index == 0) { + return this->template __multi_list_keys(object_pool_pathname); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +std::vector>>> ServiceClient::__multi_list_keys(const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + uint32_t subgroup_index = opm.subgroup_index; + uint32_t shards = get_number_of_shards(subgroup_index); + std::vector>>> result; + for(uint32_t shard_index = 0; shard_index < shards; shard_index++) { + if(!is_external_client()) { + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, object_pool_pathname); + try { + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + auto shard_keys = subgroup_handle.template p2p_send(node_id, object_pool_pathname); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } catch(derecho::invalid_subgroup_exception& ex) { + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + auto shard_keys = subgroup_handle.template p2p_send(node_id, object_pool_pathname); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, object_pool_pathname); + auto shard_keys = caller.template p2p_send(node_id, object_pool_pathname); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } + } + return result; +} + +template +auto ServiceClient::multi_list_keys(const std::string& object_pool_pathname) { + volatile uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(object_pool_pathname + "/_"); + return this->template type_recursive_multi_list_keys(subgroup_type_index, object_pool_pathname); +} + +template +template +derecho::rpc::QueryResults> ServiceClient::list_keys_by_time( + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index) { + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + try { + // do p2p list_keys_by_time as a subgroup member + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + return subgroup_handle.template p2p_send(node_id, "", ts_us, stable); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p list_keys_by_time as an external client. + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, "", ts_us, stable); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + return caller.template p2p_send(node_id, "", ts_us, stable); + } +} + +template +template +auto ServiceClient::type_recursive_list_keys_by_time( + uint32_t type_index, + const uint64_t& ts_us, + const bool stable, + const std::string& object_pool_pathname) { + if(type_index == 0) { + return this->template __list_keys_by_time(ts_us, stable, object_pool_pathname); + } else { + return this->template type_recursive_list_keys_by_time(type_index - 1, ts_us, stable, object_pool_pathname); + } +} + +template +template +auto ServiceClient::type_recursive_list_keys_by_time( + uint32_t type_index, + const uint64_t& ts_us, + const bool stable, + const std::string& object_pool_pathname) { + if(type_index == 0) { + return this->template __list_keys_by_time(ts_us, stable, object_pool_pathname); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +template +std::vector>>> ServiceClient::__list_keys_by_time( + const uint64_t& ts_us, + const bool stable, + const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + uint32_t subgroup_index = opm.subgroup_index; + uint32_t shards = get_number_of_shards(subgroup_index); + std::vector>>> result; + for(uint32_t shard_index = 0; shard_index < shards; shard_index++) { + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, object_pool_pathname); + try { + // do p2p list_keys_by_time as a subgroup member. + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + node_id = group_ptr->get_my_id(); + } + auto shard_keys = subgroup_handle.template p2p_send(node_id, object_pool_pathname, ts_us, stable); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } catch(derecho::invalid_subgroup_exception& ex) { + // do p2p list_keys_by_time as an external client. + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + auto shard_keys = subgroup_handle.template p2p_send(node_id, object_pool_pathname, ts_us, stable); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + // call as an external client (ExternalClientCaller). + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, object_pool_pathname); + auto shard_keys = caller.template p2p_send(node_id, object_pool_pathname, ts_us, stable); + result.emplace_back(std::make_unique>>(std::move(shard_keys))); + } + } + return result; +} + +template +auto ServiceClient::list_keys_by_time(const uint64_t& ts_us, const bool stable, const std::string& object_pool_pathname) { + volatile uint32_t subgroup_type_index, subgroup_index, shard_index; + std::tie(subgroup_type_index, subgroup_index, shard_index) = this->template key_to_shard(object_pool_pathname + "/_"); + return this->template type_recursive_list_keys_by_time(subgroup_type_index, ts_us, stable, object_pool_pathname); +} + +template +void ServiceClient::refresh_object_pool_metadata_cache() { + std::unordered_map refreshed_metadata; + uint32_t num_shards = this->template get_number_of_shards>(METADATA_SERVICE_SUBGROUP_INDEX); + for(uint32_t shard = 0; shard < num_shards; shard++) { + auto results = this->template multi_list_keys>(METADATA_SERVICE_SUBGROUP_INDEX, shard); + for(auto& reply : results.get()) { // only once + for(auto& key : reply.second.get()) { // iterate over keys + // we only read the stable version. + auto opm_result = this->template get>(key, CURRENT_VERSION, false, METADATA_SERVICE_SUBGROUP_INDEX, shard); + for(auto& opm_reply : opm_result.get()) { // only once + refreshed_metadata.emplace(key, opm_reply.second.get()); + break; + } + } + break; + } + } + + std::unique_lock wlck(object_pool_metadata_cache_mutex); + this->object_pool_metadata_cache = std::move(refreshed_metadata); +} + +template +template +derecho::rpc::QueryResults ServiceClient::create_object_pool( + const std::string& pathname, const uint32_t subgroup_index, + const sharding_policy_t sharding_policy, const std::unordered_map& object_locations, + const std::string& affinity_set_regex) { + uint32_t subgroup_type_index = ObjectPoolMetadata::template get_subgroup_type_index(); + if(subgroup_type_index == ObjectPoolMetadata::invalid_subgroup_type_index) { + dbg_default_crit("Create object pool failed because of invalid SubgroupType:{}", typeid(SubgroupType).name()); + throw derecho::derecho_exception(std::string("Create object pool failed because SubgroupType is invalid:") + typeid(SubgroupType).name()); + } + ObjectPoolMetadata opm(pathname, subgroup_type_index, subgroup_index, sharding_policy, object_locations, affinity_set_regex, false); + // clear local cache entry. + std::shared_lock rlck(object_pool_metadata_cache_mutex); + if(object_pool_metadata_cache.find(pathname) == object_pool_metadata_cache.end()) { + rlck.unlock(); + } else { + rlck.unlock(); + std::unique_lock wlck(object_pool_metadata_cache_mutex); + object_pool_metadata_cache.erase(pathname); + } + // determine the shard index by hashing + uint32_t metadata_service_shard_index = std::hash{}(pathname) % this->template get_number_of_shards>(METADATA_SERVICE_SUBGROUP_INDEX); + + return this->template put>(opm, METADATA_SERVICE_SUBGROUP_INDEX, metadata_service_shard_index); +} + +template +derecho::rpc::QueryResults ServiceClient::remove_object_pool(const std::string& pathname) { + // determine the shard index by hashing + uint32_t metadata_service_shard_index = std::hash{}(pathname) % this->template get_number_of_shards>(METADATA_SERVICE_SUBGROUP_INDEX); + + // check if this object pool exist in metadata service. + auto opm = find_object_pool(pathname); + // remove it from local cache. + std::shared_lock rlck(object_pool_metadata_cache_mutex); + if(object_pool_metadata_cache.find(pathname) == object_pool_metadata_cache.end()) { + // no entry in cache + rlck.unlock(); + } else { + // remove from cache + rlck.unlock(); + std::unique_lock wlck(object_pool_metadata_cache_mutex); + object_pool_metadata_cache.erase(pathname); + wlck.unlock(); + } + if(opm.is_valid() && !opm.is_null()) { + if(opm.deleted) { + throw derecho::derecho_exception(std::string("object pool:") + pathname + " has been deleted already."); + } + opm.deleted = true; + opm.set_previous_version(CURRENT_VERSION, opm.version); // only check previous_version_by_key + return this->template put>(opm, METADATA_SERVICE_SUBGROUP_INDEX, metadata_service_shard_index); + } + + // we didn't find the object pool, but we do the normal 'remove', which has no effect but return a version. + dbg_default_warn("deleteing a non-existing objectpool:{}.", pathname); + return this->template remove>(pathname, METADATA_SERVICE_SUBGROUP_INDEX, metadata_service_shard_index); +} + +template +ObjectPoolMetadata ServiceClient::internal_find_object_pool( + const std::string& pathname, + std::shared_lock& rlck) { + auto components = str_tokenizer(pathname); + std::string prefix; + for(const auto& comp : components) { + prefix = prefix + PATH_SEPARATOR + comp; + if(object_pool_metadata_cache.find(prefix) != object_pool_metadata_cache.end()) { + return object_pool_metadata_cache.at(prefix).opm; + } + } + rlck.unlock(); + + // refresh and try again. + refresh_object_pool_metadata_cache(); + prefix = ""; + rlck.lock(); + for(const auto& comp : components) { + prefix = prefix + PATH_SEPARATOR + comp; + if(object_pool_metadata_cache.find(prefix) != object_pool_metadata_cache.end()) { + return object_pool_metadata_cache.at(prefix).opm; + } + } + return ObjectPoolMetadata::IV; +} + +template +ObjectPoolMetadata ServiceClient::find_object_pool(const std::string& pathname) { + std::shared_lock rlck(object_pool_metadata_cache_mutex); + return this->internal_find_object_pool(pathname, rlck); +} + +template +template +std::pair, std::string> ServiceClient::find_object_pool_and_affinity_set_by_key( + const KeyType& key) { + std::string object_pool_pathname = get_pathname(key); + if(object_pool_pathname.empty()) { + throw derecho::derecho_exception(std::string("Key:") + key + " does not belong to any object pool."); + } + + std::shared_lock rlck(object_pool_metadata_cache_mutex); + auto opm = this->internal_find_object_pool(object_pool_pathname, rlck); + + std::string affinity_set = ""; + if(opm.is_valid() && !opm.is_null() && !opm.deleted) { + affinity_set = object_pool_metadata_cache.at(opm.pathname).to_affinity_set(key); + } + + return {opm, affinity_set}; +} + +template +std::vector ServiceClient::list_object_pools(bool include_deleted, bool refresh) { + if(refresh) { + this->refresh_object_pool_metadata_cache(); + } + + std::vector ret; + std::shared_lock rlck(this->object_pool_metadata_cache_mutex); + for(auto& op : this->object_pool_metadata_cache) { + if(op.second.opm.deleted) { + if(include_deleted) { + ret.emplace_back(op.first + "(!)"); + } + } else { + ret.emplace_back(op.first); + } + } + + return ret; +} + +template +template +bool ServiceClient::register_notification_handler( + const cascade_notification_handler_t& handler, + const uint32_t subgroup_index) { + return register_notification_handler(handler, std::string{}, subgroup_index); +} + +template +template +bool ServiceClient::register_notification_handler( + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname, + const uint32_t subgroup_index) { + if(!is_external_client()) { + throw derecho_exception(std::string(__PRETTY_FUNCTION__) + "Cannot register notification handler because external_group_ptr is null."); + } + + std::unique_lock type_registry_lock(this->notification_handler_registry_mutex); + auto& per_type_registry = notification_handler_registry.template get(); + // Register Cascade's root handler: + // if subgroup_index exists in the per_type_registry, Cascade's root handler is registered already. + if(per_type_registry.find(subgroup_index) == per_type_registry.cend()) { + per_type_registry.emplace(subgroup_index, SubgroupNotificationHandler{}); + // register to subgroup_caller + auto& subgroup_caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + // to do ... register it. + per_type_registry.at(subgroup_index).initialize(subgroup_caller); + } + auto& subgroup_handlers = per_type_registry.at(subgroup_index); + + // Register the handler + std::lock_guard subgroup_handlers_lock(*subgroup_handlers.object_pool_notification_handlers_mutex); + bool ret = (subgroup_handlers.object_pool_notification_handlers.find(object_pool_pathname) != subgroup_handlers.object_pool_notification_handlers.cend()); + + if(handler) { + subgroup_handlers.object_pool_notification_handlers[object_pool_pathname] = handler; + } else { + subgroup_handlers.object_pool_notification_handlers[object_pool_pathname].reset(); + } + return ret; +} + +template +template +bool ServiceClient::type_recursive_register_notification_handler( + uint32_t type_index, + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname, + const uint32_t subgroup_index) { + if(type_index == 0) { + return this->template register_notification_handler(handler, object_pool_pathname, subgroup_index); + } else { + return this->template type_recursive_register_notification_handler( + type_index - 1, handler, object_pool_pathname, subgroup_index); + } +} + +template +template +bool ServiceClient::type_recursive_register_notification_handler( + uint32_t type_index, + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname, + const uint32_t subgroup_index) { + if(type_index == 0) { + return this->template register_notification_handler(handler, object_pool_pathname, subgroup_index); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +bool ServiceClient::register_notification_handler( + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + + return this->template type_recursive_register_notification_handler( + opm.subgroup_type_index, handler, object_pool_pathname, opm.subgroup_index); +} + +template +template +void ServiceClient::notify( + const Blob& msg, + const uint32_t subgroup_index, + const node_id_t client_id) const { + notify(msg, "", subgroup_index, client_id); +} + +template +template +void ServiceClient::notify( + const Blob& msg, + const std::string& object_pool_pathname, + const uint32_t subgroup_index, + const node_id_t client_id) const { + if(is_external_client()) { + throw derecho_exception(std::string(__PRETTY_FUNCTION__) + "Cannot notify an external client from an external client."); + } + + auto& client_handle = group_ptr->template get_client_callback(subgroup_index); + + // TODO: redesign to avoid memory copies. + CascadeNotificationMessage cascade_notification_message(object_pool_pathname, msg); + derecho::NotificationMessage derecho_notification_message(CASCADE_NOTIFICATION_MESSAGE_TYPE, mutils::bytes_size(cascade_notification_message)); + mutils::to_bytes(cascade_notification_message, derecho_notification_message.body); + + client_handle.template p2p_send(client_id, derecho_notification_message); +} + +template +template +void ServiceClient::type_recursive_notify( + uint32_t type_index, + const Blob& msg, + const std::string& object_pool_pathname, + const uint32_t subgroup_index, + const node_id_t client_id) const { + if(type_index == 0) { + this->template notify(msg, object_pool_pathname, subgroup_index, client_id); + } else { + this->template type_recursive_notify(type_index - 1, msg, object_pool_pathname, subgroup_index, client_id); + } +} + +template +template +void ServiceClient::type_recursive_notify( + uint32_t type_index, + const Blob& msg, + const std::string& object_pool_pathname, + const uint32_t subgroup_index, + const node_id_t client_id) const { + if(type_index == 0) { + this->template notify(msg, object_pool_pathname, subgroup_index, client_id); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +void ServiceClient::notify( + const Blob& msg, + const std::string& object_pool_pathname, + const node_id_t client_id) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + this->template type_recursive_notify(opm.subgroup_type_index, msg, object_pool_pathname, opm.subgroup_index, client_id); +} + +#ifdef ENABLE_EVALUATION + +template +template +derecho::rpc::QueryResults ServiceClient::dump_timestamp(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index) { + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + return subgroup_handle.template ordered_send(filename); + } else { + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, filename); + return subgroup_handle.template p2p_send(node_id, filename); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, filename); + return caller.template p2p_send(node_id, filename); + } +} + +template +template +void ServiceClient::type_recursive_dump( + uint32_t type_index, + uint32_t subgroup_index, + const std::string& filename) { + if(type_index == 0) { + this->template dump_timestamp(subgroup_index, filename); + } else { + this->template type_recursive_dump(type_index - 1, subgroup_index, filename); + } +} + +template +template +void ServiceClient::type_recursive_dump( + uint32_t type_index, + uint32_t subgroup_index, + const std::string& filename) { + if(type_index == 0) { + this->template dump_timestamp(subgroup_index, filename); + } else { + throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); + } +} + +template +void ServiceClient::dump_timestamp(const std::string& filename, const std::string& object_pool_pathname) { + auto opm = find_object_pool(object_pool_pathname); + if(!opm.is_valid() || opm.is_null() || opm.deleted) { + throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); + } + + this->template type_recursive_dump(opm.subgroup_type_index, opm.subgroup_index, filename); +} + +template +template +void ServiceClient::dump_timestamp(const uint32_t subgroup_index, const std::string& filename) { + uint32_t shards = get_number_of_shards(subgroup_index); + for(uint32_t shard_index = 0; shard_index < shards; shard_index++) { + auto result = this->template dump_timestamp(filename, subgroup_index, shard_index); + result.get(); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::dump_timestamp_workaround(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index, const node_id_t node_id) { + if(!is_external_client()) { + std::lock_guard lck(this->group_ptr_mutex); + if(static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { + auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, filename); + } else { + auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); + return subgroup_handle.template p2p_send(node_id, filename); + } + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + return caller.template p2p_send(node_id, filename); + } +} + +template +template +derecho::rpc::QueryResults ServiceClient::perf_put(const uint32_t message_size, const uint64_t duration_sec, const uint32_t subgroup_index, const uint32_t shard_index) { + if(!is_external_client()) { + // 'perf_put' must be issued from an external client. + throw derecho::derecho_exception{"perf_put must be issued from an external client."}; + } else { + std::lock_guard lck(this->external_group_ptr_mutex); + auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); + node_id_t node_id = pick_member_by_policy(subgroup_index, shard_index, 0); + return caller.template p2p_send(node_id, message_size, duration_sec); + } +} +#endif // ENABLE_EVALUATION + +#ifndef __WITHOUT_SERVICE_SINGLETONS__ +template +const std::vector ServiceClient::subgroup_type_order{typeid(CascadeTypes)...}; + +template +const uint32_t ServiceClient::invalid_subgroup_type_index = 0xffffffff; + +template +template +uint32_t ServiceClient::get_subgroup_type_index() { + uint32_t index = 0; + while(index < subgroup_type_order.size()) { + if(std::type_index(typeid(SubgroupType)) == subgroup_type_order.at(index)) { + return index; + } + index++; + } + return invalid_subgroup_type_index; +} + +template +std::unique_ptr> ServiceClient::service_client_singleton_ptr; + +template +std::mutex ServiceClient::singleton_mutex; + +template +void ServiceClient::initialize(derecho::Group, CascadeTypes...>* _group_ptr) { + std::lock_guard lock_guard(singleton_mutex); + if(!service_client_singleton_ptr) { + dbg_default_trace("initializing ServiceClient singleton as cascade member, group pointer={:p}", static_cast(_group_ptr)); + service_client_singleton_ptr = std::unique_ptr>(new ServiceClient(_group_ptr)); + } +} + +template +ServiceClient& ServiceClient::get_service_client() { + if(!service_client_singleton_ptr) { + std::lock_guard lock_guard(singleton_mutex); + // test again in case another thread has initialized it already. + if(!service_client_singleton_ptr) { + dbg_default_trace("initializing ServiceClient singleton as external client"); + service_client_singleton_ptr = std::unique_ptr>(new ServiceClient(nullptr)); + } + } + return *service_client_singleton_ptr; +} +#endif //__WITHOUT_SERVICE_SINGLETONS__ + +} // namespace cascade +} // namespace derecho diff --git a/include/cascade/detail/service_impl.hpp b/include/cascade/detail/service_impl.hpp index 7d992e54..ef407301 100644 --- a/include/cascade/detail/service_impl.hpp +++ b/include/cascade/detail/service_impl.hpp @@ -1,14 +1,14 @@ -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include using namespace std::chrono_literals; @@ -139,2107 +139,6 @@ void Service::wait() { } #endif//__WITHOUT_SERVICE_SINGLETONS__ -template -std::unique_ptr client_stub_factory() { - return std::make_unique(); -} - - -#ifdef ENABLE_EVALUATION -#define LOG_SERVICE_CLIENT_TIMESTAMP(tag,msgid) \ - TimestampLogger::log(tag,this->get_my_id(),msgid); -#else -#define LOG_SERVICE_CLIENT_TIMESTAMP(tag,msgid) -#endif - - -template -ServiceClient::ServiceClient(derecho::Group,CascadeTypes...>* _group_ptr): - external_group_ptr(nullptr), - group_ptr(_group_ptr) { - if (group_ptr == nullptr) { - this->external_group_ptr = - std::make_unique,CascadeTypes...>>( - client_stub_factory>, - client_stub_factory...); - } -} - -template -bool ServiceClient::is_external_client() const { - return (group_ptr == nullptr) && (external_group_ptr != nullptr); -} - -template -node_id_t ServiceClient::get_my_id() const { - if (!is_external_client()) { - return group_ptr->get_my_id(); - } else { - return external_group_ptr->get_my_id(); - } -} - -template -std::vector ServiceClient::get_members() const { - if (!is_external_client()) { - return group_ptr->get_members(); - } else { - return external_group_ptr->get_members(); - } -} - -template -template -std::vector ServiceClient::get_shard_members(uint32_t subgroup_index, uint32_t shard_index) const { - if (!is_external_client()) { - std::vector> subgroup_members = group_ptr->template get_subgroup_members(subgroup_index); - if (subgroup_members.size() > shard_index) { - return subgroup_members[shard_index]; - } else { - return {}; - } - } else { - return external_group_ptr->template get_shard_members(subgroup_index,shard_index); - } -} - -template -template -std::vector ServiceClient::type_recursive_get_shard_members(uint32_t type_index, - uint32_t subgroup_index, uint32_t shard_index) const { - if (type_index == 0) { - return this->template get_shard_members(subgroup_index,shard_index); - } else { - return this->template type_recursive_get_shard_members(type_index-1,subgroup_index,shard_index); - } -} - -template -template -std::vector ServiceClient::type_recursive_get_shard_members(uint32_t type_index, - uint32_t subgroup_index, uint32_t shard_index) const { - if (type_index == 0) { - return this->template get_shard_members(subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); - } -} - -template -std::vector ServiceClient::get_shard_members( - const std::string& object_pool_pathname,uint32_t shard_index) { - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - return this->template type_recursive_get_shard_members(opm.subgroup_type_index,opm.subgroup_index,shard_index); -} - -template -template -std::vector> ServiceClient::get_subgroup_members(uint32_t subgroup_index) const { - if (!is_external_client()) { - return group_ptr->template get_subgroup_members(subgroup_index); - } else { - return external_group_ptr->template get_subgroup_members(subgroup_index); - } -} - -template -template -std::vector> ServiceClient::type_recursive_get_subgroup_members( - uint32_t type_index, uint32_t subgroup_index) const { - if (type_index == 0) { - return this->template get_subgroup_members(subgroup_index); - } else { - return this->template type_recursive_get_subgroup_members(type_index-1,subgroup_index); - } -} - -template -template -std::vector> ServiceClient::type_recursive_get_subgroup_members( - uint32_t type_index, uint32_t subgroup_index) const { - if (type_index == 0) { - return this->template get_subgroup_members(subgroup_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); - } -} - -template -std::vector> ServiceClient::get_subgroup_members( - const std::string& object_pool_pathname) { - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - return this->template type_recursive_get_subgroup_members(opm.subgroup_type_index,opm.subgroup_index); -} - -template -template -uint32_t ServiceClient::get_number_of_subgroups() const { - if (!is_external_client()) { - return group_ptr->template get_num_subgroups(); - } else { - return external_group_ptr->template get_number_of_subgroups(); - } -} - -template -template -uint32_t ServiceClient::get_number_of_shards(uint32_t subgroup_index) const { - if (!is_external_client()) { - return group_ptr->template get_subgroup_members(subgroup_index).size(); - } else { - return external_group_ptr->template get_number_of_shards(subgroup_index); - } -} - -template -template -uint32_t ServiceClient::type_recursive_get_number_of_shards ( - uint32_t type_index,uint32_t subgroup_index) const { - if (type_index == 0 ) { - return this->template get_number_of_shards(subgroup_index); - } else { - return this->template type_recursive_get_number_of_shards(type_index-1,subgroup_index); - } -} - -template -template -uint32_t ServiceClient::type_recursive_get_number_of_shards ( - uint32_t type_index,uint32_t subgroup_index) const { - if (type_index == 0) { - return this->template get_number_of_shards(subgroup_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); - } -} - -template -uint32_t ServiceClient::get_number_of_shards ( - uint32_t subgroup_type_index,uint32_t subgroup_index) const { - return type_recursive_get_number_of_shards(subgroup_type_index,subgroup_index); -} - -template -uint32_t ServiceClient::get_number_of_shards ( - const std::string& object_pool_pathname) { - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - return get_number_of_shards(opm.subgroup_type_index,opm.subgroup_index); -} - -template -template -int32_t ServiceClient::get_my_shard(uint32_t subgroup_index) const { - if (!is_external_client()) { - return group_ptr->template get_my_shard(subgroup_index); - } else { - return -1; - } -} - -template -template -int32_t ServiceClient::type_recursive_get_my_shard ( - uint32_t type_index,uint32_t subgroup_index) const { - if (type_index == 0) { - return this->template get_my_shard(subgroup_index); - } else { - return this->template type_recursive_get_number_of_shards(type_index-1,subgroup_index); - } -} - -template -template -int32_t ServiceClient::type_recursive_get_my_shard ( - uint32_t type_index, uint32_t subgroup_index) const { - if (type_index == 0) { - return this->template get_my_shard(subgroup_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + " type index is out of boundary"); - } -} - -template -int32_t ServiceClient::get_my_shard ( - uint32_t subgroup_type_index, uint32_t subgroup_index) const { - return this->template type_recursive_get_my_shard(subgroup_type_index,subgroup_index); -} - -template -int32_t ServiceClient::get_my_shard ( - const std::string& object_pool_pathname) { - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - return get_my_shard(opm.subgroup_type_index,opm.subgroup_index); -} - -template -template -void ServiceClient::set_member_selection_policy(uint32_t subgroup_index,uint32_t shard_index, - ShardMemberSelectionPolicy policy, node_id_t user_specified_node_id) { - // write lock policies - std::unique_lock wlck(this->member_selection_policies_mutex); - // update map - this->member_selection_policies[std::make_tuple(std::type_index(typeid(SubgroupType)),subgroup_index,shard_index)] = - std::make_tuple(policy,user_specified_node_id); -} - -template -template -std::tuple ServiceClient::get_member_selection_policy( - uint32_t subgroup_index, uint32_t shard_index) const { - // read lock policies - std::shared_lock rlck(this->member_selection_policies_mutex); - auto key = std::make_tuple(std::type_index(typeid(SubgroupType)),subgroup_index,shard_index); - // read map - if (member_selection_policies.find(key) != member_selection_policies.end()) { - return member_selection_policies.at(key); - } else { - return std::make_tuple(DEFAULT_SHARD_MEMBER_SELECTION_POLICY,INVALID_NODE_ID); - } -} - -template -template -void ServiceClient::refresh_member_cache_entry(uint32_t subgroup_index, - uint32_t shard_index) { - auto key = std::make_tuple(std::type_index(typeid(SubgroupType)),subgroup_index,shard_index); - auto members = get_shard_members(subgroup_index,shard_index); - std::shared_lock rlck(member_cache_mutex); - if (member_cache.find(key) == member_cache.end()) { - rlck.unlock(); - std::unique_lock wlck(member_cache_mutex); - member_cache[key] = members; - } else { - member_cache[key].swap(members); - } -} - -template -template -std::tuple ServiceClient::key_to_shard( - const KeyType& key, - bool check_object_location) { - - auto pair = find_object_pool_and_affinity_set_by_key(key); - - auto& opm = std::get<0>(pair); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to identify the object_pool from key:" + key); - } - auto& affinity_set = std::get<1>(pair); - - return std::tuple{opm.subgroup_type_index,opm.subgroup_index, - opm.key_to_shard_index(key,affinity_set,get_number_of_shards(opm.subgroup_type_index,opm.subgroup_index),check_object_location)}; -} - -template -ServiceClient::ObjectPoolMetadataCacheEntry::ObjectPoolMetadataCacheEntry( - const ObjectPoolMetadata& _opm): opm(_opm), database(nullptr) { - if (opm.affinity_set_regex.size() > 0) { - hs_compile_error_t* compile_err; - if (hs_compile(opm.affinity_set_regex.c_str(), HS_FLAG_DOTALL|HS_FLAG_SOM_LEFTMOST, HS_MODE_BLOCK, NULL, &database, - &compile_err) != HS_SUCCESS) { - hs_free_compile_error(compile_err); - dbg_default_error("Compilation of affinity set regex:" + opm.affinity_set_regex + " failed with message:" + - compile_err->message); - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + - ": compilation of affinity_set_regex:" + - opm.affinity_set_regex + - " failed with message:" + - compile_err->message); - } - } -} - -template -ServiceClient::ObjectPoolMetadataCacheEntry::~ObjectPoolMetadataCacheEntry() { - if (this->database != nullptr) { - hs_free_database(database); - this->database = nullptr; - } -} - -template -inline std::string ServiceClient::ObjectPoolMetadataCacheEntry::to_affinity_set( - const std::string& key_string) { - if (key_string.size() > 0 && this->opm.affinity_set_regex.size() > 0) { - if (scratch == nullptr) { - if (hs_alloc_scratch(database, &scratch) != HS_SUCCESS) { - hs_free_database(database); - dbg_default_error("failed to allocate hyperscan scratch space."); - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + - " failed to allocate hyperscan scratch space."); - } - } - - struct hs_scan_ctxt { - unsigned long long from = 0; - unsigned long long to = 0; - } ctxt; - - hs_scan(database, key_string.c_str(), key_string.size(), HS_FLAG_SOM_LEFTMOST, scratch, - [](unsigned int /*id*/, unsigned long long from, - unsigned long long to, unsigned int /*flags*/, - void* ctxt)->int { - struct hs_scan_ctxt* p_hs_ctxt = static_cast(ctxt); - p_hs_ctxt->from = from; - p_hs_ctxt->to = to; - return 0; // do the longest match - }, - &ctxt); - if (ctxt.to > ctxt.from) { - return key_string.substr(ctxt.from,(ctxt.to-ctxt.from)); - } - } - - return key_string; -} - -template -thread_local hs_scratch_t* ServiceClient::ObjectPoolMetadataCacheEntry::scratch = nullptr; - -template -template -node_id_t ServiceClient::pick_member_by_policy(uint32_t subgroup_index, - uint32_t shard_index, - const KeyTypeForHashing& key_for_hashing, - bool retry) { - ShardMemberSelectionPolicy policy; - node_id_t last_specified_node_id_or_index; - - std::tie(policy,last_specified_node_id_or_index) = get_member_selection_policy(subgroup_index,shard_index); - - if (policy == ShardMemberSelectionPolicy::UserSpecified) { - return last_specified_node_id_or_index; - } - - auto key = std::make_tuple(std::type_index(typeid(SubgroupType)),subgroup_index,shard_index); - - if (member_cache.find(key) == member_cache.end() || retry) { - refresh_member_cache_entry(subgroup_index,shard_index); - } - - std::shared_lock rlck(member_cache_mutex); - - node_id_t node_id = last_specified_node_id_or_index; - - switch(policy) { - case ShardMemberSelectionPolicy::FirstMember: - node_id = member_cache.at(key).front(); - break; - case ShardMemberSelectionPolicy::LastMember: - node_id = member_cache.at(key).back(); - break; - case ShardMemberSelectionPolicy::Random: - node_id = member_cache.at(key)[get_time()%member_cache.at(key).size()]; // use time as random source. - break; - case ShardMemberSelectionPolicy::FixedRandom: - if (node_id == INVALID_NODE_ID || retry) { - node_id = member_cache.at(key)[get_time()%member_cache.at(key).size()]; // use time as random source. - } - break; - case ShardMemberSelectionPolicy::RoundRobin: - { - std::shared_lock rlck(member_selection_policies_mutex); - node_id = static_cast(node_id+1)%member_cache.at(key).size(); - auto new_policy = std::make_tuple(ShardMemberSelectionPolicy::RoundRobin,node_id); - member_selection_policies[key].swap(new_policy); - } - node_id = member_cache.at(key)[node_id]; - break; - case ShardMemberSelectionPolicy::KeyHashing: - { - uint64_t hash = 0; - if constexpr (std::is_integral_v) { - hash = static_cast(key_for_hashing); - } else if constexpr (std::is_convertible_v) { - hash = std::hash{}(std::string(key_for_hashing)); - } else { - dbg_default_warn("Key type {} is neither integral nor string, falling back to FirstMember policy. {}:{}", - typeid(KeyTypeForHashing).name(), __FILE__, __LINE__); - } - node_id = member_cache.at(key)[hash % member_cache.at(key).size()]; - } - break; - default: - throw derecho::derecho_exception("Unknown member selection policy:" + std::to_string(static_cast(policy)) ); - } - - return node_id; -} - -template -template -derecho::rpc::QueryResults ServiceClient::put( - const typename SubgroupType::ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_PUT_START, - (std::is_base_of::value?value.get_message_id():0)); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - // ordered put as a shard member - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - return subgroup_handle.template ordered_send(value,as_trigger); - } else { - // p2p put - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,value.get_key_ref()); - try { - // as a subgroup member - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,value,as_trigger); - } catch (derecho::invalid_subgroup_exception& ex) { - // as an external caller - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,value,as_trigger); - } - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,value.get_key_ref()); - return caller.template p2p_send(node_id,value,as_trigger); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_put( - uint32_t type_index, - const ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger) { - if (type_index == 0) { - return this->template put(value,subgroup_index,shard_index,as_trigger); - } else { - return this->template type_recursive_put(type_index-1,value,subgroup_index,shard_index,as_trigger); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_put( - uint32_t type_index, - const ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger) { - if (type_index == 0) { - return this->template put(value,subgroup_index,shard_index,as_trigger); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::put( - const ObjectType& value, bool as_trigger) { - - // STEP 1 - get key - if constexpr (!std::is_base_of_v,ObjectType>) { - throw derecho::derecho_exception(std::string("ServiceClient<>::put() only support object of type ICascadeObject,but we get ") + typeid(ObjectType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(value.get_key_ref()); - - // STEP 3 - call recursive put - return this->template type_recursive_put(subgroup_type_index,value,subgroup_index,shard_index,as_trigger); -} - -template -template -void ServiceClient::put_and_forget( - const typename SubgroupType::ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_PUT_AND_FORGET_START, - (std::is_base_of::value?value.get_message_id():0)); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - // do ordered put as a shard member (Replicated). - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - subgroup_handle.template ordered_send(value,as_trigger); - } else { - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,value.get_key_ref()); - // do p2p put - try{ - // as a subgroup member - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - subgroup_handle.template p2p_send(node_id,value,as_trigger); - } catch (derecho::invalid_subgroup_exception& ex) { - // as an external caller - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - subgroup_handle.template p2p_send(node_id,value,as_trigger); - } - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,value.get_key_ref()); - caller.template p2p_send(node_id,value,as_trigger); - } -} - -template -template -void ServiceClient::type_recursive_put_and_forget( - uint32_t type_index, - const ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger) { - if (type_index == 0) { - put_and_forget(value,subgroup_index,shard_index,as_trigger); - } else { - type_recursive_put_and_forget(type_index-1,value,subgroup_index,shard_index,as_trigger); - } -} - -template -template -void ServiceClient::type_recursive_put_and_forget( - uint32_t type_index, - const ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger) { - if (type_index == 0) { - put_and_forget(value,subgroup_index,shard_index,as_trigger); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -void ServiceClient::put_and_forget(const ObjectType& value,bool as_trigger) { - // STEP 1 - get key - if constexpr (!std::is_base_of_v,ObjectType>) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports object of type ICascadeObject,but we get ") + typeid(ObjectType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(value.get_key_ref()); - - // STEP 3 - call recursive put_and_forget - this->template type_recursive_put_and_forget(subgroup_type_index,value,subgroup_index,shard_index,as_trigger); -} - -template -template -derecho::rpc::QueryResults ServiceClient::trigger_put( - const typename SubgroupType::ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_TRIGGER_PUT_START, - (std::is_base_of::value?value.get_message_id():0)); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index){ - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,value.get_key_ref()); - dbg_default_trace("trigger_put to node {}",node_id); - return subgroup_handle.template p2p_send(node_id,value); - } else { - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,value.get_key_ref()); - dbg_default_trace("trigger_put to node {}",node_id); - return subgroup_handle.template p2p_send(node_id,value); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,value.get_key_ref()); - dbg_default_trace("trigger_put to node {}",node_id); - return caller.template p2p_send(node_id,value); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_trigger_put( - uint32_t type_index, - const ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return trigger_put(value,subgroup_index,shard_index); - } else { - return type_recursive_trigger_put(type_index-1,value,subgroup_index,shard_index); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_trigger_put( - uint32_t type_index, - const ObjectType& value, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return trigger_put(value,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::trigger_put( - const ObjectType& value) { - // STEP 1 - get key - if constexpr (!std::is_base_of_v,ObjectType>) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports object of type ICascadeObject,but we get ") + typeid(ObjectType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(value.get_key_ref()); - - // STEP 3 - call recursive trigger_put - return this->template type_recursive_trigger_put(subgroup_type_index,value,subgroup_index,shard_index); -} - -template -template -void ServiceClient::collective_trigger_put( - const typename SubgroupType::ObjectType& value, - uint32_t subgroup_index, - std::unordered_map>>& nodes_and_futures) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_COLLECTIVE_TRIGGER_PUT_START, - (std::is_base_of::value?value.get_message_id():0)); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - if (group_ptr->template get_my_shard(subgroup_index) != -1) { - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - for (auto& kv: nodes_and_futures) { - nodes_and_futures[kv.first] = std::make_unique>( - std::move(subgroup_handle.template p2p_send(kv.first,value))); - } - } else { - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - for (auto& kv: nodes_and_futures) { - nodes_and_futures[kv.first] = std::make_unique>( - std::move(subgroup_handle.template p2p_send(kv.first,value))); - } - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - for (auto& kv: nodes_and_futures) { - nodes_and_futures[kv.first] = std::make_unique>( - std::move(caller.template p2p_send(kv.first,value))); - } - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::remove( - const typename SubgroupType::KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_REMOVE_START,0); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - // do ordered remove as a member (Replicated). - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - return subgroup_handle.template ordered_send(key); - } else { - // do p2p remove - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - try { - // as a subgroup member - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key); - } catch (derecho::invalid_subgroup_exception& ex) { - // as an external caller - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key); - } - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - return caller.template p2p_send(node_id,key); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_remove( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template remove(key,subgroup_index,shard_index); - } else { - return this->template type_recursive_remove(type_index-1,key,subgroup_index,shard_index); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_remove( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template remove(key,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::remove( - const KeyType& key) { - // STEP 1 - get key - if constexpr (!std::is_convertible_v) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(key); - - // STEP 3 - call recursive remove - return this->template type_recursive_remove(subgroup_type_index,key,subgroup_index,shard_index); -} - -template -template -derecho::rpc::QueryResults ServiceClient::get( - const typename SubgroupType::KeyType& key, - const persistent::version_t& version, - bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_GET_START,0); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - try { - // do p2p get as a subgroup member - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - // local get - auto obj = subgroup_handle.get_ref().get(key,version,stable); - auto pending_results = std::make_shared>(); - pending_results->fulfill_map({node_id}); - pending_results->set_value(node_id,obj); - auto query_results = pending_results->get_future(); - return std::move(*query_results); - } - return subgroup_handle.template p2p_send(node_id,key,version,stable,false); - } catch (derecho::invalid_subgroup_exception& ex) { - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key,version,stable,false); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - return caller.template p2p_send(node_id,key,version,stable,false); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::multi_get( - const typename SubgroupType::KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_MULTI_GET_START,0); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - try { - // do p2p multi_get as a subgroup member. - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,key); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p multi_get as an external caller. - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - return caller.template p2p_send(node_id,key); - } -} - -template -template -auto ServiceClient::type_recursive_get( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get(key,version,stable,subgroup_index,shard_index); - } else { - return this->template type_recursive_get(type_index-1,key,version,stable,subgroup_index,shard_index); - } -} - -template -template -auto ServiceClient::type_recursive_get( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get(key,version,stable,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -auto ServiceClient::get( - // const std::decay_t>& key, - const KeyType& key, - const persistent::version_t& version, - bool stable) { - // STEP 1 - get key - if constexpr (!std::is_convertible_v) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(key); - - // STEP 3 - call recursive get - return this->template type_recursive_get(subgroup_type_index,key,version,stable,subgroup_index,shard_index); -} - -template -template -auto ServiceClient::type_recursive_multi_get( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template multi_get(key,subgroup_index,shard_index); - } else { - return this->template type_recursive_multi_get(type_index-1,key,subgroup_index,shard_index); - } -} - -template -template -auto ServiceClient::type_recursive_multi_get( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template multi_get(key,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -auto ServiceClient::multi_get( - const KeyType& key) { - // STEP 1 - get key - if constexpr (!std::is_convertible_v) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(key); - - // STEP 3 - call recursive get - return this->template type_recursive_multi_get(subgroup_type_index,key,subgroup_index,shard_index); -} - -template -template -derecho::rpc::QueryResults ServiceClient::get_by_time( - const typename SubgroupType::KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - try { - // do p2p get_by_time - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - // as a shard member. - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,key,ts_us,stable); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p get_by_time as an external caller - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key,ts_us,stable); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - return caller.template p2p_send(node_id,key,ts_us,stable); - } -} - -template -template -auto ServiceClient::type_recursive_get_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get_by_time(key,ts_us,stable,subgroup_index,shard_index); - } else { - return this->template type_recursive_get_by_time(type_index-1,key,ts_us,stable,subgroup_index,shard_index); - } -} - -template -template -auto ServiceClient::type_recursive_get_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get_by_time(key,ts_us,stable,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -auto ServiceClient::get_by_time( - const KeyType& key, - const uint64_t& ts_us, - const bool stable) { - // STEP 1 - get key - if constexpr (!std::is_convertible_v) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(key); - - // STEP 3 - call recursive get_by_time - return this->template type_recursive_get_by_time(subgroup_type_index,key,ts_us,stable,subgroup_index,shard_index); -} - -template -template -derecho::rpc::QueryResults ServiceClient::get_size( - const typename SubgroupType::KeyType& key, - const persistent::version_t& version, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_GET_SIZE_START,0); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - try { - // do p2p get_size as a subgroup_member - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - // as a shard member. - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,key,version,stable,false); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p get_size as an external caller - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key,version,stable,false); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - return caller.template p2p_send(node_id,key,version,stable,false); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_get_size( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get_size(key,version,stable,subgroup_index,shard_index); - } else { - return this->template type_recursive_get_size(type_index-1,key,version,stable,subgroup_index,shard_index); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_get_size( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get_size(key,version,stable,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::get_size( - const KeyType& key, - const persistent::version_t& version, - const bool stable) { - // STEP 1 - verify the keys - if constexpr (!std::is_convertible_v) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(key); - - // STEP 3 - call recursive get - return this->template type_recursive_get_size(subgroup_type_index,key,version,stable,subgroup_index,shard_index); -} - -template -template -derecho::rpc::QueryResults ServiceClient::multi_get_size( - const typename SubgroupType::KeyType& key, - uint32_t subgroup_index, uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_MULTI_GET_SIZE_START,0); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - try { - // do p2p multi_get_size as a subgroup member. - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,key); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p multi_get_size as an external caller. - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - return caller.template p2p_send(node_id,key); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_multi_get_size( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template multi_get_size(key,subgroup_index,shard_index); - } else { - return this->template type_recursive_multi_get_size(type_index-1,key,subgroup_index,shard_index); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_multi_get_size( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template multi_get_size(key,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::multi_get_size(const KeyType& key) { - // STEP 1 - verify the keys - if constexpr (!std::is_convertible_v) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(key); - - // STEP 3 - call recursive get - return this->template type_recursive_multi_get_size(subgroup_type_index,key,subgroup_index,shard_index); -} - -template -template -derecho::rpc::QueryResults ServiceClient::get_size_by_time( - const typename SubgroupType::KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - try { - // do p2p get_size_by_time as a subgroup member. - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,key,ts_us,stable); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p get_size_by_time as an external caller. - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,key,ts_us,stable); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,key); - return caller.template p2p_send(node_id,key,ts_us,stable); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_get_size_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get_size_by_time(key,ts_us,stable,subgroup_index,shard_index); - } else { - return this->template type_recursive_get_size_by_time(type_index-1,key,ts_us,stable,subgroup_index,shard_index); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::type_recursive_get_size_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (type_index == 0) { - return this->template get_size_by_time(key,ts_us,stable,subgroup_index,shard_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::get_size_by_time( - const KeyType& key, - const uint64_t& ts_us, - const bool stable) { - // STEP 1 - verify the keys - if constexpr (!std::is_convertible_v) { - throw derecho::derecho_exception(__PRETTY_FUNCTION__ + std::string(" only supports string key,but we get ") + typeid(KeyType).name()); - } - - // STEP 2 - get shard - uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(key); - - // STEP 3 - call recursive get - return this->template type_recursive_get_size_by_time(subgroup_type_index,key,ts_us,stable,subgroup_index,shard_index); -} - -template -template -derecho::rpc::QueryResults> ServiceClient::list_keys( - const persistent::version_t& version, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_LIST_KEYS_START,0); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - try { - // do p2p list_keys as a subgroup member. - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,"",version,stable); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p list_keys as an external client. - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,"",version,stable); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - return caller.template p2p_send(node_id,"",version,stable); - } -} - -template -template -auto ServiceClient::type_recursive_list_keys( - uint32_t type_index, - const persistent::version_t& version, - const bool stable, - const std::string& object_pool_pathname) { - if (type_index == 0) { - return this->template __list_keys(version,stable,object_pool_pathname); - } else { - return this->template type_recursive_list_keys(type_index-1, version, stable, object_pool_pathname); - } -} - -template -template -auto ServiceClient::type_recursive_list_keys( - uint32_t type_index, - const persistent::version_t& version, - const bool stable, - const std::string& object_pool_pathname) { - if (type_index == 0) { - return this->template __list_keys(version,stable,object_pool_pathname); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -std::vector>>> ServiceClient::__list_keys( - const persistent::version_t& version, - const bool stable, - const std::string& object_pool_pathname){ - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - uint32_t subgroup_index = opm.subgroup_index; - uint32_t shards = get_number_of_shards(subgroup_index); - std::vector>>> result; - for (uint32_t shard_index = 0; shard_index < shards; shard_index ++){ - if (!is_external_client()) { - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - try { - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - auto shard_keys = subgroup_handle.template p2p_send(node_id,object_pool_pathname,version,stable); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } catch (derecho::invalid_subgroup_exception& ex) { - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - auto shard_keys= subgroup_handle.template p2p_send(node_id,object_pool_pathname,version,stable); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - auto shard_keys = caller.template p2p_send(node_id,object_pool_pathname,version,stable); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } - } - return result; -} - -template -auto ServiceClient::list_keys( - const persistent::version_t& version, - const bool stable, - const std::string& object_pool_pathname) { - volatile uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(object_pool_pathname+"/_"); - return this->template type_recursive_list_keys(subgroup_type_index,version,stable,object_pool_pathname); -} - -template -inline ReturnType wait_for_future(derecho::rpc::QueryResults& result){ - // iterate through ReplyMap - for (auto& reply_future: result.get()) { - ReturnType reply = static_cast(reply_future.second.get()); - return reply; - } - return ReturnType(); -} - - -template -template -std::vector ServiceClient::wait_list_keys( - std::vector>>>& future){ - std::vector result; - // iterate over each shard's Query result - for(auto& query_result: future){ - std::vector reply = wait_for_future>(*(query_result.get())); - std::move(reply.begin(), reply.end(), std::back_inserter(result)); - } - return result; -} - -template -template -derecho::rpc::QueryResults> ServiceClient::multi_list_keys( - uint32_t subgroup_index, - uint32_t shard_index) { - LOG_SERVICE_CLIENT_TIMESTAMP(TLT_SERVICE_CLIENT_MULTI_LIST_KEYS_START,0); - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - try { - // do p2p multi_list_keys as a subgroup member. - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,""); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p multi_list_keys as an external client. - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - return subgroup_handle.template p2p_send(node_id,""); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - return caller.template p2p_send(node_id,""); - } -} - -template -template -auto ServiceClient::type_recursive_multi_list_keys ( - uint32_t type_index, - const std::string& object_pool_pathname) { - if (type_index == 0) { - return this->template __multi_list_keys(object_pool_pathname); - } else { - return this->template type_recursive_multi_list_keys(type_index-1,object_pool_pathname); - } -} - -template -template -auto ServiceClient::type_recursive_multi_list_keys ( - uint32_t type_index, - const std::string& object_pool_pathname) { - if (type_index == 0) { - return this->template __multi_list_keys(object_pool_pathname); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -std::vector>>> ServiceClient::__multi_list_keys(const std::string& object_pool_pathname) { - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - uint32_t subgroup_index = opm.subgroup_index; - uint32_t shards = get_number_of_shards(subgroup_index); - std::vector>>> result; - for (uint32_t shard_index = 0; shard_index < shards; shard_index ++){ - if (!is_external_client()) { - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,object_pool_pathname); - try { - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - auto shard_keys = subgroup_handle.template p2p_send(node_id,object_pool_pathname); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } catch (derecho::invalid_subgroup_exception& ex) { - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - auto shard_keys= subgroup_handle.template p2p_send(node_id,object_pool_pathname); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,object_pool_pathname); - auto shard_keys = caller.template p2p_send(node_id,object_pool_pathname); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } - } - return result; -} - -template -auto ServiceClient::multi_list_keys(const std::string& object_pool_pathname) { - volatile uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(object_pool_pathname+"/_"); - return this->template type_recursive_multi_list_keys(subgroup_type_index,object_pool_pathname); -} - -template -template -derecho::rpc::QueryResults> ServiceClient::list_keys_by_time( - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index) { - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - try { - // do p2p list_keys_by_time as a subgroup member - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - return subgroup_handle.template p2p_send(node_id,"",ts_us,stable); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p list_keys_by_time as an external client. - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,"",ts_us,stable); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - return caller.template p2p_send(node_id,"",ts_us,stable); - } -} - -template -template -auto ServiceClient::type_recursive_list_keys_by_time( - uint32_t type_index, - const uint64_t& ts_us, - const bool stable, - const std::string& object_pool_pathname) { - if (type_index == 0) { - return this->template __list_keys_by_time(ts_us,stable,object_pool_pathname); - } else { - return this->template type_recursive_list_keys_by_time(type_index-1,ts_us,stable,object_pool_pathname); - } -} - -template -template -auto ServiceClient::type_recursive_list_keys_by_time( - uint32_t type_index, - const uint64_t& ts_us, - const bool stable, - const std::string& object_pool_pathname) { - if (type_index == 0) { - return this->template __list_keys_by_time(ts_us,stable,object_pool_pathname); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -template -std::vector>>> ServiceClient::__list_keys_by_time( - const uint64_t& ts_us, - const bool stable, - const std::string& object_pool_pathname){ - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - uint32_t subgroup_index = opm.subgroup_index; - uint32_t shards = get_number_of_shards(subgroup_index); - std::vector>>> result; - for (uint32_t shard_index = 0; shard_index < shards; shard_index ++){ - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,object_pool_pathname); - try { - // do p2p list_keys_by_time as a subgroup member. - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - node_id = group_ptr->get_my_id(); - } - auto shard_keys = subgroup_handle.template p2p_send(node_id,object_pool_pathname,ts_us,stable); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } catch (derecho::invalid_subgroup_exception& ex) { - // do p2p list_keys_by_time as an external client. - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - auto shard_keys = subgroup_handle.template p2p_send(node_id,object_pool_pathname,ts_us,stable); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - // call as an external client (ExternalClientCaller). - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,object_pool_pathname); - auto shard_keys = caller.template p2p_send(node_id,object_pool_pathname,ts_us,stable); - result.emplace_back(std::make_unique>>(std::move(shard_keys))); - } - } - return result; -} - -template -auto ServiceClient::list_keys_by_time(const uint64_t& ts_us, const bool stable, const std::string& object_pool_pathname) { - volatile uint32_t subgroup_type_index,subgroup_index,shard_index; - std::tie(subgroup_type_index,subgroup_index,shard_index) = this->template key_to_shard(object_pool_pathname+"/_"); - return this->template type_recursive_list_keys_by_time(subgroup_type_index,ts_us,stable,object_pool_pathname); -} - -template -void ServiceClient::refresh_object_pool_metadata_cache() { - std::unordered_map refreshed_metadata; - uint32_t num_shards = this->template get_number_of_shards>(METADATA_SERVICE_SUBGROUP_INDEX); - for(uint32_t shard=0;shardtemplate multi_list_keys>(METADATA_SERVICE_SUBGROUP_INDEX,shard); - for (auto& reply : results.get()) { // only once - for(auto& key: reply.second.get()) { // iterate over keys - // we only read the stable version. - auto opm_result = this->template get>(key,CURRENT_VERSION,false,METADATA_SERVICE_SUBGROUP_INDEX,shard); - for (auto& opm_reply:opm_result.get()) { // only once - refreshed_metadata.emplace(key,opm_reply.second.get()); - break; - } - } - break; - } - } - - std::unique_lock wlck(object_pool_metadata_cache_mutex); - this->object_pool_metadata_cache = std::move(refreshed_metadata); -} - -template -template -derecho::rpc::QueryResults ServiceClient::create_object_pool( - const std::string& pathname, const uint32_t subgroup_index, - const sharding_policy_t sharding_policy, const std::unordered_map& object_locations, - const std::string& affinity_set_regex) { - uint32_t subgroup_type_index = ObjectPoolMetadata::template get_subgroup_type_index(); - if (subgroup_type_index == ObjectPoolMetadata::invalid_subgroup_type_index) { - dbg_default_crit("Create object pool failed because of invalid SubgroupType:{}", typeid(SubgroupType).name()); - throw derecho::derecho_exception(std::string("Create object pool failed because SubgroupType is invalid:")+typeid(SubgroupType).name()); - } - ObjectPoolMetadata opm(pathname,subgroup_type_index,subgroup_index,sharding_policy,object_locations,affinity_set_regex,false); - // clear local cache entry. - std::shared_lock rlck(object_pool_metadata_cache_mutex); - if (object_pool_metadata_cache.find(pathname)==object_pool_metadata_cache.end()) { - rlck.unlock(); - } else { - rlck.unlock(); - std::unique_lock wlck(object_pool_metadata_cache_mutex); - object_pool_metadata_cache.erase(pathname); - } - // determine the shard index by hashing - uint32_t metadata_service_shard_index = std::hash{}(pathname) % this->template get_number_of_shards>(METADATA_SERVICE_SUBGROUP_INDEX); - - return this->template put>(opm,METADATA_SERVICE_SUBGROUP_INDEX,metadata_service_shard_index); -} - -template -derecho::rpc::QueryResults ServiceClient::remove_object_pool(const std::string& pathname) { - // determine the shard index by hashing - uint32_t metadata_service_shard_index = std::hash{}(pathname) % this->template get_number_of_shards>(METADATA_SERVICE_SUBGROUP_INDEX); - - - // check if this object pool exist in metadata service. - auto opm = find_object_pool(pathname); - // remove it from local cache. - std::shared_lock rlck(object_pool_metadata_cache_mutex); - if (object_pool_metadata_cache.find(pathname) == object_pool_metadata_cache.end()) { - // no entry in cache - rlck.unlock(); - } else { - // remove from cache - rlck.unlock(); - std::unique_lock wlck(object_pool_metadata_cache_mutex); - object_pool_metadata_cache.erase(pathname); - wlck.unlock(); - } - if (opm.is_valid() && !opm.is_null()) { - if (opm.deleted) { - throw derecho::derecho_exception(std::string("object pool:")+pathname+" has been deleted already."); - } - opm.deleted = true; - opm.set_previous_version(CURRENT_VERSION,opm.version); // only check previous_version_by_key - return this->template put>(opm,METADATA_SERVICE_SUBGROUP_INDEX,metadata_service_shard_index); - } - - // we didn't find the object pool, but we do the normal 'remove', which has no effect but return a version. - dbg_default_warn("deleteing a non-existing objectpool:{}.", pathname); - return this->template remove>(pathname,METADATA_SERVICE_SUBGROUP_INDEX,metadata_service_shard_index); -} - -template -ObjectPoolMetadata ServiceClient::internal_find_object_pool( - const std::string& pathname, - std::shared_lock& rlck) { - auto components = str_tokenizer(pathname); - std::string prefix; - for (const auto& comp: components) { - prefix = prefix + PATH_SEPARATOR + comp; - if (object_pool_metadata_cache.find(prefix) != object_pool_metadata_cache.end()) { - return object_pool_metadata_cache.at(prefix).opm; - } - } - rlck.unlock(); - - // refresh and try again. - refresh_object_pool_metadata_cache(); - prefix = ""; - rlck.lock(); - for (const auto& comp: components) { - prefix = prefix + PATH_SEPARATOR + comp; - if (object_pool_metadata_cache.find(prefix) != object_pool_metadata_cache.end()) { - return object_pool_metadata_cache.at(prefix).opm; - } - } - return ObjectPoolMetadata::IV; -} - -template -ObjectPoolMetadata ServiceClient::find_object_pool(const std::string& pathname) { - std::shared_lock rlck(object_pool_metadata_cache_mutex); - return this->internal_find_object_pool(pathname,rlck); -} - -template -template -std::pair,std::string> ServiceClient::find_object_pool_and_affinity_set_by_key( - const KeyType& key) { - std::string object_pool_pathname = get_pathname(key); - if (object_pool_pathname.empty()) { - throw derecho::derecho_exception(std::string("Key:") + key + " does not belong to any object pool."); - } - - std::shared_lock rlck(object_pool_metadata_cache_mutex); - auto opm = this->internal_find_object_pool(object_pool_pathname,rlck); - - std::string affinity_set = ""; - if (opm.is_valid() && !opm.is_null() && !opm.deleted) { - affinity_set = object_pool_metadata_cache.at(opm.pathname).to_affinity_set(key); - } - - return {opm,affinity_set}; -} - -template -std::vector ServiceClient::list_object_pools(bool include_deleted, bool refresh) { - if (refresh) { - this->refresh_object_pool_metadata_cache(); - } - - std::vector ret; - std::shared_lock rlck(this->object_pool_metadata_cache_mutex); - for (auto& op:this->object_pool_metadata_cache) { - if (op.second.opm.deleted) { - if (include_deleted) { - ret.emplace_back(op.first+"(!)"); - } - } else { - ret.emplace_back(op.first); - } - } - - return ret; -} - -template -template -bool ServiceClient::register_notification_handler( - const cascade_notification_handler_t& handler, - const uint32_t subgroup_index) { - return register_notification_handler(handler,std::string{},subgroup_index); -} - -template -template -bool ServiceClient::register_notification_handler( - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname, - const uint32_t subgroup_index) { - if (!is_external_client()) { - throw derecho_exception(std::string(__PRETTY_FUNCTION__) + - "Cannot register notification handler because external_group_ptr is null."); - } - - std::unique_lock type_registry_lock(this->notification_handler_registry_mutex); - auto& per_type_registry = notification_handler_registry.template get(); - // Register Cascade's root handler: - // if subgroup_index exists in the per_type_registry, Cascade's root handler is registered already. - if (per_type_registry.find(subgroup_index) == per_type_registry.cend()) { - per_type_registry.emplace(subgroup_index,SubgroupNotificationHandler{}); - // register to subgroup_caller - auto& subgroup_caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - // to do ... register it. - per_type_registry.at(subgroup_index).initialize(subgroup_caller); - } - auto& subgroup_handlers = per_type_registry.at(subgroup_index); - - // Register the handler - std::lock_guard subgroup_handlers_lock(*subgroup_handlers.object_pool_notification_handlers_mutex); - bool ret = (subgroup_handlers.object_pool_notification_handlers.find(object_pool_pathname) != - subgroup_handlers.object_pool_notification_handlers.cend()); - - if (handler) { - subgroup_handlers.object_pool_notification_handlers[object_pool_pathname] = handler; - } else { - subgroup_handlers.object_pool_notification_handlers[object_pool_pathname].reset(); - } - return ret; -} - -template -template -bool ServiceClient::type_recursive_register_notification_handler( - uint32_t type_index, - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname, - const uint32_t subgroup_index) { - if (type_index == 0) { - return this->template register_notification_handler(handler,object_pool_pathname,subgroup_index); - } else { - return this->template type_recursive_register_notification_handler( - type_index-1,handler,object_pool_pathname,subgroup_index); - } -} - -template -template -bool ServiceClient::type_recursive_register_notification_handler( - uint32_t type_index, - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname, - const uint32_t subgroup_index) { - if (type_index == 0) { - return this->template register_notification_handler(handler,object_pool_pathname,subgroup_index); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -bool ServiceClient::register_notification_handler( - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname) { - auto opm = find_object_pool(object_pool_pathname); - - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - - return this->template type_recursive_register_notification_handler( - opm.subgroup_type_index,handler,object_pool_pathname,opm.subgroup_index); -} - -template -template -void ServiceClient::notify( - const Blob& msg, - const uint32_t subgroup_index, - const node_id_t client_id) const { - notify(msg,"",subgroup_index,client_id); -} - -template -template -void ServiceClient::notify( - const Blob& msg, - const std::string& object_pool_pathname, - const uint32_t subgroup_index, - const node_id_t client_id) const { - if (is_external_client()) { - throw derecho_exception(std::string(__PRETTY_FUNCTION__) + - "Cannot notify an external client from an external client."); - } - - auto& client_handle = group_ptr->template get_client_callback(subgroup_index); - - //TODO: redesign to avoid memory copies. - CascadeNotificationMessage cascade_notification_message(object_pool_pathname,msg); - derecho::NotificationMessage derecho_notification_message(CASCADE_NOTIFICATION_MESSAGE_TYPE, mutils::bytes_size(cascade_notification_message)); - mutils::to_bytes(cascade_notification_message,derecho_notification_message.body); - - client_handle.template p2p_send(client_id,derecho_notification_message); -} - -template -template -void ServiceClient::type_recursive_notify( - uint32_t type_index, - const Blob& msg, - const std::string& object_pool_pathname, - const uint32_t subgroup_index, - const node_id_t client_id) const { - if (type_index == 0) { - this->template notify(msg,object_pool_pathname,subgroup_index,client_id); - } else { - this->template type_recursive_notify(type_index-1,msg,object_pool_pathname,subgroup_index,client_id); - } -} - -template -template -void ServiceClient::type_recursive_notify( - uint32_t type_index, - const Blob& msg, - const std::string& object_pool_pathname, - const uint32_t subgroup_index, - const node_id_t client_id) const { - if (type_index == 0) { - this->template notify(msg,object_pool_pathname,subgroup_index,client_id); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -void ServiceClient::notify( - const Blob& msg, - const std::string& object_pool_pathname, - const node_id_t client_id) { - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - this->template type_recursive_notify(opm.subgroup_type_index,msg,object_pool_pathname,opm.subgroup_index,client_id); -} - -#ifdef ENABLE_EVALUATION - -template -template -derecho::rpc::QueryResults ServiceClient::dump_timestamp(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index) { - - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - return subgroup_handle.template ordered_send(filename); - } else { - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,filename); - return subgroup_handle.template p2p_send(node_id,filename); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,filename); - return caller.template p2p_send(node_id,filename); - } -} - -template -template -void ServiceClient::type_recursive_dump( - uint32_t type_index, - uint32_t subgroup_index, - const std::string& filename) { - if (type_index == 0) { - this->template dump_timestamp(subgroup_index,filename); - } else { - this->template type_recursive_dump(type_index-1,subgroup_index,filename); - } -} - -template -template -void ServiceClient::type_recursive_dump( - uint32_t type_index, - uint32_t subgroup_index, - const std::string& filename) { - if (type_index == 0) { - this->template dump_timestamp(subgroup_index,filename); - } else { - throw derecho::derecho_exception(std::string(__PRETTY_FUNCTION__) + ": type index is out of boundary."); - } -} - -template -void ServiceClient::dump_timestamp(const std::string& filename, const std::string& object_pool_pathname) { - auto opm = find_object_pool(object_pool_pathname); - if (!opm.is_valid() || opm.is_null() || opm.deleted) { - throw derecho::derecho_exception("Failed to find object_pool:" + object_pool_pathname); - } - - this->template type_recursive_dump(opm.subgroup_type_index,opm.subgroup_index,filename); -} - -template -template -void ServiceClient::dump_timestamp(const uint32_t subgroup_index,const std::string& filename){ - uint32_t shards = get_number_of_shards(subgroup_index); - for (uint32_t shard_index = 0; shard_index < shards; shard_index ++){ - auto result = this->template dump_timestamp(filename,subgroup_index,shard_index); - result.get(); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::dump_timestamp_workaround(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index, const node_id_t node_id) { - if (!is_external_client()) { - std::lock_guard lck(this->group_ptr_mutex); - if (static_cast(group_ptr->template get_my_shard(subgroup_index)) == shard_index) { - auto& subgroup_handle = group_ptr->template get_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id, filename); - } else { - auto& subgroup_handle = group_ptr->template get_nonmember_subgroup(subgroup_index); - return subgroup_handle.template p2p_send(node_id,filename); - } - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - return caller.template p2p_send(node_id,filename); - } -} - -template -template -derecho::rpc::QueryResults ServiceClient::perf_put(const uint32_t message_size, const uint64_t duration_sec, const uint32_t subgroup_index, const uint32_t shard_index) { - if (!is_external_client()) { - // 'perf_put' must be issued from an external client. - throw derecho::derecho_exception{"perf_put must be issued from an external client."}; - } else { - std::lock_guard lck(this->external_group_ptr_mutex); - auto& caller = external_group_ptr->template get_subgroup_caller(subgroup_index); - node_id_t node_id = pick_member_by_policy(subgroup_index,shard_index,0); - return caller.template p2p_send(node_id,message_size,duration_sec); - } -} -#endif//ENABLE_EVALUATION - -#ifndef __WITHOUT_SERVICE_SINGLETONS__ -template -const std::vector ServiceClient::subgroup_type_order{typeid(CascadeTypes)...}; - -template -const uint32_t ServiceClient::invalid_subgroup_type_index = 0xffffffff; - -template -template -uint32_t ServiceClient::get_subgroup_type_index() { - uint32_t index = 0; - while (index < subgroup_type_order.size()) { - if ( std::type_index(typeid(SubgroupType)) == subgroup_type_order.at(index)) { - return index; - } - index ++; - } - return invalid_subgroup_type_index; -} - -template -std::unique_ptr> ServiceClient::service_client_singleton_ptr; - -template -std::mutex ServiceClient::singleton_mutex; - -template -void ServiceClient::initialize(derecho::Group, CascadeTypes...>* _group_ptr) { - std::lock_guard lock_guard(singleton_mutex); - if (!service_client_singleton_ptr) { - dbg_default_trace("initializing ServiceClient singleton as cascade member, group pointer={:p}",static_cast(_group_ptr)); - service_client_singleton_ptr = std::unique_ptr>(new ServiceClient(_group_ptr)); - } -} - -template -ServiceClient& ServiceClient::get_service_client() { - if (!service_client_singleton_ptr) { - std::lock_guard lock_guard(singleton_mutex); - // test again in case another thread has initialized it already. - if (!service_client_singleton_ptr) { - dbg_default_trace("initializing ServiceClient singleton as external client"); - service_client_singleton_ptr = std::unique_ptr>(new ServiceClient(nullptr)); - } - } - return *service_client_singleton_ptr; -} -#endif//__WITHOUT_SERVICE_SINGLETONS__ template ExecutionEngine::ExecutionEngine() { diff --git a/include/cascade/service.hpp b/include/cascade/service.hpp index 891d26f0..bd2d8fd1 100644 --- a/include/cascade/service.hpp +++ b/include/cascade/service.hpp @@ -6,40 +6,38 @@ * @brief This file includes the cascade service templates * * Type neutral templates components go here. Since the server binary and client library has to be type aware (because - * they are pre-compiled), we separate the api and implementation of them in type-awared header files as follows: + * they are pre-compiled), we separate the api and implementation of them in type-aware header files as follows: * - service_types.hpp contains the predefined types for derecho Subgroups, which are specialized from - * derecho::cascade::VolatileCascadeStore/PersistentCascadeStore templates. - * - service_client_api.hpp contains the client API definition. - * - service_server_api.hpp contains the server API definition. Huh, Server API??? YES! because the application need to - * specify their 'onData()' behaviours by implementing the APIs in service_server_api.hpp as a shared library. The - * server will load them on restart. + * derecho::cascade::VolatileCascadeStore/PersistentCascadeStore templates, and the server API definition, + * which is a specialization of the Service template. + * - service_client_api.hpp contains the client API definition, which is a specialization of ServiceClient. */ +#include "cascade.hpp" +#include "data_flow_graph.hpp" +#include "detail/prefix_registry.hpp" +#include "object_pool_metadata.hpp" +#include "user_defined_logic_manager.hpp" +#include "utils.hpp" + +#include +#include #include +#include #include #include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include +#include #include #include #include -#include -#include "cascade.hpp" -#include "utils.hpp" -#include "object_pool_metadata.hpp" -#include "user_defined_logic_manager.hpp" -#include "data_flow_graph.hpp" -#include "detail/prefix_registry.hpp" namespace derecho { namespace cascade { @@ -81,15 +79,10 @@ namespace cascade { #define METADATA_SERVICE_SUBGROUP_INDEX (0) - /* The cascade context to be defined later */ - template - class CascadeContext; - + /* The cascade execution engine to be defined later */ template class ExecutionEngine; - /* The Action to be defined later */ - struct Action; /** * The off-critical data path handler API */ @@ -347,1377 +340,9 @@ namespace cascade { } }; - - /** The notification handler type */ - using cascade_notification_handler_t = std::function; - - /** The CascadeNotificationMessage type */ -#define CASCADE_NOTIFICATION_MESSAGE_TYPE (0x100000000ull) - struct CascadeNotificationMessage: public mutils::ByteRepresentable { - /** The object pool pathname, empty string for raw cascade notification message */ - std::string object_pool_pathname; - /** data */ - Blob blob; - - /** TODO: the default serialization support macro might contain unnecessary copies. Check it!!! */ - DEFAULT_SERIALIZATION_SUPPORT(CascadeNotificationMessage,object_pool_pathname,blob); - - /** constructors */ - CascadeNotificationMessage(): - object_pool_pathname(), - blob() {} - CascadeNotificationMessage(CascadeNotificationMessage&& other): - object_pool_pathname(other.object_pool_pathname), - blob(std::move(other.blob)) {} - CascadeNotificationMessage(const CascadeNotificationMessage& other): - object_pool_pathname(other.object_pool_pathname), - blob(other.blob) {} - CascadeNotificationMessage(const std::string& _object_pool_pathname, - const Blob& _blob) : - object_pool_pathname(_object_pool_pathname), - blob(_blob) {} - }; - - /** - * This is the structure for the server side notification handlers - */ - template - struct SubgroupNotificationHandler { - // key: object_pool_pathname - // value: an option for the handler - // The handler for "" key is the default handler, which will always be triggered. - std::unordered_map> object_pool_notification_handlers; - mutable std::unique_ptr object_pool_notification_handlers_mutex; - - SubgroupNotificationHandler(): - object_pool_notification_handlers_mutex(std::make_unique()) {} - - template - inline void initialize(derecho::ExternalClientCaller& subgroup_caller) { - dbg_default_trace("SubgroupNotificationHandler(this={:x}) is initialized for SubgroupType:{}", - reinterpret_cast(this),typeid(SubgroupType).name()); - subgroup_caller.register_notification_handler( - [this](const derecho::NotificationMessage& msg){ - dbg_default_trace("subgroup notification handler is triggered with this={:x}, msg type={}, size={} bytes", - reinterpret_cast(this), msg.message_type, msg.size); - (*this)(msg); - }); - } - - inline void operator ()(const derecho::NotificationMessage& msg) { - dbg_default_trace("SubgroupNotificationHandler(this={:x}) is triggered with message_type={:x}, size={} bytes", - reinterpret_cast(this),msg.message_type, msg.size); - if (msg.message_type != CASCADE_NOTIFICATION_MESSAGE_TYPE) { - return; - } - // mutils::deserialize_and_run(nullptr, msg.body, - mutils::deserialize_and_run(nullptr, msg.body, - [this](const CascadeNotificationMessage& cascade_message)->void { - dbg_default_trace("Handling cascade_message: {}. size={} bytes", - cascade_message.object_pool_pathname,cascade_message.blob.size); - std::lock_guard lck(*object_pool_notification_handlers_mutex); - // call default handler - if (object_pool_notification_handlers.find("") != - object_pool_notification_handlers.cend()) { - if (object_pool_notification_handlers.at("").has_value()) { - (*object_pool_notification_handlers.at(""))(cascade_message.blob); - } - } - // call object pool handler - if (object_pool_notification_handlers.find(cascade_message.object_pool_pathname) != - object_pool_notification_handlers.cend()) { - if (object_pool_notification_handlers.at(cascade_message.object_pool_pathname).has_value()) { - (*object_pool_notification_handlers.at(cascade_message.object_pool_pathname))(cascade_message.blob); - } - } - }); - } - }; - - template - using per_type_notification_handler_registry_t = - std::unordered_map>; - /** - * The ServiceClient template class contains all APIs needed to read/write data. The four core APIs are put, remove, - * get, and get_by_time. We also provide a set of helper APIs for the client to get the group topology. The core APIs - * target a specific subgroup and shard of the service, or a specific object pool (which maps to a subgroup). - * The client uses a ShardMemberSelectionPolicy to determine which member of that subgroup/shard to communicate with. - */ + /* Forward declaration of the ServiceClient, to be defined in service_client.hpp */ template - class ServiceClient { - static_assert(have_same_object_type()); - private: - // default caller as an external client. - std::unique_ptr,CascadeTypes...>> external_group_ptr; - mutable std::mutex external_group_ptr_mutex; - // caller as a group member. - derecho::Group, CascadeTypes...>* group_ptr; - mutable std::mutex group_ptr_mutex; - // cascade server side notification handler registry. - mutable mutils::KindMap notification_handler_registry; - mutable std::mutex notification_handler_registry_mutex; - /** - * 'member_selection_policies' is a map from derecho shard to its member selection policy. - * We use a 3-tuple consisting of subgroup type index, subgroup index, and shard index to identify a shard. And - * the policy is defined by a 2-tuple with the ShardMemberSelectionPolicy enum and a user specified node id, in - * case of ShardMemorySelectionPolicy::UserSpecified. The user specified node id is used as member index if the - * policy is ShardMemberSelectionPolicy::RoundRobin - * - * The default member selection policy is defined as DEFAULT_SHARD_MEMBER_SELECTION_POLICY. - */ - std::unordered_map< - std::tuple, - std::tuple, - do_hash>> member_selection_policies; - mutable std::shared_mutex member_selection_policies_mutex; - /** - * 'member_cache' is a map from derecho shard to its member list. This cache is used to accelerate the member - * choices process. If the client cannot connect to the cached member (after a couple of retries), it will refresh - * the corresponding cache entry. - */ - std::unordered_map< - std::tuple, - std::vector, - do_hash>> member_cache; - mutable std::shared_mutex member_cache_mutex; - /** - * 'object_pool_info_cache' is a local cache for object pool metadata. This cache is used to accelerate the - * object access process. If an object pool does not exists, it will be loaded from metadata service. - * - * Each entry of the object_pool_info_cache is an object of type ObjectPoolMetadataCacheEntry. Such an object - * caches an object pool metadata object (opm) along with the affinity set regex processing data structures. - */ - class ObjectPoolMetadataCacheEntry { - public: - ObjectPoolMetadata opm; - /** - * The constructor - * @param[in] _opm object pool metadata - */ - ObjectPoolMetadataCacheEntry(const ObjectPoolMetadata& _opm); - - /** - * The destructor - */ - virtual ~ObjectPoolMetadataCacheEntry(); - - /** - * Convert a key string to corresponding affinity set string. - * @param[in] key_string - * - * @return affinity set string - */ - inline std::string to_affinity_set(const std::string& key_string); - private: - /* the database storing compiled regex */ - hs_database_t* database; - /* the scratch for the regex */ - thread_local static hs_scratch_t* scratch; - }; - - std::unordered_map< - std::string, - ObjectPoolMetadataCacheEntry> object_pool_metadata_cache; - mutable std::shared_mutex object_pool_metadata_cache_mutex; - - /** - * Pick a member by a given a policy. - * @param[in] subgroup_index - * @param[in] shard_index - * @param[in] key_for_hashing - only for KeyHashing policy, ignored otherwise. - * @param[in] retry - if true, refresh the member_cache. - */ - template - node_id_t pick_member_by_policy(uint32_t subgroup_index, - uint32_t shard_index, - const KeyTypeForHashing& key_for_hashing, - bool retry = false); - - /** - * Refresh(or fill) a member cache entry. - * @param[in] subgroup_index - * @param[in] shard_index - */ - template - void refresh_member_cache_entry(uint32_t subgroup_index, uint32_t shard_index); - - /** - * Metadata API Helper: turn a string key to subgroup type index, subgroup index, and shard index. - */ - template - std::tuple key_to_shard( - const KeyType& key, bool check_object_location = true); - - /** - * The Constructor - * We prevent calling the constructor explicitly, because the ServiceClient is a singleton. - * @param[in] _group_ptr The caller can pass a pointer pointing to a derecho group object. If the pointer is - * valid, the implementation will reply on the group object instead of creating an external - * client to communicate with group members. - */ - ServiceClient(derecho::Group, CascadeTypes...>* _group_ptr=nullptr); - - public: - /** - * ServiceClient can be an external client or a cascade server. is_external_client() test this condition. - * The external client implementation is based on ExternalGroupClient<> while the cascade node implementation is - * based on Group<>. - * - * @return true for external client; other wise false. - */ - inline bool is_external_client() const; - - /** - * Derecho group helpers: They derive the API in derecho::ExternalClient. - * - get_my_id return my local node id. - * - get_members returns all members in the top-level Derecho group. - * - get_subgroup_members returns a vector of vectors of node ids: [[node ids in shard 0],[node ids in shard 1],...] - * - get_shard_members returns the members in a shard specified by subgroup id(or subgroup type/index pair) and - * shard index. - * - get_number_of_subgroups returns the number of subgroups of a given type - * - get_number_of_shards returns the number of shards of a given subgroup - * - get_my_shard returns the shard number that this node is a member of in the specific - * subgroup (by subgroup type and index), or -1 if this node is not a member - * of any shard in the specified subgroup. - * During view change, the Client might experience failure if the member is gone. In such a case, the client needs - * refresh its local member cache by calling get_shard_members. - */ - node_id_t get_my_id() const; - - std::vector get_members() const; - - template - std::vector> get_subgroup_members(uint32_t subgroup_index) const; - protected: - template - std::vector> type_recursive_get_subgroup_members(uint32_t type_index, uint32_t subgroup_index) const; - template - std::vector> type_recursive_get_subgroup_members(uint32_t type_index, uint32_t subgroup_index) const; - public: - std::vector> get_subgroup_members(const std::string& object_pool_pathname); - - template - std::vector get_shard_members(uint32_t subgroup_index,uint32_t shard_index) const; - protected: - template - std::vector type_recursive_get_shard_members(uint32_t type_index, - uint32_t subgroup_index, uint32_t shard_index) const; - template - std::vector type_recursive_get_shard_members(uint32_t type_index, - uint32_t subgroup_index, uint32_t shard_index) const; - public: - std::vector get_shard_members(const std::string& object_pool_pathname,uint32_t shard_index); - - template - uint32_t get_number_of_subgroups() const; - - template - uint32_t get_number_of_shards(uint32_t subgroup_index) const; - - // type recursive helpers for get_number_of_shards - protected: - template - uint32_t type_recursive_get_number_of_shards(uint32_t type_index, uint32_t subgroup_index) const; - template - uint32_t type_recursive_get_number_of_shards(uint32_t type_index, uint32_t subgroup_index) const; - public: - /** - * This get_number_of_shards() overload the typed version. - * @param[in] subgroup_type_index - the type index of the subrgoup type. - * @param[in] subgroup_index - the subgroup index in the given type. - */ - uint32_t get_number_of_shards(uint32_t subgroup_type_index, uint32_t subgroup_index) const; - - /** - * This get_number_of_shards(), pick subgroup using object pool pathname. - * @param[in] object_pool_pathname - the object pool name - */ - uint32_t get_number_of_shards(const std::string& object_pool_pathname); - - template - int32_t get_my_shard(uint32_t subgroup_index) const; - protected: - template - int32_t type_recursive_get_my_shard(uint32_t type_index, uint32_t subgroup_index) const; - template - int32_t type_recursive_get_my_shard(uint32_t type_index, uint32_t subgroup_index) const; - public: - /** - * @fn int32_t get_my_shard(uint32_t subgroup_type_index, uint32_t subgroup_index) const - * @brief find the shard I belong to, given the subgroup specified by type and index. - * @param[in] subgroup_type_index - the type index of the subgroup type. - * @param[in] subgroup_index - the subgroup index in the given type. - * @return The number of the shard, or -1 if current node is not in the specified subgroup. - */ - int32_t get_my_shard(uint32_t subgroup_type_index, uint32_t subgroup_index) const; - - /** - * @fn int32_t get_my_shard(const std::string& object_pool_pathname) - * @brief find the shard I belong to, given the object pool specified by object pool path name. - * @param[in] object_pool_pathname - the object pool path name. - * @return The number of the shard, or -1 if current node is not in the specified subgroup. - */ - int32_t get_my_shard(const std::string& object_pool_pathname); - - /** - * Updates the member selection policy for a shard. - * - * @tparam SubgroupType The Cascade subgroup type the shard is in - * @param[in] subgroup_index - * @param[in] shard_index - * @param[in] policy - the new policy - * @param[in] user_specified_node_id - optional, the node ID to contact if the policy is UserSpecified - */ - template - void set_member_selection_policy(uint32_t subgroup_index,uint32_t shard_index, - ShardMemberSelectionPolicy policy,node_id_t user_specified_node_id=INVALID_NODE_ID); - - /** - * Reads the member selection policy for a shard. - * - * @tparam SubgroupType The Cascade subgroup type the shard is in - * @param[in] subgroup_index - * @param[in] shard_index - * @return a 2-tuple of policy and user_specified_node_id. - */ - template - std::tuple get_member_selection_policy( - uint32_t subgroup_index, uint32_t shard_index) const; - - /** - * "put" writes an object to a given subgroup/shard. - * - * @param[in] object the object to write. - * User provided SubgroupType::ObjectType must have the following two members: - * - SubgroupType::ObjectType::key of SubgroupType::KeyType, which must be set to a - * valid key. - * - SubgroupType::ObjectType::ver of std::tuple. - * Similar to the return object, this member is a two tuple with the first member - * for a version and the second for a timestamp. A caller of put can specify either - * of the version and timestamp meaning what is the latest version/timestamp the caller - * has seen. Cascade will reject the write if the corresponding key has been updated - * already. TODO: should we make it an optional feature? - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be - * used to update the state. - * - * @return a future to the version and timestamp of the put operation. - * TODO: check if the user application is responsible for reclaim the future by reading it sometime. - */ - template - derecho::rpc::QueryResults put(const typename SubgroupType::ObjectType& object, - uint32_t subgroup_index, uint32_t shard_index, bool as_trigger = false); - /** - * "type_recursive_put" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. And the FirstType, - * SecondType, ..., RestTypes should be in the same order. - * @param[in] object the object to write - * @param[in] subgroup_index - * the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be - * used to update the state. - * - * @return a future to the version and timestamp of the put operation. - */ - protected: - template - derecho::rpc::QueryResults type_recursive_put( - uint32_t type_index, - const ObjectType& object, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger = false); - - template - derecho::rpc::QueryResults type_recursive_put( - uint32_t type_index, - const ObjectType& object, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger = false); - public: - /** - * object pool version of "put" - * @param[in] object the object to write, the object pool is extracted from the object key. - * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be - * used to update the state. - * - * @return a future to the version and timestamp of the put operation. - */ - template - derecho::rpc::QueryResults put(const ObjectType& object, bool as_trigger = false); - - /** - * "put_and_forget" writes an object to a given subgroup/shard, but no return value. - * - * @param[in] object the object to write. - * User provided SubgroupType::ObjectType must have the following two members: - * - SubgroupType::ObjectType::key of SubgroupType::KeyType, which must be set to a - * valid key. - * - SubgroupType::ObjectType::ver of std::tuple. - * Similar to the return object, this member is a two tuple with the first member - * for a version and the second for a timestamp. A caller of put can specify either - * of the version and timestamp meaning what is the latest version/timestamp the caller - * has seen. Cascade will reject the write if the corresponding key has been updated - * already. TODO: should we make it an optional feature? - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be - * used to update the state. - */ - template - void put_and_forget(const typename SubgroupType::ObjectType& object, - uint32_t subgroup_index, uint32_t shard_index, bool as_trigger = false); - - protected: - /** - * "type_recursive_put_and_forget" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] object the object to write - * @param[in] subgroup_index - * the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be - * used to update the state. - */ - template - void type_recursive_put_and_forget( - uint32_t type_index, - const ObjectType& object, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger = false); - - template - void type_recursive_put_and_forget( - uint32_t type_index, - const ObjectType& object, - uint32_t subgroup_index, - uint32_t shard_index, - bool as_trigger = false); - public: - /** - * object pool version of "put_and_forget" - * @param[in] object the object to write, the object pool is extracted from the object key. - * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be - * used to update the state. - */ - template - void put_and_forget(const ObjectType& object, bool as_trigger = false); - - /** - * "trigger_put" writes an object to a given subgroup/shard. - * - * @param[in] object the object to write. - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a void future. - */ - template - derecho::rpc::QueryResults trigger_put(const typename SubgroupType::ObjectType& object, - uint32_t subgroup_index, uint32_t shard_index); - protected: - /** - * "type_recursive_trigger_put" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] object the object to write - * @param[in] subgroup_index - * the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return future - */ - template - derecho::rpc::QueryResults type_recursive_trigger_put( - uint32_t type_index, - const ObjectType& object, - uint32_t subgroup_index, - uint32_t shard_index); - - template - derecho::rpc::QueryResults type_recursive_trigger_put( - uint32_t type_index, - const ObjectType& object, - uint32_t subgroup_index, - uint32_t shard_index); - public: - /** - * object pool version of "trigger_put" - * @param[in] object the object to write, the object pool is extracted from the object key. - */ - template - derecho::rpc::QueryResults trigger_put(const ObjectType& object); - /** - * "collective_trigger_put" writes an object to a set of nodes. - * - * Please notice that returning from QueryResults::get() only means that the message has been sent by the - * sender. It does NOT guarantee that the message is/will be successfully processed by the remote side. However, - * we agree that QueryResults should reflect exceptions or errors either on local or remote side, which is - * not enabled so far. TODO: Track exception in derecho::rpc::QueryResults - * - * @param[in] object the object to write. - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] nodes_and_futures map from node ids to futures. - */ - template - void collective_trigger_put(const typename SubgroupType::ObjectType& object, - uint32_t subgroup_index, - std::unordered_map>>& nodes_and_futures); - - /** - * "remove" deletes an object with the given key. - * - * @param[in] key the object key - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future to the version and timestamp of the put operation. - * TODO: check if the user application is responsible for reclaim the future by reading it sometime. - */ - template - derecho::rpc::QueryResults remove(const typename SubgroupType::KeyType& key, - uint32_t subgroup_index, uint32_t shard_index); - - protected: - /** - * "type_recursive_remove" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] key the key - * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return a future to the version and timestamp of the put operation. - */ - template - derecho::rpc::QueryResults type_recursive_remove( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index); - - template - derecho::rpc::QueryResults type_recursive_remove( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index); - public: - /** - * object pool version of "remove" - * @param[in] key the object key - * - * @return returns a future - */ - template - derecho::rpc::QueryResults remove(const KeyType& key); - - /** - * "get" retrieve the object of a given key - * - * @param[in] key the object key - * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, get will either - * read the current object from memory of the replica that handles the get request - * (if stable is false), or read the latest stable version that is persisted (if - * stable is true). Note that in any case "get" will contact only a single replica; - * to use an atomic multicast to get the latest version that is present on all replicas, - * use multi_get. - * @param[in] stable if true, get will wait until the requested version's persistent data is safe, meaning the - * persistent data is persisted on all replicas. - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future to the retrieved object. - * TODO: check if the user application is responsible for reclaim the future by reading it sometime. - */ - template - derecho::rpc::QueryResults get( - const typename SubgroupType::KeyType& key, - const persistent::version_t& version = CURRENT_VERSION, - bool stable = true, - uint32_t subgroup_index = 0, - uint32_t shard_index = 0); - protected: - /** - * "type_recursive_get" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] key the key - * @param[in] version the version - * @param[in] stable stable or not? - * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return a future for the object. - */ - template - auto type_recursive_get( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - - template - auto type_recursive_get( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - public: - /** - * object pool version of "get" - * - * @param[in] key the object key; the object pool is extracted from this key - * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, get will either - * read the current object from memory of the replica that handles the get request - * (if stable is false), or read the latest stable version that is persisted (if - * stable is true). Note that in any case "get" will contact only a single replica; - * to use an atomic multicast to get the latest version that is present on all replicas, - * use multi_get. - * @param[in] stable if true, get will wait until the requested version's persistent data is safe, meaning the - * persistent data is persisted on all replicas. - * @return a future to the retrieved object. - */ - template - auto get( - const KeyType& key, - const persistent::version_t& version = CURRENT_VERSION, - bool stable = true); - - /** - * "multi_get" retrieves the latest version of the object for a given key using an atomic broadcast. - * This ensures that the get request is mutually exclusive and linearizable with any concurrent put - * requests to the same key. - * - * @param[in] key the object key - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future to the retrieved object, including a response from each replica in the shard. - */ - template - derecho::rpc::QueryResults multi_get(const typename SubgroupType::KeyType& key, - uint32_t subgroup_index, uint32_t shard_index); - - /** - * "type_recursive_multi_get" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] key the key - * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return a future for the object. - */ - protected: - template - auto type_recursive_multi_get( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index); - - template - auto type_recursive_multi_get( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index); - public: - - /** - * object pool version of "multi_get" - * - * @param[in] key the object key; the object pool is extracted from this key - * - * @return a future for the retrieved object, including a response from each replica in the object pool - */ - template - auto multi_get(const KeyType& key); - - /** - * "get_by_time" retrieve the object of a given key - * - * @param[in] key the object key - * @param[in] ts_us Wall clock time in microseconds. - * @param[in] stable stable get or not - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future to the retrieved object. - * TODO: check if the user application is responsible for reclaim the future by reading it sometime. - */ - template - derecho::rpc::QueryResults get_by_time( - const typename SubgroupType::KeyType& key, - const uint64_t& ts_us, - const bool stable = true, - uint32_t subgroup_index = 0, - uint32_t shard_index = 0); - - /** - * "type_recursive_get_by_time" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] key the key - * @param[in] ts_us Wall clock time in microseconds. - * @param[in] stable stable get or not - * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return a future for the object. - */ - protected: - template - auto type_recursive_get_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - - template - auto type_recursive_get_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - public: - - /** - * object pool version of "get_by_time" - * - * @param[in] key the object key; the object pool is extracted from this key - * @param[in] ts_us Wall clock time in microseconds. - * @param[in] stable stable get or not - * - * @return a future for the retrieved object. - */ - template - auto get_by_time( - const KeyType& key, - const uint64_t& ts_us, - const bool stable = true); - - /** - * "get_size" retrieve size of the object of a given key - * - * @param[in] key the object key - * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, this will either - * get the size of the current object from memory of the replica that handles the request - * (if stable is false), or get the size of the latest stable version that is persisted - * (if stable is true). Note that in any case "get_size" will contact only a single replica; - * to use an atomic multicast to check the latest version that is present on all replicas, - * use multi_get_size. - * @param[in] stable stable get or not - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future to the retrieved size. - * TODO: check if the user application is responsible for reclaim the future by reading it sometime. - */ - template - derecho::rpc::QueryResults get_size( - const typename SubgroupType::KeyType& key, - const persistent::version_t& version, - const bool stable = true, - uint32_t subgroup_index = 0, - uint32_t shard_index = 0); - - protected: - /** - * "type_recursive_get_size" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] key the key - * @param[in] version version - * @param[in] stable stable get size or not - * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return a future for the retrieved size. - */ - template - derecho::rpc::QueryResults type_recursive_get_size( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - - template - derecho::rpc::QueryResults type_recursive_get_size( - uint32_t type_index, - const KeyType& key, - const persistent::version_t& version, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - - public: - /** - * object pool version of "get_size" - * @param[in] key the object key - * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, this will either - * get the size of the current object from memory of the replica that handles the request - * (if stable is false), or get the size of the latest stable version that is persisted - * (if stable is true). Note that in any case "get_size" will contact only a single replica; - * to use an atomic multicast to check the latest version that is present on all replicas, - * use multi_get_size. - * @param[in] stable stable get or not - * @return a future for the retrieved size. - */ - template - derecho::rpc::QueryResults get_size( - const KeyType& key, - const persistent::version_t& version, - const bool stable = true); - - /** - * "multi_get_size" retrieves the size of the current version of the object with a given key, using an atomic broadcast. - * - * @param[in] key the object key - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future to the retrieved size. - */ - template - derecho::rpc::QueryResults multi_get_size( - const typename SubgroupType::KeyType& key, - uint32_t subgroup_index, uint32_t shard_index); - - /** - * "type_recursive_multi_get_size" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] key the key - * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return a future for the object. - */ - protected: - template - derecho::rpc::QueryResults type_recursive_multi_get_size( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index); - - template - derecho::rpc::QueryResults type_recursive_multi_get_size( - uint32_t type_index, - const KeyType& key, - uint32_t subgroup_index, - uint32_t shard_index); - - public: - /** - * object pool version of "multi_get_size" - * - * @param[in] key the object key; the object pool is extracted from this key - * - * @return a future for the retrieved size. - */ - template - derecho::rpc::QueryResults multi_get_size(const KeyType& key); - - /** - * "get_size_by_time" retrieve size of the object of a given key - * - * @param[in] key the object key - * @param[in] ts_us Wall clock time in microseconds. - * @param[in] stable stable get or not - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future to the retrieved size. - * TODO: check if the user application is responsible for reclaim the future by reading it sometime. - */ - template - derecho::rpc::QueryResults get_size_by_time( - const typename SubgroupType::KeyType& key, - const uint64_t& ts_us, - const bool stable = true, - uint32_t subgroup_index = 0, - uint32_t shard_index = 0); - - protected: - /** - * "type_recursive_get_size" is a helper function for internal use only. - * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, - * SecondType, .../ RestTypes should be in the same order. - * @param[in] key the key - * @param[in] ts_us Wall clock time in microseconds. - * @param[in] stable stable get or not - * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index - * @param[in] shard_index the shard index - * - * @return a future for the object. - */ - template - derecho::rpc::QueryResults type_recursive_get_size_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - - template - derecho::rpc::QueryResults type_recursive_get_size_by_time( - uint32_t type_index, - const KeyType& key, - const uint64_t& ts_us, - const bool stable, - uint32_t subgroup_index, - uint32_t shard_index); - public: - - /** - * object pool version of "get_size_by_time" - * - * @param[in] key the object key - * @param[in] ts_us Wall clock time in microseconds. - * @param[in] stable stable get or not - * - * @return a future for the retrieved size. - */ - template - derecho::rpc::QueryResults get_size_by_time( - const KeyType& key, - const uint64_t& ts_us, - const bool stable = true); - - /** - * "list_keys" retrieve the list of keys in a shard - * - * @param[in] version the version at which to list the keys; all keys that existed at or before this version - * will be included. If equal to CURRENT_VERSION, list_keys will list all keys currently - * in memory at the replica that handles the request. - * @param[in] stable if true, list_keys will wait until the requested version's persistent data is safe, meaning the - * persistent data is persisted on all replicas, before listing the keys present at that version. - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future for the vector of keys - * TODO: check if the user application is responsible for reclaim the future by reading it sometime. - */ - template - derecho::rpc::QueryResults> list_keys( - const persistent::version_t& version, - const bool stable = true, - uint32_t subgroup_index = 0, - uint32_t shard_index = 0); - - protected: - template - auto type_recursive_list_keys( - uint32_t type_index, - const persistent::version_t& version, - const bool stable, - const std::string& object_pool_pathname); - template - auto type_recursive_list_keys( - uint32_t type_index, - const persistent::version_t& version, - const bool stable, - const std::string& object_pool_pathname); - template - std::vector>>> - __list_keys(const persistent::version_t& version, const bool stable, const std::string& object_pool_pathname); - public: - /** - * @brief object pool version of "list_keys"; lists all keys in an object pool - * - * @param[in] version the version at which to list the keys; all keys that existed at or before this version - * will be included. If equal to CURRENT_VERSION, list_keys will list all keys currently - * in memory at the replica that handles the request. - * @param[in] stable if true, list_keys will wait until the requested version's persistent data is safe, meaning the - * persistent data is persisted on all replicas, before listing the keys present at that version. - * @param[in] object_pool_pathname the object pathname - * - * @return a vector of futures for key lists, with one key list for each shard in the object pool. - * The return value's type will look like vector>>>, where KeyType is either string or uint64_t - */ - auto list_keys(const persistent::version_t& version, const bool stable, const std::string& object_pool_pathname); - - /** - * A function that helps unpack the return value of list_keys (object-pool version). Iterates through the - * vector of QueryResults and waits for each one to complete, then combines the resulting vectors of keys - * into a single vector of keys (across all shards in the object pool). - * - * @tparam KeyType The type of a key in this object pool - * @param future The vector-of-futures returned by list_keys or multi_list_keys - * @return a vector of keys in the object pool - */ - template - std::vector wait_list_keys( - std::vector>>>& future); - - /** - * "multi_list_keys" retrieves the list of keys in a shard at the current version, using an atomic multicast - * - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future for the list of keys. - */ - template - derecho::rpc::QueryResults> multi_list_keys( - uint32_t subgroup_index, - uint32_t shard_index); - - protected: - template - auto type_recursive_multi_list_keys( - uint32_t type_index, - const std::string& object_pool_pathname); - template - auto type_recursive_multi_list_keys( - uint32_t type_index, - const std::string& object_pool_pathname); - template - std::vector>>> - __multi_list_keys(const std::string& object_pool_pathname); - public: - /** - * object pool version of "multi_list_keys" - * - * @param[in] object_pool_pathname the object pathname - * - * @return a vector of futures for key lists, with one key list for each shard in the object pool. - * The return value's type will look like vector>>>, where KeyType is either string or uint64_t - */ - auto multi_list_keys(const std::string& object_pool_pathname); - - /** - * "list_keys_by_time" retrieves the list of keys in a shard at a specific time - * - * @param[in] ts_us Wall clock time in microseconds. - * @param[in] stable stable get or not - * @param[in] subgroup_index the subgroup index of CascadeType - * @param[in] shard_index the shard index. - * - * @return a future for the list of keys - */ - template - derecho::rpc::QueryResults> list_keys_by_time( - const uint64_t& ts_us, - const bool stable = true, - uint32_t subgroup_index = 0, - uint32_t shard_index = 0); - - protected: - template - auto type_recursive_list_keys_by_time( - uint32_t type_index, - const uint64_t& ts_us, - const bool stable, - const std::string& object_pool_pathname); - template - auto type_recursive_list_keys_by_time( - uint32_t type_index, - const uint64_t& ts_us, - const bool stable, - const std::string& object_pool_pathname); - template - std::vector>>> - __list_keys_by_time(const uint64_t& ts_us, const bool stable, const std::string& object_pool_pathname); - public: - /** - * object pool version of "list_keys_by_time" - * - * @param[in] ts_us timestamp - * @param[in] stable stable flag - * @param[in] object_pool_pathname the object pathname - * - * @return a vector of futures for key lists, with one key list for each shard in the object pool. - * The return value's type will look like vector>>>, where KeyType is either string or uint64_t - */ - auto list_keys_by_time(const uint64_t& ts_us, const bool stable, const std::string& object_pool_pathname); - - /** - * Object Pool Management API: refresh object pool cache - * We load 'unstable' (committed but maybe not persisted) metadata here. - */ - void refresh_object_pool_metadata_cache(); - - /** - * Object Pool Management API: create object pool - * - * @tparam SubgroupType Type of the subgroup for the created object pool - * @param[in] pathname Object pool's pathname as identifier. - * @param[in] subgroup_index Index of the subgroup - * @param[in] sharding_policy The default sharding policy for this object pool - * @param[in] object_locations The set of special object locations. - * @param[in] affinity_set_regex - * The affinity set regex. - * - * @return a future to the version and timestamp of the put operation. - */ - template - derecho::rpc::QueryResults create_object_pool( - const std::string& pathname, const uint32_t subgroup_index, - const sharding_policy_t sharding_policy = HASH, - const std::unordered_map& object_locations = {}, - const std::string& affinity_set_regex = ""); - - /** - * ObjectPoolManagement API: remove object pool - * - * @param[in] pathname Object pool pathname - * - * @return a future to the version and timestamp of the put operation. - */ - derecho::rpc::QueryResults remove_object_pool(const std::string& pathname); - private: - /** - * ObjectPoolManagement API: find object pool - * - * @param[in] pathname Object pool pathname - * @param[in] rlck shared lock, which needs to be hold. - * - * @return the object pool metadata - */ - ObjectPoolMetadata internal_find_object_pool(const std::string& pathname, - std::shared_lock& rlck); - public: - /** - * ObjectPoolManagement API: find object pool - * - * @param[in] pathname Object pool pathname - * - * @return the object pool metadata - */ - ObjectPoolMetadata find_object_pool(const std::string& pathname); - - /** - * ObjectPoolManagement API: find object pool and affinity_set from key - * - * @param[in] key The key of an object. - * - * @return the object pool metadata along with the affinity set string - */ - template - std::pair,std::string> - find_object_pool_and_affinity_set_by_key(const KeyType& key); - - /** - * ObjectPoolManagement API: list all the object pools by pathnames - * - * @param[in] include_deleted show deleted pools with an exclaimation point(!). - * @param[in] refresh false for cached object ids, true for refreshed ids. - * - * @return the pool ids. - */ - std::vector list_object_pools(bool include_deleted, bool refresh = false); - - /** - * Register an notification handler to a subgroup. If such a handler has been registered, it will be replaced - * by the new one. - * - * @tparam SubgroupType The Subgroup Type - * @param[in] handler The handler to reigster - * @param[in] subgroup_index Index of the subgroup - * - * @return true if a previous notification handler is replaced. - */ - template - bool register_notification_handler( - const cascade_notification_handler_t& handler, - const uint32_t subgroup_index = 0); - - protected: - template - bool register_notification_handler( - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname, - const uint32_t subgroup_index); - template - bool type_recursive_register_notification_handler( - uint32_t type_index, - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname, - const uint32_t subgroup_index); - template - bool type_recursive_register_notification_handler( - uint32_t type_index, - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname, - const uint32_t subgroup_index); - - public: - /** - * Register notification handler(object pool version). If such a handler has been registered, it will be - * replaced by the new one. - * - * @tparam SubgroupType The Subgroup Type - * @param[in] handler The handler to reigster - * @param[in] object_pool_pathname To with object pool is this handler registered. - * - * @return true if a previous notification handler is replaced. - */ - bool register_notification_handler( - const cascade_notification_handler_t& handler, - const std::string& object_pool_pathname); - - /** - * Send a notification message to an external client. - * - * @tparam SubgroupType The Subgroup Type - * @param[in] msg The message to send - * @param[in] subgroup_index The subgroup index - * @param[in] client_id The node id of the external client to be notified - */ - template - void notify(const Blob& msg, - const uint32_t subgroup_index, - const node_id_t client_id) const; - protected: - template - void notify(const Blob& msg, - const std::string& object_pool_pathname, - const uint32_t subgroup_index, - const node_id_t client_id) const; - template - void type_recursive_notify( - uint32_t type_index, - const Blob& msg, - const std::string& object_pool_pathname, - const uint32_t subgroup_index, - const node_id_t client_id) const; - template - void type_recursive_notify( - uint32_t type_index, - const Blob& msg, - const std::string& object_pool_pathname, - const uint32_t subgroup_index, - const node_id_t client_id) const; - public: - /** - * Send a notification message to an external client. - * - * @param[in] msg The messgae to send - * @param[in] object_pool_pathname In which object_pool the notification is in. - * @param[in] client_id The client id - */ - void notify(const Blob& msg, - const std::string& object_pool_pathname, - const node_id_t client_id); - -#ifdef ENABLE_EVALUATION - /** - * Dump the timestamp log entries into a file on each of the nodes in a shard. - * - * @param[in] filename - the output filename - * @param[in] subgroup_index - the subgroup index - * @param[in] shard_index - the shard index - * - * @return query results - */ - template - derecho::rpc::QueryResults dump_timestamp(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index); - - /** - * The object store version: - * - * @param[in] filename - the filename - * @param[in] object_pool_pathname - the object pool pathname - */ - void dump_timestamp(const std::string& filename, const std::string& object_pool_pathname); - - /** - * Dump the timestamp log entries into a file on each of the nodes in a subgroup. - * - * @param[in] filename - the output filename - * @param[in] subgroup_index - the subgroup index - */ - template - void dump_timestamp(const uint32_t subgroup_index, const std::string& filename); - - protected: - template - void type_recursive_dump(uint32_t type_index, uint32_t subgroup_index, const std::string& filename); - - template - void type_recursive_dump(uint32_t type_index, uint32_t subgroup_index, const std::string& filename); - - public: -#ifdef DUMP_TIMESTAMP_WORKAROUND - /** - * Dump the timestamp log entries into a file on a specific node. - * - * @param[in] filename - the output filename - * @param[in] subgroup_index - the subgroup index - * @param[in] shard_index - the shard index - * @param[in] node_id - the given node id. - * - * @return a vector of query results. - */ - template - derecho::rpc::QueryResults dump_timestamp_workaround(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index, const node_id_t node_id); -#endif - - /** - * Evaluate the ordered put performance inside a shard. Please note that those put does not involve the - * external client data path. - * - * @param[in] message_size - the message size for the shard. TODO: we should be able to retrieve the maximum - * message size from SubgroupType, subgroup_index and shard_index. How? - * @param[in] duration_sec - the duration of the test in seconds. - * @param[in] subgroup_index - the subgroup index - * @param[in] shard_index - the shard index - * - * @return the value in ops. - */ - template - derecho::rpc::QueryResults perf_put(const uint32_t message_size, const uint64_t duration_sec, const uint32_t subgroup_index, const uint32_t shard_index); -#endif//ENABLE_EVALUATION - - const static std::vector subgroup_type_order; - const static uint32_t invalid_subgroup_type_index; - /** - * Get type index - * @return the the subgroup type index - */ - template - static uint32_t get_subgroup_type_index(); - - /* singleton */ - private: - static std::unique_ptr service_client_singleton_ptr; - static std::mutex singleton_mutex; - public: - /** - * Initialize the service_client_single_ptr singleton with a cascade service. This can only be called once - * before any get_service_client() is called. - * @param[in] _group_ptr The caller can pass a pointer pointing to a derecho group object. If the pointer is - * valid, the implementation will reply on the group object instead of creating an external - * client to communicate with group members. - */ - static void initialize(derecho::Group, CascadeTypes...>* _group_ptr); - - /** - * Get the singleton ServiceClient API. If it does not exists, initialize it as an external client. - */ - static ServiceClient& get_service_client(); - }; // ServiceClient - + class ServiceClient; /** * configuration keys diff --git a/include/cascade/service_client.hpp b/include/cascade/service_client.hpp new file mode 100644 index 00000000..ce6e015a --- /dev/null +++ b/include/cascade/service_client.hpp @@ -0,0 +1,1386 @@ +#pragma once + +#include "object.hpp" +#include "object_pool_metadata.hpp" +#include "service.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace derecho { +namespace cascade { +/** The notification handler type */ +using cascade_notification_handler_t = std::function; + +/** The CascadeNotificationMessage type */ +#define CASCADE_NOTIFICATION_MESSAGE_TYPE (0x100000000ull) +struct CascadeNotificationMessage : public mutils::ByteRepresentable { + /** The object pool pathname, empty string for raw cascade notification message */ + std::string object_pool_pathname; + /** data */ + Blob blob; + + /** TODO: the default serialization support macro might contain unnecessary copies. Check it!!! */ + DEFAULT_SERIALIZATION_SUPPORT(CascadeNotificationMessage, object_pool_pathname, blob); + + /** constructors */ + CascadeNotificationMessage() : object_pool_pathname(), + blob() {} + CascadeNotificationMessage(CascadeNotificationMessage&& other) : object_pool_pathname(other.object_pool_pathname), + blob(std::move(other.blob)) {} + CascadeNotificationMessage(const CascadeNotificationMessage& other) : object_pool_pathname(other.object_pool_pathname), + blob(other.blob) {} + CascadeNotificationMessage(const std::string& _object_pool_pathname, + const Blob& _blob) : object_pool_pathname(_object_pool_pathname), + blob(_blob) {} +}; + +/** + * This is the structure for the server side notification handlers + */ +template +struct SubgroupNotificationHandler { + /** + * key: object_pool_pathname + * value: an optional std::function for the handler + * The handler for "" key is the default handler, which will always be triggered. + */ + std::unordered_map> object_pool_notification_handlers; + mutable std::unique_ptr object_pool_notification_handlers_mutex; + + SubgroupNotificationHandler() : object_pool_notification_handlers_mutex(std::make_unique()) {} + + template + void initialize(derecho::ExternalClientCaller& subgroup_caller); + + void operator()(const derecho::NotificationMessage& msg); +}; + +template +using per_type_notification_handler_registry_t = std::unordered_map>; +/** + * The ServiceClient template class contains all APIs needed to read/write data. The four core APIs are put, remove, + * get, and get_by_time. We also provide a set of helper APIs for the client to get the group topology. The core APIs + * target a specific subgroup and shard of the service, or a specific object pool (which maps to a subgroup). + * The client uses a ShardMemberSelectionPolicy to determine which member of that subgroup/shard to communicate with. + */ +template +class ServiceClient { + static_assert(have_same_object_type()); + +private: + // default caller as an external client. + std::unique_ptr, CascadeTypes...>> external_group_ptr; + mutable std::mutex external_group_ptr_mutex; + // caller as a group member. + derecho::Group, CascadeTypes...>* group_ptr; + mutable std::mutex group_ptr_mutex; + // cascade server side notification handler registry. + mutable mutils::KindMap notification_handler_registry; + mutable std::mutex notification_handler_registry_mutex; + /** + * 'member_selection_policies' is a map from derecho shard to its member selection policy. + * We use a 3-tuple consisting of subgroup type index, subgroup index, and shard index to identify a shard. And + * the policy is defined by a 2-tuple with the ShardMemberSelectionPolicy enum and a user specified node id, in + * case of ShardMemorySelectionPolicy::UserSpecified. The user specified node id is used as member index if the + * policy is ShardMemberSelectionPolicy::RoundRobin + * + * The default member selection policy is defined as DEFAULT_SHARD_MEMBER_SELECTION_POLICY. + */ + std::unordered_map< + std::tuple, + std::tuple, + do_hash>> + member_selection_policies; + mutable std::shared_mutex member_selection_policies_mutex; + /** + * 'member_cache' is a map from derecho shard to its member list. This cache is used to accelerate the member + * choices process. If the client cannot connect to the cached member (after a couple of retries), it will refresh + * the corresponding cache entry. + */ + std::unordered_map< + std::tuple, + std::vector, + do_hash>> + member_cache; + mutable std::shared_mutex member_cache_mutex; + /** + * 'object_pool_info_cache' is a local cache for object pool metadata. This cache is used to accelerate the + * object access process. If an object pool does not exists, it will be loaded from metadata service. + * + * Each entry of the object_pool_info_cache is an object of type ObjectPoolMetadataCacheEntry. Such an object + * caches an object pool metadata object (opm) along with the affinity set regex processing data structures. + */ + class ObjectPoolMetadataCacheEntry { + public: + ObjectPoolMetadata opm; + /** + * The constructor + * @param[in] _opm object pool metadata + */ + ObjectPoolMetadataCacheEntry(const ObjectPoolMetadata& _opm); + + /** + * The destructor + */ + virtual ~ObjectPoolMetadataCacheEntry(); + + /** + * Convert a key string to corresponding affinity set string. + * @param[in] key_string + * + * @return affinity set string + */ + inline std::string to_affinity_set(const std::string& key_string); + + private: + /* the database storing compiled regex */ + hs_database_t* database; + /* the scratch for the regex */ + thread_local static hs_scratch_t* scratch; + }; + + std::unordered_map< + std::string, + ObjectPoolMetadataCacheEntry> + object_pool_metadata_cache; + mutable std::shared_mutex object_pool_metadata_cache_mutex; + + /** + * Pick a member by a given a policy. + * @param[in] subgroup_index + * @param[in] shard_index + * @param[in] key_for_hashing - only for KeyHashing policy, ignored otherwise. + * @param[in] retry - if true, refresh the member_cache. + */ + template + node_id_t pick_member_by_policy(uint32_t subgroup_index, + uint32_t shard_index, + const KeyTypeForHashing& key_for_hashing, + bool retry = false); + + /** + * Refresh(or fill) a member cache entry. + * @param[in] subgroup_index + * @param[in] shard_index + */ + template + void refresh_member_cache_entry(uint32_t subgroup_index, uint32_t shard_index); + + /** + * Metadata API Helper: turn a string key to subgroup type index, subgroup index, and shard index. + */ + template + std::tuple key_to_shard( + const KeyType& key, bool check_object_location = true); + + /** + * The Constructor + * We prevent calling the constructor explicitly, because the ServiceClient is a singleton. + * @param[in] _group_ptr The caller can pass a pointer pointing to a derecho group object. If the pointer is + * valid, the implementation will reply on the group object instead of creating an external + * client to communicate with group members. + */ + ServiceClient(derecho::Group, CascadeTypes...>* _group_ptr = nullptr); + +public: + /** + * ServiceClient can be an external client or a cascade server. is_external_client() test this condition. + * The external client implementation is based on ExternalGroupClient<> while the cascade node implementation is + * based on Group<>. + * + * @return true for external client; other wise false. + */ + inline bool is_external_client() const; + + /** + * Derecho group helpers: They derive the API in derecho::ExternalClient. + * - get_my_id return my local node id. + * - get_members returns all members in the top-level Derecho group. + * - get_subgroup_members returns a vector of vectors of node ids: [[node ids in shard 0],[node ids in shard 1],...] + * - get_shard_members returns the members in a shard specified by subgroup id(or subgroup type/index pair) and + * shard index. + * - get_number_of_subgroups returns the number of subgroups of a given type + * - get_number_of_shards returns the number of shards of a given subgroup + * - get_my_shard returns the shard number that this node is a member of in the specific + * subgroup (by subgroup type and index), or -1 if this node is not a member + * of any shard in the specified subgroup. + * During view change, the Client might experience failure if the member is gone. In such a case, the client needs + * refresh its local member cache by calling get_shard_members. + */ + node_id_t get_my_id() const; + + std::vector get_members() const; + + template + std::vector> get_subgroup_members(uint32_t subgroup_index) const; + +protected: + template + std::vector> type_recursive_get_subgroup_members(uint32_t type_index, uint32_t subgroup_index) const; + template + std::vector> type_recursive_get_subgroup_members(uint32_t type_index, uint32_t subgroup_index) const; + +public: + std::vector> get_subgroup_members(const std::string& object_pool_pathname); + + template + std::vector get_shard_members(uint32_t subgroup_index, uint32_t shard_index) const; + +protected: + template + std::vector type_recursive_get_shard_members(uint32_t type_index, + uint32_t subgroup_index, uint32_t shard_index) const; + template + std::vector type_recursive_get_shard_members(uint32_t type_index, + uint32_t subgroup_index, uint32_t shard_index) const; + +public: + std::vector get_shard_members(const std::string& object_pool_pathname, uint32_t shard_index); + + template + uint32_t get_number_of_subgroups() const; + + template + uint32_t get_number_of_shards(uint32_t subgroup_index) const; + + // type recursive helpers for get_number_of_shards +protected: + template + uint32_t type_recursive_get_number_of_shards(uint32_t type_index, uint32_t subgroup_index) const; + template + uint32_t type_recursive_get_number_of_shards(uint32_t type_index, uint32_t subgroup_index) const; + +public: + /** + * This get_number_of_shards() overload the typed version. + * @param[in] subgroup_type_index - the type index of the subrgoup type. + * @param[in] subgroup_index - the subgroup index in the given type. + */ + uint32_t get_number_of_shards(uint32_t subgroup_type_index, uint32_t subgroup_index) const; + + /** + * This get_number_of_shards(), pick subgroup using object pool pathname. + * @param[in] object_pool_pathname - the object pool name + */ + uint32_t get_number_of_shards(const std::string& object_pool_pathname); + + template + int32_t get_my_shard(uint32_t subgroup_index) const; + +protected: + template + int32_t type_recursive_get_my_shard(uint32_t type_index, uint32_t subgroup_index) const; + template + int32_t type_recursive_get_my_shard(uint32_t type_index, uint32_t subgroup_index) const; + +public: + /** + * @fn int32_t get_my_shard(uint32_t subgroup_type_index, uint32_t subgroup_index) const + * @brief find the shard I belong to, given the subgroup specified by type and index. + * @param[in] subgroup_type_index - the type index of the subgroup type. + * @param[in] subgroup_index - the subgroup index in the given type. + * @return The number of the shard, or -1 if current node is not in the specified subgroup. + */ + int32_t get_my_shard(uint32_t subgroup_type_index, uint32_t subgroup_index) const; + + /** + * @fn int32_t get_my_shard(const std::string& object_pool_pathname) + * @brief find the shard I belong to, given the object pool specified by object pool path name. + * @param[in] object_pool_pathname - the object pool path name. + * @return The number of the shard, or -1 if current node is not in the specified subgroup. + */ + int32_t get_my_shard(const std::string& object_pool_pathname); + + /** + * Updates the member selection policy for a shard. + * + * @tparam SubgroupType The Cascade subgroup type the shard is in + * @param[in] subgroup_index + * @param[in] shard_index + * @param[in] policy - the new policy + * @param[in] user_specified_node_id - optional, the node ID to contact if the policy is UserSpecified + */ + template + void set_member_selection_policy(uint32_t subgroup_index, uint32_t shard_index, + ShardMemberSelectionPolicy policy, node_id_t user_specified_node_id = INVALID_NODE_ID); + + /** + * Reads the member selection policy for a shard. + * + * @tparam SubgroupType The Cascade subgroup type the shard is in + * @param[in] subgroup_index + * @param[in] shard_index + * @return a 2-tuple of policy and user_specified_node_id. + */ + template + std::tuple get_member_selection_policy( + uint32_t subgroup_index, uint32_t shard_index) const; + + /** + * "put" writes an object to a given subgroup/shard. + * + * @param[in] object the object to write. + * User provided SubgroupType::ObjectType must have the following two members: + * - SubgroupType::ObjectType::key of SubgroupType::KeyType, which must be set to a + * valid key. + * - SubgroupType::ObjectType::ver of std::tuple. + * Similar to the return object, this member is a two tuple with the first member + * for a version and the second for a timestamp. A caller of put can specify either + * of the version and timestamp meaning what is the latest version/timestamp the caller + * has seen. Cascade will reject the write if the corresponding key has been updated + * already. TODO: should we make it an optional feature? + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be + * used to update the state. + * + * @return a future to the version and timestamp of the put operation. + * TODO: check if the user application is responsible for reclaim the future by reading it sometime. + */ + template + derecho::rpc::QueryResults put(const typename SubgroupType::ObjectType& object, + uint32_t subgroup_index, uint32_t shard_index, bool as_trigger = false); + /** + * "type_recursive_put" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. And the FirstType, + * SecondType, ..., RestTypes should be in the same order. + * @param[in] object the object to write + * @param[in] subgroup_index + * the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be + * used to update the state. + * + * @return a future to the version and timestamp of the put operation. + */ +protected: + template + derecho::rpc::QueryResults type_recursive_put( + uint32_t type_index, + const ObjectType& object, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger = false); + + template + derecho::rpc::QueryResults type_recursive_put( + uint32_t type_index, + const ObjectType& object, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger = false); + +public: + /** + * object pool version of "put" + * @param[in] object the object to write, the object pool is extracted from the object key. + * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be + * used to update the state. + * + * @return a future to the version and timestamp of the put operation. + */ + template + derecho::rpc::QueryResults put(const ObjectType& object, bool as_trigger = false); + + /** + * "put_and_forget" writes an object to a given subgroup/shard, but no return value. + * + * @param[in] object the object to write. + * User provided SubgroupType::ObjectType must have the following two members: + * - SubgroupType::ObjectType::key of SubgroupType::KeyType, which must be set to a + * valid key. + * - SubgroupType::ObjectType::ver of std::tuple. + * Similar to the return object, this member is a two tuple with the first member + * for a version and the second for a timestamp. A caller of put can specify either + * of the version and timestamp meaning what is the latest version/timestamp the caller + * has seen. Cascade will reject the write if the corresponding key has been updated + * already. TODO: should we make it an optional feature? + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be + * used to update the state. + */ + template + void put_and_forget(const typename SubgroupType::ObjectType& object, + uint32_t subgroup_index, uint32_t shard_index, bool as_trigger = false); + +protected: + /** + * "type_recursive_put_and_forget" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] object the object to write + * @param[in] subgroup_index + * the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be + * used to update the state. + */ + template + void type_recursive_put_and_forget( + uint32_t type_index, + const ObjectType& object, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger = false); + + template + void type_recursive_put_and_forget( + uint32_t type_index, + const ObjectType& object, + uint32_t subgroup_index, + uint32_t shard_index, + bool as_trigger = false); + +public: + /** + * object pool version of "put_and_forget" + * @param[in] object the object to write, the object pool is extracted from the object key. + * @param[in] as_trigger If true, the object will NOT apply to the K/V store. The object will only be + * used to update the state. + */ + template + void put_and_forget(const ObjectType& object, bool as_trigger = false); + + /** + * "trigger_put" writes an object to a given subgroup/shard. + * + * @param[in] object the object to write. + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a void future. + */ + template + derecho::rpc::QueryResults trigger_put(const typename SubgroupType::ObjectType& object, + uint32_t subgroup_index, uint32_t shard_index); + +protected: + /** + * "type_recursive_trigger_put" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] object the object to write + * @param[in] subgroup_index + * the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return future + */ + template + derecho::rpc::QueryResults type_recursive_trigger_put( + uint32_t type_index, + const ObjectType& object, + uint32_t subgroup_index, + uint32_t shard_index); + + template + derecho::rpc::QueryResults type_recursive_trigger_put( + uint32_t type_index, + const ObjectType& object, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "trigger_put" + * @param[in] object the object to write, the object pool is extracted from the object key. + */ + template + derecho::rpc::QueryResults trigger_put(const ObjectType& object); + /** + * "collective_trigger_put" writes an object to a set of nodes. + * + * Please notice that returning from QueryResults::get() only means that the message has been sent by the + * sender. It does NOT guarantee that the message is/will be successfully processed by the remote side. However, + * we agree that QueryResults should reflect exceptions or errors either on local or remote side, which is + * not enabled so far. TODO: Track exception in derecho::rpc::QueryResults + * + * @param[in] object the object to write. + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] nodes_and_futures map from node ids to futures. + */ + template + void collective_trigger_put(const typename SubgroupType::ObjectType& object, + uint32_t subgroup_index, + std::unordered_map>>& nodes_and_futures); + + /** + * "remove" deletes an object with the given key. + * + * @param[in] key the object key + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future to the version and timestamp of the put operation. + * TODO: check if the user application is responsible for reclaim the future by reading it sometime. + */ + template + derecho::rpc::QueryResults remove(const typename SubgroupType::KeyType& key, + uint32_t subgroup_index, uint32_t shard_index); + +protected: + /** + * "type_recursive_remove" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] key the key + * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return a future to the version and timestamp of the put operation. + */ + template + derecho::rpc::QueryResults type_recursive_remove( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index); + + template + derecho::rpc::QueryResults type_recursive_remove( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "remove" + * @param[in] key the object key + * + * @return returns a future + */ + template + derecho::rpc::QueryResults remove(const KeyType& key); + + /** + * "get" retrieve the object of a given key + * + * @param[in] key the object key + * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, get will either + * read the current object from memory of the replica that handles the get request + * (if stable is false), or read the latest stable version that is persisted (if + * stable is true). Note that in any case "get" will contact only a single replica; + * to use an atomic multicast to get the latest version that is present on all replicas, + * use multi_get. + * @param[in] stable if true, get will wait until the requested version's persistent data is safe, meaning the + * persistent data is persisted on all replicas. + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future to the retrieved object. + * TODO: check if the user application is responsible for reclaim the future by reading it sometime. + */ + template + derecho::rpc::QueryResults get( + const typename SubgroupType::KeyType& key, + const persistent::version_t& version = CURRENT_VERSION, + bool stable = true, + uint32_t subgroup_index = 0, + uint32_t shard_index = 0); + +protected: + /** + * "type_recursive_get" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] key the key + * @param[in] version the version + * @param[in] stable stable or not? + * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return a future for the object. + */ + template + auto type_recursive_get( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + + template + auto type_recursive_get( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "get" + * + * @param[in] key the object key; the object pool is extracted from this key + * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, get will either + * read the current object from memory of the replica that handles the get request + * (if stable is false), or read the latest stable version that is persisted (if + * stable is true). Note that in any case "get" will contact only a single replica; + * to use an atomic multicast to get the latest version that is present on all replicas, + * use multi_get. + * @param[in] stable if true, get will wait until the requested version's persistent data is safe, meaning the + * persistent data is persisted on all replicas. + * @return a future to the retrieved object. + */ + template + auto get( + const KeyType& key, + const persistent::version_t& version = CURRENT_VERSION, + bool stable = true); + + /** + * "multi_get" retrieves the latest version of the object for a given key using an atomic broadcast. + * This ensures that the get request is mutually exclusive and linearizable with any concurrent put + * requests to the same key. + * + * @param[in] key the object key + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future to the retrieved object, including a response from each replica in the shard. + */ + template + derecho::rpc::QueryResults multi_get(const typename SubgroupType::KeyType& key, + uint32_t subgroup_index, uint32_t shard_index); + + /** + * "type_recursive_multi_get" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] key the key + * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return a future for the object. + */ +protected: + template + auto type_recursive_multi_get( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index); + + template + auto type_recursive_multi_get( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "multi_get" + * + * @param[in] key the object key; the object pool is extracted from this key + * + * @return a future for the retrieved object, including a response from each replica in the object pool + */ + template + auto multi_get(const KeyType& key); + + /** + * "get_by_time" retrieve the object of a given key + * + * @param[in] key the object key + * @param[in] ts_us Wall clock time in microseconds. + * @param[in] stable stable get or not + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future to the retrieved object. + * TODO: check if the user application is responsible for reclaim the future by reading it sometime. + */ + template + derecho::rpc::QueryResults get_by_time( + const typename SubgroupType::KeyType& key, + const uint64_t& ts_us, + const bool stable = true, + uint32_t subgroup_index = 0, + uint32_t shard_index = 0); + + /** + * "type_recursive_get_by_time" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] key the key + * @param[in] ts_us Wall clock time in microseconds. + * @param[in] stable stable get or not + * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return a future for the object. + */ +protected: + template + auto type_recursive_get_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + + template + auto type_recursive_get_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "get_by_time" + * + * @param[in] key the object key; the object pool is extracted from this key + * @param[in] ts_us Wall clock time in microseconds. + * @param[in] stable stable get or not + * + * @return a future for the retrieved object. + */ + template + auto get_by_time( + const KeyType& key, + const uint64_t& ts_us, + const bool stable = true); + + /** + * "get_size" retrieve size of the object of a given key + * + * @param[in] key the object key + * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, this will either + * get the size of the current object from memory of the replica that handles the request + * (if stable is false), or get the size of the latest stable version that is persisted + * (if stable is true). Note that in any case "get_size" will contact only a single replica; + * to use an atomic multicast to check the latest version that is present on all replicas, + * use multi_get_size. + * @param[in] stable stable get or not + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future to the retrieved size. + * TODO: check if the user application is responsible for reclaim the future by reading it sometime. + */ + template + derecho::rpc::QueryResults get_size( + const typename SubgroupType::KeyType& key, + const persistent::version_t& version, + const bool stable = true, + uint32_t subgroup_index = 0, + uint32_t shard_index = 0); + +protected: + /** + * "type_recursive_get_size" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] key the key + * @param[in] version version + * @param[in] stable stable get size or not + * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return a future for the retrieved size. + */ + template + derecho::rpc::QueryResults type_recursive_get_size( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + + template + derecho::rpc::QueryResults type_recursive_get_size( + uint32_t type_index, + const KeyType& key, + const persistent::version_t& version, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "get_size" + * @param[in] key the object key + * @param[in] version the version of the object to read. If equal to CURRENT_VERSION, this will either + * get the size of the current object from memory of the replica that handles the request + * (if stable is false), or get the size of the latest stable version that is persisted + * (if stable is true). Note that in any case "get_size" will contact only a single replica; + * to use an atomic multicast to check the latest version that is present on all replicas, + * use multi_get_size. + * @param[in] stable stable get or not + * @return a future for the retrieved size. + */ + template + derecho::rpc::QueryResults get_size( + const KeyType& key, + const persistent::version_t& version, + const bool stable = true); + + /** + * "multi_get_size" retrieves the size of the current version of the object with a given key, using an atomic broadcast. + * + * @param[in] key the object key + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future to the retrieved size. + */ + template + derecho::rpc::QueryResults multi_get_size( + const typename SubgroupType::KeyType& key, + uint32_t subgroup_index, uint32_t shard_index); + + /** + * "type_recursive_multi_get_size" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] key the key + * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return a future for the object. + */ +protected: + template + derecho::rpc::QueryResults type_recursive_multi_get_size( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index); + + template + derecho::rpc::QueryResults type_recursive_multi_get_size( + uint32_t type_index, + const KeyType& key, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "multi_get_size" + * + * @param[in] key the object key; the object pool is extracted from this key + * + * @return a future for the retrieved size. + */ + template + derecho::rpc::QueryResults multi_get_size(const KeyType& key); + + /** + * "get_size_by_time" retrieve size of the object of a given key + * + * @param[in] key the object key + * @param[in] ts_us Wall clock time in microseconds. + * @param[in] stable stable get or not + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future to the retrieved size. + * TODO: check if the user application is responsible for reclaim the future by reading it sometime. + */ + template + derecho::rpc::QueryResults get_size_by_time( + const typename SubgroupType::KeyType& key, + const uint64_t& ts_us, + const bool stable = true, + uint32_t subgroup_index = 0, + uint32_t shard_index = 0); + +protected: + /** + * "type_recursive_get_size" is a helper function for internal use only. + * @param[in] type_index the index of the subgroup type in the CascadeTypes... list. and the FirstType, + * SecondType, .../ RestTypes should be in the same order. + * @param[in] key the key + * @param[in] ts_us Wall clock time in microseconds. + * @param[in] stable stable get or not + * @param[in] subgroup_index the subgroup index in the subgroup type designated by type_index + * @param[in] shard_index the shard index + * + * @return a future for the object. + */ + template + derecho::rpc::QueryResults type_recursive_get_size_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + + template + derecho::rpc::QueryResults type_recursive_get_size_by_time( + uint32_t type_index, + const KeyType& key, + const uint64_t& ts_us, + const bool stable, + uint32_t subgroup_index, + uint32_t shard_index); + +public: + /** + * object pool version of "get_size_by_time" + * + * @param[in] key the object key + * @param[in] ts_us Wall clock time in microseconds. + * @param[in] stable stable get or not + * + * @return a future for the retrieved size. + */ + template + derecho::rpc::QueryResults get_size_by_time( + const KeyType& key, + const uint64_t& ts_us, + const bool stable = true); + + /** + * "list_keys" retrieve the list of keys in a shard + * + * @param[in] version the version at which to list the keys; all keys that existed at or before this version + * will be included. If equal to CURRENT_VERSION, list_keys will list all keys currently + * in memory at the replica that handles the request. + * @param[in] stable if true, list_keys will wait until the requested version's persistent data is safe, meaning the + * persistent data is persisted on all replicas, before listing the keys present at that version. + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future for the vector of keys + * TODO: check if the user application is responsible for reclaim the future by reading it sometime. + */ + template + derecho::rpc::QueryResults> list_keys( + const persistent::version_t& version, + const bool stable = true, + uint32_t subgroup_index = 0, + uint32_t shard_index = 0); + +protected: + template + auto type_recursive_list_keys( + uint32_t type_index, + const persistent::version_t& version, + const bool stable, + const std::string& object_pool_pathname); + template + auto type_recursive_list_keys( + uint32_t type_index, + const persistent::version_t& version, + const bool stable, + const std::string& object_pool_pathname); + template + std::vector>>> + __list_keys(const persistent::version_t& version, const bool stable, const std::string& object_pool_pathname); + +public: + /** + * @brief object pool version of "list_keys"; lists all keys in an object pool + * + * @param[in] version the version at which to list the keys; all keys that existed at or before this version + * will be included. If equal to CURRENT_VERSION, list_keys will list all keys currently + * in memory at the replica that handles the request. + * @param[in] stable if true, list_keys will wait until the requested version's persistent data is safe, meaning the + * persistent data is persisted on all replicas, before listing the keys present at that version. + * @param[in] object_pool_pathname the object pathname + * + * @return a vector of futures for key lists, with one key list for each shard in the object pool. + * The return value's type will look like vector>>>, where KeyType is either string or uint64_t + */ + auto list_keys(const persistent::version_t& version, const bool stable, const std::string& object_pool_pathname); + + /** + * A function that helps unpack the return value of list_keys (object-pool version). Iterates through the + * vector of QueryResults and waits for each one to complete, then combines the resulting vectors of keys + * into a single vector of keys (across all shards in the object pool). + * + * @tparam KeyType The type of a key in this object pool + * @param future The vector-of-futures returned by list_keys or multi_list_keys + * @return a vector of keys in the object pool + */ + template + std::vector wait_list_keys( + std::vector>>>& future); + + /** + * "multi_list_keys" retrieves the list of keys in a shard at the current version, using an atomic multicast + * + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future for the list of keys. + */ + template + derecho::rpc::QueryResults> multi_list_keys( + uint32_t subgroup_index, + uint32_t shard_index); + +protected: + template + auto type_recursive_multi_list_keys( + uint32_t type_index, + const std::string& object_pool_pathname); + template + auto type_recursive_multi_list_keys( + uint32_t type_index, + const std::string& object_pool_pathname); + template + std::vector>>> + __multi_list_keys(const std::string& object_pool_pathname); + +public: + /** + * object pool version of "multi_list_keys" + * + * @param[in] object_pool_pathname the object pathname + * + * @return a vector of futures for key lists, with one key list for each shard in the object pool. + * The return value's type will look like vector>>>, where KeyType is either string or uint64_t + */ + auto multi_list_keys(const std::string& object_pool_pathname); + + /** + * "list_keys_by_time" retrieves the list of keys in a shard at a specific time + * + * @param[in] ts_us Wall clock time in microseconds. + * @param[in] stable stable get or not + * @param[in] subgroup_index the subgroup index of CascadeType + * @param[in] shard_index the shard index. + * + * @return a future for the list of keys + */ + template + derecho::rpc::QueryResults> list_keys_by_time( + const uint64_t& ts_us, + const bool stable = true, + uint32_t subgroup_index = 0, + uint32_t shard_index = 0); + +protected: + template + auto type_recursive_list_keys_by_time( + uint32_t type_index, + const uint64_t& ts_us, + const bool stable, + const std::string& object_pool_pathname); + template + auto type_recursive_list_keys_by_time( + uint32_t type_index, + const uint64_t& ts_us, + const bool stable, + const std::string& object_pool_pathname); + template + std::vector>>> + __list_keys_by_time(const uint64_t& ts_us, const bool stable, const std::string& object_pool_pathname); + +public: + /** + * object pool version of "list_keys_by_time" + * + * @param[in] ts_us timestamp + * @param[in] stable stable flag + * @param[in] object_pool_pathname the object pathname + * + * @return a vector of futures for key lists, with one key list for each shard in the object pool. + * The return value's type will look like vector>>>, where KeyType is either string or uint64_t + */ + auto list_keys_by_time(const uint64_t& ts_us, const bool stable, const std::string& object_pool_pathname); + + /** + * Object Pool Management API: refresh object pool cache + * We load 'unstable' (committed but maybe not persisted) metadata here. + */ + void refresh_object_pool_metadata_cache(); + + /** + * Object Pool Management API: create object pool + * + * @tparam SubgroupType Type of the subgroup for the created object pool + * @param[in] pathname Object pool's pathname as identifier. + * @param[in] subgroup_index Index of the subgroup + * @param[in] sharding_policy The default sharding policy for this object pool + * @param[in] object_locations The set of special object locations. + * @param[in] affinity_set_regex + * The affinity set regex. + * + * @return a future to the version and timestamp of the put operation. + */ + template + derecho::rpc::QueryResults create_object_pool( + const std::string& pathname, const uint32_t subgroup_index, + const sharding_policy_t sharding_policy = HASH, + const std::unordered_map& object_locations = {}, + const std::string& affinity_set_regex = ""); + + /** + * ObjectPoolManagement API: remove object pool + * + * @param[in] pathname Object pool pathname + * + * @return a future to the version and timestamp of the put operation. + */ + derecho::rpc::QueryResults remove_object_pool(const std::string& pathname); + +private: + /** + * ObjectPoolManagement API: find object pool + * + * @param[in] pathname Object pool pathname + * @param[in] rlck shared lock, which needs to be hold. + * + * @return the object pool metadata + */ + ObjectPoolMetadata internal_find_object_pool(const std::string& pathname, + std::shared_lock& rlck); + +public: + /** + * ObjectPoolManagement API: find object pool + * + * @param[in] pathname Object pool pathname + * + * @return the object pool metadata + */ + ObjectPoolMetadata find_object_pool(const std::string& pathname); + + /** + * ObjectPoolManagement API: find object pool and affinity_set from key + * + * @param[in] key The key of an object. + * + * @return the object pool metadata along with the affinity set string + */ + template + std::pair, std::string> + find_object_pool_and_affinity_set_by_key(const KeyType& key); + + /** + * ObjectPoolManagement API: list all the object pools by pathnames + * + * @param[in] include_deleted show deleted pools with an exclaimation point(!). + * @param[in] refresh false for cached object ids, true for refreshed ids. + * + * @return the pool ids. + */ + std::vector list_object_pools(bool include_deleted, bool refresh = false); + + /** + * Register an notification handler to a subgroup. If such a handler has been registered, it will be replaced + * by the new one. + * + * @tparam SubgroupType The Subgroup Type + * @param[in] handler The handler to reigster + * @param[in] subgroup_index Index of the subgroup + * + * @return true if a previous notification handler is replaced. + */ + template + bool register_notification_handler( + const cascade_notification_handler_t& handler, + const uint32_t subgroup_index = 0); + +protected: + template + bool register_notification_handler( + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname, + const uint32_t subgroup_index); + template + bool type_recursive_register_notification_handler( + uint32_t type_index, + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname, + const uint32_t subgroup_index); + template + bool type_recursive_register_notification_handler( + uint32_t type_index, + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname, + const uint32_t subgroup_index); + +public: + /** + * Register notification handler(object pool version). If such a handler has been registered, it will be + * replaced by the new one. + * + * @tparam SubgroupType The Subgroup Type + * @param[in] handler The handler to reigster + * @param[in] object_pool_pathname To with object pool is this handler registered. + * + * @return true if a previous notification handler is replaced. + */ + bool register_notification_handler( + const cascade_notification_handler_t& handler, + const std::string& object_pool_pathname); + + /** + * Send a notification message to an external client. + * + * @tparam SubgroupType The Subgroup Type + * @param[in] msg The message to send + * @param[in] subgroup_index The subgroup index + * @param[in] client_id The node id of the external client to be notified + */ + template + void notify(const Blob& msg, + const uint32_t subgroup_index, + const node_id_t client_id) const; + +protected: + template + void notify(const Blob& msg, + const std::string& object_pool_pathname, + const uint32_t subgroup_index, + const node_id_t client_id) const; + template + void type_recursive_notify( + uint32_t type_index, + const Blob& msg, + const std::string& object_pool_pathname, + const uint32_t subgroup_index, + const node_id_t client_id) const; + template + void type_recursive_notify( + uint32_t type_index, + const Blob& msg, + const std::string& object_pool_pathname, + const uint32_t subgroup_index, + const node_id_t client_id) const; + +public: + /** + * Send a notification message to an external client. + * + * @param[in] msg The messgae to send + * @param[in] object_pool_pathname In which object_pool the notification is in. + * @param[in] client_id The client id + */ + void notify(const Blob& msg, + const std::string& object_pool_pathname, + const node_id_t client_id); + +#ifdef ENABLE_EVALUATION + /** + * Dump the timestamp log entries into a file on each of the nodes in a shard. + * + * @param[in] filename - the output filename + * @param[in] subgroup_index - the subgroup index + * @param[in] shard_index - the shard index + * + * @return query results + */ + template + derecho::rpc::QueryResults dump_timestamp(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index); + + /** + * The object store version: + * + * @param[in] filename - the filename + * @param[in] object_pool_pathname - the object pool pathname + */ + void dump_timestamp(const std::string& filename, const std::string& object_pool_pathname); + + /** + * Dump the timestamp log entries into a file on each of the nodes in a subgroup. + * + * @param[in] filename - the output filename + * @param[in] subgroup_index - the subgroup index + */ + template + void dump_timestamp(const uint32_t subgroup_index, const std::string& filename); + +protected: + template + void type_recursive_dump(uint32_t type_index, uint32_t subgroup_index, const std::string& filename); + + template + void type_recursive_dump(uint32_t type_index, uint32_t subgroup_index, const std::string& filename); + +public: +#ifdef DUMP_TIMESTAMP_WORKAROUND + /** + * Dump the timestamp log entries into a file on a specific node. + * + * @param[in] filename - the output filename + * @param[in] subgroup_index - the subgroup index + * @param[in] shard_index - the shard index + * @param[in] node_id - the given node id. + * + * @return a vector of query results. + */ + template + derecho::rpc::QueryResults dump_timestamp_workaround(const std::string& filename, const uint32_t subgroup_index, const uint32_t shard_index, const node_id_t node_id); +#endif + + /** + * Evaluate the ordered put performance inside a shard. Please note that those put does not involve the + * external client data path. + * + * @param[in] message_size - the message size for the shard. TODO: we should be able to retrieve the maximum + * message size from SubgroupType, subgroup_index and shard_index. How? + * @param[in] duration_sec - the duration of the test in seconds. + * @param[in] subgroup_index - the subgroup index + * @param[in] shard_index - the shard index + * + * @return the value in ops. + */ + template + derecho::rpc::QueryResults perf_put(const uint32_t message_size, const uint64_t duration_sec, const uint32_t subgroup_index, const uint32_t shard_index); +#endif // ENABLE_EVALUATION + + const static std::vector subgroup_type_order; + const static uint32_t invalid_subgroup_type_index; + /** + * Get type index + * @return the the subgroup type index + */ + template + static uint32_t get_subgroup_type_index(); + + /* singleton */ +private: + static std::unique_ptr service_client_singleton_ptr; + static std::mutex singleton_mutex; + +public: + /** + * Initialize the service_client_single_ptr singleton with a cascade service. This can only be called once + * before any get_service_client() is called. + * @param[in] _group_ptr The caller can pass a pointer pointing to a derecho group object. If the pointer is + * valid, the implementation will reply on the group object instead of creating an external + * client to communicate with group members. + */ + static void initialize(derecho::Group, CascadeTypes...>* _group_ptr); + + /** + * Get the singleton ServiceClient API. If it does not exists, initialize it as an external client. + */ + static ServiceClient& get_service_client(); +}; // ServiceClient + +} // namespace cascade +} // namespace derecho + +#include "detail/service_client_impl.hpp" diff --git a/include/cascade/service_client_api.hpp b/include/cascade/service_client_api.hpp index 04d7bc9d..cbaa224d 100644 --- a/include/cascade/service_client_api.hpp +++ b/include/cascade/service_client_api.hpp @@ -1,6 +1,7 @@ #pragma once #include "service_types.hpp" #include "service.hpp" +#include "service_client.hpp" #ifdef HAS_BOOLINQ #include @@ -60,7 +61,7 @@ class CascadeShardLinq : public boolinq::Linq CascadeShardLinq from_shard( std::vector& key_list, - ServiceClientType& capi, uint32_t subgroup_index, + ServiceClientType& capi, uint32_t subgroup_index, uint32_t shard_index, persistent::version_t version) { /* load keys. */ auto result = capi.template list_keys(version, true, subgroup_index, shard_index); @@ -95,7 +96,7 @@ CascadeShardLinq from_shard( * @param capi The cascade client. * @param subgroup_index * @param shard_index - * @param ts_us The unix epoch time in microsecond. + * @param ts_us The unix epoch time in microsecond. * @return a Linq object */ template @@ -138,11 +139,11 @@ class CascadeVersionLinq : public boolinq::Linq() {}; - - CascadeVersionLinq(ServiceClientType& capi, - uint32_t sgidx, - uint32_t shidx, - const typename CascadeType::KeyType& objkey, + + CascadeVersionLinq(ServiceClientType& capi, + uint32_t sgidx, + uint32_t shidx, + const typename CascadeType::KeyType& objkey, persistent::version_t ver, std::function nextFunc) : @@ -165,7 +166,7 @@ class CascadeVersionLinq : public boolinq::Linq CascadeVersionLinq from_versions( - const typename CascadeType::KeyType& key, + const typename CascadeType::KeyType& key, ServiceClientType &capi, uint32_t subgroup_index, uint32_t shard_index, persistent::version_t version) { @@ -174,12 +175,12 @@ CascadeVersionLinq from_versions( if (ver == INVALID_VERSION) { throw boolinq::LinqEndException(); } - + do { auto result = capi.template get(key,ver,true/*always use stable data*/,subgroup_index,shard_index); for (auto& reply_future:result.get()) { auto object = reply_future.second.get(); - ver = object.previous_version_by_key; + ver = object.previous_version_by_key; if (!object.is_null()) return object; } @@ -210,7 +211,7 @@ class CascadeSubgroupLinq : public boolinq::Linq>& shard_linq_list, std::function&)> nextFunc) : - + boolinq::Linq, typename CascadeType::ObjectType>(std::make_pair(shard_linq_list.begin(),shard_linq_list.end()), nextFunc), client_api(capi), subgroup_index(sgidx), @@ -219,10 +220,10 @@ class CascadeSubgroupLinq : public boolinq::Linq CascadeSubgroupLinq from_subgroup( - std::unordered_map>& shardidx_to_keys, + std::unordered_map>& shardidx_to_keys, std::vector>& shard_linq_list, ServiceClientType& capi, uint32_t sgidx, persistent::version_t ver) { - + uint32_t num_shards = capi.template get_number_of_shards(sgidx); std::cout << "num_shards=" << num_shards << std::endl; for (uint32_t shidx=0;shidx from_subgroup( shard_linq_list.emplace_back(from_shard(shardidx_to_keys[shidx],capi,sgidx,shidx,ver)); } std::cout << "done prepare shard iterators." << std::endl; - + return CascadeSubgroupLinq(capi,sgidx,ver,shard_linq_list, [&shard_linq_list](CascadeSubgroupLinqStorageType& _storage) { if (_storage.first == _storage.second) { @@ -253,12 +254,12 @@ CascadeSubgroupLinq from_subgroup( try { auto obj = _storage.first->next(); return obj; - } + } catch(boolinq::LinqEndException &) { _storage.first++; - } + } } while (_storage.first != _storage.second); - + throw boolinq::LinqEndException(); }); } @@ -301,7 +302,7 @@ class CascadeObjpoolLinq : public boolinq::Linq CascadeObjpoolLinq from_objectpool( ServiceClientType& capi, - std::vector& key_list, + std::vector& key_list, persistent::version_t version, const std::string objpool_path) { /* load keys. */ diff --git a/src/service/server.cpp b/src/service/server.cpp index c7850d45..0f29d25b 100644 --- a/src/service/server.cpp +++ b/src/service/server.cpp @@ -2,6 +2,7 @@ #include "cascade/cascade.hpp" #include "cascade/service.hpp" +#include "cascade/service_client.hpp" #include "cascade/service_types.hpp" #include "cascade/object.hpp" diff --git a/src/service/server.hpp b/src/service/server.hpp index ee348cb5..1c1e31ad 100644 --- a/src/service/server.hpp +++ b/src/service/server.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -73,13 +74,13 @@ class CascadeServiceCDPO : public CriticalDataPathObserver { // dfg_ocdpos.second is a set of ocdpo info object of type prefix_ocdpo_info_t. for(auto oiit = dfg_ocdpos.second.begin(); oiit != dfg_ocdpos.second.end();) { if((oiit->hook != DataFlowGraph::VertexHook::BOTH) && ( - (oiit->hook == DataFlowGraph::VertexHook::ORDERED_PUT && is_trigger) || + (oiit->hook == DataFlowGraph::VertexHook::ORDERED_PUT && is_trigger) || (oiit->hook == DataFlowGraph::VertexHook::TRIGGER_PUT && !is_trigger))) { // not my hook, skip it. oiit = dfg_ocdpos.second.erase(oiit); } else if (is_trigger) { new_actions = true; - if (oiit->execution_environment != + if (oiit->execution_environment != DataFlowGraph::VertexExecutionEnvironment::PTHREAD) { has_mproc_udl = true; } @@ -133,13 +134,13 @@ class CascadeServiceCDPO : public CriticalDataPathObserver { value_ptr, oi.output_map // outputs ); - + #ifdef ENABLE_EVALUATION ActionPostExtraInfo apei; apei.uint64_val = 0; apei.info.is_trigger = is_trigger; #endif - + #ifdef ENABLE_EVALUATION apei.info.stateful = oi.statefulness; #endif