/*
* HA-JDBC: High-Availability JDBC
* Copyright (C) 2014 Paul Ferraro
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.hajdbc.lock.distributed;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import net.sf.hajdbc.DatabaseCluster;
import net.sf.hajdbc.distributed.CommandDispatcherFactory;
import net.sf.hajdbc.distributed.jgroups.JGroupsCommandDispatcherFactory;
import net.sf.hajdbc.lock.LockManager;
import net.sf.hajdbc.lock.semaphore.SemaphoreLockManager;
/**
* @author Paul Ferraro
*/
public class DistributedLockManagerTest
{
LockManager manager1;
LockManager manager2;
@Before
public void init() throws Exception
{
String id = "cluster";
DatabaseCluster<?, ?> cluster1 = mock(DatabaseCluster.class);
DatabaseCluster<?, ?> cluster2 = mock(DatabaseCluster.class);
LockManager lockManager1 = new SemaphoreLockManager(false);
LockManager lockManager2 = new SemaphoreLockManager(false);
CommandDispatcherFactory dispatcherFactory1 = createCommandDispatcherFactory("node1");
CommandDispatcherFactory dispatcherFactory2 = createCommandDispatcherFactory("node2");
when(cluster1.getId()).thenReturn(id);
when(cluster1.getLockManager()).thenReturn(lockManager1);
when(cluster2.getId()).thenReturn(id);
when(cluster2.getLockManager()).thenReturn(lockManager2);
this.manager1 = new DistributedLockManager(cluster1, dispatcherFactory1);
this.manager1.start();
this.manager2 = new DistributedLockManager(cluster2, dispatcherFactory2);
this.manager2.start();
}
static CommandDispatcherFactory createCommandDispatcherFactory(String name)
{
JGroupsCommandDispatcherFactory factory = new JGroupsCommandDispatcherFactory();
factory.setName(name);
factory.setStack("fast.xml");
return factory;
}
@After
public void destroy()
{
this.manager1.stop();
this.manager1 = null;
this.manager2.stop();
this.manager2 = null;
}
@Test
public void simple()
{
test(this.manager1);
test(this.manager2);
}
private static void test(LockManager manager)
{
test(manager.readLock("1"));
test(manager.readLock(null));
test(manager.writeLock("1"));
test(manager.writeLock(null));
}
private static void test(Lock lock)
{
assertTrue(lock.tryLock());
lock.unlock();
}
@Test
public void blocking()
{
blocking(this.manager1, this.manager2);
blocking(this.manager2, this.manager1);
}
private static void blocking(LockManager manager1, LockManager manager2)
{
Lock readLock = manager1.readLock(null);
Lock readLock2 = manager2.readLock(null);
Lock writeLock = manager2.writeLock(null);
assertTrue(readLock.tryLock());
try
{
// Validate that reads do not block on another
assertTrue(readLock2.tryLock());
readLock2.unlock();
// Validate that remote read blocks local write
boolean locked = writeLock.tryLock();
try
{
assertFalse(locked);
}
finally
{
if (locked)
{
writeLock.unlock();
}
}
}
finally
{
readLock.unlock();
}
assertTrue(writeLock.tryLock());
try
{
// Validate that remote write blocks local read
boolean locked = readLock.tryLock();
try
{
assertFalse(locked);
}
finally
{
if (locked)
{
readLock.unlock();
}
}
}
finally
{
writeLock.unlock();
}
}
@Test
public void failover() throws Exception
{
Lock lock1 = this.manager1.writeLock(null);
Lock lock2 = this.manager2.writeLock(null);
assertTrue(lock1.tryLock());
boolean locked = lock2.tryLock();
try
{
assertFalse(locked);
}
finally
{
if (locked)
{
lock2.unlock();
}
}
ExecutorService executor = Executors.newSingleThreadExecutor();
try
{
Callable<Void> task = new Callable<Void>()
{
@Override
public Void call() throws Exception
{
DistributedLockManagerTest.this.manager1.stop();
DistributedLockManagerTest.this.manager1.start();
return null;
}
};
Future<?> future = executor.submit(task);
locked = lock2.tryLock(10, TimeUnit.SECONDS);
assertTrue(locked);
lock2.unlock();
future.get();
}
finally
{
executor.shutdownNow();
}
}
}