package org.wonderdb.txnlogger; /******************************************************************************* * Copyright 2013 Vilas Athavale * * 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. *******************************************************************************/ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.wonderdb.cache.impl.CacheWriter; import org.wonderdb.seralizers.block.SerializedBlockImpl; import org.wonderdb.server.WonderDBPropertyManager; import org.wonderdb.types.BlockPtr; public class LogManager { AtomicInteger at = new AtomicInteger(); Set<Integer> committingTxns = new HashSet<Integer>(); Map<BlockPtr, SerializedBlockImpl> blockPtrBlockMap = new HashMap<BlockPtr, SerializedBlockImpl>(); Map<Integer, Set<BlockPtr>> blockPtrsInTxn = new HashMap<Integer, Set<BlockPtr>>(); List<RandomAccessFile> logFiles = new ArrayList<RandomAccessFile>(); int currentFilePosn = 0; CacheWriter<BlockPtr, ChannelBuffer> writer = null; String filePath = ""; long currentMaxTxnTime = -1; boolean loggingEnabled = false; private static LogManager instance = new LogManager(); private LogManager() { if (!WonderDBPropertyManager.getInstance().isLookAheadLoggingEnabled()) { return; } loggingEnabled = true; filePath = WonderDBPropertyManager.getInstance().getLogFilePath(); while (true) { File file = null; file = new File("./"+filePath+"/redolog"+logFiles.size()); if (!file.exists()) { try { file.createNewFile(); RandomAccessFile raf = new RandomAccessFile(file, "rw"); logFiles.add(raf); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } else { break; } } } public static LogManager getInstance() { return instance; } public TransactionId startTxn() { if (!loggingEnabled) { return null; } TransactionId txnId = new TransactionId(at.getAndAdd(1)); Set<BlockPtr> ptrList = new HashSet<BlockPtr>(); blockPtrsInTxn.put(txnId.getId(), ptrList); return txnId; } public synchronized void logBlock(TransactionId txnId, SerializedBlockImpl block) { if (!loggingEnabled) { return; } ChannelBuffer buffer = ChannelBuffers.copiedBuffer(block.getFullBuffer()); SerializedBlockImpl sb = new SerializedBlockImpl(block.getPtr(), buffer); blockPtrBlockMap.put(block.getPtr(), sb); sb.setLastAccessTime(block.getLastAccessTime()); Set<BlockPtr> ptrList = blockPtrsInTxn.get(txnId.getId()); ptrList.add(block.getPtr()); } public synchronized void commitTxn(TransactionId txnId) { if (!loggingEnabled) { return; } if (txnId == null) { return; } committingTxns.add(txnId.getId()); logTransaction(txnId.getId()); } private boolean shouldWait(int txnId, Set<Integer> dependetTxns) { Set<BlockPtr> ptrList = blockPtrsInTxn.get(txnId); Iterator<Integer> iter = blockPtrsInTxn.keySet().iterator(); while (iter.hasNext()) { int txn = iter.next(); if (txn == txnId) { continue; } Set<BlockPtr> list = blockPtrsInTxn.get(txn); Iterator<BlockPtr> txnPtrIter = ptrList.iterator(); while (txnPtrIter.hasNext()) { BlockPtr ptr = txnPtrIter.next(); if (list.contains(ptr) && !committingTxns.contains(txn)) { dependetTxns.clear(); return true; } if (list.contains(ptr) && committingTxns.contains(txn)) { dependetTxns.add(txn); } } } return false; } private synchronized void logTransaction(int txnId) { Set<Integer> dependentTxns = new HashSet<Integer>(); while (true) { if (!committingTxns.contains(txnId)) { return; } if (shouldWait(txnId, dependentTxns)) { try { wait(); } catch (InterruptedException e) { continue; } } else { break; } } Set<BlockPtr> bpList = new HashSet<BlockPtr>(); bpList.addAll(blockPtrsInTxn.remove(txnId)); Iterator<Integer> iter = dependentTxns.iterator(); while (iter.hasNext()) { int txn = iter.next(); bpList.addAll(blockPtrsInTxn.remove(txn)); } Set<BlockPtr> bpSet = new HashSet<BlockPtr>(bpList); List<SerializedBlockImpl> sblist = new ArrayList<SerializedBlockImpl>(); Iterator<BlockPtr> iter1 = bpSet.iterator(); while (iter1.hasNext()) { SerializedBlockImpl sb = blockPtrBlockMap.remove(iter1.next()); sblist.add(sb); } write(sblist); committingTxns.remove(txnId); committingTxns.removeAll(dependentTxns); notifyAll(); } private void write(List<SerializedBlockImpl> list) { if (list == null || list.size() == 0) { return; } ChannelBuffer[] buffers = new ChannelBuffer[list.size()]; for (int i = 0; i < list.size(); i++) { SerializedBlockImpl sb = list.get(i); buffers[i] = sb.getFullBuffer(); currentMaxTxnTime = Math.max(currentMaxTxnTime, sb.getLastAccessTime()); } ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(buffers); ChannelBuffer header = ChannelBuffers.buffer((Integer.SIZE/8)*2); header.writeInt(buffer.capacity()); header.writeInt(buffer.hashCode()); ChannelBuffer wrapped = ChannelBuffers.wrappedBuffer(header, buffer); FileChannel channel = getFileChannel(buffer.capacity()); ByteBuffer buf = wrapped.toByteBuffer(); try { channel.write(buf); } catch (IOException e) { } } private FileChannel getFileChannel(int requiredSize) { FileChannel channel = logFiles.get(currentFilePosn).getChannel(); long currentSize = -1; try { currentSize = channel.position(); } catch (IOException e) { } if (currentSize + requiredSize < 100000000) { return channel; } return findOrCreateNewLogFile(); } private void resetCurrentChannel(long time) { FileChannel channel = logFiles.get(currentFilePosn).getChannel(); ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE/8); buffer.putLong(System.currentTimeMillis()); buffer.flip(); try { channel.position(0); channel.write(buffer); } catch (IOException e) { e.printStackTrace(); } } private FileChannel findOrCreateNewLogFile() { long currentSyncTime = writer.getSyncTime(); resetCurrentChannel(currentMaxTxnTime); for (int i = 0; i < logFiles.size(); i++) { if (i == currentFilePosn) { continue; } FileChannel channel = logFiles.get(i).getChannel(); long size = 0; try { size = channel.size(); } catch (IOException e1) { e1.printStackTrace(); } if (size == 0) { continue; } try { channel.position(0); } catch (IOException e) { } ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE/8); try { channel.read(buffer); } catch (IOException e) { e.printStackTrace(); } buffer.flip(); long l = buffer.getLong(); if (currentSyncTime >= l) { currentFilePosn = i; resetCurrentChannel(Long.MAX_VALUE); return channel; } } // create new one RandomAccessFile raf = null; try { raf = new RandomAccessFile(filePath+"/redolog"+logFiles.size(), "rw"); logFiles.add(raf); currentFilePosn = logFiles.size()-1; resetCurrentChannel(Long.MAX_VALUE); } catch (FileNotFoundException e) { e.printStackTrace(); } return logFiles.get(currentFilePosn).getChannel(); } public void shutdown() { for (int i = 0; i < logFiles.size(); i++) { try { logFiles.get(i).setLength(0); } catch (IOException e) { e.printStackTrace(); } } } public void resetLogs(long writerSyncedTime) { synchronized (committingTxns) { for (int i = 0; i < logFiles.size(); i++) { if (i == currentFilePosn) { continue; } RandomAccessFile raf = logFiles.get(i); FileChannel channel = raf.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE/8); try { channel.position(0); channel.read(buffer); } catch (IOException e) { e.printStackTrace(); } buffer.flip(); long l = buffer.getLong(); if (l < writerSyncedTime) { try { raf.setLength(0); } catch (IOException e) { e.printStackTrace(); } } } } } }