/**
* 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 java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
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.VolumeClosedException;
import com.persistit.exception.VolumeFullException;
/**
* Manage all details of file I/O for a temporary <code>Volume</code> backing
* file. A temporary volume is intended to store data that does not need to be
* durable. All trees in a temporary volume are lost when Persistit shuts down,
* whether abruptly or gracefully. Therefore pages writes are not logged,
* buffers are not flushed on shutdown and there is no volume header page to
* hold metadata.
*
* @author peter
*/
class VolumeStorageT2 extends VolumeStorage {
final static String TEMP_FILE_PREFIX = "persistit_tempvol_";
private final File _tempDirectory;
private long _maxPages;
private volatile String _path;
private volatile FileChannel _channel;
private volatile long _nextAvailablePage;
private volatile boolean _opened;
private volatile boolean _closed;
VolumeStorageT2(final Persistit persistit, final Volume volume, final File tempDirectory) {
super(persistit, volume);
_tempDirectory = tempDirectory;
}
/**
* Returns the path name by which this volume was opened.
*
* @return The path name
*/
@Override
String getPath() {
return _path;
}
/**
* Indicate whether this <code>Volume</code> prohibits updates.
*
* @return <code>true</code> if this Volume prohibits updates.
*/
@Override
boolean isReadOnly() {
return false;
}
/**
* Indicate whether this is a temporary volume
*
* @return <code>true</code> if this volume is temporary
*/
@Override
boolean isTemp() {
return true;
}
/**
* @return the channel used to read and write pages of this volume.
*/
@Override
synchronized FileChannel getChannel() throws PersistitIOException {
if (_channel == null) {
try {
final File file = File.createTempFile(TEMP_FILE_PREFIX, null, _tempDirectory);
_path = file.getPath();
_channel = new MediatedFileChannel(_path, "rw");
} catch (final IOException ioe) {
_persistit.getLogBase().tempVolumeCreateException.log(ioe, _path);
throw new PersistitIOException(ioe);
}
}
return _channel;
}
/**
* Create a new <code>Volume</code> backing file according to the
* {@link Volume}'s volume specification.
*
* @throws PersistitException
*/
@Override
void create() throws PersistitException {
final long maxSize = _persistit.getConfiguration().getTmpVolMaxSize();
_maxPages = maxSize / _volume.getStructure().getPageSize();
_path = "";
_channel = null;
truncate();
_opened = true;
}
/**
* Open an existing <code>Volume</code> backing file.
*
* @throws PersistitException
*/
@Override
void open() throws PersistitException {
throw new UnsupportedOperationException("Temporary volume can only be created.");
}
/**
* @return <code>true</code> if a backing file exists on the specified path.
* @throws PersistitException
*/
@Override
boolean exists() throws PersistitException {
return false;
}
/**
* 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 {
return false;
}
/**
* Force all file system buffers to disk.
*
* @throws PersistitIOException
*/
@Override
void force() throws PersistitIOException {
// No need to force a temporary Volume
}
/**
* 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 {
synchronized (this) {
if (_closed) {
return;
}
_closed = true;
}
PersistitException pe = null;
try {
closeChannel();
} catch (final Exception e) {
_persistit.getLogBase().exception.log(e);
// has priority over Exception thrown by
// releasing file lock.
pe = new PersistitException(e);
}
try {
if (_path != null) {
final File file = new File(_path);
file.delete();
}
} 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;
}
}
private void closeChannel() throws IOException {
final FileChannel channel = _channel;
_channel = null;
if (channel != null) {
channel.close();
}
}
@Override
void truncate() throws PersistitException {
if (!claim(true, 0)) {
throw new InUseException("Unable to acquire claim on " + this);
}
try {
truncateInternal();
} finally {
release();
}
}
protected void truncateInternal() throws PersistitException {
final VolumeStatistics stat = _volume.getStatistics();
final VolumeStructure struc = _volume.getStructure();
final long now = System.currentTimeMillis();
stat.setCreateTime(now);
stat.setOpenTime(now);
_nextAvailablePage = 1;
struc.init(0, 0);
}
@Override
boolean isOpened() {
return _opened;
}
@Override
boolean isClosed() {
return _closed;
}
@Override
long getExtentedPageCount() {
return _nextAvailablePage;
}
@Override
long getNextAvailablePage() {
return _nextAvailablePage;
}
@Override
void claimHeadBuffer() throws PersistitException {
}
@Override
void releaseHeadBuffer() {
}
@Override
void readPage(final Buffer buffer) throws PersistitIOException, InvalidPageAddressException, VolumeClosedException,
InUseException, PersistitInterruptedException {
// non-exclusive claim here intended to conflict with exclusive claim in
// close and truncate
if (!claim(false, 0)) {
throw new InUseException("Unable to acquire claim on " + this);
}
try {
final long page = buffer.getPageAddress();
if (page < 1 || page >= _nextAvailablePage) {
throw new InvalidPageAddressException("Page " + page + " out of bounds [0-" + _nextAvailablePage + "]");
}
try {
final ByteBuffer bb = buffer.getByteBuffer();
bb.position(0).limit(buffer.getBufferSize());
int read = 0;
while (read < buffer.getBufferSize()) {
final long position = (page - 1) * _volume.getStructure().getPageSize() + bb.position();
final int bytesRead = getChannel().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 PersistitIOException, InvalidPageAddressException,
ReadOnlyVolumeException, VolumeClosedException, InUseException, PersistitInterruptedException {
final int pageSize = _volume.getStructure().getPageSize();
final ByteBuffer bb = buffer.getByteBuffer();
bb.position(0).limit(pageSize);
writePage(bb, buffer.getPageAddress());
}
@Override
void writePage(final ByteBuffer bb, final long page) throws PersistitIOException, InvalidPageAddressException,
ReadOnlyVolumeException, VolumeClosedException, InUseException, PersistitInterruptedException {
// non-exclusive claim here intended to conflict with exclusive claim in
// close and truncate
final int pageSize = _volume.getStructure().getPageSize();
if (!claim(false, 0)) {
throw new InUseException("Unable to acquire claim on " + this);
}
try {
if (page < 0 || page >= _nextAvailablePage) {
throw new InvalidPageAddressException("Page " + page + " out of bounds [0-" + _nextAvailablePage + "]");
}
try {
getChannel().write(bb, (page - 1) * pageSize);
} 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);
}
} finally {
release();
}
}
@Override
synchronized long allocNewPage() throws PersistitException {
if (_nextAvailablePage >= _maxPages) {
throw new VolumeFullException(_volume.getName());
}
final long page = _nextAvailablePage++;
_volume.getStatistics().setNextAvailablePage(page);
return page;
}
@Override
void flush() throws PersistitException {
}
@Override
void flushMetaData() throws PersistitException {
}
@Override
void extend(final long pageAddr) throws PersistitException {
}
@Override
boolean updateMetaData(final byte[] bytes) {
// Temporary volume has no head buffer
return false;
}
@Override
public String toString() {
return _volume.toString();
}
}