1.. _module-pw_channel-design: 2 3====== 4Design 5====== 6.. pigweed-module-subpage:: 7 :name: pw_channel 8 9.. _module-pw_channel-design-why: 10 11-------------- 12Why pw_channel 13-------------- 14 15Flow control 16============ 17Flow control ensures that: 18 19- Channels do not send more data than the receiver is prepared to handle. 20- Channels do not request more data than they are prepared to handle. 21 22If one node sends more data than the other node can receive, network performance 23degrades. The effects vary, but could include data loss, throughput drops, or 24even crashes in one or both nodes. The ``Channel`` API avoids these issues by 25providing backpressure. 26 27What is backpressure? 28--------------------- 29In networking, backpressure is when a node, overwhelmed by inbound traffic, 30exterts pressure on upstream nodes. Communications APIs have to provide ways to 31release the pressure, allowing higher layers to can reduce data rates or drop 32stale or unnecessary packets. 33 34Pitfalls of simplistic backpressure APIs 35---------------------------------------- 36Expressing backpressure in an API might seem simple. You could just return a 37status code that indicates that the link isn't ready, and retry when it is. 38 39.. code-block:: cpp 40 41 // Returns `UNAVAILABLE` if the link isn't ready for data yet; retry later. 42 Status Write(std::span<const std::byte> packet); 43 44In practice, this is very difficult to work with: 45 46.. code-block:: cpp 47 48 std::span packet = ExpensiveOperationToPreprarePacket(); 49 if (Write(packet).IsUnavailable()) { 50 // ... then what? 51 } 52 53Now what do you do? You did work to prepare the packet, but you can't send it. 54Do you store the packet somewhere and retry? Or, wait a bit and recreate the 55packet, then try to send again? How long do you wait before sending? 56 57The result is inefficient code that is difficult to write correctly. 58 59There are other options: you can add an ``IsReadyToWrite()`` function, and only 60call ``Write`` when that is true. But what if ``IsReadyToWrite()`` becomes false 61while you're preparing the packet? Then, you're back in the same situation. 62Another approach: block until the link is ready for a write. But this means an 63entire thread and its resources are locked up for an arbitrary amount of time. 64 65How pw_channel addresses write-side backpressure 66------------------------------------------------ 67When writing into a ``Channel`` instance, the ``Channel`` may provide 68backpressure in several locations: 69 70- :cpp:func:`PendReadyToWrite <pw::channel::AnyChannel::PendReadyToWrite>` -- 71 Before writing to a channel, users must check that it is ready to receive 72 writes. If the channel is not ready, the channel will wake up the async task 73 when it becomes ready to accept outbound data. 74- :cpp:func:`GetWriteAllocator <pw::channel::AnyChannel::GetWriteAllocator>` -- 75 Once the channel becomes ready to receive writes, the writer must ensure that 76 there is space in an outgoing write buffer for the message they wish to send. 77 If there is not yet enough space, the channel will wake up the async task 78 once there is space again in the future. 79 80Only once these two operations have completed can the writing task may place its 81data into the outgoing buffer and send it into the channel. 82 83How pw_channel addresses read-side backpressure 84----------------------------------------------- 85When reading from a ``Channel`` instance, the consumer of the ``Channel`` data 86exerts backpressure by *not* invoking :cpp:func:`PendRead <pw::channel::AnyChannel::PendRead>`. 87The buffers returned by ``PendRead`` are allocated by the ``Channel`` itself. 88 89Zero-copy 90========= 91It's common to see async IO APIs like this: 92 93.. code-block:: cpp 94 95 Status Read(pw::Function<void(pw::Result<std::span<const std::byte>)> callback); 96 97These APIs suffer from an obvious problem: what is the lifetime of the span 98passed into the callback? Usually, it only lasts for the duration of the 99callback. Users must therefore copy the data into a separate buffer if 100they need it to persist. 101 102Another common structure uses user-provided buffers: 103 104.. code-block:: cpp 105 106 Status ReadIntoProvidedBuffer(std::span<const std::byte> buffer, pw::Function<...> callback); 107 108But this a similar problem: the low-level implementor of the read interface 109must copy data from its source (usually a lower-level protocol buffer or 110a peripheral-associated DMA buffer) into the user-provided buffer. This copy 111is also required when passing between layers of the stack that need to e.g. 112erase headers, perform defragmentation, or otherwise modify the structure 113of the incoming data. 114 115This process requires both runtime overhead due to copying and memory overhead 116due to the need for multiple buffers to hold every message. 117 118``Channel`` avoids this problem by using 119:cpp:class:`MultiBuf <pw::multibuf::MultiBuf>`. The lower layers of the stack 120are responsible for allocating peripheral-compatible buffers that are then 121passed up the stack for the application code to read from or write into. 122``MultiBuf`` allows for fragementation, coalescing, insertion of headers, 123footers etc. without the need for a copy. 124 125Composable 126========== 127Many traditional communications code hard-codes its lower layers, making it 128difficult or impossible to reused application code between e.g. a UART-based 129protocol and an IP-based one. By providing a single standard interface for byte 130and packet streams, ``Channel`` allows communications stacks to be layered on 131top of one another in various fashions without need rewrites or intermediate 132buffering of data. 133 134Asynchronous 135============ 136``Channel`` uses ``pw_async2`` to allow an unlimited number of channel IO 137operations without the need for dedicated threads. ``pw_async2``'s 138dispatcher-based structure ensures that work is only done as-needed, 139cancellation and timeouts are built-in and composable, and there is no 140need for deeply-nested callbacks or careful consideration of what 141context a particular callback may be invoked from. 142 143------------------ 144Channel attributes 145------------------ 146Channels may be reliable, readable, writable, or seekable. A channel may be 147substituted for another as long as it provides at least the same set of 148capabilities; additional capabilities are okay. The channel's data type 149(datagram or byte) implies different read/write semantics, so datagram/byte 150channels cannot be used interchangeably in general. 151 152Using datagram channels as byte channels 153======================================== 154For datagram channels, the exact bytes provided to a write call will appear in a 155read call on the other end. A zero-byte datagram write results in a zero-byte 156datagram read, so empty datagrams may convey information. 157 158For byte channels, bytes written may be grouped differently when read. A 159zero-length byte write is meaningless and will not result in a zero-length byte 160read. If a zero-length byte read occurs, it is ignored. 161 162To facilitate simple code reuse, datagram-oriented channels may used as 163byte-oriented channels when appropriate. Calling 164:cpp:func:`Channel::IgnoreDatagramBoundaries` on a datagram channel returns a 165byte channel reference to it. The byte view of the channel is simply the 166concatenation of the contents of the datagrams. 167 168This is only valid if, for the datagram channel: 169 170- datagram boundaries have no significance or meaning, 171- zero-length datagrams are not used to convey information, since they are 172 meaningless for byte channels, 173- short or zero-length writes through the byte API will not result in 174 unacceptable overhead. 175