1.. _seed-0114: 2 3============== 40114: Channels 5============== 6.. seed:: 7 :number: 114 8 :name: Channels 9 :status: Accepted 10 :proposal_date: 2023-10-10 11 :cl: 175471 12 :authors: Wyatt Hepler 13 :facilitator: Carlos Chinchilla 14 15------- 16Summary 17------- 18This document proposes a new ``pw_channel`` module and 19:cpp:class:`pw::channel::Channel` class. The module is similar to 20pw_stream, with three key changes: 21 22- Supports byte stream or datagram semantics (:ref:`module-pw_stream` is a 23 subset of ``pw_channel``). 24- Provides an asynchronous API (:ref:`SEED-0112`). 25- Uses the :ref:`SEED-0109` buffers system, which enables zero-copy 26 and vectored I/O. 27 28``pw_channel`` will provide the data transmit and receive API for the upcoming 29socket abstraction. 30 31-------- 32Proposal 33-------- 34This SEED proposes the following: 35 36- Introduce a new ``pw_channel`` module. 37- Introduce a :cpp:class:`pw::channel::Channel` virtual interface. 38 Implementors of this interface may expose byte stream or datagram semantics. 39 All operations in this interface are async and use :ref:`seed-0109` buffers 40 to provide zero-copy operations. 41- Use ``pw_channel`` as the basis for the upcoming Pigweed sockets API. 42- Replace ``pw_stream`` with ``pw_channel`` to the extent possible. 43 44---------- 45Motivation 46---------- 47One of the fundamental operations of computing is sending data from one place to 48another. Examples include exchanging data with 49 50- an in-memory data structure, 51- a file in a filesystem, 52- a hardware peripheral, 53- another process, or 54- a device across a network. 55 56There are many interfaces for data exchange. Pigweed provides 57:ref:`module-pw_stream`, which is a simple synchronous API for transmitting and 58receiving data. ``pw_stream``'s simple model has made it prevalent in Pigweed. 59 60The Pigweed team is revamping its communications systems (see :ref:`seed-0107`). 61The new sockets API will be a critical piece of that story. The core job of a 62socket is to exchange data with another node in the network. ``pw_stream``'s 63purpose is to facilitate data exchange, but it is too limited for sockets. 64pw_stream is missing support for several features, including: 65 66- Datagrams – Data is a byte stream. Datagrams are not supported. 67- Unreliability – Sockets may not guarantee delivery of data. ``pw_stream`` 68 assumes no data is lost if ``Write`` returns ``OK``. 69- Asynchronous operations – All functions block until the operation completes. 70- Zero copy operations – All reads and writes require data to be copied. 71- Vectored I/O – All reads and writes use a single contiguous buffer. 72- Backpressure – There is no mechanism for a stream to notify the producer that 73 it needs more time to process data. 74 75These features are fairly complex and may be exposed in a variety of ways in an 76API. This SEED proposes a new ``pw_stream``-like ``Channel`` data exchange API. 77``Channel`` provides a standard I/O interface with these advanced features. 78Like ``pw_stream``, this API will be used anywhere data needs to be read and/or 79written. 80 81--------- 82Use cases 83--------- 84pw_rpc 85====== 86pw_rpc is a communications protocol that enables calling procedures on different 87nodes (i.e. RPCs), and sharing data between them. RPCs can be sent using 88pw_stream APIs, which are blocking. 89 90Sockets 91======= 92Sockets are a communications channel between two endpoints in a network. 93Sockets support exchanging data: 94 95- as datagrams or a stream or bytes, and 96- reliably or unreliably. 97 98pw_stream 99========= 100``Channel`` should support all use cases addressed by ``pw_stream``. These 101include: 102 103- :cpp:class:`pw::stream::NullStream` -- ``NullStream`` ignores all bytes 104 written to it and produces no bytes when read. This is used when no input or 105 output is needed. 106- :cpp:class:`pw::stream::CountingNullStream` -- Counts bytes written to it. 107 Used to to determine the size of an encoded object before it is encoded to its 108 final destination. 109- :cpp:class:`pw::stream::MemoryReader` / :cpp:class:`pw::stream::MemoryWriter` 110 -- Writes data to or reads data from a fixed, contiguous memory buffer. 111 Example uses include encoding a protobuf for transport. 112- :cpp:class:`pw::stream::SocketStream` -- Supports reading from and writing to 113 a TCP socket. 114- :cpp:class:`pw::blob_store::BlobStore::Reader` / 115 :cpp:class:`pw::blob_store::BlobStore::Writer` -- ``pw_blob_store`` uses a 116 stream interface for reading and writing. This is similar to a file object. 117 118Hardware interfaces 119=================== 120It is often necessary to exchange data with hardware I/O blocks. 121The ``Channel`` API could be used to abstract communications with I/O 122interfaces. 123 124------------------ 125Existing solutions 126------------------ 127 128pw_stream 129========= 130pw_stream provides for a synchronous, reliable byte-oriented stream. 131 132See :ref:`module-pw_stream`. 133 134C++ 135=== 136C++ provides an I/O stream family of classes. 137 138Java 139==== 140Java provides a hierarchy of channel classes with a variety of flavors. The 141`Channel interface 142<https://docs.oracle.com/javase/8/docs/api/java/nio/channels/Channel.html>`_ 143provides just two methods: ``isOpen()`` and ``close()``. Various I/O operations 144are mixed in through different interfaces. ``Channel`` supports `byte stream 145<https://docs.oracle.com/javase/8/docs/api/java/nio/channels/ByteChannel.html>`_, 146`datagram 147<https://docs.oracle.com/javase/8/docs/api/java/nio/channels/DatagramChannel.html>`_, 148`asynchronous <https://docs.oracle.com/javase/8/docs/api/java/nio/channels/AsynchronousChannel.html>`_, 149and `scatter <https://docs.oracle.com/javase/8/docs/api/java/nio/channels/ScatteringByteChannel.html>`_/ 150`gather <https://docs.oracle.com/javase/8/docs/api/java/nio/channels/GatheringByteChannel.html>`_ IO. 151 152C# 153== 154The C# programming language offers a stream class similar to pw_stream and the 155proposed pw_channel module. It supports synchronous and asynchronous operations 156on a stream of bytes. 157https://learn.microsoft.com/en-us/dotnet/api/system.io.stream?view=net-7.0 158 159C#’s Channel API has a different intent than pw_channel. Its purpose is to 160synchronize objects between endpoints, and is somewhat different from what is 161proposed here. 162https://learn.microsoft.com/en-us/dotnet/api/system.threading.channels?view=net-7.0 163 164------------ 165Requirements 166------------ 167* Support data transmission for the upcoming sockets API (:ref:`seed-0107`): 168 169 - reliable byte stream (``SOCK_STREAM``) 170 - unreliable datagram (``SOCK_DGRAM``) 171 - reliable datagram (``SOCK_SEQPACKET``) 172 173* Asynchronous operations. 174* Efficient, minimally copying buffer with ``MultiBuf`` (:ref:`seed-0109`). 175 176------ 177Design 178------ 179Conceptually, a channel is a sequence of bytes or datagrams exchanged between 180two endpoints. An endpoint can be anything that produces or consumes data, such 181as an in-memory data structure, a file in a filesystem, a hardware peripheral, 182or a network socket. Both endpoints may be ``Channel`` implementations, or the 183``Channel`` may simply forward to something that provides compatible semantics, 184e.g. a memory buffer or OS socket. 185 186In Unix, "everything is a file". File descriptors provide a common I/O interface 187used for everything from files to pipes to sockets to hardware devices. Channels 188fill a similar role as POSIX file descriptors. 189 190Channel semantics 191================= 192pw_channel will provide the data exchange API for Pigweed’s upcoming network 193sockets. To this end, ``Channel`` supports the following socket semantics: 194 195- reliable byte stream (``SOCK_STREAM``) 196- unreliable datagram (``SOCK_DGRAM``) 197- reliable datagram (``SOCK_SEQPACKET``) 198 199Reliability and data type (stream versus datagram) are essential aspects of 200channel semantics. These properties affect how code that uses the APIs is 201written. A channel with different semantics cannot be swapped for another 202without updating the assumptions in the surrounding code. 203 204Data type: datagrams & byte streams 205----------------------------------- 206Fundamentally, a channel involves sending data from one endpoint to another. 207The endpoints might both be ``Channel`` instances (e.g. two sockets). Or, one 208endpoint could be a ``Channel`` while the other is an in-memory data structure, 209file in a file system, or hardware peripheral. 210 211The data type dictates the basic unit of data transmission. Datagram channels 212send and receive datagrams: "self-contained, independent entit[ies] of data" 213(`RFC 1594 <https://www.rfc-editor.org/rfc/rfc1594.txt>`_). Datagrams contain a 214payload of zero or more bytes. pw_channel does not define a maximum payload size 215for datagrams. 216 217Byte stream channels send and receive an arbitrary sequence of bytes. 218Zero-length byte stream writes are no-ops and may not result in any bytes being 219transmitted. 220 221In terms of the channels API, ``Read``, ``Write``, and ``Seek`` functions have 222different meanings for byte and and datagram channels. For byte stream channels, 223these functions work with an arbitrary number of bytes. For datagram channels, 224``Read``, ``Write``, and ``Seek`` are in terms of datagrams. 225 226Reliable channels 227----------------- 228Reliable channels guarantee that their data is received in order and without 229loss. The API user does not have to do anything to ensure this. After a write is 230accepted, the user will never have to retry it. Reads always provide data in 231order without loss. The channel implementation is responsible for this. 232 233For some channels, reliability is trivial; for others it requires significant 234work: 235 236- A memory channel that writes to a buffer is trivially reliable. 237- A socket communicating across a network will require a complex protocol such 238 as TCP to guarantee that the data is delivered. 239 240Initially, only reliable byte-oriented channels will be supported. Unreliable 241byte streams are not commonly supported, and would be difficult to apply in many 242use cases. There are circumstances where unreliable byte streams do makes sense, 243such as reading time-sensitive sensor data, where the consumer only wants the 244very latest data regardless of drops. Unreliable byte streams may be added in 245the future. 246 247Data loss 248^^^^^^^^^ 249Data is never silently lost in a reliable channel. Unrecoverable data loss 250always results in the eventual closure of the channel, since a fundamental 251invariant of the channel cannot be maintained. 252 253A few examples: 254 255- A write to a TCP channel fails because of a transient hardware issue. The 256 channel and underlying TCP connection are closed. 257- A TCP channel times out on a retry. The channel and underlying TCP connection 258 are closed. 259- A write to a channel that fills a ring buffer is requested. A ``MultiBuf`` for 260 the write is not provided immediately because the ring buffer is full. The 261 channel stays open, but the write is delayed until the ring buffer has 262 sufficient space. 263 264Reliability & connections 265^^^^^^^^^^^^^^^^^^^^^^^^^ 266Reliable channels operate as if they have a connection, even if the underlying 267implementation does not establish a connection. This specifically means that: 268 269- It is assumed that the peer endpoint will receive data for which the write 270 call succeeded. 271- If data is lost, the error will be reported in some form and the channel will 272 be closed. 273 274For example, a TCP socket channel would maintain an explicit connection, while a 275ring buffer channel would not. 276 277Unreliable channels 278------------------- 279Unreliable datagram channels make no guarantees about whether datagrams are 280delivered and in what order they arrive. Users are responsible for tracking 281drops and ordering if required. 282 283Unreliable channels should report read and write failures whenever possible, 284but an ``OK`` write does not indicate that the data is received by the other 285endpoint. 286 287Flow control, backpressure, and ``ConservativeLimit`` 288===================================================== 289A channel may provide backpressure through its async write API. The 290``PollWritable`` method should be used to ensure that the channel is ready 291to receive calls to ``Write``. Additionally, the ``MultiBufAllocator`` may wait 292to provide a ``MultiBuf`` for writing until memory becomes available. 293 294pw_stream offered a notion of flow control through the 295:cpp:func:`pw::stream::Stream::ConservativeWriteLimit` function. Code using a 296stream could check the write limit prior to writing data to determine if the 297stream is ready to receive more. This function will not be provided in 298``pw_channel``. 299 300Openness / closedness 301===================== 302pw_channel will have an explicit open/closed concept that ``pw_stream`` lacks. 303Reads and writes may succeed when the channel is open. Reads and writes never 304succeed when the channel is closed. 305 306The channel API supports closing a channel, but does not support opening a 307channel. Channels are opened by interacting with a concrete class. 308 309Reliable channels are closed if unrecoverable data loss occurs. Unreliable 310channels may be closed when reads or writes are known to fail (e.g. a 311cable was unplugged), but this is not required. 312 313Synchronous APIs 314================ 315The ``pw_channel`` class may provide synchronous versions of its functions, 316implementated in terms of the asynchronous API. These will poll the asynchronous 317API until it completes, blocking on a binary semaphore or similar primitive if 318supported. This will leverage a ``pw_async`` helper for this purpose. 319 320Channel Class Capabilities 321========================== 322``Channel`` s may offer any of five capabilities: 323 324.. list-table:: 325 :header-rows: 1 326 327 * - Capability 328 - Description 329 * - ``kReliable`` 330 - Data is guaranteed to arrive in order, without loss. 331 * - ``kSeekable`` 332 - The read/write position may be changed via the ``Seek`` method. 333 * - ``kDatagram`` 334 - Data is guaranteed to be received in whole packets matching the size and 335 contents of a single ``Write`` call. 336 * - ``kReadable`` 337 - Supports reading data. 338 * - ``kWritable`` 339 - Supports writing data 340 341These capabilities are expressed as generic arguments to the ``Channel`` class, 342e.g. ``Channel<kReadable | kReliable>`` for a ``Channel`` that is readable and 343reliable. Aliases are provided for common combinations, such as ``ByteStream`` 344for a reliable non-seekable non-datagram stream of bytes (such as a TCP stream). 345Certain nonsensical combinations, such as a channel that is ``kSeekable`` but 346not ``kReadable`` or ``kWritable`` are disallowed via ``static_assert``. 347 348Conversion 349---------- 350Channels may be freely converted to channels with fewer capabilities, e.g. 351``Channel<kReadable | kWritable>`` may be used as a ``Channel<kReadable>``. 352This allows Channels with compatible semantics to be substituted for one another 353safely. 354 355Shared Base Class for Minimal Code Size 356--------------------------------------- 357``Channel`` also inherits from an ``AnyChannel`` base class which provides the 358underlying ``virtual`` interface. Sharing a single base class avoids multiple 359inheritance, minimizing vtable overhead. 360 361Prototype Demonstrating Channel Capabilities 362-------------------------------------------- 363A prototype demonstrating this interface can be seen `here 364<https://godbolt.org/z/3c4M3Y17r>`_. 365 366API sketch 367========== 368An outline of the ``AnyChannel`` base class follows. ``AnyChannel`` will rarely 369be used directly, since it makes no guarantees about any channel capabilities or 370the data type. The function signatures and comments apply to all derived classes, 371however. 372 373.. code-block:: cpp 374 375 namespace pw::channel { 376 377 /// A generic data channel that may support reading or writing bytes. 378 /// 379 /// Note that this channel should be used from only one ``pw::async::Task`` 380 /// at a time, as the ``Poll`` methods are only required to remember the 381 /// latest ``pw::async::Context`` that was provided. 382 class AnyChannel { 383 public: 384 // Properties 385 [[nodiscard]] bool reliable() const; 386 [[nodiscard]] DataType data_type() const; 387 [[nodiscard]] bool readable() const; 388 [[nodiscard]] bool writable() const; 389 [[nodiscard]] Seekability seekable() const; 390 391 [[nodiscard]] bool is_open() const; 392 393 // Write API 394 395 // Checks whether a writeable channel is *currently* writeable. 396 // 397 // This should be called before attempting to ``Write``, and may be called 398 // before allocating a write buffer if trying to reduce memory pressure. 399 // 400 // If ``Ready`` is returned, a *single* caller may proceed to ``Write``. 401 // 402 // If ``Pending`` is returned, ``cx`` will be awoken when the channel 403 // becomes writeable again. 404 // 405 // Note: this method will always return ``Ready`` for non-writeable 406 // channels. 407 MaybeReady<> PollWritable(pw::async::Context& cx); 408 409 // Gives access to an allocator for write buffers. The MultiBufAllocator 410 // provides an asynchronous API for obtaining a buffer. 411 // 412 // This allocator must *only* be used to allocate the next argument to 413 // ``Write``. The allocator must be used at most once per call to 414 // ``Write``, and the returned ``MultiBuf`` must not be combined with 415 // any other ``MultiBuf`` s or ``Chunk`` s. 416 // 417 // Write allocation attempts will always return ``std::nullopt`` for 418 // channels that do not support writing. 419 MultiBufAllocator& GetWriteAllocator(); 420 421 // Writes using a previously allocated MultiBuf. Returns a token that 422 // refers to this write. These tokens are monotonically increasing, and 423 // FlushPoll() returns the value of the latest token it has flushed. 424 // 425 // The ``MultiBuf`` argument to ``Write`` may consist of either: 426 // (1) A single ``MultiBuf`` allocated by ``GetWriteAllocator()`` 427 // that has not been combined with any other ``MultiBuf`` s 428 // or ``Chunk``s OR 429 // (2) A ``MultiBuf`` containing any combination of buffers from sources 430 // other than ``GetWriteAllocator``. 431 // 432 // This requirement allows for more efficient use of memory in case (1). 433 // For example, a ring-buffer implementation of a ``Channel`` may 434 // specialize ``GetWriteAllocator`` to return the next section of the 435 // buffer available for writing. 436 // 437 // May fail with the following error codes: 438 // 439 // * OK - Data was accepted by the channel 440 // * UNIMPLEMENTED - The channel does not support writing. 441 // * UNAVAILABLE - The write failed due to a transient error (only applies 442 // to unreliable channels). 443 // * FAILED_PRECONDITION - The channel is closed. 444 Result<WriteToken> Write(MultiBuf&&); 445 446 // Flushes pending writes. 447 // 448 // Returns a ``MaybeReady`` indicating whether or not flushing has 449 // completed. 450 // 451 // After this call, ``LastFlushed`` may be used to discover which 452 // ``Write`` calls have successfully finished flushing. 453 // 454 // * Ready(OK) - All data has been successfully flushed. 455 // * Ready(UNIMPLEMENTED) - The channel does not support writing. 456 // * Ready(FAILED_PRECONDITION) - The channel is closed. 457 // * Pending - Data remains to be flushed. 458 [[nodiscard]] MaybeReady<pw::Status> PollFlush(async::Context& cx); 459 460 // Returns the latest ```WriteToken``` that was successfully flushed. 461 // 462 // Note that a ``Write`` being flushed does not necessarily mean that the 463 // data was received by the remote. For unreliable channels, flushing may 464 // simply mean that data was written out, not that it was received. 465 [[nodiscard]] WriteToken LastFlushed() const; 466 467 // Read API 468 469 // Returns a MultiBuf read data, if available. If data is not available, 470 // invokes cx.waker() when it becomes available. 471 // 472 // For datagram channels, each successful read yields one complete 473 // datagram. For byte stream channels, each successful read yields some 474 // number of bytes. 475 // 476 // Channels only support one read operation / waker at a time. 477 // 478 // * OK - Data was read into a MultiBuf. 479 // * UNIMPLEMENTED - The channel does not support reading. 480 // * FAILED_PRECONDITION - The channel is closed. 481 // * OUT_OF_RANGE - The end of the stream was reached. This may be though 482 // of as reaching the end of a file. Future reads may succeed after 483 // ``Seek`` ing backwards, but no more new data will be produced. The 484 // channel is still open; writes and seeks may succeed. 485 MaybeReady<Result<MultiBuf>> PollRead(async::Context& cx); 486 487 // On byte stream channels, reads up to max_bytes from the channel. 488 // This function is hidden on datagram-oriented channels. 489 MaybeReady<Result<MultiBuf>> PollRead(async::Context& cx, size_t max_bytes); 490 491 // Changes the position in the stream. 492 // 493 // Any ``PollRead`` or ``Write`` calls following a call to ``Seek`` will be 494 // relative to the new position. Already-written data still being flushed 495 // will be output relative to the old position. 496 // 497 // * OK - The current position was successfully changed. 498 // * UNIMPLEMENTED - The channel does not support seeking. 499 // * FAILED_PRECONDITION - The channel is closed. 500 // * NOT_FOUND - The seek was to a valid position, but the channel is no 501 // longer capable of seeking to this position (partially seekable 502 // channels only). 503 // * OUT_OF_RANGE - The seek went beyond the end of the stream. 504 Status Seek(ptrdiff_t position, Whence whence); 505 506 // Returns the current position in the stream, or kUnknownPosition if 507 // unsupported. 508 size_t Position() const; 509 510 // Closes the channel, flushing any data. 511 // 512 // * OK - The channel was closed and all data was sent successfully. 513 // * DATA_LOSS - The channel was closed, but not all previously written 514 // data was delivered. 515 // * FAILED_PRECONDITION - Channel was already closed, which can happen 516 // out-of-band due to errors. 517 MaybeReady<pw::Status> PollClose(async::Context& cx); 518 519 private: 520 virtual bool do_reliable() const; 521 virtual DataType do_data_type() const; 522 virtual bool do_readable() const; 523 virtual bool do_writable() const; 524 virtual Seekability do_seekable() const; 525 virtual bool do_is_open() const; 526 527 // Virtual interface. 528 virtual MultiBufAllocator& DoGetWriteBufferAllocator() = 0; 529 530 virtual MaybeReady<> PollWritable(async::Context& cx) = 0; 531 532 virtual Result<WriteToken> DoWrite(MultiBuf&& buffer) = 0; 533 534 virtual WriteToken DoPollFlush(async::Context& cx) = 0; 535 536 [[nodiscard]] WriteToken LastFlushed() const = 0; 537 538 // The max_bytes argument is ignored for datagram-oriented channels. 539 virtual MaybeReady<Result<MultiBuf>> DoReadPoll( 540 async::Context& cx, size_t max_bytes) = 0; 541 542 virtual DoSeek(ptrdiff_t position, Whence whence) = 0; 543 544 virtual size_t DoPosition() const { return kUnknownPosition; } 545 546 virtual async::MaybeReady<Status> DoClosePoll(async::Context& cx); 547 }; 548 } // namespace pw::channel 549 550pw_channel and pw_stream 551======================== 552As described, ``pw_channel`` is closely based on ``pw_stream``. It adds async, 553``MultiBuf``, and new socket-inspired semantics. 554 555``pw_channel`` is intended to supersede ``pw_stream``. There are a few options 556for how to reconcile the two modules. From most to least ideal, these are: 557 558- Fully replace ``pw_stream`` with ``pw_channel`` and remove the ``pw_stream`` 559 module. 560- Rework ``pw_stream`` so it inherits from ``pw::channel::Channel``. 561- Keep ``pw_stream``, but provide adapters to convert between ``pw_stream`` and 562 ``pw_channel``. 563 564Fully replacing ``pw_stream`` with ``pw_channel`` could be complicated due to: 565 566- Potential code size increase because of ``MultiBuf`` and the async poll model. 567- The scale of migrating the all Pigweed users off of ``pw_stream``. 568- Increased API complexity imposing a burden on Pigweed users. 569