/*
* JBoss, Home of Professional Open Source.
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ha.framework.server.lock;
import java.io.Serializable;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.jboss.ha.framework.interfaces.ClusterNode;
import org.jboss.ha.framework.interfaces.HAPartition;
/**
* @author Brian Stansberry
*
* @version $Revision:$
*/
public class LocalAndClusterLockManager
{
private class LocalLock
{
private volatile ClusterNode holder;
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
private void lock(ClusterNode caller, long timeout) throws TimeoutException
{
long deadline = System.currentTimeMillis() + timeout;
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
waiters.add(current);
try
{
// Block while not first in queue or cannot acquire lock
while (waiters.peek() != current ||
!locked.compareAndSet(false, true))
{
LockSupport.parkUntil(deadline);
if (Thread.interrupted()) // ignore interrupts while waiting
wasInterrupted = true;
if (System.currentTimeMillis() >= deadline)
{
if (waiters.peek() != current ||
!locked.compareAndSet(false, true))
{
throw new TimeoutException(this.holder);
}
break;
}
}
if (locked.get())
{
holder = caller;
}
else
{
throw new TimeoutException(this.holder);
}
}
finally
{
waiters.remove();
if (wasInterrupted) // reassert interrupt status on exit
current.interrupt();
}
}
private void unlock(ClusterNode caller)
{
if (caller.equals(holder))
{
locked.set(false);
holder = null;
LockSupport.unpark(waiters.peek());
}
}
}
/** Handles callbacks from the cluster lock support object */
private class ClusterHandler implements LocalLockHandler
{
// ----------------------------------------------------- LocalLockHandler
public ClusterNode getLocalNode(ClusterNode localNode)
{
return LocalAndClusterLockManager.this.localNode;
}
public void setLocalNode(ClusterNode localNode)
{
LocalAndClusterLockManager.this.localNode = localNode;
}
public void lockFromCluster(Serializable lockName, ClusterNode caller, long timeout) throws TimeoutException,
InterruptedException
{
LocalAndClusterLockManager.this.doLock(lockName, caller, timeout);
}
public ClusterNode getLockHolder(Serializable lockName)
{
LocalLock lock = LocalAndClusterLockManager.this.getLocalLock(lockName, false);
return lock == null ? null : lock.holder;
}
public void unlockFromCluster(Serializable lockName, ClusterNode caller)
{
LocalAndClusterLockManager.this.doUnlock(lockName, caller);
}
}
private ClusterNode localNode;
private ConcurrentMap<Serializable, LocalLock> localLocks = new ConcurrentHashMap<Serializable, LocalLock>();
private final NonGloballyExclusiveClusterLockSupport clusterSupport;
public LocalAndClusterLockManager(String serviceHAName, HAPartition partition)
{
ClusterHandler handler = new ClusterHandler();
clusterSupport = new NonGloballyExclusiveClusterLockSupport(serviceHAName, partition, handler);
}
// ----------------------------------------------------------------- Public
public void lockLocally(Serializable lockName, long timeout)
throws TimeoutException, InterruptedException
{
if (this.localNode == null)
{
throw new IllegalStateException("Null localNode");
}
doLock(lockName, this.localNode, timeout);
}
public void unlockLocally(Serializable lockName)
{
if (this.localNode == null)
{
throw new IllegalStateException("Null localNode");
}
doUnlock(lockName, this.localNode);
}
public void lockGlobally(Serializable lockName, long timeout)
throws TimeoutException, InterruptedException
{
this.clusterSupport.lock(lockName, timeout);
}
public void unlockGlobally(Serializable lockName)
{
this.clusterSupport.unlock(lockName);
}
public void start() throws Exception
{
this.clusterSupport.start();
}
public void stop() throws Exception
{
this.clusterSupport.stop();
}
// ----------------------------------------------------------------- Private
private LocalLock getLocalLock(Serializable categoryName, boolean create)
{
LocalLock category = localLocks.get(categoryName);
if (category == null && create)
{
category = new LocalLock();
LocalLock existing = localLocks.putIfAbsent(categoryName, category);
if (existing != null)
{
category = existing;
}
}
return category;
}
private void doLock(Serializable lockName, ClusterNode caller, long timeout) throws TimeoutException,
InterruptedException
{
LocalLock lock = getLocalLock(lockName, true);
lock.lock(caller, timeout);
}
private void doUnlock(Serializable lockName, ClusterNode caller)
{
LocalLock lock = getLocalLock(lockName, false);
if (lock != null)
{
lock.unlock(caller);
}
}
}