/* * Copyright 2008-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * 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. */ package com.amazon.carbonado.repo.map; import java.util.concurrent.TimeUnit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.FetchInterruptedException; import com.amazon.carbonado.FetchTimeoutException; import com.amazon.carbonado.IsolationLevel; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.PersistInterruptedException; import com.amazon.carbonado.PersistTimeoutException; import com.amazon.carbonado.Storable; /** * * * @author Brian S O'Neill */ class MapTransaction { private final MapTransaction mParent; private final IsolationLevel mLevel; private final Object mLocker; private final int mLockTimeout; private final TimeUnit mLockTimeoutUnit; private Set<UpgradableLock> mUpgradeLocks; private Set<UpgradableLock> mWriteLocks; private List<Undoable> mUndoLog; MapTransaction(MapTransaction parent, IsolationLevel level, int lockTimeout, TimeUnit lockTimeoutUnit) { mParent = parent; mLevel = level; mLocker = parent == null ? this : parent.mLocker; mLockTimeout = lockTimeout; mLockTimeoutUnit = lockTimeoutUnit; } void lockForUpgrade(UpgradableLock lock, boolean isForUpdate) throws FetchException { if (!isForUpdate && mLevel.isAtMost(IsolationLevel.READ_COMMITTED)) { doLockForUpgrade(lock); } else { Set<UpgradableLock> locks = mUpgradeLocks; if (locks == null) { mUpgradeLocks = locks = new HashSet<UpgradableLock>(); doLockForUpgrade(lock); locks.add(lock); } else if (!locks.contains(lock)) { doLockForUpgrade(lock); locks.add(lock); } } } private void doLockForUpgrade(UpgradableLock lock) throws FetchException { try { if (!lock.tryLockForUpgrade(mLocker, mLockTimeout, mLockTimeoutUnit)) { throw new FetchTimeoutException("" + mLockTimeout + ' ' + mLockTimeoutUnit.toString().toLowerCase()); } } catch (InterruptedException e) { throw new FetchInterruptedException(e); } } void unlockFromUpgrade(UpgradableLock lock, boolean isForUpdate) { if (!isForUpdate && mLevel.isAtMost(IsolationLevel.READ_COMMITTED)) { lock.unlockFromUpgrade(mLocker); } } void lockForWrite(UpgradableLock lock) throws PersistException { Set<UpgradableLock> locks = mWriteLocks; if (locks == null) { mWriteLocks = locks = new HashSet<UpgradableLock>(); doLockForWrite(lock); locks.add(lock); } else if (!locks.contains(lock)) { doLockForWrite(lock); locks.add(lock); } } private void doLockForWrite(UpgradableLock lock) throws PersistException { try { if (!lock.tryLockForWrite(mLocker, mLockTimeout, mLockTimeoutUnit)) { throw new PersistTimeoutException("" + mLockTimeout + ' ' + mLockTimeoutUnit.toString().toLowerCase()); } } catch (InterruptedException e) { throw new PersistInterruptedException(e); } } /** * Add to undo log. */ <S extends Storable> void inserted(final MapStorage<S> storage, final S key) { addToUndoLog(new Undoable() { public void undo() { storage.mapRemove(key); } @Override public String toString() { return "undo insert by remove: " + key; } }); } /** * Add to undo log. */ <S extends Storable> void updated(final MapStorage<S> storage, final S old) { addToUndoLog(new Undoable() { public void undo() { storage.mapPut(old); } @Override public String toString() { return "undo update by put: " + old; } }); } /** * Add to undo log. */ <S extends Storable> void deleted(final MapStorage<S> storage, final S old) { addToUndoLog(new Undoable() { public void undo() { storage.mapPut(old); } @Override public String toString() { return "undo delete by put: " + old; } }); } void commit() { MapTransaction parent = mParent; if (parent == null) { releaseLocks(); return; } // Pass undo log to parent. if (parent.mUndoLog == null) { parent.mUndoLog = mUndoLog; } else if (mUndoLog != null) { parent.mUndoLog.addAll(mUndoLog); } mUndoLog = null; // Pass write locks to parent or release if parent already has the lock. { Set<UpgradableLock> locks = mWriteLocks; if (locks != null) { Set<UpgradableLock> parentLocks = parent.mWriteLocks; if (parentLocks == null) { parent.mWriteLocks = locks; } else { for (UpgradableLock lock : locks) { if (!parentLocks.add(lock)) { lock.unlockFromWrite(mLocker); } } } mWriteLocks = null; } } // Upgrade locks can simply be released. releaseUpgradeLocks(); } void abort() { List<Undoable> log = mUndoLog; if (log != null) { for (int i=log.size(); --i>=0; ) { log.get(i).undo(); } } mUndoLog = null; releaseLocks(); } private void addToUndoLog(Undoable entry) { List<Undoable> log = mUndoLog; if (log == null) { mUndoLog = log = new ArrayList<Undoable>(); } log.add(entry); } private void releaseLocks() { releaseWriteLocks(); releaseUpgradeLocks(); } private void releaseWriteLocks() { Set<UpgradableLock> locks = mWriteLocks; if (locks != null) { for (UpgradableLock lock : locks) { lock.unlockFromWrite(mLocker); } mWriteLocks = null; } } private void releaseUpgradeLocks() { Set<UpgradableLock> locks = mUpgradeLocks; if (locks != null) { for (UpgradableLock lock : locks) { lock.unlockFromUpgrade(mLocker); } mUpgradeLocks = null; } } private static interface Undoable { void undo(); } }