/** * 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.clientsmanagement; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.locks.ReentrantLock; import bftsmart.communication.ServerCommunicationSystem; import bftsmart.reconfiguration.ServerViewManager; import bftsmart.tom.core.messages.TOMMessage; import bftsmart.tom.core.timer.RequestsTimer; import bftsmart.tom.util.Logger; /** * * @author alysson */ public class ClientsManager { private ServerViewManager manager; private RequestsTimer timer; private HashMap<Integer, ClientData> clientsData = new HashMap<Integer, ClientData>(); private ReentrantLock clientsLock = new ReentrantLock(); public ClientsManager(ServerViewManager manager, RequestsTimer timer) { this.manager = manager; this.timer = timer; } /** * We are assuming that no more than one thread will access * the same clientData during creation. * * * @param clientId * @return the ClientData stored on the manager */ public ClientData getClientData(int clientId) { clientsLock.lock(); /******* BEGIN CLIENTS CRITICAL SECTION ******/ ClientData clientData = clientsData.get(clientId); if (clientData == null) { Logger.println("(ClientsManager.getClientData) Creating new client data, client id=" + clientId); //******* EDUARDO BEGIN **************// clientData = new ClientData(clientId, (manager.getStaticConf().getUseSignatures() == 1) ? manager.getStaticConf().getRSAPublicKey(clientId) : null); //******* EDUARDO END **************// clientsData.put(clientId, clientData); } /******* END CLIENTS CRITICAL SECTION ******/ clientsLock.unlock(); return clientData; } /** * Get pending requests in a fair way (one request from each client * queue until the max number of requests is obtained). * * @return the set of all pending requests of this system */ public RequestList getPendingRequests() { RequestList allReq = new RequestList(); clientsLock.lock(); /******* BEGIN CLIENTS CRITICAL SECTION ******/ Set<Entry<Integer, ClientData>> clientsEntrySet = clientsData.entrySet(); for (int i = 0; true; i++) { Iterator<Entry<Integer, ClientData>> it = clientsEntrySet.iterator(); int noMoreMessages = 0; while (it.hasNext() && allReq.size() < manager.getStaticConf().getMaxBatchSize() && noMoreMessages < clientsEntrySet.size()) { ClientData clientData = it.next().getValue(); RequestList clientPendingRequests = clientData.getPendingRequests(); clientData.clientLock.lock(); /******* BEGIN CLIENTDATA CRITICAL SECTION ******/ TOMMessage request = (clientPendingRequests.size() > i) ? clientPendingRequests.get(i) : null; /******* END CLIENTDATA CRITICAL SECTION ******/ clientData.clientLock.unlock(); if (request != null) { if(!request.alreadyProposed) { //this client have pending message request.alreadyProposed = true; allReq.addLast(request); } } else { //this client don't have more pending requests noMoreMessages++; } } if(allReq.size() == manager.getStaticConf().getMaxBatchSize() || noMoreMessages == clientsEntrySet.size()) { break; } } /******* END CLIENTS CRITICAL SECTION ******/ clientsLock.unlock(); return allReq; } /** * We've implemented some protection for individual client * data, but the clients table can change during the operation. * * @return true if there are some pending requests and false otherwise */ public boolean havePendingRequests() { boolean havePending = false; clientsLock.lock(); /******* BEGIN CLIENTS CRITICAL SECTION ******/ Iterator<Entry<Integer, ClientData>> it = clientsData.entrySet().iterator(); while (it.hasNext() && !havePending) { ClientData clientData = it.next().getValue(); clientData.clientLock.lock(); RequestList reqs = clientData.getPendingRequests(); if (!reqs.isEmpty()) { for(TOMMessage msg:reqs) { if(!msg.alreadyProposed) { havePending = true; break; } } } clientData.clientLock.unlock(); } /******* END CLIENTS CRITICAL SECTION ******/ clientsLock.unlock(); return havePending; } /** * Verifies if some reqId is pending. * * @param reqId the request identifier * @return true if the request is pending */ public boolean isPending(int reqId) { return getPending(reqId) != null; } /** * Get some reqId that is pending. * * @param reqId the request identifier * @return the pending request, or null */ public TOMMessage getPending(int reqId) { ClientData clientData = getClientData(TOMMessage.getSenderFromId(reqId)); clientData.clientLock.lock(); /******* BEGIN CLIENTDATA CRITICAL SECTION ******/ TOMMessage pendingMessage = clientData.getPendingRequests().getById(reqId); /******* END CLIENTDATA CRITICAL SECTION ******/ clientData.clientLock.unlock(); return pendingMessage; } public boolean requestReceived(TOMMessage request, boolean fromClient) { return requestReceived(request, fromClient, null); } /** * Notifies the ClientsManager that a new request from a client arrived. * This method updates the ClientData of the client request.getSender(). * * @param request the received request * @param fromClient the message was received from client or not? * @param storeMessage the message should be stored or not? (read-only requests are not stored) * @param cs server com. system to be able to send replies to already processed requests * * @return true if the request is ok and is added to the pending messages * for this client, false if there is some problem and the message was not * accounted */ public boolean requestReceived(TOMMessage request, boolean fromClient, ServerCommunicationSystem cs) { request.receptionTime = System.nanoTime(); int clientId = request.getSender(); boolean accounted = false; //Logger.println("(ClientsManager.requestReceived) getting info about client "+clientId); ClientData clientData = getClientData(clientId); //Logger.println("(ClientsManager.requestReceived) wait for lock for client "+clientData.getClientId()); clientData.clientLock.lock(); /******* BEGIN CLIENTDATA CRITICAL SECTION ******/ //Logger.println("(ClientsManager.requestReceived) lock for client "+clientData.getClientId()+" acquired"); /* ################################################ */ //pjsousa: simple flow control mechanism to avoid out of memory exception if (fromClient && (manager.getStaticConf().getUseControlFlow() != 0)) { if (clientData.getPendingRequests().size() > manager.getStaticConf().getUseControlFlow()) { //clients should not have more than defined in the config file //outstanding messages, otherwise they will be dropped. //just account for the message reception clientData.setLastMessageReceived(request.getSequence()); clientData.setLastMessageReceivedTime(request.receptionTime); clientData.clientLock.unlock(); return false; } } /* ################################################ */ //new session... just reset the client counter if (clientData.getSession() != request.getSession()) { clientData.setSession(request.getSession()); clientData.setLastMessageReceived(-1); } if ((clientData.getLastMessageReceived() == -1) || //first message received or new session (see above) (clientData.getLastMessageReceived() + 1 == request.getSequence()) || //message received is the expected ((request.getSequence() > clientData.getLastMessageReceived()) && !fromClient)) { //it is a new message and I have to verify it's signature if (!request.signed || clientData.verifySignature(request.serializedMessage, request.serializedMessageSignature)) { //I don't have the message but it is valid, I will //insert it in the pending requests of this client clientData.getPendingRequests().add(request); clientData.setLastMessageReceived(request.getSequence()); clientData.setLastMessageReceivedTime(request.receptionTime); //create a timer for this message if (timer != null) { timer.watch(request); } accounted = true; } } else { //I will not put this message on the pending requests list if (clientData.getLastMessageReceived() >= request.getSequence()) { //I already have/had this message //send reply if it is available TOMMessage reply = clientData.getReply(request.getId()); if (reply != null && cs != null) { cs.send(new int[]{request.getSender()}, reply); } accounted = true; } else { //a too forward message... the client must be malicious accounted = false; } } /******* END CLIENTDATA CRITICAL SECTION ******/ clientData.clientLock.unlock(); return accounted; } /** * Notifies the ClientsManager that these requests were already executed. * * @param requests the array of requests to account as ordered */ public void requestsOrdered(TOMMessage[] requests) { clientsLock.lock(); for (TOMMessage request : requests) { requestOrdered(request); } clientsLock.unlock(); } /** * Cleans all state for this request (e.g., removes it from the pending * requests queue and stop any timer for it). * * @param request the request ordered by the consensus */ private void requestOrdered(TOMMessage request) { //stops the timer associated with this message if (timer != null) { timer.unwatch(request); } ClientData clientData = getClientData(request.getSender()); clientData.clientLock.lock(); /******* BEGIN CLIENTDATA CRITICAL SECTION ******/ if (!clientData.removeOrderedRequest(request)) { Logger.println("(ClientsManager.requestOrdered) Request " + request + " does not exist in pending requests"); } clientData.setLastMessageExecuted(request.getSequence()); /******* END CLIENTDATA CRITICAL SECTION ******/ clientData.clientLock.unlock(); } public ReentrantLock getClientsLock() { return clientsLock; } }