finished statement cache class
This commit is contained in:
@@ -71,9 +71,14 @@ connection_pool::connection_pool(const std::string& dns, size_t count)
|
||||
|
||||
connection_ptr connection_pool::acquire() {
|
||||
std::unique_lock lock(mutex_);
|
||||
while (idle_connections_.empty()) {
|
||||
cv.wait(lock);
|
||||
if (!cv.wait_for(lock,
|
||||
std::chrono::seconds(30),
|
||||
[this] { return !idle_connections_.empty(); })) {
|
||||
return {nullptr, this};
|
||||
}
|
||||
// while (idle_connections_.empty()) {
|
||||
// cv.wait(lock);
|
||||
// }
|
||||
|
||||
return get_next_connection();
|
||||
}
|
||||
@@ -89,23 +94,40 @@ connection_ptr connection_pool::try_acquire() {
|
||||
|
||||
connection_ptr connection_pool::acquire(const size_t id) {
|
||||
using namespace std::chrono_literals;
|
||||
pointer next_connection{nullptr};
|
||||
auto try_count{0};
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
do {
|
||||
if (auto it = idle_connections_.find(id); it != idle_connections_.end()) {
|
||||
next_connection = it->second;
|
||||
auto node = idle_connections_.extract(it);
|
||||
inuse_connections_.insert(std::move(node));
|
||||
} else {
|
||||
lock.unlock();
|
||||
std::this_thread::sleep_for(100ms);
|
||||
lock.lock();
|
||||
}
|
||||
} while(try_count++ < 5);
|
||||
if (!cv.wait_for(lock,
|
||||
5s,
|
||||
[this, id] {
|
||||
return idle_connections_.find(id) != idle_connections_.end();
|
||||
})) {
|
||||
return {nullptr, this};
|
||||
}
|
||||
|
||||
auto it = idle_connections_.find(id);
|
||||
auto next_connection = it->second;
|
||||
auto node = idle_connections_.extract(it);
|
||||
inuse_connections_.insert(std::move(node));
|
||||
return {next_connection, this};
|
||||
|
||||
// using namespace std::chrono_literals;
|
||||
// pointer next_connection{nullptr};
|
||||
// auto try_count{0};
|
||||
// std::unique_lock lock(mutex_);
|
||||
//
|
||||
// do {
|
||||
// if (auto it = idle_connections_.find(id); it != idle_connections_.end()) {
|
||||
// next_connection = it->second;
|
||||
// auto node = idle_connections_.extract(it);
|
||||
// inuse_connections_.insert(std::move(node));
|
||||
// } else {
|
||||
// lock.unlock();
|
||||
// std::this_thread::sleep_for(100ms);
|
||||
// lock.lock();
|
||||
// }
|
||||
// } while(try_count++ < 5);
|
||||
//
|
||||
// return {next_connection, this};
|
||||
}
|
||||
|
||||
void connection_pool::release(identifiable_connection* c) {
|
||||
@@ -116,6 +138,7 @@ void connection_pool::release(identifiable_connection* c) {
|
||||
if (const auto it = inuse_connections_.find(c->id); it != inuse_connections_.end()) {
|
||||
auto node = inuse_connections_.extract(it);
|
||||
idle_connections_.insert(std::move(node));
|
||||
cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ void statement_proxy::reset() const {
|
||||
statement_->reset();
|
||||
}
|
||||
|
||||
std::string statement_proxy::sql() const {
|
||||
return statement_->query_.sql;
|
||||
}
|
||||
|
||||
std::unique_ptr<utils::attribute_writer> statement_proxy::create_binder() const {
|
||||
return statement_->create_binder();
|
||||
}
|
||||
|
||||
@@ -95,4 +95,7 @@ void statement::reset() const
|
||||
statement_proxy_->reset();
|
||||
}
|
||||
|
||||
}
|
||||
std::string statement::sql() const {
|
||||
return statement_proxy_->sql();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,35 +4,65 @@
|
||||
#include "matador/sql/connection_pool.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
namespace matador::sql {
|
||||
namespace internal {
|
||||
class statement_cache_proxy final : public statement_proxy {
|
||||
public:
|
||||
explicit statement_cache_proxy(std::unique_ptr<statement_impl>&& stmt)
|
||||
: statement_proxy(std::move(stmt)) {}
|
||||
struct retry_config {
|
||||
size_t max_attempts{10};
|
||||
std::chrono::milliseconds initial_wait{10};
|
||||
std::chrono::milliseconds max_wait{250};
|
||||
};
|
||||
|
||||
explicit statement_cache_proxy(std::unique_ptr<statement_impl>&& stmt, connection_pool &pool, const size_t connection_id)
|
||||
: statement_proxy(std::move(stmt))
|
||||
, pool_(pool)
|
||||
, connection_id_(connection_id) {}
|
||||
|
||||
utils::result<size_t, utils::error> execute(interface::parameter_binder& bindings) override {
|
||||
if (!try_lock()) {
|
||||
return utils::failure(utils::error{
|
||||
error_code::STATEMENT_LOCKED,
|
||||
"Failed to execute statement because it is already in use"
|
||||
});
|
||||
}
|
||||
auto result = try_with_retry([this, &bindings]() -> utils::result<size_t, utils::error> {
|
||||
if (!try_lock()) {
|
||||
return utils::failure(utils::error{
|
||||
error_code::STATEMENT_LOCKED,
|
||||
"Failed to execute statement because it is already in use"
|
||||
});
|
||||
}
|
||||
|
||||
auto guard = statement_guard(*this);
|
||||
return statement_->execute(bindings);
|
||||
auto guard = statement_guard(*this);
|
||||
if (auto conn = pool_.acquire(connection_id_); !conn.valid()) {
|
||||
return utils::failure(utils::error{
|
||||
error_code::EXECUTE_FAILED,
|
||||
"Failed to execute statement because couldn't lock connection"
|
||||
});
|
||||
}
|
||||
|
||||
return statement_->execute(bindings);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
utils::result<std::unique_ptr<query_result_impl>, utils::error> fetch(interface::parameter_binder& bindings) override {
|
||||
if (!try_lock()) {
|
||||
return utils::failure(utils::error{
|
||||
error_code::STATEMENT_LOCKED,
|
||||
"Failed to execute statement because it is already in use"
|
||||
});
|
||||
}
|
||||
|
||||
auto guard = statement_guard(*this);
|
||||
return statement_->fetch(bindings);
|
||||
utils::result<std::unique_ptr<query_result_impl>, utils::error> fetch(interface::parameter_binder& bindings) override {
|
||||
auto result = try_with_retry([this, &bindings]() -> utils::result<std::unique_ptr<query_result_impl>, utils::error> {
|
||||
if (!try_lock()) {
|
||||
return utils::failure(utils::error{
|
||||
error_code::STATEMENT_LOCKED,
|
||||
"Failed to execute statement because it is already in use"
|
||||
});
|
||||
}
|
||||
auto guard = statement_guard(*this);
|
||||
if (const auto conn = pool_.acquire(connection_id_); !conn.valid()) {
|
||||
return utils::failure(utils::error{
|
||||
error_code::EXECUTE_FAILED,
|
||||
"Failed to execute statement because couldn't lock connection"
|
||||
});
|
||||
}
|
||||
return statement_->fetch(bindings);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -41,6 +71,28 @@ protected:
|
||||
return locked_.compare_exchange_strong(expected, true);
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
[[nodiscard]] auto try_with_retry(Func &&func) -> decltype(func()) {
|
||||
auto current_wait = config_.initial_wait;
|
||||
|
||||
for (size_t attempt = 0; attempt < config_.max_attempts; ++attempt) {
|
||||
if (auto result = func(); result.is_ok() ||
|
||||
result.err().ec() != error_code::STATEMENT_LOCKED) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (attempt + 1 < config_.max_attempts) {
|
||||
std::this_thread::sleep_for(current_wait);
|
||||
current_wait = std::min(current_wait * 2, config_.max_wait);
|
||||
}
|
||||
}
|
||||
|
||||
return utils::failure(utils::error{
|
||||
error_code::STATEMENT_LOCKED,
|
||||
"Failed to execute statement because it is already in use"
|
||||
});
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
locked_.store(false);
|
||||
}
|
||||
@@ -56,6 +108,9 @@ private:
|
||||
|
||||
private:
|
||||
std::atomic_bool locked_{false};
|
||||
connection_pool &pool_;
|
||||
size_t connection_id_{};
|
||||
retry_config config_{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -68,35 +123,46 @@ utils::result<statement, utils::error> statement_cache::acquire(const query_cont
|
||||
std::unique_lock lock(mutex_);
|
||||
// hash statement
|
||||
const auto key = std::hash<std::string>{}(ctx.sql);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
// Found in cache. Move it to of the LRU list
|
||||
if (const auto it = cache_map_.find(key); it != cache_map_.end()) {
|
||||
usage_list_.splice(usage_list_.begin(), usage_list_, it->second.second);
|
||||
return utils::ok(it->second.first);
|
||||
usage_list_.splice(usage_list_.begin(), usage_list_, it->second.position);
|
||||
it->second.last_access = now;
|
||||
push(statement_cache_event::Type::Accessed, ctx.sql);
|
||||
return utils::ok(it->second.stmt);
|
||||
}
|
||||
// Prepare a new statement
|
||||
// acquire pool connection
|
||||
const auto conn = pool_.acquire();
|
||||
auto result = conn->perform_prepare(ctx);
|
||||
if (!result) {
|
||||
return utils::failure(utils::error{error_code::PREPARE_FAILED, std::string("Failed to prepare")});
|
||||
size_t id{};
|
||||
std::unique_ptr<statement_impl> stmt;
|
||||
{
|
||||
const auto conn = pool_.acquire();
|
||||
auto result = conn->perform_prepare(ctx);
|
||||
if (!result) {
|
||||
return utils::failure(utils::error{error_code::PREPARE_FAILED, std::string("Failed to prepare")});
|
||||
}
|
||||
id = conn.id().value();
|
||||
stmt = result.release();
|
||||
}
|
||||
|
||||
// If cache max size reached ensure space
|
||||
if (cache_map_.size() >= max_size_) {
|
||||
const auto& key_to_remove = usage_list_.back();
|
||||
cache_map_.erase(key_to_remove);
|
||||
usage_list_.pop_back();
|
||||
push(statement_cache_event::Type::Evicted, ctx.sql);
|
||||
}
|
||||
|
||||
usage_list_.push_front(key);
|
||||
const auto it = cache_map_.insert({
|
||||
key,
|
||||
std::make_pair(statement{
|
||||
std::make_shared<internal::statement_cache_proxy>(result.release())},
|
||||
usage_list_.begin())
|
||||
{statement{
|
||||
std::make_shared<internal::statement_cache_proxy>(std::move(stmt), pool_, id)},
|
||||
std::chrono::steady_clock::now(),
|
||||
usage_list_.begin()}
|
||||
}).first;
|
||||
push(statement_cache_event::Type::Added, ctx.sql);
|
||||
|
||||
return utils::ok(it->second.first);
|
||||
return utils::ok(it->second.stmt);
|
||||
}
|
||||
|
||||
size_t statement_cache::size() const {
|
||||
@@ -109,4 +175,19 @@ size_t statement_cache::capacity() const {
|
||||
bool statement_cache::empty() const {
|
||||
return cache_map_.empty();
|
||||
}
|
||||
|
||||
void statement_cache::subscribe(statement_cache_observer_interface &observer) {
|
||||
observers_.push_back(&observer);
|
||||
}
|
||||
|
||||
void statement_cache::push(statement_cache_event::Type type, const std::string &sql) const {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
const auto ts = Clock::now();
|
||||
const statement_cache_event evt{type, sql, ts};
|
||||
for (auto& obs : observers_) {
|
||||
if (obs) {
|
||||
obs->on_event(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user