• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_INTERRUPT_SAFE
18
19  Whether an interrupt-safe lock is used to guard multisink read/write
20  operations.
21
22  By default, this option is enabled and the multisink uses an interrupt
23  spin-lock to guard its transactions. If disabled, a mutex is used instead.
24
25  Disabling this will alter the entry precondition of the multisink,
26  requiring that it not be called from an interrupt context.
27
28Late Drain Attach
29=================
30It is possible to push entries or inform the multisink of drops before any
31drains are attached to it, allowing you to defer the creation of the drain
32further into an application. The multisink maintains the location and drop
33count of the oldest drain and will set drains to match on attachment. This
34permits drains that are attached late to still consume any entries that were
35pushed into the ring buffer, so long as those entries have not yet been evicted
36by newer entries. This may be particularly useful in early-boot scenarios where
37drain consumers may need time to initialize their output paths. Listeners are
38notified immediately when attached, to allow late drain users to consume
39existing entries. If draining in response to the notification, ensure that
40the drain is attached prior to registering the listener; attempting to drain
41when unattached will crash.
42
43.. code-block:: cpp
44
45  // Create a multisink during global construction.
46  std::byte buffer[1024];
47  MultiSink multisink(buffer);
48
49  int main() {
50    // Do some initialization work for the application that pushes information
51    // into the multisink.
52    multisink.HandleEntry("Booting up!");
53    Initialize();
54
55    multisink.HandleEntry("Prepare I/O!");
56    PrepareIO();
57
58    // Start a thread to process logs in multisink.
59    StartLoggingThread();
60  }
61
62  void StartLoggingThread() {
63    MultiSink::Drain drain;
64    multisink.AttachDrain(drain);
65
66    std::byte read_buffer[512];
67    uint32_t drop_count = 0;
68    do {
69      Result<ConstByteSpan> entry = drain.PopEntry(read_buffer, drop_count);
70      if (drop_count > 0) {
71        StringBuilder<32> sb;
72        sb.Format("Dropped %d entries.", drop_count);
73        // Note: PrintByteArray is not a provided utility function.
74        PrintByteArray(sb.as_bytes());
75      }
76
77      // Iterate through the entries, this will print out:
78      //   "Booting up!"
79      //   "Prepare I/O!"
80      //
81      // Even though the drain was attached after entries were pushed into the
82      // multisink, this drain will still be able to consume those entries.
83      //
84      // Note: PrintByteArray is not a provided utility function.
85      if (entry.status().ok()) {
86        PrintByteArray(read_buffer);
87      }
88    } while (true);
89  }
90
91Iterator
92========
93It may be useful to access the entries in the underlying buffer when no drains
94are attached or in crash contexts where dumping out all entries is desirable,
95even if those entries were previously consumed by a drain. This module provides
96an iteration class that is thread-unsafe and like standard iterators, assumes
97that the buffer is not being mutated while iterating. A
98``MultiSink::UnsafeIterationWrapper`` class that supports range-based for-loop
99usage can be acquired via ``MultiSink::UnsafeIteration()``.
100
101The iterator starts from the oldest available entry in the buffer, regardless of
102whether all attached drains have already consumed that entry. This allows the
103iterator to be used even if no drains have been previously attached.
104
105.. code-block:: cpp
106
107  // Create a multisink and a test string to push into it.
108  constexpr char kExampleEntry[] = "Example!";
109  std::byte buffer[1024];
110  MultiSink multisink(buffer);
111  MultiSink::Drain drain;
112
113  // Push an entry before a drain is attached.
114  multisink.HandleEntry(kExampleEntry);
115  multisink.HandleEntry(kExampleEntry);
116
117  // Iterate through the entries, this will print out:
118  //  "Example!"
119  //  "Example!"
120  // Note: PrintByteArray is not a provided utility function.
121  for (ConstByteSpan entry : multisink.UnsafeIteration()) {
122    PrintByteArray(entry);
123  }
124
125  // Attach a drain and consume only one of the entries.
126  std::byte read_buffer[512];
127  uint32_t drop_count = 0;
128
129  multisink.AttachDrain(drain);
130  drain.PopEntry(read_buffer, drop_count);
131
132  // !! A function causes a crash before we've read out all entries.
133  FunctionThatCrashes();
134
135  // ... Crash Context ...
136
137  // You can use a range-based for-loop to walk through all entries,
138  // even though the attached drain has consumed one of them.
139  // This will also print out:
140  //  "Example!"
141  //  "Example!"
142  for (ConstByteSpan entry : multisink.UnsafeIteration()) {
143    PrintByteArray(entry);
144  }
145
146As an alternative to using the ``UnsafeIterationWrapper``,
147``MultiSink::UnsafeForEachEntry()`` may be used to run a callback for each
148entry in the buffer. This helper also provides a way to limit the iteration to
149the ``N`` most recent entries.
150
151Peek & Pop
152==========
153A drain can peek the front multisink entry without removing it using
154`PeekEntry`, which is the same as `PopEntry` without removing the entry from the
155multisink. Once the drain is done with the peeked entry, `PopEntry` will tell
156the drain to remove the peeked entry from the multisink and advance one entry.
157
158.. code-block:: cpp
159
160  constexpr char kExampleEntry[] = "Example!";
161  std::byte buffer[1024];
162  MultiSink multisink(buffer);
163  MultiSink::Drain drain;
164
165  multisink.AttachDrain(drain);
166  multisink.HandleEntry(kExampleEntry);
167
168  std::byte read_buffer[512];
169  uint32_t drop_count = 0;
170  Result<PeekedEntry> peeked_entry = drain.PeekEntry(read_buffer, drop_count);
171  // ... Handle drop_count ...
172
173  if (peeked_entry.ok()) {
174    // Note: SendByteArray is not a provided utility function.
175    Status send_status = SendByteArray(peeked_entry.value().entry());
176    if (send_status.ok()) {
177      drain.PopEntry(peeked_entry.value());
178    } else {
179      // ... Handle send error ...
180    }
181  }
182
183Drop Counts
184===========
185The `PeekEntry` and `PopEntry` return two different drop counts, one for the
186number of entries a drain was skipped forward for providing a small buffer or
187draining too slow, and the other for entries that failed to be added to the
188MultiSink.
189