package com.ctriposs.bigmap.page; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ctriposs.bigmap.utils.FileUtil; import static java.nio.channels.FileChannel.MapMode.READ_WRITE; /** * Mapped mapped page resource manager, * responsible for the creation, cache the mapped pages. * * @author bulldog * */ public class MappedPageFactoryImpl implements IMappedPageFactory { private final static Logger logger = LoggerFactory.getLogger(MappedPageFactoryImpl.class); private int pageSize; private String pageDir; private File pageDirFile; private String pageFile; public static final String PAGE_FILE_NAME = "page"; public static final String PAGE_FILE_SUFFIX = ".dat"; private Map<Long, MappedPageImpl> cache; public MappedPageFactoryImpl(int pageSize, String pageDir) { this.pageSize = pageSize; this.pageDir = pageDir; this.pageDirFile = new File(this.pageDir); if (!pageDirFile.exists()) { pageDirFile.mkdirs(); } if (!this.pageDir.endsWith(File.separator)) { this.pageDir += File.separator; } this.pageFile = this.pageDir + PAGE_FILE_NAME + "-"; this.cache = new HashMap<Long, MappedPageImpl>(); } public IMappedPage acquirePage(long index) throws IOException { MappedPageImpl mpi = cache.get(index); if (mpi == null) { // not in cache, need to create one synchronized(cache) { // lock the map mpi = cache.get(index); // double check if (mpi == null) { RandomAccessFile raf = null; FileChannel channel = null; try { String fileName = this.getFileNameByIndex(index); raf = new RandomAccessFile(fileName, "rw"); channel = raf.getChannel(); MappedByteBuffer mbb = channel.map(READ_WRITE, 0, this.pageSize); mpi = new MappedPageImpl(mbb, fileName, index); cache.put(index, mpi); if (logger.isDebugEnabled()) { logger.debug("Mapped page for " + fileName + " was just created and cached."); } } finally { if (channel != null) channel.close(); if (raf != null) raf.close(); } } } } else { if (logger.isDebugEnabled()) { logger.debug("Hit mapped page " + mpi.getPageFile() + " in cache."); } } return mpi; } private String getFileNameByIndex(long index) { return this.pageFile + index + PAGE_FILE_SUFFIX; } public int getPageSize() { return pageSize; } public String getPageDir() { return pageDir; } /** * thread unsafe, caller need synchronization */ @Override public void releaseCachedPages() throws IOException { this.removeAllCache(); } /** * thread unsafe, caller need synchronization */ @Override public void deleteAllPages() throws IOException { this.removeAllCache(); Set<Long> indexSet = getExistingBackFileIndexSet(); this.deletePages(indexSet); if (logger.isDebugEnabled()) { logger.debug("All page files in dir " + this.pageDir + " have been deleted."); } } private void removeCache(long index) throws IOException { MappedPageImpl page = cache.remove(index); if (page != null) { page.close(); } } private void removeAllCache() throws IOException { for(MappedPageImpl page : cache.values()) { page.close(); } cache.clear(); } /** * thread unsafe, caller need synchronization */ @Override public void deletePages(Set<Long> indexes) throws IOException { if (indexes == null) return; for(long index : indexes) { this.deletePage(index); } } /** * thread unsafe, caller need synchronization */ @Override public void deletePage(long index) throws IOException { // remove the page from cache first this.removeCache(index); String fileName = this.getFileNameByIndex(index); int count = 0; int maxRound = 10; boolean deleted = false; while(count < maxRound) { try { FileUtil.deleteFile(new File(fileName)); deleted = true; break; } catch (IllegalStateException ex) { try { Thread.sleep(200); } catch (InterruptedException e) { } count++; if (logger.isDebugEnabled()) { logger.warn("fail to delete file " + fileName + ", tried round = " + count); } } } if (deleted) { logger.info("Page file " + fileName + " was just deleted."); } else { logger.warn("fail to delete file " + fileName + " after max " + maxRound + " rounds of try, you may delete it manually."); } } @Override public Set<Long> getPageIndexSetBefore(long timestamp) { Set<Long> beforeIndexSet = new HashSet<Long>(); File[] pageFiles = this.pageDirFile.listFiles(); if (pageFiles != null && pageFiles.length > 0) { for(File pageFile : pageFiles) { if (pageFile.lastModified() < timestamp) { String fileName = pageFile.getName(); if (fileName.endsWith(PAGE_FILE_SUFFIX)) { long index = this.getIndexByFileName(fileName); beforeIndexSet.add(index); } } } } return beforeIndexSet; } private long getIndexByFileName(String fileName) { int beginIndex = fileName.lastIndexOf('-'); beginIndex += 1; int endIndex = fileName.lastIndexOf(PAGE_FILE_SUFFIX); String sIndex = fileName.substring(beginIndex, endIndex); long index = Long.parseLong(sIndex); return index; } /** * thread unsafe, caller need synchronization * @throws IOException */ @Override public void deletePagesBefore(long timestamp) throws IOException { Set<Long> indexSet = this.getPageIndexSetBefore(timestamp); this.deletePages(indexSet); if (logger.isDebugEnabled()) { logger.debug("All page files in dir [" + this.pageDir + "], before [" + timestamp + "] have been deleted."); } } @Override public Set<Long> getExistingBackFileIndexSet() { Set<Long> indexSet = new HashSet<Long>(); File[] pageFiles = this.pageDirFile.listFiles(); if (pageFiles != null && pageFiles.length > 0) { for(File pageFile : pageFiles) { String fileName = pageFile.getName(); if (fileName.endsWith(PAGE_FILE_SUFFIX)) { long index = this.getIndexByFileName(fileName); indexSet.add(index); } } } return indexSet; } @Override public int getCacheSize() { return cache.size(); } @Override public long getPageFileLastModifiedTime(long index) { String pageFileName = this.getFileNameByIndex(index); File pageFile = new File(pageFileName); if (!pageFile.exists()) { return -1L; } return pageFile.lastModified(); } @Override public long getFirstPageIndexBefore(long timestamp) { Set<Long> beforeIndexSet = getPageIndexSetBefore(timestamp); if (beforeIndexSet.size() == 0) return -1L; TreeSet<Long> sortedIndexSet = new TreeSet<Long>(beforeIndexSet); Long largestIndex = sortedIndexSet.last(); if (largestIndex != Long.MAX_VALUE) { // no wrap, just return the largest return largestIndex; } else { // wrapped case Long next = 0L; while(sortedIndexSet.contains(next)) { next++; } if (next == 0L) { return Long.MAX_VALUE; } else { return --next; } } } /** * thread unsafe, caller need synchronization */ @Override public void flush() { Collection<MappedPageImpl> cachedPages = cache.values(); for(IMappedPage mappedPage : cachedPages) { mappedPage.flush(); } } @Override public Set<String> getBackPageFileSet() { Set<String> fileSet = new HashSet<String>(); File[] pageFiles = this.pageDirFile.listFiles(); if (pageFiles != null && pageFiles.length > 0) { for(File pageFile : pageFiles) { String fileName = pageFile.getName(); if (fileName.endsWith(PAGE_FILE_SUFFIX)) { fileSet.add(fileName); } } } return fileSet; } @Override public long getBackPageFileSize() { long totalSize = 0L; File[] pageFiles = this.pageDirFile.listFiles(); if (pageFiles != null && pageFiles.length > 0) { for(File pageFile : pageFiles) { String fileName = pageFile.getName(); if (fileName.endsWith(PAGE_FILE_SUFFIX)) { totalSize += pageFile.length(); } } } return totalSize; } }