Skip to content

Commit d52c21c

Browse files
authored
Merge pull request #8 from isocpp/master
Add CP.52 and CP.53 guidelines, closes #1805, closes #1806 (#1812)
2 parents 84fd3aa + ddc8f3e commit d52c21c

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

CppCoreGuidelines.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15023,6 +15023,8 @@ This section focuses on uses of coroutines.
1502315023
Coroutine rule summary:
1502415024

1502515025
* [CP.51: Do not use capturing lambdas that are coroutines](#Rcoro-capture)
15026+
* [CP.52: Do not hold locks or other synchronization primitives across suspension points](#Rcoro-locks)
15027+
* [CP.53: Parameters to coroutines should not be passed by reference](#Rcoro-reference-parameters)
1502615028

1502715029
### <a name="Rcoro-capture"></a>CP.51: Do not use capturing lambdas that are coroutines
1502815030

@@ -15082,6 +15084,84 @@ Use a function for coroutines.
1508215084
Flag a lambda that is a coroutine and has a non-empty capture list.
1508315085

1508415086

15087+
### <a name="Rcoro-locks"></a>CP.52: Do not hold locks or other synchronization primitives across suspension points
15088+
15089+
##### Reason
15090+
15091+
This pattern creates a significant risk of deadlocks. Some types of waits will allow the current thread to perform additional work until the asynchronous operation has completed. If the thread holding the lock performs work that requires the same lock then it will deadlock because it is trying to acquire a lock that it is already holding.
15092+
15093+
If the coroutine completes on a different thread from the thread that acquired the lock then that is undefined behavior. Even with an explicit return to the original thread an exception might be thrown before coroutine resumes and the result will be that the lock guard is not destructed.
15094+
15095+
##### Example, Bad
15096+
15097+
std::mutex g_lock;
15098+
15099+
std::future<void> Class::do_something()
15100+
{
15101+
std::lock_guard<std::mutex> guard(g_lock);
15102+
co_await something(); // DANGER: coroutine has suspended execution while holding a lock
15103+
co_await somethingElse();
15104+
}
15105+
15106+
##### Example, Good
15107+
15108+
std::mutex g_lock;
15109+
15110+
std::future<void> Class::do_something()
15111+
{
15112+
{
15113+
std::lock_guard<std::mutex> guard(g_lock);
15114+
// modify data protected by lock
15115+
}
15116+
co_await something(); // OK: lock has been released before coroutine suspends
15117+
co_await somethingElse();
15118+
}
15119+
15120+
15121+
##### Note
15122+
15123+
This pattern is also bad for performance. When a suspension point is reached, such as co_await, execution of the current function stops and other code begins to run. It may be a long period of time before the coroutine resumes. For that entire duration the lock will be held and cannot be acquired by other threads to perform work.
15124+
15125+
##### Enforcement
15126+
15127+
Flag all lock guards that are not destructed before a coroutine suspends.
15128+
15129+
### <a name="Rcoro-reference-parameters"></a>CP.53: Parameters to coroutines should not be passed by reference
15130+
15131+
##### Reason
15132+
15133+
Once a coroutine reaches the first suspension point, such as a co_await, the synchronous portion returns. After that point any parameters passed by reference are dangling. Any usage beyond that is undefined behavior which may include writing to freed memory.
15134+
15135+
##### Example, Bad
15136+
15137+
std::future<int> Class::do_something(const std::shared_ptr<int>& input)
15138+
{
15139+
co_await something();
15140+
15141+
// DANGER: the reference to input may no longer be valid and may be freed memory
15142+
co_return *input + 1;
15143+
}
15144+
15145+
##### Example, Good
15146+
15147+
std::future<int> Class::do_something(std::shared_ptr<int> input)
15148+
{
15149+
co_await something();
15150+
co_return *input + 1; // input is a copy that is still valid here
15151+
}
15152+
15153+
##### Note
15154+
15155+
This problem does not apply to reference parameters that are only accessed before the first suspension point. Subsequent changes to the function may add or move suspension points which would reintroduce this class of bug. Some types of coroutines have the suspension point before the first line of code in the coroutine executes, in which case reference parameters are always unsafe. It is safer to always pass by value because the copied parameter will live in the coroutine frame that is safe to access throughout the coroutine.
15156+
15157+
##### Note
15158+
15159+
The same danger applies to output parameters. [F.20: For "out" output values, prefer return values to output parameters](#Rf-out) discourages output parameters. Coroutines should avoid them entirely.
15160+
15161+
##### Enforcement
15162+
15163+
Flag all reference parameters to a coroutine.
15164+
1508515165
## <a name="SScp-par"></a>CP.par: Parallelism
1508615166

1508715167
By "parallelism" we refer to performing a task (more or less) simultaneously ("in parallel with") on many data items.

0 commit comments

Comments
 (0)