// $Id$ // // Copyright 2007-2008 Cisco Systems Inc. // // 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. using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; namespace Etch.Util { /// /// The alarm manager is used to implement alarms. /// A listener desiring a wakeup call should add itself to the manager /// with the specified delay in milliseconds. The listener may be removed /// at any time. When the alarm goes off, the listener may reset or remove /// the alarm. /// public class AlarmManager : AbstractStartable { /// /// Adds the listener to the set of those getting wakeup calls. /// /// the target of the wakeup call. /// a bit of state for the listener. /// the positive delay in milliseconds before the wakeup call. public static void staticAdd(AlarmListener listener, Object state, int delay) { GetAlarmManager(true).Add(listener, state, delay); } /// /// Removes the listener from the set of those getting wakeup calls. /// /// the target of the wakeup call. public static void staticRemove(AlarmListener listener) { AlarmManager am = GetAlarmManager(false); if (am != null) am.Remove(listener); } /// /// Gets the statically configured alarm manager, creating one if /// specified and none exists. /// /// if true, we really need one and if there isn't /// one then one should be started, otherwise one should not be started /// he statically configured alarm manager, or a newly /// created one if there is none. public static AlarmManager GetAlarmManager(bool startIt) { if (alarmManager == null) { lock (alarmManagerSync) { if (alarmManager == null) { if (!startIt) return null; AlarmManager am = new AlarmManager(); am.Start(); alarmManager = am; } } } return alarmManager; } /// /// Sets the statically configured alarm manager, returning the old one /// if any. Don't forget to stop the old one. /// /// a new AlarmManager to be statically available /// the old statically available AlarmManager. public static AlarmManager SetAlarmManager(AlarmManager newAlarmManager) { lock (alarmManagerSync) { AlarmManager oldAlarmManager = alarmManager; alarmManager = newAlarmManager; return oldAlarmManager; } } private static AlarmManager alarmManager; private readonly static Object alarmManagerSync = new Object(); /// /// Constructs the AlarmManager. /// /// the number of worker threads to create to process /// wakeups. Must be > 0 and < 100. public AlarmManager(int nWorkers) { if (nWorkers <= 0 || nWorkers >= 100) throw new ArgumentException("nWorkers <= 0 || nWorkers >= 100"); this.nWorkers = nWorkers; } /// /// onstructs the AlarmManager with the default number of workers. /// public AlarmManager() : this(DEFAULT_NWORKERS) { } private readonly int nWorkers; /// /// The default number of worker threads (1). /// public const int DEFAULT_NWORKERS = 1; protected override void Start0() { ClearAlarms(); ClearQueue(); worker = new Thread[nWorkers]; for (int i = 0; i < nWorkers; i++) { worker[i] = new Thread(new ThreadStart(run)); worker[i].IsBackground = true; worker[i].Start(); } } private Thread[] worker; protected override void Stop0() { ClearAlarms(); ClearQueue(); System.Threading.Monitor.PulseAll(this); for (int i = 0; i < nWorkers; i++) { if (worker[i] != null) { worker[i].Join(); worker[i] = null; } } } /// /// Adds the listener to the set of those getting wakeup calls. If the /// listener is already scheduled for a wakeup call, the schedule is /// adjusted. There can only be one outstanding wakeup call per listener. /// This method is thread safe. /// /// the target of the wakeup call. /// a bit of state for the listener. /// the positive delay in milliseconds before the wakeup call. /// throws ArgumentException if the delay is less /// than or equal to 0 [MethodImpl(MethodImplOptions.Synchronized)] public void Add(AlarmListener listener, Object state, int delay) { if (listener == null) throw new NullReferenceException("listener == null"); if (delay <= 0) throw new ArgumentException("delay <= 0"); CheckIsStarted(); long due = HPTimer.Now() + delay * HPTimer.NS_PER_MILLISECOND; Alarm alarm = GetAlarm(listener); if (alarm != null) { // schedule is being adjusted Dequeue(alarm); alarm.setDue(due); alarm.setState(state); Enqueue(alarm); } else { alarm = new Alarm(listener, state, due); AddAlarm(listener, alarm); Enqueue(alarm); } NotifyWorker("add"); } [MethodImpl(MethodImplOptions.Synchronized)] private void Update(Alarm alarm, int delay) { long due = delay > 0 ? alarm.getDue() + delay * HPTimer.NS_PER_MILLISECOND : HPTimer.Now() - delay * HPTimer.NS_PER_MILLISECOND; alarm.setDue(due); Enqueue(alarm); NotifyWorker("update"); } /// /// Removes any scheduled wakeup call for this listener. /// This method is thread safe. /// /// the target of the wakeup call. [MethodImpl(MethodImplOptions.Synchronized)] public void Remove(AlarmListener listener) { CheckIsStarted(); Alarm alarm = RemoveAlarm(listener); if (alarm != null) Dequeue(alarm); NotifyWorker("remove"); } [MethodImpl(MethodImplOptions.Synchronized)] private void Remove(Alarm alarm) { RemoveAlarm(alarm.listener); } private void Wakeup(Alarm alarm) { try { int delay = alarm.listener.Wakeup(this, alarm.getState(), alarm.getDue()); if (delay != 0) Update(alarm, delay); else Remove(alarm); } catch (Exception) { Remove(alarm); // Log.report( "wakeup", "who", alarm.listener, Log.EXCP, e ); } } private Alarm getNextDueAlarm() { // ok, the worker needs to get the next due alarm and // then wait until its due time, then return it. if alerted // by notifyWorker, it should refresh the next due alarm. // one trick will be in excluding multiple workers from // coming in here at the same time. lock (getNextDueAlarmSync) { lock (this) { while (true) { // if (!IsStarted()) return null; Alarm alarm = getFirst(); if (alarm == null) { try { //Console.WriteLine(" Waiting in getNextDueAlarm "); System.Threading.Monitor.Wait(this, Int32.MaxValue); //Console.WriteLine(" Done Waiting in getNextDueAlarm "); continue; } catch (Exception e) { Console.WriteLine(e); return null; } } long delayNs = alarm.getDue() - HPTimer.Now(); if (delayNs > 0) { try { long delay = delayNs / HPTimer.NS_PER_MILLISECOND; if (delay > 0) { int d = (int)delay; System.Threading.Monitor.Wait(this, d); } continue; } catch (Exception e) { Console.WriteLine(e); return null; } } // the alarm being returned has not been removed // from alarmsByListener. it is presumed that the // alarm will be set again. if not, it should be // removed. Dequeue(alarm); return alarm; } } } } private readonly Object getNextDueAlarmSync = new Object(); private void NotifyWorker(String reason) { // Log.report( "notify", "who", this, "reason", reason, "where", new Throwable() ); // the set of alarms has changed. System.Threading.Monitor.Pulse(this); } public void run() { try { Alarm alarm; while ((alarm = getNextDueAlarm()) != null) { Wakeup(alarm); } } catch (Exception e) { Console.WriteLine(e); } } private Alarm GetAlarm(AlarmListener listener) { Alarm alarm; return alarmsByListener.TryGetValue(listener, out alarm) ? alarm : null; } private void AddAlarm(AlarmListener listener, Alarm alarm) { alarmsByListener.Add(listener, alarm); } private Alarm RemoveAlarm(AlarmListener listener) { Alarm alarm = GetAlarm(listener); if (alarm != null) alarmsByListener.Remove(listener); return alarm; } private void ClearAlarms() { alarmsByListener.Clear(); } private readonly Dictionary alarmsByListener = new Dictionary(); //////////////////////// // ALARMS BY DUE TIME // //////////////////////// private Alarm getFirst() { if (alarms.Count == 0) return null; return alarms[0]; } private void Enqueue(Alarm alarm) { alarms.Add(alarm); alarms.Sort(); } private void Dequeue(Alarm alarm) { alarms.Remove(alarm); } private void ClearQueue() { alarms.Clear(); } private readonly List alarms = new List(); private class Alarm : IComparable { /// /// /// /// the target of the wakeup call. /// a bit of state for the listener. /// he absolute due time for the alarm. public Alarm(AlarmListener listener, Object state, long due) { this.listener = listener; this.state = state; this.due = due; } /// /// /// /// the state for the listener. public Object getState() { return state; } /// /// /// /// a new bit of state for the listener./param> public void setState(Object state) { this.state = state; } /// /// /// /// the time the alarm is due. public long getDue() { return due; } /// /// /// /// public void setDue(long due) { this.due = due; } public override int GetHashCode() { return (int)((due ^ (due >> 32)) ^ (seq ^ (seq >> 32))); } public override bool Equals(Object obj) { if (obj == this) return true; if (obj == null) return false; if (obj.GetType() != typeof(Alarm)) return false; Alarm other = (Alarm)obj; return due == other.due && seq == other.seq; } public int CompareTo(Alarm other) { if (due < other.due) return -1; if (due > other.due) return 1; // due time is the same for both, now we need to // compare the seq. if (seq < other.seq) return -1; if (seq > other.seq) return 1; return 0; } /// /// The listener for wakeup events. /// public readonly AlarmListener listener; /// /// Just a bit of state for the listener. /// private Object state; /// /// The time the alarm is due. /// private long due; /// /// A unique for all reasonable time sequence number. /// private readonly long seq = idGen.Next(); private readonly static IdGenerator idGen = new IdGenerator(); } /// /// Shuts down the default alarm manager if there is one. /// public static void shutdown() { AlarmManager am = SetAlarmManager(null); if (am != null) am.Stop(); } } }