Author: cduffy
Date: Thu Dec 19 21:14:39 2013
New Revision: 1552434
URL: http://svn.apache.org/r1552434
Log:
IVY-1454 handle multi-threading (as opposed to only multi-process) case in
locking
Added:
ant/ivy/core/trunk/test/repositories/ivysettings-with-nio.xml
- copied, changed from r1552403,
ant/ivy/core/trunk/test/repositories/ivysettings.xml
Modified:
ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java
ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyResolveTest.java
Modified:
ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java
URL:
http://svn.apache.org/viewvc/ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java?rev=1552434&r1=1552433&r2=1552434&view=diff
==============================================================================
---
ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java
(original)
+++
ant/ivy/core/trunk/src/java/org/apache/ivy/plugins/lock/FileBasedLockStrategy.java
Thu Dec 19 21:14:39 2013
@@ -21,8 +21,11 @@ import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import org.apache.ivy.util.Message;
@@ -37,8 +40,13 @@ public abstract class FileBasedLockStrat
private FileLocker locker;
private long timeout = DEFAULT_TIMEOUT;
-
- private Map/*<File, Integer>*/ currentLockCounters = new HashMap();
+
+ /**
+ * Lock counter list must be static: locks are implicitly shared to the
+ * entire process, so the list too much be.
+ */
+ private static ConcurrentMap/*<File, Map<Thread, Integer>>*/
currentLockHolders
+ = new ConcurrentHashMap();
protected FileBasedLockStrategy() {
this(new CreateFileLocker(false), false);
@@ -53,40 +61,68 @@ public abstract class FileBasedLockStrat
this.locker = locker;
}
-
protected boolean acquireLock(File file) throws InterruptedException {
+ Thread currentThread = Thread.currentThread();
if (isDebugLocking()) {
debugLocking("acquiring lock on " + file);
}
long start = System.currentTimeMillis();
do {
- synchronized (this) {
- if (hasLock(file)) {
- int holdLocks = incrementLock(file);
+ synchronized (currentLockHolders) {
+ if (isDebugLocking()) {
+ debugLocking("entered synchronized area (locking)");
+ }
+ int lockCount = hasLock(file, currentThread);
+ if (isDebugLocking()) {
+ debugLocking("current status for " + file
+ + " is " + lockCount
+ + " held locks: "
+ + getCurrentLockHolderNames(file));
+ }
+ if (lockCount < 0) {
+ /* Another thread in this process holds the lock; we need
to wait */
+ if (isDebugLocking()) {
+ debugLocking("waiting for another thread to release
the lock: "
+ + getCurrentLockHolderNames(file));
+ }
+ } else if (lockCount > 0) {
+ int holdLocks = incrementLock(file, currentThread);
if (isDebugLocking()) {
debugLocking("reentrant lock acquired on " + file
+ " in " + (System.currentTimeMillis() - start) +
"ms"
+ " - hold locks = " + holdLocks);
}
return true;
- }
- if (locker.tryLock(file)) {
- if (isDebugLocking()) {
- debugLocking("lock acquired on " + file
- + " in " + (System.currentTimeMillis() - start) +
"ms");
+ } else {
+ /* No prior lock on this file is held at all */
+ if (locker.tryLock(file)) {
+ if (isDebugLocking()) {
+ debugLocking("lock acquired on " + file
+ + " in " + (System.currentTimeMillis() -
start) + "ms");
+ }
+ incrementLock(file, currentThread);
+ return true;
}
- incrementLock(file);
- return true;
}
}
+ if (isDebugLocking()) {
+ debugLocking("failed to acquire lock; sleeping for retry...");
+ }
Thread.sleep(SLEEP_TIME);
} while (System.currentTimeMillis() - start < timeout);
return false;
}
protected void releaseLock(File file) {
- synchronized (this) {
- int holdLocks = decrementLock(file);
+ Thread currentThread = Thread.currentThread();
+ if (isDebugLocking()) {
+ debugLocking("releasing lock on " + file);
+ }
+ synchronized (currentLockHolders) {
+ if (isDebugLocking()) {
+ debugLocking("entered synchronized area (unlocking)");
+ }
+ int holdLocks = decrementLock(file, currentThread);
if (holdLocks == 0) {
locker.unlock(file);
if (isDebugLocking()) {
@@ -106,25 +142,105 @@ public abstract class FileBasedLockStrat
Message.info(Thread.currentThread() + " " + System.currentTimeMillis()
+ " " + msg);
}
- private boolean hasLock(File file) {
- Integer c = (Integer) currentLockCounters.get(file);
- return c != null && c.intValue() > 0;
+ /** Determine the state of the lockfile.
+ *
+ * Must be called from within a synchronized block.
+ *
+ * Three possibilities exist:
+ * - The lock is held by the current thread (>0)
+ * - The lock is held by one or more different threads (-1)
+ * - The lock is not held at all (0).
+ *
+ * @param file file to lock
+ * @param forThread thread for which lock status is being queried
+ */
+ private int hasLock(File file, Thread forThread) {
+ Map locksPerThread = (Map) currentLockHolders.get(file);
+ if (locksPerThread == null) {
+ return 0;
+ }
+ if (locksPerThread.isEmpty()) {
+ return 0;
+ }
+ Integer counterObj = (Integer) locksPerThread.get(forThread);
+ int counter = counterObj == null ? 0 : counterObj.intValue();
+ if (counter > 0) {
+ return counter;
+ } else {
+ return -1;
+ }
}
- private int incrementLock(File file) {
- Integer c = (Integer) currentLockCounters.get(file);
- int holdLocks = c == null ? 1 : c.intValue() + 1;
- currentLockCounters.put(file, new Integer(holdLocks));
+ /** Record that this thread holds the lock.
+ *
+ * Asserts that the lock has been previously grabbed by this thread.
+ * Must be called from a synchronized block in which the lock was grabbed.
+ *
+ * @param file file which has been locked
+ * @param forThread thread for which locking occurred
+ * @return number of times this thread has grabbed the lock
+ */
+ private int incrementLock(File file, Thread forThread) {
+ Map locksPerThread = (Map) currentLockHolders.get(file);
+ if (locksPerThread == null) {
+ locksPerThread = new ConcurrentHashMap();
+ currentLockHolders.put(file, locksPerThread);
+ }
+ Integer c = (Integer) locksPerThread.get(forThread);
+ int holdLocks = c == null ? 1 : c.intValue() + 1;
+ locksPerThread.put(forThread, new Integer(holdLocks));
return holdLocks;
}
-
- private int decrementLock(File file) {
- Integer c = (Integer) currentLockCounters.get(file);
- int dc = c == null ? 0 : c.intValue() - 1;
- currentLockCounters.put(file, new Integer(dc));
- return dc;
+
+ /** Decrease depth of this thread's lock.
+ *
+ * Must be called within a synchronized block.
+ *
+ * If this returns 0, the caller is responsible for releasing the lock
+ * within that same block.
+ *
+ * @param file file for which lock depth is being decreased
+ * @param forThread thread for which lock depth is being decreased
+ * @return remaining depth of this lock
+ */
+ private int decrementLock(File file, Thread forThread) {
+ ConcurrentHashMap locksPerThread = (ConcurrentHashMap)
currentLockHolders.get(file);
+ if (locksPerThread == null) {
+ throw new RuntimeException("Calling decrementLock on a thread
which holds no locks");
+ }
+ Integer c = (Integer) locksPerThread.get(forThread);
+ int oldHeldLocks = c == null ? 0 : c.intValue();
+ if (oldHeldLocks <= 0) {
+ throw new RuntimeException("Calling decrementLock on a thread
which holds no locks");
+ }
+ int newHeldLocks = oldHeldLocks - 1;
+ if (newHeldLocks > 0) {
+ locksPerThread.put(forThread, new Integer(newHeldLocks));
+ } else {
+ locksPerThread.remove(forThread);
+ }
+ return newHeldLocks;
}
+ /** Return a string naming the threads which currently hold this lock.
+ */
+ protected String getCurrentLockHolderNames(File file) {
+ StringBuilder sb = new StringBuilder();
+ ConcurrentHashMap m = (ConcurrentHashMap) currentLockHolders.get(file);
+ if (m == null) {
+ return "(NULL)";
+ }
+ Enumeration threads = m.keys();
+ while (threads.hasMoreElements()) {
+ Thread t = (Thread) threads.nextElement();
+ sb.append(t.toString());
+ if (threads.hasMoreElements()) {
+ sb.append(", ");
+ }
+ }
+ return sb.toString();
+ }
+
public static interface FileLocker {
boolean tryLock(File f);
void unlock(File f);
@@ -171,7 +287,7 @@ public abstract class FileBasedLockStrat
*/
public static class NIOFileLocker implements FileLocker {
- private Map locks = new HashMap();
+ private Map locks = new ConcurrentHashMap();
private boolean debugLocking;
public NIOFileLocker(boolean debugLocking) {
@@ -191,8 +307,9 @@ public abstract class FileBasedLockStrat
public boolean tryLock(File file) {
try {
if (file.getParentFile().exists() ||
file.getParentFile().mkdirs()) {
+ // this must not be closed until unlock
RandomAccessFile raf =
- new RandomAccessFile(file, "rw");
+ new RandomAccessFile(file, "rw");
FileLock l = raf.getChannel().tryLock();
if (l != null) {
synchronized (this) {
Modified: ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyResolveTest.java
URL:
http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyResolveTest.java?rev=1552434&r1=1552433&r2=1552434&view=diff
==============================================================================
--- ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyResolveTest.java
(original)
+++ ant/ivy/core/trunk/test/java/org/apache/ivy/ant/IvyResolveTest.java Thu Dec
19 21:14:39 2013
@@ -30,15 +30,17 @@ import org.apache.ivy.util.Message;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Delete;
+import org.apache.tools.ant.taskdefs.Parallel;
public class IvyResolveTest extends TestCase {
private File cache;
+ private Project project;
private IvyResolve resolve;
protected void setUp() throws Exception {
createCache();
- Project project = new Project();
+ project = new Project();
project.setProperty("ivy.settings.file",
"test/repositories/ivysettings.xml");
project.setProperty("ivy.cache.dir", cache.getAbsolutePath());
@@ -62,6 +64,24 @@ public class IvyResolveTest extends Test
del.execute();
}
+ public void testIVY1454() throws Exception {
+ // run init in parent thread, then resolve in children
+ project.setProperty("ivy.settings.file",
"test/repositories/ivysettings-with-nio.xml");
+ project.setProperty("ivy.log.locking", "true");
+ resolve.setFile(new
File("test/java/org/apache/ivy/ant/ivy-simple.xml"));
+
+ Parallel parallel = new Parallel();
+ parallel.setThreadCount(4);
+ parallel.addTask(resolve);
+ parallel.addTask(resolve);
+ parallel.addTask(resolve);
+ parallel.addTask(resolve);
+ parallel.execute();
+
+ assertTrue(getResolvedIvyFileInCache(
+ ModuleRevisionId.newInstance("apache", "resolve-simple",
"1.0")).exists());
+ }
+
public void testIVY779() throws Exception {
Project project = new Project();
project.setProperty("ivy.local.default.root", new
File("test/repositories/norev").getAbsolutePath());
Copied: ant/ivy/core/trunk/test/repositories/ivysettings-with-nio.xml (from
r1552403, ant/ivy/core/trunk/test/repositories/ivysettings.xml)
URL:
http://svn.apache.org/viewvc/ant/ivy/core/trunk/test/repositories/ivysettings-with-nio.xml?p2=ant/ivy/core/trunk/test/repositories/ivysettings-with-nio.xml&p1=ant/ivy/core/trunk/test/repositories/ivysettings.xml&r1=1552403&r2=1552434&rev=1552434&view=diff
==============================================================================
--- ant/ivy/core/trunk/test/repositories/ivysettings.xml (original)
+++ ant/ivy/core/trunk/test/repositories/ivysettings-with-nio.xml Thu Dec 19
21:14:39 2013
@@ -19,7 +19,7 @@
<ivysettings>
<properties file="${ivy.settings.dir}/ivysettings.properties" />
<settings defaultResolver="test"/>
- <caches defaultCacheDir="${cache.dir}" />
+ <caches lockStrategy="artifact-lock-nio" defaultCacheDir="${cache.dir}"
/>
<resolvers>
<chain name="test">
<filesystem name="1">