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