/* * * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) * * * * 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. * * * * For more information: http://www.orientechnologies.com * */ package com.orientechnologies.orient.core.storage.impl.local.paginated; import com.orientechnologies.common.log.OLogManager; import com.orientechnologies.orient.core.config.OGlobalConfiguration; import com.orientechnologies.orient.core.exception.OStorageException; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author Andrey Lomakin (a.lomakin-at-orientechnologies.com) * @since 5/6/14 */ public class OPaginatedStorageDirtyFlag { private final String dirtyFilePath; private File dirtyFile; private RandomAccessFile dirtyFileData; private FileChannel channel; private FileLock fileLock; private volatile boolean dirtyFlag; private volatile boolean indexRebuildScheduled; private final Lock lock = new ReentrantLock(); public OPaginatedStorageDirtyFlag(String dirtyFilePath) { this.dirtyFilePath = dirtyFilePath; } public void create() throws IOException { lock.lock(); try { dirtyFile = new File(dirtyFilePath); if (dirtyFile.exists()) { final boolean fileDeleted = dirtyFile.delete(); if (!fileDeleted) throw new IllegalStateException("Cannot delete file : " + dirtyFilePath); } final boolean fileCreated = dirtyFile.createNewFile(); if (!fileCreated) throw new IllegalStateException("Cannot create file : " + dirtyFilePath); dirtyFileData = new RandomAccessFile(dirtyFile, "rwd"); channel = dirtyFileData.getChannel(); if (OGlobalConfiguration.FILE_LOCK.getValueAsBoolean()) { lockFile(); } dirtyFlag = true; indexRebuildScheduled = false; writeState(dirtyFlag, indexRebuildScheduled); } finally { lock.unlock(); } } private void lockFile() throws IOException { try { fileLock = channel.tryLock(); } catch (OverlappingFileLockException e) { OLogManager.instance().warn(this, "Database is open by another process"); } if (fileLock == null) throw new OStorageException("Can not open storage it is acquired by other process"); } public boolean exists() { lock.lock(); try { return new File(dirtyFilePath).exists(); } finally { lock.unlock(); } } public void open() throws IOException { lock.lock(); try { dirtyFile = new File(dirtyFilePath); if (!dirtyFile.exists()) { final boolean fileCreated = dirtyFile.createNewFile(); if (!fileCreated) throw new IllegalStateException("Cannot create file : " + dirtyFilePath); dirtyFileData = new RandomAccessFile(dirtyFile, "rwd"); channel = dirtyFileData.getChannel(); writeState(false, false); } dirtyFileData = new RandomAccessFile(dirtyFile, "rwd"); channel = dirtyFileData.getChannel(); if (OGlobalConfiguration.FILE_LOCK.getValueAsBoolean()) { lockFile(); } channel.position(0); if (channel.size() < 2) { ByteBuffer buffer = ByteBuffer.allocate(1); readByteBuffer(buffer, channel); buffer.position(0); dirtyFlag = buffer.get() > 0; writeState(dirtyFlag, indexRebuildScheduled); } else { ByteBuffer buffer = ByteBuffer.allocate(2); readByteBuffer(buffer, channel); buffer.position(0); dirtyFlag = buffer.get() > 0; indexRebuildScheduled = buffer.get() > 0; } } finally { lock.unlock(); } } public void close() throws IOException { lock.lock(); try { if (dirtyFile == null) { return; } if (dirtyFile.exists()) { if (fileLock != null) { fileLock.release(); fileLock = null; } dirtyFileData.close(); } } finally { lock.unlock(); } } public void delete() throws IOException { lock.lock(); try { if (dirtyFile == null) return; if (dirtyFile.exists()) { if (fileLock != null) { fileLock.release(); fileLock = null; } channel.close(); dirtyFileData.close(); boolean deleted = dirtyFile.delete(); while (!deleted) { deleted = !dirtyFile.exists() || dirtyFile.delete(); } } } finally { lock.unlock(); } } public void makeDirty() throws IOException { if (dirtyFlag) return; lock.lock(); try { if (dirtyFlag) return; dirtyFlag = true; writeState(dirtyFlag, indexRebuildScheduled); } finally { lock.unlock(); } } public void clearDirty() throws IOException { if (!dirtyFlag) return; lock.lock(); try { if (!dirtyFlag) return; dirtyFlag = false; writeState(dirtyFlag, indexRebuildScheduled); } finally { lock.unlock(); } } public void scheduleIndexRebuild() throws IOException { if (indexRebuildScheduled) return; lock.lock(); try { if (indexRebuildScheduled) return; indexRebuildScheduled = true; writeState(dirtyFlag, indexRebuildScheduled); } finally { lock.unlock(); } } public void clearIndexRebuild() throws IOException { if (!indexRebuildScheduled) return; lock.lock(); try { if (!indexRebuildScheduled) return; indexRebuildScheduled = false; writeState(dirtyFlag, indexRebuildScheduled); } finally { lock.unlock(); } } public boolean isDirty() { return dirtyFlag; } public boolean isIndexRebuildScheduled() { return indexRebuildScheduled; } private void writeByteBuffer(ByteBuffer buffer, FileChannel channel) throws IOException { int bytesToWrite = buffer.limit(); int written = 0; while (written < bytesToWrite) { written += channel.write(buffer, written); } } private void readByteBuffer(ByteBuffer buffer, FileChannel channel) throws IOException { int bytesToRead = buffer.limit(); int read = 0; while (read < bytesToRead) { int r = channel.read(buffer); if (r == -1) throw new EOFException("End of file is reached"); read += r; } } private void writeState(boolean dirtyFlag, boolean indexRebuildScheduled) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(2); buffer.put(dirtyFlag ? (byte) 1 : 0); buffer.put(indexRebuildScheduled ? (byte) 1 : 0); channel.position(0); buffer.position(0); writeByteBuffer(buffer, channel); } }