libquentier 0.8.0
The library for rich desktop clients of Evernote service
Loading...
Searching...
No Matches
QtFutureContinuations.h
1/*
2 * Copyright 2021-2024 Dmitry Ivanov
3 *
4 * This file is part of libquentier
5 *
6 * libquentier is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, version 3 of the License.
9 *
10 * libquentier is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with libquentier. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21#include <QtGlobal>
22
23#include <QFutureWatcher>
24#include <QRunnable>
25#include <QThreadPool>
26#include <quentier/exception/RuntimeError.h>
27#include <quentier/threading/Post.h>
28#include <quentier/threading/QtFutureHelpers.h>
29
30#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
31#include <quentier/threading/Qt5Promise.h>
32#endif // QT_VERSION
33
34#include <quentier/threading/Runnable.h>
35
36#include <boost/core/demangle.hpp>
37
38#include <memory>
39#include <type_traits>
40#include <typeinfo>
41
42namespace quentier::threading {
43
44// NOTE: "native" implementation of continuations for Qt6 is currently disabled
45// due to bugs in their implementation, in particular (but not limited to)
46// https://bugreports.qt.io/browse/QTBUG-119579 and
47// https://bugreports.qt.io/browse/QTBUG-117918. It's a shame but it is what it
48// is.
49/*
50#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
51
52template <class T, class Function>
53QFuture<typename QtPrivate::ResultTypeHelper<Function, T>::ResultType> then(
54 QFuture<T> && future, Function && function)
55{
56 return future.then(std::forward<decltype(function)>(function));
57}
58
59template <class T, class Function>
60QFuture<typename QtPrivate::ResultTypeHelper<Function, T>::ResultType> then(
61 QFuture<T> && future, QtFuture::Launch policy, Function && function)
62{
63 return future.then(policy, std::forward<decltype(function)>(function));
64}
65
66template <class T, class Function>
67QFuture<typename QtPrivate::ResultTypeHelper<Function, T>::ResultType> then(
68 QFuture<T> && future, QThreadPool * pool, Function && function)
69{
70 return future.then(pool, std::forward<decltype(function)>(function));
71}
72
73template <class T, class Function>
74QFuture<typename QtPrivate::ResultTypeHelper<Function, T>::ResultType> then(
75 QFuture<T> && future, QObject * context, Function && function)
76{
77 return future.then(context, std::forward<decltype(function)>(function));
78}
79
80template <class T, class Function>
81std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs, QFuture<T>>
82 onFailed(QFuture<T> && future, Function && handler)
83{
84 return future.onFailed(std::forward<decltype(handler)>(handler));
85}
86
87template <class T, class Function>
88std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs, QFuture<T>>
89 onFailed(QFuture<T> && future, QObject * context, Function && handler)
90{
91 return future.onFailed(context, std::forward<decltype(handler)>(handler));
92}
93
94#else // QT_VERSION
95
96// implementation for Qt5
97*/
98
99namespace detail {
100
101template <class T, class Function>
102void processParentFuture(
103 std::shared_ptr<
104 QPromise<typename ResultTypeHelper<Function, T>::ResultType>>
105 promise,
106 QFuture<T> && future, Function && function)
107{
108 Q_ASSERT(promise);
109
110 using ResultType = typename ResultTypeHelper<Function, T>::ResultType;
111
112 promise->start();
113
114 // If future contains exception, just forward it to the promise and
115 // don't call the function at all
116 try {
117 future.waitForFinished();
118 }
119 catch (const QException & e) {
120 promise->setException(e);
121 promise->finish();
122 return;
123 }
124 // NOTE: there cannot be other exception types in this context in Qt5
125 // because exception store can only contain QExceptions
126
127 // Try to run the handler, in case of success forward the result to promise
128 // (unless it is void), catch possible exceptions and if caught put them
129 // to the promise
130 try {
131 if constexpr (std::is_void_v<ResultType>) {
132 if constexpr (std::is_void_v<T>) {
133 function();
134 }
135 else {
136 if (future.resultCount() == 0) {
137 promise->setException(RuntimeError{ErrorString{
138 QString::fromUtf8(
139 "Invalid future continuation: detected future "
140 "without result for type %1")
141 .arg(QString::fromStdString(std::string{
142 boost::core::demangle(typeid(T).name())}))}});
143 promise->finish();
144 return;
145 }
146
147 function(future.result());
148 }
149 }
150 else {
151 if constexpr (std::is_void_v<T>) {
152 promise->addResult(function());
153 }
154 else {
155 promise->addResult(function(future.result()));
156 }
157 }
158 }
159 catch (const QException & e) {
160 promise->setException(e);
161 }
162 catch (const std::exception & e) {
163 ErrorString error{QT_TRANSLATE_NOOP(
164 "utility", "Unknown std::exception in then future handler")};
165 error.details() = QString::fromStdString(std::string{e.what()});
166 promise->setException(RuntimeError{std::move(error)});
167 }
168 catch (...) {
169 ErrorString error{QT_TRANSLATE_NOOP(
170 "utility", "Unknown exception in then future handler")};
171 promise->setException(RuntimeError{std::move(error)});
172 }
173
174 promise->finish();
175}
176
177} // namespace detail
178
179template <class T, class Function>
180QFuture<typename detail::ResultTypeHelper<Function, T>::ResultType> then(
181 QFuture<T> && future, Function && function)
182{
183 using ResultType =
184 typename detail::ResultTypeHelper<Function, T>::ResultType;
185
186 auto promise = std::make_shared<QPromise<ResultType>>();
187 auto result = promise->future();
188
189 if (future.isFinished()) {
190 detail::processParentFuture(
191 std::move(promise), std::move(future),
192 std::forward<decltype(function)>(function));
193 return result;
194 }
195
196 auto watcher = std::make_unique<QFutureWatcher<T>>();
197 auto * rawWatcher = watcher.get();
198 QObject::connect(
199 rawWatcher, &QFutureWatcher<T>::finished, rawWatcher,
200 [rawWatcher, function = std::forward<decltype(function)>(function),
201 promise = std::move(promise)]() mutable {
202 detail::processParentFuture(
203 std::move(promise), rawWatcher->future(),
204 std::forward<decltype(function)>(function));
205 rawWatcher->deleteLater();
206 });
207
208 QObject::connect(
209 rawWatcher, &QFutureWatcher<T>::canceled, rawWatcher,
210 [rawWatcher] { rawWatcher->deleteLater(); });
211
212 watcher->setFuture(std::move(future));
213 Q_UNUSED(watcher.release())
214
215 return result;
216}
217
218template <class T, class Function>
219QFuture<typename detail::ResultTypeHelper<Function, T>::ResultType> then(
220 QFuture<T> && future, QtFuture::Launch policy, Function && function)
221{
222 if (policy == QtFuture::Launch::Sync) {
223 return then(
224 std::move(future), std::forward<decltype(function)>(function));
225 }
226
227 return then(
228 std::move(future), QThreadPool::globalInstance(),
229 std::forward<decltype(function)>(function));
230}
231
232template <class T, class Function>
233QFuture<typename detail::ResultTypeHelper<Function, T>::ResultType> then(
234 QFuture<T> && future, QThreadPool * pool, Function && function)
235{
236 using ResultType =
237 typename detail::ResultTypeHelper<Function, T>::ResultType;
238
239 auto promise = std::make_shared<QPromise<ResultType>>();
240 auto result = promise->future();
241
242 if (future.isFinished()) {
243 auto * runnable = createFunctionRunnable(
244 [future = std::move(future), promise = std::move(promise),
245 function = std::forward<decltype(function)>(function)]() mutable {
246 detail::processParentFuture(
247 std::move(promise), std::move(future),
248 std::forward<decltype(function)>(function));
249 });
250 runnable->setAutoDelete(true);
251 pool->start(runnable);
252 return result;
253 }
254
255 auto watcher = std::make_unique<QFutureWatcher<T>>();
256 auto * rawWatcher = watcher.get();
257 QObject::connect(
258 rawWatcher, &QFutureWatcher<T>::finished, rawWatcher,
259 [rawWatcher, function = std::forward<decltype(function)>(function),
260 promise = std::move(promise), pool]() mutable {
261 auto * runnable = createFunctionRunnable(
262 [function = std::forward<decltype(function)>(function),
263 promise = std::move(promise),
264 future = rawWatcher->future()]() mutable {
265 detail::processParentFuture(
266 std::move(promise), std::move(future),
267 std::forward<decltype(function)>(function));
268 });
269 runnable->setAutoDelete(true);
270 pool->start(runnable);
271 rawWatcher->deleteLater();
272 });
273
274 QObject::connect(
275 rawWatcher, &QFutureWatcher<T>::canceled, rawWatcher,
276 [rawWatcher] { rawWatcher->deleteLater(); });
277
278 watcher->setFuture(std::move(future));
279 Q_UNUSED(watcher.release())
280
281 return result;
282}
283
284template <class T, class Function>
285QFuture<typename detail::ResultTypeHelper<Function, T>::ResultType> then(
286 QFuture<T> && future, QObject * context, Function && function)
287{
288 using ResultType =
289 typename detail::ResultTypeHelper<Function, T>::ResultType;
290
291 auto promise = std::make_shared<QPromise<ResultType>>();
292 auto result = promise->future();
293
294 if (future.isFinished()) {
295 postToObject(
296 context,
297 [future = std::move(future), promise = std::move(promise),
298 function = std::forward<decltype(function)>(function)]() mutable {
299 detail::processParentFuture(
300 std::move(promise), std::move(future),
301 std::forward<decltype(function)>(function));
302 });
303 return result;
304 }
305
306 auto watcher = std::make_unique<QFutureWatcher<T>>();
307 auto * rawWatcher = watcher.get();
308
309 QObject::connect(
310 rawWatcher, &QFutureWatcher<T>::finished, context,
311 [context, rawWatcher,
312 function = std::forward<decltype(function)>(function),
313 promise = std::move(promise)]() mutable {
314 postToObject(
315 context,
316 [function = std::forward<decltype(function)>(function),
317 promise = std::move(promise),
318 future = rawWatcher->future()]() mutable {
319 detail::processParentFuture(
320 std::move(promise), std::move(future),
321 std::forward<decltype(function)>(function));
322 });
323 rawWatcher->deleteLater();
324 });
325
326 QObject::connect(
327 rawWatcher, &QFutureWatcher<T>::canceled, rawWatcher,
328 [rawWatcher] { rawWatcher->deleteLater(); });
329
330 watcher->setFuture(std::move(future));
331 Q_UNUSED(watcher.release())
332
333 return result;
334}
335
336namespace detail {
337
338template <class T, class Function>
339std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs, void>
340 processPossibleFutureException(
341 std::shared_ptr<QPromise<T>> promise, QFuture<T> && future,
342 Function && handler)
343{
344 Q_ASSERT(promise);
345
346 using ArgType = typename QtPrivate::ArgResolver<Function>::First;
347 using ResultType =
348 typename ResultTypeHelper<Function, std::decay_t<ArgType>>::ResultType;
349 static_assert(std::is_convertible_v<ResultType, T>);
350
351 promise->start();
352
353 try {
354 try {
355 future.waitForFinished();
356 }
357 catch (const ArgType & e) {
358 try {
359 if constexpr (std::is_void_v<ResultType>) {
360 handler(e);
361 }
362 else {
363 promise->addResult(handler(e));
364 }
365 }
366 catch (const QException & e) {
367 promise->setException(e);
368 }
369 catch (const std::exception & e) {
370 ErrorString error{QT_TRANSLATE_NOOP(
371 "utility",
372 "Unknown std::exception in onFailed future handler")};
373 error.details() = QString::fromStdString(std::string{e.what()});
374 promise->setException(RuntimeError{std::move(error)});
375 }
376 catch (...) {
377 ErrorString error{QT_TRANSLATE_NOOP(
378 "utility", "Unknown exception in onFailed future handler")};
379 promise->setException(RuntimeError{std::move(error)});
380 }
381 }
382 }
383 // Exception doesn't match with handler's argument type, propagate
384 // the exception to be handled later.
385 catch (const QException & e) {
386 promise->setException(e);
387 }
388 catch (const std::exception & e) {
389 ErrorString error{QT_TRANSLATE_NOOP(
390 "utility",
391 "Unknown std::exception which did not match with onFailed "
392 "future handler")};
393 error.details() = QString::fromStdString(std::string{e.what()});
394 promise->setException(RuntimeError{std::move(error)});
395 }
396 catch (...) {
397 ErrorString error{QT_TRANSLATE_NOOP(
398 "utility",
399 "Unknown which did not match with onFailed "
400 "future handler")};
401 promise->setException(RuntimeError{std::move(error)});
402 }
403
404 promise->finish();
405}
406
407} // namespace detail
408
409// WARNING! "Chaining" of onFailed calls would only work properly with Qt5 if
410// all involved exceptions subclass QException. It is due to the way exception
411// storage is implemented in Qt5. In Qt6 is was made to store std::exception_ptr
412// so there's no requirement to use QExceptions in Qt6.
413
414template <class T, class Function>
415std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs, QFuture<T>>
416 onFailed(QFuture<T> && future, Function && handler)
417{
418 auto promise = std::make_shared<QPromise<T>>();
419 auto result = promise->future();
420
421 if (future.isFinished()) {
422 detail::processPossibleFutureException(
423 std::move(promise), std::move(future),
424 std::forward<decltype(handler)>(handler));
425 return result;
426 }
427
428 auto watcher = std::make_unique<QFutureWatcher<T>>();
429 auto * rawWatcher = watcher.get();
430 QObject::connect(
431 rawWatcher, &QFutureWatcher<T>::finished, rawWatcher,
432 [rawWatcher, promise = std::move(promise),
433 handler = std::forward<decltype(handler)>(handler)]() mutable {
434 auto future = rawWatcher->future();
435 rawWatcher->deleteLater();
436 detail::processPossibleFutureException(
437 std::move(promise), std::move(future),
438 std::forward<decltype(handler)>(handler));
439 });
440
441 QObject::connect(
442 rawWatcher, &QFutureWatcher<T>::canceled, rawWatcher,
443 [rawWatcher] { rawWatcher->deleteLater(); });
444
445 watcher->setFuture(std::move(future));
446 Q_UNUSED(watcher.release())
447
448 return result;
449}
450
451template <class T, class Function>
452std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs, QFuture<T>>
453 onFailed(QFuture<T> && future, QObject * context, Function && handler)
454{
455 auto promise = std::make_shared<QPromise<T>>();
456 auto result = promise->future();
457
458 if (future.isFinished()) {
459 postToObject(
460 context,
461 [promise = std::move(promise), future = std::move(future),
462 handler = std::forward<decltype(handler)>(handler)]() mutable {
463 detail::processPossibleFutureException(
464 std::move(promise), std::move(future),
465 std::forward<decltype(handler)>(handler));
466 });
467 return result;
468 }
469
470 auto watcher = std::make_unique<QFutureWatcher<T>>();
471 auto * rawWatcher = watcher.get();
472 QObject::connect(
473 rawWatcher, &QFutureWatcher<T>::finished, context,
474 [context, rawWatcher, promise = std::move(promise),
475 handler = std::forward<decltype(handler)>(handler)]() mutable {
476 postToObject(
477 context,
478 [promise = std::move(promise), future = rawWatcher->future(),
479 handler = std::forward<decltype(handler)>(handler)]() mutable {
480 detail::processPossibleFutureException(
481 std::move(promise), std::move(future),
482 std::forward<decltype(handler)>(handler));
483 });
484 rawWatcher->deleteLater();
485 });
486
487 QObject::connect(
488 rawWatcher, &QFutureWatcher<T>::canceled, rawWatcher,
489 [rawWatcher] { rawWatcher->deleteLater(); });
490
491 watcher->setFuture(std::move(future));
492 Q_UNUSED(watcher.release())
493
494 return result;
495}
496
497// #endif // QT_VERSION
498
499// Convenience functions for both Qt versions
500
501template <class T, class U, class Function>
502void thenOrFailed(
503 QFuture<T> && future, std::shared_ptr<QPromise<U>> promise,
504 Function && function)
505{
506 auto thenFuture =
507 then(std::move(future), std::forward<decltype(function)>(function));
508
509 onFailed(std::move(thenFuture), [promise](const QException & e) {
510 promise->setException(e);
511 promise->finish();
512 });
513}
514
515template <class T, class U, class Function>
516void thenOrFailed(
517 QFuture<T> && future, QThread * thread,
518 std::shared_ptr<QPromise<U>> promise, Function && function)
519{
520 auto thenFuture =
521 then(std::move(future), thread, std::forward<Function>(function));
522
523 onFailed(std::move(thenFuture), thread, [promise](const QException & e) {
524 promise->setException(e);
525 promise->finish();
526 });
527}
528
529template <class T, class U>
530void thenOrFailed(QFuture<T> && future, std::shared_ptr<QPromise<U>> promise)
531{
532 thenOrFailed(std::move(future), promise, [promise] { promise->finish(); });
533}
534
535template <class T, class U>
536void thenOrFailed(
537 QFuture<T> && future, QThread * thread,
538 std::shared_ptr<QPromise<U>> promise)
539{
540 thenOrFailed(
541 std::move(future), thread, promise, [promise] { promise->finish(); });
542}
543
544} // namespace quentier::threading
Definition Qt5Promise.h:28