Package com.persistit

Source Code of com.persistit.VolumeStorageV2

/**
* Copyright 2011-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 static com.persistit.VolumeHeader.changeCreateTime;
import static com.persistit.VolumeHeader.changeDirectoryRoot;
import static com.persistit.VolumeHeader.changeExtendedPageCount;
import static com.persistit.VolumeHeader.changeExtensionPages;
import static com.persistit.VolumeHeader.changeFetchCounter;
import static com.persistit.VolumeHeader.changeGarbageRoot;
import static com.persistit.VolumeHeader.changeGlobalTimestamp;
import static com.persistit.VolumeHeader.changeInitialPages;
import static com.persistit.VolumeHeader.changeLastExtensionTime;
import static com.persistit.VolumeHeader.changeLastReadTime;
import static com.persistit.VolumeHeader.changeLastWriteTime;
import static com.persistit.VolumeHeader.changeMaximumPages;
import static com.persistit.VolumeHeader.changeNextAvailablePage;
import static com.persistit.VolumeHeader.changeReadCounter;
import static com.persistit.VolumeHeader.changeRemoveCounter;
import static com.persistit.VolumeHeader.changeStoreCounter;
import static com.persistit.VolumeHeader.changeTraverseCounter;
import static com.persistit.VolumeHeader.changeWriteCounter;
import static com.persistit.VolumeHeader.getCreateTime;
import static com.persistit.VolumeHeader.getDirectoryRoot;
import static com.persistit.VolumeHeader.getExtensionPages;
import static com.persistit.VolumeHeader.getGarbageRoot;
import static com.persistit.VolumeHeader.getGlobalTimestamp;
import static com.persistit.VolumeHeader.getId;
import static com.persistit.VolumeHeader.getInitialPages;
import static com.persistit.VolumeHeader.getLastExtensionTime;
import static com.persistit.VolumeHeader.getLastReadTime;
import static com.persistit.VolumeHeader.getLastWriteTime;
import static com.persistit.VolumeHeader.getMaximumPages;
import static com.persistit.VolumeHeader.putId;
import static com.persistit.VolumeHeader.putPageSize;
import static com.persistit.VolumeHeader.putSignature;
import static com.persistit.VolumeHeader.putVersion;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;

import com.persistit.AlertMonitor.AlertLevel;
import com.persistit.AlertMonitor.Event;
import com.persistit.exception.InUseException;
import com.persistit.exception.InvalidPageAddressException;
import com.persistit.exception.PersistitException;
import com.persistit.exception.PersistitIOException;
import com.persistit.exception.PersistitInterruptedException;
import com.persistit.exception.ReadOnlyVolumeException;
import com.persistit.exception.VolumeAlreadyExistsException;
import com.persistit.exception.VolumeClosedException;
import com.persistit.exception.VolumeFullException;
import com.persistit.exception.VolumeNotFoundException;

/**
* Manage all details of file I/O for a <code>Volume</code> backing file for
* Version 2.xx storage formats.
*
* @author peter
*/
class VolumeStorageV2 extends VolumeStorage {

    private volatile FileChannel _channel;
    private volatile FileLock _fileLock;

    private volatile Buffer _headBuffer;

    private volatile long _nextAvailablePage;
    private volatile long _extendedPageCount;
    private volatile boolean _opened;
    private volatile boolean _closed;

    /**
     * Generate a random positive (non-zero) long value to be used as a
     * validation of a Volume's identity.
     *
     * @return
     */

    VolumeStorageV2(final Persistit persistit, final Volume volume) {
        super(persistit, volume);
    }

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

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

    /**
     * Indicate whether this is a temporary volume
     *
     * @return <code>true</code> if this volume is temporary
     */
    @Override
    boolean isTemp() {
        return false;
    }

    /**
     * @return the channel used to read and write pages of this volume.
     */
    @Override
    FileChannel getChannel() {
        return _channel;
    }

    /**
     * Create a new <code>Volume</code> backing file according to the
     * {@link Volume}'s volume specification.
     *
     * @throws PersistitException
     */
    @Override
    synchronized void create() throws PersistitException {
        if (_opened) {
            throw new IllegalStateException("Volume " + this + " cannot be reopened");
        }
        if (exists()) {
            throw new VolumeAlreadyExistsException(getPath());
        }
        try {
            _channel = new MediatedFileChannel(getPath(), "rw");
            lockChannel();
            truncate();
            _opened = true;
        } catch (final IOException ioe) {
            throw new PersistitIOException(ioe);
        } finally {
            if (!_opened) {
                try {
                    closeChannel();
                } catch (final IOException e) {
                    // Not much to do - we're going to try to delete
                    // the file anyway.
                }
                if (getPath() != null) {
                    delete();
                }
            }
        }
    }

    /**
     * Open an existing <code>Volume</code> backing file.
     *
     * @throws PersistitException
     */
    @Override
    synchronized void open() throws PersistitException {
        if (_opened) {
            throw new IllegalStateException("Volume " + this + " cannot be reopened");
        }
        if (!exists()) {
            throw new VolumeNotFoundException(getPath());
        }

        final VolumeSpecification spec = _volume.getSpecification();
        final VolumeStatistics stat = _volume.getStatistics();
        final VolumeStructure struc = _volume.getStructure();

        try {
            _channel = new MediatedFileChannel(getPath(), isReadOnly() ? "r" : "rw");
            lockChannel();
            _nextAvailablePage = 1; // correct value installed below
            _volume.setId(spec.getId());

            _headBuffer = _volume.getStructure().getPool().get(_volume, 0, true, true);
            _headBuffer.setFixed();
            final byte[] bytes = _headBuffer.getBytes();

            _nextAvailablePage = VolumeHeader.getNextAvailablePage(bytes);
            _extendedPageCount = _channel.size() / _volume.getStructure().getPageSize();

            final long id = getId(bytes);
            _volume.verifyId(id);
            _volume.setId(id);

            final long now = System.currentTimeMillis();

            spec.setInitialPages(getInitialPages(bytes));
            spec.setMaximumPages(getMaximumPages(bytes));
            spec.setExtensionPages(getExtensionPages(bytes));

            stat.setCreateTime(getCreateTime(bytes));
            stat.setNextAvailablePage(VolumeHeader.getNextAvailablePage(bytes));
            stat.setLastExtensionTime(getLastExtensionTime(bytes));
            stat.setLastReadTime(getLastReadTime(bytes));
            stat.setLastWriteTime(getLastWriteTime(bytes));
            stat.setOpenTime(now);

            final long directoryRootPage = getDirectoryRoot(bytes);
            final long garbageRootPage = getGarbageRoot(bytes);
            struc.init(directoryRootPage, garbageRootPage);

            final long globalTimestamp = getGlobalTimestamp(bytes);
            stat.setLastGlobalTimestamp(globalTimestamp);

            flushMetaData();
            _opened = true;

        } catch (final IOException ioe) {
            throw new PersistitIOException(ioe);
        } finally {
            if (_headBuffer != null) {
                if (!_opened) {
                    _headBuffer.clearFixed();
                    _headBuffer.clearValid();
                    releaseHeadBuffer();
                    _headBuffer = null;
                } else {
                    releaseHeadBuffer();
                }
            }
        }
    }

    /**
     * @return <code>true</code> if a backing file exists on the specified path.
     * @throws PersistitException
     */
    @Override
    boolean exists() throws PersistitException {
        final File file = new File(getPath());
        return file.exists() && file.isFile();
    }

    /**
     * Delete the backing file for this Volume if it exists.
     *
     * @return <code>true</code> if there was a file and it was successfully
     *         deleted
     * @throws PersistitException
     */
    @Override
    boolean delete() throws PersistitException {
        final File file = new File(getPath());
        return file.exists() && file.isFile() && file.delete();
    }

    /**
     * Force all file system buffers to disk.
     *
     * @throws PersistitIOException
     */
    @Override
    void force() throws PersistitIOException {
        try {
            _channel.force(true);
        } catch (final IOException ioe) {
            throw new PersistitIOException(ioe);
        }
    }

    /**
     * Close the file resources held by this <code>Volume</code>. After this
     * method is called no further file I/O is possible.
     *
     * @throws PersistitException
     */
    @Override
    void close() throws PersistitException {
        /*
         * Exclusive claim here intended to conflict with readPage and writePage
         */
        if (!claim(true)) {
            throw new InUseException("Unable to acquire claim on " + this);
        }
        try {
            if (_closed) {
                return;
            }
            _closed = true;
            _headBuffer = null;
            PersistitException pe = null;
            try {
                final FileLock lock = _fileLock;
                _fileLock = null;
                if (lock != null) {
                    lock.release();
                }
            } catch (final Exception e) {
                _persistit.getLogBase().exception.log(e);
                pe = new PersistitException(e);
            }
            try {
                closeChannel();
            } catch (final Exception e) {
                _persistit.getLogBase().exception.log(e);
                // has priority over Exception thrown by
                // releasing file lock.
                pe = new PersistitException(e);
            }
            if (pe != null) {
                throw pe;
            }
        } finally {
            release();
        }
    }

    private void closeChannel() throws IOException {
        final FileChannel channel = _channel;
        _channel = null;
        if (channel != null) {
            channel.close();
        }
    }

    /**
     * Truncate an existing volume. This method deletes all existing data in the
     * volume. It is equivalent to deleting the volume and creating a new one
     * using the same VolumeSpecifications; however the file is never actually
     * deleted by this operation.
     * <p />
     * This method assigns a new unique id value to the volume so that journal
     * records pertaining to its state prior to being truncated are not confused
     * with the new empty state. Caution: information in the volume is
     * irrecoverably destroyed by this method.
     */

    @Override
    void truncate() throws PersistitException {
        _volume.setId(0);
        _volume.setId(generateId());

        final VolumeSpecification spec = _volume.getSpecification();
        final VolumeStatistics stat = _volume.getStatistics();
        final VolumeStructure struc = _volume.getStructure();

        resize(1);
        resize(spec.getInitialPages());

        final long now = System.currentTimeMillis();
        stat.setCreateTime(now);
        stat.setOpenTime(now);
        _extendedPageCount = spec.getInitialPages();
        _nextAvailablePage = 1;

        _headBuffer = _volume.getStructure().getPool().get(_volume, 0, true, false);
        boolean truncated = false;
        try {
            _headBuffer.init(Buffer.PAGE_TYPE_HEAD);
            _headBuffer.setFixed();

            initMetaData(_headBuffer.getBytes());
            //
            // Lay down the initial version of the header page so that the
            // volume file will be valid on restart
            //
            final ByteBuffer bb = _headBuffer.getByteBuffer();
            bb.limit(_headBuffer.getBufferSize()).position(0);
            writePage(bb, _headBuffer.getPageAddress());
            //
            // Now create directory root page, etc.
            //
            struc.init(0, 0);
            flushMetaData();
            truncated = true;
        } finally {
            if (!truncated) {
                _headBuffer.clearValid();
                _headBuffer.clearFixed();
                releaseHeadBuffer();
                _headBuffer = null;
            } else {
                releaseHeadBuffer();
            }
        }
    }

    @Override
    boolean isOpened() {
        return _opened;
    }

    @Override
    boolean isClosed() {
        return _closed;
    }

    @Override
    long getExtentedPageCount() {
        return _extendedPageCount;
    }

    @Override
    long getNextAvailablePage() {
        return _nextAvailablePage;
    }

    @Override
    void claimHeadBuffer() throws PersistitException {
        if (!_headBuffer.claim(true)) {
            throw new InUseException("Unable to acquire claim on " + _headBuffer);
        }
    }

    @Override
    void releaseHeadBuffer() {
        _headBuffer.release();
    }

    @Override
    void readPage(final Buffer buffer) throws PersistitIOException, InvalidPageAddressException, VolumeClosedException,
            PersistitInterruptedException, InUseException {
        // non-exclusive claim here intended to conflict with exclusive claim in
        // close and truncate
        if (!claim(false)) {
            throw new InUseException("Unable to acquire claim on " + this);
        }
        try {
            final long page = buffer.getPageAddress();
            if (page < 0 || page >= _nextAvailablePage) {
                throw new InvalidPageAddressException("Page " + page + " out of bounds [0-" + _nextAvailablePage + "]");
            }
            if (_persistit.getJournalManager().readPageFromJournal(buffer)) {
                return;
            }

            try {
                final ByteBuffer bb = buffer.getByteBuffer();
                bb.position(0).limit(buffer.getBufferSize());
                int read = 0;
                while (read < buffer.getBufferSize()) {
                    final long position = page * _volume.getStructure().getPageSize() + bb.position();
                    final int bytesRead = _channel.read(bb, position);
                    if (bytesRead <= 0) {
                        throw new PersistitIOException("Unable to read bytes at position " + position + " in " + this);
                    }
                    read += bytesRead;
                }
                _persistit.getIOMeter().chargeReadPageFromVolume(this._volume, buffer.getPageAddress(),
                        buffer.getBufferSize(), buffer.getIndex());
                _volume.getStatistics().bumpReadCounter();

            } catch (final IOException ioe) {
                _persistit.getAlertMonitor().post(
                        new Event(AlertLevel.ERROR, _persistit.getLogBase().readException, ioe, _volume, page,
                                buffer.getIndex()), AlertMonitor.READ_PAGE_CATEGORY);
                throw new PersistitIOException(ioe);
            }
        } finally {
            release();
        }
    }

    @Override
    void writePage(final Buffer buffer) throws PersistitException {
        /*
         * Non-exclusive claim here intended to conflict with exclusive claim in
         * close and truncate
         */
        if (!claim(false)) {
            throw new InUseException("Unable to acquire claim on " + this);
        }
        try {
            _persistit.getJournalManager().writePageToJournal(buffer);
        } finally {
            release();
        }

    }

    @Override
    void writePage(final ByteBuffer bb, final long page) throws PersistitIOException, InvalidPageAddressException,
            ReadOnlyVolumeException, VolumeClosedException {
        if (page < 0 || page >= _nextAvailablePage) {
            throw new InvalidPageAddressException("Page " + page + " out of bounds [0-" + _nextAvailablePage + "]");
        }

        if (isReadOnly()) {
            throw new ReadOnlyVolumeException(getPath());
        }

        try {
            _channel.write(bb, page * _volume.getStructure().getPageSize());
        } catch (final IOException ioe) {
            _persistit.getAlertMonitor().post(
                    new Event(AlertLevel.ERROR, _persistit.getLogBase().writeException, ioe, _volume, page),
                    AlertMonitor.WRITE_PAGE_CATEGORY);
            throw new PersistitIOException(ioe);
        }
    }

    @Override
    long allocNewPage() throws PersistitException {
        long page = -1;
        claimHeadBuffer();
        try {
            for (;;) {
                if (_nextAvailablePage < _extendedPageCount) {
                    page = _nextAvailablePage++;
                    _volume.getStatistics().setNextAvailablePage(page);
                    break;
                }
                extend();
            }
            flushMetaData();
        } finally {
            releaseHeadBuffer();
        }
        return page;
    }

    @Override
    void flush() throws PersistitException {
        claimHeadBuffer();
        try {
            flushMetaData();
        } finally {
            releaseHeadBuffer();
        }
    }

    @Override
    void flushMetaData() throws PersistitException {
        if (!isReadOnly()) {
            assert _headBuffer.isOwnedAsWriterByMe();
            final long timestamp = _persistit.getTimestampAllocator().updateTimestamp();
            _volume.getStatistics().setLastGlobalTimestamp(timestamp);
            _headBuffer.writePageOnCheckpoint(timestamp);
            if (updateMetaData(_headBuffer.getBytes())) {
                _headBuffer.setDirtyAtTimestamp(timestamp);
            }
        }
    }

    @Override
    void extend(final long pageAddr) throws PersistitException {
        if (pageAddr >= _extendedPageCount) {
            extend();
            flush();
        }
    }

    private void lockChannel() throws InUseException, IOException {
        try {
            _fileLock = _channel.tryLock(0, Long.MAX_VALUE, isReadOnly());
        } catch (final OverlappingFileLockException e) {
            // Note: OverlappingFileLockException is a RuntimeException
            throw new InUseException("Volume file " + getPath() + " is locked by another thread in this JVM");
        }
        if (_fileLock == null) {
            throw new InUseException("Volume file " + getPath() + " is locked by another process");
        }
    }

    private void initMetaData(final byte[] bytes) {
        final VolumeStructure struc = _volume.getStructure();
        putSignature(bytes);
        putVersion(bytes);
        putPageSize(bytes, struc.getPageSize());
        putId(bytes, _volume.getId());
        changeNextAvailablePage(bytes, _nextAvailablePage);
        changeExtendedPageCount(bytes, _extendedPageCount);
        changeCreateTime(bytes, _volume.getStatistics().getCreateTime());
        changeInitialPages(bytes, _volume.getSpecification().getInitialPages());
        changeMaximumPages(bytes, _volume.getSpecification().getMaximumPages());
        changeExtensionPages(bytes, _volume.getSpecification().getExtensionPages());
    }

    @Override
    boolean updateMetaData(final byte[] bytes) {
        final VolumeStatistics stat = _volume.getStatistics();
        final VolumeStructure struc = _volume.getStructure();

        boolean changed = false;
        changed |= changeNextAvailablePage(bytes, _nextAvailablePage);
        changed |= changeExtendedPageCount(bytes, _extendedPageCount);
        changed |= changeDirectoryRoot(bytes, struc.getDirectoryRoot());
        changed |= changeGarbageRoot(bytes, struc.getGarbageRoot());
        changed |= changeFetchCounter(bytes, stat.getFetchCounter());
        changed |= changeTraverseCounter(bytes, stat.getTraverseCounter());
        changed |= changeStoreCounter(bytes, stat.getStoreCounter());
        changed |= changeRemoveCounter(bytes, stat.getRemoveCounter());
        changed |= changeReadCounter(bytes, stat.getReadCounter());
        changed |= changeLastExtensionTime(bytes, stat.getLastExtensionTime());
        changed |= changeLastReadTime(bytes, stat.getLastReadTime());

        // Ugly, but the act of closing the system increments this
        // counter, leading to an extra write. So basically we
        // ignore the final write by not setting the changed flag.
        changeWriteCounter(bytes, stat.getWriteCounter());
        changeLastWriteTime(bytes, stat.getLastWriteTime());
        changeGlobalTimestamp(bytes, stat.getLastGlobalTimestamp());

        return changed;
    }

    private void extend() throws PersistitException {
        final long maximumPages = _volume.getSpecification().getMaximumPages();
        final long extensionPages = _volume.getSpecification().getExtensionPages();
        if (_extendedPageCount >= maximumPages || extensionPages <= 0) {
            throw new VolumeFullException(this + " is full: " + _extendedPageCount + " pages");
        }
        // Do not extend past maximum pages
        final long pageCount = Math.min(_extendedPageCount + extensionPages, maximumPages);
        resize(pageCount);
    }

    private void resize(final long pageCount) throws PersistitException {
        final long newSize = pageCount * _volume.getStructure().getPageSize();
        long currentSize = -1;

        try {
            currentSize = _channel.size();
            if (currentSize > newSize) {
                _persistit.getLogBase().extendLonger.log(this, currentSize, newSize);
            }
            if (currentSize < newSize) {
                final ByteBuffer bb = ByteBuffer.allocate(1);
                bb.position(0).limit(1);
                _channel.write(bb, newSize - 1);
                _channel.force(true);
                _persistit.getLogBase().extendNormal.log(this, currentSize, newSize);
            }

            _volume.getStatistics().setLastExtensionTime(System.currentTimeMillis());
            _extendedPageCount = pageCount;
        } catch (final IOException ioe) {
            _persistit.getAlertMonitor().post(
                    new Event(AlertLevel.ERROR, _persistit.getLogBase().extendException, ioe, _volume.getName(),
                            currentSize, newSize), AlertMonitor.EXTEND_VOLUME_CATEGORY);
            throw new PersistitIOException(ioe);
        }
    }

    @Override
    public String toString() {
        return _volume.toString();
    }

}
TOP

Related Classes of com.persistit.VolumeStorageV2

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.