Search Results for

    Show / Hide Table of Contents

    Class PooledPayloadWriter

    A growable, pooled IBufferWriter<T> that plugs the gap between the bare IBufferWriter<T> contract and what a zero-copy transport actually needs: the ability to (a) read back the bytes already written, (b) patch already-written bytes at an absolute position (reserve-then-patch for length prefixes and headers whose value is only known once later bytes have been written), and (c) hand off pooled-buffer OWNERSHIP so a transport can complete an asynchronous socket write and only then return the memory to the pool, with no intermediate copy.

    This contract was first invented privately, as Artery G1's internal PooledFrameWriter, because IBufferWriter<T> alone could not express it. It belongs on the core SerializerV2 surface: any V2 encode path -- Artery, a future sourcegen'd serializer, or a user transport -- needs the same reserve/patch/detach shape. Semantically it plays the same role as Pekko's EnvelopeBufferPool: a reusable wire buffer whose lifetime is decoupled from the encode call that filled it.

    Ownership rules (read this before using the type):

    • A freshly constructed writer owns one array rented from its buffer source: the ArrayPool<T> passed to the constructor, or Shared when none is given. It grows by renting a bigger array, copying the already-written prefix, and returning the old array -- never by copying on every write. EVERY rent and return in the writer's lifetime -- the initial rent, growth, Dispose(), and the disposal of the owner returned by Detach() -- goes through that same pool.
    • Dispose() (without a prior Detach()) returns the current backing array to the pool. This is the "I'm done, nobody else needs these bytes" path -- e.g. an encode that threw partway through.
    • Detach() is the "hand the bytes to someone else" path: it returns an IMemoryOwner<T> whose Memory is exactly the written slice, and whose own Dispose() is what returns the array to the pool (e.g. once an async socket write against that memory has completed). After Detach(), this writer is SPENT: every member except Dispose() throws ObjectDisposedException, calling Detach() again throws, and this writer's own Dispose() becomes a no-op because ownership already moved.
    • Reset() reuses the current rented array for a new encode (no re-rent); it is only valid while the writer is still alive (neither disposed nor detached).

    Why ArrayPool<T> and not a custom buffer-source abstraction (or MemoryPool<T>)? Deliberate: ArrayPool<T> is the narrowest standard type that covers every buffer-source strategy this writer needs -- the shared pool, a dedicated per-transport pool, and POH-pinned arrays via a custom ArrayPool<T> subclass (artery design.md, Decision 9: POH-pinned buffers only if pinning churn shows up in measurement). A bespoke interface would duplicate it; MemoryPool<T> would force Memory<T>-based internals and give up the raw array the patch/growth paths rely on.

    Inheritance
    object
    PooledPayloadWriter
    Implements
    IBufferWriter<byte>
    IDisposable
    Inherited Members
    object.Equals(object)
    object.Equals(object, object)
    object.GetHashCode()
    object.GetType()
    object.ReferenceEquals(object, object)
    object.ToString()
    Namespace: Akka.Serialization
    Assembly: Akka.dll
    Syntax
    public sealed class PooledPayloadWriter : IBufferWriter<byte>, IDisposable

    Constructors

    | Edit this page View Source

    PooledPayloadWriter(int, int, ArrayPool<byte>?)

    Initializes a new instance of the PooledPayloadWriter class.

    Declaration
    public PooledPayloadWriter(int initialCapacityHint = 256, int maxCapacity = 2147483647, ArrayPool<byte>? pool = null)
    Parameters
    Type Name Description
    int initialCapacityHint

    A hint for the initial rented buffer size; the writer grows automatically if exceeded (bounded by maxCapacity).

    int maxCapacity

    The maximum total number of bytes this writer will ever hold. Any GetSpan(int), GetMemory(int), or Advance(int) call that would push the written count past this bound throws PayloadSizeExceededException. Defaults to MaxValue (effectively unbounded).

    ArrayPool<byte> pool

    The buffer source for every rent and return in this writer's lifetime (initial rent, growth, Dispose(), and the disposal of the owner returned by Detach()). null (the default) means Shared. Pass a dedicated pool -- or a custom ArrayPool<T> subclass over POH-pinned arrays -- to control buffer placement without changing this type.

    Exceptions
    Type Condition
    ArgumentOutOfRangeException

    maxCapacity is not positive.

    Properties

    | Edit this page View Source

    WrittenCount

    The number of bytes written so far.

    Declaration
    public int WrittenCount { get; }
    Property Value
    Type Description
    int
    | Edit this page View Source

    WrittenMemory

    A read-only view over the bytes written so far.

    Declaration
    public ReadOnlyMemory<byte> WrittenMemory { get; }
    Property Value
    Type Description
    ReadOnlyMemory<byte>
    | Edit this page View Source

    WrittenSpan

    A read-only view over the bytes written so far.

    Declaration
    public ReadOnlySpan<byte> WrittenSpan { get; }
    Property Value
    Type Description
    ReadOnlySpan<byte>

    Methods

    | Edit this page View Source

    Advance(int)

    Notifies the IBufferWriter<T> that count data items were written to the output Span<T> or Memory<T>.

    Declaration
    public void Advance(int count)
    Parameters
    Type Name Description
    int count

    The number of data items written to the Span<T> or Memory<T>.

    Exceptions
    Type Condition
    PayloadSizeExceededException

    Advancing by count would exceed maxCapacity.

    | Edit this page View Source

    Detach()

    Detaches ownership of the backing buffer from this writer: returns an IMemoryOwner<T> whose Memory is exactly the written slice. Disposing the returned owner returns the underlying array to the SAME pool this writer rented it from (the constructor's pool, or Shared). The IMemoryOwner<T> return type deliberately leaves room for a future pooled-owner implementation (Pekko EnvelopeBufferPool-style) without an API change.

    After this call the writer is SPENT: every member except Dispose() throws ObjectDisposedException -- including a second call to Detach(). This writer's own Dispose() becomes a no-op, because ownership of the buffer has moved to the returned owner.

    Declaration
    public IMemoryOwner<byte> Detach()
    Returns
    Type Description
    IMemoryOwner<byte>

    An owner of the written bytes; the caller is responsible for disposing it.

    Exceptions
    Type Condition
    ObjectDisposedException

    The writer has already been detached or disposed.

    | Edit this page View Source

    Dispose()

    Returns the rented buffer to the pool it was rented from. A no-op if this writer has already been detached (ownership moved to the IMemoryOwner<T> returned by Detach()) or already disposed.

    Declaration
    public void Dispose()
    | Edit this page View Source

    GetMemory(int)

    Returns a Memory<T> to write to that is at least the requested size (specified by sizeHint).

    Declaration
    public Memory<byte> GetMemory(int sizeHint = 0)
    Parameters
    Type Name Description
    int sizeHint

    The minimum length of the returned Memory<T>. If 0, a non-empty buffer is returned.

    Returns
    Type Description
    Memory<byte>

    A Memory<T> of at least the size sizeHint. If sizeHint is 0, returns a non-empty buffer.

    Exceptions
    Type Condition
    OutOfMemoryException

    The requested buffer size is not available.

    PayloadSizeExceededException

    Satisfying sizeHint would exceed maxCapacity.

    | Edit this page View Source

    GetPatchSpan(int, int)

    Returns a mutable view over an already-written absolute byte range, so a caller can back-patch a field (a frame length, a fixed header) whose value is only known after later bytes have been written.

    Declaration
    public Span<byte> GetPatchSpan(int start, int length)
    Parameters
    Type Name Description
    int start

    The absolute start offset, in bytes, from the start of the writer.

    int length

    The number of bytes to expose for patching.

    Returns
    Type Description
    Span<byte>
    Exceptions
    Type Condition
    ArgumentOutOfRangeException

    The patch range falls outside the already-written bytes.

    | Edit this page View Source

    GetSpan(int)

    Returns a Span<T> to write to that is at least the requested size (specified by sizeHint).

    Declaration
    public Span<byte> GetSpan(int sizeHint = 0)
    Parameters
    Type Name Description
    int sizeHint

    The minimum length of the returned Span<T>. If 0, a non-empty buffer is returned.

    Returns
    Type Description
    Span<byte>

    A Span<T> of at least the size sizeHint. If sizeHint is 0, returns a non-empty buffer.

    Exceptions
    Type Condition
    PayloadSizeExceededException

    Satisfying sizeHint would exceed maxCapacity.

    | Edit this page View Source

    Reset()

    Resets the writer for reuse without re-renting: the currently rented array is kept and the written count is set back to zero. Only valid while the writer is still alive (has not been detached or disposed).

    Declaration
    public void Reset()
    Exceptions
    Type Condition
    ObjectDisposedException

    The writer has been detached or disposed.

    Implements

    IBufferWriter<T>
    IDisposable

    Extension Methods

    ObjectExtensions.IsDefaultForType<T>(T)
    ObjectExtensions.AsOption<T>(T)
    Extensions.AsInstanceOf<T>(object)
    In this article
    • githubEdit this page
    • View Source
    Back to top
    Contribute
    • Project Chat
    • Discussion Forum
    • Source Code
    Support
    • Akka.NET Support Plans
    • Akka.NET Observability Tools
    • Akka.NET Training & Consulting
    Maintained By
    • Petabridge - The Akka.NET Company
    • Learn Akka.NET