aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/onos/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java')
-rw-r--r--framework/src/onos/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java504
1 files changed, 504 insertions, 0 deletions
diff --git a/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java
new file mode 100644
index 00000000..4116cbef
--- /dev/null
+++ b/framework/src/onos/utils/misc/src/test/java/org/onlab/util/ManuallyAdvancingTimer.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onlab.util;
+
+import com.google.common.collect.Lists;
+import org.onlab.junit.TestUtils;
+import org.slf4j.Logger;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.junit.TestTools.delay;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Provides manually scheduled timer utility. All schedulable methods are subject to overflow (you can set a period of
+ * max long). Additionally if a skip skips a period of time greater than one period for a periodic task that task will
+ * only be executed once for that skip and scheduled it's period after the last execution.
+ */
+public class ManuallyAdvancingTimer extends java.util.Timer {
+
+ /* States whether or not the static values from timer task have been set ensures population will only occur once.*/
+ private boolean staticsPopulated = false;
+
+ /* Virgin value from timer task */
+ private int virginState;
+
+ /* Scheduled value from timer task */
+ private int scheduledState;
+
+ /* Executed value from timer task */
+ private int executedState;
+
+ /* Cancelled value from timer task */
+ private int cancelledState;
+
+ private final Logger logger = getLogger(getClass());
+
+ /* Service for executing timer tasks */
+ private final ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+ /* Internal time representation independent of system time, manually advanced */
+ private final TimerKeeper timerKeeper = new TimerKeeper();
+
+ /* Data structure for tracking tasks */
+ private final TaskQueue queue = new TaskQueue();
+
+ @Override
+ public void schedule(TimerTask task, long delay) {
+ if (!staticsPopulated) {
+ populateStatics(task);
+ }
+ if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
+ timerKeeper.currentTimeInMillis() - delay, 0)) {
+ logger.error("Failed to submit task");
+ }
+ }
+
+ @Override
+ public void schedule(TimerTask task, Date time) {
+ if (!staticsPopulated) {
+ populateStatics(task);
+ }
+ if (!submitTask(task, time.getTime(), 0)) {
+ logger.error("Failed to submit task");
+ }
+ }
+
+ @Override
+ public void schedule(TimerTask task, long delay, long period) {
+ if (!staticsPopulated) {
+ populateStatics(task);
+ }
+ if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
+ timerKeeper.currentTimeInMillis() - delay, period)) {
+ logger.error("Failed to submit task");
+ }
+ }
+
+ @Override
+ public void schedule(TimerTask task, Date firstTime, long period) {
+ if (!staticsPopulated) {
+ populateStatics(task);
+ }
+ if (!submitTask(task, firstTime.getTime(), period)) {
+ logger.error("Failed to submit task");
+ }
+ }
+
+ /*################################################WARNING################################################*/
+ /* Schedule at fixed rate methods do not work exactly as in the java timer. They are clones of the periodic
+ *scheduling methods. */
+ @Override
+ public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
+ if (!staticsPopulated) {
+ populateStatics(task);
+ }
+ if (!submitTask(task, delay > 0 ? timerKeeper.currentTimeInMillis() + delay :
+ timerKeeper.currentTimeInMillis() - delay, period)) {
+ logger.error("Failed to submit task");
+ }
+ }
+
+ @Override
+ public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
+ if (!staticsPopulated) {
+ populateStatics(task);
+ }
+ if (!submitTask(task, firstTime.getTime(), period)) {
+ logger.error("Failed to submit task");
+ }
+ }
+
+ @Override
+ public void cancel() {
+ executorService.shutdown();
+ queue.clear();
+ }
+
+ @Override
+ public int purge() {
+ return queue.removeCancelled();
+ }
+
+ /**
+ * Returns the virtual current time in millis.
+ *
+ * @return long representing simulated current time.
+ */
+ public long currentTimeInMillis() {
+ return timerKeeper.currentTimeInMillis();
+ }
+
+ /**
+ * Returns the new simulated current time in millis after advancing the absolute value of millis to advance.
+ * Triggers event execution of all events scheduled for execution at times up to and including the returned time.
+ * Passing in the number zero has no effect.
+ *
+ * @param millisToAdvance the number of millis to advance.
+ * @return a long representing the current simulated time in millis
+ */
+ public long advanceTimeMillis(long millisToAdvance) {
+ return timerKeeper.advanceTimeMillis(millisToAdvance);
+ }
+
+ /**
+ * Advances the virtual time a certain number of millis triggers execution delays a certain amount to
+ * allow time for execution.
+ *
+ * @param virtualTimeAdvance the time to be advances in millis of simulated time.
+ * @param realTimeDelay the time to delay in real time to allow for processing.
+ */
+ public void advanceTimeMillis(long virtualTimeAdvance, int realTimeDelay) {
+ timerKeeper.advanceTimeMillis(virtualTimeAdvance);
+ delay(realTimeDelay);
+ }
+
+ /**
+ * Sets up the task and submits it to the queue.
+ *
+ * @param task the task to be added to the queue
+ * @param runtime the first runtime of the task
+ * @param period the period between runs thereafter
+ * @return returns true if the task was successfully submitted, false otherwise
+ */
+ private boolean submitTask(TimerTask task, long runtime, long period) {
+ checkNotNull(task);
+ try {
+ TestUtils.setField(task, "state", scheduledState);
+ TestUtils.setField(task, "nextExecutionTime", runtime);
+ TestUtils.setField(task, "period", period);
+ } catch (TestUtils.TestUtilsException e) {
+ e.printStackTrace();
+ return false;
+ }
+ queue.insertOrdered(task);
+ return true;
+ }
+
+ /**
+ * Executes the given task (only if it is in the scheduled state) and proceeds to reschedule it or mark it as
+ * executed. Does not remove from the queue (this must be done outside).
+ *
+ * @param task the timer task to be executed
+ */
+ private boolean executeTask(TimerTask task) {
+ checkNotNull(task);
+ int currentState;
+ try {
+ currentState = TestUtils.getField(task, "state");
+ } catch (TestUtils.TestUtilsException e) {
+ logger.error("Could not get state of task.");
+ e.printStackTrace();
+ return false;
+ }
+ //If cancelled or already executed stop here.
+ if (currentState == executedState || currentState == cancelledState) {
+ return false;
+ } else if (currentState == virginState) {
+ logger.error("Task was set for execution without being scheduled.");
+ return false;
+ } else if (currentState == scheduledState) {
+ long period;
+
+ try {
+ period = TestUtils.getField(task, "period");
+ } catch (TestUtils.TestUtilsException e) {
+ logger.error("Could not read period of task.");
+ e.printStackTrace();
+ return false;
+ }
+ //Period of zero means one time execution.
+ if (period == 0) {
+ try {
+ TestUtils.setField(task, "state", executedState);
+ } catch (TestUtils.TestUtilsException e) {
+ logger.error("Could not set executed state.");
+ e.printStackTrace();
+ return false;
+ }
+ executorService.execute(task);
+ return true;
+ } else {
+ //Calculate next execution time, using absolute value of period
+ long nextTime = (period > 0) ? (timerKeeper.currentTimeInMillis() + period) :
+ (timerKeeper.currentTimeInMillis() - period);
+ try {
+ TestUtils.setField(task, "nextExecutionTime", nextTime);
+ } catch (TestUtils.TestUtilsException e) {
+ logger.error("Could not set next execution time.");
+ e.printStackTrace();
+ return false;
+ }
+ //Schedule next execution
+ queue.insertOrdered(task);
+ executorService.execute(task);
+ return true;
+ }
+ }
+ logger.error("State property of {} is in an illegal state and did not execute.", task);
+ return false;
+ }
+
+ /**
+ * Executes all tasks in the queue scheduled for execution up to and including the current time.
+ *
+ * @return the total number of tasks run, -1 if failure
+ */
+ private int executeEventsUpToPresent() {
+ int totalRun = 0;
+ if (queue.isEmpty()) {
+ return -1;
+ }
+ TimerTask currTask = queue.peek();
+ long currExecTime;
+ try {
+ currExecTime = TestUtils.getField(currTask, "nextExecutionTime");
+ } catch (TestUtils.TestUtilsException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Could not get nextExecutionTime");
+ }
+ while (currExecTime <= timerKeeper.currentTimeInMillis()) {
+ if (executeTask(queue.pop())) {
+ totalRun++;
+ }
+ if (queue.isEmpty()) {
+ break;
+ }
+ currTask = queue.peek();
+ try {
+ currExecTime = TestUtils.getField(currTask, "nextExecutionTime");
+ } catch (TestUtils.TestUtilsException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Could not get nextExecutionTime");
+ }
+ }
+ return totalRun;
+ }
+
+ /**
+ * Populates the static fields from timer task. Should only be called once.
+ */
+ private void populateStatics(TimerTask task) {
+ try {
+ virginState = TestUtils.getField(task, "VIRGIN");
+ scheduledState = TestUtils.getField(task, "SCHEDULED");
+ executedState = TestUtils.getField(task, "EXECUTED");
+ cancelledState = TestUtils.getField(task, "CANCELLED");
+ staticsPopulated = true;
+ } catch (TestUtils.TestUtilsException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * A class used to maintain the virtual time.
+ */
+ private class TimerKeeper {
+
+ private long currentTime = 0;
+
+ /**
+ * Returns the virtual current time in millis.
+ *
+ * @return long representing simulated current time.
+ */
+ long currentTimeInMillis() {
+ return currentTime;
+ }
+
+ /**
+ * Returns the new simulated current time in millis after advancing the absolute value of millis to advance.
+ * Triggers event execution of all events scheduled for execution at times up to and including the returned
+ * time. Passing in the number zero has no effect.
+ *
+ * @param millisToAdvance the number of millis to advance.
+ * @return a long representing the current simulated time in millis
+ */
+ long advanceTimeMillis(long millisToAdvance) {
+ currentTime = (millisToAdvance >= 0) ? (currentTime + millisToAdvance) : (currentTime - millisToAdvance);
+ if (millisToAdvance != 0) {
+ executeEventsUpToPresent();
+ }
+ return currentTime;
+ }
+ }
+
+ /**
+ * A queue backed by a linked list. Keeps elements sorted in ascending order of execution time. All calls are safe
+ * even on empty queue's.
+ */
+ private class TaskQueue {
+ private final LinkedList<TimerTask> taskList = Lists.newLinkedList();
+
+ /**
+ * Adds the task to the queue in ascending order of scheduled execution. If execution time has already passed
+ * execute immediately.
+ *
+ * @param task the task to be added to the queue
+ */
+ void insertOrdered(TimerTask task) {
+ //Using O(N) insertion because random access is expensive in linked lists worst case is 2N links followed
+ // for binary insertion vs N for simple insertion.
+ checkNotNull(task);
+ if (!staticsPopulated) {
+ populateStatics(task);
+ }
+ long insertTime;
+ try {
+ insertTime = TestUtils.getField(task, "nextExecutionTime");
+ TestUtils.setField(task, "state", scheduledState);
+ } catch (TestUtils.TestUtilsException e) {
+ e.printStackTrace();
+ return;
+ }
+ //If the task was scheduled in the past or for the current time run it immediately and do not add to the
+ // queue, subsequent executions will be scheduled as normal
+ if (insertTime <= timerKeeper.currentTimeInMillis()) {
+ executeTask(task);
+ return;
+ }
+
+ Iterator<TimerTask> iter = taskList.iterator();
+ int positionCounter = 0;
+ long nextTaskTime;
+ TimerTask currentTask;
+ while (iter.hasNext()) {
+ currentTask = iter.next();
+ try {
+ nextTaskTime = TestUtils.getField(currentTask, "nextExecutionTime");
+ } catch (TestUtils.TestUtilsException e) {
+ e.printStackTrace();
+ return;
+ }
+ if (insertTime < nextTaskTime) {
+ taskList.add(positionCounter, task);
+ return;
+ }
+ positionCounter++;
+ }
+ taskList.addLast(task);
+ }
+
+ /**
+ * Returns the first item in the queue (next scheduled for execution) without removing it, returns null if the
+ * queue is empty.
+ *
+ * @return the next TimerTask to run or null if the queue is empty
+ */
+ TimerTask peek() {
+ if (taskList.isEmpty()) {
+ return null;
+ }
+ return taskList.getFirst();
+ }
+
+ /**
+ * Returns and removes the first item in the queue or null if it is empty.
+ *
+ * @return the first element of the queue or null if the queue is empty
+ */
+ TimerTask pop() {
+ if (taskList.isEmpty()) {
+ return null;
+ }
+ return taskList.pop();
+ }
+
+ /**
+ * Performs a sort on the set of timer tasks, earliest task is first. Does nothing if queue is empty.
+ */
+ void sort() {
+ if (taskList.isEmpty()) {
+ return;
+ }
+ taskList.sort((o1, o2) -> {
+ checkNotNull(o1);
+ checkNotNull(o2);
+ long executionTimeOne;
+ long executionTimeTwo;
+ try {
+ executionTimeOne = TestUtils.getField(o1, "nextExecutionTime");
+ executionTimeTwo = TestUtils.getField(o2, "nextExecutionTime");
+ } catch (TestUtils.TestUtilsException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Could not get next execution time.");
+ }
+ if (executionTimeOne == executionTimeTwo) {
+ return 0;
+ } else if (executionTimeOne < executionTimeTwo) {
+ return -1;
+ } else {
+ return 1;
+ }
+ });
+ }
+
+ /**
+ * Returns whether the queue is currently empty.
+ *
+ * @return true if the queue is empty, false otherwise
+ */
+ boolean isEmpty() {
+ return taskList.isEmpty();
+ }
+
+ /**
+ * Clears the underlying list of the queue.
+ */
+ void clear() {
+ taskList.clear();
+ }
+
+ /**
+ * Removes all cancelled tasks from the queue. Has no effect on behavior.
+ *
+ * @return returns the total number of items removed, -1 if list is empty or failure occurs.
+ */
+ int removeCancelled() {
+ if (taskList.isEmpty()) {
+ return -1;
+ }
+ int removedCount = 0;
+ Iterator<TimerTask> taskIterator = taskList.iterator();
+ TimerTask currTask;
+ int currState;
+ while (taskIterator.hasNext()) {
+ currTask = taskIterator.next();
+ try {
+ currState = TestUtils.getField(currTask, "state");
+ } catch (TestUtils.TestUtilsException e) {
+ logger.error("Could not get task state.");
+ e.printStackTrace();
+ return -1;
+ }
+ if (currState == cancelledState) {
+ removedCount++;
+ taskIterator.remove();
+ }
+ }
+ return removedCount;
+ }
+ }
+}