354 lines
11 KiB
C++
354 lines
11 KiB
C++
#ifndef _WIN32
|
|
#include "SecurityIDRecorder.h"
|
|
#include "lib/domain_model/Security.h"
|
|
#include "lib/logging/logging.h"
|
|
#include "lib/persistence/IStore.h"
|
|
#include "lib/persistence/SqliteStore.h"
|
|
#include "lib/utils/checked_arithmetic.h"
|
|
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/pool/object_pool.hpp>
|
|
#include <memory>
|
|
#include <sqlite/sqlite3.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <tbb/concurrent_queue.h>
|
|
|
|
using namespace std;
|
|
using namespace bts::utils;
|
|
using std::size_t;
|
|
|
|
namespace bts {
|
|
namespace md {
|
|
|
|
using bts::api::Security;
|
|
using bts::api::SecurityType;
|
|
using bts::api::Venue;
|
|
using bts::persistence::IStore;
|
|
using bts::persistence::SqliteStore;
|
|
|
|
uint64_t exchange_id(const api::Security &security);
|
|
|
|
static std::unique_ptr<IStore> get_default_store() {
|
|
return std::make_unique<SqliteStore>("key_ids");
|
|
}
|
|
|
|
static persistence::StoreRow get_row(
|
|
Venue venue,
|
|
SecurityType type,
|
|
const std::string &name,
|
|
const std::string &prefix = "") {
|
|
return persistence::StoreRow{
|
|
fmt::format(
|
|
"{}{}.{}", std::string{prefix}, Venue_Name(venue).substr(6),
|
|
SecurityType_Name(type).substr(13)),
|
|
name, ""};
|
|
}
|
|
|
|
static persistence::StoreRow get_row(
|
|
const api::Security &s, const std::string &prefix = "") {
|
|
return get_row(
|
|
s.exchange(), bts::securities::security_type(s),
|
|
securities::descriptor(s), prefix);
|
|
}
|
|
|
|
struct SecurityIdRequest {
|
|
Venue venue;
|
|
SecurityType type;
|
|
std::string name;
|
|
std::function<void(Venue, SecurityType, const std::string &, std::uint32_t)>
|
|
callback;
|
|
};
|
|
|
|
struct SecurityIDRecorderState {
|
|
SecurityIDRecorderState(
|
|
std::unique_ptr<IStore> store, KeyLookupOption lookup_key)
|
|
: store(store == nullptr ? get_default_store() : std::move(store)),
|
|
pending_handle(), thread_id(), timer(), lookup_key(lookup_key),
|
|
unresolved_legs() {}
|
|
|
|
std::unique_ptr<IStore> store{nullptr};
|
|
tbb::concurrent_queue<std::shared_ptr<SecurityIdRequest>> id_request_queue;
|
|
tbb::concurrent_queue<MDProducerHandle> pending_handle;
|
|
std::thread::id thread_id;
|
|
unique_ptr<DispatcherTimer> timer;
|
|
|
|
KeyLookupOption lookup_key;
|
|
std::map<ExchangeKey, std::vector<FixedSpreadLeg *>> unresolved_legs;
|
|
|
|
// Oh no, I got methods in my state
|
|
|
|
void run(utils::Dispatcher &dispatcher) {
|
|
timer = std::make_unique<utils::DispatcherTimer>(
|
|
dispatcher, 200, [=, this]() { do_work(); });
|
|
timer->start();
|
|
}
|
|
void do_work() {
|
|
thread_id = std::this_thread::get_id();
|
|
work_spreads();
|
|
work_handles();
|
|
work_db();
|
|
}
|
|
void work_spreads();
|
|
void work_handles();
|
|
void work_db();
|
|
|
|
void assign_key(
|
|
const api::Security &security,
|
|
const SecurityIDRecorder::Callback &key_assigned);
|
|
void assign_key(
|
|
api::Venue venue,
|
|
api::SecurityType type,
|
|
const std::string &name,
|
|
std::function<void(
|
|
api::Venue, api::SecurityType, const std::string &, std::uint32_t)>
|
|
key_assigned);
|
|
|
|
void check_access() { bts_assert(thread_id == std::this_thread::get_id()); }
|
|
void add_unresolved_spread(MDProducerHandle &handle);
|
|
security_key search(const persistence::StoreRow &row);
|
|
security_key insert(const persistence::StoreRow &row);
|
|
};
|
|
|
|
SecurityIDRecorder::SecurityIDRecorder(
|
|
std::unique_ptr<persistence::IStore> store,
|
|
KeyLookupOption lookup_key,
|
|
utils::Dispatcher *dispatcher)
|
|
: _self(std::make_unique<SecurityIDRecorderState>(
|
|
store == nullptr
|
|
? std::make_unique<persistence::SqliteStore>("key_ids")
|
|
: std::move(store),
|
|
lookup_key)) {
|
|
init();
|
|
if (dispatcher != nullptr) {
|
|
_self->run(*dispatcher);
|
|
}
|
|
}
|
|
|
|
SecurityIDRecorder::~SecurityIDRecorder() {}
|
|
|
|
void SecurityIDRecorder::init() {}
|
|
|
|
void SecurityIDRecorder::ensure_key(MDProducerHandle handle) {
|
|
LOG(md, Info, "SecurityIDRecorder::ensure_key desc: {} key: {} num_id: {}",
|
|
std::string(handle.security().security_desc()), handle.security().key,
|
|
handle.security().numeric_id);
|
|
_self->pending_handle.push(handle);
|
|
}
|
|
|
|
void SecurityIDRecorder::assign_key(
|
|
const api::Security &security, const Callback &callback) {
|
|
assign_key(
|
|
security.exchange(), bts::securities::security_type(security),
|
|
bts::securities::descriptor(security),
|
|
[callback, msec = security](
|
|
api::Venue, api::SecurityType, const std::string &,
|
|
uint32_t id) mutable {
|
|
msec.set_id(id);
|
|
callback(msec);
|
|
});
|
|
}
|
|
|
|
void SecurityIDRecorderState::assign_key(
|
|
api::Venue venue,
|
|
api::SecurityType type,
|
|
const std::string &name,
|
|
std::function<
|
|
void(api::Venue, api::SecurityType, const std::string &, std::uint32_t)>
|
|
callback) {
|
|
LOG(md, Info, "SecurityIDRecorder::assign_key {} {} {}", venue, type, name);
|
|
auto id_req = std::make_shared<SecurityIdRequest>();
|
|
id_req->venue = venue;
|
|
id_req->type = type;
|
|
id_req->name = name;
|
|
id_req->callback = callback;
|
|
id_request_queue.push(id_req);
|
|
}
|
|
|
|
void SecurityIDRecorder::assign_key(
|
|
api::Venue venue,
|
|
api::SecurityType type,
|
|
const std::string &name,
|
|
std::function<
|
|
void(api::Venue, api::SecurityType, const std::string &, std::uint32_t)>
|
|
callback) {
|
|
_self->assign_key(venue, type, name, std::move(callback));
|
|
}
|
|
|
|
void SecurityIDRecorder::do_work() { _self->do_work(); }
|
|
|
|
void SecurityIDRecorderState::add_unresolved_spread(MDProducerHandle &handle) {
|
|
bts_assert_d(lookup_key);
|
|
FixedSpreadSecurityXL *pspread =
|
|
static_cast<FixedSpreadSecurityXL *>(handle.mutable_security());
|
|
if (pspread != nullptr) {
|
|
for (size_t x = 0; x < pspread->leg_count(); ++x) {
|
|
auto &leg = pspread->legs[x];
|
|
bts_assert_d(leg.exchange_key.value() != 0);
|
|
if (leg.key == invalid_security_key) {
|
|
// Because leg is a reference to shared memory we know it will not
|
|
// move
|
|
for (const auto *existing : unresolved_legs[leg.exchange_key]) {
|
|
if (existing == &leg)
|
|
goto next;
|
|
}
|
|
unresolved_legs[leg.exchange_key].push_back(&leg);
|
|
}
|
|
next:;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SecurityIDRecorderState::work_spreads() {
|
|
check_access();
|
|
if (!lookup_key)
|
|
return;
|
|
KeyLookup &lookup = *lookup_key;
|
|
int count = 0;
|
|
for (auto iter = unresolved_legs.begin();
|
|
count < 500 && iter != unresolved_legs.end();) {
|
|
const auto &pair = *iter;
|
|
++count;
|
|
SecurityKey key = lookup(pair.first);
|
|
if (key.value() != invalid_security_key) {
|
|
for (FixedSpreadLeg *leg : pair.second) {
|
|
leg->key = key.value();
|
|
}
|
|
iter = unresolved_legs.erase(iter);
|
|
} else {
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool has_all_leg_keys(const MDProducerHandle &handle) {
|
|
if (handle.security().type == SecurityType::SECURITYTYPE_SPREAD) {
|
|
const FixedSpreadSecurityXL *pspread =
|
|
static_cast<const FixedSpreadSecurityXL *>(&handle.security());
|
|
for (size_t x = 0; x < pspread->leg_count(); ++x) {
|
|
if (pspread->legs[x].key == invalid_security_key) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// SyntheticFixedSecurity is guaranteed to have all of its legs
|
|
return true;
|
|
}
|
|
|
|
void SecurityIDRecorderState::work_handles() {
|
|
check_access();
|
|
Security security;
|
|
MDProducerHandle handle;
|
|
vector<MDProducerHandle> retry;
|
|
while (pending_handle.try_pop(handle)) {
|
|
security.Clear();
|
|
// if a spread does not have all of its legs we need to wait
|
|
if (!has_all_leg_keys(handle)) {
|
|
// add
|
|
add_unresolved_spread(handle);
|
|
retry.push_back(handle);
|
|
} else {
|
|
convert_security(handle.security(), &security);
|
|
assign_key(
|
|
security.exchange(), bts::securities::security_type(security),
|
|
bts::securities::descriptor(security),
|
|
[handle](
|
|
api::Venue, api::SecurityType, const std::string &,
|
|
uint32_t id) mutable { handle.set_key(id); });
|
|
}
|
|
}
|
|
|
|
for (const auto &h : retry) {
|
|
pending_handle.push(h);
|
|
}
|
|
}
|
|
|
|
void SecurityIDRecorderState::work_db() {
|
|
check_access();
|
|
// Drain the work queue
|
|
// Query the database for each security. If it has an ID set it
|
|
// If it does not have an ID add it to a list
|
|
// Then persist that list
|
|
// Once a security has a key it is public so record it in the security
|
|
// database
|
|
std::shared_ptr<SecurityIdRequest> temp;
|
|
vector<std::pair<uint32_t, std::shared_ptr<SecurityIdRequest>>> new_entries;
|
|
// SPARK-1446 Need to try to add existing securities to the db in case we
|
|
// deleted them by hand
|
|
vector<std::shared_ptr<SecurityIdRequest>> existing_securities;
|
|
vector<persistence::StoreRow> new_rows;
|
|
size_t count = 0;
|
|
while (id_request_queue.try_pop(temp)) {
|
|
security_key key = invalid_security_key;
|
|
auto row = get_row(temp->venue, temp->type, temp->name);
|
|
try {
|
|
key = search(row);
|
|
} catch (const std::overflow_error &ex) {
|
|
LOG(persist, Error,
|
|
"SecurityIDRecorderState::work_db. Row search threw ex: {}",
|
|
std::string{ex.what()});
|
|
}
|
|
if (key != invalid_security_key) {
|
|
temp->callback(temp->venue, temp->type, temp->name, key);
|
|
existing_securities.push_back(std::move(temp));
|
|
} else {
|
|
// always push in parallel
|
|
new_entries.push_back(std::make_pair(key, std::move(temp)));
|
|
new_rows.push_back(std::move(row));
|
|
++count;
|
|
// Limit the amount of time we spend writing to sqlite during startup
|
|
// to avoid blocking the heartbeat
|
|
if (count > 200) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!new_entries.empty()) {
|
|
store->insert_many_resolve_ids(new_rows);
|
|
for (size_t x = 0; x < new_entries.size(); ++x) {
|
|
auto &entry = new_entries[x];
|
|
auto &row = new_rows[x];
|
|
entry.first = row.id;
|
|
bts_assert_d(entry.first != invalid_security_key);
|
|
}
|
|
}
|
|
|
|
if (!new_entries.empty()) {
|
|
for (size_t x = 0; x < new_entries.size(); ++x) {
|
|
auto &[id, req] = new_entries[x];
|
|
req->callback(req->venue, req->type, req->name, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SecurityIDRecorder::bulk_insert(
|
|
const std::vector<const api::Security *> &data, const char *prefix) {
|
|
std::vector<persistence::StoreRow> rows;
|
|
rows.reserve(data.size());
|
|
for (const auto *s : data) {
|
|
rows.push_back(get_row(*s, prefix));
|
|
}
|
|
_self->store->insert_many_resolve_ids(rows);
|
|
}
|
|
|
|
security_key SecurityIDRecorderState::search(const persistence::StoreRow &row) {
|
|
auto [exists, rowid, val] = store->retrieve_one(row.ns, row.key);
|
|
if (exists) {
|
|
if (!val.empty()) {
|
|
bts_assert(stol(val) == rowid);
|
|
}
|
|
return system::math::checked_ltou(rowid);
|
|
}
|
|
return invalid_security_key;
|
|
}
|
|
|
|
security_key SecurityIDRecorderState::insert(const persistence::StoreRow &row) {
|
|
return store->insert(row.ns, row.key, row.value);
|
|
}
|
|
|
|
} // namespace md
|
|
} // namespace bts
|
|
|
|
#endif
|