org.powertac.server.SimulationClockControl.java Source code

Java tutorial

Introduction

Here is the source code for org.powertac.server.SimulationClockControl.java

Source

/*
 * Copyright (c) 2011 by the original author or authors.
 *
 * 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.powertac.server;

import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;

import org.powertac.common.TimeService;

/**
 * Timer-based clock management for the Power TAC simulator. This is a
 * singleton class, but it must be initialized with a reference to the 
 * timeService in order to do its job. Therefore, it is an error to 
 * attempt to retrieve the instance before initialization.
 * <p>
 * The basic design for this scheme is given at 
 * https://github.com/powertac/powertac-server/wiki/Time-management.
 * The goal is to produce a tick by calling timeService.updateTime() 
 * every n seconds, where n is 3600/timeService.rate. So if 
 * timeService.rate is 360, this would be a tick every 10 seconds.
 * Current simulation time is given by the formula
 *   time = rate * (systemTime - start) + base
 * where start, base, and rate are simulation parameters.</p>
 * <p>
 * We assume that the simulator is a single thread, although it may
 * control other threads. That thread waits for the next tick, then
 * does some work. Ideally, that work will be completed in fewer than
 * n seconds, but occasionally it may take longer. Since the broker 
 * needs to keep track of time on its own, it needs to know when this
 * happens, and must be informed of the updated start time so that it
 * can compute a simulation time that agrees with the server. In
 * addition, a broker might want to pause the clock in order to allow
 * a user to fill out a dialog or otherwise interact with a user. Given
 * an appropriate set of messages, this event could be handled in much
 * the same way as a server timeslot overrun.</p>
 * 
 * @author John Collins
 */
public class SimulationClockControl {
    public enum Status {
        CLEAR, COMPLETE, DELAYED, PAUSED, STOPPED
    }

    private static final long postPauseDelay = 500l; // 500 msec
    private static final long watchdogSlack = 200l; // 200 msec

    @Autowired
    private TimeService timeService;

    @Autowired
    private CompetitionControlService competitionControl;

    private long base;
    private long start;
    private long rate;
    private long modulo;

    private Status state = Status.CLEAR; // package visibility for testing
    private int nextTick = -1;
    private boolean pauseRequested = false;

    private Timer theTimer;
    private WatchdogAction currentWatchdog;

    // ------------- Singleton methods -------------
    private static SimulationClockControl instance;

    /**
     * Creates the instance and sets the reference to the timeService.
     */
    public static void initialize(CompetitionControlService competitionControl, TimeService timeService) {
        instance = new SimulationClockControl(competitionControl, timeService);
    }

    /**
     * Returns the instance, which of course will be null if the singleton
     * is not yet initialized.
     * @return
     */
    public static SimulationClockControl getInstance() {
        return instance;
    }

    private SimulationClockControl(CompetitionControlService competitionControl, TimeService timeService) {
        super();
        this.competitionControl = competitionControl;
        this.timeService = timeService;
        this.base = timeService.getBase();
        this.rate = timeService.getRate();
        this.modulo = timeService.getModulo();
        theTimer = new Timer();
    }

    // --------------- external api ----------------
    /**
     * Sets the sim clock start time, which in turn gets propagated to the
     * timeService.
     */
    public void setStart(long start) {
        this.start = start;
        timeService.setStart(start);
    }

    /**
     * Schedules the next tick. The interval between now and the next tick is
     * determined by comparing the current system time with what the time should
     * be on the next tick.
     */
    public void scheduleTick() {
        //System.out.println("scheduleTick() " + new Date().getTime());
        long nextTick = computeNextTickTime();
        boolean success = false;
        while (!success) {
            try {
                theTimer.schedule(new TickAction(this), new Date(nextTick));
                success = true;
            } catch (IllegalStateException ise) {
                //System.out.println("Timer failure: " + ise.toString());
                // at this point there should be no outstanding timer tasks
                theTimer = new Timer();
            }
        }
    }

    /**
     * Indicates that the simulator has completed its work on the current
     * timeslot. If the sim was delayed, then resume it. On the last tick,
     * client must call stop() rather than complete().
     */
    public synchronized void complete() {
        //System.out.println("complete() " + new Date().getTime());
        if (state == Status.DELAYED) {
            if (pauseRequested) {
                // already paused, just change the state
                state = Status.PAUSED;
                pauseRequested = false;
                return;
            } else
                resume();
        }
        // let watchdog start the next tick
        state = Status.COMPLETE;
    }

    /**
     * Stops the clock. Call this method when processing on the last tick is
     * finished.
     */
    public synchronized void stop() {
        //System.out.println("Stop at " + new Date().getTime());
        state = Status.STOPPED;
        if (currentWatchdog != null) {
            currentWatchdog.cancel();
            currentWatchdog = null;
        }
    }

    /**
     * Blocks the caller until the next tick.
     */
    public synchronized void waitForTick(int n) {
        // Can we guarantee this is called BEFORE the corresponding notifyTick()?
        //System.out.println("waitForTick() nextTick=" + nextTick + ", n=" + n);
        while (nextTick < n) {
            try {
                wait();
            } catch (InterruptedException ie) {
            }
        }
    }

    /**
     * Serves an external pause request. 
     */
    public synchronized void requestPause() {
        // just set the flag. We'll pay attention to it the next
        // time complete() is called.
        pauseRequested = true;
    }

    /**
     * Releases an externally-requested pause.
     */
    public synchronized void releasePause() {
        if (state != Status.PAUSED) {
            // not paused yet, just clear the request
            pauseRequested = false;
        } else {
            // already paused, just proceed
            state = Status.COMPLETE;
            resume();
        }
    }

    // ------------------------- internal methods ------------------
    /**
     * notifies the waiting thread (if any).
     */
    private synchronized void notifyTick() {
        nextTick += 1;
        notifyAll();
    }

    /**
     * Pauses the clock and notifies brokers just in case the current state
     * is CLEAR, otherwise if the state is COMPLETE, schedules the next tick. 
     * No action is taken if the current state is PAUSED or STOPPED, thereby
     * effectively stopping the clock. 
     * This method and the complete() method are synchronized to protect 
     * against complete() being called before state is set to paused.
     */
    private synchronized void delayMaybe() {
        //System.out.println("delayMaybe() " + new Date().getTime());
        if (state == Status.CLEAR) {
            // sim thread is not finished
            //System.out.println("delaying");
            state = Status.DELAYED; // clock resumed by calling complete()
            competitionControl.pause();
        } else if (pauseRequested) {
            // don't schedule the next tick here
            state = Status.PAUSED;
            competitionControl.pause();
            pauseRequested = false;
        } else if (state == Status.COMPLETE) {
            // sim finished - schedule the next tick
            scheduleTick();
        }
    }

    // compute new start time, communicate it to brokers, and re-start
    // the clock.
    private void resume() {
        //System.out.println("resume()");
        long originalNextTick = computeNextTickTime();
        long actualNextTick = new Date().getTime() + postPauseDelay;
        start += actualNextTick - originalNextTick;
        timeService.setStart(start);
        competitionControl.resume(start);
        scheduleTick();
    }

    synchronized Status getState() // package visibility for test support
    {
        return state;
    }

    /**
     * Sets state in synchronized block. Needed for cases (such as
     * TickAction.run()) where state needs to be set from outside a
     * synchronized block.
     */
    synchronized void setState(Status newState) {
        state = newState;
    }

    private long computeNextTickTime() {
        long current = new Date().getTime();
        // not a valid test?
        if (current < start) {
            // first tick is special
            //System.out.println("first tick at " + start + "; current is " + current);
            return start;
        } else {
            // second and subsequent ticks
            long simTime = timeService.getCurrentTime().getMillis();
            long nextSimTime = simTime + modulo;
            long nextTick = start + (nextSimTime - base) / rate;
            //System.out.println("next tick: current " + current + "; next tick at " + nextTick);
            return nextTick;
        }
    }

    private class TickAction extends TimerTask {
        SimulationClockControl scc;

        TickAction(SimulationClockControl scc) {
            super();
            this.scc = scc;
        }

        /**
         * Runs a tick - updates the timeService, clears the state,
         * schedules the watchdog, and notifies the simulator. The
         * monitor object is the singleton.
         */
        @Override
        public void run() {
            //System.out.println("TickAction.run() " + new Date().getTime());
            timeService.updateTime();
            scc.setState(Status.CLEAR);
            long wdTime = computeNextTickTime() - watchdogSlack;
            //System.out.println("watchdog set for " + wdTime);
            currentWatchdog = new WatchdogAction(scc);
            theTimer.schedule(currentWatchdog, new Date(wdTime));
            scc.notifyTick();
        }
    }

    private class WatchdogAction extends TimerTask {
        SimulationClockControl scc;

        WatchdogAction(SimulationClockControl scc) {
            super();
            this.scc = scc;
        }

        /**
         * Checks for sim task completion by calling pauseMaybe on the
         * instance.
         */
        @Override
        public void run() {
            //System.out.println("WatchdogAction.run() " + new Date().getTime());
            scc.delayMaybe();
            currentWatchdog = null;
        }
    }
}