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