diff options
Diffstat (limited to 'src/corelib/thread/qfuture_impl.h')
-rw-r--r-- | src/corelib/thread/qfuture_impl.h | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h new file mode 100644 index 0000000000..4a57ba7846 --- /dev/null +++ b/src/corelib/thread/qfuture_impl.h @@ -0,0 +1,515 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFUTURE_H +#error Do not include qfuture_impl.h directly +#endif + +#if 0 +#pragma qt_sync_skip_header_check +#pragma qt_sync_stop_processing +#endif + +#include <QtCore/qglobal.h> +#include <QtCore/qfutureinterface.h> +#include <QtCore/qthreadpool.h> + +QT_BEGIN_NAMESPACE + +// +// forward declarations +// +template<class T> +class QFuture; +template<class T> +class QFutureInterface; + +namespace QtFuture { +enum class Launch { Sync, Async, Inherit }; +} + +namespace QtPrivate { + +template<class T> +using EnableForVoid = std::enable_if_t<std::is_same_v<T, void>>; + +template<class T> +using EnableForNonVoid = std::enable_if_t<!std::is_same_v<T, void>>; + +template<typename F, typename Arg, typename Enable = void> +struct ResultTypeHelper +{ +}; + +// The callable takes an argument of type Arg +template<typename F, typename Arg> +struct ResultTypeHelper< + F, Arg, typename std::enable_if_t<!std::is_invocable_v<std::decay_t<F>, QFuture<Arg>>>> +{ + using ResultType = std::invoke_result_t<std::decay_t<F>, std::decay_t<Arg>>; +}; + +// The callable takes an argument of type QFuture<Arg> +template<class F, class Arg> +struct ResultTypeHelper< + F, Arg, typename std::enable_if_t<std::is_invocable_v<std::decay_t<F>, QFuture<Arg>>>> +{ + using ResultType = std::invoke_result_t<std::decay_t<F>, QFuture<Arg>>; +}; + +// The callable takes an argument of type QFuture<void> +template<class F> +struct ResultTypeHelper< + F, void, typename std::enable_if_t<std::is_invocable_v<std::decay_t<F>, QFuture<void>>>> +{ + using ResultType = std::invoke_result_t<std::decay_t<F>, QFuture<void>>; +}; + +// The callable doesn't take argument +template<class F> +struct ResultTypeHelper< + F, void, typename std::enable_if_t<!std::is_invocable_v<std::decay_t<F>, QFuture<void>>>> +{ + using ResultType = std::invoke_result_t<std::decay_t<F>>; +}; + +// Helpers to resolve argument types of callables. +template<typename...> +struct ArgsType; + +template<typename Arg, typename... Args> +struct ArgsType<Arg, Args...> +{ + using First = Arg; + static const bool HasExtraArgs = (sizeof...(Args) > 0); +}; + +template<> +struct ArgsType<> +{ + using First = void; + static const bool HasExtraArgs = false; +}; + +template<typename F> +struct ArgResolver : ArgResolver<decltype(&std::decay_t<F>::operator())> +{ +}; + +template<typename R, typename... Args> +struct ArgResolver<R(Args...)> : public ArgsType<Args...> +{ +}; + +template<typename R, typename... Args> +struct ArgResolver<R (*)(Args...)> : public ArgsType<Args...> +{ +}; + +template<typename R, typename... Args> +struct ArgResolver<R (&)(Args...)> : public ArgsType<Args...> +{ +}; + +template<typename Class, typename R, typename... Args> +struct ArgResolver<R (Class::*)(Args...)> : public ArgsType<Args...> +{ +}; + +template<typename Class, typename R, typename... Args> +struct ArgResolver<R (Class::*)(Args...) noexcept> : public ArgsType<Args...> +{ +}; + +template<typename Class, typename R, typename... Args> +struct ArgResolver<R (Class::*)(Args...) const> : public ArgsType<Args...> +{ +}; + +template<typename Class, typename R, typename... Args> +struct ArgResolver<R (Class::*)(Args...) const noexcept> : public ArgsType<Args...> +{ +}; + +template<typename Function, typename ResultType, typename ParentResultType> +class Continuation +{ +public: + Continuation(Function &&func, const QFuture<ParentResultType> &f, + const QFutureInterface<ResultType> &p) + : promise(p), parentFuture(f), function(std::forward<Function>(func)) + { + } + virtual ~Continuation() = default; + + bool execute(); + + static void create(Function &&func, QFuture<ParentResultType> *f, + QFutureInterface<ResultType> &p, QtFuture::Launch policy); + + static void create(Function &&func, QFuture<ParentResultType> *f, + QFutureInterface<ResultType> &p, QThreadPool *pool); + +protected: + virtual void runImpl() = 0; + + void runFunction(); + +protected: + QFutureInterface<ResultType> promise; + const QFuture<ParentResultType> parentFuture; + Function function; +}; + +template<typename Function, typename ResultType, typename ParentResultType> +class SyncContinuation final : public Continuation<Function, ResultType, ParentResultType> +{ +public: + SyncContinuation(Function &&func, const QFuture<ParentResultType> &f, + const QFutureInterface<ResultType> &p) + : Continuation<Function, ResultType, ParentResultType>(std::forward<Function>(func), f, p) + { + } + + ~SyncContinuation() override = default; + +private: + void runImpl() override { this->runFunction(); } +}; + +template<typename Function, typename ResultType, typename ParentResultType> +class AsyncContinuation final : public QRunnable, + public Continuation<Function, ResultType, ParentResultType> +{ +public: + AsyncContinuation(Function &&func, const QFuture<ParentResultType> &f, + const QFutureInterface<ResultType> &p, QThreadPool *pool = nullptr) + : Continuation<Function, ResultType, ParentResultType>(std::forward<Function>(func), f, p), + threadPool(pool) + { + this->promise.setRunnable(this); + } + + ~AsyncContinuation() override = default; + +private: + void runImpl() override // from Continuation + { + QThreadPool *pool = threadPool ? threadPool : QThreadPool::globalInstance(); + pool->start(this); + } + + void run() override // from QRunnable + { + this->runFunction(); + } + +private: + QThreadPool *threadPool; +}; + +#ifndef QT_NO_EXCEPTIONS + +template<class Function, class ResultType> +class FailureHandler +{ +public: + static void create(Function &&function, QFuture<ResultType> *future, + const QFutureInterface<ResultType> &promise); + + FailureHandler(Function &&func, const QFuture<ResultType> &f, + const QFutureInterface<ResultType> &p) + : promise(p), parentFuture(f), handler(std::forward<Function>(func)) + { + } + +public: + void run(); + +private: + template<class ArgType> + void handleException(); + void handleAllExceptions(); + +private: + QFutureInterface<ResultType> promise; + const QFuture<ResultType> parentFuture; + Function handler; +}; + +#endif + +template<typename Function, typename ResultType, typename ParentResultType> +void Continuation<Function, ResultType, ParentResultType>::runFunction() +{ + promise.reportStarted(); + + Q_ASSERT(parentFuture.isFinished()); + +#ifndef QT_NO_EXCEPTIONS + try { +#endif + if constexpr (!std::is_void_v<ResultType>) { + if constexpr (std::is_invocable_v<std::decay_t<Function>, QFuture<ParentResultType>>) { + promise.reportResult(function(parentFuture)); + } else if constexpr (std::is_void_v<ParentResultType>) { + promise.reportResult(function()); + } else { + // This assert normally should never fail, this is to make sure + // that nothing unexpected happend. + static_assert( + std::is_invocable_v<std::decay_t<Function>, std::decay_t<ParentResultType>>, + "The continuation is not invocable with the provided arguments"); + + promise.reportResult(function(parentFuture.result())); + } + } else { + if constexpr (std::is_invocable_v<std::decay_t<Function>, QFuture<ParentResultType>>) { + function(parentFuture); + } else if constexpr (std::is_void_v<ParentResultType>) { + function(); + } else { + // This assert normally should never fail, this is to make sure + // that nothing unexpected happend. + static_assert( + std::is_invocable_v<std::decay_t<Function>, std::decay_t<ParentResultType>>, + "The continuation is not invocable with the provided arguments"); + + function(parentFuture.result()); + } + } +#ifndef QT_NO_EXCEPTIONS + } catch (...) { + promise.reportException(std::current_exception()); + } +#endif + promise.reportFinished(); +} + +template<typename Function, typename ResultType, typename ParentResultType> +bool Continuation<Function, ResultType, ParentResultType>::execute() +{ + Q_ASSERT(parentFuture.isFinished()); + + if (parentFuture.isCanceled()) { +#ifndef QT_NO_EXCEPTIONS + if (parentFuture.d.exceptionStore().hasException()) { + // If the continuation doesn't take a QFuture argument, propagate the exception + // to the caller, by reporting it. If the continuation takes a QFuture argument, + // the user may want to catch the exception inside the continuation, to not + // interrupt the continuation chain, so don't report anything yet. + if constexpr (!std::is_invocable_v<std::decay_t<Function>, QFuture<ParentResultType>>) { + promise.reportStarted(); + promise.reportException(parentFuture.d.exceptionStore().exception()); + promise.reportFinished(); + return false; + } + } else +#endif + { + promise.reportStarted(); + promise.reportCanceled(); + promise.reportFinished(); + return false; + } + } + + runImpl(); + return true; +} + +template<typename Function, typename ResultType, typename ParentResultType> +void Continuation<Function, ResultType, ParentResultType>::create(Function &&func, + QFuture<ParentResultType> *f, + QFutureInterface<ResultType> &p, + QtFuture::Launch policy) +{ + Q_ASSERT(f); + + QThreadPool *pool = nullptr; + + bool launchAsync = (policy == QtFuture::Launch::Async); + if (policy == QtFuture::Launch::Inherit) { + launchAsync = f->d.launchAsync(); + + // If the parent future was using a custom thread pool, inherit it as well. + if (launchAsync && f->d.threadPool()) { + pool = f->d.threadPool(); + p.setThreadPool(pool); + } + } + + Continuation<Function, ResultType, ParentResultType> *continuationJob = nullptr; + if (launchAsync) { + continuationJob = new AsyncContinuation<Function, ResultType, ParentResultType>( + std::forward<Function>(func), *f, p, pool); + } else { + continuationJob = new SyncContinuation<Function, ResultType, ParentResultType>( + std::forward<Function>(func), *f, p); + } + + p.setLaunchAsync(launchAsync); + + auto continuation = [continuationJob, launchAsync]() mutable { + bool isLaunched = continuationJob->execute(); + // If continuation is successfully launched, AsyncContinuation will be deleted + // by the QThreadPool which has started it. Synchronous continuation will be + // executed immediately, so it's safe to always delete it here. + if (!(launchAsync && isLaunched)) { + delete continuationJob; + continuationJob = nullptr; + } + }; + + f->d.setContinuation(std::move(continuation)); +} + +template<typename Function, typename ResultType, typename ParentResultType> +void Continuation<Function, ResultType, ParentResultType>::create(Function &&func, + QFuture<ParentResultType> *f, + QFutureInterface<ResultType> &p, + QThreadPool *pool) +{ + Q_ASSERT(f); + + auto continuationJob = new AsyncContinuation<Function, ResultType, ParentResultType>( + std::forward<Function>(func), *f, p, pool); + p.setLaunchAsync(true); + p.setThreadPool(pool); + + auto continuation = [continuationJob]() mutable { + bool isLaunched = continuationJob->execute(); + // If continuation is successfully launched, AsyncContinuation will be deleted + // by the QThreadPool which has started it. + if (!isLaunched) { + delete continuationJob; + continuationJob = nullptr; + } + }; + + f->d.setContinuation(continuation); +} + +#ifndef QT_NO_EXCEPTIONS + +template<class Function, class ResultType> +void FailureHandler<Function, ResultType>::create(Function &&function, QFuture<ResultType> *future, + const QFutureInterface<ResultType> &promise) +{ + Q_ASSERT(future); + + FailureHandler<Function, ResultType> *failureHandler = new FailureHandler<Function, ResultType>( + std::forward<Function>(function), *future, promise); + + auto failureContinuation = [failureHandler]() mutable { + failureHandler->run(); + delete failureHandler; + }; + + future->d.setContinuation(std::move(failureContinuation)); +} + +template<class Function, class ResultType> +void FailureHandler<Function, ResultType>::run() +{ + Q_ASSERT(parentFuture.isFinished()); + + promise.reportStarted(); + + if (parentFuture.d.exceptionStore().hasException()) { + using ArgType = typename QtPrivate::ArgResolver<Function>::First; + if constexpr (std::is_void_v<ArgType>) { + handleAllExceptions(); + } else { + handleException<ArgType>(); + } + } else { + if constexpr (!std::is_void_v<ResultType>) + promise.reportResult(parentFuture.result()); + } + promise.reportFinished(); +} + +template<class Function, class ResultType> +template<class ArgType> +void FailureHandler<Function, ResultType>::handleException() +{ + try { + parentFuture.d.exceptionStore().throwPossibleException(); + } catch (const ArgType &e) { + try { + // Handle exceptions matching with the handler's argument type + if constexpr (std::is_void_v<ResultType>) { + handler(e); + } else { + promise.reportResult(handler(e)); + } + } catch (...) { + promise.reportException(std::current_exception()); + } + } catch (...) { + // Exception doesn't match with handler's argument type, propagate + // the exception to be handled later. + promise.reportException(std::current_exception()); + } +} + +template<class Function, class ResultType> +void FailureHandler<Function, ResultType>::handleAllExceptions() +{ + try { + parentFuture.d.exceptionStore().throwPossibleException(); + } catch (...) { + try { + if constexpr (std::is_void_v<ResultType>) { + handler(); + } else { + promise.reportResult(handler()); + } + } catch (...) { + promise.reportException(std::current_exception()); + } + } +} + +#endif // QT_NO_EXCEPTIONS + +} // namespace QtPrivate + +QT_END_NAMESPACE |