libquentier  0.8.0
The library for rich desktop clients of Evernote service
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 
42 namespace 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 
52 template <class T, class Function>
53 QFuture<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 
59 template <class T, class Function>
60 QFuture<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 
66 template <class T, class Function>
67 QFuture<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 
73 template <class T, class Function>
74 QFuture<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 
80 template <class T, class Function>
81 std::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 
87 template <class T, class Function>
88 std::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 
99 namespace detail {
100 
101 template <class T, class Function>
102 void 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 
179 template <class T, class Function>
180 QFuture<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 
218 template <class T, class Function>
219 QFuture<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 
232 template <class T, class Function>
233 QFuture<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 
284 template <class T, class Function>
285 QFuture<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 
336 namespace detail {
337 
338 template <class T, class Function>
339 std::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 
414 template <class T, class Function>
415 std::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 
451 template <class T, class Function>
452 std::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 
501 template <class T, class U, class Function>
502 void 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 
515 template <class T, class U, class Function>
516 void 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 
529 template <class T, class U>
530 void thenOrFailed(QFuture<T> && future, std::shared_ptr<QPromise<U>> promise)
531 {
532  thenOrFailed(std::move(future), promise, [promise] { promise->finish(); });
533 }
534 
535 template <class T, class U>
536 void 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:27
Definition: threading/Factory.h:24