Create rule S6365: Use symmetric transfer to switch execution between coroutines
This commit is contained in:
parent
a5bc5b1473
commit
b006f130e4
19
rules/S6365/cfamily/metadata.json
Normal file
19
rules/S6365/cfamily/metadata.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"title": "Use symmetric transfer to switch execution between coroutines",
|
||||||
|
"type": "CODE_SMELL",
|
||||||
|
"status": "ready",
|
||||||
|
"remediation": {
|
||||||
|
"func": "Constant\/Issue",
|
||||||
|
"constantCost": "5min"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"pitfall",
|
||||||
|
"since-c++20"
|
||||||
|
],
|
||||||
|
"defaultSeverity": "Major",
|
||||||
|
"ruleSpecification": "RSPEC-6365",
|
||||||
|
"sqKey": "S6365",
|
||||||
|
"scope": "All",
|
||||||
|
"defaultQualityProfiles": ["Sonar way"],
|
||||||
|
"quickfix": "unknown"
|
||||||
|
}
|
100
rules/S6365/cfamily/rule.adoc
Normal file
100
rules/S6365/cfamily/rule.adoc
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
With {cpp}20 coroutines, the `co_await`/`co_yield` expression suspends the currently executed coroutine and resumes the execution of either the caller or the coroutine function
|
||||||
|
or to some already suspended coroutine (including the current coroutine).
|
||||||
|
|
||||||
|
The resumption of the coroutine represented by the `std::coroutine_handle` object is usually performed by calling the `.resume()` on it.
|
||||||
|
However, performing such an operation during the execution of `await_suspend` (that is part of `co_await` expression evaluation) will preserve the activation frame of the `await_suspend` function and the calling code on the stack.
|
||||||
|
This may lead to stack overflows in a situation where the chain of directly resumed coroutines is deep enough.
|
||||||
|
|
||||||
|
The use of the symmetric transfer may avoid this problem. When the `await_suspend` function returns a `std::coroutine_handle`, the compiler will automatically use this handle to resume its coroutine after `await_suspend` returns (and its activation frame is removed from the stack).
|
||||||
|
Or, when a `std::noop_coroutine_handle` is returned, the execution will be passed to the caller.
|
||||||
|
|
||||||
|
Symmetric transfer solution can also be used to resume the current coroutine (by returning handle passed as the parameter).
|
||||||
|
However, in such cases, conditional suspension can be a more optimal solution.
|
||||||
|
|
||||||
|
This rule raises an issue on `await_suspend` functions that could use symmetric transfer.
|
||||||
|
|
||||||
|
== Noncompliant Code Example
|
||||||
|
|
||||||
|
----
|
||||||
|
struct InvokeOtherAwaiter {
|
||||||
|
/* .... */
|
||||||
|
void await_suspend(std::coroutine_handle<PromiseType> current) {
|
||||||
|
if (auto other = current.promise().other_handle) {
|
||||||
|
other.resume(); // Noncompliant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WaitForAwaiter {
|
||||||
|
Event& event;
|
||||||
|
/* .... */
|
||||||
|
void await_suspend(std::coroutine_handle<> current) {
|
||||||
|
if (bool ready = event.register_callback(current)) {
|
||||||
|
current.resume(); // Noncompliant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BufferedExecutionAwaiter {
|
||||||
|
std::queue<std::coroutine_handle<>>& taskQueue;
|
||||||
|
/* .... */
|
||||||
|
void await_suspend(std::coroutine_handle<> current) {
|
||||||
|
if (taskQueue.empty()) {
|
||||||
|
current.resume(); // Noncompliant
|
||||||
|
}
|
||||||
|
auto next = taskQueue.front();
|
||||||
|
taskQueue.pop();
|
||||||
|
taskQueue.push(current);
|
||||||
|
next.resume(); // Noncompliant
|
||||||
|
}
|
||||||
|
};
|
||||||
|
----
|
||||||
|
|
||||||
|
== Compliant Solution
|
||||||
|
|
||||||
|
----
|
||||||
|
struct InvokeOtherAwaiter {
|
||||||
|
/* .... */
|
||||||
|
std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseType> current) {
|
||||||
|
if (auto other = current.promise().other_handle) {
|
||||||
|
return other;
|
||||||
|
} else {
|
||||||
|
return std::noop_coroutine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WaitForAwaiter {
|
||||||
|
Event& event;
|
||||||
|
/* .... */
|
||||||
|
std::coroutine_handle<> await_suspend(std::coroutine_handle<> current) {
|
||||||
|
if (bool ready = event.register_callback(current)) {
|
||||||
|
return current;
|
||||||
|
} else {
|
||||||
|
return std::noop_coroutine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Alternatively
|
||||||
|
bool await_suspend(std::coroutine_handle<> current) {
|
||||||
|
return !event.register_callback(current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BufferedExecutionAwaiter {
|
||||||
|
std::queue<std::coroutine_handle<>>& taskQueue;
|
||||||
|
/* .... */
|
||||||
|
std::coroutine_handle<> await_suspend(std::coroutine_handle<> current) {
|
||||||
|
if (taskQueue.empty()) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
auto next = list.front();
|
||||||
|
taskQueue.pop();
|
||||||
|
taskQueue.push(current);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
----
|
||||||
|
|
||||||
|
== See
|
||||||
|
|
||||||
|
S6366 - conditionally suspending current coroutine in optimal way
|
2
rules/S6365/metadata.json
Normal file
2
rules/S6365/metadata.json
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user