xref: /aosp_15_r20/external/pigweed/pw_multisink/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _module-pw_multisink:
2
3============
4pw_multisink
5============
6This is an module that forwards messages to multiple attached sinks, which
7consume messages asynchronously. It is not ready for use and is under
8construction.
9
10Module Configuration Options
11============================
12The following configurations can be adjusted via compile-time configuration
13of this module, see the
14:ref:`module documentation <module-structure-compile-time-configuration>` for
15more details.
16
17.. c:macro:: PW_MULTISINK_CONFIG_LOCK_TYPE
18
19  Set to configure the underlying lock used to guard multisink read/write
20  operations.  By default, this is set to uses an interrupt spin-lock to guard
21  the multisink transactions. Should be set to one of the following options:
22
23  - PW_MULTISINK_INTERRUPT_SPIN_LOCK: Use an interrupt spin lock
24  - PW_MULTISINK_MUTEX: Use a mutex. Not safe to use multisink in interrupt
25    context
26  - PW_MULTISINK_VIRTUAL_LOCK: User provided locking implementation. Interrupt
27    support will be left to the user to manage.
28
29
30.. _module-pw_multisink-late_drain_attach:
31
32Late Drain Attach
33=================
34It is possible to push entries or inform the multisink of drops before any
35drains are attached to it, allowing you to defer the creation of the drain
36further into an application. The multisink maintains the location and drop
37count of the oldest drain and will set drains to match on attachment. This
38permits drains that are attached late to still consume any entries that were
39pushed into the ring buffer, so long as those entries have not yet been evicted
40by newer entries. This may be particularly useful in early-boot scenarios where
41drain consumers may need time to initialize their output paths. Listeners are
42notified immediately when attached, to allow late drain users to consume
43existing entries. If draining in response to the notification, ensure that
44the drain is attached prior to registering the listener; attempting to drain
45when unattached will crash.
46
47.. code-block:: cpp
48
49   // Create a multisink during global construction.
50   std::byte buffer[1024];
51   MultiSink multisink(buffer);
52
53   int main() {
54     // Do some initialization work for the application that pushes information
55     // into the multisink.
56     multisink.HandleEntry("Booting up!");
57     Initialize();
58
59     multisink.HandleEntry("Prepare I/O!");
60     PrepareIO();
61
62     // Start a thread to process logs in multisink.
63     StartLoggingThread();
64   }
65
66   void StartLoggingThread() {
67     MultiSink::Drain drain;
68     multisink.AttachDrain(drain);
69
70     std::byte read_buffer[512];
71     uint32_t drop_count = 0;
72     do {
73       Result<ConstByteSpan> entry = drain.PopEntry(read_buffer, drop_count);
74       if (drop_count > 0) {
75         StringBuilder<32> sb;
76         sb.Format("Dropped %d entries.", drop_count);
77         // Note: PrintByteArray is not a provided utility function.
78         PrintByteArray(sb.as_bytes());
79       }
80
81       // Iterate through the entries, this will print out:
82       //   "Booting up!"
83       //   "Prepare I/O!"
84       //
85       // Even though the drain was attached after entries were pushed into the
86       // multisink, this drain will still be able to consume those entries.
87       //
88       // Note: PrintByteArray is not a provided utility function.
89       if (entry.status().ok()) {
90         PrintByteArray(read_buffer);
91       }
92     } while (true);
93   }
94
95Iterator
96========
97It may be useful to access the entries in the underlying buffer when no drains
98are attached or in crash contexts where dumping out all entries is desirable,
99even if those entries were previously consumed by a drain. This module provides
100an iteration class that is thread-unsafe and like standard iterators, assumes
101that the buffer is not being mutated while iterating. A
102``MultiSink::UnsafeIterationWrapper`` class that supports range-based for-loop
103usage can be acquired via ``MultiSink::UnsafeIteration()``.
104
105The iterator starts from the oldest available entry in the buffer, regardless of
106whether all attached drains have already consumed that entry. This allows the
107iterator to be used even if no drains have been previously attached.
108
109.. code-block:: cpp
110
111   // Create a multisink and a test string to push into it.
112   constexpr char kExampleEntry[] = "Example!";
113   std::byte buffer[1024];
114   MultiSink multisink(buffer);
115   MultiSink::Drain drain;
116
117   // Push an entry before a drain is attached.
118   multisink.HandleEntry(kExampleEntry);
119   multisink.HandleEntry(kExampleEntry);
120
121   // Iterate through the entries, this will print out:
122   //  "Example!"
123   //  "Example!"
124   // Note: PrintByteArray is not a provided utility function.
125   for (ConstByteSpan entry : multisink.UnsafeIteration()) {
126     PrintByteArray(entry);
127   }
128
129   // Attach a drain and consume only one of the entries.
130   std::byte read_buffer[512];
131   uint32_t drop_count = 0;
132
133   multisink.AttachDrain(drain);
134   drain.PopEntry(read_buffer, drop_count);
135
136   // !! A function causes a crash before we've read out all entries.
137   FunctionThatCrashes();
138
139   // ... Crash Context ...
140
141   // You can use a range-based for-loop to walk through all entries,
142   // even though the attached drain has consumed one of them.
143   // This will also print out:
144   //  "Example!"
145   //  "Example!"
146   for (ConstByteSpan entry : multisink.UnsafeIteration()) {
147     PrintByteArray(entry);
148   }
149
150As an alternative to using the ``UnsafeIterationWrapper``,
151``MultiSink::UnsafeForEachEntry()`` may be used to run a callback for each
152entry in the buffer. This helper also provides a way to limit the iteration to
153the ``N`` most recent entries. In certain cases such as when there isn't
154enough space to copy the entire buffer, it is desirable to capture
155the latest entries rather than the first entries. In this case
156``MultiSink::UnsafeForEachEntryFromEnd`` can be used.
157
158Peek & Pop
159==========
160A drain can peek the front multisink entry without removing it using
161`PeekEntry`, which is the same as `PopEntry` without removing the entry from the
162multisink. Once the drain is done with the peeked entry, `PopEntry` will tell
163the drain to remove the peeked entry from the multisink and advance one entry.
164
165.. code-block:: cpp
166
167   constexpr char kExampleEntry[] = "Example!";
168   std::byte buffer[1024];
169   MultiSink multisink(buffer);
170   MultiSink::Drain drain;
171
172   multisink.AttachDrain(drain);
173   multisink.HandleEntry(kExampleEntry);
174
175   std::byte read_buffer[512];
176   uint32_t drop_count = 0;
177   Result<PeekedEntry> peeked_entry = drain.PeekEntry(read_buffer, drop_count);
178   // ... Handle drop_count ...
179
180   if (peeked_entry.ok()) {
181     // Note: SendByteArray is not a provided utility function.
182     Status send_status = SendByteArray(peeked_entry.value().entry());
183     if (send_status.ok()) {
184       drain.PopEntry(peeked_entry.value());
185     } else {
186       // ... Handle send error ...
187     }
188   }
189
190Drop Counts
191===========
192The `PeekEntry` and `PopEntry` return two different drop counts, one for the
193number of entries a drain was skipped forward for providing a small buffer or
194draining too slow, and the other for entries that failed to be added to the
195MultiSink.
196
197Zephyr
198======
199To enable `pw_multisink` with Zephyr use the following Kconfigs:
200- `CONFIG_PIGWEED_MULTISINK` to link `pw_multisink` into your Zephyr build
201- `CONFIG_PIGWEED_MULTISINK_UTIL` to link `pw_multisink.util`
202
203To enable the Pigweed config value `PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE`, use
204`CONFIG_PIGWEED_MULTISINK_LOCK_INTERRUPT_SAFE`.
205