All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.kafka.clients.producer.internals.BufferPool Maven / Gradle / Ivy

The newest version!
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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
 *
 *    http://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 org.apache.kafka.clients.producer.internals;

import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.kafka.clients.producer.BufferExhaustedException;
import org.apache.kafka.common.MetricName;
import org.apache.kafka.common.metrics.Metrics;
import org.apache.kafka.common.metrics.Sensor;
import org.apache.kafka.common.metrics.stats.Rate;
import org.apache.kafka.common.utils.Time;


/**
 * A pool of ByteBuffers kept under a given memory limit. This class is fairly specific to the needs of the producer. In
 * particular it has the following properties:
 * 
    *
  1. There is a special "poolable size" and buffers of this size are kept in a free list and recycled *
  2. It is fair. That is all memory is given to the longest waiting thread until it has sufficient memory. This * prevents starvation or deadlock when a thread asks for a large chunk of memory and needs to block until multiple * buffers are deallocated. *
*/ public final class BufferPool { private final long totalMemory; private final int poolableSize; private final boolean blockOnExhaustion; private final ReentrantLock lock; private final Deque free; private final Deque waiters; private long availableMemory; private final Metrics metrics; private final Time time; private final Sensor waitTime; /** * Create a new buffer pool * * @param memory The maximum amount of memory that this buffer pool can allocate * @param poolableSize The buffer size to cache in the free list rather than deallocating * @param blockOnExhaustion This controls the behavior when the buffer pool is out of memory. If true the * {@link #allocate(int)} call will block and wait for memory to be returned to the pool. If false * {@link #allocate(int)} will throw an exception if the buffer is out of memory. * @param metrics instance of Metrics * @param time time instance * @param metricGrpName logical group name for metrics * @param metricTags additional key/val attributes for metrics */ public BufferPool(long memory, int poolableSize, boolean blockOnExhaustion, Metrics metrics, Time time , String metricGrpName , Map metricTags) { this.poolableSize = poolableSize; this.blockOnExhaustion = blockOnExhaustion; this.lock = new ReentrantLock(); this.free = new ArrayDeque(); this.waiters = new ArrayDeque(); this.totalMemory = memory; this.availableMemory = memory; this.metrics = metrics; this.time = time; this.waitTime = this.metrics.sensor("bufferpool-wait-time"); MetricName metricName = new MetricName("bufferpool-wait-ratio", metricGrpName, "The fraction of time an appender waits for space allocation.", metricTags); this.waitTime.add(metricName, new Rate(TimeUnit.NANOSECONDS)); } /** * Allocate a buffer of the given size. This method blocks if there is not enough memory and the buffer pool * is configured with blocking mode. * * @param size The buffer size to allocate in bytes * @return The buffer * @throws InterruptedException If the thread is interrupted while blocked * @throws IllegalArgumentException if size is larger than the total memory controlled by the pool (and hence we would block * forever) * @throws BufferExhaustedException if the pool is in non-blocking mode and size exceeds the free memory in the pool */ public ByteBuffer allocate(int size) throws InterruptedException { if (size > this.totalMemory) throw new IllegalArgumentException("Attempt to allocate " + size + " bytes, but there is a hard limit of " + this.totalMemory + " on memory allocations."); this.lock.lock(); try { // check if we have a free buffer of the right size pooled if (size == poolableSize && !this.free.isEmpty()) return this.free.pollFirst(); // now check if the request is immediately satisfiable with the // memory on hand or if we need to block int freeListSize = this.free.size() * this.poolableSize; if (this.availableMemory + freeListSize >= size) { // we have enough unallocated or pooled memory to immediately // satisfy the request freeUp(size); this.availableMemory -= size; lock.unlock(); return ByteBuffer.allocate(size); } else if (!blockOnExhaustion) { throw new BufferExhaustedException("You have exhausted the " + this.totalMemory + " bytes of memory you configured for the client and the client is configured to error" + " rather than block when memory is exhausted."); } else { // we are out of memory and will have to block int accumulated = 0; ByteBuffer buffer = null; Condition moreMemory = this.lock.newCondition(); this.waiters.addLast(moreMemory); // loop over and over until we have a buffer or have reserved // enough memory to allocate one while (accumulated < size) { long startWait = time.nanoseconds(); moreMemory.await(); long endWait = time.nanoseconds(); this.waitTime.record(endWait - startWait, time.milliseconds()); // check if we can satisfy this request from the free list, // otherwise allocate memory if (accumulated == 0 && size == this.poolableSize && !this.free.isEmpty()) { // just grab a buffer from the free list buffer = this.free.pollFirst(); accumulated = size; } else { // we'll need to allocate memory, but we may only get // part of what we need on this iteration freeUp(size - accumulated); int got = (int) Math.min(size - accumulated, this.availableMemory); this.availableMemory -= got; accumulated += got; } } // remove the condition for this thread to let the next thread // in line start getting memory Condition removed = this.waiters.removeFirst(); if (removed != moreMemory) throw new IllegalStateException("Wrong condition: this shouldn't happen."); // signal any additional waiters if there is more memory left // over for them if (this.availableMemory > 0 || !this.free.isEmpty()) { if (!this.waiters.isEmpty()) this.waiters.peekFirst().signal(); } // unlock and return the buffer lock.unlock(); if (buffer == null) return ByteBuffer.allocate(size); else return buffer; } } finally { if (lock.isHeldByCurrentThread()) lock.unlock(); } } /** * Attempt to ensure we have at least the requested number of bytes of memory for allocation by deallocating pooled * buffers (if needed) */ private void freeUp(int size) { while (!this.free.isEmpty() && this.availableMemory < size) this.availableMemory += this.free.pollLast().capacity(); } /** * Return buffers to the pool. If they are of the poolable size add them to the free list, otherwise just mark the * memory as free. * * @param buffer The buffer to return * @param size The size of the buffer to mark as deallocated, note that this maybe smaller than buffer.capacity * since the buffer may re-allocate itself during in-place compression */ public void deallocate(ByteBuffer buffer, int size) { lock.lock(); try { if (size == this.poolableSize && size == buffer.capacity()) { buffer.clear(); this.free.add(buffer); } else { this.availableMemory += size; } Condition moreMem = this.waiters.peekFirst(); if (moreMem != null) moreMem.signal(); } finally { lock.unlock(); } } public void deallocate(ByteBuffer buffer) { deallocate(buffer, buffer.capacity()); } /** * the total free memory both unallocated and in the free list */ public long availableMemory() { lock.lock(); try { return this.availableMemory + this.free.size() * this.poolableSize; } finally { lock.unlock(); } } /** * Get the unallocated memory (not in the free list or in use) */ public long unallocatedMemory() { lock.lock(); try { return this.availableMemory; } finally { lock.unlock(); } } /** * The number of threads blocked waiting on memory */ public int queued() { lock.lock(); try { return this.waiters.size(); } finally { lock.unlock(); } } /** * The buffer size that will be retained in the free list after use */ public int poolableSize() { return this.poolableSize; } /** * The total memory managed by this pool */ public long totalMemory() { return this.totalMemory; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy