/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.vfs.server; import junit.framework.TestCase; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * @author <a href="mailto:andrew00x@gmail.com">Andrey Parfonov</a> */ public class PathLockFactoryTest extends TestCase { private final int maxThreads = 3; private final Path path = Path.fromString("/a/b/c"); // Path not need to be real path on file system private PathLockFactory pathLockFactory; @Override protected void setUp() throws Exception { super.setUp(); pathLockFactory = new PathLockFactory(maxThreads); } public void testLock() throws Exception { final AtomicBoolean acquired = new AtomicBoolean(false); final CountDownLatch waiter = new CountDownLatch(1); Thread t = new Thread() { @Override public void run() { try { pathLockFactory.getLock(path, true).acquire(); acquired.set(true); } finally { waiter.countDown(); } } }; t.start(); waiter.await(); assertTrue(acquired.get()); } public void testConcurrentExclusiveLocks() throws Throwable { final AtomicInteger acquired = new AtomicInteger(0); final CountDownLatch waiter = new CountDownLatch(3); final List<Throwable> errors = new ArrayList<>(3); Runnable task = new Runnable() { @Override public void run() { PathLockFactory.PathLock exclusiveLock = pathLockFactory.getLock(path, true); try { exclusiveLock.acquire(); // Only one thread has exclusive access assertEquals(0, acquired.getAndIncrement()); Thread.sleep(100); } catch (Throwable e) { errors.add(e); } finally { acquired.getAndDecrement(); exclusiveLock.release(); waiter.countDown(); } } }; new Thread(task).start(); new Thread(task).start(); new Thread(task).start(); waiter.await(); assertEquals(0, acquired.get()); // all locks must be released if (!errors.isEmpty()) { throw errors.get(0); } } public void testLockTimeout() throws Exception { final CountDownLatch starter = new CountDownLatch(1); Runnable task = new Runnable() { @Override public void run() { PathLockFactory.PathLock exclusiveLock = pathLockFactory.getLock(path, true); try { exclusiveLock.acquire(); starter.countDown(); Thread.sleep(2000); // get lock and sleep } catch (InterruptedException ignored) { } finally { exclusiveLock.release(); } } }; new Thread(task).start(); starter.await(); // wait while child thread acquire exclusive lock PathLockFactory.PathLock timeoutExclusiveLock = pathLockFactory.getLock(path, true); try { // Wait lock timeout is much less then sleep time of child thread. // Here we must be failed to get exclusive permit. timeoutExclusiveLock.acquire(100); fail(); } catch (RuntimeException e) { // OK } } public void testConcurrentLocks() throws Throwable { final AtomicInteger acquired = new AtomicInteger(0); final CountDownLatch starter = new CountDownLatch(1); final CountDownLatch waiter = new CountDownLatch(2); Runnable task1 = new Runnable() { @Override public void run() { PathLockFactory.PathLock lock = pathLockFactory.getLock(path, false); lock.acquire(); acquired.incrementAndGet(); starter.countDown(); try { Thread.sleep(1000); } catch (InterruptedException ignored) { } finally { acquired.getAndDecrement(); lock.release(); waiter.countDown(); } } }; final List<Throwable> errors = new ArrayList<>(1); Runnable task2 = new Runnable() { @Override public void run() { PathLockFactory.PathLock exclusiveLock = pathLockFactory.getLock(path, true); try { exclusiveLock.acquire(); // This thread must be blocked while another thread keeps lock. assertEquals(0, acquired.getAndIncrement()); } catch (Throwable e) { errors.add(e); } finally { acquired.getAndDecrement(); exclusiveLock.release(); waiter.countDown(); } } }; new Thread(task1).start(); starter.await(); new Thread(task2).start(); waiter.await(); assertEquals(0, acquired.get()); // all locks must be released if (!errors.isEmpty()) { throw errors.get(0); } } public void testHierarchyLock() throws Throwable { final AtomicInteger acquired = new AtomicInteger(0); final Path parent = path.getParent(); final CountDownLatch starter = new CountDownLatch(1); final CountDownLatch waiter = new CountDownLatch(2); Runnable parentTask = new Runnable() { @Override public void run() { PathLockFactory.PathLock lock = pathLockFactory.getLock(parent, true); lock.acquire(); acquired.incrementAndGet(); starter.countDown(); try { Thread.sleep(100); } catch (InterruptedException ignored) { } finally { acquired.getAndDecrement(); lock.release(); waiter.countDown(); } } }; final List<Throwable> errors = new ArrayList<>(1); Runnable childTask = new Runnable() { @Override public void run() { PathLockFactory.PathLock lock = pathLockFactory.getLock(path, false); try { lock.acquire(); // This thread must be blocked while another thread keeps lock. assertEquals(0, acquired.getAndIncrement()); } catch (Throwable e) { errors.add(e); } finally { lock.release(); acquired.getAndDecrement(); waiter.countDown(); } } }; new Thread(parentTask).start(); starter.await(); new Thread(childTask).start(); waiter.await(); assertEquals(0, acquired.get()); // all locks must be released if (!errors.isEmpty()) { throw errors.get(0); } } public void testLockSameThread() throws Exception { final AtomicInteger acquired = new AtomicInteger(0); final CountDownLatch waiter = new CountDownLatch(1); Runnable task = new Runnable() { @Override public void run() { try { PathLockFactory.PathLock lock1 = pathLockFactory.getLock(path, true); PathLockFactory.PathLock lock2 = pathLockFactory.getLock(path, true); lock1.acquire(); acquired.incrementAndGet(); lock2.acquire(1000); // try with timeout. acquired.incrementAndGet(); } finally { waiter.countDown(); } } }; new Thread(task).start(); waiter.await(); assertEquals(2, acquired.get()); } }