Switched away from boost::asio as it doesn't play well with libpq
This commit is contained in:
parent
6dd079bf87
commit
6bb5525d5e
13 changed files with 566 additions and 143 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
error( "Use local.pri.sample to create your own local.pri" )
|
error( "Use local.pri.sample to create your own local.pri" )
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBS += -lws2_32 -llibpq
|
LIBS += -lUser32 -lws2_32 -llibpq
|
||||||
|
|
||||||
CONFIG += c++17
|
CONFIG += c++17
|
||||||
QMAKE_CXXFLAGS += /std:c++17
|
QMAKE_CXXFLAGS += /std:c++17
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include "catalog/PgAttribute.h"
|
#include "catalog/PgAttribute.h"
|
||||||
#include "catalog/PgAttributeContainer.h"
|
#include "catalog/PgAttributeContainer.h"
|
||||||
#include "catalog/PgConstraintContainer.h"
|
#include "catalog/PgConstraintContainer.h"
|
||||||
#include "GlobalIoService.h"
|
|
||||||
#include "SqlFormattingUtils.h"
|
#include "SqlFormattingUtils.h"
|
||||||
#include "Pgsql_oids.h"
|
#include "Pgsql_oids.h"
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
@ -19,7 +18,7 @@
|
||||||
|
|
||||||
CrudModel::CrudModel(QObject *parent)
|
CrudModel::CrudModel(QObject *parent)
|
||||||
: QAbstractTableModel(parent)
|
: QAbstractTableModel(parent)
|
||||||
, m_dbConn(*getGlobalAsioIoService())
|
, m_dbConn()
|
||||||
{
|
{
|
||||||
qDebug("CrudModel created");
|
qDebug("CrudModel created");
|
||||||
connect(&m_dbConn, &ASyncDBConnection::onStateChanged, this, &CrudModel::connectionStateChanged);
|
connect(&m_dbConn, &ASyncDBConnection::onStateChanged, this, &CrudModel::connectionStateChanged);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#include "GlobalIoService.h"
|
|
||||||
|
|
||||||
std::shared_ptr<boost::asio::io_service> getGlobalAsioIoService()
|
|
||||||
{
|
|
||||||
static auto ios = std::make_shared<boost::asio::io_service>();
|
|
||||||
return ios;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
|
|
||||||
std::shared_ptr<boost::asio::io_service> getGlobalAsioIoService();
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
#include "catalog/PgDatabaseCatalog.h"
|
#include "catalog/PgDatabaseCatalog.h"
|
||||||
#include "QueryParamListController.h"
|
#include "QueryParamListController.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "GlobalIoService.h"
|
|
||||||
#include "UserConfiguration.h"
|
#include "UserConfiguration.h"
|
||||||
#include "IDatabaseWindow.h"
|
#include "IDatabaseWindow.h"
|
||||||
|
|
||||||
|
|
@ -27,7 +26,7 @@ QueryTool::QueryTool(IDatabaseWindow *context, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_context(context)
|
, m_context(context)
|
||||||
, ui(new Ui::QueryTab)
|
, ui(new Ui::QueryTab)
|
||||||
, m_dbConnection(*getGlobalAsioIoService())
|
, m_dbConnection()
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
# include <winsock2.h>
|
# include <winsock2.h>
|
||||||
#endif
|
#endif
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "GlobalIoService.h"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
@ -29,19 +28,13 @@ int main(int argc, char *argv[])
|
||||||
QCoreApplication::setOrganizationDomain("eelkeklein.nl");
|
QCoreApplication::setOrganizationDomain("eelkeklein.nl");
|
||||||
QCoreApplication::setApplicationName("pglab");
|
QCoreApplication::setApplicationName("pglab");
|
||||||
|
|
||||||
std::thread asio_service_thread;
|
|
||||||
int result = -1;
|
int result = -1;
|
||||||
{
|
{
|
||||||
auto ios = getGlobalAsioIoService();
|
|
||||||
boost::asio::io_service::work work(*ios); // Prevent service from running out of work so run doesn't return
|
|
||||||
asio_service_thread = std::thread([ios](){ ios->run(); });
|
|
||||||
|
|
||||||
// make sure the io_service is stopped before we wait on the future
|
// make sure the io_service is stopped before we wait on the future
|
||||||
auto master_controller = std::make_unique<MasterController>();
|
auto master_controller = std::make_unique<MasterController>();
|
||||||
master_controller->init();
|
master_controller->init();
|
||||||
result = a.exec();
|
result = a.exec();
|
||||||
}
|
}
|
||||||
asio_service_thread.join();
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ SOURCES += main.cpp\
|
||||||
DatabasesTableModel.cpp \
|
DatabasesTableModel.cpp \
|
||||||
RolesTableModel.cpp \
|
RolesTableModel.cpp \
|
||||||
ProcessStdioWidget.cpp \
|
ProcessStdioWidget.cpp \
|
||||||
GlobalIoService.cpp \
|
|
||||||
ResultTableModelUtil.cpp \
|
ResultTableModelUtil.cpp \
|
||||||
BaseTableModel.cpp \
|
BaseTableModel.cpp \
|
||||||
QueryParamListController.cpp \
|
QueryParamListController.cpp \
|
||||||
|
|
@ -111,7 +110,6 @@ HEADERS += \
|
||||||
DatabasesTableModel.h \
|
DatabasesTableModel.h \
|
||||||
RolesTableModel.h \
|
RolesTableModel.h \
|
||||||
ProcessStdioWidget.h \
|
ProcessStdioWidget.h \
|
||||||
GlobalIoService.h \
|
|
||||||
ResultTableModelUtil.h \
|
ResultTableModelUtil.h \
|
||||||
BaseTableModel.h \
|
BaseTableModel.h \
|
||||||
QueryParamListController.h \
|
QueryParamListController.h \
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
#include "ASyncDBConnection.h"
|
#include "ASyncDBConnection.h"
|
||||||
#include "ScopeGuard.h"
|
#include "ScopeGuard.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "Pgsql_PgException.h"
|
||||||
|
#include "Win32Event.h"
|
||||||
|
#include "WaitHandleList.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <queue>
|
||||||
using namespace boost::asio;
|
#include <WinSock2.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -17,70 +20,447 @@ namespace {
|
||||||
}
|
}
|
||||||
} registerMetaTypes_instance;
|
} registerMetaTypes_instance;
|
||||||
|
|
||||||
|
class Command {
|
||||||
|
public:
|
||||||
|
std::string command;
|
||||||
|
Pgsql::Params params;
|
||||||
|
ASyncDBConnection::on_result_callback on_result;
|
||||||
|
|
||||||
|
Command() = default;
|
||||||
|
Command(const std::string &cmd, ASyncDBConnection::on_result_callback cb)
|
||||||
|
: command(cmd), on_result(cb)
|
||||||
|
{}
|
||||||
|
Command(const std::string &cmd, Pgsql::Params &&p, ASyncDBConnection::on_result_callback cb)
|
||||||
|
: command(cmd), params(p), on_result(cb)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ASyncDBConnection::ASyncDBConnection(boost::asio::io_service &ios)
|
class ASyncDBConnectionThread {
|
||||||
: m_asioSock(ios)
|
public:
|
||||||
|
using t_CommandQueue = std::queue<Command>;
|
||||||
|
// struct {
|
||||||
|
// std::mutex m_mutex;
|
||||||
|
// on_state_callback m_func;
|
||||||
|
// } m_stateCallback;
|
||||||
|
// struct {
|
||||||
|
// std::mutex m_mutex;
|
||||||
|
// on_notice_callback m_func;
|
||||||
|
// } m_noticeCallback;
|
||||||
|
|
||||||
|
struct t_Command {
|
||||||
|
std::mutex m_mutex;
|
||||||
|
t_CommandQueue m_queue;
|
||||||
|
//std::condition_variable m_newEvent;
|
||||||
|
Win32Event m_newEvent;
|
||||||
|
t_Command()
|
||||||
|
: m_newEvent(Win32Event::Reset::Auto, Win32Event::Initial::Clear)
|
||||||
|
{}
|
||||||
|
} m_commandQueue;
|
||||||
|
|
||||||
|
// std::string m_initString;
|
||||||
|
ConnectionConfig m_config;
|
||||||
|
ASyncDBConnection::State m_state = ASyncDBConnection::State::NotConnected;
|
||||||
|
|
||||||
|
ASyncDBConnectionThread(ASyncDBConnection *asco);
|
||||||
|
|
||||||
|
/// Is started as a seperate thread by ASyncDBConnection
|
||||||
|
void run();
|
||||||
|
|
||||||
|
/// Sends a cancel request to the DB server
|
||||||
|
bool cancel();
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ASyncDBConnection *asyncConnObject = nullptr;
|
||||||
|
|
||||||
|
/// \todo Implement new method to stop the thread
|
||||||
|
Win32Event m_stopEvent;
|
||||||
|
Pgsql::Connection m_connection;
|
||||||
|
bool terminateRequested = false; ///< is set when the thread should stop
|
||||||
|
bool m_terminated = true;
|
||||||
|
Pgsql::Canceller m_canceller;
|
||||||
|
QElapsedTimer m_timer;
|
||||||
|
|
||||||
|
|
||||||
|
bool makeConnection();
|
||||||
|
void communicate();
|
||||||
|
|
||||||
|
void doStateCallback(ASyncDBConnection::State state);
|
||||||
|
/// Wait's for a command to come in and send's it to the server
|
||||||
|
void waitForAndSendCommand();
|
||||||
|
void doNewCommand();
|
||||||
|
void waitForResult();
|
||||||
|
|
||||||
|
|
||||||
|
void processNotice(const PGresult *result);
|
||||||
|
/** Function to call when after sending a command the socket is ready for reading.
|
||||||
|
*
|
||||||
|
* It might take several consumes before all data is read.
|
||||||
|
*/
|
||||||
|
bool consumeResultInput();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ASyncDBConnectionThread::ASyncDBConnectionThread(ASyncDBConnection *asco)
|
||||||
|
: asyncConnObject(asco)
|
||||||
|
, m_stopEvent(Win32Event::Reset::Manual, Win32Event::Initial::Clear)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
ASyncDBConnection::~ASyncDBConnection() = default;
|
void ASyncDBConnectionThread::run()
|
||||||
|
{
|
||||||
|
m_terminated = false;
|
||||||
|
SCOPE_EXIT {
|
||||||
|
m_state = ASyncDBConnection::State::NotConnected;
|
||||||
|
m_terminated = true;
|
||||||
|
};
|
||||||
|
while (!terminateRequested) {
|
||||||
|
|
||||||
|
// make or recover connection
|
||||||
|
if (makeConnection()) {
|
||||||
|
m_connection.setNoticeReceiver(
|
||||||
|
[this](const PGresult *result) { processNotice(result); });
|
||||||
|
m_canceller = m_connection.getCancel();
|
||||||
|
|
||||||
|
|
||||||
|
// send commands and receive results
|
||||||
|
communicate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// It is not possible to determine the source of the problem.
|
||||||
|
// Accept for PQconnectionNeedsPassword
|
||||||
|
|
||||||
|
// Pass problem to main thread and stop this thread
|
||||||
|
|
||||||
|
// Main thread needs to know it has to restart connecting if it want's to.
|
||||||
|
// TODO: add status functions to help main thread so it doesn't have to remember
|
||||||
|
// everything reported through callbacks.
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// close connection
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ASyncDBConnectionThread::cancel()
|
||||||
|
{
|
||||||
|
return m_canceller.cancel(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ASyncDBConnectionThread::makeConnection()
|
||||||
|
{
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
|
// start connecting
|
||||||
|
// auto keywords = m_config.getKeywords();
|
||||||
|
// auto values = m_config.getValues();
|
||||||
|
QString conn_string = m_config.connectionString();
|
||||||
|
#if false
|
||||||
|
try {
|
||||||
|
m_connection.connect(conn_string); //keywords, values, 0);
|
||||||
|
doStateCallback(ASyncDBConnection::State::Connected);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Pgsql::PgConnectionError &) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
while (!terminateRequested) {
|
||||||
|
|
||||||
|
bool ok = m_connection.connectStart(conn_string); //keywords, values);
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
if (ok && m_connection.status() != CONNECTION_BAD) {
|
||||||
|
int sock = m_connection.socket();
|
||||||
|
Win32Event socket_event(Win32Event::Reset::Auto, Win32Event::Initial::Clear);
|
||||||
|
|
||||||
|
long fd = FD_WRITE;
|
||||||
|
while (true) {
|
||||||
|
// poll till complete or failed (we can get an abort command)
|
||||||
|
WSAEventSelect(sock, socket_event.handle(), fd);
|
||||||
|
WaitHandleList whl;
|
||||||
|
auto wait_result_socket_event = whl.add(socket_event);
|
||||||
|
auto wait_result_stop = whl.add(m_stopEvent);
|
||||||
|
|
||||||
|
auto nu = std::chrono::steady_clock::now();
|
||||||
|
std::chrono::duration<float, std::milli> diff = -(nu-start);
|
||||||
|
diff += 30s;
|
||||||
|
DWORD timeout = diff.count();
|
||||||
|
|
||||||
|
DWORD res = MsgWaitForMultipleObjectsEx(
|
||||||
|
whl.count(), // _In_ DWORD nCount,
|
||||||
|
whl, // _In_ const HANDLE *pHandles,
|
||||||
|
timeout, // _In_ DWORD dwMilliseconds,
|
||||||
|
0, // _In_ DWORD dwWakeMask,
|
||||||
|
0 // _In_ DWORD dwFlags
|
||||||
|
);
|
||||||
|
if (res == wait_result_socket_event) {
|
||||||
|
auto poll_state = m_connection.connectPoll();
|
||||||
|
if (poll_state == PGRES_POLLING_OK) {
|
||||||
|
// if connected return true
|
||||||
|
doStateCallback(ASyncDBConnection::State::Connected);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (poll_state == PGRES_POLLING_FAILED) {
|
||||||
|
doStateCallback(ASyncDBConnection::State::NotConnected);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (poll_state == PGRES_POLLING_READING) {
|
||||||
|
doStateCallback(ASyncDBConnection::State::Connecting);
|
||||||
|
fd = FD_READ;
|
||||||
|
}
|
||||||
|
else if (poll_state == PGRES_POLLING_WRITING) {
|
||||||
|
doStateCallback(ASyncDBConnection::State::Connecting);
|
||||||
|
fd = FD_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (res == wait_result_stop) {
|
||||||
|
|
||||||
|
}
|
||||||
|
} // end while (true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASyncDBConnectionThread::communicate()
|
||||||
|
{
|
||||||
|
while (!terminateRequested) {
|
||||||
|
// wait for something to do:
|
||||||
|
// - command to send to server
|
||||||
|
// - wait for results and (notifies can also come in)
|
||||||
|
// - pass each result on to the completion routine
|
||||||
|
// - notify comming in from the server
|
||||||
|
// - pass to notify callback
|
||||||
|
// - connection raises an error
|
||||||
|
// - return
|
||||||
|
// - stop signal
|
||||||
|
// - return
|
||||||
|
|
||||||
|
|
||||||
|
if (m_state == ASyncDBConnection::State::Connected) {
|
||||||
|
waitForAndSendCommand();
|
||||||
|
}
|
||||||
|
else if (m_state == ASyncDBConnection::State::QuerySend || m_state == ASyncDBConnection::State::CancelSend) {
|
||||||
|
// Wait for result, even after a cancel we should wait, for all results
|
||||||
|
// New command's are not excepted when one has been send
|
||||||
|
waitForResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASyncDBConnectionThread::stop()
|
||||||
|
{
|
||||||
|
terminateRequested = true;
|
||||||
|
m_stopEvent.set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::State state)
|
||||||
|
{
|
||||||
|
m_state = state;
|
||||||
|
// std::lock_guard<std::mutex> lg(m_stateCallback.m_mutex);
|
||||||
|
// if (m_stateCallback.m_func) {
|
||||||
|
// m_stateCallback.m_func(state);
|
||||||
|
// }
|
||||||
|
emit asyncConnObject->onStateChanged(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASyncDBConnectionThread::waitForAndSendCommand()
|
||||||
|
{
|
||||||
|
#if false
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
// lock the data
|
||||||
|
std::unique_lock<std::mutex> lk(m_commandQueue.m_mutex);
|
||||||
|
if (m_commandQueue.m_queue.empty()) {
|
||||||
|
// no data wait till there is data
|
||||||
|
m_commandQueue.m_newEvent.wait_for(lk, 1000ms);
|
||||||
|
// can we use the predicate to reimplement the stop function???, []{return ready;});
|
||||||
|
|
||||||
|
}
|
||||||
|
doNewCommand();
|
||||||
|
|
||||||
|
#else
|
||||||
|
WaitHandleList whl;
|
||||||
|
auto wait_result_new_command = whl.add(m_commandQueue.m_newEvent);
|
||||||
|
auto wait_result_stop = whl.add(m_stopEvent);
|
||||||
|
|
||||||
|
DWORD res = MsgWaitForMultipleObjectsEx(
|
||||||
|
whl.count(), // _In_ DWORD nCount,
|
||||||
|
whl, // _In_ const HANDLE *pHandles,
|
||||||
|
INFINITE, // _In_ DWORD dwMilliseconds,
|
||||||
|
0, // _In_ DWORD dwWakeMask,
|
||||||
|
0 // _In_ DWORD dwFlags
|
||||||
|
);
|
||||||
|
if (res == wait_result_new_command) {
|
||||||
|
doNewCommand();
|
||||||
|
}
|
||||||
|
if (res == wait_result_stop)
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASyncDBConnectionThread::doNewCommand()
|
||||||
|
{
|
||||||
|
// get command from top of queue (but leave it in the queue, we need the callback)
|
||||||
|
if (! m_commandQueue.m_queue.empty()) {
|
||||||
|
const Command &command = m_commandQueue.m_queue.front();
|
||||||
|
if (!command.command.empty()) {
|
||||||
|
bool query_send = false;
|
||||||
|
if (command.params.empty())
|
||||||
|
query_send = m_connection.sendQuery(command.command.c_str());
|
||||||
|
else
|
||||||
|
query_send = m_connection.sendQueryParams(command.command.c_str(), command.params);
|
||||||
|
|
||||||
|
if (query_send) {
|
||||||
|
m_timer.start();
|
||||||
|
doStateCallback(ASyncDBConnection::State::QuerySend);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::string error = m_connection.getErrorMessage();
|
||||||
|
// todo: need to report the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ASyncDBConnectionThread::consumeResultInput()
|
||||||
|
{
|
||||||
|
bool finished = false;
|
||||||
|
if (m_connection.consumeInput()) {
|
||||||
|
while ( ! finished && ! m_connection.isBusy()) {
|
||||||
|
auto res(m_connection.getResult());
|
||||||
|
{
|
||||||
|
qint64 ms = m_timer.restart();
|
||||||
|
std::lock_guard<std::mutex> lg(m_commandQueue.m_mutex);
|
||||||
|
m_commandQueue.m_queue.front().on_result(res, ms);
|
||||||
|
if (res == nullptr) {
|
||||||
|
m_timer.invalidate();
|
||||||
|
m_commandQueue.m_queue.pop();
|
||||||
|
doStateCallback(ASyncDBConnection::State::Connected);
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// else is still waiting for more data
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// error during consume
|
||||||
|
|
||||||
|
}
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASyncDBConnectionThread::waitForResult()
|
||||||
|
{
|
||||||
|
int sock = m_connection.socket();
|
||||||
|
|
||||||
|
fd_set readfds;
|
||||||
|
timeval timeout;
|
||||||
|
bool finished = false;
|
||||||
|
while (!finished && !terminateRequested) {
|
||||||
|
FD_ZERO(&readfds);
|
||||||
|
FD_SET(sock, &readfds);
|
||||||
|
|
||||||
|
timeout.tv_sec = 5;
|
||||||
|
timeout.tv_usec = 0;
|
||||||
|
|
||||||
|
int select_result = select(sock + 1, &readfds, nullptr, nullptr, &timeout);
|
||||||
|
if (select_result > 0) {
|
||||||
|
if (FD_ISSET(sock, &readfds)) {
|
||||||
|
if (consumeResultInput()) {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#if false
|
||||||
|
Win32Event socket_event(Win32Event::Reset::Manual, Win32Event::Initial::Clear);
|
||||||
|
|
||||||
|
long fd = FD_READ | FD_CLOSE;
|
||||||
|
|
||||||
|
bool finished = false;
|
||||||
|
while ( ! finished) {
|
||||||
|
WSAEventSelect(sock, socket_event.handle(), fd);
|
||||||
|
|
||||||
|
WaitHandleList whl;
|
||||||
|
auto wait_result_socket = whl.add(socket_event);
|
||||||
|
//auto wait_result_stop = whl.add(m_stopEvent);
|
||||||
|
|
||||||
|
DWORD res = MsgWaitForMultipleObjectsEx(
|
||||||
|
whl.count(), // _In_ DWORD nCount,
|
||||||
|
whl, // _In_ const HANDLE *pHandles,
|
||||||
|
INFINITE, // _In_ DWORD dwMilliseconds,
|
||||||
|
0, // _In_ DWORD dwWakeMask,
|
||||||
|
0 // _In_ DWORD dwFlags
|
||||||
|
);
|
||||||
|
if (res == wait_result_socket) {
|
||||||
|
WSANETWORKEVENTS net_events;
|
||||||
|
WSAEnumNetworkEvents(sock, socket_event.handle(), &net_events);
|
||||||
|
|
||||||
|
if (net_events.lNetworkEvents & FD_READ) {
|
||||||
|
if (consumeResultInput()) {
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res == wait_result_stop) {
|
||||||
|
// Send cancel, close connection and terminate thread
|
||||||
|
cancel();
|
||||||
|
doStateCallback(State::Terminating);
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
} // end while
|
||||||
|
// When last result received, remove command from queue
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASyncDBConnectionThread::processNotice(const PGresult *result)
|
||||||
|
{
|
||||||
|
// Pgsql::Result res(result);
|
||||||
|
// std::lock_guard<std::mutex> lg(m_noticeCallback.m_mutex);
|
||||||
|
// if (m_noticeCallback.m_func) {
|
||||||
|
// Pgsql::ErrorDetails details = Pgsql::ErrorDetails::createErrorDetailsFromPGresult(result);
|
||||||
|
// m_noticeCallback.m_func(details);
|
||||||
|
// }
|
||||||
|
Pgsql::ErrorDetails details = Pgsql::ErrorDetails::createErrorDetailsFromPGresult(result);
|
||||||
|
emit asyncConnObject->onNotice(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ASyncDBConnection::ASyncDBConnection()
|
||||||
|
: m_threadData(std::make_unique<ASyncDBConnectionThread>(this))
|
||||||
|
{}
|
||||||
|
|
||||||
|
ASyncDBConnection::~ASyncDBConnection()
|
||||||
|
{
|
||||||
|
closeConnection();
|
||||||
|
}
|
||||||
|
|
||||||
ASyncDBConnection::State ASyncDBConnection::state() const
|
ASyncDBConnection::State ASyncDBConnection::state() const
|
||||||
{
|
{
|
||||||
return m_state;
|
return m_threadData->m_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASyncDBConnection::setupConnection(const ConnectionConfig &config)
|
void ASyncDBConnection::setupConnection(const ConnectionConfig &config)
|
||||||
{
|
{
|
||||||
m_config = config;
|
if (m_thread.joinable()) {
|
||||||
// auto keywords = m_config.getKeywords();
|
m_threadData->stop();
|
||||||
// auto values = m_config.getValues();
|
m_thread.join();
|
||||||
QString conn_string = config.connectionString();
|
}
|
||||||
bool ok = m_connection.connectStart(conn_string.toStdString().c_str());
|
m_threadData->m_config = config;
|
||||||
// auto start = std::chrono::steady_clock::now();
|
m_thread = std::thread([this] () { m_threadData->run(); });
|
||||||
if (ok && m_connection.status() != CONNECTION_BAD) {
|
|
||||||
auto sock_handle = m_connection.socket();
|
|
||||||
|
|
||||||
m_asioSock.assign(ip::tcp::v4(), sock_handle);
|
|
||||||
m_asioSock.non_blocking(true);
|
|
||||||
|
|
||||||
m_asioSock.async_write_some(null_buffers(),
|
|
||||||
[this] (boost::system::error_code ec, std::size_t s)
|
|
||||||
{ async_connect_handler(ec, s); }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void ASyncDBConnection::async_connect_handler(boost::system::error_code ec, std::size_t /*s*/)
|
|
||||||
{
|
|
||||||
// boost::asio::error::operation_aborted
|
|
||||||
if (ec == boost::system::errc::success) {
|
|
||||||
auto poll_state = m_connection.connectPoll();
|
|
||||||
if (poll_state == PGRES_POLLING_OK) {
|
|
||||||
// if connected return true
|
|
||||||
doStateCallback(State::Connected);
|
|
||||||
}
|
|
||||||
else if (poll_state == PGRES_POLLING_FAILED) {
|
|
||||||
doStateCallback(State::NotConnected);
|
|
||||||
}
|
|
||||||
else if (poll_state == PGRES_POLLING_READING) {
|
|
||||||
doStateCallback(State::Connecting);
|
|
||||||
m_asioSock.async_read_some(null_buffers(),
|
|
||||||
[this] (boost::system::error_code ec, std::size_t s)
|
|
||||||
{ async_connect_handler(ec, s); }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (poll_state == PGRES_POLLING_WRITING) {
|
|
||||||
doStateCallback(State::Connecting);
|
|
||||||
m_asioSock.async_write_some(null_buffers(),
|
|
||||||
[this] (boost::system::error_code ec, std::size_t s)
|
|
||||||
{ async_connect_handler(ec, s); }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ASyncDBConnection::doStateCallback(State state)
|
void ASyncDBConnection::doStateCallback(State state)
|
||||||
{
|
{
|
||||||
|
|
@ -96,79 +476,38 @@ void ASyncDBConnection::doStateCallback(State state)
|
||||||
|
|
||||||
void ASyncDBConnection::closeConnection()
|
void ASyncDBConnection::closeConnection()
|
||||||
{
|
{
|
||||||
// SHould this be async too????
|
// doStateCallback(State::NotConnected);
|
||||||
if (m_state == State::QuerySend) {
|
// TODO also send cancel???
|
||||||
m_canceller.cancel(nullptr);
|
m_threadData->stop();
|
||||||
|
if (m_thread.joinable()) {
|
||||||
|
m_thread.join();
|
||||||
}
|
}
|
||||||
if (m_state != State::NotConnected) {
|
|
||||||
// Do not really want to close it before libpq is finished with it
|
|
||||||
// However explicitly is the destroctor doing the right thing?
|
|
||||||
//m_asioSock.close();
|
|
||||||
m_connection.close();
|
|
||||||
}
|
|
||||||
doStateCallback(State::NotConnected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ASyncDBConnection::send(const std::string &command, on_result_callback on_result)
|
bool ASyncDBConnection::send(const std::string &command, on_result_callback on_result)
|
||||||
{
|
{
|
||||||
m_connection.sendQuery(command);
|
{
|
||||||
m_timer.start();
|
std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex);
|
||||||
doStateCallback(State::QuerySend);
|
m_threadData->m_commandQueue.m_queue.emplace(command, on_result);
|
||||||
m_asioSock.async_read_some(null_buffers(),
|
m_threadData->m_commandQueue.m_newEvent.set();
|
||||||
[this, on_result] (boost::system::error_code ec, std::size_t s)
|
}
|
||||||
{ async_query_handler(ec, s, on_result); }
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ASyncDBConnection::send(const std::string &command, Pgsql::Params params, on_result_callback on_result)
|
bool ASyncDBConnection::send(const std::string &command, Pgsql::Params params, on_result_callback on_result)
|
||||||
{
|
{
|
||||||
m_connection.sendQueryParams(command.c_str(), params);
|
|
||||||
m_timer.start();
|
|
||||||
doStateCallback(State::QuerySend);
|
|
||||||
m_asioSock.async_read_some(null_buffers(),
|
|
||||||
[this, on_result] (boost::system::error_code ec, std::size_t s)
|
|
||||||
{ async_query_handler(ec, s, on_result); }
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ASyncDBConnection::async_query_handler(boost::system::error_code ec, std::size_t /*s*/, on_result_callback on_result)
|
|
||||||
{
|
{
|
||||||
if (ec == boost::system::errc::success) {
|
std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex);
|
||||||
bool finished = false;
|
m_threadData->m_commandQueue.m_queue.emplace(command, std::move(params), on_result);
|
||||||
if (m_connection.consumeInput()) {
|
m_threadData->m_commandQueue.m_newEvent.set();
|
||||||
while ( ! finished && ! m_connection.isBusy()) {
|
|
||||||
auto res = m_connection.getResultNoThrow();
|
|
||||||
qint64 ms = m_timer.restart();
|
|
||||||
on_result(res, ms);
|
|
||||||
if (res == nullptr) {
|
|
||||||
m_timer.invalidate();
|
|
||||||
doStateCallback(State::Connected);
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else is still waiting for more data
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// error during consume
|
|
||||||
auto error_msg = m_connection.getErrorMessage();
|
|
||||||
|
|
||||||
}
|
|
||||||
//return finished;
|
|
||||||
if (!finished) {
|
|
||||||
// wait for more
|
|
||||||
m_asioSock.async_read_some(null_buffers(),
|
|
||||||
[this, on_result] (boost::system::error_code ec, std::size_t s)
|
|
||||||
{ async_query_handler(ec, s, on_result); }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ASyncDBConnection::cancel()
|
bool ASyncDBConnection::cancel()
|
||||||
{
|
{
|
||||||
return m_canceller.cancel(nullptr);
|
return m_threadData->cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASyncDBConnection::processNotice(const PGresult *result)
|
void ASyncDBConnection::processNotice(const PGresult *result)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
#include "ConnectionConfig.h"
|
#include "ConnectionConfig.h"
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <boost/asio/ip/tcp.hpp>
|
#include <memory>
|
||||||
#include <boost/asio/io_service.hpp>
|
|
||||||
|
|
||||||
|
class ASyncDBConnectionThread;
|
||||||
/** \brief Class that handles asynchronous execution of queries.
|
/** \brief Class that handles asynchronous execution of queries.
|
||||||
*
|
*
|
||||||
* Queries are passed to this class with a routine to call on completion
|
* Queries are passed to this class with a routine to call on completion
|
||||||
|
|
@ -32,7 +32,7 @@ public:
|
||||||
|
|
||||||
using on_result_callback = std::function<void(Expected<std::shared_ptr<Pgsql::Result>>, qint64)>;
|
using on_result_callback = std::function<void(Expected<std::shared_ptr<Pgsql::Result>>, qint64)>;
|
||||||
|
|
||||||
explicit ASyncDBConnection(boost::asio::io_service &ios);
|
explicit ASyncDBConnection();
|
||||||
~ASyncDBConnection();
|
~ASyncDBConnection();
|
||||||
|
|
||||||
State state() const;
|
State state() const;
|
||||||
|
|
@ -73,17 +73,18 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Pgsql::Connection m_connection;
|
Pgsql::Connection m_connection;
|
||||||
boost::asio::ip::tcp::socket m_asioSock;
|
std::unique_ptr<ASyncDBConnectionThread> m_threadData;
|
||||||
|
std::thread m_thread;
|
||||||
ConnectionConfig m_config;
|
ConnectionConfig m_config;
|
||||||
State m_state = State::NotConnected;
|
State m_state = State::NotConnected;
|
||||||
Pgsql::Canceller m_canceller;
|
Pgsql::Canceller m_canceller;
|
||||||
|
|
||||||
QElapsedTimer m_timer;
|
QElapsedTimer m_timer;
|
||||||
|
|
||||||
void async_connect_handler(boost::system::error_code ec, std::size_t s);
|
|
||||||
void async_query_handler(boost::system::error_code ec, std::size_t s, on_result_callback on_result);
|
|
||||||
void doStateCallback(State state);
|
void doStateCallback(State state);
|
||||||
void processNotice(const PGresult *result);
|
void processNotice(const PGresult *result);
|
||||||
|
|
||||||
|
friend class ASyncDBConnectionThread;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ASyncDBConnection::State);
|
Q_DECLARE_METATYPE(ASyncDBConnection::State);
|
||||||
|
|
|
||||||
31
pglablib/WaitHandleList.cpp
Normal file
31
pglablib/WaitHandleList.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include "WaitHandleList.h"
|
||||||
|
#include "win32event.h"
|
||||||
|
|
||||||
|
WaitHandleList::WaitHandleList() = default;
|
||||||
|
WaitHandleList::~WaitHandleList() = default;
|
||||||
|
|
||||||
|
WaitResultValue WaitHandleList::add(HANDLE h)
|
||||||
|
{
|
||||||
|
m_waitHandles.push_back(h);
|
||||||
|
return WAIT_OBJECT_0 + static_cast<DWORD>(m_waitHandles.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitResultValue WaitHandleList::add(Win32Event &e)
|
||||||
|
{
|
||||||
|
return add(e.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WaitHandleList::count() const
|
||||||
|
{
|
||||||
|
return static_cast<DWORD>(m_waitHandles.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitHandleList::clear()
|
||||||
|
{
|
||||||
|
m_waitHandles.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitHandleList::operator const HANDLE*() const
|
||||||
|
{
|
||||||
|
return m_waitHandles.data();
|
||||||
|
}
|
||||||
26
pglablib/WaitHandleList.h
Normal file
26
pglablib/WaitHandleList.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#ifndef WAITHANDLELIST_H
|
||||||
|
#define WAITHANDLELIST_H
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Win32Event;
|
||||||
|
|
||||||
|
using WaitResultValue = DWORD;
|
||||||
|
|
||||||
|
class WaitHandleList {
|
||||||
|
public:
|
||||||
|
WaitHandleList();
|
||||||
|
~WaitHandleList();
|
||||||
|
|
||||||
|
WaitResultValue add(HANDLE h);
|
||||||
|
WaitResultValue add(Win32Event &e);
|
||||||
|
DWORD count() const;
|
||||||
|
void clear();
|
||||||
|
operator const HANDLE*() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<HANDLE> m_waitHandles;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WAITHANDLELIST_H
|
||||||
47
pglablib/Win32Event.h
Normal file
47
pglablib/Win32Event.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#ifndef WIN32EVENT_H
|
||||||
|
#define WIN32EVENT_H
|
||||||
|
|
||||||
|
#include <WinSock2.h>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
/** Simpel wrapper around a Win32 Event object.
|
||||||
|
|
||||||
|
Mostly to make cleanup automatic.*/
|
||||||
|
class Win32Event {
|
||||||
|
public:
|
||||||
|
enum class Reset { Auto=0, Manual=1 };
|
||||||
|
enum class Initial { Clear=0, Set=1 };
|
||||||
|
|
||||||
|
Win32Event(Reset r, Initial is)
|
||||||
|
: hEvent(CreateEvent(
|
||||||
|
nullptr, // _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
|
||||||
|
BOOL(r), // _In_ BOOL bManualReset,
|
||||||
|
BOOL(is), // _In_ BOOL bInitialState,
|
||||||
|
nullptr //_In_opt_ LPCTSTR lpName
|
||||||
|
))
|
||||||
|
{}
|
||||||
|
|
||||||
|
Win32Event(Reset r, Initial is, int sock, long net_events)
|
||||||
|
: Win32Event(r, is)
|
||||||
|
{
|
||||||
|
WSAEventSelect(sock, hEvent, net_events);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Win32Event()
|
||||||
|
{
|
||||||
|
CloseHandle(hEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32Event(const Win32Event &) = delete;
|
||||||
|
Win32Event &operator=(const Win32Event &) = delete;
|
||||||
|
|
||||||
|
void set() { SetEvent(hEvent); }
|
||||||
|
|
||||||
|
void reset() { ResetEvent(hEvent); }
|
||||||
|
|
||||||
|
HANDLE handle() { return hEvent; }
|
||||||
|
private:
|
||||||
|
HANDLE hEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WIN32EVENT_H
|
||||||
|
|
@ -23,6 +23,7 @@ SOURCES += \
|
||||||
Pglablib.cpp \
|
Pglablib.cpp \
|
||||||
ASyncDBConnection.cpp \
|
ASyncDBConnection.cpp \
|
||||||
ConnectionConfig.cpp \
|
ConnectionConfig.cpp \
|
||||||
|
WaitHandleList.cpp \
|
||||||
catalog/PgType.cpp \
|
catalog/PgType.cpp \
|
||||||
catalog/PgTypeContainer.cpp \
|
catalog/PgTypeContainer.cpp \
|
||||||
catalog/PgNamespace.cpp \
|
catalog/PgNamespace.cpp \
|
||||||
|
|
@ -88,6 +89,8 @@ HEADERS += \
|
||||||
Pglablib.h \
|
Pglablib.h \
|
||||||
ASyncDBConnection.h \
|
ASyncDBConnection.h \
|
||||||
ConnectionConfig.h \
|
ConnectionConfig.h \
|
||||||
|
WaitHandleList.h \
|
||||||
|
Win32Event.h \
|
||||||
catalog/PgType.h \
|
catalog/PgType.h \
|
||||||
catalog/PgTypeContainer.h \
|
catalog/PgTypeContainer.h \
|
||||||
catalog/PgNamespace.h \
|
catalog/PgNamespace.h \
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue