-
-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement coroutine features and guardThis feature (#177)
By calling `co_await QCoro::thisCoro()` user can obtain CoroutineFeatures object specific to the current coroutine, and through it they can control behavior of the coroutine. The one feature implemented so far is "guardThis", which monitors the lifetime of a given QObject-derived object. If the object is destroyed while the coroutine is suspended, when the time comes to resume it, it is destroyed instead. This is mostly useful to watch lifetime of `this`, so that when a member coroutine function is suspended and the object is deleted in the meantime, it will prevent the coroutine from continuing with a dangling `this` pointer when resumed.
- Loading branch information
Showing
11 changed files
with
306 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<!-- | ||
SPDX-FileCopyrightText: 2023 Daniel Vrátil <dvratil@kde.org> | ||
SPDX-License-Identifier: GFDL-1.3-or-later | ||
--> | ||
|
||
# Coroutine Features | ||
|
||
!!! note "This feature is available since QCoro 0.10" | ||
|
||
QCoro coroutines can be tuned at runtime to adjust their behavior. | ||
|
||
## Obtaining Coroutine Features | ||
|
||
```cpp | ||
auto QCoro::thisCoro(); | ||
``` | ||
|
||
Current coroutine's features can be obtained by `co_await`ing on `QCoro::thisCoro()` to | ||
obtain `QCoro::CoroutineFeatures` object: | ||
|
||
```cpp | ||
QCoro::Task<> myCoroutine() { | ||
QCoro::CoroutineFeatures &features = co_await QCoro::thisCoro(); | ||
// use features here to tune behavior of this coroutine. | ||
} | ||
``` | ||
|
||
The `co_await` does not actually suspend the current coroutine, the features object is returned | ||
immediatelly synchronously. | ||
|
||
The following features can be configured for QCoro coroutines: | ||
|
||
## `CoroutineFeatures::guardThis(QObject *)` | ||
|
||
When coroutine is a member function of a QObject-derived class, it can happen that the `this` object | ||
is deleted while the coroutine is suspended. When the coroutine is resumed, the program would crash | ||
when it would try to dereference `this` due to use-after-free. To prevent this, a coroutine can | ||
guard the `this` pointer. If the object is destroyed while the coroutine is suspended, it will terminate | ||
immediatelly after resumption. | ||
|
||
```cpp | ||
QCoro::Task<> MyButton::onButtonClicked() | ||
{ | ||
auto &features = co_await QCoro::thisCoro(); | ||
features.guardThis(this); | ||
|
||
setLabel(tr("Downloading...")); | ||
const auto result = co_await fetchData(); | ||
// If the button is destroyed while the coroutine is suspended waiting for the `fetchData()` coroutine, | ||
// it will immediately terminate here once `fetchData()` finishes. | ||
// If `this` is still a valid pointer at this point, the coroutine will continue as usual. | ||
|
||
setLabel(tr("Done")); | ||
} | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// SPDX-FileCopyrightText: 2023 Daniel Vrátil <dvratil@kde.org> | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
#pragma once | ||
|
||
#include "coroutine.h" | ||
|
||
#include <optional> | ||
#include <QPointer> | ||
|
||
namespace QCoro { | ||
|
||
/*! \cond internal */ | ||
namespace detail { | ||
class TaskPromiseBase; | ||
struct ThisCoroPromise; | ||
} // namespace detail | ||
/*! \endcond */ | ||
|
||
|
||
//! A special coroutine that returns features of the current coroutine | ||
/*! | ||
* A special coroutine that can be co_awaited inside a current QCoro coroutine | ||
* to obtain QCoro::CoroutineFeatures object that allows the user to tune | ||
* certain features and behavior of the current coroutine. | ||
* | ||
* @see QCoro::CoroutineFeatures | ||
*/ | ||
auto thisCoro() -> detail::ThisCoroPromise; | ||
|
||
//! Features of the current coroutine | ||
/*! | ||
* Allows configuring behavior of the current coroutine. | ||
* | ||
* Use `co_await QCoro::thisCoro()` to obtain the current coroutine's Features | ||
* and modify them. | ||
* | ||
* @see QCoro::thisCoro() | ||
*/ | ||
class CoroutineFeatures : public std::suspend_never | ||
{ | ||
public: | ||
CoroutineFeatures(const CoroutineFeatures &) = delete; | ||
CoroutineFeatures(CoroutineFeatures &&) = delete; | ||
CoroutineFeatures &operator=(const CoroutineFeatures &) = delete; | ||
CoroutineFeatures &operator=(CoroutineFeatures &&) = delete; | ||
~CoroutineFeatures() = default; | ||
|
||
constexpr auto await_resume() noexcept -> CoroutineFeatures &; | ||
|
||
//! Bind the coroutine lifetime to the lifetime of the given object | ||
/*! | ||
* Watches the given \c obj object. If the object is destroyed while the | ||
* current coroutine is suspended, the coroutine will be destroyed immediately | ||
* after resuming to prevent a use-after-free "this" pointer. | ||
* | ||
* \param obj QObject to observe its lifetime. Pass `nullptr` to stop observing. | ||
*/ | ||
void guardThis(QObject *obj); | ||
|
||
//! Returns the currently guarded QObject | ||
/*! | ||
* Returns the currently guarded QObject set by guardThis(). If no object was being | ||
* guarded, the returned `std::optional` is empty. If the returned `std::optional` | ||
* is not empty, then an object was specified to be guarded. When the QPointer inside | ||
* the `std::optional` is empty it means that the guarded object has already been | ||
* destroyed. Otherwise pointer to the guarded QObject is obtained. | ||
*/ | ||
auto guardedThis() const -> const std::optional<QPointer<QObject>> &; | ||
|
||
private: | ||
std::optional<QPointer<QObject>> mGuardedThis; | ||
|
||
explicit CoroutineFeatures() = default; | ||
friend class QCoro::detail::TaskPromiseBase; | ||
}; | ||
|
||
} // namespace QCoro | ||
|
||
#include "impl/coroutinefeatures.h" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// SPDX-FileCopyrightText: 2023 Daniel Vrátil <dvratil@kde.org> | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
|
||
/* | ||
* Do NOT include this file directly - include the QCoroTask header instead! | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "../qcorotask.h" | ||
|
||
namespace QCoro { | ||
|
||
// \cond internal | ||
namespace detail { | ||
struct ThisCoroPromise | ||
{ | ||
private: | ||
ThisCoroPromise() = default; | ||
friend ThisCoroPromise QCoro::thisCoro(); | ||
}; | ||
} // namespace detail | ||
|
||
// \endcond | ||
|
||
inline auto thisCoro() -> detail::ThisCoroPromise | ||
{ | ||
return detail::ThisCoroPromise{}; | ||
} | ||
|
||
inline constexpr CoroutineFeatures & CoroutineFeatures::await_resume() noexcept | ||
{ | ||
return *this; | ||
} | ||
|
||
inline void CoroutineFeatures::guardThis(QObject *obj) | ||
{ | ||
if (obj == nullptr) { | ||
mGuardedThis.reset(); | ||
} else { | ||
mGuardedThis.emplace(obj); | ||
} | ||
} | ||
|
||
inline auto CoroutineFeatures::guardedThis() const -> const std::optional<QPointer<QObject>> & | ||
{ | ||
return mGuardedThis; | ||
} | ||
|
||
} // namespace QCoro | ||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.