Skip to content

Commit

Permalink
Allow QSharedPointer as rejection reason
Browse files Browse the repository at this point in the history
Embed the promise fulfillment value and rejection reason in respectively PromiseValue and PromiseError private wrappers, both storing the data in a shared pointer (QPromiseError is now deprecated).
  • Loading branch information
simonbrunel committed May 10, 2018
1 parent 2c8ed6e commit 7b0cba5
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 97 deletions.
1 change: 1 addition & 0 deletions src/qtpromise/qpromise.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

// QtPromise
#include "qpromise_p.h"
#include "qpromiseerror.h"
#include "qpromiseglobal.h"

// Qt
Expand Down
105 changes: 59 additions & 46 deletions src/qtpromise/qpromise_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#define QTPROMISE_QPROMISE_P_H

// QtPromise
#include "qpromiseerror.h"
#include "qpromiseglobal.h"

// Qt
Expand Down Expand Up @@ -72,6 +71,43 @@ static void qtpromise_defer(F&& f)
qtpromise_defer(std::forward<F>(f), QThread::currentThread());
}

template <typename T>
class PromiseValue
{
public:
PromiseValue() { }
PromiseValue(const T& data) : m_data(new T(data)) { }
PromiseValue(T&& data) : m_data(new T(std::move(data))) { }
bool isNull() const { return m_data.isNull(); }
const T& data() const { return *m_data; }

private:
QSharedPointer<T> m_data;
};

class PromiseError
{
public:
template <typename T>
PromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_data = std::current_exception();
}
}

PromiseError() { }
PromiseError(const std::exception_ptr& exception) : m_data(exception) { }
void rethrow() const { std::rethrow_exception(m_data); }
bool isNull() const { return m_data == nullptr; }

private:
// NOTE(SB) std::exception_ptr is already a shared pointer
std::exception_ptr m_data;
};

template <typename T>
struct PromiseDeduce
{
Expand Down Expand Up @@ -306,12 +342,12 @@ struct PromiseCatcher
using ResType = typename std::result_of<THandler(TArg)>::type;

template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
const THandler& handler,
const TResolve& resolve,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
try {
error.rethrow();
} catch (const TArg& error) {
Expand All @@ -329,12 +365,12 @@ struct PromiseCatcher<T, THandler, void>
using ResType = typename std::result_of<THandler()>::type;

template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
const THandler& handler,
const TResolve& resolve,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
try {
error.rethrow();
} catch (...) {
Expand All @@ -348,12 +384,12 @@ template <typename T>
struct PromiseCatcher<T, std::nullptr_t, void>
{
template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
std::nullptr_t,
const TResolve&,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
// 2.2.7.4. If onRejected is not a function and promise1 is rejected,
// promise2 must be rejected with the same reason as promise1
reject(error);
Expand All @@ -367,9 +403,8 @@ template <typename T, typename F>
class PromiseDataBase : public QSharedData
{
public:
using Error = QtPromise::QPromiseError;
using Handler = std::pair<QPointer<QThread>, std::function<F>>;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const Error&)>>;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const PromiseError&)>>;

virtual ~PromiseDataBase() {}

Expand All @@ -395,29 +430,22 @@ class PromiseDataBase : public QSharedData
m_handlers.append({QThread::currentThread(), std::move(handler)});
}

void addCatcher(std::function<void(const Error&)> catcher)
void addCatcher(std::function<void(const PromiseError&)> catcher)
{
QWriteLocker lock(&m_lock);
m_catchers.append({QThread::currentThread(), std::move(catcher)});
}

void reject(Error error)
template <typename E>
void reject(E&& error)
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error.reset(new Error(std::move(error)));
m_error = PromiseError(std::forward<E>(error));
setSettled();
}

void reject(const QSharedPointer<Error>& error)
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error = error;
this->setSettled();
}

const QSharedPointer<Error>& error() const
const PromiseError& error() const
{
Q_ASSERT(isRejected());
return m_error;
Expand Down Expand Up @@ -446,13 +474,13 @@ class PromiseDataBase : public QSharedData
return;
}

QSharedPointer<Error> error = m_error;
PromiseError error(m_error);
Q_ASSERT(!error.isNull());

for (const auto& catcher: catchers) {
const auto& fn = catcher.second;
qtpromise_defer([=]() {
fn(*error);
fn(error);
}, catcher.first);
}
}
Expand All @@ -473,7 +501,7 @@ class PromiseDataBase : public QSharedData
bool m_settled = false;
QVector<Handler> m_handlers;
QVector<Catcher> m_catchers;
QSharedPointer<Error> m_error;
PromiseError m_error;
};

template <typename T>
Expand All @@ -482,51 +510,36 @@ class PromiseData : public PromiseDataBase<T, void(const T&)>
using Handler = typename PromiseDataBase<T, void(const T&)>::Handler;

public:
void resolve(T&& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(std::move(value)));
this->setSettled();
}

void resolve(const T& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(value));
this->setSettled();
}

void resolve(const QSharedPointer<T>& value)
template <typename V>
void resolve(V&& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value = value;
m_value = PromiseValue<T>(std::forward<V>(value));
this->setSettled();
}

const QSharedPointer<T>& value() const
const PromiseValue<T>& value() const
{
Q_ASSERT(this->isFulfilled());
return m_value;
}

void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{
QSharedPointer<T> value(m_value);
PromiseValue<T> value(m_value);
Q_ASSERT(!value.isNull());

for (const auto& handler: handlers) {
const auto& fn = handler.second;
qtpromise_defer([=]() {
fn(*value);
fn(value.data());
}, handler.first);
}
}

private:
QSharedPointer<T> m_value;
PromiseValue<T> m_value;
};

template <>
Expand Down
54 changes: 7 additions & 47 deletions src/qtpromise/qpromiseerror.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,14 @@
#define QTPROMISE_QPROMISEERROR_H

// QtPromise
#include "qpromise_p.h"
#include "qpromiseglobal.h"

// Qt
#include <QException>

namespace QtPromise {

class QPromiseError
{
public:
template <typename T>
QPromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_exception = std::current_exception();
}
}

QPromiseError(const std::exception_ptr& exception)
: m_exception(exception)
{ }

QPromiseError(const QPromiseError& error)
: m_exception(error.m_exception)
{ }

QPromiseError(QPromiseError&& other)
: m_exception(nullptr)
{
swap(other);
}

QPromiseError& operator=(QPromiseError other)
{
swap(other);
return *this;
}

void swap(QPromiseError& other)
{
qSwap(m_exception, other.m_exception);
}

void rethrow() const
{
std::rethrow_exception(m_exception);
}

private:
std::exception_ptr m_exception;
};

class QPromiseTimeoutException : public QException
{
public:
Expand All @@ -66,6 +20,12 @@ class QPromiseTimeoutException : public QException
}
};

// QPromiseError is provided for backward compatibility and will be
// removed in the next major version: it wasn't intended to be used
// directly and thus should not be part of the public API.
// TODO Remove QPromiseError at version 1.0
using QPromiseError = QtPromisePrivate::PromiseError;

} // namespace QtPromise

#endif // QTPROMISE_QPROMISEERROR_H
1 change: 1 addition & 0 deletions tests/auto/qtpromise/qpromise/qpromise.pro
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ SUBDIRS += \
fail \
finally \
operators \
reject \
resolve \
tap \
tapfail \
Expand Down
4 changes: 4 additions & 0 deletions tests/auto/qtpromise/qpromise/reject/reject.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
TARGET = tst_qpromise_reject
SOURCES += $$PWD/tst_reject.cpp

include(../../qtpromise.pri)
74 changes: 74 additions & 0 deletions tests/auto/qtpromise/qpromise/reject/tst_reject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Tests
#include "../../shared/utils.h"

// QtPromise
#include <QtPromise>

// Qt
#include <QtTest>

// STL
#include <memory>

using namespace QtPromise;

class tst_qpromise_reject : public QObject
{
Q_OBJECT

private Q_SLOTS:
void rejectWithValue();
void rejectWithQSharedPtr();
void rejectWithStdSharedPtr();
};

QTEST_MAIN(tst_qpromise_reject)
#include "tst_reject.moc"

void tst_qpromise_reject::rejectWithValue()
{
auto p = QPromise<int>::reject(42);

QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForError(p, -1), 42);
}

// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_reject::rejectWithQSharedPtr()
{
QWeakPointer<int> wptr;

{
QSharedPointer<int> sptr(new int(42));
auto p = QPromise<int>::reject(sptr);

QCOMPARE(waitForError(p, QSharedPointer<int>()), sptr);

wptr = sptr;
sptr.reset();

QCOMPARE(wptr.isNull(), false); // "p" still holds a reference
}

QCOMPARE(wptr.isNull(), true);
}

// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_reject::rejectWithStdSharedPtr()
{
std::weak_ptr<int> wptr;

{
std::shared_ptr<int> sptr(new int(42));
auto p = QPromise<int>::reject(sptr);

QCOMPARE(waitForError(p, std::shared_ptr<int>()), sptr);

wptr = sptr;
sptr.reset();

QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}

QCOMPARE(wptr.use_count(), 0l);
}
Loading

0 comments on commit 7b0cba5

Please # to comment.