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

org.apache.arrow.memory.BufferLedger Maven / Gradle / Ivy

There is a newer version: 18.0.0
Show 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.arrow.memory;

import static org.apache.arrow.memory.BaseAllocator.indent;

import java.util.IdentityHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.arrow.memory.util.HistoricalLog;
import org.apache.arrow.util.Preconditions;

import io.netty.buffer.ArrowBuf;
import io.netty.buffer.UnsafeDirectLittleEndian;

/**
 * The reference manager that binds an {@link AllocationManager} to
 * {@link BufferAllocator} and a set of {@link ArrowBuf}. The set of
 * ArrowBufs managed by this reference manager share a common
 * fate (same reference count).
 */
public class BufferLedger implements ValueWithKeyIncluded, ReferenceManager  {
  private final IdentityHashMap buffers =
          BaseAllocator.DEBUG ? new IdentityHashMap<>() : null;
  private static final AtomicLong LEDGER_ID_GENERATOR = new AtomicLong(0);
  // unique ID assigned to each ledger
  private final long ledgerId = LEDGER_ID_GENERATOR.incrementAndGet();
  private final AtomicInteger bufRefCnt = new AtomicInteger(0); // start at zero so we can
  // manage request for retain
  // correctly
  private final long lCreationTime = System.nanoTime();
  private final BaseAllocator allocator;
  private final AllocationManager allocationManager;
  private final HistoricalLog historicalLog =
      BaseAllocator.DEBUG ? new HistoricalLog(BaseAllocator.DEBUG_LOG_LENGTH,
        "BufferLedger[%d]", 1) : null;
  private volatile long lDestructionTime = 0;

  BufferLedger(final BaseAllocator allocator, final AllocationManager allocationManager) {
    this.allocator = allocator;
    this.allocationManager = allocationManager;
  }

  boolean isOwningLedger() {
    return this == allocationManager.getOwningLedger();
  }

  public BaseAllocator getKey() {
    return allocator;
  }

  /**
   * Get the buffer allocator associated with this reference manager.
   * @return buffer allocator
   */
  @Override
  public BufferAllocator getAllocator() {
    return allocator;
  }

  /**
   * Get this ledger's reference count.
   * @return reference count
   */
  @Override
  public int getRefCount() {
    return bufRefCnt.get();
  }

  /**
   * Increment the ledger's reference count for the associated
   * underlying memory chunk. All ArrowBufs managed by this ledger
   * will share the ref count.
   */
  void increment() {
    bufRefCnt.incrementAndGet();
  }

  /**
   * Decrement the ledger's reference count by 1 for the associated underlying
   * memory chunk. If the reference count drops to 0, it implies that
   * no ArrowBufs managed by this reference manager need access to the memory
   * chunk. In that case, the ledger should inform the allocation manager
   * about releasing its ownership for the chunk. Whether or not the memory
   * chunk will be released is something that {@link AllocationManager} will
   * decide since tracks the usage of memory chunk across multiple reference
   * managers and allocators.
   * @return true if the new ref count has dropped to 0, false otherwise
   */
  @Override
  public boolean release() {
    return release(1);
  }

  /**
   * Decrement the ledger's reference count for the associated underlying
   * memory chunk. If the reference count drops to 0, it implies that
   * no ArrowBufs managed by this reference manager need access to the memory
   * chunk. In that case, the ledger should inform the allocation manager
   * about releasing its ownership for the chunk. Whether or not the memory
   * chunk will be released is something that {@link AllocationManager} will
   * decide since tracks the usage of memory chunk across multiple reference
   * managers and allocators.
   * @param decrement amount to decrease the reference count by
   * @return true if the new ref count has dropped to 0, false otherwise
   */
  @Override
  public boolean release(int decrement) {
    Preconditions.checkState(decrement >= 1,
          "ref count decrement should be greater than or equal to 1");
    // decrement the ref count
    final int refCnt = decrement(decrement);
    if (BaseAllocator.DEBUG) {
      historicalLog.recordEvent("release(%d). original value: %d",
          decrement, refCnt + decrement);
    }
    // the new ref count should be >= 0
    Preconditions.checkState(refCnt >= 0, "RefCnt has gone negative");
    return refCnt == 0;
  }

  /**
   * Decrement the ledger's reference count for the associated underlying
   * memory chunk. If the reference count drops to 0, it implies that
   * no ArrowBufs managed by this reference manager need access to the memory
   * chunk. In that case, the ledger should inform the allocation manager
   * about releasing its ownership for the chunk. Whether or not the memory
   * chunk will be released is something that {@link AllocationManager} will
   * decide since tracks the usage of memory chunk across multiple reference
   * managers and allocators.
   *
   * @param decrement amount to decrease the reference count by
   * @return the new reference count
   */
  private int decrement(int decrement) {
    allocator.assertOpen();
    final int outcome;
    synchronized (allocationManager) {
      outcome = bufRefCnt.addAndGet(-decrement);
      if (outcome == 0) {
        lDestructionTime = System.nanoTime();
        // refcount of this reference manager has dropped to 0
        // inform the allocation manager that this reference manager
        // no longer holds references to underlying memory
        allocationManager.release(this);
      }
    }
    return outcome;
  }

  /**
   * Increment the ledger's reference count for associated
   * underlying memory chunk by 1.
   */
  @Override
  public void retain() {
    retain(1);
  }

  /**
   * Increment the ledger's reference count for associated
   * underlying memory chunk by the given amount.
   *
   * @param increment amount to increase the reference count by
   */
  @Override
  public void retain(int increment) {
    Preconditions.checkArgument(increment > 0, "retain(%d) argument is not positive", increment);
    if (BaseAllocator.DEBUG) {
      historicalLog.recordEvent("retain(%d)", increment);
    }
    final int originalReferenceCount = bufRefCnt.getAndAdd(increment);
    Preconditions.checkArgument(originalReferenceCount > 0);
  }

  /**
   * Derive a new ArrowBuf from a given source ArrowBuf. The new derived
   * ArrowBuf will share the same reference count as rest of the ArrowBufs
   * associated with this ledger. This operation is typically used for
   * slicing -- creating new ArrowBufs from a compound ArrowBuf starting at
   * a particular index in the underlying memory and having access to a
   * particular length (in bytes) of data in memory chunk.
   * 

* This method is also used as a helper for transferring ownership and retain to target * allocator. *

* @param sourceBuffer source ArrowBuf * @param index index (relative to source ArrowBuf) new ArrowBuf should be * derived from * @param length length (bytes) of data in underlying memory that derived buffer will * have access to in underlying memory * @return derived buffer */ @Override public ArrowBuf deriveBuffer(final ArrowBuf sourceBuffer, int index, int length) { /* * Usage type 1 for deriveBuffer(): * Used for slicing where index represents a relative index in the source ArrowBuf * as the slice start point. This is why we need to add the source buffer offset * to compute the start virtual address of derived buffer within the * underlying chunk. * * Usage type 2 for deriveBuffer(): * Used for retain(target allocator) and transferOwnership(target allocator) * where index is 0 since these operations simply create a new ArrowBuf associated * with another combination of allocator buffer ledger for the same underlying memory */ // the memory address stored inside ArrowBuf is its starting virtual // address in the underlying memory chunk from the point it has // access. so it is already accounting for the offset of the source buffer // we can simply add the index to get the starting address of new buffer. final long derivedBufferAddress = sourceBuffer.memoryAddress() + index; // create new ArrowBuf final ArrowBuf derivedBuf = new ArrowBuf( this, null, length, // length (in bytes) in the underlying memory chunk for this new ArrowBuf derivedBufferAddress, // starting byte address in the underlying memory for this new ArrowBuf, false); // logging if (BaseAllocator.DEBUG) { historicalLog.recordEvent( "ArrowBuf(BufferLedger, BufferAllocator[%s], " + "UnsafeDirectLittleEndian[identityHashCode == " + "%d](%s)) => ledger hc == %d", allocator.name, System.identityHashCode(derivedBuf), derivedBuf.toString(), System.identityHashCode(this)); synchronized (buffers) { buffers.put(derivedBuf, null); } } return derivedBuf; } /** * Used by an allocator to create a new ArrowBuf. This is provided * as a helper method for the allocator when it allocates a new memory chunk * using a new instance of allocation manager and creates a new reference manager * too. * * @param length The length in bytes that this ArrowBuf will provide access to. * @param manager An optional BufferManager argument that can be used to manage expansion of * this ArrowBuf * @return A new ArrowBuf that shares references with all ArrowBufs associated * with this BufferLedger */ ArrowBuf newArrowBuf(final int length, final BufferManager manager) { allocator.assertOpen(); // the start virtual address of the ArrowBuf will be same as address of memory chunk final long startAddress = allocationManager.getMemoryChunk().memoryAddress(); // create ArrowBuf final ArrowBuf buf = new ArrowBuf(this, manager, length, startAddress, false); // logging if (BaseAllocator.DEBUG) { historicalLog.recordEvent( "ArrowBuf(BufferLedger, BufferAllocator[%s], " + "UnsafeDirectLittleEndian[identityHashCode == " + "%d](%s)) => ledger hc == %d", allocator.name, System.identityHashCode(buf), buf.toString(), System.identityHashCode(this)); synchronized (buffers) { buffers.put(buf, null); } } return buf; } /** * Create a new ArrowBuf that is associated with an alternative allocator for the purposes of * memory ownership and accounting. This has no impact on the reference counting for the current * ArrowBuf except in the situation where the passed in Allocator is the same as the current buffer. *

* This operation has no impact on the reference count of this ArrowBuf. The newly created * ArrowBuf with either have a reference count of 1 (in the case that this is the first time this * memory is being associated with the target allocator or in other words allocation manager currently * doesn't hold a mapping for the target allocator) or the current value of the reference count for * the target allocator-reference manager combination + 1 in the case that the provided allocator * already had an association to this underlying memory. *

* * @param srcBuffer source ArrowBuf * @param target The target allocator to create an association with. * @return A new ArrowBuf which shares the same underlying memory as the provided ArrowBuf. */ @Override public ArrowBuf retain(final ArrowBuf srcBuffer, BufferAllocator target) { if (srcBuffer.isEmpty()) { return srcBuffer; } if (BaseAllocator.DEBUG) { historicalLog.recordEvent("retain(%s)", target.getName()); } // the call to associate will return the corresponding reference manager (buffer ledger) for // the target allocator. if the allocation manager didn't already have a mapping // for the target allocator, it will create one and return the new reference manager with a // reference count of 1. Thus the newly created buffer in this case will have a ref count of 1. // alternatively, if there was already a mapping for in // allocation manager, the ref count of the new buffer will be targetrefmanager.refcount() + 1 // and this will be true for all the existing buffers currently managed by targetrefmanager final BufferLedger targetRefManager = allocationManager.associate((BaseAllocator)target); // create a new ArrowBuf to associate with new allocator and target ref manager final int targetBufLength = srcBuffer.capacity(); ArrowBuf targetArrowBuf = targetRefManager.deriveBuffer(srcBuffer, 0, targetBufLength); targetArrowBuf.readerIndex(srcBuffer.readerIndex()); targetArrowBuf.writerIndex(srcBuffer.writerIndex()); return targetArrowBuf; } /** * Transfer any balance the current ledger has to the target ledger. In the case * that the current ledger holds no memory, no transfer is made to the new ledger. * * @param targetReferenceManager The ledger to transfer ownership account to. * @return Whether transfer fit within target ledgers limits. */ boolean transferBalance(final ReferenceManager targetReferenceManager) { Preconditions.checkArgument(targetReferenceManager != null, "Expecting valid target reference manager"); final BaseAllocator targetAllocator = (BaseAllocator)targetReferenceManager.getAllocator(); Preconditions.checkArgument(allocator.root == targetAllocator.root, "You can only transfer between two allocators that share the same root."); allocator.assertOpen(); targetReferenceManager.getAllocator().assertOpen(); // if we're transferring to ourself, just return. if (targetReferenceManager == this) { return true; } // since two balance transfers out from the allocation manager could cause incorrect // accounting, we need to ensure // that this won't happen by synchronizing on the allocation manager instance. synchronized (allocationManager) { if (allocationManager.getOwningLedger() != this) { // since the calling reference manager is not the owning // reference manager for the underlying memory, transfer is // a NO-OP return true; } if (BaseAllocator.DEBUG) { this.historicalLog.recordEvent("transferBalance(%s)", targetReferenceManager.getAllocator().getName()); } boolean overlimit = targetAllocator.forceAllocate(allocationManager.getSize()); allocator.releaseBytes(allocationManager.getSize()); // since the transfer can only happen from the owning reference manager, // we need to set the target ref manager as the new owning ref manager // for the chunk of memory in allocation manager allocationManager.setOwningLedger((BufferLedger)targetReferenceManager); return overlimit; } } /** * Transfer the memory accounting ownership of this ArrowBuf to another allocator. * This will generate a new ArrowBuf that carries an association with the underlying memory * of this ArrowBuf. If this ArrowBuf is connected to the owning BufferLedger of this memory, * that memory ownership/accounting will be transferred to the target allocator. If this * ArrowBuf does not currently own the memory underlying it (and is only associated with it), * this does not transfer any ownership to the newly created ArrowBuf. *

* This operation has no impact on the reference count of this ArrowBuf. The newly created * ArrowBuf with either have a reference count of 1 (in the case that this is the first time * this memory is being associated with the new allocator) or the current value of the reference * count for the other AllocationManager/BufferLedger combination + 1 in the case that the provided * allocator already had an association to this underlying memory. *

*

* Transfers will always succeed, even if that puts the other allocator into an overlimit * situation. This is possible due to the fact that the original owning allocator may have * allocated this memory out of a local reservation whereas the target allocator may need to * allocate new memory from a parent or RootAllocator. This operation is done n a mostly-lockless * but consistent manner. As such, the overlimit==true situation could occur slightly prematurely * to an actual overlimit==true condition. This is simply conservative behavior which means we may * return overlimit slightly sooner than is necessary. *

* * @param target The allocator to transfer ownership to. * @return A new transfer result with the impact of the transfer (whether it was overlimit) as * well as the newly created ArrowBuf. */ @Override public TransferResult transferOwnership(final ArrowBuf srcBuffer, final BufferAllocator target) { if (srcBuffer.isEmpty()) { return new TransferResult(true, srcBuffer); } // the call to associate will return the corresponding reference manager (buffer ledger) for // the target allocator. if the allocation manager didn't already have a mapping // for the target allocator, it will create one and return the new reference manager with a // reference count of 1. Thus the newly created buffer in this case will have a ref count of 1. // alternatively, if there was already a mapping for in // allocation manager, the ref count of the new buffer will be targetrefmanager.refcount() + 1 // and this will be true for all the existing buffers currently managed by targetrefmanager final BufferLedger targetRefManager = allocationManager.associate((BaseAllocator)target); // create a new ArrowBuf to associate with new allocator and target ref manager final int targetBufLength = srcBuffer.capacity(); final ArrowBuf targetArrowBuf = targetRefManager.deriveBuffer(srcBuffer, 0, targetBufLength); targetArrowBuf.readerIndex(srcBuffer.readerIndex()); targetArrowBuf.writerIndex(srcBuffer.writerIndex()); final boolean allocationFit = transferBalance(targetRefManager); return new TransferResult(allocationFit, targetArrowBuf); } /** * The outcome of a Transfer. */ public class TransferResult implements OwnershipTransferResult { // Whether this transfer fit within the target allocator's capacity. final boolean allocationFit; // The newly created buffer associated with the target allocator public final ArrowBuf buffer; private TransferResult(boolean allocationFit, ArrowBuf buffer) { this.allocationFit = allocationFit; this.buffer = buffer; } @Override public ArrowBuf getTransferredBuffer() { return buffer; } @Override public boolean getAllocationFit() { return allocationFit; } } /** * Total size (in bytes) of memory underlying this reference manager. * @return Size (in bytes) of the memory chunk */ @Override public int getSize() { return allocationManager.getSize(); } /** * How much memory is accounted for by this ledger. This is either getSize() * if this is the owning ledger for the memory or zero in the case that this * is not the owning ledger associated with this memory. * @return Amount of accounted(owned) memory associated with this ledger. */ @Override public int getAccountedSize() { synchronized (allocationManager) { if (allocationManager.getOwningLedger() == this) { return allocationManager.getSize(); } else { return 0; } } } /** * Print the current ledger state to the provided StringBuilder. * * @param sb The StringBuilder to populate. * @param indent The level of indentation to position the data. * @param verbosity The level of verbosity to print. */ void print(StringBuilder sb, int indent, BaseAllocator.Verbosity verbosity) { indent(sb, indent) .append("ledger[") .append(ledgerId) .append("] allocator: ") .append(allocator.name) .append("), isOwning: ") .append(", size: ") .append(", references: ") .append(bufRefCnt.get()) .append(", life: ") .append(lCreationTime) .append("..") .append(lDestructionTime) .append(", allocatorManager: [") .append(", life: "); if (!BaseAllocator.DEBUG) { sb.append("]\n"); } else { synchronized (buffers) { sb.append("] holds ") .append(buffers.size()) .append(" buffers. \n"); for (ArrowBuf buf : buffers.keySet()) { buf.print(sb, indent + 2, verbosity); sb.append('\n'); } } } } public UnsafeDirectLittleEndian getUnderlying() { return allocationManager.getMemoryChunk(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy