diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/ContentCache.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/ContentCache.java index e74d9f438fb08033bf508b2abc9a94f78c3cc1d7..83fd664fcc9fb972bc8648f0ddc51920954ba895 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/ContentCache.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/ContentCache.java @@ -26,6 +26,7 @@ import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.io.IOUtils; @@ -245,34 +246,40 @@ public class ContentCache implements IContentCache } } } - private static final class LockManager { - private final Map<String, ReentrantLock> locks = new HashMap<String, ReentrantLock>(); + private static final class LockWithCounter + { + private Lock lock = new ReentrantLock(); + private int count; + } + + private final Map<String, LockWithCounter> locks = new HashMap<String, LockWithCounter>(); void lock(String path) { - ReentrantLock lock; + LockWithCounter lock; synchronized (locks) { lock = locks.get(path); if (lock == null) { - lock = new ReentrantLock(); + lock = new LockWithCounter(); locks.put(path, lock); } + lock.count++; } - lock.lock(); + lock.lock.lock(); } synchronized void unlock(String path) { - ReentrantLock lock = locks.get(path); + LockWithCounter lock = locks.get(path); if (lock != null) { - lock.unlock(); - if (lock.isLocked() == false) + lock.lock.unlock(); + if (--lock.count == 0) { locks.remove(path); } diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/RemoteHierarchicalContentNodeMultiThreadTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/RemoteHierarchicalContentNodeMultiThreadTest.java index 5826fcc637f7619bc8c8d7bd2084decc32203b15..7e2187e6f817c2d9f2b0f555a5a15206becbf999 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/RemoteHierarchicalContentNodeMultiThreadTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/content/RemoteHierarchicalContentNodeMultiThreadTest.java @@ -259,6 +259,87 @@ public class RemoteHierarchicalContentNodeMultiThreadTest extends AbstractRemote context.assertIsSatisfied(); } + + @Test + public void testGetSameFileInThreeThreads() throws Exception + { + ContentCache cache = createCache(false); + final DataSetPathInfo pathInfo = new DataSetPathInfo(); + pathInfo.setRelativePath(remoteFile1.getName()); + pathInfo.setDirectory(false); + final IHierarchicalContentNode node1 = createRemoteNode(pathInfo, cache); + ConsoleLogger logger = new ConsoleLogger(); + final MessageChannel channel1 = + new MessageChannelBuilder(10000).name("1").logger(logger).getChannel(); + final IHierarchicalContentNode node2 = createRemoteNode(pathInfo, cache); + final MessageChannel channel2 = + new MessageChannelBuilder(10000).name("2").logger(logger).getChannel(); + final IHierarchicalContentNode node3 = createRemoteNode(pathInfo, cache); + final MessageChannel channel3 = + new MessageChannelBuilder(10000).name("3").logger(logger).getChannel(); + final MessageChannel channel4 = + new MessageChannelBuilder(10000).name("4").logger(logger).getChannel(); + GetFileRunnable fileRunnable1 = new GetFileRunnable(node1, channel1); + GetFileRunnable fileRunnable2 = new GetFileRunnable(node2, channel2) + { + @Override + public void run() + { + channel1.assertNextMessage(STARTED_MESSAGE); + channel2.send(STARTED_MESSAGE); + super.run(); + } + }; + GetFileRunnable fileRunnable3 = new GetFileRunnable(node3, channel3) + { + @Override + public void run() + { + channel2.assertNextMessage(STARTED_MESSAGE); + channel3.send(STARTED_MESSAGE); + super.run(); + } + }; + final Thread thread1 = new Thread(fileRunnable1, "thread1"); + final Thread thread2 = new Thread(fileRunnable2, "thread2"); + final Thread thread3 = new Thread(fileRunnable3, "thread3"); + context.checking(new Expectations() + { + { + one(remoteDss).getDownloadUrlForFileForDataSet(SESSION_TOKEN, DATA_SET_CODE, + pathInfo.getRelativePath()); + will(new ProxyAction(returnValue(remoteFile1.toURI().toURL().toString())) + { + @Override + protected void doBeforeReturn() + { + channel1.send(STARTED_MESSAGE); + channel3.assertNextMessage(STARTED_MESSAGE); + channel4.send(FINISHED_MESSAGE); + } + }); + } + }); + + thread1.start(); + thread2.start(); + thread3.start(); + channel4.assertNextMessage(FINISHED_MESSAGE); + channel1.assertNextMessage(FINISHED_MESSAGE); + channel2.assertNextMessage(FINISHED_MESSAGE); + channel3.assertNextMessage(FINISHED_MESSAGE); + + File file1 = fileRunnable1.tryGetResult(); + File file2 = fileRunnable2.tryGetResult(); + File file3 = fileRunnable3.tryGetResult(); + assertEquals(new File(workSpace, ContentCache.CACHE_FOLDER + "/" + DATA_SET_CODE + "/" + + remoteFile1.getName()).getAbsolutePath(), file1.getAbsolutePath()); + assertEquals(FILE1_CONTENT, FileUtilities.loadToString(file1).trim()); + assertEquals(file1, file2); + assertEquals(file1, file3); + context.assertIsSatisfied(); + } + @Test public void testGetSameFileInTwoThreadsFirstDownloadFails() throws Exception {