/** * * DbClientLockFree.cc * An Tao * * Copyright 2018, An Tao. All rights reserved. * https://github.com/an-tao/drogon * Use of this source code is governed by a MIT license * that can be found in the License file. * * Drogon * */ #include "DbClientLockFree.h" #include "DbConnection.h" #include "TransactionImpl.h" #include "../../lib/src/TaskTimeoutFlag.h" #include #include #include #include #include #include #include #include #include #include #include #if USE_POSTGRESQL #include "postgresql_impl/PgConnection.h" #endif #if USE_MYSQL #include "mysql_impl/MysqlConnection.h" #endif #ifndef _WIN32 #include #endif using namespace drogon::orm; DbClientLockFree::DbClientLockFree(const std::string &connInfo, trantor::EventLoop *loop, ClientType type, #if LIBPQ_SUPPORTS_BATCH_MODE size_t connectionNumberPerLoop, bool autoBatch) #else size_t connectionNumberPerLoop) #endif : connectionInfo_(connInfo), loop_(loop), #if LIBPQ_SUPPORTS_BATCH_MODE autoBatch_(autoBatch), #endif numberOfConnections_(connectionNumberPerLoop) { type_ = type; LOG_TRACE << "type=" << (int)type; if (type == ClientType::PostgreSQL || type == ClientType::Mysql) { loop_->queueInLoop([this]() { for (size_t i = 0; i < numberOfConnections_; ++i) newConnection(); }); } else { LOG_ERROR << "No supported database type:" << (int)type; } } DbClientLockFree::~DbClientLockFree() noexcept { closeAll(); } void DbClientLockFree::closeAll() { for (auto &conn : connections_) { conn->disconnect(); } connections_.clear(); } void DbClientLockFree::execSql( const char *sql, size_t sqlLength, size_t paraNum, std::vector &¶meters, std::vector &&length, std::vector &&format, ResultCallback &&rcb, std::function &&exceptCallback) { assert(paraNum == parameters.size()); assert(paraNum == length.size()); assert(paraNum == format.size()); assert(rcb); loop_->assertInLoopThread(); if (timeout_ > 0.0) { execSqlWithTimeout(sql, sqlLength, paraNum, std::move(parameters), std::move(length), std::move(format), std::move(rcb), std::move(exceptCallback)); return; } if (!connections_.empty() && sqlCmdBuffer_.empty() && transCallbacks_.empty()) { #if (!LIBPQ_SUPPORTS_BATCH_MODE) for (auto &conn : connections_) { if (!conn->isWorking() && (transSet_.empty() || transSet_.find(conn) == transSet_.end())) { conn->execSql( std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), [rcb = std::move(rcb), this](const Result &r) { if (sqlCmdBuffer_.empty()) { rcb(r); } else { loop_->queueInLoop( [rcb = std::move(rcb), r]() { rcb(r); }); } }, std::move(exceptCallback)); return; } } #else if (type_ != ClientType::PostgreSQL) { for (auto &conn : connections_) { if (!conn->isWorking() && (transSet_.empty() || transSet_.find(conn) == transSet_.end())) { conn->execSql( std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), [rcb = std::move(rcb), this](const Result &r) { if (sqlCmdBuffer_.empty()) { rcb(r); } else { loop_->queueInLoop( [rcb = std::move(rcb), r]() { rcb(r); }); } }, std::move(exceptCallback)); return; } } } else { /// pg batch mode for (size_t i = 0; i < connections_.size(); ++i) { auto &conn = connections_[connectionPos_++]; if (connectionPos_ >= connections_.size()) connectionPos_ = 0; if (transSet_.empty() || transSet_.find(conn) == transSet_.end()) { conn->execSql(std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), std::move(rcb), std::move(exceptCallback)); return; } } } #endif } if (sqlCmdBuffer_.size() > 20000) { // too many queries in buffer; auto exceptPtr = std::make_exception_ptr(Failure("Too many queries in buffer")); exceptCallback(exceptPtr); return; } // LOG_TRACE << "Push query to buffer"; sqlCmdBuffer_.emplace_back(std::make_shared( std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), [rcb = std::move(rcb), this](const Result &r) { if (sqlCmdBuffer_.empty()) { rcb(r); } else { loop_->queueInLoop([rcb = std::move(rcb), r]() { rcb(r); }); } }, std::move(exceptCallback))); } std::shared_ptr DbClientLockFree::newTransaction( const std::function &) noexcept(false) { // Don't support transaction; LOG_ERROR << "You can't use the synchronous interface in the fast Database " "client, please use the asynchronous version (newTransactionAsync)"; assert(0); return nullptr; } void DbClientLockFree::newTransactionAsync( const std::function &)> &callback) { loop_->assertInLoopThread(); for (auto &conn : connections_) { if (!conn->isWorking() && transSet_.find(conn) == transSet_.end()) { makeTrans(conn, std::function &)>( callback)); return; } } auto callbackPtr = std::make_shared< std::function &)>>(callback); if (timeout_ > 0.0) { auto newCallbackPtr = std::make_shared &)>>>(); auto timeoutFlagPtr = std::make_shared( loop_, std::chrono::duration(timeout_), [callbackPtr, this, newCallbackPtr]() { auto cbPtr = (*newCallbackPtr).lock(); if (cbPtr) { for (auto iter = transCallbacks_.begin(); iter != transCallbacks_.end(); ++iter) { if (cbPtr == *iter) { transCallbacks_.erase(iter); break; } } } (*callbackPtr)(nullptr); }); callbackPtr = std::make_shared< std::function &)>>( [callbackPtr, timeoutFlagPtr](const std::shared_ptr &trans) { if (timeoutFlagPtr->done()) return; (*callbackPtr)(trans); }); *newCallbackPtr = callbackPtr; timeoutFlagPtr->runTimer(); } transCallbacks_.push_back(callbackPtr); } void DbClientLockFree::makeTrans( const DbConnectionPtr &conn, std::function &)> &&callback) { std::weak_ptr weakThis = shared_from_this(); auto trans = std::make_shared( type_, conn, std::function(), [weakThis, conn]() { auto thisPtr = weakThis.lock(); if (!thisPtr) return; if (conn->status() == ConnectStatus::Bad) { return; } if (!thisPtr->transCallbacks_.empty()) { auto callback = std::move(thisPtr->transCallbacks_.front()); thisPtr->transCallbacks_.pop_front(); thisPtr->makeTrans(conn, std::move(*callback)); return; } for (auto &connPtr : thisPtr->connections_) { if (connPtr == conn) { conn->loop()->queueInLoop([weakThis, conn]() { auto thisPtr = weakThis.lock(); if (!thisPtr) return; std::weak_ptr weakConn = conn; conn->setIdleCallback([weakThis, weakConn]() { auto thisPtr = weakThis.lock(); if (!thisPtr) return; auto connPtr = weakConn.lock(); if (!connPtr) return; thisPtr->handleNewTask(connPtr); }); thisPtr->transSet_.erase(conn); thisPtr->handleNewTask(conn); }); break; } } }); transSet_.insert(conn); trans->doBegin(); if (timeout_ > 0.0) { trans->setTimeout(timeout_); } conn->loop()->queueInLoop( [callback = std::move(callback), trans] { callback(trans); }); } void DbClientLockFree::handleNewTask(const DbConnectionPtr &conn) { assert(conn); assert(!conn->isWorking()); if (!transCallbacks_.empty()) { auto callback = std::move(transCallbacks_.front()); transCallbacks_.pop_front(); makeTrans(conn, std::move(*callback)); return; } if (!sqlCmdBuffer_.empty()) { #if LIBPQ_SUPPORTS_BATCH_MODE if (type_ != ClientType::PostgreSQL) { std::shared_ptr cmd = std::move(sqlCmdBuffer_.front()); sqlCmdBuffer_.pop_front(); conn->execSql(std::move(cmd->sql_), cmd->parametersNumber_, std::move(cmd->parameters_), std::move(cmd->lengths_), std::move(cmd->formats_), std::move(cmd->callback_), std::move(cmd->exceptionCallback_)); } else { std::deque> cmds; using std::swap; swap(cmds, sqlCmdBuffer_); conn->batchSql(std::move(cmds)); } #else std::shared_ptr cmd = std::move(sqlCmdBuffer_.front()); sqlCmdBuffer_.pop_front(); conn->execSql(std::move(cmd->sql_), cmd->parametersNumber_, std::move(cmd->parameters_), std::move(cmd->lengths_), std::move(cmd->formats_), std::move(cmd->callback_), std::move(cmd->exceptionCallback_)); #endif return; } } DbConnectionPtr DbClientLockFree::newConnection() { DbConnectionPtr connPtr; if (type_ == ClientType::PostgreSQL) { #if USE_POSTGRESQL #if LIBPQ_SUPPORTS_BATCH_MODE connPtr = std::make_shared(loop_, connectionInfo_, autoBatch_); #else connPtr = std::make_shared(loop_, connectionInfo_, false); #endif #else return nullptr; #endif } else if (type_ == ClientType::Mysql) { #if USE_MYSQL connPtr = std::make_shared(loop_, connectionInfo_); #else return nullptr; #endif } else { return nullptr; } std::weak_ptr weakPtr = shared_from_this(); connPtr->setCloseCallback([weakPtr](const DbConnectionPtr &closeConnPtr) { // Erase the connection auto thisPtr = weakPtr.lock(); if (!thisPtr) return; auto iter = std::find(thisPtr->connections_.begin(), thisPtr->connections_.end(), closeConnPtr); if (iter != thisPtr->connections_.end()) thisPtr->connections_.erase(iter); iter = std::find(thisPtr->connectionHolders_.begin(), thisPtr->connectionHolders_.end(), closeConnPtr); if (iter != thisPtr->connectionHolders_.end()) thisPtr->connectionHolders_.erase(iter); thisPtr->transSet_.erase(closeConnPtr); // Reconnect after 1 second thisPtr->loop_->runAfter(1, [weakPtr, closeConnPtr] { auto thisPtr = weakPtr.lock(); if (!thisPtr) return; thisPtr->newConnection(); }); }); connPtr->setOkCallback([weakPtr](const DbConnectionPtr &okConnPtr) { LOG_TRACE << "connected!"; auto thisPtr = weakPtr.lock(); if (!thisPtr) return; thisPtr->connections_.push_back(okConnPtr); thisPtr->handleNewTask(okConnPtr); }); std::weak_ptr weakConnPtr = connPtr; connPtr->setIdleCallback([weakPtr, weakConnPtr]() { auto thisPtr = weakPtr.lock(); if (!thisPtr) return; auto connPtr = weakConnPtr.lock(); if (!connPtr) return; thisPtr->handleNewTask(connPtr); }); connectionHolders_.push_back(connPtr); // Init database connection only after all callbacks are set and connPtr // is added to connectionHolders_. connPtr->init(); // std::cout<<"newConn end"< &¶meters, std::vector &&length, std::vector &&format, ResultCallback &&rcb, std::function &&ecb) { auto commandPtr = std::make_shared>(); auto ecpPtr = std::make_shared>( std::move(ecb)); auto timeoutFlagPtr = std::make_shared( loop_, std::chrono::duration(timeout_), [commandPtr, ecpPtr, thisPtr = shared_from_this()]() { auto cbPtr = (*commandPtr).lock(); if (cbPtr) { for (auto iter = thisPtr->sqlCmdBuffer_.begin(); iter != thisPtr->sqlCmdBuffer_.end(); ++iter) { if (*iter == cbPtr) { thisPtr->sqlCmdBuffer_.erase(iter); break; } } } (*ecpPtr)( std::make_exception_ptr(TimeoutError("SQL execution timeout"))); }); auto resultCallback = [rcb = std::move(rcb), timeoutFlagPtr](const Result &result) { if (timeoutFlagPtr->done()) return; rcb(result); }; auto exceptionCallback = [ecpPtr, timeoutFlagPtr](const std::exception_ptr &err) { if (timeoutFlagPtr->done()) return; (*ecpPtr)(err); }; if (!connections_.empty() && sqlCmdBuffer_.empty() && transCallbacks_.empty()) { #if (!LIBPQ_SUPPORTS_BATCH_MODE) for (auto &conn : connections_) { if (!conn->isWorking() && (transSet_.empty() || transSet_.find(conn) == transSet_.end())) { conn->execSql( std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), [resultCallback = std::move(resultCallback), this](const Result &r) { if (sqlCmdBuffer_.empty()) { resultCallback(r); } else { loop_->queueInLoop( [resultCallback = std::move(resultCallback), r]() { resultCallback(r); }); } }, std::move(exceptionCallback)); timeoutFlagPtr->runTimer(); return; } } #else if (type_ != ClientType::PostgreSQL) { for (auto &conn : connections_) { if (!conn->isWorking() && (transSet_.empty() || transSet_.find(conn) == transSet_.end())) { conn->execSql( std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), [resultCallback = std::move(resultCallback), this](const Result &r) { if (sqlCmdBuffer_.empty()) { resultCallback(r); } else { loop_->queueInLoop( [resultCallback = std::move(resultCallback), r]() { resultCallback(r); }); } }, std::move(exceptionCallback)); timeoutFlagPtr->runTimer(); return; } } } else { /// pg batch mode for (size_t i = 0; i < connections_.size(); ++i) { auto &conn = connections_[connectionPos_++]; if (connectionPos_ >= connections_.size()) connectionPos_ = 0; if (transSet_.empty() || transSet_.find(conn) == transSet_.end()) { conn->execSql(std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), std::move(resultCallback), std::move(exceptionCallback)); timeoutFlagPtr->runTimer(); return; } } } #endif } if (sqlCmdBuffer_.size() > 20000) { // too many queries in buffer; exceptionCallback( std::make_exception_ptr(Failure("Too many queries in buffer"))); return; } // LOG_TRACE << "Push query to buffer"; auto cmdPtr = std::make_shared( std::string_view{sql, sqlLength}, paraNum, std::move(parameters), std::move(length), std::move(format), [resultCallback = std::move(resultCallback), this](const Result &r) { if (sqlCmdBuffer_.empty()) { resultCallback(r); } else { loop_->queueInLoop([resultCallback = std::move(resultCallback), r]() { resultCallback(r); }); } }, std::move(exceptionCallback)); sqlCmdBuffer_.emplace_back(cmdPtr); *commandPtr = cmdPtr; timeoutFlagPtr->runTimer(); }