Java tutorial
/** * 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.Arrays; import java.util.Comparator; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.codec.binary.Base64; import bftsmart.communication.client.ReplyListener; import bftsmart.reconfiguration.ReconfigureReply; import bftsmart.reconfiguration.StatusReply; import bftsmart.reconfiguration.views.View; import bftsmart.tom.core.messages.TOMMessage; import bftsmart.tom.core.messages.TOMMessageType; import bftsmart.tom.util.Extractor; import bftsmart.tom.util.Logger; import bftsmart.tom.util.TOMUtil; /** * This class implements a TOMSender and represents a proxy to be used on the * client side of the replicated system. * It sends a request to the replicas, receives the reply, and delivers it to * the application. */ public class ServiceProxy extends TOMSender { // Locks for send requests and receive replies private ReentrantLock canReceiveLock = new ReentrantLock(); private ReentrantLock canSendLock = new ReentrantLock(); private Semaphore sm = new Semaphore(0); private int reqId = -1; // request id private TOMMessageType requestType; private int replyQuorum = 0; // size of the reply quorum private TOMMessage replies[] = null; // Replies from replicas are stored here private int receivedReplies = 0; // Number of received replies private TOMMessage response = null; // Reply delivered to the application private int invokeTimeout = 40; private Comparator<byte[]> comparator; private Extractor extractor; private ReplyListener replyListener = null; /** * Constructor * * @see bellow */ public ServiceProxy(int processId) { this(processId, null, null, null); } /** * Constructor * * @see bellow */ public ServiceProxy(int processId, String configHome) { this(processId, configHome, null, null); } /** * Constructor * * @param processId Process id for this client (should be different from replicas) * @param configHome Configuration directory for BFT-SMART * @param replyComparator used for comparing replies from different servers * to extract one returned by f+1 * @param replyExtractor used for extracting the response from the matching * quorum of replies */ public ServiceProxy(int processId, String configHome, Comparator<byte[]> replyComparator, Extractor replyExtractor) { if (configHome == null) { init(processId); } else { init(processId, configHome); } if (getViewManager().getStaticConf().isBFT()) { replyQuorum = (int) Math .ceil((getViewManager().getCurrentViewN() + getViewManager().getCurrentViewF()) / 2) + 1; } else { replyQuorum = (int) Math.ceil((getViewManager().getCurrentViewN()) / 2) + 1; } replies = new TOMMessage[getViewManager().getCurrentViewN()]; comparator = (replyComparator != null) ? replyComparator : new Comparator<byte[]>() { @Override public int compare(byte[] o1, byte[] o2) { return Arrays.equals(o1, o2) ? 0 : -1; } }; extractor = (replyExtractor != null) ? replyExtractor : new Extractor() { @Override public TOMMessage extractResponse(TOMMessage[] replies, int sameContent, int lastReceived) { return replies[lastReceived]; } }; replyListener = null; } /** * Get the amount of time (in seconds) that this proxy will wait for * servers replies before returning null. * * @return the invokeTimeout */ public int getInvokeTimeout() { return invokeTimeout; } /** * Set the amount of time (in seconds) that this proxy will wait for * servers replies before returning null. * * @param invokeTimeout the invokeTimeout to set */ public void setInvokeTimeout(int invokeTimeout) { this.invokeTimeout = invokeTimeout; } public byte[] invokeOrdered(byte[] request) { return invoke(request, TOMMessageType.ORDERED_REQUEST); } public byte[] invokeUnordered(byte[] request) { return invoke(request, TOMMessageType.UNORDERED_REQUEST); } public void invokeAsynchronous(byte[] request, ReplyListener listener, int[] targets) { reqId = generateRequestId(TOMMessageType.UNORDERED_REQUEST); this.replyListener = listener; if (this.getViewManager().getStaticConf().isTheTTP()) { requestType = TOMMessageType.STATUS_REPLY; try { sendMessageToTargets(request, reqId, targets); } catch (RuntimeException re) { if (re.getMessage().equals("Server not connected")) { TOMMessage tm = new TOMMessage(targets[0], 0, reqId, TOMUtil.getBytes(StatusReply.OFFLINE.toString()), 0, requestType); listener.replyReceived(tm); } } } else sendMessageToTargets(request, reqId, targets); } /** * This method sends a request to the replicas, and returns the related reply. * If the servers take more than invokeTimeout seconds the method returns null. * This method is thread-safe. * * @param request Request to be sent * @param reqType TOM_NORMAL_REQUESTS for service requests, and other for * reconfig requests. * @return The reply from the replicas related to request */ public byte[] invoke(byte[] request, TOMMessageType reqType) { canSendLock.lock(); // Clean all statefull data to prepare for receiving next replies Arrays.fill(replies, null); receivedReplies = 0; response = null; replyListener = null; // Send the request to the replicas, and get its ID reqId = generateRequestId(reqType); requestType = reqType; TOMulticast(request, reqId, reqType); Logger.println("Sending request (" + reqType + ") with reqId=" + reqId); Logger.println("Expected number of matching replies: " + replyQuorum); /*try{ this.sm.acquire(); }catch(Exception e){ Logger.println("Problems acquiering the sem in line 195 of class ServiceProxy."); }*/ // This instruction blocks the thread, until a response is obtained. // The thread will be unblocked when the method replyReceived is invoked // by the client side communication system try { if (!this.sm.tryAcquire(invokeTimeout, TimeUnit.SECONDS)) { Logger.println("###################TIMEOUT#######################"); Logger.println("Reply timeout for reqId=" + reqId); canSendLock.unlock(); System.out.print(getProcessId() + " // " + reqId + " // TIMEOUT // "); System.out.println("Replies received: " + receivedReplies); System.exit(0); return null; } } catch (InterruptedException ex) { } Logger.println("Response extracted = " + response); byte[] ret = null; if (response == null) { //the response can be null if n-f replies are received but there isn't //a replyQuorum of matching replies Logger.println("Received n-f replies and no response could be extracted."); canSendLock.unlock(); if (reqType == TOMMessageType.UNORDERED_REQUEST) { //invoke the operation again, whitout the read-only flag Logger.println("###################RETRY#######################"); return invokeOrdered(request); } else { throw new RuntimeException("Received n-f replies without f+1 of them matching."); } } else { //normal operation //******* EDUARDO BEGIN **************// if (reqType == TOMMessageType.ORDERED_REQUEST) { //Reply to a normal request! if (response.getViewID() == getViewManager().getCurrentViewId()) { ret = response.getContent(); // return the response } else {//if(response.getViewID() > getViewManager().getCurrentViewId()) //updated view received reconfigureTo((View) TOMUtil.getObject(response.getContent())); canSendLock.unlock(); return invoke(request, reqType); } } else { if (response.getViewID() > getViewManager().getCurrentViewId()) { //Reply to a reconfigure request! Logger.println("Reconfiguration request' reply received!"); Object r = TOMUtil.getObject(response.getContent()); if (r instanceof View) { //did not executed the request because it is using an outdated view reconfigureTo((View) r); canSendLock.unlock(); return invoke(request, reqType); } else { //reconfiguration executed! reconfigureTo(((ReconfigureReply) r).getView()); ret = response.getContent(); } } else { // Reply to readonly request ret = response.getContent(); } } } //******* EDUARDO END **************// canSendLock.unlock(); return ret; } //******* EDUARDO BEGIN **************// private void reconfigureTo(View v) { Logger.println("Installing a most up-to-date view with id=" + v.getId()); getViewManager().reconfigureTo(v); replies = new TOMMessage[getViewManager().getCurrentViewN()]; getCommunicationSystem().updateConnections(); } //******* EDUARDO END **************// /** * This is the method invoked by the client side comunication system. * * @param reply The reply delivered by the client side comunication system */ @Override public void replyReceived(TOMMessage reply) { Logger.println("EU SOU: " + this.getProcessId() + " RECEBI MSG DE: " + reply.getSender() + " COM O ID:" + reply.getId() + " COM O CONTEUDO: " + Base64.encodeBase64String(reply.getContent())); canReceiveLock.lock(); if (reqId == -1) {//no message being expected Logger.println("throwing out request: sender=" + reply.getSender() + " reqId=" + reply.getSequence()); canReceiveLock.unlock(); return; } int pos = getViewManager().getCurrentViewPos(reply.getSender()); if (pos < 0) { //ignore messages that don't come from replicas canReceiveLock.unlock(); return; } if (reply.getSequence() == reqId && reply.getReqType() == requestType) { if (replyListener != null) { replyListener.replyReceived(reply); canReceiveLock.unlock(); return; } Logger.println("Receiving reply from " + reply.getSender() + " with reqId:" + reply.getSequence() + ". Putting on pos=" + pos); if (replies[pos] == null) { receivedReplies++; } replies[pos] = reply; // Compare the reply just received, to the others int sameContent = 1; for (int i = 0; i < replies.length; i++) { if (i != pos && replies[i] != null && (comparator.compare(replies[i].getContent(), reply.getContent()) == 0)) { sameContent++; if (sameContent >= replyQuorum) { response = extractor.extractResponse(replies, sameContent, pos); reqId = -1; this.sm.release(); // resumes the thread that is executing the "invoke" method break; } } } if (response == null) { if (requestType.equals(TOMMessageType.ORDERED_REQUEST)) { if (receivedReplies == getViewManager().getCurrentViewN()) { reqId = -1; this.sm.release(); // resumes the thread that is executing the "invoke" method } } else { // UNORDERED if (receivedReplies != sameContent) { reqId = -1; this.sm.release(); // resumes the thread that is executing the "invoke" method } } } } // Critical section ends here. The semaphore can be released canReceiveLock.unlock(); } }