xref: /aosp_15_r20/external/pigweed/pw_async2/guides.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _module-pw_async2-quickstart-guides:
2
3===================
4Quickstart & guides
5===================
6.. pigweed-module-subpage::
7   :name: pw_async2
8
9.. _module-pw_async2-guides:
10
11------
12Guides
13------
14
15Implementing tasks
16==================
17:cpp:class:`pw::async2::Task` instances complete one or more asynchronous
18operations. They are the top-level "thread" primitives of ``pw_async2``.
19
20You can use one of the concrete subclasses of ``Task`` that Pigweed provides:
21
22* :cpp:class:`pw::async2::CoroOrElseTask`: Delegates to a provided
23  coroutine and executes an ``or_else`` handler function on failure.
24* :cpp:class:`pw::async2::PendFuncTask`: Delegates to a provided
25  function.
26* :cpp:class:`pw::async2::PendableAsTask`: Delegates to a type
27  with a :cpp:func:`pw::async2::Pend` method.
28* :cpp:func:`pw::async2::AllocateTask`: Creates a concrete subclass of
29  ``Task``, just like ``PendableAsTask``, but the created task is
30  dynamically allocated and frees the associated memory upon
31  completion.
32
33Or you can subclass ``Task`` yourself. See :cpp:class:`pw::async2::Task`
34for more guidance on subclassing.
35
36.. _module-pw_async2-guides-tasks:
37
38How a dispatcher manages tasks
39==============================
40The purpose of a :cpp:class:`pw::async2::Dispatcher` is to keep track of a set
41of :cpp:class:`pw::async2::Task` objects and run them to completion. The
42dispatcher is essentially a scheduler for cooperatively-scheduled
43(non-preemptive) threads (tasks).
44
45While a dispatcher is running, it waits for one or more tasks to waken and then
46advances each task by invoking its :cpp:func:`pw::async2::Task::DoPend` method.
47The ``DoPend`` method is typically implemented manually by users, though it is
48automatically provided by coroutines.
49
50If the task is able to complete, ``DoPend`` will return ``Ready``, in which case
51the task is then deregistered from the dispatcher.
52
53If the task is unable to complete, ``DoPend`` must return ``Pending`` and arrange
54for the task to be woken up when it is able to make progress again. Once the
55task is rewoken, the task is re-added to the ``Dispatcher`` queue. The
56dispatcher will then invoke ``DoPend`` once more, continuing the cycle until
57``DoPend`` returns ``Ready`` and the task is completed.
58
59The following sequence diagram summarizes the basic workflow:
60
61.. mermaid::
62
63   sequenceDiagram
64       participant e as External Event e.g. Interrupt
65       participant d as Dispatcher
66       participant t as Task
67       e->>t: Init Task
68       e->>d: Register task via Dispatcher::Post(Task)
69       d->>d: Add task to queue
70       d->>t: Run task via Task::DoPend()
71       t->>t: Task is waiting for data and can't yet complete
72       t->>e: Arrange for rewake via PW_ASYNC_STORE_WAKER
73       t->>d: Indicate that task is not complete via Pending()
74       d->>d: Remove task from queue
75       d->>d: Go to sleep because task queue is empty
76       e->>e: The data that the task needs has arrived
77       e->>d: Rewake via Waker::Wake()
78       d->>d: Re-add task to queue
79       d->>t: Run task via Task::DoPend()
80       t->>t: Task runs to completion
81       t->>d: Indicate that task is complete via Ready()
82       d->>d: Deregister the task
83
84.. _module-pw_async2-guides-pendables:
85
86Implementing invariants for pendable functions
87==============================================
88.. _invariants: https://stackoverflow.com/a/112088
89
90Any ``Pend``-like function or method similar to
91:cpp:func:`pw::async2::Task::DoPend` that can pause when it's not able
92to make progress on its task is known as a **pendable function**. When
93implementing a pendable function, make sure that you always uphold the
94following `invariants`_:
95
96* :ref:`module-pw_async2-guides-pendables-incomplete`
97* :ref:`module-pw_async2-guides-pendables-complete`
98
99.. note:: Exactly which APIs are considered pendable?
100
101   If it has the signature ``(Context&, ...) -> Poll<T>``,
102   then it's a pendable function.
103
104.. _module-pw_async2-guides-pendables-incomplete:
105
106Arranging future completion of incomplete tasks
107-----------------------------------------------
108When your pendable function can't yet complete:
109
110#. Do one of the following to make sure the task rewakes when it's ready to
111   make more progress:
112
113   * Delegate waking to a subtask. Arrange for that subtask's
114     pendable function to wake this task when appropriate.
115
116   * Arrange an external wakeup. Use :c:macro:`PW_ASYNC_STORE_WAKER`
117     to store the task's waker somewhere, and then call
118     :cpp:func:`pw::async2::Waker::Wake` from an interrupt or another thread
119     once the event that the task is waiting for has completed.
120
121   * Re-enqueue the task with :cpp:func:`pw::async2::Context::ReEnqueue`.
122     This is a rare case. Usually, you should just create an immediately
123     invoked ``Waker``.
124
125#. Make sure to return :cpp:type:`pw::async2::Pending` to signal that the task
126   is incomplete.
127
128In other words, whenever your pendable function returns
129:cpp:type:`pw::async2::Pending`, you must guarantee that
130:cpp:func:`pw::async2::Context::Wake` is called once in the future.
131
132For example, one implementation of a delayed task might arrange for its ``Waker``
133to be woken by a timer once some time has passed. Another case might be a
134messaging library which calls ``Wake()`` on the receiving task once a sender has
135placed a message in a queue.
136
137.. _module-pw_async2-guides-pendables-complete:
138
139Cleaning up complete tasks
140--------------------------
141When your pendable function has completed, make sure to return
142:cpp:type:`pw::async2::Ready` to signal that the task is complete.
143
144.. _module-pw_async2-guides-passing-data:
145
146Passing data between tasks
147==========================
148Astute readers will have noticed that the ``Wake`` method does not take any
149arguments, and ``DoPoll`` does not provide the task being polled with any
150values!
151
152Unlike callback-based interfaces, tasks (and the libraries they use)
153are responsible for storage of the inputs and outputs of events. A common
154technique is for a task implementation to provide storage for outputs of an
155event. Then, upon completion of the event, the outputs will be stored in the
156task before it is woken. The task will then be invoked again by the
157dispatcher and can then operate on the resulting values.
158
159This common pattern is implemented by the
160:cpp:class:`pw::async2::OnceSender` and
161:cpp:class:`pw::async2::OnceReceiver` types (and their ``...Ref`` counterparts).
162These interfaces allow a task to asynchronously wait for a value:
163
164.. tab-set::
165
166   .. tab-item:: Manual ``Task`` State Machine
167
168      .. literalinclude:: examples/once_send_recv.cc
169         :language: cpp
170         :linenos:
171         :start-after: [pw_async2-examples-once-send-recv-manual]
172         :end-before: [pw_async2-examples-once-send-recv-manual]
173
174   .. tab-item:: Coroutine Function
175
176      .. literalinclude:: examples/once_send_recv.cc
177         :language: cpp
178         :linenos:
179         :start-after: [pw_async2-examples-once-send-recv-coro]
180         :end-before: [pw_async2-examples-once-send-recv-coro]
181
182More primitives (such as ``MultiSender`` and ``MultiReceiver``) are in-progress.
183Users who find that they need other async primitives are encouraged to
184contribute them upstream to ``pw::async2``!
185
186.. _module-pw_async2-guides-coroutines:
187
188Coroutines
189==========
190C++20 users can define tasks using coroutines!
191
192.. literalinclude:: examples/basic.cc
193   :language: cpp
194   :linenos:
195   :start-after: [pw_async2-examples-basic-coro]
196   :end-before: [pw_async2-examples-basic-coro]
197
198Any value with a ``Poll<T> Pend(Context&)`` method can be passed to
199``co_await``, which will return with a ``T`` when the result is ready.
200
201To return from a coroutine, ``co_return <expression>`` must be used instead of
202the usual ``return <expression>`` syntax. Because of this, the
203:c:macro:`PW_TRY` and :c:macro:`PW_TRY_ASSIGN` macros are not usable within
204coroutines. :c:macro:`PW_CO_TRY` and :c:macro:`PW_CO_TRY_ASSIGN` should be
205used instead.
206
207For a more detailed explanation of Pigweed's coroutine support, see
208:cpp:class:`pw::async2::Coro`.
209
210.. _module-pw_async2-guides-timing:
211
212Timing
213======
214When using ``pw::async2``, timing functionality should be injected
215by accepting a :cpp:class:`pw::async2::TimeProvider` (most commonly
216``TimeProvider<SystemClock>`` when using the system's built-in ``time_point``
217and ``duration`` types).
218
219:cpp:class:`pw::async2::TimeProvider` allows for easily waiting
220for a timeout or deadline using the
221:cpp:func:`pw::async2::TimePoint::WaitFor` and
222:cpp:func:`pw::async2::TimePoint::WaitUntil` methods.
223
224Additionally, code which uses :cpp:class:`pw::async2::TimeProvider` for timing
225can be tested with simulated time using
226:cpp:class:`pw::async2::SimulatedTimeProvider`. Doing so helps avoid
227timing-dependent test flakes and helps ensure that tests are fast since they
228don't need to wait for real-world time to elapse.
229
230.. _module-pw_async2-guides-faqs:
231
232---------------------------------
233Frequently asked questions (FAQs)
234---------------------------------
235