All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bboss.org.jgroups.blocks.VotingAdapter Maven / Gradle / Ivy

The newest version!
package bboss.org.jgroups.blocks;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import bboss.org.jgroups.Address;
import bboss.org.jgroups.Channel;
import bboss.org.jgroups.ChannelException;
import bboss.org.jgroups.MembershipListener;
import bboss.org.jgroups.MessageListener;
import bboss.org.jgroups.View;
import bboss.org.jgroups.logging.Log;
import bboss.org.jgroups.logging.LogFactory;
import bboss.org.jgroups.util.Rsp;
import bboss.org.jgroups.util.RspList;

/**
 * Voting adapter provides a voting functionality for an application. There 
 * should be at most one {@link VotingAdapter} listening on one {@link Channel}
 * instance. Each adapter can have zero or more registered {@link VotingListener} 
 * instances that will be called during voting process. 
 * 

* Decree is an object that has some semantic meaning within the application. * Each voting listener receives a decree and can respond with either * true or false. If the decree has no meaning for the voting * listener, it is required to throw {@link VoteException}. In this case * this specific listener will be excluded from the voting on the specified * decree. After performing local voting, this voting adapter sends the request * back to the originator of the voting process. Originator receives results * from each node and decides if all voting process succeeded or not depending * on the consensus type specified during voting. * * @author Roman Rokytskyy ([email protected]) * @author Robert Schaffar-Taurok ([email protected]) * @version $Id: VotingAdapter.java,v 1.11 2009/05/13 13:06:54 belaban Exp $ */ public class VotingAdapter implements MessageListener, MembershipListener, VoteResponseProcessor { /** * This consensus type means that at least one positive vote is required * for the voting to succeed. */ public static final int VOTE_ANY = 0; /** * This consensus type means that at least one positive vote and no negative * votes are required for the voting to succeed. */ public static final int VOTE_ALL = 1; /** * This consensus type means that number of positive votes should be greater * than number of negative votes. */ public static final int VOTE_MAJORITY = 2; private static final int PROCESS_CONTINUE = 0; private static final int PROCESS_SKIP = 1; private static final int PROCESS_BREAK = 2; private final RpcDispatcher rpcDispatcher; protected final Log log=LogFactory.getLog(getClass()); private final HashSet suspectedNodes = new HashSet(); private boolean closed; private final List membership_listeners=new LinkedList(); /** * Creates an instance of the VoteChannel that uses JGroups * for communication between group members. * @param channel JGroups channel. */ public VotingAdapter(Channel channel) { rpcDispatcher = new RpcDispatcher(channel, this, this, this); } public VotingAdapter(PullPushAdapter adapter, Serializable id) { rpcDispatcher = new RpcDispatcher(adapter, id, this, this, this); } public Collection getMembers() { return rpcDispatcher != null? rpcDispatcher.getMembers() : null; } public void addMembershipListener(MembershipListener l) { if(l != null && !membership_listeners.contains(l)) membership_listeners.add(l); } public void removeMembershipListener(MembershipListener l) { if(l != null) membership_listeners.remove(l); } /** * Performs actual voting on the VoteChannel using the JGroups * facilities for communication. */ public boolean vote(Object decree, int consensusType, long timeout) throws ChannelException { return vote(decree, consensusType, timeout, null); } /** * Performs actual voting on the VoteChannel using the JGroups * facilities for communication. */ public boolean vote(Object decree, int consensusType, long timeout, VoteResponseProcessor voteResponseProcessor) throws ChannelException { if (closed) throw new ChannelException("Channel was closed."); if(log.isDebugEnabled()) log.debug("Conducting voting on decree " + decree + ", consensus type " + getConsensusStr(consensusType) + ", timeout " + timeout); int mode = GroupRequest.GET_ALL; // perform the consensus mapping switch (consensusType) { case VotingAdapter.VOTE_ALL : mode = GroupRequest.GET_ALL; break; case VotingAdapter.VOTE_ANY : mode = GroupRequest.GET_FIRST; break; case VotingAdapter.VOTE_MAJORITY : mode = GroupRequest.GET_MAJORITY; break; default : mode = GroupRequest.GET_ALL; } try { java.lang.reflect.Method method = this.getClass().getMethod( "localVote", new Class[] { Object.class }); MethodCall methodCall = new MethodCall(method, new Object[] {decree}); if(log.isDebugEnabled()) log.debug("Calling remote methods..."); // vote RspList responses = rpcDispatcher.callRemoteMethods( null, methodCall, mode, timeout); if(log.isDebugEnabled()) log.debug("Checking responses."); if (voteResponseProcessor == null) { voteResponseProcessor = this; } return voteResponseProcessor.processResponses(responses, consensusType, decree); } catch(NoSuchMethodException nsmex) { // UPS!!! How can this happen?! if(log.isErrorEnabled()) log.error("Could not find method localVote(Object). " + nsmex.toString()); throw new UnsupportedOperationException( "Cannot execute voting because of absence of " + this.getClass().getName() + ".localVote(Object) method."); } } /** * Processes the response list and makes a decision according to the * type of the consensus for current voting. *

* Note: we do not support voting in case of Byzantine failures, i.e. * when the node responds with the fault message. */ public boolean processResponses(RspList responses, int consensusType, Object decree) throws ChannelException { if (responses == null) { return false; } boolean voteResult = false; int totalPositiveVotes = 0; int totalNegativeVotes = 0; for(Iterator it=responses.values().iterator(); it.hasNext();) { Rsp response = (Rsp)it.next(); switch(checkResponse(response)) { case PROCESS_SKIP : continue; case PROCESS_BREAK : return false; } VoteResult result = (VoteResult)response.getValue(); totalPositiveVotes += result.getPositiveVotes(); totalNegativeVotes += result.getNegativeVotes(); } switch(consensusType) { case VotingAdapter.VOTE_ALL : voteResult = (totalNegativeVotes == 0 && totalPositiveVotes > 0); break; case VotingAdapter.VOTE_ANY : voteResult = (totalPositiveVotes > 0); break; case VotingAdapter.VOTE_MAJORITY : voteResult = (totalPositiveVotes > totalNegativeVotes); } return voteResult; } /** * This method checks the response and says the processResponses() method * what to do. * @return PROCESS_CONTINUE to continue calculating votes, * PROCESS_BREAK to stop calculating votes from the nodes, * PROCESS_SKIP to skip current response. * @throws ChannelException when the response is fatal to the * current voting process. */ private int checkResponse(Rsp response) throws ChannelException { if (!response.wasReceived()) { if(log.isDebugEnabled()) log.debug("Response from node " + response.getSender() + " was not received."); // what do we do when one node failed to respond? //throw new ChannelException("Node " + response.GetSender() + // " failed to respond."); return PROCESS_BREAK ; } /**@todo check what to do here */ if (response.wasSuspected()) { if(log.isDebugEnabled()) log.debug("Node " + response.getSender() + " was suspected."); // wat do we do when one node is suspected? return PROCESS_SKIP ; } Object object = response.getValue(); // we received exception/error, something went wrong // on one of the nodes... and we do not handle such faults if (object instanceof Throwable) { throw new ChannelException("Node " + response.getSender() + " is faulty."); } if (object == null) { return PROCESS_SKIP; } // it is always interesting to know the class that caused failure... if (!(object instanceof VoteResult)) { String faultClass = object.getClass().getName(); // ...but we do not handle byzantine faults throw new ChannelException("Node " + response.getSender() + " generated fault (class " + faultClass + ')'); } // what if we received the response from faulty node? if (object instanceof FailureVoteResult) { if(log.isErrorEnabled()) log.error(((FailureVoteResult)object).getReason()); return PROCESS_BREAK; } // everything is fine :) return PROCESS_CONTINUE; } /** * Callback for notification about the new view of the group. */ public void viewAccepted(View newView) { // clean nodes that were suspected but still exist in new view Iterator iterator = suspectedNodes.iterator(); while(iterator.hasNext()) { Address suspectedNode = (Address)iterator.next(); if (newView.containsMember(suspectedNode)) iterator.remove(); } for(Iterator it=membership_listeners.iterator(); it.hasNext();) { MembershipListener listener=(MembershipListener)it.next(); try { listener.viewAccepted(newView); } catch(Throwable t) { if(log.isErrorEnabled()) log.error("failed calling viewAccepted() on " + listener, t); } } } /** * Callback for notification that one node is suspected */ public void suspect(Address suspected) { suspectedNodes.add(suspected); for(Iterator it=membership_listeners.iterator(); it.hasNext();) { MembershipListener listener=(MembershipListener)it.next(); try { listener.suspect(suspected); } catch(Throwable t) { if(log.isErrorEnabled()) log.error("failed calling suspect() on " + listener, t); } } } /** * Blocks the channel until the ViewAccepted is invoked. */ public void block() { for(Iterator it=membership_listeners.iterator(); it.hasNext();) { MembershipListener listener=(MembershipListener)it.next(); try { listener.block(); } catch(Throwable t) { if(log.isErrorEnabled()) log.error("failed calling block() on " + listener, t); } } } /** * Get the channel state. * * @return always null, we do not have any group-shared * state. */ public byte[] getState() { return null; } /** * Receive the message. All messages are ignored. * * @param msg message to check. */ public void receive(bboss.org.jgroups.Message msg) { // do nothing } /** * Set the channel state. We do nothing here. */ public void setState(byte[] state) { // ignore the state, we do not have any. } private final Set voteListeners = new HashSet(); private VotingListener[] listeners; /** * Vote on the specified decree requiring all nodes to vote. * * @param decree decree on which nodes should vote. * @param timeout time during which nodes can vote. * * @return true if nodes agreed on a decree, otherwise * false * * @throws ChannelException if something went wrong. */ public boolean vote(Object decree, long timeout) throws ChannelException { return vote(decree, timeout, null); } /** * Vote on the specified decree requiring all nodes to vote. * * @param decree decree on which nodes should vote. * @param timeout time during which nodes can vote. * @param voteResponseProcessor processor which will be called for every response that is received. * * @return true if nodes agreed on a decree, otherwise * false * * @throws ChannelException if something went wrong. */ public boolean vote(Object decree, long timeout, VoteResponseProcessor voteResponseProcessor) throws ChannelException { return vote(decree, VOTE_ALL, timeout, voteResponseProcessor); } /** * Adds voting listener. */ public void addVoteListener(VotingListener listener) { voteListeners.add(listener); listeners = (VotingListener[])voteListeners.toArray( new VotingListener[voteListeners.size()]); } /** * Removes voting listener. */ public void removeVoteListener(VotingListener listener) { voteListeners.remove(listener); listeners = (VotingListener[])voteListeners.toArray( new VotingListener[voteListeners.size()]); } /** * This method performs voting on the specific decree between all * local voteListeners. */ public VoteResult localVote(Object decree) { VoteResult voteResult = new VoteResult(); for(int i = 0; i < listeners.length; i++) { VotingListener listener = listeners[i]; try { voteResult.addVote(listener.vote(decree)); } catch (VoteException vex) { // do nothing here. } catch(RuntimeException ex) { if(log.isErrorEnabled()) log.error(ex.toString()); // if we are here, then listener // had thrown a RuntimeException return new FailureVoteResult(ex.getMessage()); } } if(log.isDebugEnabled()) log.debug("Voting on decree " + decree.toString() + " : " + voteResult.toString()); return voteResult; } /** * Convert consensus type into string representation. This method is * useful for debugginf. * * @param consensusType type of the consensus. * * @return string representation of the consensus type. */ public static String getConsensusStr(int consensusType) { switch(consensusType) { case VotingAdapter.VOTE_ALL : return "VOTE_ALL"; case VotingAdapter.VOTE_ANY : return "VOTE_ANY"; case VotingAdapter.VOTE_MAJORITY : return "VOTE_MAJORITY"; default : return "UNKNOWN"; } } /** * This class represents the result of local voting. It contains a * number of positive and negative votes collected during local voting. */ public static class VoteResult implements Serializable { private int positiveVotes = 0; private int negativeVotes = 0; private static final long serialVersionUID = 2868605599965196746L; public void addVote(boolean vote) { if (vote) positiveVotes++; else negativeVotes++; } public int getPositiveVotes() { return positiveVotes; } public int getNegativeVotes() { return negativeVotes; } public String toString() { return "VoteResult: up=" + positiveVotes + ", down=" + negativeVotes; } } /** * Class that represents a result of local voting on the failed node. */ public static class FailureVoteResult extends VoteResult { private final String reason; public FailureVoteResult(String reason) { this.reason = reason; } public String getReason() { return reason; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy