Package io.grpc.netty

Class NettyAdaptiveCumulator

  • All Implemented Interfaces:
    io.netty.handler.codec.ByteToMessageDecoder.Cumulator

    class NettyAdaptiveCumulator
    extends java.lang.Object
    implements io.netty.handler.codec.ByteToMessageDecoder.Cumulator
    "Adaptive" cumulator: cumulate ByteBufs by dynamically switching between merge and compose strategies.
    • Field Summary

      Fields 
      Modifier and Type Field Description
      private int composeMinSize  
    • Constructor Summary

      Constructors 
      Constructor Description
      NettyAdaptiveCumulator​(int composeMinSize)
      "Adaptive" cumulator: cumulate ByteBufs by dynamically switching between merge and compose strategies.
    • Method Summary

      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and Type Method Description
      (package private) void addInput​(io.netty.buffer.ByteBufAllocator alloc, io.netty.buffer.CompositeByteBuf composite, io.netty.buffer.ByteBuf in)  
      io.netty.buffer.ByteBuf cumulate​(io.netty.buffer.ByteBufAllocator alloc, io.netty.buffer.ByteBuf cumulation, io.netty.buffer.ByteBuf in)
      "Adaptive" cumulator: cumulate ByteBufs by dynamically switching between merge and compose strategies.
      (package private) static void mergeWithCompositeTail​(io.netty.buffer.ByteBufAllocator alloc, io.netty.buffer.CompositeByteBuf composite, io.netty.buffer.ByteBuf in)
      Append the given ByteBuf in to CompositeByteBuf composite by expanding or replacing the tail component of the CompositeByteBuf.
      (package private) static boolean shouldCompose​(io.netty.buffer.CompositeByteBuf composite, io.netty.buffer.ByteBuf in, int composeMinSize)  
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • Field Detail

      • composeMinSize

        private final int composeMinSize
    • Constructor Detail

      • NettyAdaptiveCumulator

        NettyAdaptiveCumulator​(int composeMinSize)
        "Adaptive" cumulator: cumulate ByteBufs by dynamically switching between merge and compose strategies.
        Parameters:
        composeMinSize - Determines the minimal size of the buffer that should be composed (added as a new component of the CompositeByteBuf). If the total size of the last component (tail) and the incoming buffer is below this value, the incoming buffer is appended to the tail, and the new component is not added.
    • Method Detail

      • cumulate

        public final io.netty.buffer.ByteBuf cumulate​(io.netty.buffer.ByteBufAllocator alloc,
                                                      io.netty.buffer.ByteBuf cumulation,
                                                      io.netty.buffer.ByteBuf in)
        "Adaptive" cumulator: cumulate ByteBufs by dynamically switching between merge and compose strategies.

        This cumulator applies a heuristic to make a decision whether to track a reference to the buffer with bytes received from the network stack in an array ("zero-copy"), or to merge into the last component (the tail) by performing a memory copy.

        It is necessary as a protection from a potential attack on the ByteToMessageDecoder.COMPOSITE_CUMULATOR. Consider a pathological case when an attacker sends TCP packages containing a single byte of data, and forcing the cumulator to track each one in a separate buffer. The cost is memory overhead for each buffer, and extra compute to read the cumulation.

        Implemented heuristic establishes a minimal threshold for the total size of the tail and incoming buffer, below which they are merged. The sum of the tail and the incoming buffer is used to avoid a case where attacker alternates the size of data packets to trick the cumulator into always selecting compose strategy.

        Merging strategy attempts to minimize unnecessary memory writes. When possible, it expands the tail capacity and only copies the incoming buffer into available memory. Otherwise, when both tail and the buffer must be copied, the tail is reallocated (or fully replaced) with a new buffer of exponentially increasing capacity (bounded to composeMinSize) to ensure runtime O(n^2) is amortized to O(n).

        Specified by:
        cumulate in interface io.netty.handler.codec.ByteToMessageDecoder.Cumulator
      • addInput

        void addInput​(io.netty.buffer.ByteBufAllocator alloc,
                      io.netty.buffer.CompositeByteBuf composite,
                      io.netty.buffer.ByteBuf in)
      • shouldCompose

        static boolean shouldCompose​(io.netty.buffer.CompositeByteBuf composite,
                                     io.netty.buffer.ByteBuf in,
                                     int composeMinSize)
      • mergeWithCompositeTail

        static void mergeWithCompositeTail​(io.netty.buffer.ByteBufAllocator alloc,
                                           io.netty.buffer.CompositeByteBuf composite,
                                           io.netty.buffer.ByteBuf in)
        Append the given ByteBuf in to CompositeByteBuf composite by expanding or replacing the tail component of the CompositeByteBuf.

        The goal is to prevent O(n^2) runtime in a pathological case, that forces copying the tail component into a new buffer, for each incoming single-byte buffer. We append the new bytes to the tail, when a write (or a fast write) is possible.

        Otherwise, the tail is replaced with a new buffer, with the capacity increased enough to achieve runtime amortization.

        We assume that implementations of ByteBufAllocator.calculateNewCapacity(int, int), are similar to AbstractByteBufAllocator.calculateNewCapacity(int, int), which doubles buffer capacity by normalizing it to the closest power of two. This assumption is verified in unit tests for this method.