Files
spark/src/lib/api/core/ApiClientInterop.cpp
T

266 lines
8.6 KiB
C++

#include "ApiClientInterop.h"
#include "lib/quickbus/quickbus.h"
#include "lib/config/config.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/Dispatcher.h"
#include "../public/ServiceContext.h"
#include "../public/Result.h"
#include "lib/api/Apicore.pb.h"
#include "lib/config/protos/quickbus_cfg.pb.h"
#include "lib/config/protos/system.pb.h"
#include "lib/config/protos/Ports.pb.h"
#ifdef WIN32
#include <excpt.h>
#endif
namespace bts {
namespace api {
config::QuickbusNodeCfg get_client_node_cfg()
{
auto cfg = config::get<config::QuickbusCfg>();
config::QuickbusNodeCfg node_cfg;
node_cfg.set_reconnect_period_secs(cfg.reconnect_period_secs());
node_cfg.set_hb_period_msecs(cfg.hb_period_msecs());
node_cfg.set_max_missed_hbs(cfg.max_missed_hbs());
node_cfg.set_parent_host(
cfg.has_qbus_host() ?
cfg.qbus_host() :
config::get<config::System>().config_ip()
);
node_cfg.set_parent_port(
cfg.has_qbus_port() ?
cfg.qbus_port() :
config::get<config::Ports>().qbus_port()
);
node_cfg.set_my_port(0);
return node_cfg;
}
//
// Common
CAPI Quickbus* api_create_quickbus(const char* qbconfig, long length) {
// If config hasn't been initialized yet do it now and suppress exceptions.
// When we're called from a nuget package we may not have defaults which can cause errors
config::initialize_nothrow(nullptr, 0);
config::QuickbusNodeCfg cfg;
if (length > 0 && qbconfig != nullptr && cfg.ParseFromArray(qbconfig, length)) {
return new Quickbus(cfg);
}
// Note: Eventually api will probably want it's own instance of QB. For now we share
auto temp_conf = get_client_node_cfg();
LOG(Api,Info,"Created Quickbus");
return new Quickbus(temp_conf);
}
CAPI void api_destroy_quickbus(Quickbus* qbus) {
#ifndef WIN32
if (qbus != nullptr) {
delete qbus;
}
#endif
}
CAPI void api_post(Quickbus& qbus, const std::function<void()>& callback, uint64_t delayms) {
qbus.post(callback, delayms);
}
std::string base_topic(const char* type_name, const char* service_qualifier) {
const std::string topic_base = "api-";
if (service_qualifier == nullptr ||
strlen(service_qualifier) == 0) {
return topic_base + type_name;
}
return topic_base + type_name + "-" + service_qualifier;
}
qbus::RPCServiceName rpc_topic(const std::string& base_topic) {
return qbus::RPCServiceName(base_topic);
}
//
// Client
struct ApiClient {
ApiClient(Quickbus& qbus, const std::string& topic) :
qbus(qbus),
service_name(topic),
rpc(qbus, rpc_topic(topic)) {}
Quickbus& qbus;
std::string service_name;
qbus::RPCClient rpc;
};
CAPI ApiClient* api_client_create(Quickbus* pqbus, const char* type_name, const char* service_qualifier) {
bts_assert(pqbus);
auto service_name = base_topic(type_name, service_qualifier);
LOG(Api,Info, "Create client: {}",service_name);
ApiClient* pclient = new ApiClient(*pqbus, service_name);
return pclient;
}
CAPI void api_client_destroy(ApiClient* pclient) {
if (pclient) {
LOG(Api,Info, "destroy client: {}",pclient->service_name);
delete pclient;
}
}
template <class C>
void safe_call_fn(const C& cb) {
#ifdef _MSC_VER
__try {
#endif
cb();
#ifdef _MSC_VER
}
__except(EXCEPTION_EXECUTE_HANDLER) {
LOG(Api, Error, "Error calling CLR");
}
#endif
}
void reply_error(ApiError error, raw_response_callback cb, void* extra) {
ResponseHeader response;
response.set_code(error);
auto str = response.SerializeAsString();
str = (char)(str.size()+1) + str;
safe_call_fn([&]() {
cb(str.c_str(), str.size(), extra);
});
}
int32_t rpc_connect_timeout_ms() {
static int32_t t = config::get<config::RPCConfig>().rpc_connect_timeout_seconds() * 1000;
return t;
}
void api_client_send_internal(ApiClient* pclient, qbus::RPCRequest&& request, raw_response_callback cb, int32_t delay, void* extra) {
bts_assert(pclient);
bool connected = pclient->rpc.wait_until_connected(0);
if (!connected && delay > 0) {
// This branch is the uncommon 'slow path' where connection is taking longer than we expect
LOG(Api,Diag,"rpc call delay waiting for connection {}",((int64_t)request.value().c_str()));
pclient->qbus.post([=, request{std::move(request)}]() mutable {
api_client_send_internal(pclient, std::move(request), cb, delay - 100, extra);
}, 100);
return;
}
pclient->rpc.send(std::move(request), [=](qbus::RPCError e, const qbus::RPCReplyPtr& reply) {
LOG(Api,Diag,"rpc reply {}",((int64_t)request.value().c_str()));
switch (e) {
case qbus::RPCError::RPC_OK:
safe_call_fn([&]() {
cb(reply->value().c_str(), reply->value().size(), extra);
});
break;
case qbus::RPCError::NO_SERVER:
reply_error(ApiError::NO_SERVER, cb, extra);
break;
case qbus::RPCError::SERVER_DISCONNECT:
reply_error(ApiError::SERVER_DISCONNECT, cb, extra);
break;
default:
reply_error(ApiError::UNEXPECTED_RESPONSE, cb, extra);
}
});
}
CAPI void api_client_send(ApiClient* pclient, const char* buffer, int buffer_length, raw_response_callback cb, void* extra) {
LOG(Api,Diag,"send rpc {}",((int64_t)buffer));
std::string strbuf(buffer, buffer_length);
qbus::RPCRequest request(move(strbuf));
api_client_send_internal(pclient, std::move(request), cb, rpc_connect_timeout_ms(), extra);
}
CAPI bool api_client_is_on_qb_thread(ApiClient* pclient) {
return pclient->qbus.on_qb_thread();
}
void safe_call_subscription_callback(raw_subscription_callback cb, EventType type, const std::vector<StreamUpdate>& updates, void* extra) {
safe_call_fn([&]() {
if (updates.size() > 0)
cb(type, &updates[0], updates.size(), extra);
else
cb(type, nullptr, 0, extra);
});
}
bts::qbus::Topic stream_topic(const std::string& base_topic, const char* method, const char* qualifier) {
if (qualifier == nullptr || strlen(qualifier) == 0) {
return qbus::Topic(base_topic + "-" + method);
}
else {
return qbus::Topic(base_topic + "-" + method + "/" + qualifier);
}
}
CAPI TableSubscription api_client_subscribe(ApiClient* pclient, const char* method, const char* qualifier, raw_subscription_callback cb, void* extra) {
bts_assert(pclient);
static_assert((int)qbus::TableEvent::EventType::TABLE_CREATED == EventType::TABLE_CREATED, "Keep these Enums in sync");
static_assert((int)qbus::TableEvent::EventType::TABLE_DESTROYED == EventType::TABLE_DESTROYED, "");
static_assert((int)qbus::TableEvent::EventType::TABLE_UPDATED == EventType::TABLE_UPDATED, "");
static_assert((int)qbus::Change::Type::REMOVE == StreamUpdate::REMOVE, "");
static_assert((int)qbus::Change::Type::REPLACE == StreamUpdate::REPLACE, "");
static_assert((int)qbus::Change::Type::CREATE == StreamUpdate::CREATE, "");
auto topic = stream_topic(pclient->service_name, method, qualifier);
LOG(Api,Diag,"api subscribe: {}" ,topic.value());
auto result = new qbus::TableSubscriber(pclient->qbus, equal_topic(topic), [cb, extra](const qbus::TableEvent& ev) {
if (ev.event_type == qbus::TableEvent::EventType::TABLE_CREATED ||
ev.event_type == qbus::TableEvent::EventType::TABLE_DESTROYED) {
safe_call_fn([&]() {
cb((EventType)ev.event_type, nullptr, 0, extra);
});
}
else {
bts_assert(ev.update != nullptr);
size_t update_count = ev.update->size();
std::vector<StreamUpdate> bupdates(update_count);
// Copy structure for interop
for (size_t x = 0; x < update_count; ++x) {
const auto& update = (*ev.update)[x];
bupdates[x].type = static_cast<StreamUpdate::Type>(update.type);
bupdates[x].key = update.key.value();
const auto& val = update.value->value();
bupdates[x].buffer_len = val.size();
bupdates[x].buffer = val.c_str();
}
safe_call_subscription_callback(cb, (EventType)ev.event_type, bupdates, extra);
}
}, true);
return result;
}
CAPI void api_client_unsubscribe(ApiClient* pclient, TableSubscription pvtable) {
qbus::TableSubscriber* ptable = static_cast<qbus::TableSubscriber*>(pvtable);
if (pclient && ptable) {
ptable->disconnect();
delete ptable;
}
}
CAPI void api_client_log_error(const char* message) {
// managed api dll does not include logging but native api does `\_(o,o)_/`
std::string msg(message);
utils::Dispatcher::background().invoke([msg]() {
LOG(Api, Error, "{}", msg);
});
}
}}// namespace