diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bfa550 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +*.iml +target/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..dc609a6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: java + +dist: xenial + +matrix: + include: + - jdk: openjdk8 + - jdk: openjdk9 + - jdk: openjdk10 + - jdk: openjdk11 + - jdk: openjdk12 + +cache: + directories: + - $HOME/.m2 + +install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V + +script: mvn verify -B diff --git a/BSD-2.txt b/BSD-2.txt new file mode 100644 index 0000000..4e8d900 --- /dev/null +++ b/BSD-2.txt @@ -0,0 +1,26 @@ +Copyright © ${project.inceptionYear}, ${owner} +All rights reserved. + +Modifications and additions from the original source code at +https://github.com/kgudka/java-multilocks are +Copyright © ${modifications.year}, ${modifications.owner} + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b63f466 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright © 2010, Khilan Gudka +All rights reserved. + +Modifications and additions from the original source code at +https://github.com/kgudka/java-multilocks are +Copyright © 2017, Evolved Binary Ltd + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 32e2f0b..d556541 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,30 @@ +# MutiLock + +[![Build Status](https://travis-ci.com/evolvedbinary/multilock.svg?branch=master)](https://travis-ci.com/evolvedbinary/mulitlock) + Java implementation of Fast Multi-Level locks. -This is the source-code accompanying our EC^2 2010 Workshop on Exploiting -Concurrency Efficiently and Correctly position paper. +**NOTE:** This is a Mavenized port of the [original source code](https://github.com/kgudka/java-multilocks) +with a few additions and modifications. + +The original source code was licensed under *The BSD 2-Clause License*, +as *Copyright (c) 2010-2016 Khilan Gudka*. + +We release our modifications under the same license but +*Copyright (c) 2017-2019 Evolved Binary Ltd*. + +The original paper by Khilan Gudka and Susan Eisenbach entitled `Fast Multi-Level Locks for Java` +can be found here: https://www.cl.cam.ac.uk/~kg365/pubs/ec2-fastlocks.pdf + + +## Maven Coordinates + +The artifact is available from Maven Central as: -The paper can be found here: -http://pubs.doc.ic.ac.uk/fast-multi-level-locks/ +```xml + + com.evolvedbinary.multilock + multilock + 1.0.1 + +``` diff --git a/multilock/MultiLock.java b/multilock/MultiLock.java deleted file mode 100644 index edc33a9..0000000 --- a/multilock/MultiLock.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -package multilock; - - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.*; - -public class MultiLock { - - // individual field masks - static final long X_FIELD = 0xFFFF000000000000L; - static final long S_FIELD = 0x0000FFFF00000000L; - static final long IX_FIELD = 0x00000000FFFF0000L; - static final long IS_FIELD = 0x000000000000FFFFL; - static final long NON_X_FIELDS = ~X_FIELD; - - // bit patterns for inc/dec individual fields - static final long X_UNIT = 0x0001000000000000L; - static final long S_UNIT = 0x0000000100000000L; - static final long IX_UNIT = 0x0000000000010000L; - static final long IS_UNIT = 0x0000000000000001L; - - final MultiLock owner; - final Sync sync; - - final ReadLock readLock; - final WriteLock writeLock; - - public MultiLock(MultiLock o) { - owner = o; - sync = new Sync(); - readLock = new ReadLock(); - writeLock = new WriteLock(); - } - - class ReadLock implements Lock { - - public void lock() { - lockRead(); - } - - public void unlock() { - unlockRead(); - } - - public void lockInterruptibly() throws InterruptedException { - throw new UnsupportedOperationException(); - } - - public Condition newCondition() { - throw new UnsupportedOperationException(); - } - - public boolean tryLock() { - throw new UnsupportedOperationException(); - } - - public boolean tryLock(long time, TimeUnit unit) - throws InterruptedException { - throw new UnsupportedOperationException(); - } - - } - - class WriteLock implements Lock { - - public void lock() { - lockWrite(); - } - - public void unlock() { - unlockWrite(); - } - - public void lockInterruptibly() throws InterruptedException { - throw new UnsupportedOperationException(); - } - - public Condition newCondition() { - throw new UnsupportedOperationException(); - } - - public boolean tryLock() { - throw new UnsupportedOperationException(); - } - - public boolean tryLock(long time, TimeUnit unit) - throws InterruptedException { - throw new UnsupportedOperationException(); - } - - } - - public Lock readLock() { return readLock; } - - public Lock writeLock() { return writeLock; } - - static class Sync extends AbstractQueuedLongSynchronizer { - - static long xCount(long c) { return (c & X_FIELD) >>> 48; } - static long sCount(long c) { return (c & S_FIELD) >> 32; } - static long ixCount(long c) { return (c & IX_FIELD) >> 16; } - static long isCount(long c) { return c & IS_FIELD; } - - // store's per-thread state - static class HoldCounter { - final long tid = Thread.currentThread().getId(); - long state = 0; - } - - static class ThreadLocalHoldCounter extends ThreadLocal { - @Override - protected HoldCounter initialValue() { - return new HoldCounter(); - } - } - - final ThreadLocalHoldCounter holdCounts; - - HoldCounter cachedHoldCounter; - - Sync() { - holdCounts = new ThreadLocalHoldCounter(); - setState(getState()); // ensures visibility of holdCounts - } - - @Override - protected boolean tryAcquire(long arg) { - Thread current = Thread.currentThread(); - long c = getState(); - if (c != 0) { - long x = c & X_FIELD; - // (Note: if c != 0 and x == 0 then non-exclusive count != 0) - if (x == 0) { - // Check non-exclusive counts are only for current. - // i.e. are we upgrading? - HoldCounter rh = cachedHoldCounter; - if (rh == null || rh.tid != current.getId()) - rh = holdCounts.get(); - long group = c - rh.state; - if ((group & NON_X_FIELDS) != 0) { - // current thread is not only non-exclusive user - return false; - } - } - else if (current != getExclusiveOwnerThread()) { - return false; - } - } - if (!compareAndSetState(c, c + X_UNIT)) - return false; - setExclusiveOwnerThread(current); - return true; - } - - @Override - protected boolean tryRelease(long arg) { - long nextc = getState() - arg; - if (Thread.currentThread() != getExclusiveOwnerThread()) { - throw new IllegalMonitorStateException(); - } - if ((nextc & X_FIELD) == 0) { - setExclusiveOwnerThread(null); - setState(nextc); - return true; - } - else { - setState(nextc); - return false; - } - } - - @Override - protected long tryAcquireShared(long arg) { - Thread current = Thread.currentThread(); - for (;;) { - long c = getState(); - // someone else already is X - if ((c & X_FIELD) != 0 && - getExclusiveOwnerThread() != current) - return -1; - // either no X or current is X - if (getExclusiveOwnerThread() == current) { - if (updateState(c, arg, current)) { - return 0; - } - } - else if (arg == IS_UNIT) { // IS is compatible with S, IX, IS - if (updateState(c, arg, current)) { - return 1; - } - } - else if (arg == IX_UNIT) { - // two cases: S == 0 (ok) and S != 0 (thread check) - long s = c & S_FIELD; - if (s == 0) { - // ok - if (updateState(c, arg, current)) { - return 1; - } - } - else { - // Check if current thread is the only S - // TODO: optimise so that re-entrant IX locking is fast - HoldCounter rh = null; //cachedHoldCounter; - if (rh == null || rh.tid != current.getId()) - rh = holdCounts.get(); - long group = c - rh.state; - if ((group & S_FIELD) == 0) { - // current is only S - if (compareAndSetState(c, c + arg)) { - rh.state += arg; - cachedHoldCounter = rh; - return 1; // still return 1 because IS is always compatible - } - } - } - } - else if (arg == S_UNIT) { - // two cases: IX == 0 (ok) and IX != 0 (thread check) - long ix = c & IX_FIELD; - if (ix == 0) { - // ok - if (updateState(c, arg, current)) { - return 1; - } - } - else { - // check if current thread is the only IX - // TODO: optimise so that re-entrant S locking is fast - HoldCounter rh = cachedHoldCounter; - if (rh == null || rh.tid != current.getId()) - rh = holdCounts.get(); - long group = c - rh.state; - if ((group & IX_FIELD) == 0) { - // current is only IX - if (compareAndSetState(c, c + arg)) { - rh.state += arg; - cachedHoldCounter = rh; - return 1; // still return 1 because IS is always compatible - } - } - } - } - } - } - - private boolean updateState(long c, long arg, Thread current) { - if (compareAndSetState(c, c + arg)) { - HoldCounter rh = cachedHoldCounter; - if (rh == null || rh.tid != current.getId()) - cachedHoldCounter = rh = holdCounts.get(); - rh.state += arg; - return true; - } - return false; - } - - @Override - protected boolean tryReleaseShared(long arg) { - HoldCounter rh = cachedHoldCounter; - Thread current = Thread.currentThread(); - if (rh == null || rh.tid != current.getId()) - rh = holdCounts.get(); - rh.state -= arg; - for (;;) { - long c = getState(); - long nextc = c - arg; - if (compareAndSetState(c, nextc)) { - if ((nextc & X_FIELD) == 0) { - if (arg == S_UNIT) { - return (nextc & S_FIELD) == 0; - } - if (arg == IS_UNIT) { - return (nextc & IS_FIELD) == 0; - } - if (arg == IX_UNIT) { - return (nextc & IX_FIELD) == 0; - } - } - else { - return false; - } - } - } - } - - } - - public boolean lockRead() { - if (owner != null) { - owner.lockIntentionRead(); - } - sync.acquireShared(S_UNIT); - return true; - } - - public boolean lockWrite() { - if (owner != null) { - owner.lockIntentionWrite(); - } - sync.acquire(X_UNIT); - return true; - } - - public boolean lockIntentionRead() { - if (owner != null) { - owner.lockIntentionRead(); - } - sync.acquireShared(IS_UNIT); - return true; - } - - public boolean lockIntentionWrite() { - if (owner != null) { - owner.lockIntentionWrite(); - } - sync.acquireShared(IX_UNIT); - return true; - } - - public void unlockRead() { - sync.releaseShared(S_UNIT); - if (owner != null) { - owner.unlockIntentionRead(); - } - } - - public void unlockWrite() { - sync.release(X_UNIT); - if (owner != null) { - owner.unlockIntentionWrite(); - } - } - - public void unlockIntentionRead() { - sync.releaseShared(IS_UNIT); - if (owner != null) { - owner.unlockIntentionRead(); - } - } - - public void unlockIntentionWrite() { - sync.releaseShared(IX_UNIT); - if (owner != null) { - owner.unlockIntentionWrite(); - } - } - -} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..76929b4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,225 @@ + + + 4.0.0 + + + org.sonatype.oss + oss-parent + 9 + + + + com.evolvedbinary.multilock + multilock + 1.0.2-SNAPSHOT + + Multilock + Fast Multi-Level Locks for Java + https://www.cl.cam.ac.uk/~kg365/pubs/ec2-fastlocks.pdf + 2010 + + + Evolved Binary + https://evolvedbinary.com + + + + + The BSD 2-Clause License + http://www.opensource.org/licenses/BSD-2-Clause + repo + + + + + scm:git:https://github.com/evolvedbinary/multilock.git + scm:git:https://github.com/evolvedbinary/multilock.git + scm:git:https://github.com/evolvedbinary/multilock.git + HEAD + + + + 1.8 + 1.8 + 5.5.0-RC2 + UTF-8 + + + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + com.evolvedbinary.thirdparty.org.deucestm + deucestm-annotations + 1.3.0 + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + + + + + com.mycila + license-maven-plugin + 3.0 + true + +
BSD-2.txt
+ true + true + true + + Khilan Gudka + 2017 + ${project.organization.name} + + + pom.xml + README.md + LICENSE + + ${project.build.sourceEncoding} +
+ + + check-headers + verify + + check + + + +
+ + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + ${tests.java.version} + ${tests.java.version} + ${project.build.sourceEncoding} + + + + compile + + compile + + + + + + com.code54.mojo + buildversion-plugin + 1.0.3 + + + validate + + set-properties + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.2 + + + + ${build-tag} + ${build-commit} + ${build-commit-abbrev} + ${build-version} + ${build-tstamp} + ${project.scm.connection} + ${project.description} + ${project.url} + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + + true + true + + + ${build-tag} + ${build-commit} + ${build-commit-abbrev} + ${build-version} + ${build-tstamp} + ${project.scm.connection} + ${project.description} + ${project.url} + + + + + + attach-sources + verify + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + true + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + forked-path + true + @{project.version} + + +
+
+ + + + clojars.org + http://clojars.org/repo + + +
diff --git a/src/main/java/uk/ac/ic/doc/slurp/multilock/HierarchicalMultiLock.java b/src/main/java/uk/ac/ic/doc/slurp/multilock/HierarchicalMultiLock.java new file mode 100644 index 0000000..9229d23 --- /dev/null +++ b/src/main/java/uk/ac/ic/doc/slurp/multilock/HierarchicalMultiLock.java @@ -0,0 +1,369 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; + +public class HierarchicalMultiLock extends MultiLock { + + private final MultiLock parent; + + /** + * Constructs a MultiLock where the lock + * is within a lock hierarchy. + * + * @param parent the parent lock, or null if this lock + * should be the root of the hierarchy. + */ + public HierarchicalMultiLock(@Nullable final MultiLock parent) { + super(); + this.parent = parent; + } + + @Override + public void readLock() { + if (parent != null) { + parent.intentionReadLock(); + } + super.readLock(); + } + + @Override + public boolean tryReadLock() { + if (parent != null) { + if(!parent.tryIntentionReadLock()) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryReadLock(); + } finally { + if (!locked && parent != null) { + parent.unlockIntentionRead(); + } + } + return locked; + } + + /** + * @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the + * timeout is used twice, once for the parent and once for the node. + * @param unit the time unit of the timeout argument + */ + @Override + public boolean tryReadLock(final long timeout, final TimeUnit unit) throws InterruptedException { + if (parent != null) { + if(!parent.tryIntentionReadLock(timeout, unit)) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryReadLock(timeout, unit); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionRead(); + } + throw e; + } finally { + if (!locked && parent != null) { + parent.unlockIntentionRead(); + } + } + return locked; + } + + @Override + public void readLockInterruptibly() throws InterruptedException { + if (parent != null) { + parent.intentionReadLockInterruptibly(); + } + + try { + super.readLockInterruptibly(); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionRead(); + } + throw e; + } + } + + @Override + public void writeLock() { + if (parent != null) { + parent.intentionWriteLock(); + } + super.writeLock(); + } + + @Override + public boolean tryWriteLock() { + if (parent != null) { + if(!parent.tryIntentionWriteLock()) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryWriteLock(); + } finally { + if (!locked && parent != null) { + parent.unlockIntentionWrite(); + } + } + return locked; + } + + /** + * @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the + * timeout is used twice, once for the parent and once for the node. + * @param unit the time unit of the timeout argument + */ + @Override + public boolean tryWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException { + if (parent != null) { + if(!parent.tryIntentionWriteLock(timeout, unit)) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryWriteLock(timeout, unit); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionWrite(); + } + throw e; + } finally { + if (!locked && parent != null) { + parent.unlockIntentionWrite(); + } + } + return locked; + } + + @Override + public void writeLockInterruptibly() throws InterruptedException { + if (parent != null) { + parent.intentionWriteLockInterruptibly(); + } + + try { + super.writeLockInterruptibly(); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionWrite(); + } + throw e; + } + } + + @Override + public void intentionReadLock() { + if (parent != null) { + parent.intentionReadLock(); + } + super.intentionReadLock(); + } + + @Override + public boolean tryIntentionReadLock() { + if (parent != null) { + if(!parent.tryIntentionReadLock()) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryIntentionReadLock(); + } finally { + if (!locked && parent != null) { + parent.unlockIntentionRead(); + } + } + return locked; + } + + /** + * @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the + * timeout is used twice, once for the parent and once for the node. + * @param unit the time unit of the timeout argument + */ + @Override + public boolean tryIntentionReadLock(final long timeout, final TimeUnit unit) throws InterruptedException { + if (parent != null) { + if(!parent.tryIntentionReadLock(timeout, unit)) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryIntentionReadLock(timeout, unit); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionRead(); + } + throw e; + } finally { + if (!locked && parent != null) { + parent.unlockIntentionRead(); + } + } + return locked; + } + + @Override + public void intentionReadLockInterruptibly() throws InterruptedException { + if (parent != null) { + parent.intentionReadLockInterruptibly(); + } + + try { + super.intentionReadLockInterruptibly(); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionRead(); + } + throw e; + } + } + + @Override + public void intentionWriteLock() { + if (parent != null) { + parent.intentionWriteLock(); + } + super.intentionWriteLock(); + } + + @Override + public boolean tryIntentionWriteLock() { + if (parent != null) { + if(!parent.tryIntentionWriteLock()) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryIntentionWriteLock(); + } finally { + if (!locked && parent != null) { + parent.unlockIntentionWrite(); + } + } + return locked; + } + + /** + * @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the + * timeout is used twice, once for the parent and once for the node. + * @param unit the time unit of the timeout argument + */ + @Override + public boolean tryIntentionWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException { + if (parent != null) { + if(!parent.tryIntentionWriteLock(timeout, unit)) { + return false; + } + } + + boolean locked = false; + try { + locked = super.tryIntentionWriteLock(timeout, unit); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionWrite(); + } + throw e; + } finally { + if (!locked && parent != null) { + parent.unlockIntentionWrite(); + } + } + return locked; + } + + @Override + public void intentionWriteLockInterruptibly() throws InterruptedException { + if (parent != null) { + parent.intentionWriteLockInterruptibly(); + } + + try { + super.intentionWriteLockInterruptibly(); + } catch (final InterruptedException e) { + if (parent != null) { + parent.unlockIntentionWrite(); + } + throw e; + } + } + + @Override + public void unlockRead() { + super.unlockRead(); + if (parent != null) { + parent.unlockIntentionRead(); + } + } + + @Override + public void unlockWrite() { + super.unlockWrite(); + if (parent != null) { + parent.unlockIntentionWrite(); + } + } + + @Override + public void unlockIntentionRead() { + super.unlockIntentionRead(); + if (parent != null) { + parent.unlockIntentionRead(); + } + } + + @Override + public void unlockIntentionWrite() { + super.unlockIntentionWrite(); + if (parent != null) { + parent.unlockIntentionWrite(); + } + } +} diff --git a/src/main/java/uk/ac/ic/doc/slurp/multilock/LockMode.java b/src/main/java/uk/ac/ic/doc/slurp/multilock/LockMode.java new file mode 100644 index 0000000..e0e91a8 --- /dev/null +++ b/src/main/java/uk/ac/ic/doc/slurp/multilock/LockMode.java @@ -0,0 +1,318 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +import java.util.concurrent.TimeUnit; + +/** + * An Enumeration of the MultiLock modes. + */ +public enum LockMode { + /** + * Intention Shared + */ + IS, + + /** + * Intention Exclusive + */ + IX, + + /** + * Shared + */ + S, + + /** + * Shared Intention Exclusive + */ + SIX, + + /** + * Exclusive + */ + X; + + /** + * Locks the MultiLock with this mode. + * + * @param multiLock the MultiLock object. + */ + public void lock(final MultiLock multiLock) { + lock(multiLock, this); + } + + /** + * Attempts to lock the MultiLock with this mode. + * + * @param multiLock the MultiLock object. + * + * @return true if the lock was acquired, false otherwise. + */ + public boolean tryLock(final MultiLock multiLock) { + return tryLock(multiLock, this); + } + + /** + * Attempts to lock the MultiLock with this mode. + * + * @param multiLock the MultiLock object. + * @param timeout the time to wait for acquiring the lock. + * @param unit the time unit of the timeout argument. + * + * @return true if the lock was acquired, false otherwise. + * + * @throws InterruptedException if the thread was interrupted + */ + public boolean tryLock(final MultiLock multiLock, final long timeout, final TimeUnit unit) + throws InterruptedException { + return tryLock(multiLock, this, timeout, unit); + } + + /** + * Locks the MultiLock with this mode, aborting if interrupted. + * + * @param multiLock the MultiLock object. + * + * @throws InterruptedException if the thread was interrupted + */ + public void lockInterruptibly(final MultiLock multiLock) throws InterruptedException { + lockInterruptibly(multiLock, this); + } + + /** + * Unlocks the MultiLock with this mode. + * + * @param multiLock the MultiLock object. + */ + public void unlock(final MultiLock multiLock) { + unlock(multiLock, this); + } + + /** + * Locks the MultiLock with the provided mode. + * + * @param multiLock the MultiLock object. + * @param lockMode the mode to lock the MultiLock. + * + * @throws IllegalArgumentException if an unknown mode is provided. + */ + public static void lock(final MultiLock multiLock, final LockMode lockMode) { + switch (lockMode) { + case IS: + multiLock.intentionReadLock(); + break; + + case IX: + multiLock.intentionWriteLock(); + break; + + case S: + multiLock.readLock(); + break; + + case SIX: + multiLock.readLock(); + multiLock.intentionWriteLock(); + break; + + case X: + multiLock.writeLock(); + break; + + default: + throw new IllegalArgumentException("Unknown lock mode: " + lockMode); + } + } + + /** + * Attempts to lock the MultiLock with the provided mode. + * + * @param multiLock the MultiLock object. + * @param lockMode the mode to lock the MultiLock. + * + * @return true if the lock was acquired. + * + * @throws IllegalArgumentException if an unknown mode is provided. + */ + public static boolean tryLock(final MultiLock multiLock, final LockMode lockMode) { + switch (lockMode) { + case IS: + return multiLock.tryIntentionReadLock(); + + case IX: + return multiLock.tryIntentionWriteLock(); + + case S: + return multiLock.tryReadLock(); + + case SIX: + if (!multiLock.tryReadLock()) { + return false; + } + if (!multiLock.tryIntentionWriteLock()) { + multiLock.unlockRead(); + return false; + } + return true; + + case X: + return multiLock.tryWriteLock(); + + default: + throw new IllegalArgumentException("Unknown lock mode: " + lockMode); + } + } + + /** + * Attempts to lock the MultiLock with the provided mode. + * + * @param multiLock the MultiLock object. + * @param lockMode the mode to lock the MultiLock. + * @param timeout the time to wait for acquiring the lock. + * @param unit the time unit of the timeout argument. + * + * @return true if the lock was acquired. + * + * @throws IllegalArgumentException if an unknown mode is provided. + * @throws InterruptedException if the thread was interrupted + */ + public static boolean tryLock(final MultiLock multiLock, final LockMode lockMode, final long timeout, + final TimeUnit unit) throws InterruptedException { + switch (lockMode) { + case IS: + return multiLock.tryIntentionReadLock(timeout, unit); + + case IX: + return multiLock.tryIntentionWriteLock(timeout, unit); + + case S: + return multiLock.tryReadLock(timeout, unit); + + case SIX: + if (!multiLock.tryReadLock(timeout, unit)) { + return false; + } + try { + if (!multiLock.tryIntentionWriteLock(timeout, unit)) { + multiLock.unlockRead(); + return false; + } + } catch (final InterruptedException e) { + multiLock.unlockRead(); + throw e; + } + return true; + + case X: + return multiLock.tryWriteLock(timeout, unit); + + default: + throw new IllegalArgumentException("Unknown lock mode: " + lockMode); + } + } + + /** + * Locks the MultiLock with the provided mode, aborting if interrupted. + * + * @param multiLock the MultiLock object. + * @param lockMode the mode to lock the MultiLock. + * + * @throws IllegalArgumentException if an unknown mode is provided. + * @throws InterruptedException if the thread was interrupted + */ + public static void lockInterruptibly(final MultiLock multiLock, final LockMode lockMode) throws InterruptedException { + switch (lockMode) { + case IS: + multiLock.intentionReadLockInterruptibly(); + break; + + case IX: + multiLock.intentionWriteLockInterruptibly(); + break; + + case S: + multiLock.readLockInterruptibly(); + break; + + case SIX: + multiLock.readLockInterruptibly(); + try { + multiLock.intentionWriteLockInterruptibly(); + } catch (final InterruptedException e) { + multiLock.unlockRead(); + throw e; + } + break; + + case X: + multiLock.writeLockInterruptibly(); + break; + + default: + throw new IllegalArgumentException("Unknown lock mode: " + lockMode); + } + } + + /** + * Unlocks the MultiLock with the provided mode. + * + * @param multiLock the MultiLock object. + * @param lockMode the mode to unlock the MultiLock. + * + * @throws IllegalArgumentException if an unknown mode is provided. + */ + public static void unlock(final MultiLock multiLock, final LockMode lockMode) { + switch (lockMode) { + case IS: + multiLock.unlockIntentionRead(); + break; + + case IX: + multiLock.unlockIntentionWrite(); + break; + + case S: + multiLock.unlockRead(); + break; + + case SIX: + multiLock.unlockIntentionWrite(); + multiLock.unlockRead(); + break; + + case X: + multiLock.unlockWrite(); + break; + + default: + throw new IllegalArgumentException("Unknown lock mode: " + lockMode); + } + } +} diff --git a/src/main/java/uk/ac/ic/doc/slurp/multilock/MultiLock.java b/src/main/java/uk/ac/ic/doc/slurp/multilock/MultiLock.java new file mode 100644 index 0000000..468e317 --- /dev/null +++ b/src/main/java/uk/ac/ic/doc/slurp/multilock/MultiLock.java @@ -0,0 +1,687 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.*; + +public class MultiLock { + + // individual field masks + static final long X_FIELD = 0xFFFF000000000000L; + static final long S_FIELD = 0x0000FFFF00000000L; + static final long IX_FIELD = 0x00000000FFFF0000L; + static final long IS_FIELD = 0x000000000000FFFFL; + static final long NON_X_FIELDS = ~X_FIELD; + + // bit patterns for inc/dec individual fields + static final long X_UNIT = 0x0001000000000000L; + static final long S_UNIT = 0x0000000100000000L; + static final long IX_UNIT = 0x0000000000010000L; + static final long IS_UNIT = 0x0000000000000001L; + + final Sync sync; + + // views + transient ReadLockView readLockView; + transient WriteLockView writeLockView; + transient ReadWriteLockView readWriteLockView; + + /** + * Constructs a MultiLock. + */ + public MultiLock() { + this.sync = new Sync(); + } + + final class ReadLockView implements Lock { + + @Override + public void lock() { + readLock(); + } + + @Override + public void unlock() { + unlockRead(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + readLockInterruptibly(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + return tryReadLock(); + } + + @Override + public boolean tryLock(final long time, final TimeUnit unit) + throws InterruptedException { + return tryReadLock(time, unit); + } + } + + final class WriteLockView implements Lock { + + @Override + public void lock() { + writeLock(); + } + + @Override + public void unlock() { + unlockWrite(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + writeLockInterruptibly(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + return tryWriteLock(); + } + + @Override + public boolean tryLock(final long time, final TimeUnit unit) + throws InterruptedException { + return tryWriteLock(time, unit); + } + } + + final class ReadWriteLockView implements ReadWriteLock { + @Override + public Lock readLock() { + return asReadLock(); + } + + @Override + public Lock writeLock() { + return asWriteLock(); + } + } + + /** + * Returns a plain {@link Lock} view of this MultiLock in which + * the {@link Lock#lock} method is mapped to {@link #readLock()}, + * and similarly for other methods. The returned Lock does not + * support a {@link Condition}; method {@link + * Lock#newCondition()} throws {@code + * UnsupportedOperationException}. + * + * @return the lock + */ + public Lock asReadLock() { + ReadLockView v; + return ((v = readLockView) != null ? v : + (readLockView = new ReadLockView())); + } + + /** + * Returns a plain {@link Lock} view of this MultiLock in which + * the {@link Lock#lock} method is mapped to {@link #writeLock()}, + * and similarly for other methods. The returned Lock does not + * support a {@link Condition}; method {@link + * Lock#newCondition()} throws {@code + * UnsupportedOperationException}. + * + * @return the lock + */ + public Lock asWriteLock() { + WriteLockView v; + return ((v = writeLockView) != null ? v : + (writeLockView = new WriteLockView())); + } + + /** + * Returns a {@link ReadWriteLock} view of this MultiLock in + * which the {@link ReadWriteLock#readLock()} method is mapped to + * {@link #asReadLock()}, and {@link ReadWriteLock#writeLock()} to + * {@link #asWriteLock()}. + * + * @return the lock + */ + public ReadWriteLock asReadWriteLock() { + ReadWriteLockView v; + return ((v = readWriteLockView) != null ? v : + (readWriteLockView = new ReadWriteLockView())); + } + + static class Sync extends AbstractQueuedLongSynchronizer { + + static long xCount(long c) { return (c & X_FIELD) >>> 48; } + static long sCount(long c) { return (c & S_FIELD) >> 32; } + static long ixCount(long c) { return (c & IX_FIELD) >> 16; } + static long isCount(long c) { return c & IS_FIELD; } + + // store's per-thread state + static class HoldCounter { + final long tid = Thread.currentThread().getId(); + long state = 0; + } + + static class ThreadLocalHoldCounter extends ThreadLocal { + @Override + protected HoldCounter initialValue() { + return new HoldCounter(); + } + } + + final ThreadLocalHoldCounter holdCounts; + + HoldCounter cachedHoldCounter; + + Sync() { + holdCounts = new ThreadLocalHoldCounter(); + setState(getState()); // ensures visibility of holdCounts + } + + @Override + protected boolean tryAcquire(long arg) { + Thread current = Thread.currentThread(); + long c = getState(); + if (c != 0) { + long x = c & X_FIELD; + // (Note: if c != 0 and x == 0 then non-exclusive count != 0) + if (x == 0) { + // Check non-exclusive counts are only for current. + // i.e. are we upgrading? + HoldCounter rh = cachedHoldCounter; + if (rh == null || rh.tid != current.getId()) + rh = holdCounts.get(); + long group = c - rh.state; + if ((group & NON_X_FIELDS) != 0) { + // current thread is not only non-exclusive user + return false; + } + } + else if (current != getExclusiveOwnerThread()) { + return false; + } + } + if (!compareAndSetState(c, c + X_UNIT)) + return false; + setExclusiveOwnerThread(current); + return true; + } + + @Override + protected boolean tryRelease(long arg) { + long nextc = getState() - arg; + if (Thread.currentThread() != getExclusiveOwnerThread()) { + throw new IllegalMonitorStateException(); + } + if ((nextc & X_FIELD) == 0) { + setExclusiveOwnerThread(null); + setState(nextc); + return true; + } + else { + setState(nextc); + return false; + } + } + + @Override + protected long tryAcquireShared(long arg) { + Thread current = Thread.currentThread(); + for (;;) { + long c = getState(); + // someone else already is X + if ((c & X_FIELD) != 0 && + getExclusiveOwnerThread() != current) + return -1; + // either no X or current is X + if (getExclusiveOwnerThread() == current) { + if (updateState(c, arg, current)) { + return 0; + } + } + else if (arg == IS_UNIT) { // IS is compatible with S, IX, IS + if (updateState(c, arg, current)) { + return 1; + } + } + else if (arg == IX_UNIT) { + // two cases: S == 0 (ok) and S != 0 (thread check) + long s = c & S_FIELD; + if (s == 0) { + // ok + if (updateState(c, arg, current)) { + return 1; + } + } + else { + // Check if current thread is the only S + // TODO: optimise so that re-entrant IX locking is fast + HoldCounter rh = null; //cachedHoldCounter; + if (rh == null || rh.tid != current.getId()) + rh = holdCounts.get(); + long group = c - rh.state; + if ((group & S_FIELD) == 0) { + // current is only S + if (compareAndSetState(c, c + arg)) { + rh.state += arg; + cachedHoldCounter = rh; + return 1; // still return 1 because IS is always compatible + } + } else { + return -1; + } + } + } + else if (arg == S_UNIT) { + // two cases: IX == 0 (ok) and IX != 0 (thread check) + long ix = c & IX_FIELD; + if (ix == 0) { + // ok + if (updateState(c, arg, current)) { + return 1; + } + } + else { + // check if current thread is the only IX + // TODO: optimise so that re-entrant S locking is fast + HoldCounter rh = cachedHoldCounter; + if (rh == null || rh.tid != current.getId()) + rh = holdCounts.get(); + long group = c - rh.state; + if ((group & IX_FIELD) == 0) { + // current is only IX + if (compareAndSetState(c, c + arg)) { + rh.state += arg; + cachedHoldCounter = rh; + return 1; // still return 1 because IS is always compatible + } + } else { + return -1; + } + } + } + } + + + } + + private boolean updateState(long c, long arg, Thread current) { + if (compareAndSetState(c, c + arg)) { + HoldCounter rh = cachedHoldCounter; + if (rh == null || rh.tid != current.getId()) + cachedHoldCounter = rh = holdCounts.get(); + rh.state += arg; + return true; + } + return false; + } + + @Override + protected boolean tryReleaseShared(long arg) { + HoldCounter rh = cachedHoldCounter; + Thread current = Thread.currentThread(); + if (rh == null || rh.tid != current.getId()) + rh = holdCounts.get(); + rh.state -= arg; + for (;;) { + long c = getState(); + long nextc = c - arg; + if (compareAndSetState(c, nextc)) { + if ((nextc & X_FIELD) == 0) { + if (arg == S_UNIT) { + return (nextc & S_FIELD) == 0; + } + if (arg == IS_UNIT) { + return (nextc & IS_FIELD) == 0; + } + if (arg == IX_UNIT) { + return (nextc & IX_FIELD) == 0; + } + } + else { + return false; + } + } + } + } + + final int getIntentionReadLockCount() { + return (int)isCount(getState()); + } + + final int getReadLockCount() { + return (int)sCount(getState()); + } + + final int getIntentionWriteLockCount() { + return (int)ixCount(getState()); + } + + final int getWriteLockCount() { + return (int)xCount(getState()); + } + + final int getIntentionReadHoldCount() { + if (getIntentionReadLockCount() == 0) + return 0; + + final Thread current = Thread.currentThread(); + final HoldCounter rh = cachedHoldCounter; + if (rh != null && rh.tid == getThreadId(current)) + return (int)isCount(rh.state); + + int count = (int)isCount(holdCounts.get().state); + //if (count == 0) readHolds.remove(); + return count; + } + + final int getReadHoldCount() { + if (getReadLockCount() == 0) + return 0; + + final Thread current = Thread.currentThread(); + final HoldCounter rh = cachedHoldCounter; + if (rh != null && rh.tid == getThreadId(current)) + return (int)sCount(rh.state); + + int count = (int)sCount(holdCounts.get().state); + //if (count == 0) readHolds.remove(); + return count; + } + + final int getIntentionWriteHoldCount() { + if (getIntentionWriteLockCount() == 0) + return 0; + + final Thread current = Thread.currentThread(); + final HoldCounter rh = cachedHoldCounter; + if (rh != null && rh.tid == getThreadId(current)) + return (int)ixCount(rh.state); + + int count = (int)ixCount(holdCounts.get().state); + //if (count == 0) readHolds.remove(); + return count; + } + + + final int getWriteHoldCount() { + if (getWriteLockCount() == 0) + return 0; + + final Thread current = Thread.currentThread(); + final HoldCounter rh = cachedHoldCounter; + if (rh != null && rh.tid == getThreadId(current)) + return (int)xCount(rh.state); + + int count = (int)xCount(holdCounts.get().state); + //if (count == 0) readHolds.remove(); + return count; + } + + @Override + public boolean isHeldExclusively() { + return super.isHeldExclusively(); + } + } + + public void readLock() { + sync.acquireShared(S_UNIT); + } + + public boolean tryReadLock() { + return sync.tryAcquireShared(S_UNIT) >= 0; + } + + public boolean tryReadLock(final long timeout, final TimeUnit unit) throws InterruptedException { + return sync.tryAcquireSharedNanos(S_UNIT, unit.toNanos(timeout)); + } + + public void readLockInterruptibly() throws InterruptedException { + sync.acquireSharedInterruptibly(S_UNIT); + } + + public void writeLock() { + sync.acquire(X_UNIT); + } + + public boolean tryWriteLock() { + return sync.tryAcquire(X_UNIT); + } + + public boolean tryWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException { + return sync.tryAcquireNanos(X_UNIT, unit.toNanos(timeout)); + } + + public void writeLockInterruptibly() throws InterruptedException { + sync.acquireInterruptibly(X_UNIT); + } + + public void intentionReadLock() { + sync.acquireShared(IS_UNIT); + } + + public boolean tryIntentionReadLock() { + return sync.tryAcquireShared(IS_UNIT) >= 0; + } + + public boolean tryIntentionReadLock(final long timeout, final TimeUnit unit) throws InterruptedException { + return sync.tryAcquireSharedNanos(IS_UNIT, unit.toNanos(timeout)); + } + + public void intentionReadLockInterruptibly() throws InterruptedException { + sync.acquireSharedInterruptibly(IS_UNIT); + } + + public void intentionWriteLock() { + sync.acquireShared(IX_UNIT); + } + + public boolean tryIntentionWriteLock() { + return sync.tryAcquireShared(IX_UNIT) >= 0; + } + + public boolean tryIntentionWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException { + return sync.tryAcquireSharedNanos(IX_UNIT, unit.toNanos(timeout)); + } + + public void intentionWriteLockInterruptibly() throws InterruptedException { + sync.acquireSharedInterruptibly(IX_UNIT); + } + + public void unlockRead() { + sync.releaseShared(S_UNIT); + } + + public void unlockWrite() { + sync.release(X_UNIT); + } + + public void unlockIntentionRead() { + sync.releaseShared(IS_UNIT); + } + + public void unlockIntentionWrite() { + sync.releaseShared(IX_UNIT); + } + + /** + * Queries the number of intention read locks held for this lock. This + * method is designed for use in monitoring system state, not for + * synchronization control. + * @return the number of intention read locks held + */ + public int getIntentionReadLockCount() { + return sync.getIntentionReadLockCount(); + } + + /** + * Queries the number of read locks held for this lock. This + * method is designed for use in monitoring system state, not for + * synchronization control. + * @return the number of read locks held + */ + public int getReadLockCount() { + return sync.getReadLockCount(); + } + + /** + * Queries the number of intention write locks held for this lock. This + * method is designed for use in monitoring system state, not for + * synchronization control. + * @return the number of intention write locks held + */ + public int getIntentionWriteLockCount() { + return sync.getIntentionWriteLockCount(); + } + + /** + * Queries the number of write locks held for this lock. This + * method is designed for use in monitoring system state, not for + * synchronization control. + * @return the number of write locks held + */ + public int getWriteLockCount() { + return sync.getWriteLockCount(); + } + + /** + * Queries the number of reentrant intention read holds on this lock by the + * current thread. A reader thread has a hold on a lock for + * each lock action that is not matched by an unlock action. + * + * @return the number of holds on the intention read lock by the current thread, + * or zero if the intention read lock is not held by the current thread + */ + public int getIntentionReadHoldCount() { + return sync.getIntentionReadHoldCount(); + } + + /** + * Queries the number of reentrant read holds on this lock by the + * current thread. A reader thread has a hold on a lock for + * each lock action that is not matched by an unlock action. + * + * @return the number of holds on the read lock by the current thread, + * or zero if the read lock is not held by the current thread + */ + public int getReadHoldCount() { + return sync.getReadHoldCount(); + } + + /** + * Queries the number of reentrant intention write holds on this lock by the + * current thread. A reader thread has a hold on a lock for + * each lock action that is not matched by an unlock action. + * + * @return the number of holds on the intention write lock by the current thread, + * or zero if the intention write lock is not held by the current thread + */ + public int getIntentionWriteHoldCount() { + return sync.getIntentionWriteHoldCount(); + } + + /** + * Queries the number of reentrant write holds on this lock by the + * current thread. A reader thread has a hold on a lock for + * each lock action that is not matched by an unlock action. + * + * @return the number of holds on the write lock by the current thread, + * or zero if the write lock is not held by the current thread + */ + public int getWriteHoldCount() { + return sync.getWriteHoldCount(); + } + + + + /** + * Queries whether any threads are waiting to acquire the read or + * write lock. Note that because cancellations may occur at any + * time, a {@code true} return does not guarantee that any other + * thread will ever acquire a lock. This method is designed + * primarily for use in monitoring of the system state. + * + * @return {@code true} if there may be other threads waiting to + * acquire the lock + */ + public final boolean hasQueuedThreads() { + return sync.hasQueuedThreads(); + } + + /** + * See {@link java.util.concurrent.locks.AbstractQueuedLongSynchronizer#isHeldExclusively()} + * + * @return true if the current thread holds the write lock. + */ + public boolean isWriteLockedByCurrentThread() { + return sync.isHeldExclusively(); + } + + /** + * Returns the thread id for the given thread. We must access + * this directly rather than via method Thread.getId() because + * getId() is not final, and has been known to be overridden in + * ways that do not preserve unique mappings. + */ + static final long getThreadId(Thread thread) { + return UNSAFE.getLongVolatile(thread, TID_OFFSET); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long TID_OFFSET; + static { + try { + final Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); + singleoneInstanceField.setAccessible(true); + UNSAFE = (Unsafe) singleoneInstanceField.get(null); + + final Class tk = Thread.class; + TID_OFFSET = UNSAFE.objectFieldOffset + (tk.getDeclaredField("tid")); + } catch (final Exception e) { + throw new Error(e); + } + } +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/CompatibilityTest.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/CompatibilityTest.java new file mode 100644 index 0000000..0ca27e6 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/CompatibilityTest.java @@ -0,0 +1,260 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static uk.ac.ic.doc.slurp.multilock.Constants.LOCK_ACQUISITION_TIMEOUT; +import static uk.ac.ic.doc.slurp.multilock.LockMode.*; + +/** + * Tests which check the compatibility of lock modes. + * + * For MultiLock the compatibility matrix is as described in the + * 1975 paper by Gray et al. "Granularity of Locks in a Shared Data Base". + * + * |----------------------------------| + * | COMPATIBILITY | + * |----------------------------------| + * | | IS | IX | S | SIX | X | + * |----------------------------------| + * | IS | YES | YES | YES | YES | NO | + * | IX | YES | YES | NO | NO | NO | + * | S | YES | NO | YES | NO | NO | + * | SIX | YES | NO | NO | NO | NO | + * | X | NO | NO | NO | NO | NO | + * |----------------------------------| + * + * Compatibility is asserted by having a thread t1 acquire + * a lock mode, and thread t2 acquire another lock mode. Both + * threads must acquire their lock modes within a defined + * timeout for the lock modes to be considered compatible. + * + * @author Adam Retter + */ +public class CompatibilityTest { + + static List compatibleModesProvider() { + return Arrays.asList( + Arguments.of(IS, IS, true), + Arguments.of(IS, IX, true), + Arguments.of(IS, S, true), + Arguments.of(IS, SIX, true), + Arguments.of(IS, X, false), + + Arguments.of(IX, IS, true), + Arguments.of(IX, IX, true), + Arguments.of(IX, S, false), + Arguments.of(IX, SIX, false), + Arguments.of(IX, X, false), + + Arguments.of(S, IS, true), + Arguments.of(S, IX, false), + Arguments.of(S, S, true), + Arguments.of(S, SIX, false), + Arguments.of(S, X, false), + + Arguments.of(SIX, IS, true), + Arguments.of(SIX, IX, false), + Arguments.of(SIX, S, false), + Arguments.of(SIX, SIX, false), + Arguments.of(SIX, X, false), + + Arguments.of(X, IS, false), + Arguments.of(X, IX, false), + Arguments.of(X, S, false), + Arguments.of(X, SIX, false), + Arguments.of(X, X, false) + ); + } + + @ParameterizedTest(name = "{0} and {1}") + @DisplayName("Compatible Modes") + @MethodSource("compatibleModesProvider") + public void compatible(final LockMode mode1, final LockMode mode2, final boolean compatible) + throws InterruptedException, ExecutionException { + if (compatible) { + assertCompatible(mode1, mode2, (mode, multiLock) -> { mode.lock(multiLock); return true; }); + } else { + assertNotCompatible(mode1, mode2, (mode, multiLock) -> { mode.lock(multiLock); return true; }, true); + } + } + + @ParameterizedTest(name = "{0} and {1}") + @DisplayName("Compatible Modes Interruptibly") + @MethodSource("compatibleModesProvider") + public void compatibleInterruptibly(final LockMode mode1, final LockMode mode2, final boolean compatible) + throws InterruptedException, ExecutionException { + if (compatible) { + assertCompatible(mode1, mode2, (mode, multiLock) -> { mode.lockInterruptibly(multiLock); return true; }); + } else { + assertNotCompatible(mode1, mode2, (mode, multiLock) -> { mode.lockInterruptibly(multiLock); return true; }, true); + } + } + + @ParameterizedTest(name = "{0} and {1}") + @DisplayName("Compatible Modes Try") + @MethodSource("compatibleModesProvider") + public void compatibleTry(final LockMode mode1, final LockMode mode2, final boolean compatible) + throws InterruptedException, ExecutionException { + if (compatible) { + assertCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock)); + } else { + assertNotCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock), false); + } + } + + @ParameterizedTest(name = "{0} and {1}") + @DisplayName("Compatible Modes Try (short timeout)") + @MethodSource("compatibleModesProvider") + public void compatibleTryShortTimeout(final LockMode mode1, final LockMode mode2, final boolean compatible) + throws InterruptedException, ExecutionException { + if (compatible) { + assertCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT / 2, TimeUnit.MILLISECONDS)); + } else { + assertNotCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT / 2, TimeUnit.MILLISECONDS), false); + } + } + + @ParameterizedTest(name = "{0} and {1}") + @DisplayName("Compatible Modes Try (long timeout)") + @MethodSource("compatibleModesProvider") + public void compatibleTryLongTimeout(final LockMode mode1, final LockMode mode2, final boolean compatible) + throws InterruptedException, ExecutionException { + if (compatible) { + assertCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT * 2, TimeUnit.MILLISECONDS)); + } else { + // NOTE: we set the blockingAcquisition=true parameter, as the timeout is greater than the ExecutorService's LOCK_ACQUISITION_TIMEOUT + assertNotCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT * 2, TimeUnit.MILLISECONDS), true); + } + } + + /** + * Assert that two lock modes are compatible. + * + * @param mode1 the first lock mode + * @param mode2 the second lock mode + * @param lockFn the function for acquiring a lock + */ + private static void assertCompatible(final LockMode mode1, final LockMode mode2, final Locker lockFn) + throws InterruptedException, ExecutionException { + final List> futures = checkCompatibility(mode1, mode2, lockFn); + for (final Future future : futures) { + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + + assertTrue(future.get()); + } + } + + /** + * Assert that two lock modes are not compatible. + * + * @param mode1 the first lock mode + * @param mode2 the second lock mode + * @param lockFn the function for acquiring a lock + * @param blockingAcquisition true if the {@code lockFn} makes lock acquisition calls which block + * until the lock is granted, false otherwise. + */ + private static void assertNotCompatible(final LockMode mode1, final LockMode mode2, final Locker lockFn, + final boolean blockingAcquisition) + throws InterruptedException, ExecutionException { + final List> futures = checkCompatibility(mode1, mode2, lockFn); + + Boolean locked = null; + + for (final Future future : futures) { + assertTrue(future.isDone()); + + if (!future.isCancelled()) { + if (locked == null) { + locked = future.get(); + } else { + locked &= future.get(); + } + } + } + + /* + * `locked` will be null if all futures were cancelled, + * this is because each of our LockAcquirer(s) only complete + * if all acquisitions succeed. When using blocking acquisitions + * if one acquisition fails, no tasks complete, so all futures + * end up marked as cancelled. See the latch in LockAcquirer#call() + */ + assertTrue((blockingAcquisition && locked == null) || (!blockingAcquisition && !locked)); + } + + private static List> checkCompatibility(final LockMode mode1, final LockMode mode2, + final Locker lockFn) throws InterruptedException { + final MultiLock multiLock = new MultiLock(); + + final CountDownLatch latch = new CountDownLatch(2); + + final Callable thread1 = new LockAcquirer(multiLock, mode1, lockFn, latch); + final Callable thread2 = new LockAcquirer(multiLock, mode2, lockFn, latch); + + final ExecutorService executorService = Executors.newFixedThreadPool(2); + return executorService.invokeAll(Arrays.asList(thread1, thread2), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + } + + private static class LockAcquirer implements Callable { + private final MultiLock multiLock; + private final LockMode lockMode; + private final Locker lockFn; + private final CountDownLatch latch; + + public LockAcquirer(final MultiLock multiLock, final LockMode lockMode, final Locker lockFn, + final CountDownLatch latch) { + this.multiLock = multiLock; + this.lockMode = lockMode; + this.lockFn = lockFn; + this.latch = latch; + } + + @Override + public Boolean call() throws Exception { + final boolean locked = lockFn.lock(lockMode, multiLock); + + latch.countDown(); + latch.await(); + + return locked; + } + } +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/Constants.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/Constants.java new file mode 100644 index 0000000..bc44b3a --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/Constants.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +public interface Constants { + + // TODO(AR) this might need to be longer on slower machines... + long LOCK_ACQUISITION_TIMEOUT = 40; +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/DowngradeTest.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/DowngradeTest.java new file mode 100644 index 0000000..09fdbb8 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/DowngradeTest.java @@ -0,0 +1,157 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static uk.ac.ic.doc.slurp.multilock.Constants.LOCK_ACQUISITION_TIMEOUT; +import static uk.ac.ic.doc.slurp.multilock.LockMode.*; + +/** + * Tests which check the facility for downgrading the + * mode of a lock from a stronger to a weaker mode. + * + * We use a separate thread, to avoid locking up test + * suite execution if downgrading is blocked. + * + * For each test, a new thread proceeds as follows: + * 1. Acquire the lock for the strong mode + * 2. Acquire the lock for the weak mode + * 3. Release the lock for the strong mode + * + * If the above cannot be executed within a defined + * timeout, then downgrading is considered to be + * impossible. + * + * @author Adam Retter + */ +public class DowngradeTest { + + static List downgradeModesProvider() { + return Arrays.asList( + Arguments.of(IX, IS), + Arguments.of(S, IS), + Arguments.of(SIX, IX), + Arguments.of(SIX, S), + Arguments.of(SIX, IS), + Arguments.of(X, SIX), + Arguments.of(X, IX), + Arguments.of(X, S), + Arguments.of(X, IS) + ); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Downgrade Lock") + @MethodSource("downgradeModesProvider") + public void downgrade(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertDowngradable(fromMode, toMode, (mode, multiLock) -> { mode.lock(multiLock); return true; }); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Downgrade Lock Interruptibly") + @MethodSource("downgradeModesProvider") + public void downgradeInterruptibly(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertDowngradable(fromMode, toMode, (mode, multiLock) -> { mode.lockInterruptibly(multiLock); return true; }); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Downgrade Lock Try") + @MethodSource("downgradeModesProvider") + public void downgradeTry(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertDowngradable(fromMode, toMode, (mode, multiLock) -> mode.tryLock(multiLock)); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Downgrade Lock Try (short timeout)") + @MethodSource("downgradeModesProvider") + public void downgradeTryShortTimeout(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertDowngradable(fromMode, toMode, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT / 2, TimeUnit.MILLISECONDS)); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Downgrade Lock Try (long timeout)") + @MethodSource("downgradeModesProvider") + public void downgradeTryLongTimeout(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertDowngradable(fromMode, toMode, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT * 2, TimeUnit.MILLISECONDS)); + } + + private static void assertDowngradable(final LockMode from, final LockMode to, final Locker lockFn) + throws InterruptedException, ExecutionException { + final MultiLock multiLock = new MultiLock(); + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll( + Arrays.asList(new Downgrade(multiLock, from, to, lockFn)), + LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + + assertTrue(future.get()); + } + } + + private static class Downgrade implements Callable { + private final MultiLock multiLock; + private final LockMode from; + private final LockMode to; + private final Locker lockFn; + + private Downgrade(final MultiLock multiLock, final LockMode from, final LockMode to, final Locker lockFn) { + this.multiLock = multiLock; + this.from = from; + this.to = to; + this.lockFn = lockFn; + } + + @Override + public Boolean call() throws InterruptedException { + lockFn.lock(from, multiLock); + lockFn.lock(to, multiLock); + from.unlock(multiLock); + return true; + } + } +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/HierarchyTest.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/HierarchyTest.java new file mode 100644 index 0000000..5699c10 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/HierarchyTest.java @@ -0,0 +1,891 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static uk.ac.ic.doc.slurp.multilock.LockMode.*; + +/** + * Uses a lock hierarchy of two levels, root and child + * to test what lock acquisitions may be made on the child + * in the presence of locks on the root. + * + * Functions named like {@code t1_root_IS_t2_child_IX} + * should be read as, Test where: + * Thread 1 has IS lock on root, + * and Thread 2 will attempt IX lock on child. + * + * There are two types of tests here: + * + * 1. Single threaded tests which check what modes the accessor + * thread is allowed to take on the child after locking the root. + * + * 2. Bi-threaded tests where thread t1 locks the root, and then + * thread t2 tries to lock the child. + * + * @author Adam Retter + */ +public class HierarchyTest { + + private static final long LOCK_ACQUISITION_TIMEOUT = 20; // TODO(AR) this might need to be longer on slower machines... + + /* single threaded tests below */ + + @Test + public void t1_root_IS_t1_child_IS() throws ExecutionException, InterruptedException { + assertLockRootThenChild(IS, IS); + } + + @Test + public void t1_root_IS_t1_child_IS_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(IS, IS); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_IS_t1_child_IX() throws ExecutionException, InterruptedException { + assertLockRootThenNotChild(IS, IX); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_IS_t1_child_IX_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenNotChildInterruptibly(IS, IX); + } + + @Test + public void t1_root_IS_t1_child_S() throws ExecutionException, InterruptedException { + assertLockRootThenChild(IS, S); + } + + @Test + public void t1_root_IS_t1_child_S_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(IS, S); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_IS_t1_child_SIX() throws ExecutionException, InterruptedException { + assertLockRootThenNotChild(IS, SIX); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_IS_t1_child_SIX_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenNotChildInterruptibly(IS, SIX); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_IS_t1_child_X() throws ExecutionException, InterruptedException { + assertLockRootThenNotChild(IS, X); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_IS_t1_child_X_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenNotChildInterruptibly(IS, X); + } + + @Test + public void t1_root_IX_t1_child_IS() throws ExecutionException, InterruptedException { + assertLockRootThenChild(IX, IS); + } + + @Test + public void t1_root_IX_t1_child_IS_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(IX, IS); + } + + @Test + public void t1_root_IX_t1_child_IX() throws ExecutionException, InterruptedException { + assertLockRootThenChild(IX, IX); + } + + @Test + public void t1_root_IX_t1_child_IX_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(IX, IX); + } + + @Test + public void t1_root_IX_t1_child_S() throws ExecutionException, InterruptedException { + assertLockRootThenChild(IX, S); + } + + @Test + public void t1_root_IX_t1_child_S_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(IX, S); + } + + @Test + public void t1_root_IX_t1_child_SIX() throws ExecutionException, InterruptedException { + assertLockRootThenChild(IX, SIX); + } + + @Test + public void t1_root_IX_t1_child_SIX_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(IX, SIX); + } + + @Test + public void t1_root_IX_t1_child_X() throws ExecutionException, InterruptedException { + assertLockRootThenChild(IX, X); + } + + @Test + public void t1_root_IX_t1_child_X_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(IX, X); + } + + //TODO(AR) need tests for t1_root_S_child... -- I think it's likely that S on root should only be able to get S on child + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_SIX_t1_child_IS() throws ExecutionException, InterruptedException { + assertLockRootThenNotChild(SIX, IS); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_SIX_t1_child_IS_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenNotChildInterruptibly(SIX, IS); + } + + @Test + public void t1_root_SIX_t1_child_IX() throws ExecutionException, InterruptedException { + assertLockRootThenChild(SIX, IX); + } + + @Test + public void t1_root_SIX_t1_child_IX_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(SIX, IX); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_SIX_t1_child_S() throws ExecutionException, InterruptedException { + assertLockRootThenNotChild(SIX, S); + } + + @Disabled("TODO - page 7 of Gray's paper - Granularity of Locks in a Shared Data Base") + @Test + public void t1_root_SIX_t1_child_S_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenNotChildInterruptibly(SIX, S); + } + + @Test + public void t1_root_SIX_t1_child_SIX() throws ExecutionException, InterruptedException { + assertLockRootThenChild(SIX, SIX); + } + + @Test + public void t1_root_SIX_t1_child_SIX_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(SIX, SIX); + } + + @Test + public void t1_root_SIX_t1_child_X() throws ExecutionException, InterruptedException { + assertLockRootThenChild(SIX, X); + } + + @Test + public void t1_root_SIX_t1_child_X_interruptibly() throws ExecutionException, InterruptedException { + assertLockRootThenChildInterruptibly(SIX, X); + } + + //TODO(AR) need tests for t1_root_X_child... -- I think it's likely that X on root should only be able to get X on child + + + /* bi-threaded tests below */ + + @Test + public void t1_root_IS_t2_child_IS() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, IS); + + } + + @Test + public void t1_root_IS_t2_child_IS_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, IS); + + } + + @Test + public void t1_root_IS_t2_child_IX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, IX); + } + + @Test + public void t1_root_IS_t2_child_IX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, IX); + } + + @Test + public void t1_root_IS_t2_child_S() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, S); + } + + @Test + public void t1_root_IS_t2_child_S_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, S); + } + + @Test + public void t1_root_IS_t2_child_SIX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, SIX); + } + + @Test + public void t1_root_IS_t2_child_SIX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, SIX); + } + + @Test + public void t1_root_IS_t2_child_X() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, X); + } + + @Test + public void t1_root_IS_t2_child_X_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IS.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, X); + } + + @Test + public void t1_root_IX_t2_child_IS() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, IS); + + } + + @Test + public void t1_root_IX_t2_child_IS_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, IS); + + } + + @Test + public void t1_root_IX_t2_child_IX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, IX); + } + + @Test + public void t1_root_IX_t2_child_IX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, IX); + } + + @Test + public void t1_root_IX_t2_child_S() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, S); + } + + @Test + public void t1_root_IX_t2_child_S_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, S); + } + + @Test + public void t1_root_IX_t2_child_SIX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, SIX); + } + + @Test + public void t1_root_IX_t2_child_SIX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, SIX); + } + + @Test + public void t1_root_IX_t2_child_X() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, SIX); + } + + @Test + public void t1_root_IX_t2_child_X_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + IX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, SIX); + } + + @Test + public void t1_root_S_t2_child_IS() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, IS); + } + + @Test + public void t1_root_S_t2_child_IS_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, IS); + } + + @Test + public void t1_root_S_t2_child_IX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S on root, other thread attempts IX on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChild(child, IX); + } + + @Test + public void t1_root_S_t2_child_IX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S on root, other thread attempts IX on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, IX); + } + + @Test + public void t1_root_S_t2_child_S() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, S); + } + + @Test + public void t1_root_S_t2_child_S_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, S); + } + + @Test + public void t1_root_S_t2_child_SIX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S on root, other thread attempts SIX on child (which attempts S+IX on root). S is not compatible with IX + assertOtherThreadNotLockableChild(child, SIX); + } + + @Test + public void t1_root_S_t2_child_SIX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S on root, other thread attempts SIX on child (which attempts S+IX on root). S is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, SIX); + } + + @Test + public void t1_root_S_t2_child_X() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S on root, other thread attempts X on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChild(child, X); + } + + @Test + public void t1_root_S_t2_child_X_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + S.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S on root, other thread attempts X on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, X); + } + + @Test + public void t1_root_SIX_t2_child_IS() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, IS); + } + + @Test + public void t1_root_SIX_t2_child_IS_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, IS); + } + + @Test + public void t1_root_SIX_t2_child_IX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S+IX on root, other thread attempts IX on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChild(child, IX); + } + + @Test + public void t1_root_SIX_t2_child_IX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S+IX on root, other thread attempts IX on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, IX); + } + + @Test + public void t1_root_SIX_t2_child_S() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChild(child, S); + } + + @Test + public void t1_root_SIX_t2_child_S_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + assertOtherThreadLockableChildInterruptibly(child, S); + } + + @Test + public void t1_root_SIX_t2_child_SIX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S+IX on root, other thread attempts SIX on child (which attempts IS+IX on root). S is not compatible with IX + assertOtherThreadNotLockableChild(child, SIX); + } + + @Test + public void t1_root_SIX_t2_child_SIX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S+IX on root, other thread attempts SIX on child (which attempts IS+IX on root). S is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, SIX); + } + + @Test + public void t1_root_SIX_t2_child_X() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S+IX on root, other thread attempts X on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChild(child, X); + } + + @Test + public void t1_root_SIX_t2_child_X_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + SIX.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds S+IX on root, other thread attempts X on child (which attempts IX on root). S is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, X); + } + + @Test + public void t1_root_X_t2_child_IS() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts IS on child (which attempts IS on root). X is not compatible with IS + assertOtherThreadNotLockableChild(child, IS); + } + + @Test + public void t1_root_X_t2_child_IS_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts IS on child (which attempts IS on root). X is not compatible with IS + assertOtherThreadNotLockableChildInterruptibly(child, IS); + } + + @Test + public void t1_root_X_t2_child_IX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts IX on child (which attempts IX on root). X is not compatible with IX + assertOtherThreadNotLockableChild(child, IX); + } + + @Test + public void t1_root_X_t2_child_IX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts IX on child (which attempts IX on root). X is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, IX); + } + + @Test + public void t1_root_X_t2_child_S() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts S on child (which attempts IS on root). X is not compatible with IS + assertOtherThreadNotLockableChild(child, S); + } + + @Test + public void t1_root_X_t2_child_S_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts S on child (which attempts IS on root). X is not compatible with IS + assertOtherThreadNotLockableChildInterruptibly(child, S); + } + + @Test + public void t1_root_X_t2_child_SIX() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts SIX on child (which attempts IS+IX on root). X is not compatible with IS or IX + assertOtherThreadNotLockableChild(child, SIX); + } + + @Test + public void t1_root_X_t2_child_SIX_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts SIX on child (which attempts IS+IX on root). X is not compatible with IS or IX + assertOtherThreadNotLockableChildInterruptibly(child, SIX); + } + + @Test + public void t1_root_X_t2_child_X() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lock(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts X on child (which attempts IX on root). X is not compatible with IX + assertOtherThreadNotLockableChild(child, X); + } + + @Test + public void t1_root_X_t2_child_X_interruptibly() throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + X.lockInterruptibly(root); + + final MultiLock child = new HierarchicalMultiLock(root); + // notLockable = test thread holds X on root, other thread attempts X on child (which attempts IX on root). X is not compatible with IX + assertOtherThreadNotLockableChildInterruptibly(child, X); + } + + private static void assertLockRootThenChild(final LockMode rootMode, final LockMode childMode) throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + final MultiLock child = new HierarchicalMultiLock(root); + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new RootAndChildLockAcquirer(root, rootMode, child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + + assertFalse(future.isCancelled()); + + assertTrue(future.get()); + } + } + + private static void assertLockRootThenChildInterruptibly(final LockMode rootMode, final LockMode childMode) throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + final MultiLock child = new HierarchicalMultiLock(root); + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new RootAndChildLockInterruptiblyAcquirer(root, rootMode, child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + + assertFalse(future.isCancelled()); + + assertTrue(future.get()); + } + } + + private static void assertLockRootThenNotChild(final LockMode rootMode, final LockMode childMode) throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + final MultiLock child = new HierarchicalMultiLock(root); + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new RootAndChildLockAcquirer(root, rootMode, child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + + assertTrue(future.isCancelled()); + } + } + + private static void assertLockRootThenNotChildInterruptibly(final LockMode rootMode, final LockMode childMode) throws InterruptedException, ExecutionException { + final MultiLock root = new MultiLock(); + final MultiLock child = new HierarchicalMultiLock(root); + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new RootAndChildLockInterruptiblyAcquirer(root, rootMode, child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + + assertTrue(future.isCancelled()); + } + } + + private static void assertOtherThreadLockableChild(final MultiLock child, final LockMode childMode) throws InterruptedException, ExecutionException { + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new ChildLockAcquirer(child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + + assertTrue(future.get()); + } + } + + private static void assertOtherThreadLockableChildInterruptibly(final MultiLock child, final LockMode childMode) throws InterruptedException, ExecutionException { + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new ChildLockInterruptiblyAcquirer(child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + + assertTrue(future.get()); + } + } + + private static void assertOtherThreadNotLockableChild(final MultiLock child, final LockMode childMode) throws InterruptedException, ExecutionException { + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new ChildLockAcquirer(child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + + assertTrue(future.isCancelled()); + } + } + + private static void assertOtherThreadNotLockableChildInterruptibly(final MultiLock child, final LockMode childMode) throws InterruptedException, ExecutionException { + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll(Arrays.asList(new ChildLockInterruptiblyAcquirer(child, childMode)), LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + + assertTrue(future.isCancelled()); + } + } + + private static class RootAndChildLockAcquirer implements Callable { + private final MultiLock root; + private final LockMode rootLockMode; + private final MultiLock child; + private final LockMode childLockMode; + + public RootAndChildLockAcquirer(final MultiLock root, final LockMode rootLockMode, final MultiLock child, final LockMode childLockMode) { + this.root = root; + this.rootLockMode = rootLockMode; + this.child = child; + this.childLockMode = childLockMode; + } + + @Override + public Boolean call() { + rootLockMode.lock(root); + childLockMode.lock(child); + return true; + } + } + + private static class RootAndChildLockInterruptiblyAcquirer implements Callable { + private final MultiLock root; + private final LockMode rootLockMode; + private final MultiLock child; + private final LockMode childLockMode; + + public RootAndChildLockInterruptiblyAcquirer(final MultiLock root, final LockMode rootLockMode, final MultiLock child, final LockMode childLockMode) { + this.root = root; + this.rootLockMode = rootLockMode; + this.child = child; + this.childLockMode = childLockMode; + } + + @Override + public Boolean call() throws InterruptedException { + rootLockMode.lockInterruptibly(root); + childLockMode.lockInterruptibly(child); + return true; + } + } + + private static class ChildLockAcquirer implements Callable { + private final MultiLock child; + private final LockMode lockMode; + + public ChildLockAcquirer(final MultiLock child, final LockMode lockMode) { + this.child = child; + this.lockMode = lockMode; + } + + @Override + public Boolean call() { + lockMode.lock(child); + return true; + } + } + + private static class ChildLockInterruptiblyAcquirer implements Callable { + private final MultiLock child; + private final LockMode lockMode; + + public ChildLockInterruptiblyAcquirer(final MultiLock child, final LockMode lockMode) { + this.child = child; + this.lockMode = lockMode; + } + + @Override + public Boolean call() throws InterruptedException { + lockMode.lockInterruptibly(child); + return true; + } + } +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/Lockable.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/Lockable.java new file mode 100644 index 0000000..9e39906 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/Lockable.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +import java.util.concurrent.locks.*; + +public abstract class Lockable { + public MultiLock mlock = new HierarchicalMultiLock(null); + public ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); + public Lock rlock = rwlock.readLock(); + public Lock wlock = rwlock.writeLock(); +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/Locker.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/Locker.java new file mode 100644 index 0000000..9c06bc4 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/Locker.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +@FunctionalInterface +public interface Locker { + boolean lock(final LockMode lockMode, final MultiLock multiLock) throws InterruptedException; +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/ReentrancyTest.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/ReentrancyTest.java new file mode 100644 index 0000000..0c3fe9f --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/ReentrancyTest.java @@ -0,0 +1,134 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static uk.ac.ic.doc.slurp.multilock.Constants.LOCK_ACQUISITION_TIMEOUT; + +/** + * Tests which check that acquiring the lock + * for a mode is re-entrant / recusive. + * + * We use a separate thread, to avoid locking up test + * suite execution if lock acquisition is not reentrant. + * + * @author Adam Retter + */ +public class ReentrancyTest { + + private static final int REENTER_COUNT = 10; + private static final long RENTER_LOCK_ACQUISITIONS_TIMEOUT = LOCK_ACQUISITION_TIMEOUT * REENTER_COUNT; + + @ParameterizedTest(name = "{0}") + @DisplayName("Reentrant") + @EnumSource(value = LockMode.class) + public void reentrant(final LockMode lockMode) throws InterruptedException, ExecutionException { + assertReentrant(lockMode, (mode, multiLock) -> { mode.lock(multiLock); return true; }); + } + + @ParameterizedTest(name = "{0}") + @DisplayName("Reentrant Interruptibly") + @EnumSource(value = LockMode.class) + public void reentrantInterruptibly(final LockMode lockMode) throws InterruptedException, ExecutionException { + assertReentrant(lockMode, (mode, multiLock) -> { mode.lockInterruptibly(multiLock); return true; }); + } + + @ParameterizedTest(name = "{0}") + @DisplayName("Reentrant Try") + @EnumSource(value = LockMode.class) + public void reentrantTry(final LockMode lockMode) throws InterruptedException, ExecutionException { + assertReentrant(lockMode, (mode, multiLock) -> { mode.tryLock(multiLock); return true; }); + } + + @ParameterizedTest(name = "{0}") + @DisplayName("Reentrant Try (short timeout)") + @EnumSource(value = LockMode.class) + public void reentrantTryShortTimeout(final LockMode lockMode) throws InterruptedException, ExecutionException { + assertReentrant(lockMode, (mode, multiLock) -> { mode.tryLock(multiLock, RENTER_LOCK_ACQUISITIONS_TIMEOUT / 2, TimeUnit.MILLISECONDS); return true; }); + } + + @ParameterizedTest(name = "{0}") + @DisplayName("Reentrant Try (long timeout)") + @EnumSource(value = LockMode.class) + public void reentrantTryLongTimeout(final LockMode lockMode) throws InterruptedException, ExecutionException { + assertReentrant(lockMode, (mode, multiLock) -> { mode.tryLock(multiLock, RENTER_LOCK_ACQUISITIONS_TIMEOUT * 2, TimeUnit.MILLISECONDS); return true; }); + } + + private static void assertReentrant(final LockMode lockMode, final Locker lockFn) + throws InterruptedException, ExecutionException { + final MultiLock multiLock = new MultiLock(); + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll( + Arrays.asList(new Reenter(multiLock, lockMode, lockFn, REENTER_COUNT)), + RENTER_LOCK_ACQUISITIONS_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + + assertEquals(REENTER_COUNT, (int)future.get()); + } + } + + private static class Reenter implements Callable { + private final MultiLock multiLock; + private final LockMode lockMode; + private final Locker lockFn; + private final int count; + + private Reenter(final MultiLock multiLock, final LockMode lockMode, final Locker lockFn, final int count) { + this.multiLock = multiLock; + this.lockMode = lockMode; + this.lockFn = lockFn; + this.count = count; + } + + @Override + public Integer call() throws InterruptedException { + int success = 0; + for (int i = 0; i < count; i++) { + lockFn.lock(lockMode, multiLock); + success++; + } + return success; + } + } +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/UpgradeTest.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/UpgradeTest.java new file mode 100644 index 0000000..c3d894b --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/UpgradeTest.java @@ -0,0 +1,157 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static uk.ac.ic.doc.slurp.multilock.Constants.LOCK_ACQUISITION_TIMEOUT; +import static uk.ac.ic.doc.slurp.multilock.LockMode.*; + +/** + * Tests which check the facility for upgrading the + * mode of a lock from a weaker to a stronger mode. + * + * We use a separate thread, to avoid locking up test + * suite execution if upgrading is blocked. + * + * For each test, a new thread proceeds as follows: + * 1. Acquire the lock for the weak mode + * 2. Acquire the lock for the strong mode + * 3. Release the lock for the weak mode + * + * If the above cannot be executed within a defined + * timeout, then upgrading is considered to be + * impossible. + * + * @author Adam Retter + */ +public class UpgradeTest { + + static List upgradeModesProvider() { + return Arrays.asList( + Arguments.of(IS, IX), + Arguments.of(IS, S), + Arguments.of(IS, SIX), + Arguments.of(IS, X), + Arguments.of(IX, SIX), + Arguments.of(IX, X), + Arguments.of(S, SIX), + Arguments.of(S, X), + Arguments.of(SIX, X) + ); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Upgrade Lock") + @MethodSource("upgradeModesProvider") + public void downgrade(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertUpgradeable(fromMode, toMode, (mode, multiLock) -> { mode.lock(multiLock); return true; }); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Upgrade Lock Interruptibly") + @MethodSource("upgradeModesProvider") + public void downgradeInterruptibly(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertUpgradeable(fromMode, toMode, (mode, multiLock) -> { mode.lockInterruptibly(multiLock); return true; }); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Upgrade Lock Try") + @MethodSource("upgradeModesProvider") + public void downgradeTry(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertUpgradeable(fromMode, toMode, (mode, multiLock) -> mode.tryLock(multiLock)); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Upgrade Lock Try (short timeout)") + @MethodSource("upgradeModesProvider") + public void downgradeTryShortTimeout(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertUpgradeable(fromMode, toMode, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT / 2, TimeUnit.MILLISECONDS)); + } + + @ParameterizedTest(name = "from {0} to {1}") + @DisplayName("Upgrade Lock Try (long timeout)") + @MethodSource("upgradeModesProvider") + public void downgradeTryLongTimeout(final LockMode fromMode, final LockMode toMode) + throws InterruptedException, ExecutionException { + assertUpgradeable(fromMode, toMode, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT * 2, TimeUnit.MILLISECONDS)); + } + + private static void assertUpgradeable(final LockMode from, final LockMode to, final Locker lockFn) + throws InterruptedException, ExecutionException { + final MultiLock multiLock = new MultiLock(); + + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final List> futures = executorService.invokeAll( + Arrays.asList(new Upgrade(multiLock, from, to, lockFn)), + LOCK_ACQUISITION_TIMEOUT, TimeUnit.MILLISECONDS); + + for (final Future future : futures) { + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + + assertTrue(future.get()); + } + } + + private static class Upgrade implements Callable { + private final MultiLock multiLock; + private final LockMode from; + private final LockMode to; + private final Locker lockFn; + + private Upgrade(final MultiLock multiLock, final LockMode from, final LockMode to, final Locker lockFn) { + this.multiLock = multiLock; + this.from = from; + this.to = to; + this.lockFn = lockFn; + } + + @Override + public Boolean call() throws InterruptedException { + lockFn.lock(from, multiLock); + lockFn.lock(to, multiLock); + from.unlock(multiLock); + return true; + } + } +} diff --git a/test/bank/BankTest.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTest.java similarity index 81% rename from test/bank/BankTest.java rename to src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTest.java index ee9b698..f6462a9 100644 --- a/test/bank/BankTest.java +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTest.java @@ -1,36 +1,37 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka +/** + * Copyright © 2010, Khilan Gudka * All rights reserved. * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package test.bank; +package uk.ac.ic.doc.slurp.multilock.bank; import java.io.FileNotFoundException; import java.util.Random; -import java.util.concurrent.atomic.AtomicLong; -import test.Lockable; +import uk.ac.ic.doc.slurp.multilock.Lockable; class Account extends Lockable { diff --git a/test/bank/BankTestMultiLock.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestMultiLock.java similarity index 59% rename from test/bank/BankTestMultiLock.java rename to src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestMultiLock.java index 6733a7a..d1d1427 100644 --- a/test/bank/BankTestMultiLock.java +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestMultiLock.java @@ -1,30 +1,32 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka +/** + * Copyright © 2010, Khilan Gudka * All rights reserved. * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package test.bank; +package uk.ac.ic.doc.slurp.multilock.bank; import java.io.FileNotFoundException; import java.util.Random; @@ -35,16 +37,17 @@ public BankTestMultiLock(int nWithdraw, int nDeposit, int nBranch, int nBank) { super(nWithdraw, nDeposit, nBranch, nBank); } + @Override public void withdraw(Bank b, Random r) { int branchId = r.nextInt(NUM_BRANCHES); int acctId = r.nextInt(NUM_ACCTS_PER_BRANCH); int amt = r.nextInt(60); - b.mlock.lockIntentionWrite(); + b.mlock.intentionWriteLock(); Branch branch = b.branches[branchId]; - branch.mlock.lockIntentionWrite(); + branch.mlock.intentionWriteLock(); Account acct = branch.accounts[acctId]; - acct.mlock.lockWrite(); + acct.mlock.writeLock(); acct.withdraw(amt); @@ -52,17 +55,18 @@ public void withdraw(Bank b, Random r) { branch.mlock.unlockIntentionWrite(); b.mlock.unlockIntentionWrite(); } - + + @Override public void deposit(Bank b, Random r) { int branchId = r.nextInt(NUM_BRANCHES); int acctId = r.nextInt(NUM_ACCTS_PER_BRANCH); int amt = r.nextInt(60); - b.mlock.lockIntentionWrite(); + b.mlock.intentionWriteLock(); Branch branch = b.branches[branchId]; - branch.mlock.lockIntentionWrite(); + branch.mlock.intentionWriteLock(); Account acct = branch.accounts[acctId]; - acct.mlock.lockWrite(); + acct.mlock.writeLock(); acct.deposit(amt); @@ -70,22 +74,24 @@ public void deposit(Bank b, Random r) { branch.mlock.unlockIntentionWrite(); b.mlock.unlockIntentionWrite(); } - + + @Override public void sumOneBranch(Bank b, Random r) { int branchId = r.nextInt(NUM_BRANCHES); - b.mlock.lockIntentionRead(); + b.mlock.intentionReadLock(); Branch branch = b.branches[branchId]; - branch.mlock.lockRead(); + branch.mlock.readLock(); branch.sumBalances(); branch.mlock.unlockRead(); b.mlock.unlockIntentionRead(); } - + + @Override public void sumAll(Bank b) { - b.mlock.lockRead(); + b.mlock.readLock(); b.sumAll(); diff --git a/test/bank/BankTestRWLock.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestRWLock.java similarity index 73% rename from test/bank/BankTestRWLock.java rename to src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestRWLock.java index 4f2a989..59c5341 100644 --- a/test/bank/BankTestRWLock.java +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestRWLock.java @@ -1,30 +1,32 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka +/** + * Copyright © 2010, Khilan Gudka * All rights reserved. * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package test.bank; +package uk.ac.ic.doc.slurp.multilock.bank; import java.io.FileNotFoundException; import java.util.Random; diff --git a/test/bank/BankTestSTM.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestSTM.java similarity index 62% rename from test/bank/BankTestSTM.java rename to src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestSTM.java index 00444d0..fcde445 100644 --- a/test/bank/BankTestSTM.java +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/BankTestSTM.java @@ -1,30 +1,32 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka +/** + * Copyright © 2010, Khilan Gudka * All rights reserved. * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package test.bank; +package uk.ac.ic.doc.slurp.multilock.bank; import java.io.FileNotFoundException; import java.util.Random; diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/Locker.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/Locker.java new file mode 100644 index 0000000..55c3e5a --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/bank/Locker.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock.bank; + +public interface Locker { + +} diff --git a/test/counter/CounterTest.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTest.java similarity index 59% rename from test/counter/CounterTest.java rename to src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTest.java index 805608e..f13c963 100644 --- a/test/counter/CounterTest.java +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTest.java @@ -1,32 +1,34 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka +/** + * Copyright © 2010, Khilan Gudka * All rights reserved. * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package uk.ac.ic.doc.slurp.multilock.counter; -package test.counter; - -import test.Lockable; +import uk.ac.ic.doc.slurp.multilock.Lockable; class Counter extends Lockable { long value = 0; @@ -76,8 +78,8 @@ public void run() { double took = (System.currentTimeMillis() - start)/1000.0; // time in seconds long expected = nThreads*nIncs; double throughput = expected/took; - System.out.println("Expected counter value: " + expected); - System.out.println("Actual counter value: " + c.value); + System.out.println("Expected uk.ac.ic.doc.slurp.multilock.counter value: " + expected); + System.out.println("Actual uk.ac.ic.doc.slurp.multilock.counter value: " + c.value); System.out.println("Running time (secs): " + String.format("%.2f", took)); String throughputStr = String.format("%.2f", throughput); System.out.println("incs/sec: " + throughputStr); diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestMultiLock.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestMultiLock.java new file mode 100644 index 0000000..b820b83 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestMultiLock.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock.counter; + +public class CounterTestMultiLock extends CounterTest { + + @Override + public void inc(Counter c) { + c.mlock.writeLock(); + c.inc(); + c.mlock.unlockWrite(); + } + + /** + * @param args + * @throws InterruptedException + */ + public static void main(String[] args) throws InterruptedException { + CounterTest c = new CounterTestMultiLock(); + c.runExperiment(); + } + +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestRWLock.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestRWLock.java new file mode 100644 index 0000000..5939c01 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestRWLock.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock.counter; + +public class CounterTestRWLock extends CounterTest { + + @Override + public void inc(Counter c) { + c.wlock.lock(); + c.inc(); + c.wlock.unlock(); + } + + /** + * @param args + * @throws InterruptedException + */ + public static void main(String[] args) throws InterruptedException { + CounterTest c = new CounterTestRWLock(); + c.runExperiment(); + } + +} diff --git a/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestSTM.java b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestSTM.java new file mode 100644 index 0000000..8792926 --- /dev/null +++ b/src/test/java/uk/ac/ic/doc/slurp/multilock/counter/CounterTestSTM.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2010, Khilan Gudka + * All rights reserved. + * + * Modifications and additions from the original source code at + * https://github.com/kgudka/java-multilocks are + * Copyright © 2017, Evolved Binary + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package uk.ac.ic.doc.slurp.multilock.counter; + +import org.deuce.Atomic; + +public class CounterTestSTM extends CounterTest { + + @Override + @Atomic + public void inc(Counter c) { + c.inc(); + } + + /** + * @param args + * @throws InterruptedException + */ + public static void main(String[] args) throws InterruptedException { + CounterTest c = new CounterTestSTM(); + c.runExperiment(); + } + +} diff --git a/test/Lockable.java b/test/Lockable.java deleted file mode 100644 index cf84305..0000000 --- a/test/Lockable.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -package test; - -import java.util.concurrent.locks.*; - -import multilock.MultiLock; - -public abstract class Lockable { - public MultiLock mlock = new MultiLock(null); - public ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); - public Lock rlock = rwlock.readLock(); - public Lock wlock = rwlock.writeLock(); -} diff --git a/test/bank/Locker.java b/test/bank/Locker.java deleted file mode 100644 index 1798049..0000000 --- a/test/bank/Locker.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -package test.bank; - -public interface Locker { - -} diff --git a/test/counter/CounterTestMultiLock.java b/test/counter/CounterTestMultiLock.java deleted file mode 100644 index 7162da5..0000000 --- a/test/counter/CounterTestMultiLock.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -package test.counter; - -public class CounterTestMultiLock extends CounterTest { - - @Override - public void inc(Counter c) { - c.mlock.lockWrite(); - c.inc(); - c.mlock.unlockWrite(); - } - - /** - * @param args - * @throws InterruptedException - */ - public static void main(String[] args) throws InterruptedException { - CounterTest c = new CounterTestMultiLock(); - c.runExperiment(); - } - -} diff --git a/test/counter/CounterTestRWLock.java b/test/counter/CounterTestRWLock.java deleted file mode 100644 index b3de308..0000000 --- a/test/counter/CounterTestRWLock.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -package test.counter; - -public class CounterTestRWLock extends CounterTest { - - @Override - public void inc(Counter c) { - c.wlock.lock(); - c.inc(); - c.wlock.unlock(); - } - - /** - * @param args - * @throws InterruptedException - */ - public static void main(String[] args) throws InterruptedException { - CounterTest c = new CounterTestRWLock(); - c.runExperiment(); - } - -} diff --git a/test/counter/CounterTestSTM.java b/test/counter/CounterTestSTM.java deleted file mode 100644 index c94545a..0000000 --- a/test/counter/CounterTestSTM.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2010-2016 Khilan Gudka - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -package test.counter; - -import org.deuce.Atomic; - -public class CounterTestSTM extends CounterTest { - - @Override - @Atomic - public void inc(Counter c) { - c.inc(); - } - - /** - * @param args - * @throws InterruptedException - */ - public static void main(String[] args) throws InterruptedException { - CounterTest c = new CounterTestSTM(); - c.runExperiment(); - } - -}