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