public final class

SharedByteBuffer

extends java.lang.Object

implements java.io.Closeable

 java.lang.Object

↳androidx.camera.video.internal.SharedByteBuffer

Gradle dependencies

compile group: 'androidx.camera', name: 'camera-video', version: '1.2.0-alpha01'

  • groupId: androidx.camera
  • artifactId: camera-video
  • version: 1.2.0-alpha01

Artifact androidx.camera:camera-video:1.2.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

A read-only wrapper for a java.nio.ByteBuffer that may be shared between multiple clients.

Every SharedByteBuffer must be closed with SharedByteBuffer.close() when no longer in use. This will signal to other clients that it may be safe to reclaim or reuse the wrapped ByteBuffer. Failure to call SharedByteBuffer.close() may lead to memory leaks or create stalls in processing pipelines.

The java.nio.ByteBuffer returned by SharedByteBuffer.get() is safe to read from as long as SharedByteBuffer.close() has not been called. Once SharedByteBuffer.close() has been called, it should be assumed the java.nio.ByteBuffer contains invalid data.

Summary

Methods
public voidclose()

Closes this instance of SharedByteBuffer.

protected voidfinalize()

public java.nio.ByteBufferget()

Returns a read-only view into the java.nio.ByteBuffer instance that is being shared.

public java.lang.StringtoString()

from java.lang.Objectclone, equals, getClass, hashCode, notify, notifyAll, wait, wait, wait

Methods

public void close()

Closes this instance of SharedByteBuffer.

Once an instance is closed, SharedByteBuffer.get() can no longer be used to retrieve the buffer. If any reference to the buffer exists from a previous invocation of SharedByteBuffer.get() it should no longer be used.

Closing a SharedByteBuffer is idempotent. Only the first close() will have any effect. Subsequent calls will be no-ops.

public java.nio.ByteBuffer get()

Returns a read-only view into the java.nio.ByteBuffer instance that is being shared.

Once an instance is closed with SharedByteBuffer.close(), this method can no longer be used to retrieve the buffer. Once closed, the object returned by this method should be assumed invalid and should no longer be used.

Returns:

the underlying shared java.nio.ByteBuffer

See also: SharedByteBuffer.close()

public java.lang.String toString()

protected void finalize()

Source

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed 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 androidx.camera.video.internal;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.core.Logger;
import androidx.core.util.Pair;
import androidx.core.util.Preconditions;

import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A read-only wrapper for a {@link ByteBuffer} that may be shared between multiple clients.
 *
 * <p>Every {@code SharedByteBuffer} must be closed with {@link #close()} when no longer in use.
 * This will signal to other clients that it may be safe to reclaim or reuse the wrapped ByteBuffer.
 * Failure to call {@link #close()} may lead to memory leaks or create stalls in processing
 * pipelines.
 *
 * <p>The {@link ByteBuffer} returned by {@link #get()} is safe to read from as long as
 * {@link #close()} has not been called. Once {@link #close()} has been called, it should be
 * assumed the {@link ByteBuffer} contains invalid data.
 */
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class SharedByteBuffer implements Closeable {

    private static final String TAG = "SharedByteBuffer";

    private final ByteBuffer mSharedByteBuffer;

    // Unique ID for the original SharedByteBuffer. Transferred to all share() instances.
    private final int mShareId;

    private final Object mCloseLock = new Object();

    private final Pair<Executor, Runnable> mFinalCloseAction;

    @GuardedBy("mCloseLock")
    private final AtomicInteger mSharedRefCount;

    @GuardedBy("mCloseLock")
    private boolean mClosed = false;

    /**
     * Creates an initial instance of SharedByteBuffer for sharing a {@link ByteBuffer} between
     * multiple clients.
     *
     * <p>The initial SharedByteBuffer holds the initial reference count. Each subsequent call to
     * {@link #share()} will increment the reference count. Once all instances generated by
     * {@link #share()} have been closed, and the initial instance has also been closed, the
     * provided {@code finalCloseAction} will run.
     *
     * @param sharedBuf The buffer to be wrapped.
     * @param closeActionExecutor The executor used to execute {@code finalCloseAction}.
     * @param finalCloseAction The action to take once all shared instances have been closed.
     *                         This action will only be run once the initial instance and all
     *                         instances generated by {@link #share()} have been closed. A common
     *                         {@code finalCloseAction} might be to return the ByteBuffer to a
     *                         pool so it can be reused.
     */
    @NonNull
    static SharedByteBuffer newSharedInstance(@NonNull ByteBuffer sharedBuf,
            @NonNull Executor closeActionExecutor, @NonNull Runnable finalCloseAction) {
        AtomicInteger sharedRefCount = new AtomicInteger(1);
        int shareId = System.identityHashCode(sharedBuf);
        ByteBuffer readOnlyBuf = Preconditions.checkNotNull(sharedBuf).asReadOnlyBuffer();
        return new SharedByteBuffer(readOnlyBuf, sharedRefCount,
                new Pair<>(Preconditions.checkNotNull(closeActionExecutor),
                        Preconditions.checkNotNull(finalCloseAction)), shareId);
    }

    /**
     * Creates a new instance of a SharedByteBuffer from an existing ByteBuffer and ref count.
     *
     * <p>This constructor must always be used with a pre-incremented {@code sharedRefCount}.
     */
    private SharedByteBuffer(@NonNull ByteBuffer sharedBuf, @NonNull AtomicInteger sharedRefCount,
            @NonNull Pair<Executor, Runnable> finalCloseAction, int shareId) {

        mSharedByteBuffer = sharedBuf;
        mSharedRefCount = sharedRefCount;
        mFinalCloseAction = finalCloseAction;
        mShareId = shareId;

        if (Logger.isDebugEnabled(TAG)) {
            int refCount = sharedRefCount.get();
            if (refCount < 1) {
                throw new AssertionError(String.format(Locale.US, "Cannot create new "
                        + "instance of SharedByteBuffer with invalid ref count %d. Ref count must "
                        + "be >= 1. [%s]", refCount, toString()));
            }
        }
    }

    /**
     * Creates a new instance of SharedByteBuffer which references the same underlying ByteBuffer.
     *
     * <p>This creates a new read-only instance of the same underlying {@link ByteBuffer} that
     * can be retrieved with {@link #get()}. While the two instances are independent, the new
     * instance's initial position, limit, and mark values will match those of this instance's
     * values at the time {@code share()} is called.
     */
    @NonNull
    SharedByteBuffer share() {
        int newRefCount;
        AtomicInteger sharedRefCountLocal;
        synchronized (mCloseLock) {
            checkNotClosed("share()");
            newRefCount = mSharedRefCount.incrementAndGet();
            sharedRefCountLocal = mSharedRefCount;
        }

        if (Logger.isDebugEnabled(TAG)) {
            if (newRefCount <= 1) {
                throw new AssertionError("Invalid ref count. share() should always produce "
                        + "a ref count of 2 or more.");
            }
            Logger.d(TAG, String.format(Locale.US, "Ref count incremented: %d [%s]",
                    newRefCount, toString()));
        }

        return new SharedByteBuffer(mSharedByteBuffer.asReadOnlyBuffer(), sharedRefCountLocal,
                mFinalCloseAction, mShareId);
    }

    /**
     * Closes this instance of SharedByteBuffer.
     *
     * <p>Once an instance is closed, {@link #get()} can no longer be used to retrieve the
     * buffer. If any reference to the buffer exists from a previous invocation of {@link #get()}
     * it should no longer be used.
     *
     * <p>Closing a SharedByteBuffer is idempotent. Only the first {@code close()} will have any
     * effect. Subsequent calls will be no-ops.
     *
     */
    @Override
    public void close() {
        closeInternal();
    }

    /**
     * Returns a read-only view into the {@link ByteBuffer} instance that is being shared.
     *
     *  <p>Once an instance is closed with {@link #close()}, this method can no longer be used to
     *  retrieve the buffer. Once closed, the object returned by this method should be assumed
     *  invalid and should no longer be used.
     *
     * @return the underlying shared {@link ByteBuffer}
     * @throws IllegalStateException if this SharedByteBuffer is closed.
     * @see #close()
     */
    @NonNull
    public ByteBuffer get() {
        synchronized (mCloseLock) {
            checkNotClosed("get()");
            return mSharedByteBuffer;
        }
    }

    @GuardedBy("mCloseLock")
    private void checkNotClosed(@NonNull String caller) {
        if (mClosed) {
            throw new IllegalStateException("Cannot call " + caller + " on a closed "
                    + "SharedByteBuffer.");
        }
    }

    @NonNull
    @Override
    public String toString() {
        return String.format(Locale.US, "SharedByteBuffer[buf: %s, shareId: 0x%x, instanceId:0x%x]",
                mSharedByteBuffer, mShareId, System.identityHashCode(this));
    }

    // Finalizer as a safety net. This will introduce a performance penalty on construction but it
    // is often critical that all SharedByteBuffer instances are closed, so we eat the penalty.
    @Override
    protected void finalize() throws Throwable {
        try {
            if (closeInternal()) {
                Logger.w(TAG, String.format(Locale.US,
                        "SharedByteBuffer closed by finalizer, but should have been closed "
                                + "manually with SharedByteBuffer.close() [%s]",
                        toString()));
            }
        } finally {
            super.finalize();
        }

    }

    // Closes the SharedByteBuffer and returns true if the buffer was not already closed. Returns
    // false if the SharedByteBuffer was already closed.
    private boolean closeInternal() {
        int newRefCount;
        synchronized (mCloseLock) {
            if (mClosed) {
                // close() is idempotent. Only initial close() should have any affect on ref count.
                return false;
            } else {
                mClosed = true;
            }
            newRefCount = mSharedRefCount.decrementAndGet();
        }

        if (Logger.isDebugEnabled(TAG)) {
            if (newRefCount < 0) {
                throw new AssertionError("Invalid ref count. close() should never produce a "
                        + "ref count below 0");
            }
            // Note there is no guarantee this log will be printed in order with other shared
            // instances log since there is no mutual exclusion around when logs are printed.
            // This may make it appear as if ref counts are being decremented out of order, but
            // that is not the case.
            Logger.d(TAG, String.format(Locale.US, "Ref count decremented: %d [%s]", newRefCount,
                    toString()));
        }

        if (newRefCount == 0) {
            if (Logger.isDebugEnabled(TAG)) {
                Logger.d(TAG, String.format(Locale.US, "Final reference released. Running final "
                        + "close action. [%s]", toString()));
            }

            try {
                Executor executor = Preconditions.checkNotNull(mFinalCloseAction.first);
                Runnable runnable = Preconditions.checkNotNull(mFinalCloseAction.second);
                executor.execute(runnable);
            } catch (RejectedExecutionException e) {
                Logger.e(TAG, String.format(Locale.US, "Unable to execute final close action. "
                                + "[%s]", toString()), e);
            }
        }

        return true;
    }
}