aboutsummaryrefslogtreecommitdiff
path: root/pw_thread/docs.rst
blob: dcc9f03369fa53931388998fa0c6a82e8532b372 (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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
.. _module-pw_thread:

=========
pw_thread
=========
The ``pw_thread`` module contains utilities for thread creation and thread
execution.

.. Warning::
  This module is still under construction, the API is not yet stable.

---------------
Thread Sleeping
---------------
C++
===
.. cpp:function:: void pw::this_thread::sleep_for(chrono::SystemClock::duration sleep_duration)

   Blocks the execution of the current thread for at least the specified
   duration. This function may block for longer due to scheduling or resource
   contention delays.

   A sleep duration of 0 will at minimum yield, meaning it will provide a hint
   to the implementation to reschedule the execution of threads, allowing other
   threads to run.

   **Precondition:** This can only be called from a thread, meaning the
   scheduler is running.

.. cpp:function:: void pw::this_thread::sleep_until(chrono::SystemClock::time_point wakeup_time)

   Blocks the execution of the current thread until at least the specified
   time has been reached. This function may block for longer due to scheduling
   or resource contention delays.

   A sleep deadline in the past up to the current time will at minimum yield
   meaning it will provide a hint to the implementation to reschedule the
   execution of threads, allowing other threads to run.

   **Precondition:** This can only be called from a thread, meaning the
   scheduler is running.

Examples in C++
---------------
.. code-block:: cpp

  #include <chrono>

  #include "pw_chrono/system_clock.h"
  #include "pw_thread/sleep.h"

  using std::literals::chrono_literals::ms;

  void FunctionInvokedByThread() {
    pw::this_thread::sleep_for(42ms);
  }

  void AnotherFunctionInvokedByThread() {
    pw::this_thread::sleep_until(pw::chrono::SystemClock::now() + 42ms);
  }

C
=
.. cpp:function:: void pw_this_thread_SleepFor(pw_chrono_SystemClock_Duration sleep_duration)

   Invokes ``pw::this_thread::sleep_until(sleep_duration)``.

.. cpp:function:: void pw_this_thread_SleepUntil(pw_chrono_SystemClock_TimePoint wakeup_time)

   Invokes ``pw::this_thread::sleep_until(wakeup_time)``.


---------------
Thread Yielding
---------------
C++
===
.. cpp:function:: void pw::this_thread::yield() noexcept

   Provides a hint to the implementation to reschedule the execution of threads,
   allowing other threads to run.

   The exact behavior of this function depends on the implementation, in
   particular on the mechanics of the OS scheduler in use and the state of the
   system.

   **Precondition:** This can only be called from a thread, meaning the
   scheduler is running.

Example in C++
---------------
.. code-block:: cpp

  #include "pw_thread/yield.h"

  void FunctionInvokedByThread() {
    pw::this_thread::yield();
  }

C
=
.. cpp:function:: void pw_this_thread_Yield(void)

   Invokes ``pw::this_thread::yield()``.

---------------------
Thread Identification
---------------------
The class ``pw::thread::Id`` is a lightweight, trivially copyable class that
serves as a unique identifier of Thread objects.

Instances of this class may also hold the special distinct value that does
not represent any thread. Once a thread has finished, the value of its
Thread::id may be reused by another thread.

This class is designed for use as key in associative containers, both ordered
and unordered.

Although the current API is similar to C++11 STL
`std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_, it is
missing the required hashing and streaming operators and may diverge further in
the future.

A thread's identification (``pw::thread::Id``) can be acquired only in C++ in
one of two ways:

1) Using the ``pw::thread::Thread`` handle's ``pw::thread::Id get_id() const``
   method.
2) While executing the thread using
   ``pw::thread::Id pw::this_thread::get_id() noexcept``.

.. cpp:function:: pw::thread::Id pw::this_thread::get_id() noexcept

   This is thread safe, not IRQ safe. It is implementation defined whether this
   is safe before the scheduler has started.


Example
=======
.. code-block:: cpp

  #include "pw_thread/id.h"

  void FunctionInvokedByThread() {
    const pw::thread::Id my_id = pw::this_thread::get_id();
  }


.. _module-pw_thread-thread-creation:

---------------
Thread Creation
---------------
The class ``pw::thread::Thread`` can represent a single thread of execution.
Threads allow multiple functions to execute concurrently.

The Thread's API is C++11 STL
`std::thread <https://en.cppreference.com/w/cpp/thread/thread>`_ like, meaning
the object is effectively a thread handle and not an object which contains the
thread's context. Unlike ``std::thread``, the API requires
``pw::thread::Options`` as an argument and is limited to only work with
``pw::thread::ThreadCore`` objects and functions which match the
``pw::thread::Thread::ThreadRoutine`` signature.

We recognize that the C++11's STL ``std::thread`` API has some drawbacks where
it is easy to forget to join or detach the thread handle. Because of this, we
offer helper wrappers like the ``pw::thread::DetachedThread``. Soon we will
extend this by also adding a ``pw::thread::JoiningThread`` helper wrapper which
will also have a lighter weight C++20 ``std::jthread`` like cooperative
cancellation contract to make joining safer and easier.

Threads may begin execution immediately upon construction of the associated
thread object (pending any OS scheduling delays), starting at the top-level
function provided as a constructor argument. The return value of the
top-level function is ignored. The top-level function may communicate its
return value by modifying shared variables (which may require
synchronization, see :ref:`module-pw_sync`)

Thread objects may also be in the state that does not represent any thread
(after default construction, move from, detach, or join), and a thread of
execution may be not associated with any thread objects (after detach).

No two Thread objects may represent the same thread of execution; Thread is
not CopyConstructible or CopyAssignable, although it is MoveConstructible and
MoveAssignable.

.. list-table::

  * - *Supported on*
    - *Backend module*
  * - FreeRTOS
    - :ref:`module-pw_thread_freertos`
  * - ThreadX
    - :ref:`module-pw_thread_threadx`
  * - embOS
    - :ref:`module-pw_thread_embos`
  * - STL
    - :ref:`module-pw_thread_stl`
  * - Zephyr
    - Planned
  * - CMSIS-RTOS API v2 & RTX5
    - Planned

Module Configuration Options
============================
The following configurations can be adjusted via compile-time configuration of
this module, see the
:ref:`module documentation <module-structure-compile-time-configuration>` for
more details.

.. c:macro:: PW_THREAD_CONFIG_LOG_LEVEL

  The log level to use for this module. Logs below this level are omitted.

Options
=======
The ``pw::thread::Options`` contains the parameters or attributes needed for a
thread to start.

Pigweed does not generalize options, instead we strive to give you full control
where we provide helpers to do this.

Options are backend specific and ergo the generic base class cannot be
directly instantiated.

The attributes which can be set through the options are backend specific
but may contain things like the thread name, priority, scheduling policy,
core/processor affinity, and/or an optional reference to a pre-allocated
Context (the collection of memory allocations needed for a thread to run).

Options shall NOT have an attribute to start threads as detached vs joinable.
All ``pw::thread::Thread`` instances must be explicitly ``join()``'d or
``detach()``'d through the run-time Thread API.

Note that if backends set ``PW_THREAD_JOINING_ENABLED`` to false, backends
may use native OS specific APIs to create native detached threads because the
``join()`` API would be compiled out. However, users must still explicitly
invoke ``detach()``.

Options must not contain any memory needed for a thread to run (TCB,
stack, etc.). The Options may be deleted or re-used immediately after
starting a thread.

Options subclass must contain non-default explicit constructor (parametrized or
not), e.g. ``constexpr Options() {}``. It is not enough to have them as
``= default`` ones, because C++17 considers subclasses like ``stl::Options`` as
aggregate classes if they have a default constructor and requires base class
constructor to be public (which is not the case for the ``thread::Options``) for
``Options{}`` syntax.

Please see the thread creation backend documentation for how their Options work.

Portable Thread Creation
========================
Due to the fact that ``pw::thread::Options`` cannot be created in portable code,
some extra work must be done in order to permit portable thread creation.
Namely, a reference to the portable ``pw::thread::Options`` base class interface
must be provided through a header or extern which points to an instantiation in
non-portable code.

This can be most easily done through a facade and set of backends. This approach
can be powerful; enabling multithreaded unit/integration testing which can run
on both the host and on a device with the device's exact thread options.

Alternatively, it can also be be injected at build time by instantiating backend
specific build rule which share the same common portable source file(s) but
select backend specific source files and/or dependencies which provide the
non-portable option instantiations.

As an example, let's say we want to create a thread on the host and on a device
running FreeRTOS. They could use a facade which contains a ``threads.h`` header
with the following contents:

.. code-block:: cpp

  // Contents of my_app/threads.h
  #pragma once

  #include "pw_thread/thread.h"

  namespace my_app {

  const pw::thread::Options& HellowWorldThreadOptions();

  }  // namespace my_app

This could then be backed by two different backend implementations based on
the thread backend. For example for the STL the backend's ``stl_threads.cc``
source file may look something like:

.. code-block:: cpp

  // Contents of my_app/stl_threads.cc
  #include "my_app/threads.h"
  #include "pw_thread_stl/options.h"

  namespace my_app {

  const pw::thread::Options& HelloWorldThreadOptions() {
    static constexpr auto options = pw::thread::stl::Options();
    return options;
  }

  }  // namespace my_app

While for FreeRTOS the backend's ``freertos_threads.cc`` source file may look
something like:

.. code-block:: cpp

  // Contents of my_app/freertos_threads.cc
  #include "FreeRTOS.h"
  #include "my_app/threads.h"
  #include "pw_thread_freertos/context.h"
  #include "pw_thread_freertos/options.h"
  #include "task.h"

  namespace my_app {

  StaticContextWithStack<kHelloWorldStackWords> hello_world_thread_context;
  const pw::thread::Options& HelloWorldThreadOptions() {
    static constexpr auto options =
        pw::thread::freertos::Options()
            .set_name("HelloWorld")
            .set_static_context(hello_world_thread_context)
            .set_priority(kHelloWorldThreadPriority);
    return options;
  }

  }  // namespace my_app

Detaching & Joining
===================
The ``Thread::detach()`` API is always available, to let you separate the
thread of execution from the thread object, allowing execution to continue
independently.

The joining API, more specifically ``Thread::join()``, is conditionally
available depending on the selected backend for thread creation and how it is
configured. The backend is responsible for providing the
``PW_THREAD_JOINING_ENABLED`` macro through
``pw_thread_backend/thread_native.h``. This ensures that any users which include
``pw_thread/thread.h`` can use this macro if needed.

Please see the selected thread creation backend documentation for how to
enable joining if it's not already enabled by default.

.. Warning::
  A constructed ``pw::thread::Thread`` which represents a thread of execution
  must be EITHER detached or joined, else the destructor will assert!

DetachedThread
==============
To make it slightly easier and cleaner to spawn detached threads without having
to worry about thread handles, a wrapper ``DetachedThread()`` function is
provided which creates a ``Thread`` and immediately detaches it. For example
instead of:

.. code-block:: cpp

  Thread(options, foo).detach();

You can instead use this helper wrapper to:

.. code-block:: cpp

   DetachedThread(options, foo);

The arguments are directly forwarded to the Thread constructor and ergo exactly
match the Thread constuctor arguments for creating a thread of execution.


ThreadRoutine & ThreadCore
==========================
Threads must either be invoked through a
``pw::thread::Thread::ThreadRoutine`` style function or implement the
``pw::thread::ThreadCore`` interface.

.. code-block:: cpp

  namespace pw::thread {

  // This function may return.
  using Thread::ThreadRoutine = void (*)(void* arg);

  class ThreadCore {
   public:
    virtual ~ThreadCore() = default;

    // The public API to start a ThreadCore, note that this may return.
    void Start() { Run(); }

   private:
    // This function may return.
    virtual void Run() = 0;
  };

  }  // namespace pw::thread;


To use the ``pw::thread::Thread::ThreadRoutine``, your function must have the
following signature:

.. code-block:: cpp

  void example_thread_entry_function(void *arg);


To invoke a member method of a class a static lambda closure can be used
to ensure the dispatching closure is not destructed before the thread is
done executing. For example:

.. code-block:: cpp

  class Foo {
   public:
    void DoBar() {}
  };
  Foo foo;

  static auto invoke_foo_do_bar = [](void *void_foo_ptr) {
      //  If needed, additional arguments could be set here.
      static_cast<Foo*>(void_foo_ptr)->DoBar();
  };

  // Now use the lambda closure as the thread entry, passing the foo's
  // this as the argument.
  Thread thread(options, invoke_foo_do_bar, &foo);
  thread.detach();


Alternatively, the aforementioned ``pw::thread::ThreadCore`` interface can be
be implemented by an object by overriding the private
``void ThreadCore::Run();`` method. This makes it easier to create a thread, as
a static lambda closure or function is not needed to dispatch to a member
function without arguments. For example:

.. code-block:: cpp

  class Foo : public ThreadCore {
   private:
    void Run() override {}
  };
  Foo foo;

  // Now create the thread, using foo directly.
  Thread(options, foo).detach();

.. Warning::
  Because the thread may start after the pw::Thread creation, an object which
  implements the ThreadCore MUST meet or exceed the lifetime of its thread of
  execution!

-------------------------
Unit testing with threads
-------------------------
.. doxygenclass:: pw::thread::test::TestThreadContext
   :members:

As an example, the STL :cpp:class:`TestThreadContext` backend implementation in
``test_thread_context_native.h`` is shown below.

.. literalinclude:: ../pw_thread_stl/public/pw_thread_stl/test_thread_context_native.h
   :language: cpp
   :lines: 18-36

----------------
Thread Iteration
----------------
C++
===
.. cpp:function:: Status ForEachThread(const ThreadCallback& cb)

   Calls the provided callback for each thread that has not been joined/deleted.

   This function provides a generalized subset of information that a TCB might
   contain to make it easier to introspect system state. Depending on the RTOS
   and its configuration, some of these fields may not be populated, so it is
   important to check that they have values before attempting to access them.

   **Warning:**  The function may disable the scheduler to perform
   a runtime capture of thread information.

-----------------------
Thread Snapshot Service
-----------------------
``pw_thread`` offers an optional RPC service library
(``:thread_snapshot_service``) that enables thread info capture of
running threads on a device at runtime via RPC. The service will guide
optimization of stack usage by providing an overview of thread information,
including thread name, stack bounds, and peak stack usage.

``ThreadSnapshotService`` currently supports peak stack usage capture for
all running threads (``ThreadSnapshotService::GetPeakStackUsage()``) as well as
for a specific thread, filtering by name
(``ThreadSnapshotService::GetPeakStackUsage(name=b"/* thread name */")``).
Thread information capture relies on the thread iteration facade which will
**momentarily halt your RTOS**, collect information about running threads, and
return this information through the service.

RPC service setup
=================
To expose a ``ThreadSnapshotService`` in your application, do the following:

1. Create an instance of ``pw::thread::proto::ThreadSnapshotServiceBuffer``.
   This template takes the number of expected threads, and uses it to properly
   size buffers required for a ``ThreadSnapshotService``. If no thread count
   argument is provided, this defaults to ``PW_THREAD_MAXIMUM_THREADS``.
2. Register the service with your RPC server.

For example:

.. code-block::

   #include "pw_rpc/server.h"
   #include "pw_thread/thread_snapshot_service.h"

   // Note: You must customize the RPC server setup; see pw_rpc.
   pw::rpc::Channel channels[] = {
    pw::rpc::Channel::Create<1>(&uart_output),
   };
   Server server(channels);

  // Thread snapshot service builder instance.
  pw::thread::proto::ThreadSnapshotServiceBuffer</*num threads*/>
      thread_snapshot_service;

   void RegisterServices() {
     server.RegisterService(thread_snapshot_service);
     // Register other services here.
   }

   void main() {
     // ... system initialization ...

     RegisterServices();

     // ... start your application ...
   }

.. c:macro:: PW_THREAD_MAXIMUM_THREADS

  The max number of threads to use by default for thread snapshot service.

.. cpp:function:: constexpr size_t RequiredServiceBufferSize(const size_t num_threads)

  Function provided through the service to calculate buffer sizing. If no
  argument ``num_threads`` is specified, the function will take ``num_threads``
  to be ``PW_THREAD_MAXIMUM_THREADS``.

.. attention::
    Some platforms may only support limited subsets of this service
    depending on RTOS configuration. **Ensure that your RTOS is configured
    properly before using this service.** Please see the thread iteration
    documentation for your backend for more detail on RTOS support.

-----------------------
pw_snapshot integration
-----------------------
``pw_thread`` provides some light, optional integration with pw_snapshot through
helper functions for populating a ``pw::thread::Thread`` proto. Some of these
are directly integrated into the RTOS thread backends to simplify the thread
state capturing for snapshots.

SnapshotStack()
===============
The ``SnapshotStack()`` helper captures stack metadata (stack pointer and
bounds) into a ``pw::thread::Thread`` proto. After the stack bounds are
captured, execution is passed off to the thread stack collection callback to
capture a backtrace or stack dump. Note that this function does NOT capture the
thread name: that metadata is only required in cases where a stack overflow or
underflow is detected.

Python processor
================
Threads captured as a Thread proto message can be dumped or further analyzed
using using ``pw_thread``'s Python module. This is directly integrated into
pw_snapshot's processor tool to automatically provide rich thread state dumps.

The ``ThreadSnapshotAnalyzer`` class may also be used directly to identify the
currently running thread and produce symbolized thread dumps.

.. Warning::
  Snapshot integration is a work-in-progress and may see significant API
  changes.