Skip to content

Commit

Permalink
Coroutines!
Browse files Browse the repository at this point in the history
  • Loading branch information
dantti committed Oct 30, 2024
1 parent df584ee commit 807b563
Show file tree
Hide file tree
Showing 14 changed files with 676 additions and 3 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: MIT

cmake_minimum_required(VERSION 3.16)
project(libasql VERSION 0.91.0 LANGUAGES CXX)
project(libasql VERSION 0.92.0 LANGUAGES CXX)

include(GNUInstallDirs)

Expand All @@ -22,7 +22,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
# to always look for includes there:
set(CMAKE_INCLUDE_CURRENT_DIR ON)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

Expand Down
7 changes: 7 additions & 0 deletions demos/async1/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ target_link_libraries(async1
Qt::Core
)

add_executable(coroutines coroutines.cpp)
target_link_libraries(coroutines
ASql::Core
ASql::Pg
Qt::Core
)

add_executable(transactions transactions.cpp)
target_link_libraries(transactions
ASql::Core
Expand Down
290 changes: 290 additions & 0 deletions demos/async1/coroutines.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
/*
* SPDX-FileCopyrightText: (C) 2020 Daniel Nicoletti <[email protected]>
* SPDX-License-Identifier: MIT
*/

#include "../../src/acache.h"
#include "../../src/acoroexpected.h"
#include "../../src/adatabase.h"
#include "../../src/amigrations.h"
#include "../../src/apg.h"
#include "../../src/apool.h"
#include "../../src/aresult.h"
#include "../../src/atransaction.h"

#include <thread>

#include <QCoreApplication>
#include <QDateTime>
#include <QElapsedTimer>
#include <QJsonArray>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QScopeGuard>
#include <QTimer>

using namespace ASql;
using namespace Qt::StringLiterals;

int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);

APool::create(APg::factory(u"postgres:///?target_session_attrs=read-write"_s));
APool::setMaxIdleConnections(10);

auto db = APool::database();

auto counter = std::make_shared<int>(0);
QElapsedTimer t;
if (false) {
t.start();
for (int i = 0; i < 100'000; ++i) {
db.exec(u"SELECT 1", nullptr, [counter, &t](AResult &result) {
if (*counter == 999) {
qDebug() << "lambda" << t.elapsed();
}
*counter = *counter + 1;
});
}

*counter = 0;
t.start();
auto bench = [&t, counter, &db]() -> ACoroTerminator {
auto counter = std::make_shared<int>();
for (int i = 0; i < 100'000; ++i) {
auto result = co_await db.coExec(u"SELECT 1", nullptr);
// qDebug() << "coroutine" << *counter << t.elapsed();
if (*counter == 999) {
qDebug() << "coroutine" << *counter << t.elapsed();
}
*counter = *counter + 1;
}
};
bench();
}

if (false) {
auto callEx = []() -> ACoroTerminator {
qDebug() << "coro started";

auto db = APool::database();
db.exec(u"SELECT 2", nullptr, [](AResult &result) {
if (result.error()) {
qDebug() << "Error" << result.errorString();
} else {
qDebug() << "1s loop" << result.toHash();
}
});

auto obj = new QObject;
QTimer::singleShot(500, obj, [obj] {
qDebug() << "Delete Obj";
delete obj;
});

{
auto result = co_await db.coExec(u8"SELECT now(), pg_sleep(3)", obj);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}
obj->setProperty("crash", true);
}

{
auto result = co_await db.coExec(u8"SELECT now(), pg_sleep(1)", nullptr);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}
obj->setProperty("crash", true);
}
};

callEx();
}

if (false) {
auto callTransaction = []() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro exited"; });
qDebug() << "coro started";

auto db = co_await APool::coDatabase();

auto transaction = co_await db->coBegin();
qDebug() << "transaction started";

auto result = co_await db->coExec(u8"SELECT now()");
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}

auto result2 = co_await db->coExec(u8"SELECT now()");
if (result2.has_value()) {
qDebug() << "coro result2 has value" << result2->toJsonObject();
} else {
qDebug() << "coro result2 error" << result2.error();
}

auto commit = co_await transaction->coCommit();
if (commit.has_value()) {
qDebug() << "coro commit has value" << commit->toJsonObject();
} else {
qDebug() << "coro commit error" << commit.error();
}
};

callTransaction();
}

if (false) {
auto cache = new ACache;
cache->setDatabase(db);

auto callCache = [cache]() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro cache exited"; });
qDebug() << "coro cache started";

auto result = co_await cache->coExec(u"SELECT now(), pg_sleep(1)"_s, nullptr);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}

auto resultCached = co_await cache->coExec(u"SELECT now(), pg_sleep(1)"_s, nullptr);
if (resultCached.has_value()) {
qDebug() << "coro resultCached has value" << resultCached->toJsonObject();
} else {
qDebug() << "coro resultCached error" << resultCached.error();
}
};

callCache();
}

if (false) {
auto callInvalid = []() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro db invalid exited"; });
qDebug() << "coro db invalid started";

ADatabase db;
auto result = co_await db.coExec(u"SELECT now()"_s, nullptr);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}
};

callInvalid();
}

if (true) {
auto callOuter = []() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro outer exited"; });
qDebug() << "coro outer started";

auto callInner = []() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro inner exited"; });
qDebug() << "coro inner started";

auto db = co_await APool::coDatabase();
if (!db) {
qDebug() << "coro db error" << db.error();
co_return;
}

auto result = co_await db->coExec(u"SELECT now()"_s, nullptr);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}
};

// Cannot be awaited :)
callInner();
co_return;
};

callOuter();
}

if (false) {
auto callPool = []() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro pool exited"; });
qDebug() << "coro pool started";

auto db = co_await APool::coDatabase();
if (db.has_value()) {
qDebug() << "coro pool has value" << db->isOpen();
} else {
qDebug() << "coro pool error" << db.error();
}

auto result = co_await db->coExec(u"SELECT now(), pg_sleep(1)"_s, nullptr);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}
};

callPool();
}

if (false) {
auto callTerminatorEarly = []() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro exited"; });
qDebug() << "coro started";

auto obj = new QObject;
QTimer::singleShot(500, obj, [obj] {
qDebug() << "Delete Obj";
delete obj;
});
co_yield obj; // so that this promise is destroyed if this object is destroyed

auto db = co_await APool::coDatabase();

auto result = co_await db->coExec(u8"SELECT now(), pg_sleep(2)", obj);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}
};
callTerminatorEarly();

auto callTerminatorLater = []() -> ACoroTerminator {
auto _ = qScopeGuard([] { qDebug() << "coro exited"; });
qDebug() << "coro started";

auto obj = new QObject;
QTimer::singleShot(2000, obj, [obj] {
qDebug() << "Delete Obj later";
delete obj;
});
co_yield obj; // so that this promise is destroyed if this object is destroyed

auto db = co_await APool::coDatabase();

auto result = co_await db->coExec(u8"SELECT now()", obj);
if (result.has_value()) {
qDebug() << "coro result has value" << result->toJsonObject();
} else {
qDebug() << "coro result error" << result.error();
}
};

callTerminatorLater();
}

app.exec();
}
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ set(asql_SRC
acache.cpp
apreparedquery.cpp
apreparedquery.h
acoroexpected.cpp
)

set(asql_HEADERS
adatabase.h
apreparedquery.h
apool.h
atransaction.h

acoroexpected.h
aresult.h
adriver.h
adriverfactory.h
Expand Down
33 changes: 33 additions & 0 deletions src/acache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "acache.h"

#include "acoroexpected.h"
#include "adatabase.h"
#include "apool.h"
#include "aresult.h"
Expand Down Expand Up @@ -249,6 +250,38 @@ int ACache::size() const
return d->cache.size();
}

AExpectedResult ACache::coExec(QStringView query, QObject *receiver)
{
AExpectedResult coro(receiver);
execExpiring(query, -1ms, {}, receiver, coro.callback);
return coro;
}

AExpectedResult ACache::coExec(QStringView query, const QVariantList &args, QObject *receiver)
{
AExpectedResult coro(receiver);
execExpiring(query, -1ms, args, receiver, coro.callback);
return coro;
}

AExpectedResult
ACache::coExecExpiring(QStringView query, std::chrono::milliseconds maxAge, QObject *receiver)
{
AExpectedResult coro(receiver);
execExpiring(query, maxAge, {}, receiver, coro.callback);
return coro;
}

AExpectedResult ACache::coExecExpiring(QStringView query,
std::chrono::milliseconds maxAge,
const QVariantList &args,
QObject *receiver)
{
AExpectedResult coro(receiver);
execExpiring(query, maxAge, args, receiver, coro.callback);
return coro;
}

void ACache::exec(QStringView query, QObject *receiver, AResultFn cb)
{
execExpiring(query, -1ms, {}, receiver, cb);
Expand Down
Loading

0 comments on commit 807b563

Please sign in to comment.