aboutsummaryrefslogtreecommitdiff
path: root/pw_async/docs.rst
blob: 8ead20e04620521cf28f1c8a5b19468c2f9b3b23 (plain)
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
.. _module-pw_async:

================
pw_async
================

--------
Overview
--------
Pigweed's async module provides portable APIs and utilities for writing
asynchronous code. Currently, it provides:

- Message loop APIs

.. attention::
  This module is still under construction. The API is not yet stable.

----------
Dispatcher
----------
Dispatcher is an API for a message loop that schedules and executes Tasks. See
:bdg-ref-primary-line:`module-pw_async_basic` for an example implementation.

Dispatcher is a pure virtual interface that is implemented by backends and
FakeDispatcher. A virtual interface is used instead of a facade to allow
substituting a FakeDispatcher for a Dispatcher backend in tests.

Dispatcher API
==============
.. doxygenclass:: pw::async::Dispatcher
   :members:


Task API
==============
.. doxygenstruct:: pw::async::Context
   :members:

.. doxygentypedef:: pw::async::TaskFunction

.. doxygenclass:: pw::async::Task
   :members:

Facade API
==========

Task
----
The ``Task`` type represents a work item that can be submitted to and executed
by a ``Dispatcher``.

To run work on a ``Dispatcher`` event loop, a ``Task`` can be constructed from
a function or lambda (see ``pw::async::TaskFunction``) and submitted to run
using the ``pw::async::Dispatcher::Post`` method (and its siblings, ``PostAt``
etc.).

The ``Task`` facade enables backends to provide custom storage containers for
``Task`` s, as well as to keep per- ``Task`` data alongside the ``TaskFunction``
(such as ``next`` pointers for intrusive linked-lists of ``Task``).

The active Task backend is configured with the GN variable
``pw_async_TASK_BACKEND``. The specified target must define a class
``pw::async::backend::NativeTask`` in the header ``pw_async_backend/task.h``
that meets the interface requirements in ``public/pw_async/task.h``. Task will
then trivially wrap ``NativeTask``.

The bazel build provides the ``pw_async_task_backend`` label flag to configure
the active Task backend.

FakeDispatcher
--------------
The FakeDispatcher facade is a utility for simulating a real Dispatcher
in tests. FakeDispatcher simulates time to allow for reliable, fast testing of
code that uses Dispatcher. FakeDispatcher is a facade instead of a concrete
implementation because it depends on Task state for processing tasks, which
varies across Task backends.

The active FakeDispatcher backend is configured with the GN variable
``pw_async_FAKE_DISPATCHER_BACKEND``. The specified target must define a class
``pw::async::test::backend::NativeFakeDispatcher`` in the header
``pw_async_backend/fake_dispatcher.h`` that meets the interface requirements in
``public/pw_async/task.h``. FakeDispatcher will then trivially wrap
``NativeFakeDispatcher``.

The bazel build provides the ``pw_async_fake_dispatcher_backend`` label flag to
configure the FakeDispatcher backend.

Testing FakeDispatcher
^^^^^^^^^^^^^^^^^^^^^^
The GN template ``fake_dispatcher_tests`` in ``fake_dispatcher_tests.gni``
creates a test target that tests a FakeDispatcher backend. This enables
one test suite to be shared across FakeDispatcher backends and ensures
conformance.

FunctionDispatcher
------------------
.. doxygenclass:: pw::async::FunctionDispatcher
   :members:

HeapDispatcher
--------------
.. doxygenclass:: pw::async::HeapDispatcher
   :members:

Design
======

Task Ownership
--------------
Tasks are owned by clients rather than the Dispatcher. This avoids either
memory allocation or queue size limits in Dispatcher implementations. However,
care must be taken that clients do not destroy Tasks before they have been
executed or canceled.

Getting Started
===============
First, configure the Task backend for the Dispatcher backend you will be using:

.. code-block::

   pw_async_TASK_BACKEND = "$dir_pw_async_basic:task"


Next, create an executable target that depends on the Dispatcher backend you
want to use:

.. code-block::

   pw_executable("hello_world") {
     sources = [ "main.cc" ]
     deps = [ "$dir_pw_async_basic:dispatcher" ]
   }

Next, instantiate the Dispatcher and post a task:

.. code-block:: cpp

   #include "pw_async_basic/dispatcher.h"

   int main() {
     BasicDispatcher dispatcher;

     // Spawn a thread for the dispatcher to run on.
     thread::Thread work_thread(thread::stl::Options(), dispatcher);

     Task task([](pw::async::Context& ctx){
       printf("hello world\n");
       ctx.dispatcher->RequestStop();
     });

     // Execute `task` in 5 seconds.
     dispatcher.PostAfter(task, 5s);

     // Blocks until `task` runs.
     work_thread.join();
     return 0;
   }

The above example runs the dispatcher on a new thread, but it can also run on
the current/main thread:

.. code-block:: cpp

   #include "pw_async_basic/dispatcher.h"

   int main() {
     BasicDispatcher dispatcher;

     Task task([](pw::async::Context& ctx){
       printf("hello world\n");
     });

     // Execute `task` in 5 seconds.
     dispatcher.PostAfter(task, 5s);

     dispatcher.Run();
     return 0;
   }

Fake Dispatcher
===============
To test async code, FakeDispatcher should be dependency injected in place of
Dispatcher. Then, time should be driven in unit tests using the ``Run*()``
methods. For convenience, you can use the test fixture
FakeDispatcherFixture.

.. doxygenclass:: pw::async::test::FakeDispatcherFixture
   :members:

.. attention::

   ``FakeDispatcher::now()`` will return the simulated time.
   ``Dispatcher::now()`` should therefore be used to get the current time in
   async code instead of other sources of time to ensure consistent time values
   and reliable tests.

-------
Roadmap
-------
- Stabilize Task cancellation API
- Utility for dynamically allocated Tasks
- CMake support
- Support for C++20 coroutines