/* * Copyright (c) 2010 - 2011, Jan Stender, Bjoern Kolbeck, Mikael Hoegqvist, * Felix Hupfeld, Felix Langner, Zuse Institute Berlin * * Licensed under the BSD License, see LICENSE file for details. * */ package de.mxro.thrd.babudb05; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import de.mxro.thrd.babudb05.api.dev.transaction.InMemoryProcessing; import de.mxro.thrd.babudb05.api.dev.transaction.OperationInternal; import de.mxro.thrd.babudb05.api.dev.transaction.TransactionInternal; import de.mxro.thrd.babudb05.api.dev.transaction.TransactionManagerInternal; import de.mxro.thrd.babudb05.api.exception.BabuDBException; import de.mxro.thrd.babudb05.api.exception.BabuDBException.ErrorCode; import de.mxro.thrd.babudb05.api.transaction.Operation; import de.mxro.thrd.babudb05.api.transaction.TransactionListener; import de.mxro.thrd.babudb05.log.DiskLogger; import de.mxro.thrd.babudb05.log.LogEntry; import de.mxro.thrd.babudb05.log.SyncListener; import de.mxro.thrd.babudb05.lsmdb.LSN; import de.mxro.thrd.xstreemfs.foundation.buffer.BufferPool; import de.mxro.thrd.xstreemfs.foundation.buffer.ReusableBuffer; import de.mxro.thrd.xstreemfs.foundation.logging.Logging; /** * Default implementation of the {@link TransactionManagerInternal} interface using * the {@link DiskLogger} to let operations become persistent. * * @author flangner * @since 11/03/2010 */ class TransactionManagerImpl extends TransactionManagerInternal { private final AtomicReference<DiskLogger> diskLogger = new AtomicReference<DiskLogger>(null); /** * list of transaction listeners */ private final List<TransactionListener> listeners = new LinkedList<TransactionListener>(); private volatile LSN latestOnDisk; private final boolean isAsync; public TransactionManagerImpl (boolean isAsync) { this.isAsync = isAsync; } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal#init( * org.xtreemfs.babudb.lsmdb.LSN) */ public void init(LSN initial) { latestOnDisk = initial; } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal#setLogger( * org.xtreemfs.babudb.log.DiskLogger) */ public void setLogger(DiskLogger logger) { synchronized (diskLogger) { DiskLogger old = diskLogger.getAndSet(logger); if (logger == null) { latestOnDisk = old.getLatestLSN(); } else { diskLogger.notify(); } } } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal#makePersistent( * org.xtreemfs.babudb.api.dev.transaction.TransactionInternal, * org.xtreemfs.foundation.buffer.ReusableBuffer, * org.xtreemfs.babudb.BabuDBRequestResultImpl) */ @Override public void makePersistent(final TransactionInternal txn, ReusableBuffer payload, BabuDBRequestResultImpl<Object> future) throws BabuDBException { Logging.logMessage(Logging.LEVEL_DEBUG, this, "Trying to perform transaction %s ...", txn.toString()); try { Object[] result = inMemory(txn, payload); LogEntry entry = generateLogEntry(txn, payload, future, result); onDisk(txn, entry); // notify listeners (async) if (isAsync) { future.finished(result, null); for (TransactionListener l : listeners) { l.transactionPerformed(txn); } } } finally { txn.unlockWorkers(); } } /** * Method to create a logEntry for the given transaction. * * @param txn * @param payload * @param listener * * @return a prepared logEntry. */ private final LogEntry generateLogEntry(final TransactionInternal txn, ReusableBuffer payload, final BabuDBRequestResultImpl<Object> listener, final Object[] results) { LogEntry result = new LogEntry(payload, new SyncListener() { @Override public void synced(LSN lsn) { try { if (!isAsync) { BabuDBException irregs = txn.getIrregularities(); if (irregs == null) { listener.finished(results, lsn); } else { throw new BabuDBException(irregs.getErrorCode(), "Transaction " + "failed at the execution of the " + (txn.size() + 1) + "th operation, because: " + irregs.getMessage(), irregs); } } } catch (BabuDBException error) { if (!isAsync) { listener.failed(error); } else { Logging.logError(Logging.LEVEL_WARN, this, error); } } finally { Logging.logMessage(Logging.LEVEL_DEBUG, this, "... transaction %s finished.", txn.toString()); // notify listeners (sync) if (!isAsync) { for (TransactionListener l : listeners) { l.transactionPerformed(txn); } } } } @Override public void failed(Exception ex) { if (!isAsync) { listener.failed((ex != null && ex instanceof BabuDBException) ? (BabuDBException) ex : new BabuDBException( ErrorCode.INTERNAL_ERROR, ex.getMessage())); } } }, LogEntry.PAYLOAD_TYPE_TRANSACTION); return result; } /** * Initialize the on-disk processing. The transaction's log entry is send to the diskLogger. * * @param txn * @param entry */ private final void onDisk(TransactionInternal txn, LogEntry entry) throws BabuDBException { // append the entry to the DiskLogger if available, wait otherwise try { synchronized (diskLogger) { while (diskLogger.get() == null) { diskLogger.wait(); } diskLogger.get().append(entry); } } catch (InterruptedException ie) { if (entry != null) entry.free(); throw new BabuDBException(ErrorCode.INTERRUPTED, "Operation " + "could not have been stored persistent to disk and " + "will therefore be discarded.", ie.getCause()); } } /** * Internal method to process the in-memory changes on BabuDB for a given transaction. * * @param txn * @throws BabuDBException */ private final Object[] inMemory(TransactionInternal txn, ReusableBuffer payload) throws BabuDBException { List<Object> operationResults = new ArrayList<Object>(); // in memory processing for (int i = 0; i < txn.size(); i++) { try { OperationInternal operation = txn.get(i); txn.lockResponsibleWorker(operation.getDatabaseName()); operationResults.add( inMemoryProcessing.get(operation.getType()).process(operation)); } catch (BabuDBException be) { // have there already been some successful executions? if (i > 0) { // trim the transaction txn.cutOfAt(i, be); try { payload.shrink(txn.getSize()); break; } catch (IOException ioe) { BufferPool.free(payload); throw new BabuDBException(ErrorCode.IO_ERROR, ioe.getMessage(), ioe); } } else { // no operation could have been executed so far BufferPool.free(payload); throw be; } } } return operationResults.toArray(); } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal# * replayTransaction(org.xtreemfs.babudb.api.dev.transaction.TransactionInternal) */ @Override public void replayTransaction(TransactionInternal txn) throws BabuDBException { for (OperationInternal operation : txn) { byte type = operation.getType(); // exclude database create/copy/delete calls from replay if (type != Operation.TYPE_COPY_DB && type != Operation.TYPE_CREATE_DB && type != Operation.TYPE_DELETE_DB) { // get processing logic InMemoryProcessing processing = inMemoryProcessing.get(type); // replay in-memory changes try { processing.process(operation); } catch (BabuDBException be) { // there might be false positives if a snapshot to delete has already been // deleted or a snapshot to create has already been created. // also there could be inserts for databases that have been deleted already. if (!(type == Operation.TYPE_CREATE_SNAP && (be.getErrorCode() == ErrorCode.SNAP_EXISTS || be .getErrorCode() == ErrorCode.NO_SUCH_DB)) && !(type == Operation.TYPE_DELETE_SNAP && be.getErrorCode() == ErrorCode.NO_SUCH_SNAPSHOT) && !(type == Operation.TYPE_GROUP_INSERT && be.getErrorCode().equals(ErrorCode.NO_SUCH_DB))){ throw be; } } } } } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal#lockService() */ @Override public void lockService() throws InterruptedException { DiskLogger logger = diskLogger.get(); if(logger != null) { logger.lock(); } } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal#unlockService() */ @Override public void unlockService() { DiskLogger logger = diskLogger.get(); if (logger != null && logger.hasLock()) { logger.unlock(); } } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal#getLatestOnDiskLSN() */ @Override public LSN getLatestOnDiskLSN() { DiskLogger logger = diskLogger.get(); if (logger != null) { return logger.getLatestLSN(); } else { return latestOnDisk; } } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.TransactionManagerInternal#addTransactionListener( * org.xtreemfs.babudb.api.transaction.TransactionListener) */ @Override public void addTransactionListener(TransactionListener listener) { if (listener == null) throw new NullPointerException(); listeners.add(listener); } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.TransactionManagerInternal#removeTransactionListener( * org.xtreemfs.babudb.api.transaction.TransactionListener) */ @Override public void removeTransactionListener(TransactionListener listener) { listeners.remove(listener); } }