/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.common.buffer.impl; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.teiid.common.buffer.AutoCleanupUtil; import org.teiid.common.buffer.FileStore; import org.teiid.common.buffer.StorageManager; import org.teiid.core.TeiidComponentException; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.logging.MessageLevel; import org.teiid.query.QueryPlugin; /** * Implements file storage that automatically splits large files and limits the number of open files. */ public class FileStorageManager implements StorageManager { private static final long MB = 1024L * 1024L; public static final int DEFAULT_MAX_OPEN_FILES = 64; public static final long DEFAULT_MAX_BUFFERSPACE = 50L * 1024L * MB; private static final String FILE_PREFIX = "b_"; //$NON-NLS-1$ private long maxBufferSpace = DEFAULT_MAX_BUFFERSPACE; private AtomicLong usedBufferSpace = new AtomicLong(); private AtomicInteger fileCounter = new AtomicInteger(); private AtomicLong sample = new AtomicLong(); private class FileInfo { private File file; private RandomAccessFile fileData; // may be null if not open public FileInfo(File file) { this.file = file; } public RandomAccessFile open() throws FileNotFoundException { if(this.fileData == null) { this.fileData = fileCache.remove(this.file); if (this.fileData == null) { this.fileData = new RandomAccessFile(file, "rw"); //$NON-NLS-1$ } } return this.fileData; } public void close() { fileCache.put(this.file, this.fileData); this.fileData = null; } public void delete() { if (fileData == null) { fileData = fileCache.remove(this.file); } if (fileData != null) { try { fileData.close(); } catch (IOException e) { } } file.delete(); } public String toString() { return "FileInfo<" + file.getName() + ", has fileData = " + (fileData != null) + ">"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } public class DiskStore extends FileStore { private String name; private FileInfo fileInfo; public DiskStore(String name) { this.name = name; } @Override public synchronized long getLength() { if (fileInfo == null) { return 0; } return fileInfo.file.length(); } @Override protected synchronized int readWrite(long fileOffset, byte[] b, int offSet, int length, boolean write) throws IOException { if (!write) { if (fileInfo == null) { return -1; } try { RandomAccessFile fileAccess = fileInfo.open(); fileAccess.seek(fileOffset); return fileAccess.read(b, offSet, length); } finally { fileInfo.close(); } } if (fileInfo == null) { fileInfo = new FileInfo(createFile(name)); } try { RandomAccessFile fileAccess = fileInfo.open(); long newLength = fileOffset + length; setLength(fileAccess, newLength, false); fileAccess.seek(fileOffset); fileAccess.write(b, offSet, length); } finally { fileInfo.close(); } return length; } private void setLength(RandomAccessFile fileAccess, long newLength, boolean truncate) throws IOException { long currentLength = fileAccess.length(); long bytesUsed = newLength - currentLength; if (bytesUsed == 0) { return; } if (bytesUsed < 0) { if (!truncate) { return; } } else if (bytesUsed > MB) { //this is a weak check, concurrent access may push us over the max. we are just trying to prevent large overage allocations long used = usedBufferSpace.get() + bytesUsed; if (used > maxBufferSpace) { System.gc(); //attempt a last ditch effort to cleanup AutoCleanupUtil.doCleanup(false); used = usedBufferSpace.get() + bytesUsed; if (used > maxBufferSpace) { throw new OutOfDiskException(QueryPlugin.Util.getString("FileStoreageManager.space_exhausted", bytesUsed, used, maxBufferSpace)); //$NON-NLS-1$ } } } fileAccess.setLength(newLength); long used = usedBufferSpace.addAndGet(bytesUsed); if (LogManager.isMessageToBeRecorded(org.teiid.logging.LogConstants.CTX_BUFFER_MGR, MessageLevel.DETAIL) && (sample.getAndIncrement() % 100) == 0) { LogManager.logDetail(LogConstants.CTX_BUFFER_MGR, "sampling bytes used:", used); //$NON-NLS-1$ } if (bytesUsed > 0 && used > maxBufferSpace) { System.gc(); //attempt a last ditch effort to cleanup AutoCleanupUtil.doCleanup(false); used = usedBufferSpace.get(); if (used > maxBufferSpace) { fileAccess.setLength(currentLength); usedBufferSpace.addAndGet(-bytesUsed); throw new OutOfDiskException(QueryPlugin.Util.getString("FileStoreageManager.space_exhausted", bytesUsed, used, maxBufferSpace)); //$NON-NLS-1$ } } } @Override public synchronized void setLength(long length) throws IOException { if (fileInfo == null) { fileInfo = new FileInfo(createFile(name)); } try { setLength(fileInfo.open(), length, true); } finally { fileInfo.close(); } } @Override public synchronized void removeDirect() { usedBufferSpace.addAndGet(-getLength()); if (fileInfo != null){ fileInfo.delete(); } } } // Initialization private int maxOpenFiles = DEFAULT_MAX_OPEN_FILES; private String directory; private File dirFile; //use subdirectories to hold the files since we may create a relatively unbounded amount of lob files and //fs performance will typically degrade if a single directory is too large private File[] subDirectories = new File[256]; // State private Map<File, RandomAccessFile> fileCache = Collections.synchronizedMap(new LinkedHashMap<File, RandomAccessFile>() { @Override protected boolean removeEldestEntry( java.util.Map.Entry<File, RandomAccessFile> eldest) { if (this.size() > maxOpenFiles) { try { eldest.getValue().close(); } catch (IOException e) { } return true; } return false; } }); /** * Initialize */ public void initialize() throws TeiidComponentException { if(this.directory == null) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30040, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30040)); } dirFile = new File(this.directory); makeDir(dirFile); for (int i = 0; i < subDirectories.length; i++) { subDirectories[i] = new File(this.directory, "b" +i); //$NON-NLS-1$ makeDir(subDirectories[i]); } } private static void makeDir(File file) throws TeiidComponentException { if(file.exists()) { if(! file.isDirectory()) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30041, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30041, file.getAbsoluteFile())); } } else if(! file.mkdirs()) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30042, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30042, file.getAbsoluteFile())); } } public void setMaxOpenFiles(int maxOpenFiles) { this.maxOpenFiles = maxOpenFiles; } public void setStorageDirectory(String directory) { this.directory = directory; } File createFile(String name) throws IOException { //spray the files into separate different directories in a round robin fashion. File storageFile = File.createTempFile(FILE_PREFIX + name + "_", null, this.subDirectories[fileCounter.getAndIncrement()&(this.subDirectories.length-1)]); //$NON-NLS-1$ if (LogManager.isMessageToBeRecorded(org.teiid.logging.LogConstants.CTX_BUFFER_MGR, MessageLevel.DETAIL)) { LogManager.logDetail(org.teiid.logging.LogConstants.CTX_BUFFER_MGR, "Created temporary storage area file " + storageFile.getAbsoluteFile()); //$NON-NLS-1$ } return storageFile; } public FileStore createFileStore(String name) { return new DiskStore(name); } public String getDirectory() { return directory; } Map<File, RandomAccessFile> getFileCache() { return fileCache; } public int getOpenFiles() { return this.fileCache.size(); } /** * Get the used buffer space in bytes * @return */ public long getUsedBufferSpace() { return usedBufferSpace.get(); } /** * Set the max amount of buffer space in bytes * @param maxBufferSpace */ public void setMaxBufferSpace(long maxBufferSpace) { this.maxBufferSpace = maxBufferSpace; } @Override public long getMaxStorageSpace() { return maxBufferSpace; } }