// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =================================================================================================
package com.twitter.common.zookeeper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.twitter.common.zookeeper.testing.BaseZooKeeperTest;
/**
* @author Florian Leibert
*/
public class DistributedLockTest extends BaseZooKeeperTest {
private static final String LOCK_PATH = "/test/lock";
private ZooKeeperClient zkClient;
@Before
public void mySetUp() throws Exception {
zkClient = createZkClient();
}
@Test
public void testFailDoubleLock() {
DistributedLock lock = new DistributedLockImpl(zkClient, LOCK_PATH);
lock.lock();
try {
lock.lock();
fail("Exception expected!");
} catch (DistributedLockImpl.LockingException e) {
// expected
} finally {
lock.unlock();
}
}
@Test
public void testFailUnlock() {
DistributedLock lock = new DistributedLockImpl(zkClient, LOCK_PATH);
try {
lock.unlock();
fail("Expected exception while trying to unlock!");
} catch (DistributedLockImpl.LockingException e) {
// success
}
}
@Test
public void testTwoLocks() {
DistributedLock lock1 = new DistributedLockImpl(zkClient, LOCK_PATH);
DistributedLock lock2 = new DistributedLockImpl(zkClient, LOCK_PATH);
lock1.lock();
List<String> children = expectZkNodes(LOCK_PATH);
assertEquals("One child == lock held!", children.size(), 1);
lock1.unlock();
// check no locks held/empty children
children = expectZkNodes(LOCK_PATH);
assertEquals("No children, no lock held!", children.size(), 0);
lock2.lock();
children = expectZkNodes(LOCK_PATH);
assertEquals("One child == lock held!", children.size(), 1);
lock2.unlock();
}
@Test
public void testTwoLocksFailFast() {
DistributedLock lock1 = new DistributedLockImpl(zkClient, LOCK_PATH);
DistributedLock lock2 = new DistributedLockImpl(zkClient, LOCK_PATH);
lock1.lock();
boolean acquired = lock2.tryLock(10, TimeUnit.MILLISECONDS);
assertFalse("Couldn't acquire lock because it's currently held", acquired);
lock1.unlock();
lock2.unlock();
}
@Test
@Ignore("pending: <http://jira.local.twitter.com/browse/RESEARCH-49>")
public void testMultiConcurrentLocking() throws Exception {
//TODO(Florian Leibert): this is a bit janky, so let's replace it.
for (int i = 0; i < 50; i++) {
testConcurrentLocking();
}
mySetUp();
}
@Test
public void testConcurrentLocking() throws Exception {
ZooKeeperClient zk1 = createZkClient();
ZooKeeperClient zk2 = createZkClient();
ZooKeeperClient zk3 = createZkClient();
final DistributedLock lock1 = new DistributedLockImpl(zk1, LOCK_PATH);
final DistributedLock lock2 = new DistributedLockImpl(zk2, LOCK_PATH);
final DistributedLock lock3 = new DistributedLockImpl(zk3, LOCK_PATH);
Callable<Object> t1 = new Callable<Object>() {
@Override
public Object call() throws InterruptedException {
lock1.lock();
try {
Thread.sleep(50);
} finally {
lock1.unlock();
}
return new Object();
}
};
Callable<Object> t2 = new Callable<Object>() {
@Override
public Object call() throws InterruptedException {
lock2.lock();
try {
Thread.sleep(50);
} finally {
lock2.unlock();
}
return new Object();
}
};
Callable<Object> t3 = new Callable<Object>() {
@Override
public Object call() throws InterruptedException {
lock3.lock();
try {
Thread.sleep(50);
} finally {
lock3.unlock();
}
return new Object();
}
};
//TODO(Florian Leibert): remove this executors stuff and use a latch instead.
ExecutorService ex = Executors.newCachedThreadPool();
@SuppressWarnings("unchecked") List<Callable<Object>> tlist = Arrays.asList(t1, t2, t3);
ex.invokeAll(tlist);
assertTrue("No Children left!", expectZkNodes(LOCK_PATH).size() == 0);
}
protected List<String> expectZkNodes(String path) {
try {
List<String> children = zkClient.get().getChildren(path, null);
return children;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}