/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, 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;
import java.util.EventObject;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.jboss.ha.framework.interfaces.ClusterNode;
import org.jboss.ha.framework.interfaces.DistributedReplicantManager;
import org.jboss.ha.framework.interfaces.HASingleton;
import org.jboss.ha.framework.interfaces.HASingletonElectionPolicy;
import org.jboss.ha.framework.interfaces.HASingletonLifecycle;
/**
* Base class for HA-singleton based services.
*
* @author Paul Ferraro
*/
public class HASingletonImpl<E extends EventObject> extends HAServiceImpl<E> implements HASingleton<E>, HASingletonRpcHandler<E>
{
private final AtomicBoolean master = new AtomicBoolean(false);
private final HASingletonRpcHandler<E> rpcHandler = new RpcHandler();
private final HASingletonLifecycle singletonLifecycle;
AtomicReference<ReplicantView> viewReference = new AtomicReference<ReplicantView>();
private volatile HASingletonElectionPolicy electionPolicy;
private volatile boolean restartOnMerge = true;
public HASingletonImpl(EventFactory<E> eventFactory, EventFacility<E> eventHandler, HASingletonLifecycle singletonLifecycle)
{
super(eventFactory, eventHandler);
this.singletonLifecycle = singletonLifecycle;
}
public HASingletonImpl(EventFactory<E> eventFactory)
{
super(eventFactory);
this.singletonLifecycle = this;
}
@Override
public HASingletonElectionPolicy getElectionPolicy()
{
return this.electionPolicy;
}
@Override
public void setElectionPolicy(HASingletonElectionPolicy electionPolicy)
{
this.electionPolicy = electionPolicy;
}
@Override
public boolean getRestartOnMerge()
{
return this.restartOnMerge;
}
@Override
public void setRestartOnMerge(boolean restart)
{
this.restartOnMerge = restart;
}
@Override
public boolean isMasterNode()
{
return this.master.get();
}
/**
* @{inheritDoc}
* Overridden to circumvent deadlock in the unlikely scenario that the view changes between
* drm listener registration and drm replicant addition. See JBAS-2647 for details.
* @see org.jboss.ha.framework.server.HAServiceImpl#registerDRMListener()
*/
@Override
protected void registerDRMListener() throws Exception
{
if (log.isDebugEnabled())
{
log.debug("HASingletonImpl.registerDRMListener for service=" + getHAServiceKey());
}
DistributedReplicantManager drm = this.getHAPartition().getDistributedReplicantManager();
// Temporary drm listener
RecordingReplicantListener listener = new RecordingReplicantListener();
String key = this.getHAServiceKey();
// record replicant changes, but don't handle them just yet
drm.registerListener(key, listener);
// this ensures that the DRM knows that this node has the singleton deployed
drm.add(key, this.getReplicant());
// Now register the real listener
drm.registerListener(key, this);
// ...and unregister our temporary one
drm.unregisterListener(key, listener);
ReplicantView view = this.viewReference.getAndSet(null);
// Process the recorded replicant change
// Typically this will be the replicant change from drm.add(...)
if (view != null)
{
this.partitionTopologyChanged(view.getReplicants(), view.getId(), view.isMerge());
}
}
/**
* @see org.jboss.ha.framework.interfaces.HASingletonLifecycle#startSingleton()
*/
@Override
public void startSingleton()
{
this.log.debug("startSingleton() : elected for master singleton node");
}
/**
* @see org.jboss.ha.framework.interfaces.HASingletonLifecycle#stopSingleton()
*/
@Override
public void stopSingleton()
{
this.log.debug("stopSingleton() : another node in the partition (if any) is elected for master");
}
/**
* When topology changes, a new master is elected based on the result
* of the isDRMMasterReplica() call.
*
* @see org.jboss.ha.framework.server.HAServiceImpl#partitionTopologyChanged(java.util.List, int, boolean)
* @see org.jboss.ha.framework.interfaces.DistributedReplicantManager#isMasterReplica(String)
*/
@Override
protected void partitionTopologyChanged(List<?> newReplicants, int newViewId, boolean merge)
{
ReplicantView view = this.viewReference.getAndSet(null);
// Pre-process any recorded replicant changes
if (view != null)
{
this.partitionTopologyChanged(view.getReplicants(), view.getId(), view.isMerge());
}
boolean isElectedNewMaster = this.elected();
boolean isMaster = this.master.get();
if( log.isDebugEnabled())
{
this.log.debug("partitionTopologyChanged, isElectedNewMaster=" + isElectedNewMaster + ", isMasterNode=" +
isMaster + ", viewID=" + newViewId + ", partition=" + getHAPartition().getPartitionName()
);
}
if (isElectedNewMaster)
{
// if this node is already the master, don't bother electing it again
if (isMaster)
{
// JBAS-4229
if (this.restartOnMerge && merge)
{
this.restartMaster();
}
}
// just becoming master
else
{
this.makeThisNodeMaster();
}
}
// transition from master to slave
else if (isMaster)
{
this.stopIfMaster();
}
}
private boolean elected()
{
boolean result;
if (this.electionPolicy == null)
{
result = this.isDRMMasterReplica();
}
else {
ClusterNode electedNode = this.election();
result = (electedNode != null) ? electedNode.equals(this.getHAPartition().getClusterNode()) : false;
}
if( log.isDebugEnabled())
{
log.debug("election result =" + result + ", electionPolicy=" +
(electionPolicy != null? electionPolicy.getClass().getName():"DistributedReplicantManager"));
}
return result;
}
private ClusterNode election()
{
List<ClusterNode> candidates = this.getElectionCandidates();
if ((candidates == null) || candidates.isEmpty()) return null;
return (candidates.size() == 1) ? candidates.get(0) : this.electionPolicy.elect(candidates);
}
protected List<ClusterNode> getElectionCandidates()
{
return this.getHAPartition().getDistributedReplicantManager().lookupReplicantsNodes(this.getHAServiceKey());
}
/**
* @see org.jboss.ha.singleton.HASingleton.RpcHandler#stopOldMaster()
*/
@Override
public void stopOldMaster() throws Exception
{
// ovidiu 09/02/04 - temporary solution for Case 1843, use an asynchronous
// distributed call.
//callMethodOnPartition("_stopOldMaster", new Object[0], new Class[0]);
this.callAsyncMethodOnPartition("stopOldMaster", new Object[0], new Class[0]);
}
protected boolean isDRMMasterReplica()
{
DistributedReplicantManager drm = this.getHAPartition().getDistributedReplicantManager();
return drm.isMasterReplica(this.getHAServiceKey());
}
protected void makeThisNodeMaster()
{
try
{
// stop the old master (if there is one) before starting the new one
this.stopOldMaster();
this.startNewMaster();
}
catch (Exception ex)
{
this.log.error("stopOldMaster failed. New master singleton will not start.", ex);
ex.printStackTrace(System.err);
}
}
protected void startNewMaster()
{
this.log.debug("startNewMaster, isMasterNode=" + this.master);
this.master.set(true);
// notify starting
this.sendLocalEvent(HASINGLETON_STARTING_NOTIFICATION);
// start new master
this.singletonLifecycle.startSingleton();
// notify started
this.sendLocalEvent(HASINGLETON_STARTED_NOTIFICATION);
}
protected void restartMaster()
{
this.stopIfMaster();
this.startNewMaster();
}
protected void stopIfMaster()
{
this.log.debug("stopIfMaster, isMasterNode=" + this.master.get());
try
{
// since this is a cluster call, all nodes will hear it
// so if the node is not the master, then ignore
if (this.master.compareAndSet(true, false))
{
// notify stopping
this.sendLocalEvent(HASINGLETON_STOPPING_NOTIFICATION);
// stop the singleton
this.singletonLifecycle.stopSingleton();
// notify stopped
this.sendLocalEvent(HASINGLETON_STOPPED_NOTIFICATION);
}
}
catch (Exception ex)
{
this.log.error("stopIfMaster failed. Will still try to start new master. " +
"You need to examine the reason why the old master wouldn't stop and resolve it. " +
"It is bad that the old singleton may still be running while we are starting a new one, " +
"so you need to resolve this ASAP.", ex);
}
}
private void sendLocalEvent(String type)
{
E event = this.getEventFactory().createEvent(this, type);
try
{
this.getEventFacility().notifyListeners(event);
}
catch (Exception e)
{
this.log.warn("Failed to send local event: " + event, e);
}
}
@Override
protected HAServiceRpcHandler<E> getRpcHandler()
{
return this.rpcHandler;
}
protected class RpcHandler extends HAServiceImpl<E>.RpcHandler implements HASingletonRpcHandler<E>
{
/**
* @see org.jboss.ha.singleton.HASingleton.RpcHandler#stopOldMaster()
*/
@Override
public void stopOldMaster()
{
HASingletonImpl.this.stopIfMaster();
}
}
private static class ReplicantView
{
private List<?> replicants;
private int id;
private boolean merge;
public ReplicantView(List<?> replicants, int viewId, boolean merge)
{
this.replicants = replicants;
this.id = viewId;
this.merge = merge;
}
public List<?> getReplicants()
{
return this.replicants;
}
public int getId()
{
return this.id;
}
public boolean isMerge()
{
return this.merge;
}
}
class RecordingReplicantListener implements DistributedReplicantManager.ReplicantListener
{
@Override
public void replicantsChanged(String key, List<?> newReplicants, int newReplicantsViewId, boolean merge)
{
if (HASingletonImpl.this.getHAServiceKey().equals(key))
{
// Record replicant change - overwriting any previously recorded view
HASingletonImpl.this.viewReference.set(new ReplicantView(newReplicants, newReplicantsViewId, merge));
}
}
}
}