ChannelHandler Diagram/Documentation

When watching @johannesweiss's excellent talk Testing SwiftNIO Systems from the ServerSide.swift conference in Copenhagen 2019 he showed a diagram at the 17:58 mark.

To my mind it makes it very clear and easy to understand what are the necessary calls to make to properly instrument my ChannelHandler. I found the diagram so illuminating that I decided to turn it into ASCII and include it as a comment at the top of my tests for future versions of me.

                       ┌───────────────────────────────┐
                       │ EmbeddedChannel               │
                       │ ┌───────────────────────────┐ │
  channel              │ │ChannelPipeline            │ │ channel
    .writeInbound(_:)  │ │   ┌──────────────────┐    │ │   .readInbound(as:)
                    ───┼─┼──►│                  ├────┼─┼──►
                       │ │   │                  │    │ │
                       │ │   │  ChannelHandler  │    │ │
                       │ │   │                  │    │ │
                    ◄──┼─┼───┤                  │◄───┼─┼───
  channel              │ │   └──────────────────┘    │ │ channel
    .readOutbound(as:) │ │                           │ │   .writeOutbound(_:)
                       │ └───────────────────────────┘ │
                       │                               │
                       └───────────────────────────────┘

I usually find myself struggling to remember which arrow context.fireChannelRead() is going to feed into.

Is there an equivalent diagram that exists about the inside of a ChannelHandler?

1 Like

Thank you! And sorry for the slow response. Your diagram is super cool, you could even make a PR to SwiftNIO and contribute that as a doc comment?

Regarding how ChannelHandlers communicate through the ChannelPipeline: I don't have a cool ASCII art diagram handy but I threw a quick thing together in Keynote.

Does that make it clearer? Sorry, it looks a bit busy but I went for more information over making it prettier :).

In general:

  • for inbound events (like channelRead, channelReadComplete, ...) context.fireTheNameOfTheEvent will invoke the next inbound ChannelHandler's theNameOfTheEvent. Note that the next inbound handler will sit further to the back of the ChannelPipeline because inbound events travel front to back (left to right in the above).
  • for outbound events (like write, flush, read), calling context.nameOfTheEvent will invoke the next outbound ChannelHandler's nameOfTheEvent method. Note that the next outbound handler will sit further to the front of the ChannelPipeline because outbound events travel back to front (or right to left in the above diagram).

I did something similar to help remember the right functions for the right kind of channel, and split it out into inbound/outbound/duplex :slight_smile: .

+-------------------------------------------+
|                Kernel Socket              |
+-------------------------------------------+
       |                             ^
       |                             |
       |                             |
       |                             |
       v                             |
  [InboundIn]                  [OutboundOut]
 channelRead()                context.write()
+-------------+               +-------------+
|   Inbound   |               |   Outbound  |
|   Handler   |               |   Handler   |
+-------------+               +-------------+
context.fireChannelRead()         write()
 [InboundOut]                  [OutboundIn]
       |                             ^
       |                             |
       |                             |
       |                             |
       v                             |
  [InboundIn]                  [OutboundOut]
 channelRead()                context.write()
+-------------------------------------------+
|              Duplex Handler               |
+-------------------------------------------+
context.fireChannelRead()         write()
 [InboundOut]                  [OutboundIn]
       |                             ^
       |                             |
       |                             |
       |                             |
       v                             |
  [InboundIn]                  [OutboundOut]
 channelRead()                context.write()
+-------------------------------------------+
|             Terminating Handler           |
+-------------------------------------------+