netty5/buffer/src/main/java/io/netty/buffer/api/BufferAllocator.java
Chris Vest 765f8989ca
Introduce alternative Buffer API (#11347)
Motivation:

In Netty 5 we wish to have a simpler, safe, future proof, and more consistent buffer API.
We developed such an API in the incubating buffer repository, and taking it through multiple rounds of review and adjustments.
This PR/commit bring the results of that work into the Netty 5 branch of the main Netty repository.

Modifications:

* `Buffer` is an interface, and all implementations are hidden behind it.
  There is no longer an inheritance hierarchy of abstract classes and implementations.
* Reference counting is gone.
  After a buffer has been allocated, calling `close` on it will deallocate it.
  It is then up to users and integrators to ensure that the life-times of buffers are managed correctly.
  This is usually not a problem as buffers tend to flow through the pipeline to be released after a terminal IO operation.
* Slice and duplicate methods are replaced with `split`.
  By removing slices, duplicate, and reference counting, there is no longer a possibility that a buffer and/or its memory can be shared and accessible through multiple routes.
  This solves the problem of data being accessed from multiple places in an uncoordinated way, and the problem of buffer memory being closed while being in use by some unsuspecting piece of code.
  Some adjustments will have to be made to other APIs, idioms, and usages, since `split` is not always a replacement for `slice` in some use cases.
* The `split` has been added which allows memory to be shared among multiple buffers, but in non-overlapping regions.
  When the memory regions don't overlap, it will not be possible for the different buffers to interfere with each other.
  An internal, and completely transparent, reference counting system ensures that the backing memory is released once the last buffer view is closed.
* A Send API has been introduced that can be used to enforce (in the type system) the transfer of buffer ownership.
  This is not expected to be used in the pipeline flow itself, but rather for other objects that wrap buffers and wish to avoid becoming "shared views" — the absence of "shared views" of memory is important for avoiding bugs in the absence of reference counting.
* A new BufferAllocator API, where the choice of implementation determines factors like on-/off-heap, pooling or not.
  How access to the different allocators will be exposed to integrators will be decided later.
  Perhaps they'll be directly accessible on the `ChannelHandlerContext`.
* The `PooledBufferAllocator` has been copied and modified to match the new allocator API.
  This includes unifying its implementation that was previously split across on-heap and off-heap.
* The `PooledBufferAllocator` implementation has also been adjusted to allocate 4 MiB chunks by default, and a few changes have been made to the implementation to make a newly created, empty allocator use significantly less heap memory.
* A `Resource` interface has been added, which defines the life-cycle methods and the `send` method.
  The `Buffer` interface extends this.
* Analogues for `ByteBufHolder` has been added in the `BufferHolder` and `BufferRef` classes.
* `ByteCursor` is added as a new way to iterate the data in buffers.
  The byte cursor API is designed to be more JIT friendly than an iterator, or the existing `ByteProcessor` interface.
* `CompositeBuffer` no longer permit the same level of access to its internal components.
  The composite buffer enforces its ownership of its components via the `Send` API, and the components can only be individually accessed with the `forEachReadable` and `forEachWritable` methods.
  This keeps the API and behavioral differences between composite and non-composite buffers to a minimum.
* Two implementations of the `Buffer` interface are provided with the API: One based on `ByteBuffer`, and one based on `sun.misc.Unsafe`.
  The `ByteBuffer` implementation is used by default.
  More implementations can be loaded from the classpath via service loading.
  The `MemorySegment` based implementation is left behind in the incubator repository.
* An extensive and highly parameterised test suite has been added, to ensure that all implementations have consistent and correct behaviour, regardless of their configuration or composition.

Result:

We have a new buffer API that is simpler, better tested, more consistent in behaviour, and safer by design, than the existing `ByteBuf` API.

The next legs of this journey will be about integrating this new API into Netty proper, and deprecate (and eventually remove) the `ByteBuf` API.

This fixes #11024, #8601, #8543, #8542, #8534, #3358, and #3306.
2021-06-28 12:06:44 +02:00

129 lines
6.0 KiB
Java

/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.buffer.api;
import io.netty.buffer.api.pool.PooledBufferAllocator;
import java.util.function.Supplier;
/**
* Interface for allocating {@link Buffer}s.
*/
public interface BufferAllocator extends AutoCloseable {
/**
* Produces a {@link BufferAllocator} that allocates unpooled, on-heap buffers.
* On-heap buffers have a {@code byte[]} internally, and their {@linkplain Buffer#nativeAddress() native address}
* is zero.
* <p>
* The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
*
* @return A non-pooling allocator of on-heap buffers
*/
static BufferAllocator onHeapUnpooled() {
return new ManagedBufferAllocator(MemoryManager.instance(), false);
}
/**
* Produces a {@link BufferAllocator} that allocates unpooled, off-heap buffers.
* Off-heap buffers a native memory pointer internally, which can be obtained from their
* {@linkplain Buffer#nativeAddress() native address method.
* <p>
* The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
*
* @return A non-pooling allocator of on-heap buffers
*/
static BufferAllocator offHeapUnpooled() {
return new ManagedBufferAllocator(MemoryManager.instance(), true);
}
/**
* Produces a pooling {@link BufferAllocator} that allocates and recycles on-heap buffers.
* On-heap buffers have a {@code byte[]} internally, and their {@linkplain Buffer#nativeAddress() native address}
* is zero.
* <p>
* The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
*
* @return A pooling allocator of on-heap buffers
*/
static BufferAllocator onHeapPooled() {
return new PooledBufferAllocator(MemoryManager.instance(), false);
}
/**
* Produces a pooling {@link BufferAllocator} that allocates and recycles off-heap buffers.
* Off-heap buffers a native memory pointer internally, which can be obtained from their
* {@linkplain Buffer#nativeAddress() native address method.
* <p>
* The concrete {@link Buffer} implementation is chosen by {@link MemoryManager#instance()}.
*
* @return A pooling allocator of on-heap buffers
*/
static BufferAllocator offHeapPooled() {
return new PooledBufferAllocator(MemoryManager.instance(), true);
}
/**
* Allocate a {@link Buffer} of the given size in bytes. This method may throw an {@link OutOfMemoryError} if there
* is not enough free memory available to allocate a {@link Buffer} of the requested size.
* <p>
* The buffer will use big endian byte order.
*
* @param size The size of {@link Buffer} to allocate.
* @return The newly allocated {@link Buffer}.
* @throws IllegalStateException if this allocator has been {@linkplain #close() closed}.
*/
Buffer allocate(int size);
/**
* Create a supplier of "constant" {@linkplain Buffer Buffers} from this allocator, that all have the given
* byte contents. The buffer has the same capacity as the byte array length, and its write offset is placed at the
* end, and its read offset is at the beginning, such that the entire buffer contents are readable.
* <p>
* The buffers produced by the supplier will each have their own independent life-cycle, and closing them will
* make them {@linkplain Buffer#isAccessible() inaccessible}, just like normally allocated buffers.
* <p>
* The buffers produced are "constants", in the sense that they are {@linkplain Buffer#readOnly() read-only}.
* <p>
* It can generally be expected, but is not guaranteed, that the returned supplier is more resource efficient than
* allocating and copying memory with other available APIs. In such optimised implementations, the underlying memory
* baking the buffers will be shared among all the buffers produced by the supplier.
* <p>
* The primary use case for this API, is when you need to repeatedly produce buffers with the same contents, and
* you perhaps wish to keep a {@code static final} field with these contents. The supplier-based API enforces
* that each usage get their own distinct buffer instance. Each of these instances cannot interfere with each other,
* so bugs like closing, or modifying the contents, of a shared buffer cannot occur.
*
* @param bytes The byte contents of the buffers produced by the returned supplier.
* @return A supplier of read-only buffers with the given contents.
* @throws IllegalStateException if this allocator has been {@linkplain #close() closed}, but any supplier obtained
* prior to closing the allocator will continue to work.
*/
Supplier<Buffer> constBufferSupplier(byte[] bytes);
/**
* Close this allocator, freeing all of its internal resources.
* <p>
* Existing (currently in-use) allocated buffers will not be impacted by calling this method.
* If this is a pooling or caching allocator, then existing buffers will be immediately freed when they are closed,
* instead of being pooled or cached.
* <p>
* The allocator can no longer be used to allocate more buffers after calling this method.
* Attempting to allocate from a closed allocator will cause {@link IllegalStateException}s to be thrown.
*/
@Override
void close();
}