/** * SAMOA - PROTOCOL FRAMEWORK * Copyright (C) 2005 Olivier Rütti (EPFL) (olivier.rutti@a3.epfl.ch) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package seqSamoa.protocols.consensus; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.LinkedList; import java.util.NoSuchElementException; import uka.transport.Transportable; import framework.Constants; import framework.GroupCommEventArgs; import framework.GroupCommException; import framework.GroupCommMessage; import framework.PID; import framework.libraries.DefaultSerialization; import framework.libraries.Trigger; import framework.libraries.serialization.TList; import framework.libraries.serialization.TSet; import framework.libraries.serialization.TBoolean; import framework.libraries.serialization.TInteger; import seqSamoa.ProtocolModule; import seqSamoa.ProtocolStack; import seqSamoa.Message; import seqSamoa.ServiceCallOrResponse; import seqSamoa.exceptions.AlreadyExistingProtocolModuleException; import seqSamoa.services.consensus.Consensus; import seqSamoa.services.consensus.ConsensusCallParameters; import seqSamoa.services.consensus.ConsensusResponseParameters; import seqSamoa.services.fd.FD; import seqSamoa.services.fd.FDResponseParameters; import seqSamoa.services.rpt2pt.RPT2PT; import seqSamoa.services.rpt2pt.RPT2PTCallParameters; import seqSamoa.services.rpt2pt.RPT2PTResponseParameters; import groupcomm.common.consensus.ConsensusExecution; /** * This class implement the Consensus protocol of Chandra-Toueg. It only * achieves one instance of consensus * * This Protocol need a Protocol that implements RPT2PT and FD. * * The service implemented is consensus (described in util/Services.java) */ public class ProtocolConsensusExecution extends ProtocolModule implements Trigger { final static int MAX_PROCESSES = 7; // Service provided private Consensus consensus; // Services required private RPT2PT rpt2pt; protected ConsensusExecution handlers; // This instance of Consensus has decided consDecision private Transportable consDecision = null; // K of the consensus private Object consK; // The Executer // It start a consensus protected Consensus.Executer consensusExecuter; // The Listeners // It listen for process supsicion protected FD.Listener fdListener; // It listen for rpt2pt messages protected RPT2PT.Listener rpt2ptListener; /** * Constructor. <br> * * @param name * Name of the layer * @param myself * The local PID * @param k * Instance of consensus * @param suspected * The group of processes initially suspected */ public ProtocolConsensusExecution(String name, ProtocolStack stack, Transportable k, TSet suspected, Consensus consensus, FD fd, RPT2PT rpt2pt) throws AlreadyExistingProtocolModuleException { super(name, stack); this.consK = k; handlers = new ConsensusExecution(stack.getPID(), k, suspected, this); this.consensus = consensus; this.rpt2pt = rpt2pt; LinkedList<ServiceCallOrResponse> initiatedCons = new LinkedList<ServiceCallOrResponse>(); for (int i=0;i<MAX_PROCESSES;i++) initiatedCons.add(ServiceCallOrResponse.createServiceCallOrResponse(rpt2pt, true)); consensusExecuter = consensus.new Executer(this, initiatedCons) { public void evaluate(ConsensusCallParameters params, Message dmessage) { synchronized (this.parent) { if (params.id.equals(consK)) { try { // Has its decision already arrived?? if (consDecision != null) { Transportable clone = deepClone(consDecision); reSendDecision(consDecision, params.id, params.group, null); GroupCommEventArgs e = new GroupCommEventArgs(); e.addLast(clone); e.addLast(params.id); trigger(Constants.DECIDE, e); } else handlers.processStart(dmessage .toGroupCommMessage(), params.group); } catch (GroupCommException ex) { throw new RuntimeException( "ProtocolConsensusExecution: " + "Executer: " + ex.getMessage()); } catch (IOException ex) { throw new RuntimeException( "ProtocolConsensusExecution: " + "Executer: " + ex.getMessage()); } catch (ClassNotFoundException ex) { throw new RuntimeException( "ProtocolConsensusExecution: " + "Executer: " + ex.getMessage()); } } } } }; LinkedList<ServiceCallOrResponse> initiatedRpt2pt = new LinkedList<ServiceCallOrResponse>(); for (int i=0;i<MAX_PROCESSES;i++) initiatedRpt2pt.add(ServiceCallOrResponse.createServiceCallOrResponse(rpt2pt, true)); initiatedRpt2pt.add(ServiceCallOrResponse.createServiceCallOrResponse(consensus, false)); rpt2ptListener = rpt2pt.new Listener(this, initiatedRpt2pt) { public void evaluate(RPT2PTResponseParameters infos, Transportable message) { synchronized (this.parent) { GroupCommMessage gm = (GroupCommMessage) message; Transportable kmessObj = (Transportable) gm.tunpack(); if (consDecision != null) return; PID source = infos.pid; int type = ((TInteger) gm.tunpack()).intValue(); try { // m = <<payload>> switch (type) { case ConsensusExecution.CONS_ESTIMATE: // m = <<r::estimate::lastupdated>> int r = ((TInteger) gm.tunpack()).intValue(); handlers.processEstimate(r, gm); break; case ConsensusExecution.CONS_PROPOSE: // m = <<r::propose>> r = ((TInteger) gm.tunpack()).intValue(); handlers.processPropose(r, gm); break; case ConsensusExecution.CONS_ACK: // m = <<r::ack>> r = ((TInteger) gm.tunpack()).intValue(); handlers.processAck(r, gm); break; case ConsensusExecution.CONS_RBCAST: // m = <<decision::group>> Transportable decision = gm.tunpack(); // m = <<group>> if (handlers.hasStarted()) { Transportable clone = deepClone(decision); TList group = (TList) gm.tunpack(); reSendDecision(decision, kmessObj, group, source); GroupCommEventArgs e = new GroupCommEventArgs(); e.addLast(clone); e.addLast(kmessObj); trigger(Constants.DECIDE, e); } else { consDecision = decision; } break; default: throw new GroupCommException( "ProtocolConsensusExecution:" + " rpt2ptListener: " + "Unknown message type: " + type); } } catch (NoSuchElementException e) { throw new RuntimeException( "ProtocolConsensusExecution: " + "rpt2ptListener: " + "NoSuchElementException: " + e.getMessage()); } catch (GroupCommException e) { throw new RuntimeException( "ProtocolConsensusExecution: " + "rpt2ptListener: " + "GroupCommException:" + e.getMessage()); } catch (IOException e) { throw new RuntimeException( "ProtocolConsensusExecution: " + "rpt2ptListener: " + "IOException:" + e.getMessage()); } catch (ClassNotFoundException e) { throw new RuntimeException( "ProtocolConsensusExecution: " + "rpt2ptListener: " + "ClassNotFoundException:" + e.getMessage()); } } } }; LinkedList<ServiceCallOrResponse> initiatedFD = new LinkedList<ServiceCallOrResponse>(); for (int i=0;i<MAX_PROCESSES;i++) initiatedFD.add(ServiceCallOrResponse.createServiceCallOrResponse(rpt2pt, true)); fdListener = fd.new Listener(this, initiatedFD) { public void evaluate(FDResponseParameters infos, Transportable message) { synchronized (this.parent) { TSet suspected = infos.suspected; try { handlers.processSuspicion(suspected); } catch (Exception ex) { throw new RuntimeException( "ProtocolConsensusExecution: " + "fdListener: " + ex.getMessage()); } } } }; } synchronized public void dump(OutputStream stream) { PrintStream err = new PrintStream(stream); err.println(handlers); } /** * Manage the triggering of the events */ public void trigger(int type, GroupCommEventArgs l) { switch (type) { case Constants.DECIDE: GroupCommMessage gm = (GroupCommMessage) l.remove(0); Message dmessage = new Message(gm); ConsensusResponseParameters infos = new ConsensusResponseParameters( l.remove(0)); this.close(); consensus.response(infos, dmessage); break; case Constants.PT2PTSEND: Transportable message = l.remove(0); PID pid = (PID) l.remove(0); TBoolean promisc = (TBoolean) l.remove(0); RPT2PTCallParameters rparams = new RPT2PTCallParameters(pid, promisc, new TBoolean(true)); rpt2pt.call(rparams, new Message(message, rpt2ptListener)); break; default: throw new RuntimeException("ProtocolConsensus: trigger: " + "Unexpected event type"); } } // Makes a copy in memory of the parameter. If the parameter has references // to other objects, they are also cloned. // This is necessary to avoid side-effects at higher-lever protocols. private Transportable deepClone(Transportable o) throws IOException, ClassNotFoundException { // TODO: There have to be better ways to do deep-clone!!! return DefaultSerialization .unmarshall(DefaultSerialization.marshall(o)); } // Send the decision again. This is done to simulate RBcast. private void reSendDecision(Transportable decision, Transportable kObj, TList group, PID dontsend) { if (!this.stack.getPID().equals(dontsend)) { GroupCommMessage decisionMessage = new GroupCommMessage(); // m = <<>> decisionMessage.tpack(group); // m = <<group>> decisionMessage.tpack(decision); // m = <<decision::group>> decisionMessage.tpack(new TInteger(ConsensusExecution.CONS_RBCAST)); // m = <<CONS_BROADCAST::decision::group>> decisionMessage.tpack(kObj); // m = <<k::CONS_BROADCAST::decision::group>> for (int i = 0; i < group.size(); i++) if (!group.get(i).equals(this.stack.getPID()) && (dontsend == null || !group.get(i).equals(dontsend))) { GroupCommEventArgs pt2ptSend = new GroupCommEventArgs(); pt2ptSend.addLast(decisionMessage.cloneGroupCommMessage()); pt2ptSend.addLast(group.get(i)); pt2ptSend.addLast(new TBoolean(false)); trigger(Constants.PT2PTSEND, pt2ptSend); } } } }