/** * 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.util.HashSet; import java.util.Iterator; import seqSamoa.exceptions.InterruptedSchedulerException; import seqSamoa.exceptions.NotInAComputationException; import seqSamoa.exceptions.NotScheduledTaskException; /** * A <CODE>scheduler</CODE> manages the execution of {@link seqSamoa.Service service} * calls and responses. The scheduler to ensure properties * such as Extended Causal Order and the different flavor of the isolation properties. * Furthermore, the <CODE>scheduler</CODE> manages the execution of the tasks that can * be delayed, executed periodically. * * In order to ensure this properties, a concurrency manager has to be passed * in parameters upon the creation of the scheduler. The concurrency manager * describes the behavior of the scheduler. */ public class SamoaScheduler { // This class represents a task that is delayed (i.e., that have to wait before being scheduled) static private class DelayedTask { public long period; public long start; public boolean periodic; public AtomicTask task; public SamoaScheduler scheduler; protected DelayedTask(AtomicTask task, boolean periodic, long period, SamoaScheduler scheduler) { this.start = System.currentTimeMillis(); this.task = task; this.period = period; this.periodic = periodic; this.scheduler = scheduler; } protected long updateAndRun() { long now = System.currentTimeMillis(); if ((now - this.start - period + scheduler.epsilon) >= 0) { scheduler.schedule(task); if (this.periodic) this.start = now; else { this.start = -1; } } return (this.start+period-now-scheduler.epsilon); } public int hashCode(){ return task.hashCode(); } public boolean equals(Object o){ if (!(o instanceof DelayedTask)) return false; DelayedTask dt = (DelayedTask) o; return (dt.task.equals(this.task)); } public String toString() { return this.task.toString(); } } // Execute the computation if nextStart is smaller than EPSILON private int epsilon = 0; static private int DEFAULT_EPSILON = 10; // Time to execute the while loop with an addition static private int TIME_WHILE = 5; static private int STEP_WHILE = 100; // The period to wait before executing the next delayed task private long timeWait = 0; // The period already waited private long timeWaited = 0; // The time when starts the last sleep period private long lastSleepPeriod = 0; // List of tasks delayed private HashSet<DelayedTask> delayedTasks; // Has the scheduler to be closed private boolean toBeClosed = false; // Thread delaying the tasks private Thread delayer; // The concurrency manager private ConcurrencyManager manager; /** * Constructor with a default precision set to 100ms (see the other constructor for more details). */ public SamoaScheduler(ConcurrencyManager manager) { this.manager = manager; this.delayedTasks = new HashSet<DelayedTask>(); this.epsilon = DEFAULT_EPSILON; } /** * Constructor * * @param epsilon * determine the precision of the timer. More precisely if a task t is scheduled in * n milliseconds then the <CODE>scheduler</CODE> ensures that t is not executed before t-epsilon * milliseconds. Note that a too small value for epsilon may result in bigger delays than * expected for delayed {@link seqSamoa.AtomicTask tasks}. */ public SamoaScheduler(int epsilon, ConcurrencyManager manager) { this.manager = manager; this.delayedTasks = new HashSet<DelayedTask>(); this.epsilon = epsilon; } /** * Start the scheduler. Note that the {@link seqSamoa.ProtocolStack stacks} * managed by the scheduler do nothing before the scheduler is started. */ public void start() { toBeClosed = false; this.manager.start(); delayer = new Thread() { public void run() { try { while (true) { // If there is no task to be executed, wait!!! synchronized (this) { timeWaited = 0; while ((timeWait == 0) && (!toBeClosed)) wait(); } // wait the necessary time while ((timeWaited + STEP_WHILE < timeWait)) { lastSleepPeriod = System.currentTimeMillis(); Thread.sleep(STEP_WHILE - TIME_WHILE); synchronized (this) { timeWaited = timeWaited + STEP_WHILE; } } if (timeWait > timeWaited) Thread.sleep(timeWait - timeWaited); if (toBeClosed) return; long minTime = Long.MAX_VALUE; // Execute the delayed tasks that can be executed synchronized(this) { Iterator<DelayedTask> it = delayedTasks.iterator(); while (it.hasNext()) { DelayedTask dTask = it.next(); long t = dTask.updateAndRun(); if (t <= 0) it.remove(); else if (t < minTime) minTime = t; } } // Compute the next waiting timer if (minTime == Long.MAX_VALUE) timeWait = 0; else timeWait = minTime; } } catch (InterruptedException ex) { throw new RuntimeException("SamoaScheduler.delayer: unrespected delay!"); } } }; delayer.start(); } /** * Close the scheduler. As a result, all {@link seqSamoa.ProtocolStack stacks} managed * by this scheduler stop their execution. */ public void close() { toBeClosed = true; manager.close(); synchronized (this.delayer) { this.delayer.notifyAll(); } } /** * This method returns the {@link seqSamoa.AtomicTask task} that is currently * executed by the scheduler. * * @return the {@link seqSamoa.AtomicTask task} currently executed */ public AtomicTask currentTask() { return this.manager.currentTask(); } /** * This method returns when the computation or the task identified by the * cID finishes. More precisely, if cID identifies a computation, * the method returns when all the {@link seqSamoa.Service service} calls and response * that causally depends on the external {@link seqSamoa.Service service} * are executed. Otherwise, it returns when the task an the resulting calls * and response terminate. * * @param cID * the id of the task or the corresponding external * {@link seqSamoa.Service service} call or response * we want to wait the end * */ public void waitEnd(long cID) throws InterruptedSchedulerException { manager.waitEnd(cID); } // Schedule a new computation (i.e., a new external call or response) protected long addExternalTask(AtomicTask task){ return manager.addExternalTask(task); } // Schedule a new internal call or response protected void addInternalTask(AtomicTask task) throws NotInAComputationException { manager.addInternalTask(task); } /** * This method informs the scheduler that a stack was reconfigured, i.e., * some {@link seqSamoa.Service.Executer executer}, * {@link seqSamoa.Service.Interceptor interceptor} or * {@link seqSamoa.Service.Listener listener} has been bound, unbound * removed or added to the {@link seqSamoa.ProtocolStack stack} * given in parameter. * * @param stack * The {@link seqSamoa.ProtocolStack stack} that is reconfigured */ public void stackReconfigured(ProtocolStack stack) { this.manager.stackReconfigured(stack); } /** * Schedules a {@link seqSamoa.AtomicTask task}. The task is executed in * isolation with computations. The method returns an identifier * that identifies the task. * * @param t * The task to be executed * * @return * The computation id attached to this task and -1 if * the task is scheduled part of a computation. */ public long schedule(AtomicTask t) { return manager.scheduleAtomicTask(t); } /** * Schedules a {@link seqSamoa.AtomicTask task} for the amount of time passed in the * third parameter. When this period of time has passed by, the {@link seqSamoa.AtomicTask task} * is executed in isolation with computations. * * Timers can be non-periodic: the {@link seqSamoa.AtomicTask task} is triggered at most once. * Then, it is immediately canceled. Timers can also be periodic: they are * re-scheduled every time they expire, until its cancellation. * * There is no guarantee in the maximum amount of time between the * scheduling operation and the actual execution of the {@link seqSamoa.AtomicTask task}. * The only guarantee is in the minimum amount of time. * * @param t * The task to be executed * @param periodic * Indicates whether this timer is periodic or non-periodic * @param time * The lapse, in milliseconds, before executing the task */ public void schedule(AtomicTask t, boolean periodic, long time) { DelayedTask dt = new DelayedTask(t, periodic, time, this); synchronized(this.delayer) { delayedTasks.add(dt); if (timeWait == 0) { timeWait = time; this.delayer.notifyAll(); } else if (((timeWait - timeWaited) > STEP_WHILE) && ((time + timeWaited + System.currentTimeMillis() - lastSleepPeriod) < timeWait)) { timeWait = time; } } } /** * Cancels a previously scheduled {@link seqSamoa.AtomicTask task}. If the {@link seqSamoa.AtomicTask task} * is currently already executed, this method has no effect. * * @param t * The {@link seqSamoa.AtomicTask task} previously scheduled. */ public void cancel(AtomicTask t) throws NotScheduledTaskException { synchronized(this.delayer) { Iterator<DelayedTask> it = delayedTasks.iterator(); while (it.hasNext()){ DelayedTask dt = it.next(); if (dt.task.equals(t)) { it.remove(); return; } } } throw new NotScheduledTaskException(); } /** * Resets the delay of {@link seqSamoa.AtomicTask task} execution to its initial value. * The achieved effect is as though the timer had just been scheduled * for the first time. * * @param t * The {@link seqSamoa.AtomicTask task} previously scheduled. */ public void reset(AtomicTask t) throws NotScheduledTaskException { synchronized(delayer) { Iterator<DelayedTask> it = delayedTasks.iterator(); while (it.hasNext()){ DelayedTask dt = it.next(); if (dt.task.equals(t)) { dt.start = System.currentTimeMillis(); return; } } } throw new NotScheduledTaskException(); } }