Projects
Kube:Winterfell
kasync
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 3
View file
kasync-4.99.0.tar.gz/autotests/CMakeLists.txt
Changed
@@ -5,9 +5,9 @@ LINK_LIBRARIES KF5Async Qt5::Test ) -if (WITH_KJOB) - ecm_add_test(kjobtest.cpp testkjob.cpp - TEST_NAME kjobtest - LINK_LIBRARIES KF5Async Qt5::Test KF5::CoreAddons - ) -endif() +# if (WITH_KJOB) +# ecm_add_test(kjobtest.cpp testkjob.cpp +# TEST_NAME kjobtest +# LINK_LIBRARIES KF5Async Qt5::Test KF5::CoreAddons +# ) +# endif()
View file
kasync-4.99.0.tar.gz/autotests/asynctest.cpp
Changed
@@ -32,6 +32,18 @@ #include <functional> +#define COMPARERET(actual, expected, retval) \ +do {\ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ + return retval;\ +} while (0) + +#define VERIFYRET(statement, retval) \ +do {\ + if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ + return retval;\ +} while (0) + class AsyncTest : public QObject { Q_OBJECT @@ -45,53 +57,18 @@ private Q_SLOTS: void testSyncPromises(); + void testErrorHandling(); + void testContext(); + void testDoWhile(); void testAsyncPromises(); - void testAsyncPromises2(); void testNestedAsync(); - void testNestedJob_data(); - void testNestedJob(); void testVoidNestedJob(); - void testImplicitConversion(); - void testStartValue(); - - void testAsyncThen(); - void testAsyncThenClassArgument(); - void testSyncThen(); - void testJoinedThen(); - void testVoidThen(); - void testMemberThen(); - void testSyncMemberThen(); - void testSyncVoidMemberThen(); - void testAsyncEach(); - void testSyncEach(); - void testNestedEach(); - void testJoinedEach(); - void testVoidEachThen(); - void testAsyncVoidEachThen(); - - void testAsyncReduce(); - void testSyncReduce(); - void testJoinedReduce(); - void testVoidReduce(); - - void testProgressReporting(); - void testErrorHandler(); - void testErrorPropagation(); - void testErrorHandlerAsync(); - void testErrorPropagationAsync(); - void testNestedErrorPropagation(); - - void testChainingRunningJob(); - void testChainingFinishedJob(); - - void testLifetimeWithoutHandle(); - void testLifetimeWithHandle(); - - void testErrorTask_data(); - void testErrorTask(); + void testAsyncSerialEach(); void benchmarkSyncThenExecutor(); + void benchmarkFutureThenExecutor(); + void benchmarkThenExecutor(); private: template<typename T> @@ -190,58 +167,342 @@ void AsyncTest::testSyncPromises() { - auto baseJob = KAsync::start<int>( - [](KAsync::Future<int> &f) { - f.setValue(42); - f.setFinished(); - }) - .then<QString, int>( - [](int v, KAsync::Future<QString> &f) { - f.setValue(QLatin1String("Result is ") + QString::number(v)); - f.setFinished(); - }); + { + auto future = KAsync::syncStart<int>( + []() { + return 42; + }).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), 42); + } + + { + auto future = KAsync::start<int>( + [](KAsync::Future<int> &f) { + f.setResult(42); + }).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), 42); + } + + //Sync start + { + auto future = KAsync::start<int>([] { + return KAsync::value<int>(42); + }).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), 42); + } - auto job = baseJob.then<QString, QString>( - [](const QString &v, KAsync::Future<QString> &f) { - f.setValue(v.toUpper()); - f.setFinished(); + //Sync start + { + bool called = false; + auto future = KAsync::start<void>( + [&called] { + called = true; + return KAsync::null<void>(); + }).exec(); + QVERIFY(future.isFinished()); + QVERIFY(called); + } + //void + { + auto future = KAsync::start<void>( + []() { + return KAsync::null<void>(); + }).exec(); + QVERIFY(future.isFinished()); + } + + //value + { + auto future = KAsync::value<int>(42).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), 42); + } + + //Sync then + { + auto job = KAsync::value<int>(42); + auto future = job.then<int, int>([](int value) { + return KAsync::value<int>(value); + }).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), 42); + } + + //Job then + { + auto job = KAsync::value<int>(42); + auto future = job.then<QString, int>([](int value) { + return KAsync::value<QString>(QString::number(value)); + }).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), QString::number(42)); + } + + //void Job then + { + bool continuationCalled = false; + auto job = KAsync::null<void>(); + auto future = job.then<void>([&continuationCalled] { + return KAsync::start<void>([&continuationCalled] { + continuationCalled = true; + return KAsync::null<void>(); + }); + }).exec(); + QVERIFY(future.isFinished()); + QVERIFY(continuationCalled); + } + + //Nested job then + { + auto job = KAsync::value<int>(42); + auto future = job.then<QString, int>( + KAsync::start<QString, int>([](int i) { + return KAsync::value<QString>(QString::number(i)); + }) + ).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), QString::number(42)); + } + + //Convert to void + { + KAsync::Job<void> job = KAsync::start<int>( + [] { + return KAsync::value<int>(42); + }).then<int, int>([](int i) { + return KAsync::value<int>(i); + }); + KAsync::Future<void> future = job.exec(); + QVERIFY(future.isFinished()); + } + + //Job then types + { + KAsync::Job<int, double> job1 = KAsync::start<int, double>( + [](double i) { + return KAsync::value<int>(i); + }); + + KAsync::Job<QString, double> job2 = job1.then<QString, int>([](int value) { + return KAsync::start<QString>([value]() { + return KAsync::value<QString>(QString::number(value)); + }); }); + double input = 42; + KAsync::Future<QString> future = job2.exec(input); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), QString::number(42)); + } + + //This is useful to be able to spawn different subjobs depending on the initial input value that the continuation gets. + { + auto future = KAsync::start<int, bool>( + [] (bool i) { + if (i) { + return KAsync::value(42); + } else { + return KAsync::error<int>(KAsync::Error("foo")); + } + }).exec(true); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), 42); + } - KAsync::Future<QString> future = job.exec(); + { + auto baseJob = KAsync::value<int>(42) + .then<QString, int>( + [](int v, KAsync::Future<QString> &f) { + f.setValue(QLatin1String("Result is ") + QString::number(v)); + f.setFinished(); + }); - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), QString::fromLatin1("RESULT IS 42")); + auto job = baseJob.then<QString, QString>( + [](const QString &v, KAsync::Future<QString> &f) { + f.setValue(v.toUpper()); + f.setFinished(); + }); + KAsync::Future<QString> future = job.exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), QString::fromLatin1("RESULT IS 42")); + } } -void AsyncTest::testAsyncPromises() +void AsyncTest::testErrorHandling() { - auto job = KAsync::start<int>( - [](KAsync::Future<int> &future) { - new AsyncSimulator<int>(future, 42); - }); + //Failing job + { + auto future = KAsync::start<int>( + [](KAsync::Future<int> &f) { + f.setError({1, "error"}); + }).exec(); + QVERIFY(future.isFinished()); + QCOMPARE(future.errorCode(), 1); + QCOMPARE(future.errorMessage().toUtf8(), QByteArray("error")); + } - KAsync::Future<int> future = job.exec(); + //Call error handler + { + bool errorHandlerCalled = false; + auto future = KAsync::error<int>({1, "error"}) + .then<int, int>([&errorHandlerCalled](const KAsync::Error &error, int) { + qWarning() << "Handler called"; + errorHandlerCalled = true; + COMPARERET(error.errorCode, 1, KAsync::error<int>(error)); + return KAsync::error<int>(error); + }).exec(); + QVERIFY(future.isFinished()); + QVERIFY(errorHandlerCalled); + QCOMPARE(future.errors().first(), KAsync::Error(1, "error")); + } - future.waitForFinished(); - QCOMPARE(future.value(), 42); + //Propagate error + { + bool errorHandlerCalled = false; + auto future = KAsync::error<int>({1, "error"}) + .then<int, int>( + [](int) { + VERIFYRET(false, KAsync::null<int>()); + return KAsync::null<int>(); + }) + .then<void, int>([&errorHandlerCalled](const KAsync::Error &error, int) { + errorHandlerCalled = true; + COMPARERET(error.errorCode, 1, KAsync::error<void>(error)); + return KAsync::error<void>(error); + }) + .exec(); + + QVERIFY(future.isFinished()); + QVERIFY(errorHandlerCalled); + QCOMPARE(future.errors().first(), KAsync::Error(1, "error")); + } + + //Propagate error + { + bool errorHandlerCalled1 = false; + bool errorHandlerCalled2 = false; + auto future = KAsync::error<int>({1, "error"}) + .then<int, int>( + [&errorHandlerCalled1](const KAsync::Error &error, int) { + errorHandlerCalled1 = true; + COMPARERET(error.errorCode, 1, KAsync::error<int>(error)); + return KAsync::error<int>(error); + }) + .then<void, int>([&errorHandlerCalled2](const KAsync::Error &error, int) { + errorHandlerCalled2 = true; + COMPARERET(error.errorCode, 1, KAsync::error<void>(error)); + return KAsync::error<void>(error); + }) + .exec(); + + QVERIFY(future.isFinished()); + QVERIFY(errorHandlerCalled1); + QVERIFY(errorHandlerCalled2); + QCOMPARE(future.errors().first(), KAsync::Error(1, "error")); + } + + //Reconcile error + { + bool errorHandlerCalled1 = false; + bool errorHandlerCalled2 = false; + auto future = KAsync::error<int>({1, "error"}) + .then<int, int>( + [&errorHandlerCalled1](const KAsync::Error &error, int) { + errorHandlerCalled1 = true; + COMPARERET(error, KAsync::Error(1, "error"), KAsync::null<int>()); + return KAsync::null<int>(); + }) + .then<void, int>([&errorHandlerCalled2](const KAsync::Error &error, int) { + VERIFYRET(!error, KAsync::null<void>()); + errorHandlerCalled2 = true; + return KAsync::null<void>(); + }) + .exec(); + + QVERIFY(errorHandlerCalled1); + QVERIFY(errorHandlerCalled2); + QVERIFY(future.isFinished()); + QVERIFY(!future.hasError()); + } + + //Propagate value on error + { + KAsync::Future<int> future = KAsync::value<int>(1) + .onError([](const KAsync::Error &error) { + Q_UNUSED(error); + QVERIFY(false); + }) + .exec(); + + QVERIFY(future.isFinished()); + QCOMPARE(future.value(), 1); + } } -void AsyncTest::testAsyncPromises2() +void AsyncTest::testContext() { - bool done = false; + QWeakPointer<QObject> refToObj; + { + KAsync::Job<int> job = KAsync::null<int>(); + { + auto contextObject = QSharedPointer<QObject>::create(); + refToObj = contextObject.toWeakRef(); + QVERIFY(refToObj); + job = KAsync::start<int>( + [](KAsync::Future<int> &future) { + new AsyncSimulator<int>(future, 42); + }); + job.addToContext(contextObject); + + //Ensure the context survives for the whole duration of the job + job = job.then<int>([](KAsync::Future<int> &future) { + new AsyncSimulator<int>(future, 42); + }); + } + + QVERIFY(refToObj); + + { + //Ensure the context survives copies + auto job2 = job; + job = KAsync::null<int>(); + KAsync::Future<int> future = job2.exec(); + QVERIFY(refToObj); + future.waitForFinished(); + } + } + QVERIFY(!refToObj); +} + +void AsyncTest::testDoWhile() +{ + int i = 0; + auto future = KAsync::doWhile([&i]() { + i++; + if (i < 5) { + return KAsync::value(KAsync::Continue); + } + return KAsync::value(KAsync::Break); + }) + .exec(); + future.waitForFinished(); + QVERIFY(future.isFinished()); + QCOMPARE(i, 5); +} + +void AsyncTest::testAsyncPromises() +{ auto job = KAsync::start<int>( [](KAsync::Future<int> &future) { new AsyncSimulator<int>(future, 42); - } - ).then<int, int>([&done](int result, KAsync::Future<int> &future) { - done = true; - future.setValue(result); - future.setFinished(); - }); - auto future = job.exec(); + }); - QTRY_VERIFY(done); + KAsync::Future<int> future = job.exec(); + + future.waitForFinished(); QCOMPARE(future.value(), 42); } @@ -269,64 +530,27 @@ QTRY_VERIFY(done); } -void AsyncTest::testNestedJob_data() -{ - QTest::addColumn<bool>("pass"); - QTest::newRow("pass") << true; - QTest::newRow("fail") << false; -} - -void AsyncTest::testNestedJob() -{ - QFETCH(bool, pass); - bool innerDone = false; - - auto job = KAsync::start<int>( - [&innerDone]() { - return KAsync::start<int>([&innerDone]() { - innerDone = true; - return 42; - }); - } - ).then<int, int>([&innerDone, pass](int in) -> KAsync::Job<int> { - if (pass) { - return KAsync::start<int>([&innerDone, in]() { - innerDone = true; - return in; - }); - } else { - return KAsync::error<int>(3, QLatin1String("foobar")); - } - }); - auto future = job.exec(); - - if (pass) { - QVERIFY(innerDone); - QCOMPARE(future.value(), 42); - QCOMPARE(future.errorCode(), 0); - } else { - QCOMPARE(future.errorCode(), 3); - } -} - void AsyncTest::testVoidNestedJob() { bool innerDone1 = false; bool innerDone2 = false; bool innerDone3 = false; - auto job = KAsync::start<void, KAsync::Job<void> >( + auto job = KAsync::start<void>( [&innerDone1]() -> KAsync::Job<void> { return KAsync::start<void>([&innerDone1]() { innerDone1 = true; + return KAsync::null<void>(); }); } ) - .then<void, KAsync::Job<void> >([&innerDone2, &innerDone3]() -> KAsync::Job<void> { + .then<void>([&innerDone2, &innerDone3]() -> KAsync::Job<void> { return KAsync::start<void>([&innerDone2]() { innerDone2 = true; + return KAsync::null<void>(); }) .then<void>([&innerDone3]() { innerDone3 = true; + return KAsync::null<void>(); }); }); auto future = job.exec(); @@ -337,779 +561,123 @@ QVERIFY(innerDone3); } -void AsyncTest::testImplicitConversion() -{ - auto job = KAsync::start<int>( - []() -> int { - return 42; - }) - .then<void, int>( - [](int in, KAsync::Future<void> &future){ - future.setFinished(); - }); - KAsync::Job<void> finalJob = job; -} - -void AsyncTest::testStartValue() -{ - auto job = KAsync::start<int, int>( - [](int in, KAsync::Future<int> &future) { - future.setValue(in); - future.setFinished(); - }); - - auto future = job.exec(42); - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 42); -} - - - - - -void AsyncTest::testAsyncThen() -{ - auto job = KAsync::start<int>( - [](KAsync::Future<int> &future) { - new AsyncSimulator<int>(future, 42); - }); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 42); -} - -void AsyncTest::testAsyncThenClassArgument() -{ - struct Test {}; - - auto job = KAsync::start<QSharedPointer<Test>>( - [](KAsync::Future<QSharedPointer<Test> > &future) { - new AsyncSimulator<QSharedPointer<Test> >(future, QSharedPointer<Test>::create()); - }).then<void, QSharedPointer<Test> >( - [](QSharedPointer<Test> i, KAsync::Future<void> &future) { - Q_UNUSED(i); - future.setFinished(); - } - ); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); -} - -void AsyncTest::testSyncThen() -{ - auto job = KAsync::start<int>( - []() -> int { - return 42; - }) - .then<int, int>( - [](int in) -> int { - return in * 2; - }); - - auto future = job.exec(); - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 84); -} - -void AsyncTest::testJoinedThen() -{ - auto job1 = KAsync::start<int, int>( - [](int in, KAsync::Future<int> &future) { - new AsyncSimulator<int>(future, in * 2); - }); - - auto job2 = KAsync::start<int>( - [](KAsync::Future<int> &future) { - new AsyncSimulator<int>(future, 42); - }) - .then<int>(job1); - - auto future = job2.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 84); -} - -void AsyncTest::testVoidThen() -{ - int check = 0; - - auto job = KAsync::start<void>( - [&check](KAsync::Future<void> &future) { - new AsyncSimulator<void>(future); - ++check; - }) - .then<void>( - [&check](KAsync::Future<void> &future) { - new AsyncSimulator<void>(future); - ++check; - }) - .then<void>( - [&check]() { - ++check; - }); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(check, 3); -} - - -void AsyncTest::testMemberThen() -{ - MemberTest memberTest; - - auto job = KAsync::start<int>( - []() -> int { - return 42; - }) - .then<MemberTest, int, int>(&memberTest, &MemberTest::asyncFoo); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 43); -} - -void AsyncTest::testSyncMemberThen() -{ - MemberTest memberTest; - - auto job = KAsync::start<int>( - []() -> int { - return 42; - }) - .then<MemberTest, int, int>(&memberTest, &MemberTest::syncFooRet); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 43); -} - -void AsyncTest::testSyncVoidMemberThen() -{ - MemberTest memberTest; - - auto job = KAsync::start<int>( - []() -> int { - return 42; - }) - .then<MemberTest, void, int>(&memberTest, &MemberTest::syncFoo); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(memberTest.mFoo, 42); -} - - - void AsyncTest::testAsyncEach() { - auto job = KAsync::start<QList<int>>( - [](KAsync::Future<QList<int>> &future) { - new AsyncSimulator<QList<int>>(future, { 1, 2, 3, 4 }); - }) - .each<QList<int>, int>( - [](const int &v, KAsync::Future<QList<int>> &future) { - new AsyncSimulator<QList<int>>(future, { v + 1 }); - }); - - auto future = job.exec(); - future.waitForFinished(); - - const QList<int> expected({ 2, 3, 4, 5 }); - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), expected); -} - -void AsyncTest::testSyncEach() -{ - auto job = KAsync::start<QList<int>>( - []() -> QList<int> { - return { 1, 2, 3, 4 }; - }) - .each<QList<int>, int>( - [](const int &v) -> QList<int> { - return { v + 1 }; - }); - - KAsync::Future<QList<int>> future = job.exec(); - - const QList<int> expected({ 2, 3, 4, 5 }); - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), expected); -} - -void AsyncTest::testNestedEach() -{ - auto job = KAsync::start<QList<int>>( - []() -> QList<int> { - return { 1, 2, 3, 4 }; - }) - .each<QList<int>, int>( - [](const int &v) { - return KAsync::start<QList<int> >([v]() -> QList<int> { - return { v + 1 }; - }); - }); - - KAsync::Future<QList<int>> future = job.exec(); - - const QList<int> expected({ 2, 3, 4, 5 }); - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), expected); -} - -void AsyncTest::testJoinedEach() -{ - auto job1 = KAsync::start<QList<int>, int>( - [](int v, KAsync::Future<QList<int>> &future) { - new AsyncSimulator<QList<int>>(future, { v * 2 }); - }); - - auto job = KAsync::start<QList<int>>( - []() -> QList<int> { - return { 1, 2, 3, 4 }; - }) - .each(job1); - - auto future = job.exec(); - future.waitForFinished(); - - const QList<int> expected({ 2, 4, 6, 8 }); - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), expected); -} - -void AsyncTest::testVoidEachThen() -{ - QList<int> check; - auto job = KAsync::start<QList<int>>( - []() -> QList<int> { - return { 1, 2, 3, 4 }; - }).each<void, int>( - [&check](const int &v) { - check << v; - }).then<void>([](){}); - - auto future = job.exec(); - - const QList<int> expected({ 1, 2, 3, 4 }); - QVERIFY(future.isFinished()); - QCOMPARE(check, expected); -} - -void AsyncTest::testAsyncVoidEachThen() -{ - bool completedJob = false; - QList<int> check; - auto job = KAsync::start<QList<int>>( - [](KAsync::Future<QList<int> > &future) { - new AsyncSimulator<QList<int>>(future, { 1, 2, 3, 4 }); - }).each<void, int>( - [&check](const int &v, KAsync::Future<void> &future) { - check << v; - new AsyncSimulator<void>(future); - }).then<void>([&completedJob](KAsync::Future<void> &future) { - completedJob = true; - future.setFinished(); - }); - - auto future = job.exec(); - future.waitForFinished(); - - const QList<int> expected({ 1, 2, 3, 4 }); - QVERIFY(future.isFinished()); - QVERIFY(completedJob); - QCOMPARE(check, expected); -} - - - - -void AsyncTest::testAsyncReduce() -{ - auto job = KAsync::start<QList<int>>( - [](KAsync::Future<QList<int>> &future) { - new AsyncSimulator<QList<int>>(future, { 1, 2, 3, 4 }); - }) - .reduce<int, QList<int>>( - [](const QList<int> &list, KAsync::Future<int> &future) { - new AsyncSimulator<int>(future, - [list](KAsync::Future<int> &future) { - int sum = 0; - for (int i : list) sum += i; - future.setValue(sum); - future.setFinished(); - } - ); - }); - - KAsync::Future<int> future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 10); -} - -void AsyncTest::testSyncReduce() -{ - auto job = KAsync::start<QList<int>>( - []() -> QList<int> { - return { 1, 2, 3, 4 }; - }) - .reduce<int, QList<int>>( - [](const QList<int> &list) -> int { - int sum = 0; - for (int i : list) sum += i; - return sum; - }); - - KAsync::Future<int> future = job.exec(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 10); -} - - -void AsyncTest::testJoinedReduce() -{ - auto job1 = KAsync::start<int, QList<int>>( - [](const QList<int> &list, KAsync::Future<int> &future) { - int sum = 0; - for (int i : list) sum += i; - new AsyncSimulator<int>(future, sum); - }); - - auto job = KAsync::start<QList<int>>( - []() -> QList<int> { - return { 1, 2, 3, 4 }; - }) - .reduce(job1); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.value(), 10); -} - -void AsyncTest::testVoidReduce() -{ -// This must not compile (reduce with void result makes no sense) -#ifdef TEST_BUILD_FAIL - auto job = KAsync::start<QList<int>>( - []() -> QList<int> { - return { 1, 2, 3, 4 }; - }) - .reduce<void, QList<int>>( - [](const QList<int> &list) -> int { - return; - }); - - auto future = job.exec(); - QVERIFY(future.isFinished()); -#endif -} - - -void AsyncTest::testProgressReporting() -{ - static int progress; - progress = 0; - - auto job = KAsync::start<void>( - [](KAsync::Future<void> &f) { - QTimer *timer = new QTimer(); - connect(timer, &QTimer::timeout, - [&f, timer]() { - f.setProgress(++progress); - if (progress == 100) { - timer->stop(); - timer->deleteLater(); - f.setFinished(); - } - }); - timer->start(1); - }); - - int progressCheck = 0; - KAsync::FutureWatcher<void> watcher; - connect(&watcher, &KAsync::FutureWatcher<void>::futureProgress, - [&progressCheck](qreal progress) { - progressCheck++; - // FIXME: Don't use Q_ASSERT in unit tests - Q_ASSERT((int) progress == progressCheck); - }); - watcher.setFuture(job.exec()); - watcher.future().waitForFinished(); - - QVERIFY(watcher.future().isFinished()); - QCOMPARE(progressCheck, 100); -} - -void AsyncTest::testErrorHandler() -{ - { - auto job = KAsync::start<int>( - [](KAsync::Future<int> &f) { - f.setError(1, QLatin1String("error")); - }); - - auto future = job.exec(); + auto job = KAsync::value<std::vector<int>>({1}); + auto future = job.each<void>([](int i) { + Q_UNUSED(i); + return KAsync::null<void>(); + }).exec(); QVERIFY(future.isFinished()); - QCOMPARE(future.errorCode(), 1); - QCOMPARE(future.errorMessage(), QString::fromLatin1("error")); } - { - int error = 0; - auto job = KAsync::start<int>( - [](KAsync::Future<int> &f) { - f.setError(1, QLatin1String("error")); - }, - [&error](int errorCode, const QString &errorMessage) { - Q_UNUSED(errorMessage); - error += errorCode; - } - ); - - auto future = job.exec(); - QVERIFY(future.isFinished()); - QCOMPARE(error, 1); - QCOMPARE(future.errorCode(), 1); - QCOMPARE(future.errorMessage(), QString::fromLatin1("error")); - } -} + const QList<int> expected({1, 2, 3}); -void AsyncTest::testErrorPropagation() -{ - int error = 0; - bool called = false; - auto job = KAsync::start<int>( - [](KAsync::Future<int> &f) { - f.setError(1, QLatin1String("error")); - }) - .then<int, int>( - [&called](int v, KAsync::Future<int> &f) { - Q_UNUSED(v); - called = true; - f.setFinished(); - }, - [&error](int errorCode, const QString &errorMessage) { - Q_UNUSED(errorMessage); - error += errorCode; - } - ); - auto future = job.exec(); - QVERIFY(future.isFinished()); - QCOMPARE(future.errorCode(), 1); - QCOMPARE(future.errorMessage(), QString::fromLatin1("error")); - QCOMPARE(called, false); - QCOMPARE(error, 1); -} - -void AsyncTest::testErrorHandlerAsync() -{ + auto job = KAsync::value<QList<int>>({1, 2, 3}); { - auto job = KAsync::start<int>( - [](KAsync::Future<int> &f) { - new AsyncSimulator<int>(f, - [](KAsync::Future<int> &f) { - f.setError(1, QLatin1String("error")); - } - ); - } - ); - - auto future = job.exec(); + QList<int> result; + //This is the all manual version + auto subjob = KAsync::forEach<QList<int>>( + KAsync::start<void, int>([&result](int i) { + result << i; + return KAsync::null<void>(); + }) + ); + auto future = job.then<void, QList<int>>( + subjob + ).exec(); future.waitForFinished(); - QVERIFY(future.isFinished()); - QCOMPARE(future.errorCode(), 1); - QCOMPARE(future.errorMessage(), QString::fromLatin1("error")); - } + QCOMPARE(result, expected); + } { - int error = 0; - auto job = KAsync::start<int>( - [](KAsync::Future<int> &f) { - new AsyncSimulator<int>(f, - [](KAsync::Future<int> &f) { - f.setError(1, QLatin1String("error")); - } - ); - }, - [&error](int errorCode, const QString &errorMessage) { - Q_UNUSED(errorMessage); - error += errorCode; - } - ); - - auto future = job.exec(); + QList<int> result; + //An this is the convenience wrapper + auto future = job.each([&result](int i) { + result << i; + return KAsync::null<void>(); + }).exec(); future.waitForFinished(); - QVERIFY(future.isFinished()); - QCOMPARE(error, 1); - QCOMPARE(future.errorCode(), 1); - QCOMPARE(future.errorMessage(), QString::fromLatin1("error")); + QCOMPARE(result, expected); } } -void AsyncTest::testErrorPropagationAsync() -{ - int error = 0; - bool called = false; - auto job = KAsync::start<int>( - [](KAsync::Future<int> &f) { - new AsyncSimulator<int>(f, - [](KAsync::Future<int> &f) { - f.setError(1, QLatin1String("error")); - } - ); - }) - .then<int, int>( - [&called](int v, KAsync::Future<int> &f) { - Q_UNUSED(v); - called = true; - f.setFinished(); - }, - [&error](int errorCode, const QString &errorMessage) { - Q_UNUSED(errorMessage); - error += errorCode; - } - ); - - auto future = job.exec(); - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.errorCode(), 1); - QCOMPARE(future.errorMessage(), QString::fromLatin1("error")); - QCOMPARE(called, false); - QCOMPARE(error, 1); -} - -void AsyncTest::testNestedErrorPropagation() -{ - int error = 0; - auto job = KAsync::start<void>([](){}) - .then<void>(KAsync::error<void>(1, QLatin1String("error"))) //Nested job that throws error - .then<void>([](KAsync::Future<void> &future) { - Q_UNUSED(future); - //We should never get here - Q_ASSERT(false); - }, - [&error](int errorCode, const QString &errorMessage) { - Q_UNUSED(errorMessage); - error += errorCode; - } - ); - auto future = job.exec(); - - future.waitForFinished(); - - QVERIFY(future.isFinished()); - QCOMPARE(future.errorCode(), 1); - QCOMPARE(future.errorMessage(), QString::fromLatin1("error")); - QCOMPARE(error, 1); -} - - - - -void AsyncTest::testChainingRunningJob() -{ - int check = 0; - - auto job = KAsync::start<int>( - [&check](KAsync::Future<int> &future) { - QTimer *timer = new QTimer(); - QObject::connect(timer, &QTimer::timeout, - [&future, &check]() { - ++check; - future.setValue(42); - future.setFinished(); - }); - QObject::connect(timer, &QTimer::timeout, - timer, &QObject::deleteLater); - timer->setSingleShot(true); - timer->start(500); - }); - - auto future1 = job.exec(); - QTest::qWait(200); - - auto job2 = job.then<int, int>( - [&check](int in) -> int { - ++check; - return in * 2; - }); - - auto future2 = job2.exec(); - QVERIFY(!future1.isFinished()); - future2.waitForFinished(); - - QEXPECT_FAIL("", "Chaining new job to a running job no longer executes the new job. " - "This is a trade-off for being able to re-execute single job multiple times.", - Abort); - - QCOMPARE(check, 2); - - QVERIFY(future1.isFinished()); - QVERIFY(future2.isFinished()); - QCOMPARE(future1.value(), 42); - QCOMPARE(future2.value(), 84); -} - -void AsyncTest::testChainingFinishedJob() +void AsyncTest::testAsyncSerialEach() { - int check = 0; - - auto job = KAsync::start<int>( - [&check]() -> int { - ++check; - return 42; - }); - - auto future1 = job.exec(); - QVERIFY(future1.isFinished()); - - auto job2 = job.then<int, int>( - [&check](int in) -> int { - ++check; - return in * 2; - }); - - auto future2 = job2.exec(); - QVERIFY(future2.isFinished()); - - QEXPECT_FAIL("", "Resuming finished job by chaining a new job and calling exec() is no longer suppported. " - "This is a trade-off for being able to re-execute single job multiple times.", - Abort); + { + auto job = KAsync::value<std::vector<int>>({1}); + auto future = job.serialEach<void>([](int i) { + Q_UNUSED(i); + return KAsync::null<void>(); + }).exec(); - QCOMPARE(check, 2); + } - QCOMPARE(future1.value(), 42); - QCOMPARE(future2.value(), 84); -} + const QList<int> expected({1, 2, 3}); -/* - * We want to be able to execute jobs without keeping a handle explicitly alive. - * If the future handle inside the continuation would keep the executor alive, that would probably already work. - */ -void AsyncTest::testLifetimeWithoutHandle() -{ - bool done = false; + auto job = KAsync::value<QList<int>>({1, 2, 3}); { - auto job = KAsync::start<void>([&done](KAsync::Future<void> &future) { - QTimer *timer = new QTimer(); - QObject::connect(timer, &QTimer::timeout, - [&future, &done]() { - done = true; - future.setFinished(); - }); - QObject::connect(timer, &QTimer::timeout, - timer, &QObject::deleteLater); - timer->setSingleShot(true); - timer->start(500); - }); - job.exec(); + QList<int> result; + auto subjob = KAsync::serialForEach<QList<int>>( + KAsync::start<void, int>([&](int i) { + result << i; + return KAsync::null<void>(); + }) + ); + auto future = job.then<void, QList<int>>(subjob).exec(); + future.waitForFinished(); + QVERIFY(future.isFinished()); + QCOMPARE(result, expected); } - - QTRY_VERIFY(done); -} - -/* - * The future handle should keep the executor alive, and the future reference should probably not become invalid inside the continuation, - * until the job is done (alternatively a copy of the future inside the continuation should work as well). - */ -void AsyncTest::testLifetimeWithHandle() -{ - KAsync::Future<void> future; { - auto job = KAsync::start<void>([](KAsync::Future<void> &future) { - QTimer *timer = new QTimer(); - QObject::connect(timer, &QTimer::timeout, - [&future]() { - future.setFinished(); - }); - QObject::connect(timer, &QTimer::timeout, - timer, &QObject::deleteLater); - timer->setSingleShot(true); - timer->start(500); - }); - future = job.exec(); + QList<int> result; + //An this is the convenience wrapper + auto future = job.serialEach([&result](int i) { + result << i; + return KAsync::null<void>(); + }).exec(); + future.waitForFinished(); + QVERIFY(future.isFinished()); + QCOMPARE(result, expected); } - - QTRY_VERIFY(future.isFinished()); } -void AsyncTest::testErrorTask_data() +void AsyncTest::benchmarkSyncThenExecutor() { - QTest::addColumn<bool>("pass"); - QTest::newRow("pass") << true; - QTest::newRow("fail") << false; + auto job = KAsync::syncStart<int>( + []() { + return 1; + }); + + QBENCHMARK { + job.exec(); + } } -void AsyncTest::testErrorTask() +void AsyncTest::benchmarkFutureThenExecutor() { - QFETCH(bool, pass); - - int errorCode = 0; - auto job = KAsync::start<int>( - []() -> int { - return 42; - }) - .then<int, int>( - [pass](int in, KAsync::Future<int> &future) { - if (pass) { - future.setValue(in); - future.setFinished(); - } else { - future.setError(in); - } - }) - .error( - [&errorCode](int error, const QString &) { - errorCode = error; + [](KAsync::Future<int> &f) { + f.setResult(1); }); - KAsync::Future<int> future = job.exec(); - QTRY_VERIFY(future.isFinished()); - if (pass) { - QCOMPARE(future.value(), 42); - QCOMPARE(errorCode, 0); - QCOMPARE(future.errorCode(), 0); - } else { - QCOMPARE(errorCode, 42); - QCOMPARE(future.errorCode(), errorCode); + QBENCHMARK { + job.exec(); } } - -void AsyncTest::benchmarkSyncThenExecutor() +void AsyncTest::benchmarkThenExecutor() { - auto job = KAsync::start<int>( - []() -> int { - return 0; - }); + // auto job = KAsync::start<int>( + // []() { + // return KAsync::value(1); + // }); + + //This is exactly the same as the future version (due to it's implementation). + auto job = KAsync::value(1); QBENCHMARK { job.exec();
View file
kasync-4.99.0.tar.gz/doc/api/Doxyfile.local
Changed
@@ -1,3 +1,4 @@ +PROJECT_NAME = KASync BUILTIN_STL_SUPPORT = YES EXTRACT_ALL = NO EXTRACT_PRIVATE = NO @@ -5,5 +6,6 @@ INLINE_INFO = NO SORT_MEMBER_DOCS = YES FILE_PATTERNS = *.h +RECURSIVE = YES PREDEFINED = ONLY_DOXYGEN \ KASYNC_EXPORT
View file
kasync-4.99.0.tar.gz/src/async.cpp
Changed
@@ -1,5 +1,6 @@ /* * Copyright 2014 Daniel Vrátil <dvratil@redhat.com> + * Copyright 2016 Christian Mollekopf <mollekopf@kolabsystems.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as @@ -27,8 +28,7 @@ Private::Execution::Execution(const Private::ExecutorBasePtr &executor) : executor(executor) , resultBase(nullptr) - , isRunning(false) - , isFinished(false) + , tracer(nullptr) { } @@ -43,13 +43,7 @@ void Private::Execution::setFinished() { - isFinished = true; - //executor.clear(); -#ifndef QT_NO_DEBUG - if (tracer) { - delete tracer; - } -#endif + delete tracer; } void Private::Execution::releaseFuture() @@ -57,19 +51,6 @@ resultBase = 0; } -bool Private::Execution::errorWasHandled() const -{ - Execution *exec = const_cast<Execution*>(this); - while (exec) { - if (exec->executor->hasErrorFunc()) { - return true; - } - exec = exec->prevExecution.data(); - } - return false; -} - - @@ -94,57 +75,32 @@ { } -static void asyncWhile(const std::function<void(std::function<void(bool)>)> &body, const std::function<void()> &completionHandler) { - body([body, completionHandler](bool complete) { - if (complete) { - completionHandler(); - } else { - asyncWhile(body, completionHandler); - } - }); -} -Job<void> KAsync::dowhile(Condition condition, ThenTask<void> body) +Job<void> KAsync::doWhile(Job<ControlFlowFlag> body) { - return KAsync::start<void>([body, condition](KAsync::Future<void> &future) { - asyncWhile([condition, body, &future](std::function<void(bool)> whileCallback) { - KAsync::start<void>(body).then<void>([whileCallback, condition]() { - whileCallback(!condition()); - }, - [&future](int code, const QString &errorMessage) { - future.setError(code, errorMessage); - }).exec(); - }, - [&future]() { //while complete - future.setFinished(); - }); + return body.then<void, ControlFlowFlag>([body](const KAsync::Error &error, ControlFlowFlag flag) { + if (error) { + return KAsync::error(error); + } else if (flag == ControlFlowFlag::Continue) { + return doWhile(body); + } + return KAsync::null(); }); } -Job<void> KAsync::dowhile(ThenTask<bool> body) +Job<void> KAsync::doWhile(JobContinuation<ControlFlowFlag> body) { - return KAsync::start<void>([body](KAsync::Future<void> &future) { - asyncWhile([body](std::function<void(bool)> whileCallback) { - KAsync::start<bool>(body).then<bool, bool>([whileCallback](bool result) { - whileCallback(!result); - //FIXME this return value is only required because .then<bool, void> doesn't work - return true; - }).exec(); - }, - [&future]() { //while complete - future.setFinished(); - }); - }); + return doWhile(KAsync::start<ControlFlowFlag>([body] { + qDebug() << "Calling wrapper"; + return body(); + })); } Job<void> KAsync::wait(int delay) { - auto timer = QSharedPointer<QTimer>::create(); - return KAsync::start<void>([timer, delay](KAsync::Future<void> &future) { - timer->setSingleShot(true); - QObject::connect(timer.data(), &QTimer::timeout, [&future]() { + return KAsync::start<void>([delay](KAsync::Future<void> &future) { + QTimer::singleShot(delay, [&future]() { future.setFinished(); }); - timer->start(delay); }); }
View file
kasync-4.99.0.tar.gz/src/async.h
Changed
@@ -1,5 +1,7 @@ /* * Copyright 2014 - 2015 Daniel Vrátil <dvratil@redhat.com> + * Copyright 2016 Daniel Vrátil <dvratil@kde.org> + * Copyright 2016 Christian Mollekopf <mollekopf@kolabsystems.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as @@ -62,7 +64,6 @@ * * Future: Representation of the result that is being calculated * * - * TODO: Composed progress reporting * TODO: Possibility to abort a job through future (perhaps optional?) * TODO: Support for timeout, specified during exec call, after which the error * handler gets called with a defined errorCode. @@ -82,32 +83,23 @@ class Job; template<typename Out, typename ... In> -using ThenTask = typename detail::identity<std::function<void(In ..., KAsync::Future<Out>&)>>::type; +using HandleContinuation = typename detail::identity<std::function<void(In ..., KAsync::Future<Out>&)>>::type; + +template<typename Out, typename ... In> +using HandleErrorContinuation = typename detail::identity<std::function<void(const KAsync::Error &, In ..., KAsync::Future<Out>&)>>::type; + +template<typename Out, typename ... In> +using SyncContinuation = typename detail::identity<std::function<Out(In ...)>>::type; + +template<typename Out, typename ... In> +using SyncErrorContinuation = typename detail::identity<std::function<Out(const KAsync::Error &, In ...)>>::type; + template<typename Out, typename ... In> -using SyncThenTask = typename detail::identity<std::function<Out(In ...)>>::type; +using JobContinuation = typename detail::identity<std::function<KAsync::Job<Out>(In ...)>>::type; + template<typename Out, typename ... In> -using NestedThenTask = typename detail::identity<std::function<KAsync::Job<Out>(In ...)>>::type; -template<typename Out, typename In> -using EachTask = typename detail::identity<std::function<void(In, KAsync::Future<Out>&)>>::type; -template<typename T, typename Out, typename In> -using MemberEachTask = void(T::*)(In, KAsync::Future<Out>&); -template<typename Out, typename In> -using SyncEachTask = typename detail::identity<std::function<Out(In)>>::type; -template<typename Out, typename In> -using NestedEachTask = typename detail::identity<std::function<KAsync::Job<Out>(In)>>::type; -template<typename T, typename Out, typename In> -using MemberSyncEachTask = Out(T::*)(In); -template<typename Out, typename In> -using ReduceTask = typename detail::identity<std::function<void(In, KAsync::Future<Out>&)>>::type; -template<typename T, typename Out, typename In> -using MemberReduceTask = void(T::*)(In, KAsync::Future<Out> &); -template<typename Out, typename In> -using SyncReduceTask = typename detail::identity<std::function<Out(In)>>::type; -template<typename T, typename Out, typename In> -using MemberSyncReduceTask = Out(T::*)(In); - -using ErrorHandler = std::function<void(int, const QString &)>; -using Condition = std::function<bool()>; +using JobErrorContinuation = typename detail::identity<std::function<KAsync::Job<Out>(const KAsync::Error &, In ...)>>::type; + //@cond PRIVATE namespace Private @@ -128,21 +120,29 @@ } void releaseFuture(); - bool errorWasHandled() const; ExecutorBasePtr executor; FutureBase *resultBase; - bool isRunning; - bool isFinished; ExecutionPtr prevExecution; -#ifndef QT_NO_DEBUG Tracer *tracer; -#endif }; +template<typename Out, typename ... In> +struct ContinuationHelper { + ContinuationHelper(const HandleContinuation<Out, In...> &func) : handleContinuation(func) {}; + ContinuationHelper(const HandleErrorContinuation<Out, In...> &func) : handleErrorContinuation(func) {}; + ContinuationHelper(const JobContinuation<Out, In...> &func) : jobContinuation(func) {}; + ContinuationHelper(const JobErrorContinuation<Out, In...> &func) : jobErrorContinuation(func) {}; + + HandleContinuation<Out, In...> handleContinuation; + HandleErrorContinuation<Out, In...> handleErrorContinuation; + JobContinuation<Out, In...> jobContinuation; + JobErrorContinuation<Out, In...> jobErrorContinuation; +}; + typedef QSharedPointer<Execution> ExecutionPtr; class KASYNC_EXPORT ExecutorBase @@ -153,7 +153,7 @@ template<typename Out, typename ... In> friend class KAsync::Job; - friend class Execution; + friend struct Execution; friend class KAsync::Tracer; public: @@ -166,97 +166,51 @@ template<typename T> KAsync::Future<T>* createFuture(const ExecutionPtr &execution) const; - virtual bool hasErrorFunc() const = 0; - virtual bool handleError(const ExecutionPtr &execution) = 0; - ExecutorBasePtr mPrev; -#ifndef QT_NO_DEBUG + void prepend(const ExecutorBasePtr &e) + { + if (mPrev) { + mPrev->prepend(e); + } else { + mPrev = e; + } + } + + void addToContext(const QVariant &entry) + { + mContext << entry; + } + QString mExecutorName; -#endif + QVector<QVariant> mContext; +}; + +enum ExecutionFlag { + Always, + ErrorCase, + GoodCase }; template<typename PrevOut, typename Out, typename ... In> class Executor : public ExecutorBase { protected: - Executor(ErrorHandler errorFunc, const Private::ExecutorBasePtr &parent) + + Executor(const Private::ExecutorBasePtr &parent, ExecutionFlag executionFlag) : ExecutorBase(parent) - , mErrorFunc(errorFunc) + , executionFlag(executionFlag) {} virtual ~Executor() {} virtual void run(const ExecutionPtr &execution) = 0; ExecutionPtr exec(const ExecutorBasePtr &self); - bool hasErrorFunc() const Q_DECL_OVERRIDE { return (bool) mErrorFunc; } - bool handleError(const ExecutionPtr &execution) Q_DECL_OVERRIDE; - - std::function<void(int, const QString &)> mErrorFunc; -}; - -template<typename Out, typename ... In> -class ThenExecutor: public Executor<typename detail::prevOut<In ...>::type, Out, In ...> -{ -public: - ThenExecutor(ThenTask<Out, In ...> then, ErrorHandler errorFunc, const ExecutorBasePtr &parent); - void run(const ExecutionPtr &execution) Q_DECL_OVERRIDE; -private: - ThenTask<Out, In ...> mFunc; -}; - -template<typename PrevOut, typename Out, typename In> -class EachExecutor : public Executor<PrevOut, Out, In> -{ -public: - EachExecutor(EachTask<Out, In> each, ErrorHandler errorFunc, const ExecutorBasePtr &parent); - void run(const ExecutionPtr &execution) Q_DECL_OVERRIDE; -private: - EachTask<Out, In> mFunc; - QVector<KAsync::FutureWatcher<Out>*> mFutureWatchers; -}; - -template<typename Out, typename In> -class ReduceExecutor : public ThenExecutor<Out, In> -{ -public: - ReduceExecutor(ReduceTask<Out, In> reduce, ErrorHandler errorFunc, const ExecutorBasePtr &parent); -private: - ReduceTask<Out, In> mFunc; -}; - -template<typename Out, typename ... In> -class SyncThenExecutor : public Executor<typename detail::prevOut<In ...>::type, Out, In ...> -{ -public: - SyncThenExecutor(SyncThenTask<Out, In ...> then, ErrorHandler errorFunc, const ExecutorBasePtr &parent); - void run(const ExecutionPtr &execution) Q_DECL_OVERRIDE; - -private: - void run(const ExecutionPtr &execution, std::false_type); // !std::is_void<Out> - void run(const ExecutionPtr &execution, std::true_type); // std::is_void<Out> - SyncThenTask<Out, In ...> mFunc; -}; -template<typename Out, typename In> -class SyncReduceExecutor : public SyncThenExecutor<Out, In> -{ -public: - SyncReduceExecutor(SyncReduceTask<Out, In> reduce, ErrorHandler errorFunc, const ExecutorBasePtr &parent); -private: - SyncReduceTask<Out, In> mFunc; -}; + const ExecutionFlag executionFlag; -template<typename PrevOut, typename Out, typename In> -class SyncEachExecutor : public Executor<PrevOut, Out, In> -{ -public: - SyncEachExecutor(SyncEachTask<Out, In> each, ErrorHandler errorFunc, const ExecutorBasePtr &parent); - void run(const ExecutionPtr &execution) Q_DECL_OVERRIDE; private: - void run(KAsync::Future<Out> *future, const typename PrevOut::value_type &arg, std::false_type); // !std::is_void<Out> - void run(KAsync::Future<Out> *future, const typename PrevOut::value_type &arg, std::true_type); // std::is_void<Out> - SyncEachTask<Out, In> mFunc; + void runExecution(const KAsync::Future<PrevOut> &prevFuture, const ExecutionPtr &execution); }; } // namespace Private @@ -270,118 +224,144 @@ * start() is your starting point to build a chain of jobs to be executed * asynchronously. * - * @param func An asynchronous function to be executed. The function must have - * void return type, and accept all arguments enumerated in - * @p In ... pack in addition to one argument of type @p KAsync::Future<Out>, - * where @p Out is type of the result. - * @param errorFunc An optional error handler. + * @param func A continuation to be executed. + */ +template<typename Out, typename ... In> +KASYNC_EXPORT Job<Out, In ...> start(const HandleContinuation<Out, In ...> &func); +/** + * @relates Job + * + * Start an asynchronous job sequence. + * + * @see start + * + * @param func A continuation to be executed. + */ +template<typename Out, typename ... In> +KASYNC_EXPORT Job<Out, In ...> start(const JobContinuation<Out, In ...> &func); +/** + * @relates Job + * + * Start an asynchronous job sequence. + * + * @see start + * + * @param func A synchronous continuation (doesn't return a job) to be executed. */ template<typename Out, typename ... In> -Job<Out, In ...> start(ThenTask<Out, In ...> func, ErrorHandler errorFunc = ErrorHandler()); +KASYNC_EXPORT Job<Out, In ...> syncStart(const SyncContinuation<Out, In ...> &func); -template<typename T, typename Out, typename ... In> -Job<Out, In ...> start(T *object, typename detail::funcHelper<T, Out, In ...>::type func, ErrorHandler errorFunc = ErrorHandler()); +enum ControlFlowFlag { + Break, + Continue +}; /** * @relates Job * - * An overload of the start() function above. This version expects a synchronous - * task. + * Async while loop. * - * @param func A synchronous task to be executed. The function must have @p Out - * return type and accept all arguments enumerated in the @p In ... - * pack. - * @param errorFunc An optional error handler. + * Loop continues while body returns ControlFlowFlag::Continue. */ -template<typename Out, typename ... In> -Job<Out, In ...> start(SyncThenTask<Out, In ...> func, ErrorHandler errorFunc = ErrorHandler()); +KASYNC_EXPORT Job<void> doWhile(Job<ControlFlowFlag> body); -template<typename Out, typename ... In> -inline typename std::enable_if<!std::is_void<Out>::value, Job<Out, In ...>>::type -start(NestedThenTask<Out, In ...> func, ErrorHandler errorFunc = ErrorHandler()); +/** + * @relates Job + * + * Async while loop. + * + * Shorthand that takes a continuation. + * + * @see doWhile + */ +KASYNC_EXPORT Job<void> doWhile(JobContinuation<ControlFlowFlag> body); -template<typename Out, typename ContOut = Job<void>> -inline typename std::enable_if<std::is_void<Out>::value, Job<void>>::type -start(NestedThenTask<void> func, ErrorHandler errorFunc = ErrorHandler()); -template<typename T, typename Out, typename ... In> -Job<Out, In ...> start(T *object, typename detail::syncFuncHelper<T, Out, In ...>::type func, ErrorHandler errorFunc = ErrorHandler()); /** * @relates Job - * An overload of the start() function above. This version is specialized to work - * with KJob. * - * The KJob is described in the template, as shown in the example below and will - * be actually instantiated when the entire chain is started. + * Async delay. + */ +KASYNC_EXPORT Job<void> wait(int delay); + +/** + * @relates Job * - * @code - * class MyJob : public KJob { - * Q_OBJECT - * public: - * MyJob(const QString &in, QObject *parent = Q_NULLPTR); - * virtual ~MyJob(); + * A null job. * - * int getResult() const; + * An async noop. * - * ... - * ... - * }; + */ +template<typename Out = void> +KASYNC_EXPORT Job<Out> null(); + +/** + * @relates Job * - * auto job = Job::start<int, MyJob, &MyJob::getResult, QString>(); - * Future<int> future = job.start("Input"); - * ... - * @endcode - **/ -template<typename ReturnType, typename KJobType, ReturnType (KJobType::*KJobResultMethod)(), typename ... Args> -typename std::enable_if<std::is_base_of<KJob, KJobType>::value, Job<ReturnType, Args ...>>::type -start(); + * Async value. + */ +template<typename Out> +KASYNC_EXPORT Job<Out> value(Out); /** * @relates Job * - * Async while loop. + * Async foreach loop. * - * The loop continues while @param condition returns true. + * This will execute a job for every value in the list. + * Errors while not stop processing of other jobs but set an error on the wrapper job. */ -KASYNC_EXPORT Job<void> dowhile(Condition condition, ThenTask<void> func); +template<typename List, typename ValueType = typename List::value_type> +KASYNC_EXPORT Job<void, List> forEach(KAsync::Job<void, ValueType> job); /** * @relates Job * - * Async while loop. + * Async foreach loop. + * + * Shorthand that takes a continuation. * - * Loop continues while body returns true. + * @see serialForEach */ -KASYNC_EXPORT Job<void> dowhile(ThenTask<bool> body); +template<typename List> +KASYNC_EXPORT Job<void, List> forEach(JobContinuation<void, typename List::value_type>); + /** * @relates Job * - * Iterate over a container. + * Serial Async foreach loop. * - * Use in conjunction with .each + * This will execute a job for every value in the list sequentially. + * Errors while not stop processing of other jobs but set an error on the wrapper job. */ -template<typename Out> -Job<Out> iterate(const Out &container); +template<typename List, typename ValueType = typename List::value_type> +KASYNC_EXPORT Job<void, List> serialForEach(KAsync::Job<void, ValueType> job); /** * @relates Job * - * Async delay. + * Serial Async foreach loop. + * + * Shorthand that takes a continuation. + * + * @see serialForEach */ -KASYNC_EXPORT Job<void> wait(int delay); +template<typename List> +KASYNC_EXPORT Job<void, List> serialForEach(JobContinuation<void, typename List::value_type>); + /** * @relates Job * - * A null job. + * An error job. * - * An async noop. + * An async error. * */ -template<typename Out> -Job<Out> null(); +template<typename Out = void> +KASYNC_EXPORT Job<Out> error(int errorCode = 1, const QString &errorMessage = QString()); /** * @relates Job @@ -391,8 +371,8 @@ * An async error. * */ -template<typename Out> -Job<Out> error(int errorCode = 1, const QString &errorMessage = QString()); +template<typename Out = void> +KASYNC_EXPORT Job<Out> error(const Error &); //@cond PRIVATE class KASYNC_EXPORT JobBase @@ -459,88 +439,96 @@ friend class Job; template<typename OutOther, typename ... InOther> - friend Job<OutOther, InOther ...> start(KAsync::ThenTask<OutOther, InOther ...> func, ErrorHandler errorFunc); + friend Job<OutOther, InOther ...> startImpl(const Private::ContinuationHelper<OutOther, InOther ...> &); - template<typename T, typename OutOther, typename ... InOther> - friend Job<OutOther, InOther ...> start(T *object, typename detail::funcHelper<T, OutOther, InOther ...>::type func, ErrorHandler errorFunc); + template<typename List, typename ValueType> + friend Job<void, List> forEach(KAsync::Job<void, ValueType> job); - template<typename OutOther, typename ... InOther> - friend Job<OutOther, InOther ...> start(KAsync::SyncThenTask<OutOther, InOther ...> func, ErrorHandler errorFunc); + template<typename List, typename ValueType> + friend Job<void, List> serialForEach(KAsync::Job<void, ValueType> job); - template<typename T, typename OutOther, typename ... InOther> - friend Job<OutOther, InOther ...> start(T *object, typename detail::syncFuncHelper<T, OutOther, InOther ...>::type func, ErrorHandler errorFunc); + template<typename OutOther, typename ... InOther> + friend Job<OutOther, InOther ...> syncStart(const SyncContinuation<OutOther, InOther ...> &func); - template<typename ReturnType, typename KJobType, ReturnType (KJobType::*KJobResultMethod)(), typename ... Args> - friend typename std::enable_if<std::is_base_of<KJob, KJobType>::value, Job<ReturnType, Args ...>>::type start(); //@endcond public: + /// A continuation template<typename OutOther, typename ... InOther> - Job<OutOther, InOther ...> then(ThenTask<OutOther, InOther ...> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename T, typename OutOther, typename ... InOther> - typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther ...>>::type - then(T *object, typename detail::funcHelper<T, OutOther, InOther ...>::type func, ErrorHandler errorFunc = ErrorHandler()); + Job<OutOther, In ...> then(const Job<OutOther, InOther ...> &job) const; + ///Shorthand for a job that returns another job from it's continuation template<typename OutOther, typename ... InOther> - Job<OutOther, InOther ...> then(SyncThenTask<OutOther, InOther ...> func, ErrorHandler errorFunc = ErrorHandler()); + Job<OutOther, In ...> then(JobContinuation<OutOther, InOther ...> func) const + { + return thenImpl<OutOther, InOther ...>({func}, Private::ExecutionFlag::GoodCase); + } + ///Shorthand for a job that receives the error and a handle template<typename OutOther, typename ... InOther> - inline typename std::enable_if<!std::is_void<OutOther>::value, Job<OutOther, InOther ...>>::type - then(NestedThenTask<OutOther, InOther ...> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename OutOther, typename ContOut, typename ... InOther> - inline typename std::enable_if<std::is_void<OutOther>::value, Job<OutOther, InOther ...>>::type - then(NestedThenTask<void, InOther ...> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename T, typename OutOther, typename ... InOther> - typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther ...>>::type - then(T *object, typename detail::syncFuncHelper<T, OutOther, InOther ...>::type func, ErrorHandler errorFunc = ErrorHandler()); + Job<OutOther, In ...> then(HandleContinuation<OutOther, InOther ...> func) const + { + return thenImpl<OutOther, InOther ...>({func}, Private::ExecutionFlag::GoodCase); + } + ///Shorthand for a job that receives the error and a handle template<typename OutOther, typename ... InOther> - Job<OutOther, InOther ...> then(Job<OutOther, InOther ...> otherJob, ErrorHandler errorFunc = ErrorHandler()); - - template<typename ReturnType, typename KJobType, ReturnType (KJobType::*KJobResultMethod)(), typename ... Args> - typename std::enable_if<std::is_base_of<KJob, KJobType>::value, Job<ReturnType, Args ...>>::type then(); - - template<typename OutOther, typename InOther> - Job<OutOther, InOther> each(EachTask<OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename T, typename OutOther, typename InOther> - typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther>>::type - each(T *object, MemberEachTask<T, OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename OutOther, typename InOther> - Job<OutOther, InOther> each(SyncEachTask<OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename OutOther, typename InOther> - Job<OutOther, InOther> each(NestedEachTask<OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename T, typename OutOther, typename InOther> - typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther>>::type - each(T *object, MemberSyncEachTask<T, OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); - - template<typename OutOther, typename InOther> - Job<OutOther, InOther> each(Job<OutOther, InOther> otherJob, ErrorHandler errorFunc = ErrorHandler()); + Job<OutOther, In ...> then(HandleErrorContinuation<OutOther, InOther ...> func) const + { + return thenImpl<OutOther, InOther ...>({func}, Private::ExecutionFlag::Always); + } - template<typename OutOther, typename InOther> - Job<OutOther, InOther> reduce(ReduceTask<OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); + /* + * Shorthand for a job that that returns another job from its contiuation + * and that receives the error. + */ + template<typename OutOther, typename ... InOther> + Job<OutOther, In ...> then(JobErrorContinuation<OutOther, InOther ...> func) const + { + return thenImpl<OutOther, InOther ...>({func}, Private::ExecutionFlag::Always); + } - template<typename T, typename OutOther, typename InOther> - typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther>>::type - reduce(T *object, MemberReduceTask<T, OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); + ///Shorthand for a synchronous job (a job that does not return another job). + template<typename OutOther, typename ... InOther> + Job<OutOther, In ...> syncThen(const SyncContinuation<OutOther, InOther ...> &func) const + { + return syncThenImpl<OutOther, InOther ...>(func, Private::ExecutionFlag::GoodCase); + } - template<typename OutOther, typename InOther> - Job<OutOther, InOther> reduce(SyncReduceTask<OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); + /** + * Shorthand for a synchronous job (a job that does not return another job) + * that receives the error. + */ + template<typename OutOther, typename ... InOther> + Job<OutOther, In ...> syncThen(const SyncErrorContinuation<OutOther, InOther ...> &func) const + { + return syncThenImpl<OutOther, InOther ...>(func, Private::ExecutionFlag::Always); + } - template<typename T, typename OutOther, typename InOther> - typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther>>::type - reduce(T *object, MemberSyncReduceTask<T, OutOther, InOther> func, ErrorHandler errorFunc = ErrorHandler()); + ///Shorthand for a job that receives the error only + Job<Out, In ...> onError(const SyncErrorContinuation<void> &errorFunc) const; - template<typename OutOther, typename InOther> - Job<OutOther, InOther> reduce(Job<OutOther, InOther> otherJob, ErrorHandler errorFunc = ErrorHandler()); + /** + * Shorthand for a forEach loop that automatically uses the return type of + * this job to deduce the type exepected. + */ + template<typename OutOther = void, typename ListType = Out, typename ValueType = typename ListType::value_type, typename std::enable_if<!std::is_void<ListType>::value, int>::type = 0> + Job<void, In ...> each(JobContinuation<void, ValueType> func) const + { + eachInvariants<OutOther>(); + return then<void, In ...>(forEach<Out, ValueType>(func)); + } - Job<Out, Out> error(ErrorHandler errorFunc = ErrorHandler()); + /** + * Shorthand for a serialForEach loop that automatically uses the return type + * of this job to deduce the type exepected. + */ + template<typename OutOther = void, typename ListType = Out, typename ValueType = typename ListType::value_type, typename std::enable_if<!std::is_void<ListType>::value, int>::type = 0> + Job<void, In ...> serialEach(JobContinuation<void, ValueType> func) const + { + eachInvariants<OutOther>(); + return then<void, In ...>(serialForEach<Out, ValueType>(func)); + } /** * Enable implicit conversion to Job<void>. @@ -551,8 +539,22 @@ * .then<void, int>( ... ) * .then<void>([](){}); //Necessary for the assignment without the implicit conversion */ - operator Job<void>() { - return then<void>([](){}); + template<typename ... InOther> + operator Job<void>(); + + /** + * Adds an unnamed value to the context. + * The context is guaranteed to persist until the jobs execution has finished. + * + * Useful for setting smart pointer to manage lifetime of objects required + * during the execution of the job. + */ + template<typename T> + Job<Out, In ...> &addToContext(const T &value) + { + assert(mExecutor); + mExecutor->addToContext(QVariant::fromValue<T>(value)); + return *this; } /** @@ -587,35 +589,34 @@ */ KAsync::Future<Out> exec(); + explicit Job(const JobContinuation<Out, In ...> &func); + explicit Job(const HandleContinuation<Out, In ...> &func); + private: //@cond PRIVATE explicit Job(Private::ExecutorBasePtr executor); - template<typename OutOther> - void eachInvariants(); - - template<typename InOther> - void reduceInvariants(); - template<typename OutOther, typename ... InOther> - inline std::function<void(InOther ..., KAsync::Future<OutOther>&)> nestedJobWrapper(Job<OutOther, InOther ...> otherJob); + Job<OutOther, In ...> thenImpl(const Private::ContinuationHelper<OutOther, InOther ...> &helper, + Private::ExecutionFlag execFlag = Private::ExecutionFlag::GoodCase) const; template<typename OutOther, typename ... InOther> - inline std::function<void(InOther ..., KAsync::Future<OutOther>&)> nestedJobWrapper(std::function<Job<OutOther>(InOther ...)> otherJob); - - template<typename OutOther, typename InOther> - inline std::function<void(InOther, KAsync::Future<OutOther>&)> nestedJobWrapper(std::function<Job<OutOther>(InOther)> otherJob); + Job<OutOther, In ...> syncThenImpl(const SyncContinuation<OutOther, InOther ...> &func, + Private::ExecutionFlag execFlag = Private::ExecutionFlag::GoodCase) const; + template<typename OutOther, typename ... InOther> + Job<OutOther, In ...> syncThenImpl(const SyncErrorContinuation<OutOther, InOther ...> &func, + Private::ExecutionFlag execFlag = Private::ExecutionFlag::Always) const; - template<typename Task, typename T, typename OutOther, typename ... InOther> - inline Task memberFuncWrapper(T *object, typename detail::funcHelper<T, OutOther, InOther ...>::type func); + template<typename InOther, typename ... InOtherTail> + void thenInvariants() const; - template<typename Task, typename T, typename OutOther, typename ... InOther> - inline typename std::enable_if<!std::is_void<OutOther>::value, Task>::type - memberFuncWrapper(T *object, typename detail::syncFuncHelper<T, OutOther, InOther ...>::type func); + //Base case for an empty parameter pack + template<typename ... InOther> + typename std::enable_if<(sizeof...(InOther) == 0)>::type + thenInvariants() const; - template<typename Task, typename T, typename OutOther, typename ... InOther> - inline typename std::enable_if<std::is_void<OutOther>::value, Task>::type - memberFuncWrapper(T *object, typename detail::syncFuncHelper<T, void, InOther ...>::type func, int */*disambiguation*/ = 0); + template<typename OutOther> + void eachInvariants() const; //@endcond }; @@ -625,389 +626,4 @@ // out-of-line definitions of Job methods #include "job_impl.h" -// ********** Out of line definitions **************** -//@cond PRIVATE -namespace KAsync { - -template<typename Out, typename ... In> -Job<Out, In ...> start(ThenTask<Out, In ...> func, ErrorHandler error) -{ - return Job<Out, In...>(Private::ExecutorBasePtr( - new Private::ThenExecutor<Out, In ...>(func, error, Private::ExecutorBasePtr()))); -} - -template<typename Out, typename ... In> -Job<Out, In ...> start(SyncThenTask<Out, In ...> func, ErrorHandler error) -{ - return Job<Out, In...>(Private::ExecutorBasePtr( - new Private::SyncThenExecutor<Out, In ...>(func, error, Private::ExecutorBasePtr()))); -} - -template<typename Out, typename ... In> -inline typename std::enable_if<!std::is_void<Out>::value, Job<Out, In ...>>::type -start(NestedThenTask<Out, In ...> func, ErrorHandler error) -{ - return start<Out, In...>([func](In ... in, KAsync::Future<Out> &future){ - auto job = func(in ...); - FutureWatcher<Out> *watcher = new FutureWatcher<Out>(); - QObject::connect(watcher, &FutureWatcherBase::futureReady, - [watcher, future]() { - // FIXME: We pass future by value, because using reference causes the - // future to get deleted before this lambda is invoked, leading to crash - // in copyFutureValue() - // copy by value is const - auto outFuture = future; - KAsync::detail::copyFutureValue(watcher->future(), outFuture); - if (watcher->future().errorCode()) { - outFuture.setError(watcher->future().errorCode(), watcher->future().errorMessage()); - } else { - outFuture.setFinished(); - } - delete watcher; - }); - watcher->setFuture(job.exec(in ...)); - }, error); -} - -template<typename Out, typename ContOut> -inline typename std::enable_if<std::is_void<Out>::value, Job<void>>::type -start(NestedThenTask<void> func, ErrorHandler error) -{ - return start<void>([func](KAsync::Future<void> &future){ - auto job = func(); - FutureWatcher<void> *watcher = new FutureWatcher<void>(); - QObject::connect(watcher, &FutureWatcherBase::futureReady, - [watcher, future]() { - // FIXME: We pass future by value, because using reference causes the - // future to get deleted before this lambda is invoked, leading to crash - // in copyFutureValue() - // copy by value is const - auto outFuture = future; - KAsync::detail::copyFutureValue(watcher->future(), outFuture); - if (watcher->future().errorCode()) { - outFuture.setError(watcher->future().errorCode(), watcher->future().errorMessage()); - } else { - outFuture.setFinished(); - } - delete watcher; - }); - watcher->setFuture(job.exec()); - }, error); -} - -template<typename ReturnType, typename KJobType, ReturnType (KJobType::*KJobResultMethod)(), typename ... Args> -typename std::enable_if<std::is_base_of<KJob, KJobType>::value, Job<ReturnType, Args ...>>::type -start() -{ - return Job<ReturnType, Args ...>(Private::ExecutorBasePtr( - new Private::ThenExecutor<ReturnType, Args ...>([](const Args & ... args, KAsync::Future<ReturnType> &future) - { - KJobType *job = new KJobType(args ...); - job->connect(job, &KJobType::finished, - [&future](KJob *job) { - KJobType *imp = qobject_cast<KJobType*>(job); - if (imp->error()) { - future.setError(imp->error(), imp->errorString()); - } else { - future.setValue((imp->*KJobResultMethod)()); - future.setFinished(); - } - }); - job->start(); - }, ErrorHandler(), Private::ExecutorBasePtr()))); -} - - -template<typename Out> -Job<Out> null() -{ - return KAsync::start<Out>( - [](KAsync::Future<Out> &future) { - future.setFinished(); - }); -} - -template<typename Out> -Job<Out> error(int errorCode, const QString &errorMessage) -{ - return KAsync::start<Out>( - [errorCode, errorMessage](KAsync::Future<Out> &future) { - future.setError(errorCode, errorMessage); - }); -} - -template<typename Out> -Job<Out> iterate(const Out &container) -{ - return KAsync::start<Out>( - [container]() { - return container; - }); -} - - -namespace Private { - -template<typename T> -KAsync::Future<T>* ExecutorBase::createFuture(const ExecutionPtr &execution) const -{ - return new KAsync::Future<T>(execution); -} - -template<typename PrevOut, typename Out, typename ... In> -ExecutionPtr Executor<PrevOut, Out, In ...>::exec(const ExecutorBasePtr &self) -{ - // Passing 'self' to execution ensures that the Executor chain remains - // valid until the entire execution is finished - ExecutionPtr execution = ExecutionPtr::create(self); -#ifndef QT_NO_DEBUG - execution->tracer = new Tracer(execution.data()); // owned by execution -#endif - - // chainup - execution->prevExecution = mPrev ? mPrev->exec(mPrev) : ExecutionPtr(); - - execution->resultBase = ExecutorBase::createFuture<Out>(execution); - auto fw = new KAsync::FutureWatcher<Out>(); - QObject::connect(fw, &KAsync::FutureWatcher<Out>::futureReady, - [fw, execution, this]() { - handleError(execution); - execution->setFinished(); - delete fw; - }); - fw->setFuture(*execution->result<Out>()); - - KAsync::Future<PrevOut> *prevFuture = execution->prevExecution ? execution->prevExecution->result<PrevOut>() : nullptr; - if (!prevFuture || prevFuture->isFinished()) { - if (prevFuture) { // prevFuture implies execution->prevExecution - if (prevFuture->errorCode()) { - // Propagate the errorCode and message to the outer Future - execution->resultBase->setError(prevFuture->errorCode(), prevFuture->errorMessage()); - if (!execution->errorWasHandled()) { - if (handleError(execution)) { - return execution; - } - } else { - return execution; - } - } else { - // Propagate error (if any) - } - } - - execution->isRunning = true; - run(execution); - } else { - auto prevFutureWatcher = new KAsync::FutureWatcher<PrevOut>(); - QObject::connect(prevFutureWatcher, &KAsync::FutureWatcher<PrevOut>::futureReady, - [prevFutureWatcher, execution, this]() { - auto prevFuture = prevFutureWatcher->future(); - assert(prevFuture.isFinished()); - delete prevFutureWatcher; - auto prevExecutor = execution->executor->mPrev; - if (prevFuture.errorCode()) { - execution->resultBase->setError(prevFuture.errorCode(), prevFuture.errorMessage()); - if (!execution->errorWasHandled()) { - if (handleError(execution)) { - return; - } - } else { - return; - } - } - - - // propagate error (if any) - execution->isRunning = true; - run(execution); - }); - - prevFutureWatcher->setFuture(*static_cast<KAsync::Future<PrevOut>*>(prevFuture)); - } - - return execution; -} - -template<typename PrevOut, typename Out, typename ... In> -bool Executor<PrevOut, Out, In ...>::handleError(const ExecutionPtr &execution) -{ - assert(execution->resultBase->isFinished()); - if (execution->resultBase->errorCode()) { - if (mErrorFunc) { - mErrorFunc(execution->resultBase->errorCode(), - execution->resultBase->errorMessage()); - return true; - } - } - - return false; -} - - -template<typename Out, typename ... In> -ThenExecutor<Out, In ...>::ThenExecutor(ThenTask<Out, In ...> then, ErrorHandler error, const ExecutorBasePtr &parent) - : Executor<typename detail::prevOut<In ...>::type, Out, In ...>(error, parent) - , mFunc(then) -{ - STORE_EXECUTOR_NAME("ThenExecutor", Out, In ...); -} - -template<typename Out, typename ... In> -void ThenExecutor<Out, In ...>::run(const ExecutionPtr &execution) -{ - KAsync::Future<typename detail::prevOut<In ...>::type> *prevFuture = nullptr; - if (execution->prevExecution) { - prevFuture = execution->prevExecution->result<typename detail::prevOut<In ...>::type>(); - assert(prevFuture->isFinished()); - } - - ThenExecutor<Out, In ...>::mFunc(prevFuture ? prevFuture->value() : In() ..., *execution->result<Out>()); -} - -template<typename PrevOut, typename Out, typename In> -EachExecutor<PrevOut, Out, In>::EachExecutor(EachTask<Out, In> each, ErrorHandler error, const ExecutorBasePtr &parent) - : Executor<PrevOut, Out, In>(error, parent) - , mFunc(each) -{ - STORE_EXECUTOR_NAME("EachExecutor", PrevOut, Out, In); -} - -template<typename PrevOut, typename Out, typename In> -void EachExecutor<PrevOut, Out, In>::run(const ExecutionPtr &execution) -{ - assert(execution->prevExecution); - auto prevFuture = execution->prevExecution->result<PrevOut>(); - assert(prevFuture->isFinished()); - - auto out = execution->result<Out>(); - if (prevFuture->value().isEmpty()) { - out->setFinished(); - return; - } - - for (auto arg : prevFuture->value()) { - //We have to manually manage the lifetime of these temporary futures - KAsync::Future<Out> *future = new KAsync::Future<Out>(); - EachExecutor<PrevOut, Out, In>::mFunc(arg, *future); - auto fw = new KAsync::FutureWatcher<Out>(); - mFutureWatchers.append(fw); - QObject::connect(fw, &KAsync::FutureWatcher<Out>::futureReady, - [out, fw, this, future]() { - assert(fw->future().isFinished()); - const int index = mFutureWatchers.indexOf(fw); - assert(index > -1); - mFutureWatchers.removeAt(index); - KAsync::detail::aggregateFutureValue<Out>(fw->future(), *out); - if (mFutureWatchers.isEmpty()) { - out->setFinished(); - } - delete fw; - delete future; - }); - fw->setFuture(*future); - } -} - -template<typename Out, typename In> -ReduceExecutor<Out, In>::ReduceExecutor(ReduceTask<Out, In> reduce, ErrorHandler errorFunc, const ExecutorBasePtr &parent) - : ThenExecutor<Out, In>(reduce, errorFunc, parent) -{ - STORE_EXECUTOR_NAME("ReduceExecutor", Out, In); -} - -template<typename Out, typename ... In> -SyncThenExecutor<Out, In ...>::SyncThenExecutor(SyncThenTask<Out, In ...> then, ErrorHandler errorFunc, const ExecutorBasePtr &parent) - : Executor<typename detail::prevOut<In ...>::type, Out, In ...>(errorFunc, parent) - , mFunc(then) -{ - STORE_EXECUTOR_NAME("SyncThenExecutor", Out, In ...); -} - -template<typename Out, typename ... In> -void SyncThenExecutor<Out, In ...>::run(const ExecutionPtr &execution) -{ - if (execution->prevExecution) { - assert(execution->prevExecution->resultBase->isFinished()); - } - - run(execution, std::is_void<Out>()); - execution->resultBase->setFinished(); -} - -template<typename Out, typename ... In> -void SyncThenExecutor<Out, In ...>::run(const ExecutionPtr &execution, std::false_type) -{ - KAsync::Future<typename detail::prevOut<In ...>::type> *prevFuture = - execution->prevExecution - ? execution->prevExecution->result<typename detail::prevOut<In ...>::type>() - : nullptr; - (void) prevFuture; // silence 'set but not used' warning - KAsync::Future<Out> *future = execution->result<Out>(); - future->setValue(SyncThenExecutor<Out, In...>::mFunc(prevFuture ? prevFuture->value() : In() ...)); -} - -template<typename Out, typename ... In> -void SyncThenExecutor<Out, In ...>::run(const ExecutionPtr &execution, std::true_type) -{ - KAsync::Future<typename detail::prevOut<In ...>::type> *prevFuture = - execution->prevExecution - ? execution->prevExecution->result<typename detail::prevOut<In ...>::type>() - : nullptr; - (void) prevFuture; // silence 'set but not used' warning - SyncThenExecutor<Out, In ...>::mFunc(prevFuture ? prevFuture->value() : In() ...); -} - -template<typename PrevOut, typename Out, typename In> -SyncEachExecutor<PrevOut, Out, In>::SyncEachExecutor(SyncEachTask<Out, In> each, ErrorHandler errorFunc, const ExecutorBasePtr &parent) - : Executor<PrevOut, Out, In>(errorFunc, parent) - , mFunc(each) -{ - STORE_EXECUTOR_NAME("SyncEachExecutor", PrevOut, Out, In); -} - -template<typename PrevOut, typename Out, typename In> -void SyncEachExecutor<PrevOut, Out, In>::run(const ExecutionPtr &execution) -{ - assert(execution->prevExecution); - auto *prevFuture = execution->prevExecution->result<PrevOut>(); - assert(prevFuture->isFinished()); - - auto out = execution->result<Out>(); - if (prevFuture->value().isEmpty()) { - out->setFinished(); - return; - } - - for (auto arg : prevFuture->value()) { - run(out, arg, std::is_void<Out>()); - } - out->setFinished(); -} - -template<typename PrevOut, typename Out, typename In> -void SyncEachExecutor<PrevOut, Out, In>::run(KAsync::Future<Out> *out, const typename PrevOut::value_type &arg, std::false_type) -{ - out->setValue(out->value() + SyncEachExecutor<PrevOut, Out, In>::mFunc(arg)); -} - -template<typename PrevOut, typename Out, typename In> -void SyncEachExecutor<PrevOut, Out, In>::run(KAsync::Future<Out> * /* unused */, const typename PrevOut::value_type &arg, std::true_type) -{ - SyncEachExecutor<PrevOut, Out, In>::mFunc(arg); -} - -template<typename Out, typename In> -SyncReduceExecutor<Out, In>::SyncReduceExecutor(SyncReduceTask<Out, In> reduce, ErrorHandler errorFunc, const ExecutorBasePtr &parent) - : SyncThenExecutor<Out, In>(reduce, errorFunc, parent) -{ - STORE_EXECUTOR_NAME("SyncReduceExecutor", Out, In); -} - - -} // namespace Private - -} // namespace KAsync -//@endcond - - #endif // KASYNC_H
View file
kasync-4.99.0.tar.gz/src/async_impl.h
Changed
@@ -1,5 +1,6 @@ /* * Copyright 2014 - 2015 Daniel Vrátil <dvratil@redhat.com> + * Copyright 2016 Daniel Vrátil <dvratil@kde.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as
View file
kasync-4.99.0.tar.gz/src/debug.cpp
Changed
@@ -66,10 +66,8 @@ void Tracer::msg(Tracer::MsgType msgType) { -#ifndef QT_NO_DEBUG qCDebug(Trace).nospace() << (QString().fill(QLatin1Char(' '), mId * 2) % (msgType == KAsync::Tracer::Start ? QStringLiteral(" START ") : QStringLiteral(" END ")) % QString::number(mId) % QStringLiteral(" ") % mExecution->executor->mExecutorName); -#endif }
View file
kasync-4.99.0.tar.gz/src/debug.h
Changed
@@ -39,7 +39,7 @@ namespace Private { -class Execution; +struct Execution; } class KASYNC_EXPORT Tracer
View file
kasync-4.99.0.tar.gz/src/future.cpp
Changed
@@ -22,7 +22,6 @@ FutureBase::PrivateBase::PrivateBase(const Private::ExecutionPtr &execution) : finished(false) - , errorCode(0) , mExecution(execution) { } @@ -73,6 +72,7 @@ return; } d->finished = true; + //TODO this could directly call the next continuation with the value, and thus avoid unnecessary copying. for (auto watcher : d->watchers) { if (watcher) { watcher->futureReadyCallback(); @@ -87,19 +87,52 @@ void FutureBase::setError(int code, const QString &message) { - d->errorCode = code; - d->errorMessage = message; + d->errors.clear(); + addError(Error(code, message)); setFinished(); } +void FutureBase::setError(const Error &error) +{ + d->errors.clear(); + addError(error); + setFinished(); +} + +void FutureBase::addError(const Error &error) +{ + d->errors << error; +} + +void FutureBase::clearErrors() +{ + d->errors.clear(); +} + +bool FutureBase::hasError() const +{ + return !d->errors.isEmpty(); +} + int FutureBase::errorCode() const { - return d->errorCode; + if (d->errors.isEmpty()) { + return 0; + } + return d->errors.first().errorCode; } QString FutureBase::errorMessage() const { - return d->errorMessage; + if (d->errors.isEmpty()) { + return QString(); + } + return d->errors.first().errorMessage; +} + +QVector<Error> FutureBase::errors() const +{ + return d->errors; } void FutureBase::setProgress(int processed, int total)
View file
kasync-4.99.0.tar.gz/src/future.h
Changed
@@ -1,5 +1,6 @@ /* * Copyright 2014 Daniel Vrátil <dvratil@redhat.com> + * Copyright 2016 Daniel Vrátil <dvratil@kde.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as @@ -38,12 +39,41 @@ class FutureWatcher; namespace Private { -class Execution; +struct Execution; class ExecutorBase; typedef QSharedPointer<Execution> ExecutionPtr; } // namespace Private +struct KASYNC_EXPORT Error +{ + Error() : errorCode(0) {}; + explicit Error(const char *message) : errorCode(1), errorMessage(QString::fromLatin1(message)) {} + Error(int code, const char *message) : errorCode(code), errorMessage(QString::fromLatin1(message)) {} + Error(int code, const QString &message) : errorCode(code), errorMessage(message) {} + + bool operator ==(const Error &other) const { + return (errorCode == other.errorCode) && (errorMessage == other.errorMessage); + } + + bool operator !=(const Error &other) const { + return !(*this == other); + } + + operator bool() const { + return (errorCode != 0); + } + + int errorCode; + QString errorMessage; +private: + //Disable all implicit conversions except to bool, to avoid accidentally implicitly casting an error to a continuation argument. + //This becomes an issue if you forget to specify all template arguments, as the template argument deduction may employ a nonsensical implicit conversion from i.e. error to int. So as long as the Error object is used in the Job::then overload resolution no implicit conversions here. + //Of course this "solution" still breaks if you forget the template argument with a boolean parameter.... + template <typename T> + operator T() const; +}; + class KASYNC_EXPORT FutureBase { friend class KAsync::Private::Execution; @@ -57,8 +87,13 @@ bool isFinished() const; void setError(int code = 1, const QString &message = QString()); + void setError(const Error &error); + void addError(const Error &error); + void clearErrors(); + bool hasError() const; int errorCode() const; QString errorMessage() const; + QVector<Error> errors() const; void setProgress(qreal progress); void setProgress(int processed, int total); @@ -73,8 +108,7 @@ void releaseExecution(); bool finished; - int errorCode; - QString errorMessage; + QVector<Error> errors; QVector<QPointer<FutureWatcherBase>> watchers; private: @@ -294,6 +328,12 @@ #endif // ONLY_DOXYGEN + void setResult(const T &value) + { + static_cast<typename FutureGeneric<T>::Private*>(this->d.data())->value = value; + FutureBase::setFinished(); + } + protected: //@cond PRIVATE Future(const KAsync::Private::ExecutionPtr &execution)
View file
kasync-4.99.0.tar.gz/src/job_impl.h
Changed
@@ -1,6 +1,7 @@ /* * Copyright 2014 - 2015 Daniel Vrátil <dvratil@redhat.com> - * Copyright 2015 Daniel Vrátil <dvratil@kde.org> + * Copyright 2015 - 2016 Daniel Vrátil <dvratil@kde.org> + * Copyright 2016 Christian Mollekopf <mollekopf@kolabsystems.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License as @@ -26,212 +27,352 @@ namespace KAsync { +namespace Private { + template<typename Out, typename ... In> -template<typename OutOther, typename ... InOther> -Job<OutOther, InOther ...> Job<Out, In ...>::then(ThenTask<OutOther, InOther ...> func, - ErrorHandler errorFunc) +class ThenExecutor: public Executor<typename detail::prevOut<In ...>::type, Out, In ...> { - return Job<OutOther, InOther ...>(Private::ExecutorBasePtr( - new Private::ThenExecutor<OutOther, InOther ...>(func, errorFunc, mExecutor))); -} +public: + ThenExecutor(ContinuationHelper<Out, In ...> workerHelper, const ExecutorBasePtr &parent = {}, + ExecutionFlag executionFlag = ExecutionFlag::GoodCase) + : Executor<typename detail::prevOut<In ...>::type, Out, In ...>(parent, executionFlag) + , mContinuationHelper(workerHelper) + { + STORE_EXECUTOR_NAME("ThenExecutor", Out, In ...); + } -template<typename Out, typename ... In> -template<typename T, typename OutOther, typename ... InOther> -typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther ...>>::type -Job<Out, In ...>::then(T *object, - typename detail::funcHelper<T, OutOther, InOther ...>::type func, - ErrorHandler errorFunc) -{ - return Job<OutOther, InOther ...>(Private::ExecutorBasePtr( - new Private::ThenExecutor<OutOther, InOther ...>( - memberFuncWrapper<ThenTask<OutOther, InOther ...>, T, OutOther, InOther ...>(object, func), - errorFunc, mExecutor))); -} + void run(const ExecutionPtr &execution) Q_DECL_OVERRIDE + { + KAsync::Future<typename detail::prevOut<In ...>::type> *prevFuture = nullptr; + if (execution->prevExecution) { + prevFuture = execution->prevExecution->result<typename detail::prevOut<In ...>::type>(); + assert(prevFuture->isFinished()); + } + + //Execute one of the available workers + KAsync::Future<Out> *future = execution->result<Out>(); + + if (ThenExecutor<Out, In ...>::mContinuationHelper.handleContinuation) { + ThenExecutor<Out, In ...>::mContinuationHelper.handleContinuation(prevFuture ? prevFuture->value() : In() ..., *future); + } else if (ThenExecutor<Out, In ...>::mContinuationHelper.handleErrorContinuation) { + ThenExecutor<Out, In ...>::mContinuationHelper.handleErrorContinuation(prevFuture->hasError() ? prevFuture->errors().first() : Error(), prevFuture ? prevFuture->value() : In() ..., *future); + } else if (ThenExecutor<Out, In ...>::mContinuationHelper.jobContinuation) { + executeJobAndApply(prevFuture ? prevFuture->value() : In() ..., ThenExecutor<Out, In...>::mContinuationHelper.jobContinuation, *future, std::is_void<Out>()); + } else if (ThenExecutor<Out, In ...>::mContinuationHelper.jobErrorContinuation) { + executeJobAndApply(prevFuture->hasError() ? prevFuture->errors().first() : Error(), prevFuture ? prevFuture->value() : In() ..., ThenExecutor<Out, In...>::mContinuationHelper.jobErrorContinuation, *future, std::is_void<Out>()); + } + } -template<typename Out, typename ... In> -template<typename OutOther, typename ... InOther> -Job<OutOther, InOther ...> Job<Out, In ...>::then(SyncThenTask<OutOther, InOther ...> func, - ErrorHandler errorFunc) -{ - return Job<OutOther, InOther ...>(Private::ExecutorBasePtr( - new Private::SyncThenExecutor<OutOther, InOther ...>(func, errorFunc, mExecutor))); -} +private: + + void executeJobAndApply(In ... input, const JobContinuation<Out, In ...> &func, + Future<Out> &future, std::false_type) + { + func(input ...) + .template then<void, Out>([&future](const KAsync::Error &error, const Out &v, + KAsync::Future<void> &f) { + if (error) { + future.setError(error); + } else { + future.setResult(v); + } + f.setFinished(); + }).exec(); + } -template<typename Out, typename ... In> -template<typename T, typename OutOther, typename ... InOther> -typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther ...>>::type -Job<Out, In ...>::then(T *object, - typename detail::syncFuncHelper<T, OutOther, InOther ...>::type func, - ErrorHandler errorFunc) -{ - return Job<OutOther, InOther ...>(Private::ExecutorBasePtr( - new Private::SyncThenExecutor<OutOther, InOther ...>( - memberFuncWrapper<SyncThenTask<OutOther, InOther ...>, T, OutOther, InOther ...>(object, func), - errorFunc, mExecutor))); -} + void executeJobAndApply(In ... input, const JobContinuation<Out, In ...> &func, + Future<Out> &future, std::true_type) + { + func(input ...) + .template then<void>([&future](const KAsync::Error &error, KAsync::Future<void> &f) { + if (error) { + future.setError(error); + } else { + future.setFinished(); + } + f.setFinished(); + }).exec(); + } -template<typename Out, typename ... In> -template<typename OutOther, typename ... InOther> -typename std::enable_if<!std::is_void<OutOther>::value, Job<OutOther, InOther ...>>::type -Job<Out, In ...>::then(NestedThenTask<OutOther, InOther ...> func, - ErrorHandler errorFunc) -{ - return then<OutOther, InOther ...>(nestedJobWrapper<OutOther, InOther ...>(func), errorFunc); -} + void executeJobAndApply(const Error &error, In ... input, const JobErrorContinuation<Out, In ...> &func, + Future<Out> &future, std::false_type) + { + func(error, input ...) + .template then<void, Out>([&future](const KAsync::Error &error, const Out &v, + KAsync::Future<void> &f) { + if (error) { + future.setError(error); + } else { + future.setResult(v); + } + f.setFinished(); + }).exec(); + } -template<typename Out, typename ... In> -template<typename OutOther, typename ContOut, typename ... InOther> -typename std::enable_if<std::is_void<OutOther>::value, Job<OutOther, InOther ...>>::type -Job<Out, In ...>::then(NestedThenTask<void, InOther ...> func, ErrorHandler errorFunc) -{ - return then<OutOther, InOther ...>(nestedJobWrapper<void, InOther ...>(func), errorFunc); -} + void executeJobAndApply(const Error &error, In ... input, const JobErrorContinuation<Out, In ...> &func, + Future<Out> &future, std::true_type) + { + func(error, input ...) + .template then<void>([&future](const KAsync::Error &error, KAsync::Future<void> &f) { + if (error) { + future.setError(error); + } else { + future.setFinished(); + } + f.setFinished(); + }).exec(); + } -template<typename Out, typename ... In> -template<typename OutOther, typename ... InOther> -Job<OutOther, InOther ...> Job<Out, In ...>::then(Job<OutOther, InOther ...> otherJob, ErrorHandler errorFunc) -{ - return then<OutOther, InOther ...>(nestedJobWrapper<OutOther, InOther ...>(otherJob), errorFunc); -} + ContinuationHelper<Out, In ...> mContinuationHelper; +}; -template<typename Out, typename ... In> -template<typename ReturnType, typename KJobType, ReturnType (KJobType::*KJobResultMethod)(), typename ... Args> -typename std::enable_if<std::is_base_of<KJob, KJobType>::value, Job<ReturnType, Args ...>>::type -Job<Out, In ...>::then() -{ - return start<ReturnType, KJobType, KJobResultMethod, Args ...>(); -} template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -Job<OutOther, InOther> Job<Out, In ...>::each(EachTask<OutOther, InOther> func, - ErrorHandler errorFunc) +class SyncThenExecutor: public Executor<typename detail::prevOut<In ...>::type, Out, In ...> { - eachInvariants<OutOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::EachExecutor<Out, OutOther, InOther>(func, errorFunc, mExecutor))); -} +private: -template<typename Out, typename ... In> -template<typename T, typename OutOther, typename InOther> -typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther> >::type -Job<Out, In ...>::each(T *object, MemberEachTask<T, OutOther, InOther> func, - ErrorHandler errorFunc) -{ - eachInvariants<OutOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::EachExecutor<Out, OutOther, InOther>( - memberFuncWrapper<EachTask<OutOther, InOther>, T, OutOther, InOther>(object, func), - errorFunc, mExecutor))); -} + void callAndApply(In ... input, const SyncContinuation<Out, In ...> &func, + Future<Out> &future, std::false_type) + { + future.setValue(func(input...)); + } + + void callAndApply(In ... input, const SyncContinuation<Out, In ...> &func, + Future<Out> &, std::true_type) + { + func(input...); + } + + void callAndApply(const Error &error, In ... input, const SyncErrorContinuation<Out, In ...> &func, + Future<Out> &future, std::false_type) + { + future.setValue(func(error, input...)); + } + + void callAndApply(const Error &error, In ... input, const SyncErrorContinuation<Out, In ...> &func, + Future<Out> &, std::true_type) + { + func(error, input...); + } + + const SyncContinuation<Out, In ...> mContinuation; + const SyncErrorContinuation<Out, In ...> mErrorContinuation; + +public: + SyncThenExecutor(const SyncContinuation<Out, In ...> &worker, + const ExecutorBasePtr &parent = ExecutorBasePtr(), + ExecutionFlag executionFlag = Always) + : Executor<typename detail::prevOut<In ...>::type, Out, In ...>(parent, executionFlag) + , mContinuation(worker) + { + + } + + SyncThenExecutor(const SyncErrorContinuation<Out, In ...> &worker, + const ExecutorBasePtr &parent = ExecutorBasePtr(), + ExecutionFlag executionFlag = Always) + : Executor<typename detail::prevOut<In ...>::type, Out, In ...>(parent, executionFlag) + , mErrorContinuation(worker) + { + + } + + void run(const ExecutionPtr &execution) Q_DECL_OVERRIDE + { + KAsync::Future<typename detail::prevOut<In ...>::type> *prevFuture = nullptr; + if (execution->prevExecution) { + prevFuture = execution->prevExecution->result<typename detail::prevOut<In ...>::type>(); + assert(prevFuture->isFinished()); + } + + KAsync::Future<Out> *future = execution->result<Out>(); + + if (SyncThenExecutor<Out, In ...>::mContinuation) { + callAndApply(prevFuture ? prevFuture->value() : In() ..., SyncThenExecutor<Out, In ...>::mContinuation, + *future, std::is_void<Out>()); + } + + if (SyncThenExecutor<Out, In ...>::mErrorContinuation) { + assert(prevFuture); + callAndApply(prevFuture->hasError() ? prevFuture->errors().first() : Error(), + prevFuture ? prevFuture->value() : In() ..., SyncThenExecutor<Out, In ...>::mErrorContinuation, + *future, std::is_void<Out>()); + } + future->setFinished(); + } +}; template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -Job<OutOther, InOther> Job<Out, In ...>::each(SyncEachTask<OutOther, InOther> func, - ErrorHandler errorFunc) +class SyncErrorExecutor: public Executor<typename detail::prevOut<In ...>::type, Out, In ...> { - eachInvariants<OutOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::SyncEachExecutor<Out, OutOther, InOther>(func, errorFunc, mExecutor))); -} +private: + const SyncErrorContinuation<void> mContinuation; -template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -Job<OutOther, InOther> Job<Out, In ...>::each(NestedEachTask<OutOther, InOther> func, - ErrorHandler errorFunc) +public: + SyncErrorExecutor(const SyncErrorContinuation<void> &worker, + const ExecutorBasePtr &parent = ExecutorBasePtr(), + ExecutionFlag executionFlag = Always) + : Executor<typename detail::prevOut<In ...>::type, Out, In ...>(parent, executionFlag) + , mContinuation(worker) + { + + } + + void run(const ExecutionPtr &execution) Q_DECL_OVERRIDE + { + KAsync::Future<typename detail::prevOut<In ...>::type> *prevFuture = nullptr; + if (execution->prevExecution) { + prevFuture = execution->prevExecution->result<typename detail::prevOut<In ...>::type>(); + assert(prevFuture->isFinished()); + } + + KAsync::Future<Out> *future = execution->result<Out>(); + assert(prevFuture->hasError()); + mContinuation(prevFuture->errors().first()); + future->setError(prevFuture->errors().first()); + } +}; + +template<typename T> +KAsync::Future<T>* ExecutorBase::createFuture(const ExecutionPtr &execution) const { - eachInvariants<OutOther>(); - return each<OutOther, InOther>(nestedJobWrapper<OutOther, InOther>(func), errorFunc); + return new KAsync::Future<T>(execution); } -template<typename Out, typename ... In> -template<typename T, typename OutOther, typename InOther> -typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther> >::type -Job<Out, In ...>::each(T *object, - MemberSyncEachTask<T, OutOther, InOther> func, - ErrorHandler errorFunc) -{ - eachInvariants<OutOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::SyncEachExecutor<Out, OutOther, InOther>( - memberFuncWrapper<SyncEachTask<OutOther, InOther>, T, OutOther, InOther>(object, func), - errorFunc, mExecutor))); +template<typename PrevOut, typename Out, typename ... In> +void Executor<PrevOut, Out, In ...>::runExecution(const KAsync::Future<PrevOut> &prevFuture, + const ExecutionPtr &execution) +{ + if (prevFuture.hasError() && executionFlag == ExecutionFlag::GoodCase) { + //Propagate the error to the outer Future + Q_ASSERT(prevFuture.errors().size() == 1); + execution->resultBase->setError(prevFuture.errors().first()); + return; + } + if (!prevFuture.hasError() && executionFlag == ExecutionFlag::ErrorCase) { + //Propagate the value to the outer Future + KAsync::detail::copyFutureValue<PrevOut>(prevFuture, *execution->result<PrevOut>()); + execution->resultBase->setFinished(); + return; + } + run(execution); } -template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -Job<OutOther, InOther> Job<Out, In ...>::each(Job<OutOther, InOther> otherJob, - ErrorHandler errorFunc) +template<typename PrevOut, typename Out, typename ... In> +ExecutionPtr Executor<PrevOut, Out, In ...>::exec(const ExecutorBasePtr &self) { - eachInvariants<OutOther>(); - return each<OutOther, InOther>(nestedJobWrapper<OutOther, InOther>(otherJob), errorFunc); + /* + * One executor per job, created with the construction of the Job object. + * One execution per job per exec(), created only once exec() is called. + * + * The executors make up the linked list that makes up the complete execution chain. + * + * The execution then tracks the execution of each executor. + */ + + // Passing 'self' to execution ensures that the Executor chain remains + // valid until the entire execution is finished + ExecutionPtr execution = ExecutionPtr::create(self); +#ifndef QT_NO_DEBUG + execution->tracer = new Tracer(execution.data()); // owned by execution +#endif + + // chainup + execution->prevExecution = mPrev ? mPrev->exec(mPrev) : ExecutionPtr(); + + execution->resultBase = ExecutorBase::createFuture<Out>(execution); + //We watch our own future to finish the execution once we're done + auto fw = new KAsync::FutureWatcher<Out>(); + QObject::connect(fw, &KAsync::FutureWatcher<Out>::futureReady, + [fw, execution, this]() { + execution->setFinished(); + delete fw; + }); + fw->setFuture(*execution->result<Out>()); + + KAsync::Future<PrevOut> *prevFuture = execution->prevExecution ? execution->prevExecution->result<PrevOut>() : nullptr; + if (!prevFuture || prevFuture->isFinished()) { //The previous job is already done + if (prevFuture) { // prevFuture implies execution->prevExecution + runExecution(*prevFuture, execution); + } else { + run(execution); + } + } else { //The previous job is still running and we have to wait for it's completion + auto prevFutureWatcher = new KAsync::FutureWatcher<PrevOut>(); + QObject::connect(prevFutureWatcher, &KAsync::FutureWatcher<PrevOut>::futureReady, + [prevFutureWatcher, execution, this]() { + auto prevFuture = prevFutureWatcher->future(); + assert(prevFuture.isFinished()); + delete prevFutureWatcher; + runExecution(prevFuture, execution); + }); + + prevFutureWatcher->setFuture(*static_cast<KAsync::Future<PrevOut>*>(prevFuture)); + } + + return execution; } +} // namespace Private + + template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -Job<OutOther, InOther> Job<Out, In ...>::reduce(ReduceTask<OutOther, InOther> func, - ErrorHandler errorFunc) +template<typename ... InOther> +Job<Out, In ...>::operator Job<void>() { - reduceInvariants<InOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::ReduceExecutor<OutOther, InOther>(func, errorFunc, mExecutor))); + return thenImpl<void, InOther ...>({[](InOther ...){ return KAsync::null<void>(); }}, {}); } template<typename Out, typename ... In> -template<typename T, typename OutOther, typename InOther> -typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther> >::type -Job<Out, In ...>::reduce(T *object, - MemberReduceTask<T, OutOther, InOther> func, - ErrorHandler errorFunc) -{ - reduceInvariants<InOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::ReduceExecutor<OutOther, InOther>( - memberFuncWrapper<ReduceTask<OutOther, InOther>, T, OutOther, InOther>(object, func), - errorFunc, mExecutor))); +template<typename OutOther, typename ... InOther> +Job<OutOther, In ...> Job<Out, In ...>::thenImpl(const Private::ContinuationHelper<OutOther, InOther ...> &workHelper, + Private::ExecutionFlag execFlag) const +{ + thenInvariants<InOther ...>(); + return Job<OutOther, In ...>(Private::ExecutorBasePtr( + new Private::ThenExecutor<OutOther, InOther ...>(workHelper, mExecutor, execFlag))); } template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -Job<OutOther, InOther> Job<Out, In ...>::reduce(SyncReduceTask<OutOther, InOther> func, - ErrorHandler errorFunc) +template<typename OutOther, typename ... InOther> +Job<OutOther, In ...> Job<Out, In ...>::then(const Job<OutOther, InOther ...> &job) const { - reduceInvariants<InOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::SyncReduceExecutor<OutOther, InOther>(func, errorFunc, mExecutor))); + thenInvariants<InOther ...>(); + auto executor = job.mExecutor; + executor->prepend(mExecutor); + return Job<OutOther, In ...>(executor); } template<typename Out, typename ... In> -template<typename T, typename OutOther, typename InOther> -typename std::enable_if<std::is_class<T>::value, Job<OutOther, InOther> >::type -Job<Out, In ...>::reduce(T *object, - MemberSyncReduceTask<T, OutOther, InOther> func, - ErrorHandler errorFunc) -{ - reduceInvariants<InOther>(); - return Job<OutOther, InOther>(Private::ExecutorBasePtr( - new Private::ReduceExecutor<OutOther, InOther>( - memberFuncWrapper<SyncReduceTask<OutOther, InOther>, T, OutOther, InOther>(object, func), - errorFunc, mExecutor))); +template<typename OutOther, typename ... InOther> +Job<OutOther, In ...> Job<Out, In ...>::syncThenImpl(const SyncContinuation<OutOther, InOther ...> &func, + Private::ExecutionFlag execFlag) const +{ + static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); + thenInvariants<InOther ...>(); + return Job<OutOther, In...>(Private::ExecutorBasePtr( + new Private::SyncThenExecutor<OutOther, InOther ...>(func, mExecutor, execFlag))); } template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -Job<OutOther, InOther> Job<Out, In ...>::reduce(Job<OutOther, InOther> otherJob, - ErrorHandler errorFunc) +template<typename OutOther, typename ... InOther> +Job<OutOther, In ...> Job<Out, In ...>::syncThenImpl(const SyncErrorContinuation<OutOther, InOther ...> &func, + Private::ExecutionFlag execFlag) const { - return reduce<OutOther, InOther>(nestedJobWrapper<OutOther, InOther>(otherJob), errorFunc); + static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); + thenInvariants<InOther ...>(); + return Job<OutOther, In...>(Private::ExecutorBasePtr( + new Private::SyncThenExecutor<OutOther, InOther ...>(func, mExecutor, execFlag))); } template<typename Out, typename ... In> -Job<Out, Out> Job<Out, In ...>::error(ErrorHandler errorFunc) -{ - return Job<Out, Out>(Private::ExecutorBasePtr( - new Private::SyncThenExecutor<Out, Out>( - [](const Out &in) -> Out { - return in; - }, - errorFunc, mExecutor))); +Job<Out, In ...> Job<Out, In ...>::onError(const SyncErrorContinuation<void> &errorFunc) const +{ + return Job<Out, In...>(Private::ExecutorBasePtr( + new Private::SyncErrorExecutor<Out, Out>([=](const Error &error) { + errorFunc(error); + }, mExecutor, Private::ExecutionFlag::ErrorCase))); } @@ -246,11 +387,11 @@ while (first->mPrev) { first = first->mPrev; } - auto init = new Private::SyncThenExecutor<FirstIn>( - [in]() -> FirstIn { - return in; - }, - ErrorHandler(), Private::ExecutorBasePtr()); + + auto init = new Private::ThenExecutor<FirstIn>({[=](Future<FirstIn> &future) { + future.setResult(in); + }}); + first->mPrev = Private::ExecutorBasePtr(init); auto result = exec(); @@ -274,8 +415,16 @@ {} template<typename Out, typename ... In> +Job<Out, In ...>::Job(const JobContinuation<Out, In ...> &func) + : JobBase(new Private::ThenExecutor<Out, In ...>({func}, {})) +{ + qWarning() << "Creating job job"; + static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); +} + +template<typename Out, typename ... In> template<typename OutOther> -void Job<Out, In ...>::eachInvariants() +void Job<Out, In ...>::eachInvariants() const { static_assert(detail::isIterable<Out>::value, "The 'Each' task can only be connected to a job that returns a list or an array."); @@ -284,130 +433,199 @@ } template<typename Out, typename ... In> -template<typename InOther> -void Job<Out, In ...>::reduceInvariants() +template<typename InOtherFirst, typename ... InOtherTail> +void Job<Out, In ...>::thenInvariants() const { - static_assert(KAsync::detail::isIterable<Out>::value, - "The 'Result' task can only be connected to a job that returns a list or an array"); - static_assert(std::is_same<typename Out::value_type, typename InOther::value_type>::value, + static_assert(!std::is_void<Out>::value && (std::is_convertible<Out, InOtherFirst>::value || std::is_base_of<Out, InOtherFirst>::value), "The return type of previous task must be compatible with input type of this task"); } template<typename Out, typename ... In> -template<typename OutOther, typename ... InOther> -inline std::function<void(InOther ..., KAsync::Future<OutOther>&)> -Job<Out, In ...>::nestedJobWrapper(Job<OutOther, InOther ...> otherJob) -{ - return [otherJob](InOther ... in, KAsync::Future<OutOther> &future) { - // copy by value is const - auto job = otherJob; - FutureWatcher<OutOther> *watcher = new FutureWatcher<OutOther>(); - QObject::connect(watcher, &FutureWatcherBase::futureReady, - [watcher, future]() { - // FIXME: We pass future by value, because using reference causes the - // future to get deleted before this lambda is invoked, leading to crash - // in copyFutureValue() - // copy by value is const - auto outFuture = future; - KAsync::detail::copyFutureValue(watcher->future(), outFuture); - if (watcher->future().errorCode()) { - outFuture.setError(watcher->future().errorCode(), watcher->future().errorMessage()); - } else { - outFuture.setFinished(); - } - delete watcher; - }); - watcher->setFuture(job.exec(in ...)); - }; +template<typename ... InOther> +typename std::enable_if<(sizeof...(InOther) == 0)>::type +Job<Out, In ...>::thenInvariants() const +{ + } + template<typename Out, typename ... In> -template<typename OutOther, typename ... InOther> -inline std::function<void(InOther ..., KAsync::Future<OutOther>&)> -Job<Out, In ...>::nestedJobWrapper(std::function<Job<OutOther>(InOther ...)> func) -{ - return [func](InOther ... in, KAsync::Future<OutOther> &future) { - auto job = func(in ...); - FutureWatcher<OutOther> *watcher = new FutureWatcher<OutOther>(); - QObject::connect(watcher, &FutureWatcherBase::futureReady, - [watcher, future]() { - // FIXME: We pass future by value, because using reference causes the - // future to get deleted before this lambda is invoked, leading to crash - // in copyFutureValue() - // copy by value is const - auto outFuture = future; - KAsync::detail::copyFutureValue(watcher->future(), outFuture); - if (watcher->future().errorCode()) { - outFuture.setError(watcher->future().errorCode(), watcher->future().errorMessage()); - } else { - outFuture.setFinished(); - } - delete watcher; - }); - watcher->setFuture(job.exec(in ...)); - }; +Job<Out, In ...> startImpl(const Private::ContinuationHelper<Out, In ...> &helper) +{ + static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); + return Job<Out, In...>(Private::ExecutorBasePtr( + new Private::ThenExecutor<Out, In ...>(helper, {}, Private::ExecutionFlag::GoodCase))); } template<typename Out, typename ... In> -template<typename OutOther, typename InOther> -inline std::function<void(InOther, KAsync::Future<OutOther>&)> -Job<Out, In ...>::nestedJobWrapper(std::function<Job<OutOther>(InOther)> func) -{ - return [func](InOther in, KAsync::Future<OutOther> &future) { - auto job = func(in); - FutureWatcher<OutOther> *watcher = new FutureWatcher<OutOther>(); - QObject::connect(watcher, &FutureWatcherBase::futureReady, - [watcher, future]() { - // FIXME: We pass future by value, because using reference causes the - // future to get deleted before this lambda is invoked, leading to crash - // in copyFutureValue() - // copy by value is const - auto outFuture = future; - KAsync::detail::copyFutureValue(watcher->future(), outFuture); - if (watcher->future().errorCode()) { - outFuture.setError(watcher->future().errorCode(), watcher->future().errorMessage()); - } else { - outFuture.setFinished(); - } - delete watcher; - }); - watcher->setFuture(job.exec(in)); - }; +Job<Out, In ...> start(const HandleContinuation<Out, In ...> &func) +{ + return startImpl<Out, In...>({func}); } template<typename Out, typename ... In> -template<typename Task, typename T, typename OutOther, typename ... InOther> -inline Task Job<Out, In ...>::memberFuncWrapper(T *object, - typename detail::funcHelper<T, OutOther, InOther ...>::type func) +Job<Out, In ...> start(const JobContinuation<Out, In ...> &func) { - return [object, func](InOther && ... inArgs, KAsync::Future<OutOther> &future) -> void - { - (object->*func)(std::forward<InOther>(inArgs) ..., future); - }; + return startImpl<Out, In...>({func}); } template<typename Out, typename ... In> -template<typename Task, typename T, typename OutOther, typename ... InOther> -inline typename std::enable_if<!std::is_void<OutOther>::value, Task>::type -Job<Out, In ...>::memberFuncWrapper(T *object, typename detail::syncFuncHelper<T, OutOther, InOther ...>::type func) +Job<Out, In ...> syncStart(const SyncContinuation<Out, In ...> &func) +{ + static_assert(sizeof...(In) <= 1, "Only one or zero input parameters are allowed."); + return Job<Out, In...>(Private::ExecutorBasePtr( + new Private::SyncThenExecutor<Out, In ...>(func))); +} + +static inline KAsync::Job<void> waitForCompletion(QList<KAsync::Future<void>> &futures) +{ + auto context = new QObject; + return start<void>([futures, context](KAsync::Future<void> &future) { + const auto total = futures.size(); + auto count = QSharedPointer<int>::create(); + int i = 0; + for (KAsync::Future<void> subFuture : futures) { + i++; + if (subFuture.isFinished()) { + *count += 1; + continue; + } + // FIXME bind lifetime all watcher to future (repectively the main job + auto watcher = QSharedPointer<KAsync::FutureWatcher<void>>::create(); + QObject::connect(watcher.data(), &KAsync::FutureWatcher<void>::futureReady, + [count, total, &future, context]() { + *count += 1; + if (*count == total) { + delete context; + future.setFinished(); + } + }); + watcher->setFuture(subFuture); + context->setProperty(QString::fromLatin1("future%1").arg(i).toLatin1().data(), + QVariant::fromValue(watcher)); + } + if (*count == total) { + delete context; + future.setFinished(); + } + }); + // .finally<void>([context]() { delete context; }); +} + +template<typename List, typename ValueType> +Job<void, List> forEach(KAsync::Job<void, ValueType> job) { - return [object, func](InOther && ... inArgs) -> OutOther - { - return (object->*func)(std::forward<InOther>(inArgs) ...); + auto cont = [job] (const List &values) mutable { + auto error = QSharedPointer<KAsync::Error>::create(); + QList<KAsync::Future<void>> list; + for (const auto &v : values) { + auto future = job + .template syncThen<void>([error] (const KAsync::Error &e) { + if (e && !*error) { + //TODO ideally we would aggregate the errors instead of just using the first one + *error = e; + } + }) + .exec(v); + list << future; + } + return waitForCompletion(list) + .then<void>([error](KAsync::Future<void> &future) { + if (*error) { + future.setError(*error); + } else { + future.setFinished(); + } + }); }; + return Job<void, List>(Private::ExecutorBasePtr( + new Private::ThenExecutor<void, List>({cont}, {}, Private::ExecutionFlag::GoodCase))); } -template<typename Out, typename ... In> -template<typename Task, typename T, typename OutOther, typename ... InOther> -inline typename std::enable_if<std::is_void<OutOther>::value, Task>::type -Job<Out, In ...>::memberFuncWrapper(T *object, typename detail::syncFuncHelper<T, void, InOther ...>::type func, int *) + +template<typename List, typename ValueType> +Job<void, List> serialForEach(KAsync::Job<void, ValueType> job) { - return [object, func](InOther && ... inArgs) -> void - { - (object->*func)(std::forward<InOther>(inArgs) ...); + auto cont = [job] (const List &values) mutable { + auto error = QSharedPointer<KAsync::Error>::create(); + auto serialJob = KAsync::null<void>(); + for (const auto &value : values) { + serialJob = serialJob.then<void>([value, job, error](KAsync::Future<void> &future) { + job.template syncThen<void>([&future, error] (const KAsync::Error &e) { + if (e && !*error) { + //TODO ideally we would aggregate the errors instead of just using the first one + *error = e; + } + future.setFinished(); + }) + .exec(value); + }); + } + return serialJob + .then<void>([error](KAsync::Future<void> &future) { + if (*error) { + future.setError(*error); + } else { + future.setFinished(); + } + }); }; + return Job<void, List>(Private::ExecutorBasePtr( + new Private::ThenExecutor<void, List>({cont}, {}, Private::ExecutionFlag::GoodCase))); } +template<typename List, typename ValueType = typename List::value_type> +Job<void, List> forEach(JobContinuation<void, ValueType> func) +{ + return forEach<List, ValueType>(KAsync::start<void, ValueType>(func)); +} + +template<typename List, typename ValueType = typename List::value_type> +Job<void, List> serialForEach(JobContinuation<void, ValueType> func) +{ + return serialForEach<List, ValueType>(KAsync::start<void, ValueType>(func)); +} + +template<typename Out> +Job<Out> null() +{ + return KAsync::start<Out>( + [](KAsync::Future<Out> &future) { + future.setFinished(); + }); +} + +template<typename Out> +Job<Out> value(Out v) +{ + return KAsync::start<Out>( + [v](KAsync::Future<Out> &future) { + future.setResult(v); + }); +} + +template<typename Out> +Job<Out> error(int errorCode, const QString &errorMessage) +{ + return error<Out>({errorCode, errorMessage}); +} + +template<typename Out> +Job<Out> error(const char *message) +{ + return error<Out>(Error(message)); +} + +template<typename Out> +Job<Out> error(const Error &error) +{ + return KAsync::start<Out>( + [error](KAsync::Future<Out> &future) { + future.setError(error); + }); +} + + } // namespace KAsync //@endconf
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.