/** * 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; import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; import framework.CompressedLongSet; import seqSamoa.exceptions.InterruptedSchedulerException; import seqSamoa.exceptions.NotInAComputationException; import seqSamoa.exceptions.UndeclaredCallOrResponseException; /** * This manager implements the route version of the algorithm to ensure the * module order property. */ public class RouteModuleOrderManager implements ConcurrencyManager { // This class represents a computation private static class Computation { /* The computation ID in which the task is executed */ public long cID; /* The task currently executed by the computation*/ public AtomicTask currentTask; /* Sorted internal tasks */ public LinkedList<AtomicTask> readyTasks = new LinkedList<AtomicTask>(); /* Internal Tasks not yet sorted */ public LinkedList<AtomicTask> initiatedTasks = new LinkedList<AtomicTask>(); // The alive CORs in each critical path public HashMap<Long, HashMap<ServiceCallOrResponse, Integer>> pathAliveCORs = new HashMap<Long, HashMap<ServiceCallOrResponse, Integer>>(); public Computation(long cID) { this.cID = cID; } } // This class represents threads private static class TaskThread extends Thread { /* The concurrency manager */ private RouteModuleOrderManager scheduler; /* The computation that is currently executed by the Thread */ public Computation currentComputation; public TaskThread(RouteModuleOrderManager scheduler) { this.scheduler = scheduler; } public void run() { try { while (true) { // Get a computation to be executed currentComputation = scheduler.getComputation(this); // Execute the computation toExecute while (!currentComputation.readyTasks.isEmpty()) { currentComputation.currentTask = currentComputation.readyTasks.removeFirst(); // Is the task ready to be executed according to // the policy implemented by the scheduler scheduler.waitTaskIsReady(currentComputation.currentTask); // Execute the task currentComputation.currentTask.execute(); // Adds the tasks initiated by task in readyTasks int size = currentComputation.readyTasks.size(); int startI = 0; while (!currentComputation.initiatedTasks.isEmpty()) { AtomicTask initiatedTask = currentComputation.initiatedTasks .removeFirst(); int index = startI; if (initiatedTask.triggeringModule != null) { for (int i = startI; i < size; i++) { AtomicTask sTaskTmp = currentComputation.readyTasks .get(i); if (initiatedTask.triggeringModule.equals(sTaskTmp.triggeringModule)) index = i + 1; } } currentComputation.readyTasks.add(index, initiatedTask); startI = index + 1; } // Indicates to the scheduler that the task // has been executed scheduler.taskExecuted(currentComputation.currentTask, currentComputation); } scheduler.finishComputation(this, currentComputation); currentComputation = null; } } catch (Exception ie) { System.out.println("Scheduler Thread Closed due to Exception."); } } } // HashMap that contains the call or response causally depending // on the given call or response private HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> dependenciesAndInfluences = new HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>>(); // All the service calls and responses private HashSet<ServiceCallOrResponse> allServicesCOR = new HashSet<ServiceCallOrResponse>(); // HashMap that contains for each ServiceCallOrResponse the order // in which computations call execute the call or response private HashMap<ServiceCallOrResponse, LinkedList<Long>> pathAccessRights = new HashMap<ServiceCallOrResponse, LinkedList<Long>>(); // List of computations that are scheduled private LinkedList<Computation> scheduledComputations = new LinkedList<Computation>(); // List of computations that are finished private CompressedLongSet finishedComputations = new CompressedLongSet(); // The next computation id available private long nextComputationID = 1; // The next critical path id avaliable private long nextCriticalPathID = 1; // The maximal number of thread private int nbThread; // The thread that is executed in sequential mode (in order to execute // AtomicTasks) private Thread sequentialThread = null; // The Thread that runs the sequential Manager private HashSet<TaskThread> runners = new HashSet<TaskThread>(); /** * Constructor * * @param nbThread * The maximal number of concurrent thread managed by the * scheduler */ public RouteModuleOrderManager(int nbThread) { super(); this.nbThread = nbThread; } public void waitEnd(long cID) throws InterruptedSchedulerException { synchronized (finishedComputations) { while (!finishedComputations.contains(cID)) { try { finishedComputations.wait(); } catch (InterruptedException ie) { throw new InterruptedSchedulerException(cID); } } } } // Schedule a new computation (i.e., a new external call or response) public long addExternalTask(AtomicTask task) { synchronized (scheduledComputations) { // Create the computation Computation c = new Computation(this.nextComputationID); c.readyTasks.addLast(task); // Increment cID for the following computation this.nextComputationID++; // Add the computation to scheduled computation scheduledComputations.addLast(c); // Notify since new computations are possibly executable scheduledComputations.notifyAll(); return c.cID; } } // Schedule a new internal call or response public void addInternalTask(AtomicTask task) throws NotInAComputationException { Computation c = currentComputation(); if (c == null) throw new NotInAComputationException(""); c.initiatedTasks.addLast(task); // Set the module that initiate the task task.triggeringModule = c.currentTask.currentModule; // Initiate the critical path if the first event // of the computation is critical if (task.getCOR().isCritical) { if (c.currentTask.pathID == 0) { initiateCriticalPath(task, c); } else if (c.currentTask.pathID != 0) { // Maintain Alive COR Iterator<ServiceCallOrResponse> itCOR = dependenciesAndInfluences .get(task.getCOR()).iterator(); while (itCOR.hasNext()) { ServiceCallOrResponse corTmp = itCOR.next(); int counter = c.pathAliveCORs.get(c.currentTask.pathID).get(corTmp); c.pathAliveCORs.get(c.currentTask.pathID).put(corTmp, counter + 1); } task.pathID = c.currentTask.pathID; } } else { task.pathID = 0; } } // Schedule a new atomic task public long scheduleAtomicTask(AtomicTask task) { synchronized (scheduledComputations) { // Create the computation and add it to scheduled computation Computation c = new Computation(this.nextComputationID); c.readyTasks.addLast(task); // Increment cID for the following computation this.nextComputationID++; // Add the computation to scheduled computation scheduledComputations.addLast(c); // Notify since new computations are possibly executable scheduledComputations.notifyAll(); return c.cID; } } // Returns the current atomic task public AtomicTask currentTask() { Computation c = currentComputation(); if (c != null) return c.currentTask; else return null; } public void start() { for (int i = 0; i < nbThread; i++) { TaskThread runner = new TaskThread(this); runner.start(); this.runners.add(runner); } } public void close() { Iterator<TaskThread> it = runners.iterator(); while (it.hasNext()) it.next().interrupt(); } @SuppressWarnings("unchecked") public void stackReconfigured(ProtocolStack stack) { synchronized (pathAccessRights) { pathAccessRights = new HashMap<ServiceCallOrResponse, LinkedList<Long>>(); // Remove all services from the stack before reconfiguration Iterator<ServiceCallOrResponse> itOldServices = allServicesCOR .iterator(); while (itOldServices.hasNext()) { Service s = itOldServices.next().service; if (s.stack.equals(stack)) itOldServices.remove(); } // Add all services in the stack after reconfiguration // and create the new table of access rights Iterator<Service> itNewServices = stack.allServices.values() .iterator(); while (itNewServices.hasNext()) { Service service = itNewServices.next(); ServiceCallOrResponse call = ServiceCallOrResponse .createServiceCallOrResponse(service, true); ServiceCallOrResponse resp = ServiceCallOrResponse .createServiceCallOrResponse(service, false); allServicesCOR.add(call); allServicesCOR.add(resp); } dependenciesAndInfluences = computeServicesInfluencesAndDependencies(); // Construct the table of access rights Iterator<ServiceCallOrResponse> itCOR = allServicesCOR.iterator(); while (itCOR.hasNext()) pathAccessRights.put(itCOR.next(), new LinkedList<Long>()); } } private void initiateCriticalPath(AtomicTask task, Computation c) { synchronized (pathAccessRights) { task.pathID = nextCriticalPathID; // Take the "version" for each COR that may be executed by the // computation and update the aliveCors HashMap<ServiceCallOrResponse, Integer> pathAliveCORs = new HashMap<ServiceCallOrResponse, Integer>(); Iterator<ServiceCallOrResponse> itCOR = this.dependenciesAndInfluences .get(task.getCOR()).iterator(); while (itCOR.hasNext()) { ServiceCallOrResponse corTmp = itCOR.next(); synchronized (corTmp) { this.pathAccessRights.get(corTmp).addLast(task.pathID); } pathAliveCORs.put(corTmp, 1); } c.pathAliveCORs.put(task.pathID, pathAliveCORs); // Update the path ID for the next critical path nextCriticalPathID++; } } // Return the current computation private Computation currentComputation() { Thread t = Thread.currentThread(); if (t instanceof TaskThread) return ((TaskThread) t).currentComputation; else return null; } // Return the next computation to be executed private Computation getComputation(Thread thread) { Computation c; // Wait that a computation is scheduled synchronized (scheduledComputations) { while ((scheduledComputations.isEmpty()) || ((sequentialThread != null) && (!sequentialThread .equals(thread)))) { try { scheduledComputations.wait(); } catch (InterruptedException ex) { throw new RuntimeException("Scheduler interrupted"); } } c = scheduledComputations.removeFirst(); ServiceCallOrResponse firstTaskCor = c.readyTasks.getFirst() .getCOR(); if ((firstTaskCor.equals(ServiceCallOrResponse.nullCOR)) || (firstTaskCor.service.stack.isReconfigured)) { // The task is atomic or some stack is reconfigured: // the computation has to be executed sequentially with other // computations // = > wait that no other thread executes a computation this.sequentialThread = thread; boolean computationsBeingExecuted = true; while (computationsBeingExecuted) { computationsBeingExecuted = false; Iterator<TaskThread> it = runners.iterator(); while ((it.hasNext()) && (!computationsBeingExecuted)) if (it.next().currentComputation != null) computationsBeingExecuted = true; if (computationsBeingExecuted) { try { scheduledComputations.wait(); } catch (InterruptedException ex) { throw new RuntimeException("Scheduler interrupted"); } } } } else { // Initiate the critical path if the first event // of the computation is critical AtomicTask firstTask = c.readyTasks.getFirst(); if (firstTask.getCOR().isCritical) initiateCriticalPath(firstTask, c); } } return c; } // Return when the task is ready private void waitTaskIsReady(AtomicTask task) { ServiceCallOrResponse cor = task.getCOR(); if (cor.isCritical) { synchronized (cor) { if (!pathAccessRights.get(cor).contains(task.pathID)) throw new UndeclaredCallOrResponseException(cor); while (pathAccessRights.get(cor).getFirst() != task.pathID) { try { cor.wait(); } catch (InterruptedException ex) { throw new RuntimeException("Scheduler interrupted"); } } } } } // Do what is necessary upon end of a task private void taskExecuted(AtomicTask task, Computation c) { if (task.getCOR().equals(ServiceCallOrResponse.nullCOR)) { synchronized (scheduledComputations) { this.sequentialThread = null; scheduledComputations.notifyAll(); } } else if (task.pathID != 0) { synchronized (pathAccessRights) { Iterator<ServiceCallOrResponse> itInfCor = dependenciesAndInfluences .get(task.getCOR()).iterator(); while (itInfCor.hasNext()) { ServiceCallOrResponse corTmp = itInfCor.next(); if (!corTmp.equals(ServiceCallOrResponse.nullCOR)) { int counter = c.pathAliveCORs.get(task.pathID).get(corTmp) - 1; if (counter > 0) { c.pathAliveCORs.get(task.pathID).put(corTmp, counter); } else { synchronized (corTmp) { pathAccessRights.get(corTmp).remove(task.pathID); corTmp.notifyAll(); } } } } } } } // Finish the computation private void finishComputation(Thread thread, Computation c) { synchronized (finishedComputations) { // The computation is finished finishedComputations.add(c.cID); // Notify since version access rights finishedComputations.notifyAll(); } } // Compute the mixed influences and dependencies between services private HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> computeServicesInfluencesAndDependencies() { HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> influences = computeServicesInfluences(); HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> dependencies = computeServicesDependencies(); HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> result = new HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>>(); Iterator<ServiceCallOrResponse> itServiceCallOrResponse = dependencies .keySet().iterator(); while (itServiceCallOrResponse.hasNext()) { ServiceCallOrResponse callOrResp = itServiceCallOrResponse.next(); HashSet<ServiceCallOrResponse> depAndInf = new HashSet<ServiceCallOrResponse>(); Iterator<ServiceCallOrResponse> itOnDep = dependencies.get( callOrResp).iterator(); while (itOnDep.hasNext()) { ServiceCallOrResponse depCOR = itOnDep.next(); Iterator<ServiceCallOrResponse> itOnInf = influences .get(depCOR).iterator(); while (itOnInf.hasNext()) depAndInf.add(itOnInf.next()); } result.put(callOrResp, depAndInf); } return result; } // Compute the influences between services @SuppressWarnings("unchecked") private HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> computeServicesInfluences() { HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> result = new HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>>(); // Compute dependencies for all the services calls and responses Iterator<ServiceCallOrResponse> itAllServices = allServicesCOR .iterator(); while (itAllServices.hasNext()) { ServiceCallOrResponse cor = itAllServices.next(); Service service = cor.service; HashSet<ServiceCallOrResponse> influences = new HashSet<ServiceCallOrResponse>(); if (cor.isCritical) { influences.add(cor); // Compute all the handlers executed upon callOrResp HashSet<Service.Handler> executedHandlers = new HashSet<Service.Handler>(); executedHandlers.addAll(service.boundInterceptors); if ((cor.call) && (service.currentExecuter != null)) executedHandlers.add(service.currentExecuter); else if (!cor.call) executedHandlers.addAll(service.allListeners.values()); // Compute the influences (only critical services are // considered) Iterator<Service.Handler> itHandlers = executedHandlers .iterator(); while (itHandlers.hasNext()) { ProtocolModule pModule = itHandlers.next().parent; Iterator<Service.Executer> itExec = pModule.allExecuters .iterator(); while (itExec.hasNext()) { Service sExec = itExec.next().getService(); ServiceCallOrResponse corTmp = ServiceCallOrResponse .createServiceCallOrResponse(sExec, true); if (corTmp.isCritical) influences.add(corTmp); } Iterator<Service.Interceptor> itInter = pModule.allInterceptors .iterator(); while (itInter.hasNext()) { Service sInt = itInter.next().getService(); ServiceCallOrResponse corTmp = ServiceCallOrResponse .createServiceCallOrResponse(sInt, true); if (corTmp.isCritical) influences.add(corTmp); corTmp = ServiceCallOrResponse .createServiceCallOrResponse(sInt, false); if (corTmp.isCritical) influences.add(corTmp); } Iterator<Service.Listener> itList = pModule.allListeners .iterator(); while (itExec.hasNext()) { Service sList = itList.next().getService(); ServiceCallOrResponse corTmp = ServiceCallOrResponse .createServiceCallOrResponse(sList, false); if (corTmp.isCritical) influences.add(corTmp); } } } result.put(cor, influences); } // Return the hashmap with all services influences return result; } // Compute the dependencies between services @SuppressWarnings("unchecked") private HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> computeServicesDependencies() { HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>> result = new HashMap<ServiceCallOrResponse, HashSet<ServiceCallOrResponse>>(); // Create a list of influences for all service calls and responses Iterator<ServiceCallOrResponse> itAllServices = allServicesCOR .iterator(); while (itAllServices.hasNext()) { ServiceCallOrResponse cor = itAllServices.next(); HashSet<ServiceCallOrResponse> dependencies = new HashSet<ServiceCallOrResponse>(); if (cor.isCritical) dependencies.add(cor); result.put(cor, dependencies); } // Compute dependencies for all the services calls and responses Iterator<ServiceCallOrResponse> itCOR = result.keySet().iterator(); while (itCOR.hasNext()) { ServiceCallOrResponse cor = itCOR.next(); if (cor.isCritical) { Service service = cor.service; HashSet<ServiceCallOrResponse> dependencies = result.get(cor); // Compute all the handlers executed upon callOrResp HashSet<Service.Handler> executedHandlers = new HashSet<Service.Handler>(); executedHandlers.addAll(service.boundInterceptors); if ((cor.call) && (service.currentExecuter != null)) executedHandlers.add(service.currentExecuter); else if (!cor.call) executedHandlers.addAll(service.allListeners.values()); // Compute the dependencies Iterator<Service.Handler> itHandlers = executedHandlers .iterator(); while (itHandlers.hasNext()) { Service.Handler h = itHandlers.next(); Iterator<ServiceCallOrResponse> itSerCOR = h.initiatedCallsAndResponses .iterator(); while (itSerCOR.hasNext()) { ServiceCallOrResponse sCOR = itSerCOR.next(); Iterator<ServiceCallOrResponse> itToBeAdded = result .get(sCOR).iterator(); while (itToBeAdded.hasNext()) { ServiceCallOrResponse corTmp = itToBeAdded.next(); if (corTmp.isCritical) dependencies.add(corTmp); } } } // Transitive closure Iterator<ServiceCallOrResponse> itResult = result.keySet() .iterator(); while (itResult.hasNext()) { ServiceCallOrResponse corTmp = itResult.next(); HashSet<ServiceCallOrResponse> dependenciesTmp = result .get(corTmp); if (dependenciesTmp.contains(cor)) { Iterator<ServiceCallOrResponse> itToBeAdded = dependencies .iterator(); while (itToBeAdded.hasNext()) { dependenciesTmp.add(itToBeAdded.next()); } } } } } // Return the hashmap with all dependencies between services return result; } }