File: coroutine-hostile-raii.rst

package info (click to toggle)
llvm-toolchain-19 1%3A19.1.7-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,998,520 kB
  • sloc: cpp: 6,951,680; ansic: 1,486,157; asm: 913,598; python: 232,024; f90: 80,126; objc: 75,281; lisp: 37,276; pascal: 16,990; sh: 10,009; ml: 5,058; perl: 4,724; awk: 3,523; makefile: 3,167; javascript: 2,504; xml: 892; fortran: 664; cs: 573
file content (83 lines) | stat: -rw-r--r-- 3,137 bytes parent folder | download | duplicates (12)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
.. title:: clang-tidy - misc-coroutine-hostile-raii

misc-coroutine-hostile-raii
===========================

Detects when objects of certain hostile RAII types persists across suspension
points in a coroutine. Such hostile types include scoped-lockable types and
types belonging to a configurable denylist.

Some objects require that they be destroyed on the same thread that created them.
Traditionally this requirement was often phrased as "must be a local variable",
under the assumption that local variables always work this way. However this is
incorrect with C++20 coroutines, since an intervening ``co_await`` may cause the
coroutine to suspend and later be resumed on another thread.

The lifetime of an object that requires being destroyed on the same thread must
not encompass a ``co_await`` or ``co_yield`` point. If you create/destroy an object,
you must do so without allowing the coroutine to suspend in the meantime.

Following types are considered as hostile:

 - Scoped-lockable types: A scoped-lockable object persisting across a suspension
   point is problematic as the lock held by this object could be unlocked by a
   different thread. This would be undefined behaviour.
   This includes all types annotated with the ``scoped_lockable`` attribute.

 - Types belonging to a configurable denylist.

.. code-block:: c++

  // Call some async API while holding a lock.
  task coro() {
    const std::lock_guard l(&mu_);

    // Oops! The async Bar function may finish on a different
    // thread from the one that created the lock_guard (and called
    // Mutex::Lock). After suspension, Mutex::Unlock will be called on the wrong thread.
    co_await Bar();
  }

Options
-------

.. option:: RAIITypesList

    A semicolon-separated list of qualified types which should not be allowed to
    persist across suspension points.
    Eg: ``my::lockable; a::b;::my::other::lockable;``
    The default value of this option is `"std::lock_guard;std::scoped_lock"`.

.. option:: AllowedAwaitablesList

    A semicolon-separated list of qualified types of awaitables types which can
    be safely awaited while having hostile RAII objects in scope.

    ``co_await``-ing an expression of ``awaitable`` type is considered
    safe if the ``awaitable`` type is part of this list.
    RAII objects persisting across such a ``co_await`` expression are
    considered safe and hence are not flagged.

    Example usage:

    .. code-block:: c++

      // Consider option AllowedAwaitablesList = "safe_awaitable"
      struct safe_awaitable {
        bool await_ready() noexcept { return false; }
        void await_suspend(std::coroutine_handle<>) noexcept {}
        void await_resume() noexcept {}
      };
      auto wait() { return safe_awaitable{}; }

      task coro() {
        // This persists across both the co_await's but is not flagged
        // because the awaitable is considered safe to await on.
        const std::lock_guard l(&mu_);
        co_await safe_awaitable{};
        co_await wait();
      }

    Eg: ``my::safe::awaitable;other::awaitable``
    The default value of this option is empty string `""`.