/** * Copyright (c) 2007-2009 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags * * This file is part of SMaRt. * * SMaRt 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 3 of the License, or * (at your option) any later version. * * SMaRt 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 SMaRt. If not, see <http://www.gnu.org/licenses/>. */ package bftsmart.tom; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import bftsmart.communication.ServerCommunicationSystem; import bftsmart.paxosatwar.executionmanager.ExecutionManager; import bftsmart.paxosatwar.executionmanager.LeaderModule; import bftsmart.paxosatwar.messages.MessageFactory; import bftsmart.paxosatwar.roles.Acceptor; import bftsmart.paxosatwar.roles.Proposer; import bftsmart.reconfiguration.Reconfiguration; import bftsmart.reconfiguration.ReconfigureReply; import bftsmart.reconfiguration.ServerViewManager; import bftsmart.reconfiguration.TTPMessage; import bftsmart.tom.core.TOMLayer; import bftsmart.tom.core.messages.TOMMessage; import bftsmart.tom.core.messages.TOMMessageType; import bftsmart.tom.server.BatchExecutable; import bftsmart.tom.server.Executable; import bftsmart.tom.server.Recoverable; import bftsmart.tom.server.Replier; import bftsmart.tom.server.SingleExecutable; import bftsmart.tom.server.defaultservices.DefaultReplier; import bftsmart.tom.util.ShutdownHookThread; import bftsmart.tom.util.TOMUtil; /** * This class implements a TOMReceiver, and also a replica for the server side of the application. * It receives requests from the clients, runs a TOM layer, and sends a reply back to the client * Applications must create a class that extends this one, and implement the executeOrdered method * */ public class ServiceReplica implements TOMReceiver { class MessageContextPair { TOMMessage message; MessageContext msgCtx; MessageContextPair(TOMMessage message, MessageContext msgCtx) { this.message = message; this.msgCtx = msgCtx; } } // replica ID private int id; // Server side comunication system private ServerCommunicationSystem cs = null; private ServerViewManager SVManager; private boolean isToJoin = false; private ReentrantLock waitTTPJoinMsgLock = new ReentrantLock(); private Condition canProceed = waitTTPJoinMsgLock.newCondition(); /** THIS IS JOAO'S CODE, TO HANDLE CHECKPOINTS */ private Executable executor = null; private Recoverable recoverer = null; private TOMLayer tomLayer = null; private boolean tomStackCreated = false; private ReplicaContext replicaCtx = null; private Replier replier = null; /*******************************************************/ /** * Constructor * @param id Replica ID */ public ServiceReplica(int id, Executable executor, Recoverable recoverer) { this(id, "", executor, recoverer); } /** * Constructor * * @param id Process ID * @param configHome Configuration directory for JBP */ public ServiceReplica(int id, String configHome, Executable executor, Recoverable recoverer) { this(id, configHome, false, executor, recoverer); } //******* EDUARDO BEGIN **************// /** * Constructor * @param id Replica ID * @param isToJoin: if true, the replica tries to join the system, otherwise it waits for TTP message * informing its join */ public ServiceReplica(int id, boolean isToJoin, Executable executor, Recoverable recoverer) { this(id, "", isToJoin, executor, recoverer); } public ServiceReplica(int id, String configHome, boolean isToJoin, Executable executor, Recoverable recoverer) { this.isToJoin = isToJoin; this.id = id; this.SVManager = new ServerViewManager(id, configHome); this.executor = executor; this.recoverer = recoverer; this.replier = new DefaultReplier(); this.init(); this.recoverer.setReplicaContext(replicaCtx); this.replier.setReplicaContext(replicaCtx); } public void setReplyController(Replier replier) { this.replier = replier; } //******* EDUARDO END **************// // this method initializes the object private void init() { try { cs = new ServerCommunicationSystem(this.SVManager, this); } catch (Exception ex) { Logger.getLogger(TOMReceiver.class.getName()).log(Level.SEVERE, null, ex); throw new RuntimeException("Unable to build a communication system."); } //******* EDUARDO BEGIN **************// if (this.SVManager.isInCurrentView()) { System.out.println("In current view: " + this.SVManager.getCurrentView()); initTOMLayer(-1, -1); // initiaze the TOM layer } else { System.out.println("Not in current view: " + this.SVManager.getCurrentView()); if (this.isToJoin) { System.out.println("Sending join: " + this.SVManager.getCurrentView()); //Não está na visão inicial e é para executar um join; int port = this.SVManager.getStaticConf().getServerToServerPort(id) - 1; String ip = this.SVManager.getStaticConf().getServerToServerRemoteAddress(id).getAddress().getHostAddress(); ReconfigureReply r = null; Reconfiguration rec = new Reconfiguration(id); do { //System.out.println("while 1"); rec.addServer(id, ip, port); r = rec.execute(); } while (!r.getView().isMember(id)); rec.close(); this.SVManager.processJoinResult(r); // initiaze the TOM layer initTOMLayer(r.getLastExecConsId(), r.getExecLeader()); this.cs.updateServersConnections(); this.cs.joinViewReceived(); } else { //Not in the initial view, just waiting for the view where the join has been executed System.out.println("Waiting for the TTP: " + this.SVManager.getCurrentView()); waitTTPJoinMsgLock.lock(); try { canProceed.awaitUninterruptibly(); } finally { waitTTPJoinMsgLock.unlock(); } } } initReplica(); } public void joinMsgReceived(TTPMessage msg) { ReconfigureReply r = msg.getReply(); if (r.getView().isMember(id)) { this.SVManager.processJoinResult(r); initTOMLayer(r.getLastExecConsId(), r.getExecLeader()); // initiaze the TOM layer //this.startState = r.getStartState(); cs.updateServersConnections(); this.cs.joinViewReceived(); waitTTPJoinMsgLock.lock(); canProceed.signalAll(); waitTTPJoinMsgLock.unlock(); } } private void initReplica() { cs.start(); } //******* EDUARDO END **************// /** * This is the method invoked to deliver a totally ordered request. * * @param msg The request delivered by the delivery thread */ @Override public final void receiveReadonlyMessage(TOMMessage tomMsg, MessageContext msgCtx) { byte[] response = null; response = executor.executeUnordered(tomMsg.getContent(), msgCtx); // build the reply and send it to the client tomMsg.reply = new TOMMessage(id, tomMsg.getSession(), tomMsg.getSequence(), response, SVManager.getCurrentViewId(), TOMMessageType.UNORDERED_REQUEST); cs.send(new int[]{tomMsg.getSender()}, tomMsg.reply); } public void receiveMessages(int consId, int regency, boolean fromConsensus, TOMMessage[] requests, byte[] decision) { TOMMessage firstRequest = requests[0]; if(executor instanceof BatchExecutable) { //DEBUG bftsmart.tom.util.Logger.println("BATCHEXECUTOR"); int numRequests = 0; //Messages to put in the batch List<TOMMessage> toBatch = new ArrayList<TOMMessage>(); //Message Contexts (one Context per message in the batch) List<MessageContext> msgCtxts = new ArrayList<MessageContext>(); for (TOMMessage request : requests) { if (!fromConsensus || request.getViewID() == SVManager.getCurrentViewId()) { //If message is a request, put message in the toBatch list if (request.getReqType() == TOMMessageType.ORDERED_REQUEST) { numRequests++; //Make new message context MessageContext msgCtx = new MessageContext( firstRequest.timestamp, firstRequest.nonces, regency, consId, request.getSender(), firstRequest); //Put context in the message context list msgCtxts.add(msgCtx); request.deliveryTime = System.nanoTime(); //Add message to the ToBatch list toBatch.add(request); } else if (request.getReqType() == TOMMessageType.RECONFIG) { // Reconfiguration request to be processed after the // batch SVManager.enqueueUpdate(request); } else { throw new RuntimeException("Should never reach here!"); } } else if (fromConsensus && request.getViewID() < SVManager.getCurrentViewId()) { // message sender had an old view, resend the message to // him (but only if it came from consensus an not state transfer) tomLayer.getCommunication().send( new int[] { request.getSender() }, new TOMMessage(SVManager.getStaticConf() .getProcessId(), request.getSession(), request.getSequence(), TOMUtil .getBytes(SVManager .getCurrentView()), SVManager.getCurrentViewId())); } } //In the end, if there are messages in the Batch if(numRequests > 0){ //Make new batch to deliver byte[][] batch = new byte[numRequests][]; //Put messages in the batch int line = 0; for(TOMMessage m : toBatch){ batch[line] = m.getContent(); line++; } MessageContext[] msgContexts = new MessageContext[msgCtxts.size()]; msgContexts = msgCtxts.toArray(msgContexts); //Deliver the batch and wait for replies byte[][] replies = ((BatchExecutable) executor).executeBatch(batch, msgContexts); //Send the replies back to the client if (fromConsensus) { for(int index = 0; index < toBatch.size(); index++){ TOMMessage request = toBatch.get(index); request.reply = new TOMMessage(id, request.getSession(), request.getSequence(), replies[index], SVManager.getCurrentViewId()); cs.send(new int[] { request.getSender() }, request.reply); } } //DEBUG bftsmart.tom.util.Logger.println("BATCHEXECUTOR END"); } } else { bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) singe executor for consensus " + consId); for (TOMMessage request: requests) { if (!fromConsensus || request.getViewID() == SVManager.getCurrentViewId()) { bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) same view"); if (request.getReqType() == TOMMessageType.ORDERED_REQUEST) { bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) this is a REQUEST type message"); byte[] response = null; //normal request execution //create a context for the batch of messages to be delivered MessageContext msgCtx = new MessageContext(firstRequest.timestamp, firstRequest.nonces, regency, consId, request.getSender(), firstRequest); msgCtx.setBatchSize(requests.length); request.deliveryTime = System.nanoTime(); bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) executing message " + request.getSequence() + " from " + request.getSender() + " decided in consensus " + consId); response = ((SingleExecutable)executor).executeOrdered(request.getContent(), msgCtx); // build the reply and send it to the client if (fromConsensus) { request.reply = new TOMMessage(id, request.getSession(), request.getSequence(), response, SVManager.getCurrentViewId()); bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) sending reply to " + request.getSender()); replier.manageReply(request, msgCtx); bftsmart.tom.util.Logger.println("ACABEI DE ENVIAR: (ServiceReplica.receiveMessages) sending reply to " + request.getSender()); } } else if (request.getReqType() == TOMMessageType.RECONFIG) { //Reconfiguration request to be processed after the batch bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) Enqueing an update"); SVManager.enqueueUpdate(request); } else { throw new RuntimeException("Should never reach here!"); } } else if (fromConsensus && request.getViewID() < SVManager.getCurrentViewId()) { bftsmart.tom.util.Logger.println("(ServiceReplica.receiveMessages) sending current view to " + request.getSender()); //message sender had an old view, resend the message to him tomLayer.getCommunication().send(new int[]{request.getSender()}, new TOMMessage(SVManager.getStaticConf().getProcessId(), request.getSession(), request.getSequence(), TOMUtil.getBytes(SVManager.getCurrentView()), SVManager.getCurrentViewId())); } else { System.out.println("WTF no consenso numero " + consId); } } } //System.out.println("WTF 2"); } /** * This method makes the replica leave the group */ public void leave() { ReconfigureReply r = null; Reconfiguration rec = new Reconfiguration(id); do { //System.out.println("while 1"); rec.removeServer(id); r = rec.execute(); } while (r.getView().isMember(id)); rec.close(); this.cs.updateServersConnections(); } /** * This method initializes the object * * @param cs Server side communication System * @param conf Total order messaging configuration */ private void initTOMLayer(int lastExec, int lastLeader) { if (tomStackCreated) { // if this object was already initialized, don't do it again return; } //******* EDUARDO BEGIN **************// if (!SVManager.isInCurrentView()) { throw new RuntimeException("I'm not an acceptor!"); } //******* EDUARDO END **************// // Assemble the total order messaging layer MessageFactory messageFactory = new MessageFactory(id); LeaderModule lm = new LeaderModule(); Acceptor acceptor = new Acceptor(cs, messageFactory, lm, SVManager); cs.setAcceptor(acceptor); Proposer proposer = new Proposer(cs, messageFactory, SVManager); ExecutionManager executionManager = new ExecutionManager(SVManager, acceptor, proposer, id); acceptor.setExecutionManager(executionManager); tomLayer = new TOMLayer(executionManager, this, recoverer, lm, acceptor, cs, SVManager); executionManager.setTOMLayer(tomLayer); SVManager.setTomLayer(tomLayer); cs.setTOMLayer(tomLayer); cs.setRequestReceiver(tomLayer); acceptor.setTOMLayer(tomLayer); if(SVManager.getStaticConf().isShutdownHookEnabled()){ Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(cs, lm, acceptor, executionManager, tomLayer)); } tomLayer.start(); // start the layer execution tomStackCreated = true; replicaCtx = new ReplicaContext(cs, SVManager); } /** * Obtains the current replica context (getting access to several information * and capabilities of the replication engine). * * @return this replica context */ public final ReplicaContext getReplicaContext() { return replicaCtx; } }