diff options
author | Brian S. O'Neill <bronee@gmail.com> | 2008-05-04 17:36:39 +0000 |
---|---|---|
committer | Brian S. O'Neill <bronee@gmail.com> | 2008-05-04 17:36:39 +0000 |
commit | dc708480b286decd4328d52d742cac000faca3f8 (patch) | |
tree | 1f7707507aa1b0cd4359635132e93731740abaf7 /src/main/java/com | |
parent | b265cada7b4901dfaca9f97789291c43599ddac0 (diff) |
Merged map repository implementation from map branch.
Diffstat (limited to 'src/main/java/com')
9 files changed, 3058 insertions, 0 deletions
diff --git a/src/main/java/com/amazon/carbonado/repo/map/Key.java b/src/main/java/com/amazon/carbonado/repo/map/Key.java new file mode 100644 index 0000000..5032f66 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/Key.java @@ -0,0 +1,273 @@ +/*
+ * Copyright 2008 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.lang.reflect.UndeclaredThrowableException;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.Label;
+import org.cojen.classfile.LocalVariable;
+import org.cojen.classfile.MethodInfo;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+
+import org.cojen.util.ClassInjector;
+import org.cojen.util.SoftValuedHashMap;
+
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.info.OrderedProperty;
+import com.amazon.carbonado.info.StorableInfo;
+import com.amazon.carbonado.info.StorableIntrospector;
+import com.amazon.carbonado.info.StorableProperty;
+
+import com.amazon.carbonado.gen.CodeBuilderUtil;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+class Key<S extends Storable> implements Comparable<Key<S>> {
+ protected final S mStorable;
+ protected final Comparator<S> mComparator;
+
+ Key(S storable, Comparator<S> comparator) {
+ mStorable = storable;
+ mComparator = comparator;
+ }
+
+ @Override
+ public String toString() {
+ return mStorable.toString();
+ }
+
+ public int compareTo(Key<S> other) {
+ int result = mComparator.compare(mStorable, other.mStorable);
+ if (result == 0) {
+ int t1 = tieBreaker();
+ int t2 = other.tieBreaker();
+ if (t1 < t2) {
+ result = -1;
+ } else if (t1 > t2) {
+ result = 1;
+ }
+ }
+ return result;
+ }
+
+ protected int tieBreaker() {
+ return 0;
+ }
+
+ public static interface Assigner<S extends Storable> {
+ void setKeyValues(S storable, Object[] identityValues);
+
+ void setKeyValues(S storable, Object[] identityValues, Object rangeValue);
+ }
+
+ private static final Map<Class, Assigner> mAssigners;
+
+ static {
+ mAssigners = new SoftValuedHashMap();
+ }
+
+ public static synchronized <S extends Storable> Assigner<S> getAssigner(Class<S> clazz) {
+ Assigner<S> assigner = mAssigners.get(clazz);
+ if (assigner == null) {
+ assigner = createAssigner(clazz);
+ mAssigners.put(clazz, assigner);
+ }
+ return assigner;
+ }
+
+ private static <S extends Storable> Assigner<S> createAssigner(Class<S> clazz) {
+ final StorableInfo<S> info = StorableIntrospector.examine(clazz);
+
+ ClassInjector ci = ClassInjector.create(clazz.getName(), clazz.getClassLoader());
+ ClassFile cf = new ClassFile(ci.getClassName());
+ cf.addInterface(Assigner.class);
+ cf.markSynthetic();
+ cf.setSourceFile(Key.class.getName());
+ cf.setTarget("1.5");
+
+ cf.addDefaultConstructor();
+
+ // Define required setKeyValues methods.
+
+ List<OrderedProperty<S>> pk =
+ new ArrayList<OrderedProperty<S>>(info.getPrimaryKey().getProperties());
+
+ TypeDesc storableType = TypeDesc.forClass(Storable.class);
+ TypeDesc storableArrayType = storableType.toArrayType();
+ TypeDesc userStorableType = TypeDesc.forClass(info.getStorableType());
+ TypeDesc objectArrayType = TypeDesc.OBJECT.toArrayType();
+
+ MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, "setKeyValues", null,
+ new TypeDesc[] {storableType, objectArrayType});
+
+ CodeBuilder b = new CodeBuilder(mi);
+
+ LocalVariable userStorableVar = b.createLocalVariable(null, userStorableType);
+ b.loadLocal(b.getParameter(0));
+ b.checkCast(userStorableType);
+ b.storeLocal(userStorableVar);
+
+ // Switch on the number of values supplied.
+
+ /* Switch looks like this for two pk properties:
+
+ switch(identityValues.length) {
+ default:
+ throw new IllegalArgumentException();
+ case 2:
+ storable.setPkProp2(identityValues[1]);
+ case 1:
+ storable.setPkProp1(identityValues[0]);
+ case 0:
+ }
+
+ */
+
+ b.loadLocal(b.getParameter(1));
+ b.arrayLength();
+
+ int[] cases = new int[pk.size() + 1];
+ Label[] labels = new Label[pk.size() + 1];
+
+ for (int i=0; i<labels.length; i++) {
+ cases[i] = pk.size() - i;
+ labels[i] = b.createLabel();
+ }
+
+ Label defaultLabel = b.createLabel();
+
+ b.switchBranch(cases, labels, defaultLabel);
+
+ for (int i=0; i<labels.length; i++) {
+ labels[i].setLocation();
+ int prop = cases[i] - 1;
+ if (prop >= 0) {
+ b.loadLocal(userStorableVar);
+ b.loadLocal(b.getParameter(1));
+ b.loadConstant(prop);
+ b.loadFromArray(storableArrayType);
+ callSetPropertyValue(b, pk.get(prop));
+ }
+ // Fall through to next case.
+ }
+
+ b.returnVoid();
+
+ defaultLabel.setLocation();
+ CodeBuilderUtil.throwException(b, IllegalArgumentException.class, null);
+
+ // The setKeyValues method that takes a range value calls the other
+ // setKeyValues method first, to take care of the identityValues.
+
+ mi = cf.addMethod(Modifiers.PUBLIC, "setKeyValues", null,
+ new TypeDesc[] {storableType, objectArrayType, TypeDesc.OBJECT});
+
+ b = new CodeBuilder(mi);
+
+ b.loadThis();
+ b.loadLocal(b.getParameter(0));
+ b.loadLocal(b.getParameter(1));
+ b.invokeVirtual("setKeyValues", null, new TypeDesc[] {storableType, objectArrayType});
+
+ userStorableVar = b.createLocalVariable(null, userStorableType);
+ b.loadLocal(b.getParameter(0));
+ b.checkCast(userStorableType);
+ b.storeLocal(userStorableVar);
+
+ // Switch on the number of values supplied.
+
+ /* Switch looks like this for two pk properties:
+
+ switch(identityValues.length) {
+ default:
+ throw new IllegalArgumentException();
+ case 0:
+ storable.setPkProp1(rangeValue);
+ return;
+ case 1:
+ storable.setPkProp2(rangeValue);
+ return;
+ }
+
+ */
+
+ b.loadLocal(b.getParameter(1));
+ b.arrayLength();
+
+ cases = new int[pk.size()];
+ labels = new Label[pk.size()];
+
+ for (int i=0; i<labels.length; i++) {
+ cases[i] = i;
+ labels[i] = b.createLabel();
+ }
+
+ defaultLabel = b.createLabel();
+
+ b.switchBranch(cases, labels, defaultLabel);
+
+ for (int i=0; i<labels.length; i++) {
+ labels[i].setLocation();
+ int prop = cases[i];
+ b.loadLocal(userStorableVar);
+ b.loadLocal(b.getParameter(2));
+ callSetPropertyValue(b, pk.get(prop));
+ b.returnVoid();
+ }
+
+ defaultLabel.setLocation();
+ CodeBuilderUtil.throwException(b, IllegalArgumentException.class, null);
+
+ try {
+ return (Assigner<S>) ci.defineClass(cf).newInstance();
+ } catch (IllegalAccessException e) {
+ throw new UndeclaredThrowableException(e);
+ } catch (InstantiationException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+
+ /**
+ * Creates code to call set method. Assumes Storable and property value
+ * are already on the stack.
+ */
+ private static void callSetPropertyValue(CodeBuilder b, OrderedProperty<?> op) {
+ StorableProperty<?> property = op.getChainedProperty().getLastProperty();
+ TypeDesc propType = TypeDesc.forClass(property.getType());
+ if (propType != TypeDesc.OBJECT) {
+ TypeDesc objectType = propType.toObjectType();
+ b.checkCast(objectType);
+ // Potentially unbox primitive.
+ b.convert(objectType, propType);
+ }
+ b.invoke(property.getWriteMethod());
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/MapCursor.java b/src/main/java/com/amazon/carbonado/repo/map/MapCursor.java new file mode 100644 index 0000000..cd0f7e3 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/MapCursor.java @@ -0,0 +1,173 @@ +/*
+ * Copyright 2008 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.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.IsolationLevel;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.cursor.AbstractCursor;
+
+import com.amazon.carbonado.spi.TransactionScope;
+
+/**
+ * Returns copies of Storables that it iterates over.
+ *
+ * @author Brian S O'Neill
+ */
+class MapCursor<S extends Storable> extends AbstractCursor<S> {
+ private static final AtomicReferenceFieldUpdater<MapCursor, Iterator> cIteratorRef =
+ AtomicReferenceFieldUpdater.newUpdater
+ (MapCursor.class, Iterator.class, "mIterator");
+
+ private final MapStorage<S> mStorage;
+ private final TransactionScope<MapTransaction> mScope;
+ private final MapTransaction mTxn;
+ private final boolean mIsForUpdate;
+
+ private volatile Iterator<S> mIterator;
+
+ MapCursor(MapStorage<S> storage,
+ TransactionScope<MapTransaction> scope,
+ Iterable<S> iterable)
+ throws Exception
+ {
+ MapTransaction txn = scope.getTxn();
+
+ mStorage = storage;
+ mScope = scope;
+ mTxn = txn;
+
+ if (txn == null) {
+ mStorage.mLock.lockForRead(scope);
+ mIsForUpdate = false;
+ } else {
+ // Since lock is so coarse, all reads in transaction scope are
+ // upgrade to avoid deadlocks.
+ txn.lockForUpgrade(mStorage.mLock, mIsForUpdate = scope.isForUpdate());
+ }
+
+ scope.register(storage.getStorableType(), this);
+ mIterator = iterable.iterator();
+ }
+
+ public void close() {
+ Iterator<S> it = mIterator;
+ if (it != null) {
+ if (cIteratorRef.compareAndSet(this, it, null)) {
+ UpgradableLock lock = mStorage.mLock;
+ if (mTxn == null) {
+ lock.unlockFromRead(mScope);
+ } else {
+ mTxn.unlockFromUpgrade(lock, mIsForUpdate);
+ }
+ mScope.unregister(mStorage.getStorableType(), this);
+ }
+ }
+ }
+
+ public boolean hasNext() throws FetchException {
+ Iterator<S> it = mIterator;
+ try {
+ if (it != null && it.hasNext()) {
+ return true;
+ } else {
+ close();
+ }
+ return false;
+ } catch (ConcurrentModificationException e) {
+ close();
+ throw new FetchException(e);
+ } catch (Error e) {
+ try {
+ close();
+ } catch (Error e2) {
+ // Ignore.
+ }
+ throw e;
+ }
+ }
+
+ public S next() throws FetchException {
+ Iterator<S> it = mIterator;
+ if (it == null) {
+ close();
+ throw new NoSuchElementException();
+ }
+ try {
+ S next = mStorage.copyAndFireLoadTrigger(it.next());
+ if (!hasNext()) {
+ close();
+ }
+ return next;
+ } catch (ConcurrentModificationException e) {
+ close();
+ throw new FetchException(e);
+ } catch (Error e) {
+ try {
+ close();
+ } catch (Error e2) {
+ // Ignore.
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public int skipNext(int amount) throws FetchException {
+ if (amount <= 0) {
+ if (amount < 0) {
+ throw new IllegalArgumentException("Cannot skip negative amount: " + amount);
+ }
+ return 0;
+ }
+
+ // Skip over entries without copying them.
+
+ int count = 0;
+ Iterator<S> it = mIterator;
+
+ if (it != null) {
+ try {
+ while (--amount >= 0 && it.hasNext()) {
+ it.next();
+ count++;
+ }
+ } catch (ConcurrentModificationException e) {
+ close();
+ throw new FetchException(e);
+ } catch (Error e) {
+ try {
+ close();
+ } catch (Error e2) {
+ // Ignore.
+ }
+ throw e;
+ }
+ }
+
+ return count;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/MapRepository.java b/src/main/java/com/amazon/carbonado/repo/map/MapRepository.java new file mode 100644 index 0000000..2e82f89 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/MapRepository.java @@ -0,0 +1,125 @@ +/*
+ * Copyright 2008 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.atomic.AtomicReference;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.TriggerFactory;
+
+import com.amazon.carbonado.capability.IndexInfo;
+import com.amazon.carbonado.capability.IndexInfoCapability;
+
+import com.amazon.carbonado.sequence.SequenceValueGenerator;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
+
+import com.amazon.carbonado.qe.RepositoryAccess;
+import com.amazon.carbonado.qe.StorageAccess;
+
+import com.amazon.carbonado.spi.AbstractRepository;
+import com.amazon.carbonado.spi.LobEngine;
+import com.amazon.carbonado.spi.TransactionManager;
+import com.amazon.carbonado.spi.TransactionScope;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ * @see MapRepositoryBuilder
+ */
+class MapRepository extends AbstractRepository<MapTransaction>
+ implements RepositoryAccess, IndexInfoCapability
+{
+ private final AtomicReference<Repository> mRootRef;
+ private final boolean mIsMaster;
+ private final int mLockTimeout;
+ private final TimeUnit mLockTimeoutUnit;
+
+ final Iterable<TriggerFactory> mTriggerFactories;
+ private final MapTransactionManager mTxnManager;
+ private LobEngine mLobEngine;
+
+ MapRepository(AtomicReference<Repository> rootRef, MapRepositoryBuilder builder) {
+ super(builder.getName());
+ mRootRef = rootRef;
+ mIsMaster = builder.isMaster();
+ mLockTimeout = builder.getLockTimeout();
+ mLockTimeoutUnit = builder.getLockTimeoutUnit();
+
+ mTriggerFactories = builder.getTriggerFactories();
+ mTxnManager = new MapTransactionManager(mLockTimeout, mLockTimeoutUnit);
+ }
+
+ public Repository getRootRepository() {
+ return mRootRef.get();
+ }
+
+ public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type)
+ throws RepositoryException
+ {
+ return (StorageAccess<S>) storageFor(type);
+ }
+
+ public <S extends Storable> IndexInfo[] getIndexInfo(Class<S> storableType)
+ throws RepositoryException
+ {
+ return ((MapStorage) storageFor(storableType)).getIndexInfo();
+ }
+
+ protected Log getLog() {
+ return null;
+ }
+
+ protected TransactionManager<MapTransaction> transactionManager() {
+ return mTxnManager;
+ }
+
+ protected TransactionScope<MapTransaction> localTransactionScope() {
+ return mTxnManager.localScope();
+ }
+
+ protected <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws RepositoryException
+ {
+ return new MapStorage<S>(this, type, mLockTimeout, mLockTimeoutUnit);
+ }
+
+ protected SequenceValueProducer createSequenceValueProducer(String name)
+ throws RepositoryException
+ {
+ return new SequenceValueGenerator(this, name);
+ }
+
+ LobEngine getLobEngine() throws RepositoryException {
+ if (mLobEngine == null) {
+ mLobEngine = new LobEngine(this, getRootRepository());
+ }
+ return mLobEngine;
+ }
+
+ boolean isMaster() {
+ return mIsMaster;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/MapRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/map/MapRepositoryBuilder.java new file mode 100644 index 0000000..8d5d399 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/MapRepositoryBuilder.java @@ -0,0 +1,151 @@ +/*
+ * Copyright 2008 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.atomic.AtomicReference;
+import java.util.concurrent.TimeUnit;
+
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
+
+import com.amazon.carbonado.repo.indexed.IndexedRepositoryBuilder;
+
+import com.amazon.carbonado.spi.AbstractRepositoryBuilder;
+
+/**
+ * Volatile repository implementation backed by a concurrent map. Locks used by
+ * repository are coarse, much like <i>table locks</i>. Loads and queries
+ * acquire read locks, and modifications acquire write locks. Within
+ * transactions, loads and queries always acquire upgradable locks, to reduce
+ * the likelihood of deadlock.
+ *
+ * <p>This repository supports transactions, which also may be
+ * nested. Supported isolation levels are read committed and serializable. Read
+ * uncommitted is promoted to read committed, and repeatable read is promoted
+ * to serializable.
+ *
+ * <p>
+ * The following extra capabilities are supported:
+ * <ul>
+ * <li>{@link com.amazon.carbonado.capability.IndexInfoCapability IndexInfoCapability}
+ * <li>{@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability}
+ * <li>{@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability}
+ * </ul>
+ *
+ * <p>Note: This repository uses concurrent navigable map classes, which became
+ * available in JDK1.6.
+ *
+ * @author Brian S O'Neill
+ * @since 1.2
+ */
+public class MapRepositoryBuilder extends AbstractRepositoryBuilder {
+ /**
+ * Convenience method to build a new MapRepository.
+ */
+ public static Repository newRepository() {
+ try {
+ MapRepositoryBuilder builder = new MapRepositoryBuilder();
+ return builder.build();
+ } catch (RepositoryException e) {
+ // Not expected.
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String mName = "";
+ private boolean mIsMaster = true;
+ private boolean mIndexSupport = true;
+ private int mLockTimeout;
+ private TimeUnit mLockTimeoutUnit;
+
+ public MapRepositoryBuilder() {
+ setLockTimeoutMillis(500);
+ }
+
+ public Repository build(AtomicReference<Repository> rootRef) throws RepositoryException {
+ if (mIndexSupport) {
+ // Temporarily set to false to avoid infinite recursion.
+ mIndexSupport = false;
+ try {
+ IndexedRepositoryBuilder ixBuilder = new IndexedRepositoryBuilder();
+ ixBuilder.setWrappedRepository(this);
+ ixBuilder.setMaster(isMaster());
+ ixBuilder.setAllClustered(true);
+ return ixBuilder.build(rootRef);
+ } finally {
+ mIndexSupport = true;
+ }
+ }
+
+ assertReady();
+
+ Repository repo = new MapRepository(rootRef, this);
+
+ rootRef.set(repo);
+ return repo;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public boolean isMaster() {
+ return mIsMaster;
+ }
+
+ public void setMaster(boolean b) {
+ mIsMaster = b;
+ }
+
+ /**
+ * Set the lock timeout, in milliseconds. Default value is 500 milliseconds.
+ */
+ public void setLockTimeoutMillis(int timeout) {
+ setLockTimeout(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Set the lock timeout. Default value is 500 milliseconds.
+ */
+ public void setLockTimeout(int timeout, TimeUnit unit) {
+ if (timeout < 0 || unit == null) {
+ throw new IllegalArgumentException();
+ }
+ mLockTimeout = timeout;
+ mLockTimeoutUnit = unit;
+ }
+
+ /**
+ * Returns the lock timeout. Call getLockTimeoutUnit to get the unit.
+ */
+ public int getLockTimeout() {
+ return mLockTimeout;
+ }
+
+ /**
+ * Returns the lock timeout unit. Call getLockTimeout to get the timeout.
+ */
+ public TimeUnit getLockTimeoutUnit() {
+ return mLockTimeoutUnit;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/MapStorage.java b/src/main/java/com/amazon/carbonado/repo/map/MapStorage.java new file mode 100644 index 0000000..520d18c --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/MapStorage.java @@ -0,0 +1,813 @@ +/*
+ * Copyright 2008 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.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Set;
+
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.TimeUnit;
+
+import org.cojen.classfile.TypeDesc;
+
+import com.amazon.carbonado.Cursor;
+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.Query;
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.Trigger;
+
+import com.amazon.carbonado.capability.IndexInfo;
+
+import com.amazon.carbonado.cursor.ArraySortBuffer;
+import com.amazon.carbonado.cursor.EmptyCursor;
+import com.amazon.carbonado.cursor.FilteredCursor;
+import com.amazon.carbonado.cursor.SingletonCursor;
+import com.amazon.carbonado.cursor.SortBuffer;
+import com.amazon.carbonado.cursor.SortedCursor;
+
+import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.filter.FilterValues;
+import com.amazon.carbonado.filter.RelOp;
+
+import com.amazon.carbonado.sequence.SequenceValueProducer;
+
+import com.amazon.carbonado.gen.DelegateStorableGenerator;
+import com.amazon.carbonado.gen.DelegateSupport;
+import com.amazon.carbonado.gen.MasterFeature;
+
+import com.amazon.carbonado.util.QuickConstructorGenerator;
+
+import com.amazon.carbonado.filter.Filter;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.Direction;
+import com.amazon.carbonado.info.OrderedProperty;
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.info.StorableInfo;
+import com.amazon.carbonado.info.StorableIntrospector;
+import com.amazon.carbonado.info.StorableProperty;
+
+import com.amazon.carbonado.qe.BoundaryType;
+import com.amazon.carbonado.qe.QueryExecutorFactory;
+import com.amazon.carbonado.qe.QueryEngine;
+import com.amazon.carbonado.qe.StorageAccess;
+
+import com.amazon.carbonado.spi.IndexInfoImpl;
+import com.amazon.carbonado.spi.LobEngine;
+import com.amazon.carbonado.spi.TransactionScope;
+import com.amazon.carbonado.spi.TriggerManager;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+class MapStorage<S extends Storable>
+ implements Storage<S>, DelegateSupport<S>, StorageAccess<S>
+{
+ private static final int DEFAULT_LOB_BLOCK_SIZE = 1000;
+ private static final Object[] NO_VALUES = new Object[0];
+
+ private final MapRepository mRepo;
+ private final StorableInfo<S> mInfo;
+ private final TriggerManager<S> mTriggers;
+ private final InstanceFactory mInstanceFactory;
+ private final StorableIndex<S> mPrimaryKeyIndex;
+ private final QueryEngine<S> mQueryEngine;
+
+ private final int mLockTimeout;
+ private final TimeUnit mLockTimeoutUnit;
+
+ private final ConcurrentNavigableMap<Key<S>, S> mMap;
+ private final Comparator<S> mFullComparator;
+ private final Comparator<S>[] mSearchComparators;
+
+ private final Key.Assigner<S> mKeyAssigner;
+
+ /**
+ * Simple lock which is reentrant for transactions, but auto-commit does not
+ * need to support reentrancy. Read lock requests in transactions can starve
+ * write lock requests, but auto-commit cannot cause starvation. In practice
+ * starvation is not possible since transactions always lock for upgrade.
+ */
+ final UpgradableLock<Object> mLock = new UpgradableLock<Object>() {
+ @Override
+ protected boolean isReadLockHeld(Object locker) {
+ return locker instanceof MapTransaction;
+ }
+ };
+
+ MapStorage(MapRepository repo, Class<S> type, int lockTimeout, TimeUnit lockTimeoutUnit)
+ throws SupportException
+ {
+ mRepo = repo;
+ mInfo = StorableIntrospector.examine(type);
+ mTriggers = new TriggerManager<S>();
+
+ EnumSet<MasterFeature> features;
+ if (repo.isMaster()) {
+ features = EnumSet.of(MasterFeature.INSERT_CHECK_REQUIRED,
+ MasterFeature.VERSIONING,
+ MasterFeature.INSERT_SEQUENCES);
+ } else {
+ features = EnumSet.of(MasterFeature.INSERT_CHECK_REQUIRED);
+ }
+
+ Class<? extends S> delegateStorableClass =
+ DelegateStorableGenerator.getDelegateClass(type, features);
+
+ mInstanceFactory = QuickConstructorGenerator
+ .getInstance(delegateStorableClass, InstanceFactory.class);
+
+ mPrimaryKeyIndex =
+ new StorableIndex<S>(mInfo.getPrimaryKey(), Direction.ASCENDING).clustered(true);
+
+ mQueryEngine = new QueryEngine<S>(type, repo);
+
+ mLockTimeout = lockTimeout;
+ mLockTimeoutUnit = lockTimeoutUnit;
+
+ mMap = new ConcurrentSkipListMap<Key<S>, S>();
+ List<OrderedProperty<S>> propList = createPkPropList();
+ mFullComparator = SortedCursor.createComparator(propList);
+ mSearchComparators = new Comparator[propList.size() + 1];
+ mSearchComparators[propList.size()] = mFullComparator;
+
+ mKeyAssigner = Key.getAssigner(type);
+
+ try {
+ if (LobEngine.hasLobs(type)) {
+ Trigger<S> lobTrigger = repo.getLobEngine()
+ .getSupportTrigger(type, DEFAULT_LOB_BLOCK_SIZE);
+ addTrigger(lobTrigger);
+ }
+
+ // Don't install automatic triggers until we're completely ready.
+ mTriggers.addTriggers(type, repo.mTriggerFactories);
+ } catch (SupportException e) {
+ throw e;
+ } catch (RepositoryException e) {
+ throw new SupportException(e);
+ }
+ }
+
+ public Class<S> getStorableType() {
+ return mInfo.getStorableType();
+ }
+
+ public S prepare() {
+ return (S) mInstanceFactory.instantiate(this);
+ }
+
+ public Query<S> query() throws FetchException {
+ return mQueryEngine.query();
+ }
+
+ public Query<S> query(String filter) throws FetchException {
+ return mQueryEngine.query(filter);
+ }
+
+ public Query<S> query(Filter<S> filter) throws FetchException {
+ return mQueryEngine.query(filter);
+ }
+
+ public void truncate() throws PersistException {
+ try {
+ Object locker = mRepo.localTransactionScope().getTxn();
+ if (locker == null) {
+ locker = Thread.currentThread();
+ }
+ doLockForWrite(locker);
+ try {
+ mMap.clear();
+ } finally {
+ mLock.unlockFromWrite(locker);
+ }
+ } catch (PersistException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new PersistException(e);
+ }
+ }
+
+ public boolean addTrigger(Trigger<? super S> trigger) {
+ return mTriggers.addTrigger(trigger);
+ }
+
+ public boolean removeTrigger(Trigger<? super S> trigger) {
+ return mTriggers.removeTrigger(trigger);
+ }
+
+ public IndexInfo[] getIndexInfo() {
+ StorableIndex<S> pkIndex = mPrimaryKeyIndex;
+
+ if (pkIndex == null) {
+ return new IndexInfo[0];
+ }
+
+ int i = pkIndex.getPropertyCount();
+ String[] propertyNames = new String[i];
+ Direction[] directions = new Direction[i];
+ while (--i >= 0) {
+ propertyNames[i] = pkIndex.getProperty(i).getName();
+ directions[i] = pkIndex.getPropertyDirection(i);
+ }
+
+ return new IndexInfo[] {
+ new IndexInfoImpl(getStorableType().getName(), true, true, propertyNames, directions)
+ };
+ }
+
+ public boolean doTryLoad(S storable) throws FetchException {
+ try {
+ TransactionScope<MapTransaction> scope = mRepo.localTransactionScope();
+ MapTransaction txn = scope.getTxn();
+ if (txn == null) {
+ doLockForRead(scope);
+ try {
+ return doTryLoadNoLock(storable);
+ } finally {
+ mLock.unlockFromRead(scope);
+ }
+ } else {
+ // Since lock is so coarse, all reads in transaction scope are
+ // upgrade to avoid deadlocks.
+ final boolean isForUpdate = scope.isForUpdate();
+ txn.lockForUpgrade(mLock, isForUpdate);
+ try {
+ return doTryLoadNoLock(storable);
+ } finally {
+ txn.unlockFromUpgrade(mLock, isForUpdate);
+ }
+ }
+ } catch (FetchException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new FetchException(e);
+ }
+ }
+
+ // Caller must hold lock.
+ boolean doTryLoadNoLock(S storable) {
+ S existing = mMap.get(new Key<S>(storable, mFullComparator));
+ if (existing == null) {
+ return false;
+ } else {
+ storable.markAllPropertiesDirty();
+ existing.copyAllProperties(storable);
+ storable.markAllPropertiesClean();
+ return true;
+ }
+ }
+
+ public boolean doTryInsert(S storable) throws PersistException {
+ try {
+ TransactionScope<MapTransaction> scope = mRepo.localTransactionScope();
+ MapTransaction txn = scope.getTxn();
+ if (txn == null) {
+ // No need to acquire full write lock since map is concurrent
+ // and existing storable (if any) is not being
+ // modified. Upgrade lock is required because a concurrent
+ // transaction might be in progress, and so insert should wait.
+ doLockForUpgrade(scope);
+ try {
+ return doTryInsertNoLock(storable);
+ } finally {
+ mLock.unlockFromUpgrade(scope);
+ }
+ } else {
+ txn.lockForWrite(mLock);
+ if (doTryInsertNoLock(storable)) {
+ txn.inserted(this, storable);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } catch (PersistException e) {
+ throw e;
+ } catch (FetchException e) {
+ throw e.toPersistException();
+ } catch (Exception e) {
+ throw new PersistException(e);
+ }
+ }
+
+ // Caller must hold upgrade or write lock.
+ private boolean doTryInsertNoLock(S storable) {
+ Key<S> key = new Key<S>(storable, mFullComparator);
+ S existing = mMap.get(key);
+ if (existing != null) {
+ return false;
+ }
+ // Create a fresh copy to ensure that custom fields are not saved.
+ S copy = (S) storable.prepare();
+ storable.copyAllProperties(copy);
+ copy.markAllPropertiesClean();
+ mMap.put(key, copy);
+ storable.markAllPropertiesClean();
+ return true;
+ }
+
+ public boolean doTryUpdate(S storable) throws PersistException {
+ try {
+ TransactionScope<MapTransaction> scope = mRepo.localTransactionScope();
+ MapTransaction txn = scope.getTxn();
+ if (txn == null) {
+ // Full write lock is required since existing storable is being
+ // modified. Readers cannot be allowed to see modifications
+ // until they are complete. In addtion, a concurrent
+ // transaction might be in progress, and so update should wait.
+ doLockForWrite(scope);
+ try {
+ return doTryUpdateNoLock(storable);
+ } finally {
+ mLock.unlockFromWrite(scope);
+ }
+ } else {
+ txn.lockForWrite(mLock);
+ S existing = mMap.get(new Key<S>(storable, mFullComparator));
+ if (existing == null) {
+ return false;
+ } else {
+ // Copy existing object to undo log.
+ txn.updated(this, (S) existing.copy());
+
+ // Copy altered values to existing object.
+ existing.markAllPropertiesDirty();
+ storable.copyDirtyProperties(existing);
+ existing.markAllPropertiesClean();
+
+ // Copy all values to user object, to simulate a reload.
+ storable.markAllPropertiesDirty();
+ existing.copyAllProperties(storable);
+ storable.markAllPropertiesClean();
+
+ return true;
+ }
+ }
+ } catch (PersistException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new PersistException(e);
+ }
+ }
+
+ // Caller must hold write lock.
+ private boolean doTryUpdateNoLock(S storable) {
+ S existing = mMap.get(new Key<S>(storable, mFullComparator));
+ if (existing == null) {
+ return false;
+ } else {
+ // Copy altered values to existing object.
+ existing.markAllPropertiesDirty();
+ storable.copyDirtyProperties(existing);
+ existing.markAllPropertiesClean();
+
+ // Copy all values to user object, to simulate a reload.
+ storable.markAllPropertiesDirty();
+ existing.copyAllProperties(storable);
+ storable.markAllPropertiesClean();
+
+ return true;
+ }
+ }
+
+ public boolean doTryDelete(S storable) throws PersistException {
+ try {
+ TransactionScope<MapTransaction> scope = mRepo.localTransactionScope();
+ MapTransaction txn = scope.getTxn();
+ if (txn == null) {
+ // No need to acquire full write lock since map is concurrent
+ // and existing storable (if any) is not being
+ // modified. Upgrade lock is required because a concurrent
+ // transaction might be in progress, and so delete should wait.
+ doLockForUpgrade(scope);
+ try {
+ return doTryDeleteNoLock(storable);
+ } finally {
+ mLock.unlockFromUpgrade(scope);
+ }
+ } else {
+ txn.lockForWrite(mLock);
+ S existing = mMap.remove(new Key<S>(storable, mFullComparator));
+ if (existing == null) {
+ return false;
+ } else {
+ txn.deleted(this, existing);
+ return true;
+ }
+ }
+ } catch (PersistException e) {
+ throw e;
+ } catch (FetchException e) {
+ throw e.toPersistException();
+ } catch (Exception e) {
+ throw new PersistException(e);
+ }
+ }
+
+ // Caller must hold upgrade or write lock.
+ private boolean doTryDeleteNoLock(S storable) {
+ return mMap.remove(new Key<S>(storable, mFullComparator)) != null;
+ }
+
+ // Called by MapTransaction, which implicitly holds lock.
+ void mapPut(S storable) {
+ mMap.put(new Key<S>(storable, mFullComparator), storable);
+ }
+
+ // Called by MapTransaction, which implicitly holds lock.
+ void mapRemove(S storable) {
+ mMap.remove(new Key<S>(storable, mFullComparator));
+ }
+
+ private void doLockForRead(Object locker) throws FetchException {
+ try {
+ if (!mLock.tryLockForRead(locker, mLockTimeout, mLockTimeoutUnit)) {
+ throw new FetchTimeoutException("" + mLockTimeout + ' ' +
+ mLockTimeoutUnit.toString().toLowerCase());
+ }
+ } catch (InterruptedException e) {
+ throw new FetchInterruptedException(e);
+ }
+ }
+
+ private void doLockForUpgrade(Object locker) throws FetchException {
+ try {
+ if (!mLock.tryLockForUpgrade(locker, mLockTimeout, mLockTimeoutUnit)) {
+ throw new FetchTimeoutException("" + mLockTimeout + ' ' +
+ mLockTimeoutUnit.toString().toLowerCase());
+ }
+ } catch (InterruptedException e) {
+ throw new FetchInterruptedException(e);
+ }
+ }
+
+ private void doLockForWrite(Object locker) throws PersistException {
+ try {
+ if (!mLock.tryLockForWrite(locker, mLockTimeout, mLockTimeoutUnit)) {
+ throw new PersistTimeoutException("" + mLockTimeout + ' ' +
+ mLockTimeoutUnit.toString().toLowerCase());
+ }
+ } catch (InterruptedException e) {
+ throw new PersistInterruptedException(e);
+ }
+ }
+
+ public Repository getRootRepository() {
+ return mRepo.getRootRepository();
+ }
+
+ public boolean isPropertySupported(String propertyName) {
+ return mInfo.getAllProperties().containsKey(propertyName);
+ }
+
+ public Trigger<? super S> getInsertTrigger() {
+ return mTriggers.getInsertTrigger();
+ }
+
+ public Trigger<? super S> getUpdateTrigger() {
+ return mTriggers.getUpdateTrigger();
+ }
+
+ public Trigger<? super S> getDeleteTrigger() {
+ return mTriggers.getDeleteTrigger();
+ }
+
+ public Trigger<? super S> getLoadTrigger() {
+ return mTriggers.getLoadTrigger();
+ }
+
+ public void locallyDisableLoadTrigger() {
+ mTriggers.locallyDisableLoad();
+ }
+
+ public void locallyEnableLoadTrigger() {
+ mTriggers.locallyEnableLoad();
+ }
+
+ public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException {
+ try {
+ return mRepo.getSequenceValueProducer(name);
+ } catch (RepositoryException e) {
+ throw e.toPersistException();
+ }
+ }
+
+ public QueryExecutorFactory<S> getQueryExecutorFactory() {
+ return mQueryEngine;
+ }
+
+ public Collection<StorableIndex<S>> getAllIndexes() {
+ return Collections.singletonList(mPrimaryKeyIndex);
+ }
+
+ public Storage<S> storageDelegate(StorableIndex<S> index) {
+ // We're the grunt and don't delegate.
+ return null;
+ }
+
+ public long countAll() throws FetchException {
+ try {
+ Object locker = mRepo.localTransactionScope().getTxn();
+ if (locker == null) {
+ locker = Thread.currentThread();
+ }
+ doLockForRead(locker);
+ try {
+ return mMap.size();
+ } finally {
+ mLock.unlockFromRead(locker);
+ }
+ } catch (FetchException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new FetchException(e);
+ }
+ }
+
+ public Cursor<S> fetchAll() throws FetchException {
+ try {
+ return new MapCursor<S>(this, mRepo.localTransactionScope(), mMap.values());
+ } catch (FetchException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new FetchException(e);
+ }
+ }
+
+ public Cursor<S> fetchOne(StorableIndex<S> index, Object[] identityValues)
+ throws FetchException
+ {
+ try {
+ S key = prepare();
+ for (int i=0; i<identityValues.length; i++) {
+ key.setPropertyValue(index.getProperty(i).getName(), identityValues[i]);
+ }
+
+ TransactionScope<MapTransaction> scope = mRepo.localTransactionScope();
+ MapTransaction txn = scope.getTxn();
+ if (txn == null) {
+ doLockForRead(scope);
+ try {
+ S value = mMap.get(new Key<S>(key, mFullComparator));
+ if (value == null) {
+ return EmptyCursor.the();
+ } else {
+ return new SingletonCursor<S>(copyAndFireLoadTrigger(value));
+ }
+ } finally {
+ mLock.unlockFromRead(scope);
+ }
+ } else {
+ // Since lock is so coarse, all reads in transaction scope are
+ // upgrade to avoid deadlocks.
+ final boolean isForUpdate = scope.isForUpdate();
+ txn.lockForUpgrade(mLock, isForUpdate);
+ try {
+ S value = mMap.get(new Key<S>(key, mFullComparator));
+ if (value == null) {
+ return EmptyCursor.the();
+ } else {
+ return new SingletonCursor<S>(copyAndFireLoadTrigger(value));
+ }
+ } finally {
+ txn.unlockFromUpgrade(mLock, isForUpdate);
+ }
+ }
+ } catch (FetchException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new FetchException(e);
+ }
+ }
+
+ S copyAndFireLoadTrigger(S storable) throws FetchException {
+ storable = (S) storable.copy();
+ Trigger<? super S> trigger = getLoadTrigger();
+ if (trigger != null) {
+ trigger.afterLoad(storable);
+ // In case trigger modified the properties, make sure they're still clean.
+ storable.markAllPropertiesClean();
+ }
+ return storable;
+ }
+
+ public Query<?> indexEntryQuery(StorableIndex<S> index) {
+ return null;
+ }
+
+ public Cursor<S> fetchFromIndexEntryQuery(StorableIndex<S> index, Query<?> indexEntryQuery) {
+ return null;
+ }
+
+ public Cursor<S> fetchSubset(StorableIndex<S> index,
+ Object[] identityValues,
+ BoundaryType rangeStartBoundary,
+ Object rangeStartValue,
+ BoundaryType rangeEndBoundary,
+ Object rangeEndValue,
+ boolean reverseRange,
+ boolean reverseOrder)
+ throws FetchException
+ {
+ if (identityValues == null) {
+ identityValues = NO_VALUES;
+ }
+
+ NavigableMap<Key<S>, S> map = mMap;
+
+ int tieBreaker = 1;
+ if (reverseOrder) {
+ map = map.descendingMap();
+ reverseRange = !reverseRange;
+ tieBreaker = -tieBreaker;
+ }
+
+ if (reverseRange) {
+ BoundaryType t1 = rangeStartBoundary;
+ rangeStartBoundary = rangeEndBoundary;
+ rangeEndBoundary = t1;
+ Object t2 = rangeStartValue;
+ rangeStartValue = rangeEndValue;
+ rangeEndValue = t2;
+ }
+
+ tail: {
+ Key<S> startKey;
+ switch (rangeStartBoundary) {
+ case OPEN: default:
+ if (identityValues.length == 0) {
+ break tail;
+ } else {
+ // Tie breaker of -1 puts search key right before first actual
+ // match, thus forming an inclusive start match.
+ startKey = searchKey(-tieBreaker, identityValues);
+ }
+ break;
+ case INCLUSIVE:
+ // Tie breaker of -1 puts search key right before first actual
+ // match, thus forming an inclusive start match.
+ startKey = searchKey(-tieBreaker, identityValues, rangeStartValue);
+ break;
+ case EXCLUSIVE:
+ // Tie breaker of +1 puts search key right after first actual
+ // match, thus forming an exlusive start match.
+ startKey = searchKey(tieBreaker, identityValues, rangeStartValue);
+ break;
+ }
+
+ map = map.tailMap(startKey, true);
+ }
+
+ Cursor<S> cursor;
+ try {
+ cursor = new MapCursor<S>(this, mRepo.localTransactionScope(), map.values());
+ } catch (FetchException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new FetchException(e);
+ }
+
+ // Use filter to stop cursor at desired ending position.
+
+ // FIXME: Let query engine do this so that filter can be
+ // cached. Somehow indicate this at a high level so that query plan
+ // shows a filter.
+ Filter<S> filter;
+ FilterValues<S> filterValues;
+
+ if (rangeEndBoundary == BoundaryType.OPEN) {
+ if (identityValues.length == 0) {
+ filter = null;
+ filterValues = null;
+ } else {
+ filter = Filter.getOpenFilter(getStorableType());
+ for (int i=0; i<identityValues.length; i++) {
+ filter = filter.and(index.getProperty(i).getName(), RelOp.EQ);
+ }
+ filterValues = filter.initialFilterValues();
+ for (int i=0; i<identityValues.length; i++) {
+ filterValues = filterValues.with(identityValues[i]);
+ }
+ }
+ } else {
+ filter = Filter.getOpenFilter(getStorableType());
+ int i = 0;
+ for (; i<identityValues.length; i++) {
+ filter = filter.and(index.getProperty(i).getName(), RelOp.EQ);
+ }
+
+ filter = filter.and(index.getProperty(i).getName(),
+ rangeEndBoundary == BoundaryType.INCLUSIVE ? RelOp.LE : RelOp.LT);
+
+ filterValues = filter.initialFilterValues();
+
+ for (i=0; i<identityValues.length; i++) {
+ filterValues = filterValues.with(identityValues[i]);
+ }
+
+ filterValues = filterValues.with(rangeEndValue);
+ }
+
+ if (filter != null) {
+ cursor = FilteredCursor.applyFilter(filter, filterValues, cursor);
+ }
+
+ return cursor;
+ }
+
+ private List<OrderedProperty<S>> createPkPropList() {
+ return new ArrayList<OrderedProperty<S>>(mInfo.getPrimaryKey().getProperties());
+ }
+
+ private Key<S> searchKey(int tieBreaker, Object[] identityValues) {
+ S storable = prepare();
+ mKeyAssigner.setKeyValues(storable, identityValues);
+ Comparator<S> c = getSearchComparator(identityValues.length);
+ return new SearchKey<S>(tieBreaker, storable, c);
+ }
+
+ private Key<S> searchKey(int tieBreaker, Object[] identityValues, Object rangeValue) {
+ S storable = prepare();
+ mKeyAssigner.setKeyValues(storable, identityValues, rangeValue);
+ Comparator<S> c = getSearchComparator(identityValues.length + 1);
+ return new SearchKey<S>(tieBreaker, storable, c);
+ }
+
+ private Comparator<S> getSearchComparator(int propertyCount) {
+ Comparator<S> comparator = mSearchComparators[propertyCount];
+ if (comparator == null) {
+ List<OrderedProperty<S>> propList = createPkPropList().subList(0, propertyCount);
+ if (propList.size() > 0) {
+ comparator = SortedCursor.createComparator(propList);
+ } else {
+ comparator = SortedCursor.createComparator(getStorableType());
+ }
+ mSearchComparators[propertyCount] = comparator;
+ }
+ return comparator;
+ }
+
+ public SortBuffer<S> createSortBuffer() {
+ return new ArraySortBuffer<S>();
+ }
+
+ public static interface InstanceFactory {
+ Storable instantiate(DelegateSupport support);
+ }
+
+ private static class SearchKey<S extends Storable> extends Key<S> {
+ private final int mTieBreaker;
+
+ SearchKey(int tieBreaker, S storable, Comparator<S> comparator) {
+ super(storable, comparator);
+ mTieBreaker = tieBreaker;
+ }
+
+ @Override
+ protected int tieBreaker() {
+ return mTieBreaker;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ", tieBreaker=" + mTieBreaker;
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/MapTransaction.java b/src/main/java/com/amazon/carbonado/repo/map/MapTransaction.java new file mode 100644 index 0000000..d2cfe87 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/MapTransaction.java @@ -0,0 +1,240 @@ +/*
+ * Copyright 2008 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;
+import com.amazon.carbonado.Storage;
+
+/**
+ *
+ *
+ * @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);
+ }
+ });
+ }
+
+ /**
+ * 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);
+ }
+ });
+ }
+
+ /**
+ * 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);
+ }
+ });
+ }
+
+ 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();
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/MapTransactionManager.java b/src/main/java/com/amazon/carbonado/repo/map/MapTransactionManager.java new file mode 100644 index 0000000..5598353 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/MapTransactionManager.java @@ -0,0 +1,98 @@ +/*
+ * Copyright 2008 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 com.amazon.carbonado.IsolationLevel;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Transaction;
+
+import com.amazon.carbonado.spi.TransactionManager;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+class MapTransactionManager extends TransactionManager<MapTransaction> {
+ private final int mLockTimeout;
+ private final TimeUnit mLockTimeoutUnit;
+
+ MapTransactionManager(int lockTimeout, TimeUnit lockTimeoutUnit) {
+ mLockTimeout = lockTimeout;
+ mLockTimeoutUnit = lockTimeoutUnit;
+ }
+
+ protected IsolationLevel selectIsolationLevel(Transaction parent, IsolationLevel level) {
+ if (level == null) {
+ if (parent == null) {
+ return IsolationLevel.READ_COMMITTED;
+ }
+ return parent.getIsolationLevel();
+ }
+
+ switch (level) {
+ case NONE:
+ return IsolationLevel.NONE;
+ case READ_UNCOMMITTED:
+ case READ_COMMITTED:
+ return IsolationLevel.READ_COMMITTED;
+ case REPEATABLE_READ:
+ case SERIALIZABLE:
+ return IsolationLevel.SERIALIZABLE;
+ default:
+ // Not supported.
+ return null;
+ }
+ }
+
+ protected boolean supportsForUpdate() {
+ return true;
+ }
+
+ protected MapTransaction createTxn(MapTransaction parent, IsolationLevel level)
+ throws Exception
+ {
+ if (level == IsolationLevel.NONE) {
+ return null;
+ }
+ return new MapTransaction(parent, level, mLockTimeout, mLockTimeoutUnit);
+ }
+
+ @Override
+ protected MapTransaction createTxn(MapTransaction parent, IsolationLevel level,
+ int timeout, TimeUnit unit)
+ throws Exception
+ {
+ if (level == IsolationLevel.NONE) {
+ return null;
+ }
+ return new MapTransaction(parent, level, timeout, unit);
+ }
+
+ protected boolean commitTxn(MapTransaction txn) throws PersistException {
+ txn.commit();
+ return false;
+ }
+
+ protected void abortTxn(MapTransaction txn) throws PersistException {
+ txn.abort();
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/UpgradableLock.java b/src/main/java/com/amazon/carbonado/repo/map/UpgradableLock.java new file mode 100644 index 0000000..9862791 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/UpgradableLock.java @@ -0,0 +1,1161 @@ +/*
+ * Copyright 2007 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.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+import java.util.concurrent.locks.LockSupport;
+
+/**
+ * Partially reentrant, upgradable read/write lock. Up to 1,073,741,824 read
+ * locks can be held. Upgraders and writers may re-enter the lock up to
+ * 2,147,483,648 times. Attempts by readers to re-enter the lock is not
+ * detected and is deadlock prone, unless locker already holds an upgrade or
+ * write lock. Subclasses can support full reentrancy by overriding the
+ * protected read lock adjust and hold check methods.
+ *
+ * <p>This lock implementation differs from the usual Java lock with respect to
+ * lock ownership. Locks are not owned by threads, but by arbitrary locker
+ * objects. A thread which attempts to acquire an upgrade or write lock twice
+ * with different locker objects will deadlock on the second attempt.
+ *
+ * <p>As is typical of read/write lock implementations, a read lock blocks
+ * waiting writers, but it doesn't block other readers. A write lock is
+ * exclusive and can be held by at most one locker. Attempting to acquire a
+ * write lock while a read lock is held by the same locker is inherently
+ * deadlock prone, and some read/write lock implementations will always
+ * deadlock.
+ *
+ * <p>An upgrade lock allows a read lock to be safely upgraded to a write
+ * lock. Instead of acquiring a read lock, an upgrade lock is acquired. This
+ * acts like a shared read lock in that readers are not blocked, but it also
+ * acts like an exclusive write lock -- writers and upgraders are blocked. With
+ * an upgrade lock held, the locker may acquire a write lock without deadlock.
+ *
+ * <pre>
+ * Locks held Locks safely Locks acquirable
+ * by owner acquirable by owner by other lockers
+ * --------------------------------------------------
+ * - - - R U W R U W
+ * R - - - - - R U -
+ * R U - - U - R - -
+ * - U - R U W R - -
+ * - U W R U W - - -
+ * R U W R U W - - -
+ * R - W R U W - - -
+ * - - W R U W - - -
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @param <L> Locker type
+ */
+class UpgradableLock<L> {
+ // Design note: This class borrows heavily from AbstractQueuedSynchronizer.
+ // Consult that class for understanding the locking mechanics.
+
+ private static enum Result {
+ /** Lock acquisition failed */
+ FAILED,
+ /** Lock has just been acquired by locker and can be safely released later */
+ ACQUIRED,
+ /** Lock is already owned by locker and should not be released more than once */
+ OWNED
+ }
+
+ private static final AtomicReferenceFieldUpdater<UpgradableLock, Node> cRWHeadRef =
+ AtomicReferenceFieldUpdater.newUpdater
+ (UpgradableLock.class, Node.class, "mRWHead");
+
+ private static final AtomicReferenceFieldUpdater<UpgradableLock, Node> cRWTailRef =
+ AtomicReferenceFieldUpdater.newUpdater
+ (UpgradableLock.class, Node.class, "mRWTail");
+
+ private static final AtomicReferenceFieldUpdater<UpgradableLock, Node> cUHeadRef =
+ AtomicReferenceFieldUpdater.newUpdater
+ (UpgradableLock.class, Node.class, "mUHead");
+
+ private static final AtomicReferenceFieldUpdater<UpgradableLock, Node> cUTailRef =
+ AtomicReferenceFieldUpdater.newUpdater
+ (UpgradableLock.class, Node.class, "mUTail");
+
+ private static final AtomicIntegerFieldUpdater<UpgradableLock> cStateRef =
+ AtomicIntegerFieldUpdater.newUpdater
+ (UpgradableLock.class, "mState");
+
+ // State mask bits for held locks. Read lock count is stored in lower 30 bits of state.
+ private static final int LOCK_STATE_UPGRADE = 0x40000000;
+ // Write state must be this value in order for quick sign check to work.
+ private static final int LOCK_STATE_WRITE = 0x80000000;
+
+ private static final int LOCK_STATE_MASK = LOCK_STATE_UPGRADE | LOCK_STATE_WRITE;
+
+ /**
+ * The number of nanoseconds for which it is faster to spin
+ * rather than to use timed park. A rough estimate suffices
+ * to improve responsiveness with very short timeouts.
+ */
+ private static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L;
+
+ // Head of read-write queue.
+ private transient volatile Node mRWHead;
+
+ // Tail of read-write queue.
+ private transient volatile Node mRWTail;
+
+ // Head of write upgrade queue.
+ private transient volatile Node mUHead;
+
+ // Tail of write upgrade queue.
+ private transient volatile Node mUTail;
+
+ private transient volatile int mState;
+
+ // Owner holds an upgradable lock and possibly a write lock too.
+ private transient L mOwner;
+
+ // Counts number of times that owner has entered an upgradable or write lock.
+ private transient int mUpgradeCount;
+ private transient int mWriteCount;
+
+ public UpgradableLock() {
+ }
+
+ /**
+ * Acquire a shared read lock, possibly blocking indefinitely.
+ *
+ * @param locker object which might be write or upgrade lock owner
+ */
+ public final void lockForRead(L locker) {
+ if (!tryLockForRead(locker)) {
+ lockForReadQueued(locker, addReadWaiter());
+ }
+ }
+
+ /**
+ * Acquire a shared read lock, possibly blocking until interrupted.
+ *
+ * @param locker object which might be write or upgrade lock owner
+ */
+ public final void lockForReadInterruptibly(L locker) throws InterruptedException {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ if (!tryLockForRead(locker)) {
+ lockForReadQueuedInterruptibly(locker, addReadWaiter());
+ }
+ }
+
+ /**
+ * Attempt to immediately acquire a shared read lock.
+ *
+ * @param locker object which might be write or upgrade lock owner
+ * @return true if acquired
+ */
+ public final boolean tryLockForRead(L locker) {
+ int state = mState;
+ if (state >= 0) { // no write lock is held
+ if (isReadWriteFirst() || isReadLockHeld(locker)) {
+ do {
+ if (incrementReadLocks(state)) {
+ adjustReadLockCount(locker, 1);
+ return true;
+ }
+ // keep looping on CAS failure if a reader or upgrader mucked with the state
+ } while ((state = mState) >= 0);
+ }
+ } else if (mOwner == locker) {
+ // keep looping on CAS failure if a reader or upgrader mucked with the state
+ while (!incrementReadLocks(state)) {
+ state = mState;
+ }
+ adjustReadLockCount(locker, 1);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempt to acquire a shared read lock, waiting a maximum amount of
+ * time.
+ *
+ * @param locker object which might be write or upgrade lock owner
+ * @return true if acquired
+ */
+ public final boolean tryLockForRead(L locker, long timeout, TimeUnit unit)
+ throws InterruptedException
+ {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ if (!tryLockForRead(locker)) {
+ return lockForReadQueuedInterruptibly(locker, addReadWaiter(), unit.toNanos(timeout));
+ }
+ return true;
+ }
+
+ /**
+ * Release a previously acquired read lock.
+ */
+ public final void unlockFromRead(L locker) {
+ adjustReadLockCount(locker, -1);
+
+ int readLocks;
+ while ((readLocks = decrementReadLocks(mState)) < 0) {}
+
+ if (readLocks == 0) {
+ Node h = mRWHead;
+ if (h != null && h.mWaitStatus != 0) {
+ unparkReadWriteSuccessor(h);
+ }
+ }
+ }
+
+ /**
+ * Acquire an upgrade lock, possibly blocking indefinitely.
+ *
+ * @param locker object trying to become lock owner
+ * @return true if acquired
+ */
+ public final boolean lockForUpgrade(L locker) {
+ return lockForUpgrade_(locker) != Result.FAILED;
+ }
+
+ /**
+ * Acquire an upgrade lock, possibly blocking indefinitely.
+ *
+ * @param locker object trying to become lock owner
+ * @return ACQUIRED or OWNED
+ */
+ private final Result lockForUpgrade_(L locker) {
+ Result result;
+ if ((result = tryLockForUpgrade_(locker)) == Result.FAILED) {
+ result = lockForUpgradeQueued(locker, addUpgradeWaiter());
+ }
+ return result;
+ }
+
+ /**
+ * Acquire an upgrade lock, possibly blocking until interrupted.
+ *
+ * @param locker object trying to become lock owner
+ * @return true if acquired
+ */
+ public final boolean lockForUpgradeInterruptibly(L locker) throws InterruptedException {
+ return lockForUpgradeInterruptibly_(locker) != Result.FAILED;
+ }
+
+ /**
+ * Acquire an upgrade lock, possibly blocking until interrupted.
+ *
+ * @param locker object trying to become lock owner
+ * @return ACQUIRED or OWNED
+ */
+ private final Result lockForUpgradeInterruptibly_(L locker) throws InterruptedException {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ Result result;
+ if ((result = tryLockForUpgrade_(locker)) == Result.FAILED) {
+ result = lockForUpgradeQueuedInterruptibly(locker, addUpgradeWaiter());
+ }
+ return result;
+ }
+
+ /**
+ * Attempt to immediately acquire an upgrade lock.
+ *
+ * @param locker object trying to become lock owner
+ * @return true if acquired
+ */
+ public final boolean tryLockForUpgrade(L locker) {
+ return tryLockForUpgrade_(locker) != Result.FAILED;
+ }
+
+ /**
+ * Attempt to immediately acquire an upgrade lock.
+ *
+ * @param locker object trying to become lock owner
+ * @return FAILED, ACQUIRED or OWNED
+ */
+ private final Result tryLockForUpgrade_(L locker) {
+ int state = mState;
+ if ((state & LOCK_STATE_MASK) == 0) { // no write or upgrade lock is held
+ if (isUpgradeFirst()) {
+ do {
+ if (setUpgradeLock(state)) {
+ mOwner = locker;
+ incrementUpgradeCount();
+ return Result.ACQUIRED;
+ }
+ // keep looping on CAS failure if a reader mucked with the state
+ } while (((state = mState) & LOCK_STATE_MASK) == 0);
+ }
+ } else if (mOwner == locker) {
+ incrementUpgradeCount();
+ return Result.OWNED;
+ }
+ return Result.FAILED;
+ }
+
+ /**
+ * Attempt to acquire an upgrade lock, waiting a maximum amount of time.
+ *
+ * @param locker object trying to become lock owner
+ * @return true if acquired
+ */
+ public final boolean tryLockForUpgrade(L locker, long timeout, TimeUnit unit)
+ throws InterruptedException
+ {
+ return tryLockForUpgrade_(locker, timeout, unit) != Result.FAILED;
+ }
+
+ /**
+ * Attempt to acquire an upgrade lock, waiting a maximum amount of time.
+ *
+ * @param locker object trying to become lock owner
+ * @return FAILED, ACQUIRED or OWNED
+ */
+ private final Result tryLockForUpgrade_(L locker, long timeout, TimeUnit unit)
+ throws InterruptedException
+ {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ Result result;
+ if ((result = tryLockForUpgrade_(locker)) == Result.FAILED) {
+ result = lockForUpgradeQueuedInterruptibly(locker, addUpgradeWaiter(),
+ unit.toNanos(timeout));
+ }
+ return result;
+ }
+
+ /**
+ * Release a previously acquired upgrade lock.
+ */
+ public final void unlockFromUpgrade(L locker) {
+ int upgradeCount = mUpgradeCount - 1;
+ if (upgradeCount < 0) {
+ throw new IllegalMonitorStateException("Too many upgrade locks released");
+ }
+ if (upgradeCount == 0 && mWriteCount > 0) {
+ // Don't release last upgrade lock and switch write lock to
+ // automatic upgrade mode.
+ clearUpgradeLock(mState);
+ return;
+ }
+ mUpgradeCount = upgradeCount;
+ if (upgradeCount > 0) {
+ return;
+ }
+
+ mOwner = null;
+
+ // keep looping on CAS failure if reader mucked with state
+ while (!clearUpgradeLock(mState)) {}
+
+ Node h = mUHead;
+ if (h != null && h.mWaitStatus != 0) {
+ unparkUpgradeSuccessor(h);
+ }
+ }
+
+ /**
+ * Acquire an exclusive write lock, possibly blocking indefinitely.
+ *
+ * @param locker object trying to become lock owner
+ */
+ public final void lockForWrite(L locker) {
+ Result writeResult;
+ if (!tryLockForWrite(locker)) {
+ Result upgradeResult = lockForUpgrade_(locker);
+ if (!tryLockForWrite(locker)) {
+ lockForWriteQueued(locker, addWriteWaiter());
+ }
+ if (upgradeResult == Result.ACQUIRED) {
+ // clear upgrade state bit to indicate automatic upgrade
+ while (!clearUpgradeLock(mState)) {}
+ } else {
+ // undo automatic upgrade count increment
+ mUpgradeCount--;
+ }
+ }
+ }
+
+ /**
+ * Acquire an exclusive write lock, possibly blocking until interrupted.
+ *
+ * @param locker object trying to become lock owner
+ */
+ public final void lockForWriteInterruptibly(L locker) throws InterruptedException {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ if (!tryLockForWrite(locker)) {
+ Result upgradeResult = lockForUpgradeInterruptibly_(locker);
+ if (!tryLockForWrite(locker)) {
+ lockForWriteQueuedInterruptibly(locker, addWriteWaiter());
+ }
+ if (upgradeResult == Result.ACQUIRED) {
+ // clear upgrade state bit to indicate automatic upgrade
+ while (!clearUpgradeLock(mState)) {}
+ } else {
+ // undo automatic upgrade count increment
+ mUpgradeCount--;
+ }
+ }
+ }
+
+ /**
+ * Attempt to immediately acquire an exclusive lock.
+ *
+ * @param locker object trying to become lock owner
+ * @return true if acquired
+ */
+ public final boolean tryLockForWrite(L locker) {
+ int state = mState;
+ if (state == 0) {
+ // no locks are held
+ if (isUpgradeOrReadWriteFirst() && setWriteLock(state)) {
+ // keep upgrade state bit clear to indicate automatic upgrade
+ mOwner = locker;
+ incrementUpgradeCount();
+ incrementWriteCount();
+ return true;
+ }
+ } else if (state == LOCK_STATE_UPGRADE) {
+ // only upgrade lock is held; upgrade to full write lock
+ if (mOwner == locker && setWriteLock(state)) {
+ incrementWriteCount();
+ return true;
+ }
+ } else if (state < 0) {
+ // write lock is held, and upgrade lock might be held too
+ if (mOwner == locker) {
+ incrementWriteCount();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Attempt to acquire an exclusive lock, waiting a maximum amount of time.
+ *
+ * @param locker object trying to become lock owner
+ * @return true if acquired
+ */
+ public final boolean tryLockForWrite(L locker, long timeout, TimeUnit unit)
+ throws InterruptedException
+ {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ if (!tryLockForWrite(locker)) {
+ return lockForWriteQueuedInterruptibly(locker, addWriteWaiter(),
+ unit.toNanos(timeout));
+ }
+ return true;
+ }
+
+ /**
+ * Release a previously acquired write lock.
+ */
+ public final void unlockFromWrite(L locker) {
+ int writeCount = mWriteCount - 1;
+ if (writeCount < 0) {
+ throw new IllegalMonitorStateException("Too many write locks released");
+ }
+ mWriteCount = writeCount;
+ if (writeCount > 0) {
+ return;
+ }
+
+ // copy original state to check if upgrade lock was automatic
+ final int state = mState;
+
+ // make sure upgrade lock is still held after releasing write lock
+ mState = LOCK_STATE_UPGRADE;
+
+ Node h = mRWHead;
+ if (h != null && h.mWaitStatus != 0) {
+ unparkReadWriteSuccessor(h);
+ }
+
+ if (state == LOCK_STATE_WRITE) {
+ // upgrade owner was automatically set, so automatically clear it
+ unlockFromUpgrade(locker);
+ }
+ }
+
+ public String toString() {
+ int state = mState;
+ int readLocks = state & ~LOCK_STATE_MASK;
+ int upgradeLocks = mUpgradeCount;
+ int writeLocks = mWriteCount;
+
+ return super.toString()
+ + "[Read locks = " + readLocks
+ + ", Upgrade locks = " + upgradeLocks
+ + ", Write locks = " + writeLocks
+ + ", Owner = " + mOwner
+ + ']';
+ }
+
+ /**
+ * Add or subtract to the count of read locks held for the given
+ * locker. Default implementation does nothing, and so read locks are not
+ * reentrant.
+ *
+ * @throws IllegalMonitorStateException if count overflows or underflows
+ */
+ protected void adjustReadLockCount(L locker, int amount) {
+ }
+
+ /**
+ * Default implementation does nothing and always returns false, and so
+ * read locks are not reentrant. Overridden implementation may choose to
+ * always returns true, in which case read lock requests can starve upgrade
+ * and write lock requests.
+ */
+ protected boolean isReadLockHeld(L locker) {
+ return false;
+ }
+
+ private Node enqForReadWrite(final Node node) {
+ for (;;) {
+ Node t = mRWTail;
+ if (t == null) { // Must initialize
+ Node h = new Node(); // Dummy header
+ h.mNext = node;
+ node.mPrev = h;
+ if (cRWHeadRef.compareAndSet(this, null, h)) {
+ mRWTail = node;
+ return h;
+ }
+ } else {
+ node.mPrev = t;
+ if (cRWTailRef.compareAndSet(this, t, node)) {
+ t.mNext = node;
+ return t;
+ }
+ }
+ }
+ }
+
+ private Node enqForUpgrade(final Node node) {
+ for (;;) {
+ Node t = mUTail;
+ if (t == null) { // Must initialize
+ Node h = new Node(); // Dummy header
+ h.mNext = node;
+ node.mPrev = h;
+ if (cUHeadRef.compareAndSet(this, null, h)) {
+ mUTail = node;
+ return h;
+ }
+ } else {
+ node.mPrev = t;
+ if (cUTailRef.compareAndSet(this, t, node)) {
+ t.mNext = node;
+ return t;
+ }
+ }
+ }
+ }
+
+ private Node addReadWaiter() {
+ return addReadWriteWaiter(true);
+ }
+
+ private Node addWriteWaiter() {
+ return addReadWriteWaiter(false);
+ }
+
+ private Node addReadWriteWaiter(boolean shared) {
+ Node node = new Node(Thread.currentThread(), shared);
+ // Try the fast path of enq; backup to full enq on failure
+ Node pred = mRWTail;
+ if (pred != null) {
+ node.mPrev = pred;
+ if (cRWTailRef.compareAndSet(this, pred, node)) {
+ pred.mNext = node;
+ return node;
+ }
+ }
+ enqForReadWrite(node);
+ return node;
+ }
+
+ private Node addUpgradeWaiter() {
+ Node node = new Node(Thread.currentThread(), false);
+ // Try the fast path of enq; backup to full enq on failure
+ Node pred = mUTail;
+ if (pred != null) {
+ node.mPrev = pred;
+ if (cUTailRef.compareAndSet(this, pred, node)) {
+ pred.mNext = node;
+ return node;
+ }
+ }
+ enqForUpgrade(node);
+ return node;
+ }
+
+ private void setReadWriteHead(Node node) {
+ mRWHead = node;
+ node.mThread = null;
+ node.mPrev = null;
+ }
+
+ private void setUpgradeHead(Node node) {
+ mUHead = node;
+ node.mThread = null;
+ node.mPrev = null;
+ }
+
+ private void unparkReadWriteSuccessor(Node node) {
+ Node.cWaitStatusRef.compareAndSet(node, Node.SIGNAL, 0);
+
+ Node s = node.mNext;
+ if (s == null || s.mWaitStatus > 0) {
+ s = null;
+ for (Node t = mRWTail; t != null && t != node; t = t.mPrev) {
+ if (t.mWaitStatus <= 0) {
+ s = t;
+ }
+ }
+ }
+ if (s != null) {
+ LockSupport.unpark(s.mThread);
+ }
+ }
+
+ private void unparkUpgradeSuccessor(Node node) {
+ Node.cWaitStatusRef.compareAndSet(node, Node.SIGNAL, 0);
+
+ Node s = node.mNext;
+ if (s == null || s.mWaitStatus > 0) {
+ s = null;
+ for (Node t = mUTail; t != null && t != node; t = t.mPrev) {
+ if (t.mWaitStatus <= 0) {
+ s = t;
+ }
+ }
+ }
+ if (s != null) {
+ LockSupport.unpark(s.mThread);
+ }
+ }
+
+ private void setReadWriteHeadAndPropagate(Node node) {
+ setReadWriteHead(node);
+ if (node.mWaitStatus != 0) {
+ Node s = node.mNext;
+ if (s == null || s.mShared) {
+ unparkReadWriteSuccessor(node);
+ }
+ }
+ }
+
+ private void cancelAcquireReadWrite(Node node) {
+ if (node != null) {
+ node.mThread = null;
+ node.mWaitStatus = Node.CANCELLED;
+ unparkReadWriteSuccessor(node);
+ }
+ }
+
+ private void cancelAcquireUpgrade(Node node) {
+ if (node != null) {
+ node.mThread = null;
+ node.mWaitStatus = Node.CANCELLED;
+ unparkUpgradeSuccessor(node);
+ }
+ }
+
+ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
+ int s = pred.mWaitStatus;
+ if (s < 0) {
+ return true;
+ }
+ if (s > 0) {
+ node.mPrev = pred.mPrev;
+ } else {
+ Node.cWaitStatusRef.compareAndSet(pred, 0, Node.SIGNAL);
+ }
+ return false;
+ }
+
+ private static void selfInterrupt() {
+ Thread.currentThread().interrupt();
+ }
+
+ private final boolean parkAndCheckInterrupt() {
+ LockSupport.park(this);
+ return Thread.interrupted();
+ }
+
+ /**
+ * @return ACQUIRED or OWNED
+ */
+ private final Result lockForUpgradeQueued(L locker, final Node node) {
+ try {
+ boolean interrupted = false;
+ for (;;) {
+ final Node p = node.predecessor();
+ Result result;
+ if (p == mUHead && (result = tryLockForUpgrade_(locker)) != Result.FAILED) {
+ setUpgradeHead(node);
+ p.mNext = null; // help GC
+ if (interrupted) {
+ selfInterrupt();
+ }
+ return result;
+ }
+ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
+ interrupted = true;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireUpgrade(node);
+ throw e;
+ }
+ }
+
+ /**
+ * @return ACQUIRED or OWNED
+ */
+ private final Result lockForUpgradeQueuedInterruptibly(L locker, final Node node)
+ throws InterruptedException
+ {
+ try {
+ for (;;) {
+ final Node p = node.predecessor();
+ Result result;
+ if (p == mUHead && (result = tryLockForUpgrade_(locker)) != Result.FAILED) {
+ setUpgradeHead(node);
+ p.mNext = null; // help GC
+ return result;
+ }
+ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
+ break;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireUpgrade(node);
+ throw e;
+ }
+ // Arrive here only if interrupted
+ cancelAcquireUpgrade(node);
+ throw new InterruptedException();
+ }
+
+ /**
+ * @return FAILED, ACQUIRED or OWNED
+ */
+ private final Result lockForUpgradeQueuedInterruptibly(L locker, final Node node,
+ long nanosTimeout)
+ throws InterruptedException
+ {
+ long lastTime = System.nanoTime();
+ try {
+ for (;;) {
+ final Node p = node.predecessor();
+ Result result;
+ if (p == mUHead && (result = tryLockForUpgrade_(locker)) != Result.FAILED) {
+ setUpgradeHead(node);
+ p.mNext = null; // help GC
+ return result;
+ }
+ if (nanosTimeout <= 0) {
+ cancelAcquireUpgrade(node);
+ return Result.FAILED;
+ }
+ if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD
+ && shouldParkAfterFailedAcquire(p, node))
+ {
+ LockSupport.parkNanos(this, nanosTimeout);
+ }
+ long now = System.nanoTime();
+ nanosTimeout -= now - lastTime;
+ lastTime = now;
+ if (Thread.interrupted()) {
+ break;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireUpgrade(node);
+ throw e;
+ }
+ // Arrive here only if interrupted
+ cancelAcquireUpgrade(node);
+ throw new InterruptedException();
+ }
+
+ private final void lockForReadQueued(L locker, final Node node) {
+ try {
+ boolean interrupted = false;
+ for (;;) {
+ final Node p = node.predecessor();
+ if (p == mRWHead && tryLockForRead(locker)) {
+ setReadWriteHeadAndPropagate(node);
+ p.mNext = null; // help GC
+ if (interrupted) {
+ selfInterrupt();
+ }
+ return;
+ }
+ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
+ interrupted = true;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireReadWrite(node);
+ throw e;
+ }
+ }
+
+ private final void lockForReadQueuedInterruptibly(L locker, final Node node)
+ throws InterruptedException
+ {
+ try {
+ for (;;) {
+ final Node p = node.predecessor();
+ if (p == mRWHead && tryLockForRead(locker)) {
+ setReadWriteHeadAndPropagate(node);
+ p.mNext = null; // help GC
+ return;
+ }
+ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
+ break;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireReadWrite(node);
+ throw e;
+ }
+ // Arrive here only if interrupted
+ cancelAcquireReadWrite(node);
+ throw new InterruptedException();
+ }
+
+ /**
+ * @return true if acquired
+ */
+ private final boolean lockForReadQueuedInterruptibly(L locker, final Node node,
+ long nanosTimeout)
+ throws InterruptedException
+ {
+ long lastTime = System.nanoTime();
+ try {
+ for (;;) {
+ final Node p = node.predecessor();
+ if (p == mRWHead && tryLockForRead(locker)) {
+ setReadWriteHeadAndPropagate(node);
+ p.mNext = null; // help GC
+ return true;
+ }
+ if (nanosTimeout <= 0) {
+ cancelAcquireReadWrite(node);
+ return false;
+ }
+ if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD
+ && shouldParkAfterFailedAcquire(p, node))
+ {
+ LockSupport.parkNanos(this, nanosTimeout);
+ }
+ long now = System.nanoTime();
+ nanosTimeout -= now - lastTime;
+ lastTime = now;
+ if (Thread.interrupted()) {
+ break;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireReadWrite(node);
+ throw e;
+ }
+ // Arrive here only if interrupted
+ cancelAcquireReadWrite(node);
+ throw new InterruptedException();
+ }
+
+ private final void lockForWriteQueued(L locker, final Node node) {
+ try {
+ boolean interrupted = false;
+ for (;;) {
+ final Node p = node.predecessor();
+ if (p == mRWHead && tryLockForWrite(locker)) {
+ setReadWriteHead(node);
+ p.mNext = null; // help GC
+ if (interrupted) {
+ selfInterrupt();
+ }
+ return;
+ }
+ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
+ interrupted = true;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireReadWrite(node);
+ throw e;
+ }
+ }
+
+ private final void lockForWriteQueuedInterruptibly(L locker, final Node node)
+ throws InterruptedException
+ {
+ try {
+ for (;;) {
+ final Node p = node.predecessor();
+ if (p == mRWHead && tryLockForWrite(locker)) {
+ setReadWriteHead(node);
+ p.mNext = null; // help GC
+ return;
+ }
+ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
+ break;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireReadWrite(node);
+ throw e;
+ }
+ // Arrive here only if interrupted
+ cancelAcquireReadWrite(node);
+ throw new InterruptedException();
+ }
+
+ /**
+ * @return true if acquired
+ */
+ private final boolean lockForWriteQueuedInterruptibly(L locker, final Node node,
+ long nanosTimeout)
+ throws InterruptedException
+ {
+ long lastTime = System.nanoTime();
+ try {
+ for (;;) {
+ final Node p = node.predecessor();
+ if (p == mRWHead && tryLockForWrite(locker)) {
+ setReadWriteHead(node);
+ p.mNext = null; // help GC
+ return true;
+ }
+ if (nanosTimeout <= 0) {
+ cancelAcquireReadWrite(node);
+ return false;
+ }
+ if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD
+ && shouldParkAfterFailedAcquire(p, node))
+ {
+ LockSupport.parkNanos(this, nanosTimeout);
+ }
+ long now = System.nanoTime();
+ nanosTimeout -= now - lastTime;
+ lastTime = now;
+ if (Thread.interrupted()) {
+ break;
+ }
+ }
+ } catch (RuntimeException e) {
+ cancelAcquireReadWrite(node);
+ throw e;
+ }
+ // Arrive here only if interrupted
+ cancelAcquireReadWrite(node);
+ throw new InterruptedException();
+ }
+
+ private final boolean isReadWriteFirst() {
+ Node h;
+ if ((h = mRWHead) == null) {
+ return true;
+ }
+ Thread current = Thread.currentThread();
+ Node s;
+ return ((s = h.mNext) != null && s.mThread == current) || fullIsReadWriteFirst(current);
+ }
+
+ private final boolean fullIsReadWriteFirst(Thread current) {
+ Node h, s;
+ Thread firstThread = null;
+ if (((h = mRWHead) != null && (s = h.mNext) != null &&
+ s.mPrev == mRWHead && (firstThread = s.mThread) != null))
+ {
+ return firstThread == current;
+ }
+ Node t = mRWTail;
+ while (t != null && t != mRWHead) {
+ Thread tt = t.mThread;
+ if (tt != null) {
+ firstThread = tt;
+ }
+ t = t.mPrev;
+ }
+ return firstThread == current || firstThread == null;
+ }
+
+ private final boolean isUpgradeFirst() {
+ Node h;
+ if ((h = mUHead) == null) {
+ return true;
+ }
+ Thread current = Thread.currentThread();
+ Node s;
+ return ((s = h.mNext) != null && s.mThread == current) || fullIsUpgradeFirst(current);
+ }
+
+ private final boolean fullIsUpgradeFirst(Thread current) {
+ Node h, s;
+ Thread firstThread = null;
+ if (((h = mUHead) != null && (s = h.mNext) != null &&
+ s.mPrev == mUHead && (firstThread = s.mThread) != null))
+ {
+ return firstThread == current;
+ }
+ Node t = mUTail;
+ while (t != null && t != mUHead) {
+ Thread tt = t.mThread;
+ if (tt != null) {
+ firstThread = tt;
+ }
+ t = t.mPrev;
+ }
+ return firstThread == current || firstThread == null;
+ }
+
+ private final boolean isUpgradeOrReadWriteFirst() {
+ Node uh, rwh;
+ if ((uh = mUHead) == null || (rwh = mRWHead) == null) {
+ return true;
+ }
+ Thread current = Thread.currentThread();
+ Node us, rws;
+ return ((us = uh.mNext) != null && us.mThread == current)
+ || ((rws = rwh.mNext) != null && rws.mThread == current)
+ || fullIsUpgradeFirst(current)
+ || fullIsReadWriteFirst(current);
+ }
+
+ /**
+ * @return false if state changed
+ */
+ private boolean incrementReadLocks(int state) {
+ int readLocks = (state & ~LOCK_STATE_MASK) + 1;
+ if (readLocks == LOCK_STATE_MASK) {
+ throw new IllegalMonitorStateException("Maximum read lock count exceeded");
+ }
+ return cStateRef.compareAndSet(this, state, state & LOCK_STATE_MASK | readLocks);
+ }
+
+ /**
+ * @return number of remaining read locks or negative if concurrent
+ * modification prevented operation
+ */
+ private int decrementReadLocks(int state) {
+ int readLocks = (state & ~LOCK_STATE_MASK) - 1;
+ if (readLocks < 0) {
+ throw new IllegalMonitorStateException("Too many read locks released");
+ }
+ if (cStateRef.compareAndSet(this, state, state & LOCK_STATE_MASK | readLocks)) {
+ return readLocks;
+ }
+ return -1;
+ }
+
+ /**
+ * @return false if concurrent modification prevented operation
+ */
+ private boolean setUpgradeLock(int state) {
+ return cStateRef.compareAndSet(this, state, state | LOCK_STATE_UPGRADE);
+ }
+
+ /**
+ * @return false if concurrent modification prevented operation
+ */
+ private boolean clearUpgradeLock(int state) {
+ return cStateRef.compareAndSet(this, state, state & ~LOCK_STATE_UPGRADE);
+ }
+
+ private void incrementUpgradeCount() {
+ int upgradeCount = mUpgradeCount + 1;
+ if (upgradeCount < 0) {
+ throw new IllegalMonitorStateException("Maximum upgrade lock count exceeded");
+ }
+ mUpgradeCount = upgradeCount;
+ }
+
+ /**
+ * @return false if concurrent modification prevented operation
+ */
+ private boolean setWriteLock(int state) {
+ return cStateRef.compareAndSet(this, state, state | LOCK_STATE_WRITE);
+ }
+
+ private void incrementWriteCount() {
+ int writeCount = mWriteCount + 1;
+ if (writeCount < 0) {
+ throw new IllegalMonitorStateException("Maximum write lock count exceeded");
+ }
+ mWriteCount = writeCount;
+ }
+
+ /**
+ * Node class ripped off from AbstractQueuedSynchronizer and modified
+ * slightly. Read the comments in that class for better understanding.
+ */
+ static final class Node {
+ static final AtomicIntegerFieldUpdater<Node> cWaitStatusRef =
+ AtomicIntegerFieldUpdater.newUpdater(Node.class, "mWaitStatus");
+
+ static final int CANCELLED = 1;
+ static final int SIGNAL = -1;
+
+ volatile int mWaitStatus;
+ volatile Node mPrev;
+ volatile Node mNext;
+ volatile Thread mThread;
+
+ final boolean mShared;
+
+ // Used to establish initial head
+ Node() {
+ mShared = false;
+ }
+
+ Node(Thread thread, boolean shared) {
+ mThread = thread;
+ mShared = shared;
+ }
+
+ final Node predecessor() throws NullPointerException {
+ Node p = mPrev;
+ if (p == null) {
+ throw new NullPointerException();
+ } else {
+ return p;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/map/package-info.java b/src/main/java/com/amazon/carbonado/repo/map/package-info.java new file mode 100644 index 0000000..b386251 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/repo/map/package-info.java @@ -0,0 +1,24 @@ +/*
+ * Copyright 2008 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.
+ */
+
+/**
+ * Volatile repository implementation backed by a concurrent map.
+ *
+ * @see com.amazon.carbonado.repo.map.MapRepositoryBuilder
+ */
+package com.amazon.carbonado.repo.map;
|