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