Package com.persistit

Source Code of com.persistit.Volume

/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* 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 com.persistit;

import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import com.persistit.exception.BufferSizeUnavailableException;
import com.persistit.exception.InUseException;
import com.persistit.exception.PersistitException;
import com.persistit.exception.ReadOnlyVolumeException;
import com.persistit.exception.TruncateVolumeException;
import com.persistit.exception.UnderSpecifiedVolumeException;
import com.persistit.exception.VolumeAlreadyExistsException;
import com.persistit.exception.VolumeClosedException;
import com.persistit.exception.VolumeNotFoundException;
import com.persistit.exception.WrongVolumeException;
import com.persistit.util.Util;

/**
* <p>
* Represent the identity and optionally the service classes that manage a
* volume. A newly constructed Volume is "hollow" in the sense that it has no
* ability to perform I/O on a backing file or allocate pages. In this state it
* represents the identity, but not the content, of the volume.
* </p>
* <p>
* To enable the <code>Volume</code> to act on data, you must supply a
* {@link VolumeSpecification} object, either through the constructor or with
* {@link #setSpecification}, and then call the {@link #open(Persistit)} method.
* </p>
*
* @author peter
*/
public class Volume {

    final static int LOCK_VOLUME_HANDLE = Integer.MAX_VALUE;
    final static String LOCK_VOLUME_NAME = "_lock";

    private final String _name;
    private long _id;
    private final AtomicBoolean _closing = new AtomicBoolean();
    private final AtomicBoolean _closed = new AtomicBoolean();
    private final AtomicInteger _handle = new AtomicInteger();
    private final AtomicReference<Object> _appCache = new AtomicReference<Object>();

    private VolumeSpecification _specification;
    private volatile VolumeStorage _storage;
    private volatile VolumeStatistics _statistics;
    private volatile VolumeStructure _structure;

    /*
     * These two constants are used to identify temporary volumes that may have
     * been identified in existing journal files due to bug 1018526. They are
     * used in code to detect and remove these records. Once all existing
     * Persistit volumes sites have been cleaned up, we can remove these
     * constants and the logic that depends on them.
     */
    private final static long TEMP_VOLUME_ID_FOR_FIXUP_DETECTION = 12345;
    private final static String TEMP_VOLUME_NAME_SUFFIX_FOR_FIXUP_DETECTION = "_temporary_volume";

    public static boolean isValidPageSize(final int pageSize) {
        for (int b = 1024; b <= 16384; b *= 2) {
            if (b == pageSize) {
                return true;
            }
        }
        return false;
    }

    static Volume createTemporaryVolume(final Persistit persistit, final int pageSize, final File tempDirectory)
            throws PersistitException {
        final Volume volume = new Volume(
                Thread.currentThread().getName() + TEMP_VOLUME_NAME_SUFFIX_FOR_FIXUP_DETECTION,
                TEMP_VOLUME_ID_FOR_FIXUP_DETECTION);
        volume.openInternal(persistit, pageSize);

        volume._storage = new VolumeStorageT2(persistit, volume, tempDirectory);
        volume._storage.create();

        return volume;
    }

    static Volume createLockVolume(final Persistit persistit, final int pageSize, final File tempDirectory)
            throws PersistitException {
        final Volume volume = new Volume(LOCK_VOLUME_NAME, 0);
        volume.setHandle(Volume.LOCK_VOLUME_HANDLE);
        volume.openInternal(persistit, pageSize);
        volume._storage = new VolumeStorageL2(persistit, volume, tempDirectory);
        volume._storage.create();
        return volume;
    }

    /**
     * Construct a hollow Volume - used by JournalManager
     *
     * @param name
     * @param id
     */
    Volume(final String name, final long id) {
        _name = name;
        _id = id;
        _specification = new VolumeSpecification(name);
    }

    /**
     * Construct a hollow Volume with its specification.
     *
     * @param specification
     */
    Volume(final VolumeSpecification specification) {
        this(specification.getName(), specification.getId());
        _specification = specification;
    }

    private void checkNull(final Object o, final String delegate) {
        if (o == null) {
            throw new IllegalStateException(this + " has no " + delegate);
        }
    }

    private void checkClosing() throws VolumeClosedException {
        if (_closing.get()) {
            throw new VolumeClosedException();
        }
    }

    VolumeSpecification getSpecification() {
        final VolumeSpecification s = _specification;
        checkNull(s, "VolumeSpecification");
        return s;
    }

    VolumeStorage getStorage() {
        final VolumeStorage s = _storage;
        checkNull(s, "VolumeStorage");
        return s;
    }

    VolumeStatistics getStatistics() {
        final VolumeStatistics s = _statistics;
        checkNull(s, "VolumeStatistics");
        return s;
    }

    VolumeStructure getStructure() {
        return _structure;
    }

    void setSpecification(final VolumeSpecification specification) {
        if (_specification != null) {
            throw new IllegalStateException("Volume " + this + " already has a VolumeSpecification");
        }
        if (specification.getName().equals(_name) && (specification.getId() == _id || specification.getId() == 0)) {
            _specification = specification;
        } else {
            throw new IllegalStateException("Volume " + this + " is incompatible with " + specification);
        }
    }

    void overwriteSpecification(final VolumeSpecification specification) {
        _specification = null;
        setSpecification(specification);
    }

    void closing() {
        _closing.set(true);
    }

    /**
     * Release all resources for this <code>Volume</code> and invalidate all its
     * buffers in the {@link BufferPool}. Exchanges based on this
     * <code>Volume</code> may no longer be used after this call. Waits up to
     * {@value com.persistit.SharedResource#DEFAULT_MAX_WAIT_TIME} milliseconds
     * for other threads to relinquish access to the volume.
     *
     * @throws PersistitException
     */
    public void close() throws PersistitException {
        close(SharedResource.DEFAULT_MAX_WAIT_TIME);
    }

    /**
     * Release all resources for this <code>Volume</code> and invalidate all its
     * buffers in the {@link BufferPool}. Exchanges based on this
     * <code>Volume</code> may no longer be used after this call.
     *
     * @param timeout
     *            Maximum time in milliseconds to wait for other threads to
     *            relinquish access to the volume.
     * @throws PersistitException
     */
    public void close(final long timeout) throws PersistitException {
        closing();
        final long expiration = System.currentTimeMillis() + timeout;
        for (;;) {
            //
            // Prevents read/write operations from starting while the
            // volume is being closed.
            //
            final VolumeStorage storage = getStorage();
            if (!storage.claim(true, timeout)) {
                throw new InUseException("Unable to acquire claim on " + this);
            }
            if (_closed.get()) {
                break;
            }
            try {
                //
                // BufferPool#invalidate may fail and return false if other
                // threads hold claims on pages of this volume. In that case we
                // need to back off all locks and retry
                //
                if (getStructure().getPool().invalidate(this)) {
                    getStructure().close();
                    getStorage().close();
                    getStatistics().reset();
                    _closed.set(true);
                    break;
                }
            } finally {
                storage.release();
            }
            if (System.currentTimeMillis() >= expiration) {
                throw new InUseException("Unable invalidate all pages on " + this);
            }
            Util.sleep(Persistit.SHORT_DELAY);
        }
    }

    /**
     * Remove all data from this volume. This is equivalent to deleting and then
     * recreating the <code>Volume</code> except that it does not actually
     * delete and close the file. Instead, this method truncates the file,
     * rewrites the head page, and invalidates all buffers belonging to this
     * <code>Volume</code> in the {@link BufferPool}.
     *
     * @throws PersistitException
     */
    public void truncate() throws PersistitException {
        if (isReadOnly()) {
            throw new ReadOnlyVolumeException();
        }
        if (!isTemporary() && !getSpecification().isCreate() && !getSpecification().isCreateOnly()) {
            throw new TruncateVolumeException();
        }
        for (;;) {
            //
            // Prevents read/write operations from starting while the
            // volume is being closed.
            //
            if (getStorage().claim(true, 0)) {
                try {
                    //
                    // BufferPool#invalidate may fail and return false if other
                    // threads hold claims on pages of this volume. In that case
                    // we need to back off all locks and retry
                    //
                    if (getStructure().getPool().invalidate(this)) {
                        getStructure().truncate();
                        getStorage().truncate();
                        getStatistics().reset();
                        break;
                    }
                } finally {
                    getStorage().release();
                }
            }
            Util.sleep(Persistit.SHORT_DELAY);
        }
    }

    /**
     * Delete the file backing this <code>Volume</code>.
     *
     * @return <code>true</code> if the file existed and was successfully
     *         deleted.
     * @throws PersistitException
     */
    public boolean delete() throws PersistitException {
        if (!isClosed()) {
            throw new IllegalStateException("Volume must be closed before deletion");
        }
        return getStorage().delete();
    }

    /**
     * Returns the path name by which this volume was opened.
     *
     * @return The path name
     */
    public String getPath() {
        return getStorage().getPath();
    }

    /**
     * Returns the absolute file by which this volume was opened.
     *
     * @return The file
     */
    public File getAbsoluteFile() {
        return getSpecification().getAbsoluteFile();
    }

    /**
     * @return page address of the first unused page in this <code>Volume</code>
     */
    public long getNextAvailablePage() {
        return getStorage().getNextAvailablePage();
    }

    /**
     * @return number of pages allocated to this <code>Volume</code>. Note that
     *         this method reflects the current length of the volume file, but
     *         on sparse file systems the disk space needed to store new pages
     *         may not yet have been allocated.
     */
    public long getExtendedPageCount() {
        return getStorage().getExtentedPageCount();
    }

    BufferPool getPool() {
        return getStructure().getPool();
    }

    Tree getDirectoryTree() {
        return getStructure().getDirectoryTree();
    }

    boolean isTemporary() {
        if (_storage == null) {
            /*
             * TODO - Temporary code to detect temporary volumes on existing
             * systems to resolve side-effects of bug 1018526.
             */
            return _id == TEMP_VOLUME_ID_FOR_FIXUP_DETECTION
                    && _name.endsWith(TEMP_VOLUME_NAME_SUFFIX_FOR_FIXUP_DETECTION);
        }
        return getStorage().isTemp();
    }

    boolean isLockVolume() {
        return getHandle() == LOCK_VOLUME_HANDLE;
    }

    /**
     * @return The size in bytes of one page in this <code>Volume</code>.
     */
    public int getPageSize() {
        return getStructure().getPageSize();
    }

    /**
     * Looks up by name and returns a <code>NewTree</code> within this
     * <code>Volume</code>. If no such tree exists, this method either creates a
     * new tree or returns null depending on whether the
     * <code>createIfNecessary</code> parameter is <code>true</code>.
     *
     * @param name
     *            The tree name
     *
     * @param createIfNecessary
     *            Determines whether this method will create a new tree if there
     *            is no tree having the specified name.
     *
     * @return The <code>NewTree</code>, or <code>null</code> if
     *         <code>createIfNecessary</code> is false and there is no such tree
     *         in this <code>Volume</code>.
     *
     * @throws PersistitException
     */
    public Tree getTree(final String name, final boolean createIfNecessary) throws PersistitException {
        checkClosing();
        return getStructure().getTree(name, createIfNecessary);
    }

    /**
     * Returns an array of all currently defined <code>Tree</code> names.
     *
     * @return The array
     *
     * @throws PersistitException
     */
    public String[] getTreeNames() throws PersistitException {
        checkClosing();
        return getStructure().getTreeNames();
    }

    /**
     * Return a TreeInfo structure for a tree by the specified name. If there is
     * no such tree, then return <code>null</code>.
     *
     * @param tree
     *            name
     * @return an information structure for the Management interface.
     */
    Management.TreeInfo getTreeInfo(final String name) {
        try {
            final Tree tree = getTree(name, false);
            if (tree != null) {
                return new Management.TreeInfo(tree);
            } else {
                return null;
            }
        } catch (final PersistitException pe) {
            return null;
        }
    }

    /**
     * Indicate whether this <code>Volume</code> has been opened or created,
     * i.e., whether a backing volume file has been created or opened.
     *
     * @return <code>true</code> if there is a backing volume file.
     */
    public boolean isOpened() {
        return _storage != null && _storage.isOpened();
    }

    /**
     * Indicate whether this <code>Volume</code> has been closed.
     *
     * @return <code>true</code> if this Volume is closed.
     */
    public boolean isClosed() {
        return _closing.get();
    }

    /**
     * Indicate whether this <code>Volume</code> prohibits updates.
     *
     * @return <code>true</code> if this Volume prohibits updates.
     */
    public boolean isReadOnly() {
        return getStorage().isReadOnly();
    }

    /**
     * Open an existing Volume file or create a new one, depending on the
     * settings of the {@link VolumeSpecification}.
     *
     * @throws PersistitException
     */
    void open(final Persistit persistit) throws PersistitException {
        checkClosing();
        if (_specification == null) {
            throw new IllegalStateException("Missing VolumeSpecification");
        }
        if (_storage != null) {
            throw new IllegalStateException("This volume has already been opened");
        }
        if (_specification.getPageSize() <= 0) {
            throw new UnderSpecifiedVolumeException(getName());
        }
        if (persistit.getBufferPool(_specification.getPageSize()) == null) {
            throw new BufferSizeUnavailableException(getName());
        }
        final boolean exists = VolumeHeader.verifyVolumeHeader(_specification, persistit.getCurrentTimestamp());

        _structure = new VolumeStructure(persistit, this, _specification.getPageSize());
        _storage = new VolumeStorageV2(persistit, this);
        _statistics = new VolumeStatistics();

        boolean opened = false;
        try {
            if (exists) {
                if (_specification.isCreateOnly()) {
                    throw new VolumeAlreadyExistsException(_specification.getPath());
                }
                _storage.open();
                opened = true;
            } else {
                if (!_specification.isCreate()) {
                    throw new VolumeNotFoundException(_specification.getPath());
                }
                _storage.create();
                opened = true;
            }
        } finally {
            if (!opened) {
                _structure = null;
                _storage = null;
                _statistics = null;
            }
        }
        persistit.addVolume(this);
    }

    private void openInternal(final Persistit persistit, final int pageSize) throws PersistitException {
        checkClosing();
        if (_storage != null) {
            throw new IllegalStateException("This volume has already been opened");
        }
        if (persistit.getBufferPool(pageSize) == null) {
            throw new BufferSizeUnavailableException("There is no buffer pool for pages of size " + pageSize);
        }
        _structure = new VolumeStructure(persistit, this, pageSize);
        _statistics = new VolumeStatistics();
    }

    public String getName() {
        return _name;
    }

    public long getId() {
        return _id;
    }

    void setId(final long id) {
        if (id != 0 && _id != 0 && _id != id) {
            throw new IllegalStateException("Volume " + this + " already has id=" + _id);
        }
        _id = id;
    }

    void verifyId(final long id) throws WrongVolumeException {
        if (id != 0 && _id != 0 && id != _id) {
            throw new WrongVolumeException(this + "id " + _id + " does not match expected id " + id);
        }
    }

    /**
     * Store an Object with this Volume for the convenience of an application.
     *
     * @param appCache
     *            the object to be cached for application convenience.
     */
    public void setAppCache(final Object appCache) {
        _appCache.set(appCache);
    }

    /**
     * @return the object cached for application convenience
     */
    public Object getAppCache() {
        return _appCache.get();
    }

    /**
     * @return The handle value used to identify this Tree in the journal
     */
    public int getHandle() {
        return _handle.get();
    }

    /**
     * Set the handle used to identify this Tree in the journal. May be invoked
     * only once.
     *
     * @param handle
     * @return the handle
     * @throws IllegalStateException
     *             if the handle has already been set
     */
    int setHandle(final int handle) {
        if (!_handle.compareAndSet(0, handle)) {
            throw new IllegalStateException("Volume handle already set");
        }
        return handle;
    }

    /**
     * Resets the handle to zero. Intended for use only by tests.
     */
    void resetHandle() {
        _handle.set(0);
    }

    @Override
    public String toString() {
        final VolumeSpecification specification = _specification;
        if (specification != null) {
            return specification.summary();
        } else {
            return _name;
        }
    }

    @Override
    public int hashCode() {
        return _name.hashCode();
    }

    @Override
    public boolean equals(final Object o) {
        if (o instanceof Volume) {
            final Volume volume = (Volume) o;
            return volume.getName().equals(getName()) && volume.getId() == getId();
        }
        return false;
    }
}
TOP

Related Classes of com.persistit.Volume

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.