package uk.ac.imperial.lsds.seepworker.core; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.imperial.lsds.seepworker.WorkerConfig; /*** * Class to move Datasets between disk and memory, as requested (by the DataReferenceManager). * @author iv * */ public class DiskCacher { final private Logger LOG = LoggerFactory.getLogger(DiskCacher.class.getName()); private Map<Integer, String> filenames; private static DiskCacher instance; private WorkerConfig wc; private DiskCacher(WorkerConfig wc) { filenames = new HashMap<Integer, String>(); this.wc = wc; } public static DiskCacher makeDiskCacher(WorkerConfig wc) { if(instance == null) { instance = new DiskCacher(wc); } return instance; } public String createDatasetOnDisk(int datasetId) { String cacheFileName = ""; //Changing to abs might lead to a conflict (HIGHLY unlikely, needs a DataSet with the opposite //ID cached at exactly the same time), but files that start with - are annoying in console //debugging. cacheFileName = Math.abs(datasetId) + "_" + System.currentTimeMillis() + ".cached"; filenames.put(datasetId, cacheFileName); return cacheFileName; } private String getCacheFileName(int id) { String cacheFileName = ""; if(filenames == null) { System.out.println("filenames null"); } if (filenames.containsKey(id)) { //Already on disk. We could claim victory and return, but this will allow us to cache any //items stuck in memory (see comment below). cacheFileName = filenames.get(id); } else { //Changing to abs might lead to a conflict (HIGHLY unlikely, needs a DataSet with the opposite //ID cached at exactly the same time), but files that start with - are annoying in console //debugging. cacheFileName = Math.abs(id) + "_" + System.currentTimeMillis() + ".cached"; filenames.put(id, cacheFileName); } return cacheFileName; } public long _cacheToDisk(Dataset data) throws FileNotFoundException, IOException { String cacheFileName = getCacheFileName(data.id()); // Prepare channel WritableByteChannel bos = Channels.newChannel(new FileOutputStream(cacheFileName)); // Basically get buffers from Dataset and write them in chunks, and ordered to disk Iterator<ByteBuffer> buffers = data.prepareForTransferToDisk(); while(buffers != null && buffers.hasNext()) { ByteBuffer bb = buffers.next(); int limit = bb.limit(); ByteBuffer size = ByteBuffer.allocate(Integer.BYTES).putInt(limit); bos.write(size); bos.write(bb); } // close long freedMemory = data.completeTransferToDisk(); bos.close(); data.setCachedLocation(cacheFileName); LOG.debug("Content is spilled to: {}", cacheFileName); return freedMemory; } public long cacheToDisk(Dataset data) throws FileNotFoundException, IOException { if (filenames.containsKey(data.id())) { return 0; } String cacheFileName = getCacheFileName(data.id()); filenames.put(data.id(), cacheFileName); // Prepare channel FileOutputStream fos = new FileOutputStream(cacheFileName, true); BufferedOutputStream bos = new BufferedOutputStream(fos, wc.getInt(WorkerConfig.BUFFERPOOL_MIN_BUFFER_SIZE)); // Basically get buffers from Dataset and write them in chunks, and ordered to disk Iterator<ByteBuffer> buffers = data.prepareForTransferToDisk(); byte[] payload = new byte[wc.getInt(WorkerConfig.BUFFERPOOL_MIN_BUFFER_SIZE)]; while(buffers != null && buffers.hasNext()) { ByteBuffer bb = buffers.next(); if (payload.length != bb.limit() - bb.arrayOffset()) { payload = new byte[bb.limit() - bb.arrayOffset()]; } bb.get(payload, bb.arrayOffset(), bb.remaining()); bos.write(ByteBuffer.allocate(Integer.BYTES).putInt(payload.length).array()); bos.write(payload, 0, payload.length); } bos.flush(); fos.getFD().sync(); bos.close(); fos.close(); data.setCachedLocation(cacheFileName); // close long freedMemory = data.completeTransferToDisk(); LOG.debug("Content is spilled to: {}", cacheFileName); return freedMemory; } public void retrieveFromDisk(Dataset data) throws FileNotFoundException { // Get cache file String cacheFileName = filenames.get(data.id()); // Prepare dataset for trasnfer to memory ByteBuffer currentPointer = data.prepareForTransferToMemory(); int bbSize = wc.getInt(WorkerConfig.BUFFERPOOL_MIN_BUFFER_SIZE); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(cacheFileName), bbSize); data.transferToMemory(bis, bbSize); data.completeTransferToMemory(currentPointer); data.unsetCachedLocation(); filenames.remove(data.id()); if (filenames.containsKey(data.id())) { data.setCachedLocation(filenames.get(data.id())); } } /*** * Check if a Dataset is currently entirely in memory (or conversely, * (perhaps partially in the case of multithreading) on disk). * @param data * @return true if data is in memory. */ public boolean inMem(Dataset data) { return (!(filenames.containsKey(data.id()))); } }