// // Copyright (c) ZeroC, Inc. All rights reserved. // // // NOTE: We don't use C# timers, the API is quite a bit different from // the C++ & Java timers and it's not clear what is the cost of // scheduling and cancelling timers. // namespace IceInternal { using System.Diagnostics; using System.Threading; using System.Collections.Generic; public interface TimerTask { void runTimerTask(); } public sealed class Timer { public void destroy() { lock(this) { if(_instance == null) { return; } _instance = null; Monitor.Pulse(this); _tokens.Clear(); _tasks.Clear(); } _thread.Join(); } public void schedule(TimerTask task, long delay) { lock(this) { if(_instance == null) { throw new Ice.CommunicatorDestroyedException(); } Token token = new Token(Time.currentMonotonicTimeMillis() + delay, ++_tokenId, 0, task); try { _tasks.Add(task, token); _tokens.Add(token, null); } catch(System.ArgumentException) { Debug.Assert(false); } if(token.scheduledTime < _wakeUpTime) { Monitor.Pulse(this); } } } public void scheduleRepeated(TimerTask task, long period) { lock(this) { if(_instance == null) { throw new Ice.CommunicatorDestroyedException(); } Token token = new Token(Time.currentMonotonicTimeMillis() + period, ++_tokenId, period, task); try { _tasks.Add(task, token); _tokens.Add(token, null); } catch(System.ArgumentException) { Debug.Assert(false); } if(token.scheduledTime < _wakeUpTime) { Monitor.Pulse(this); } } } public bool cancel(TimerTask task) { lock(this) { if(_instance == null) { return false; } Token token; if(!_tasks.TryGetValue(task, out token)) { return false; } _tasks.Remove(task); _tokens.Remove(token); return true; } } // // Only for use by Instance. // internal Timer(Instance instance, ThreadPriority priority = ThreadPriority.Normal) { init(instance, priority, true); } internal void init(Instance instance, ThreadPriority priority, bool hasPriority) { _instance = instance; string threadName = _instance.initializationData().properties.getProperty("Ice.ProgramName"); if(threadName.Length > 0) { threadName += "-"; } _thread = new Thread(new ThreadStart(Run)); _thread.IsBackground = true; _thread.Name = threadName + "Ice.Timer"; if(hasPriority) { _thread.Priority = priority; } _thread.Start(); } internal void updateObserver(Ice.Instrumentation.CommunicatorObserver obsv) { lock(this) { Debug.Assert(obsv != null); _observer = obsv.getThreadObserver("Communicator", _thread.Name, Ice.Instrumentation.ThreadState.ThreadStateIdle, _observer); if(_observer != null) { _observer.attach(); } } } public void Run() { Token token = null; while(true) { lock(this) { if(_instance != null) { // // If the task we just ran is a repeated task, schedule it // again for execution if it wasn't canceled. // if(token != null && token.delay > 0) { if(_tasks.ContainsKey(token.task)) { token.scheduledTime = Time.currentMonotonicTimeMillis() + token.delay; _tokens.Add(token, null); } } } token = null; if(_instance == null) { break; } if(_tokens.Count == 0) { _wakeUpTime = long.MaxValue; Monitor.Wait(this); } if(_instance == null) { break; } while(_tokens.Count > 0 && _instance != null) { long now = Time.currentMonotonicTimeMillis(); Token first = null; foreach(Token t in _tokens.Keys) { first = t; break; } Debug.Assert(first != null); if(first.scheduledTime <= now) { _tokens.Remove(first); token = first; if(token.delay == 0) { _tasks.Remove(token.task); } break; } _wakeUpTime = first.scheduledTime; Monitor.Wait(this, (int)(first.scheduledTime - now)); } if(_instance == null) { break; } } if(token != null) { try { Ice.Instrumentation.ThreadObserver threadObserver = _observer; if(threadObserver != null) { threadObserver.stateChanged(Ice.Instrumentation.ThreadState.ThreadStateIdle, Ice.Instrumentation.ThreadState.ThreadStateInUseForOther); try { token.task.runTimerTask(); } finally { threadObserver.stateChanged(Ice.Instrumentation.ThreadState.ThreadStateInUseForOther, Ice.Instrumentation.ThreadState.ThreadStateIdle); } } else { token.task.runTimerTask(); } } catch(System.Exception ex) { lock(this) { if(_instance != null) { string s = "unexpected exception from task run method in timer thread:\n" + ex; _instance.initializationData().logger.error(s); } } } } } } private class Token : System.IComparable { public Token(long scheduledTime, int id, long delay, TimerTask task) { this.scheduledTime = scheduledTime; this.id = id; this.delay = delay; this.task = task; } public int CompareTo(object o) { // // Token are sorted by scheduled time and token id. // Token r = (Token)o; if(scheduledTime < r.scheduledTime) { return -1; } else if(scheduledTime > r.scheduledTime) { return 1; } if(id < r.id) { return -1; } else if(id > r.id) { return 1; } return 0; } public override bool Equals(object o) { if(ReferenceEquals(this, o)) { return true; } Token t = o as Token; return t == null ? false : CompareTo(t) == 0; } public override int GetHashCode() { int h = 5381; HashUtil.hashAdd(ref h, id); HashUtil.hashAdd(ref h, scheduledTime); return h; } public long scheduledTime; public int id; // Since we can't compare references, we need to use another id. public long delay; public TimerTask task; } private IDictionary _tokens = new SortedDictionary(); private IDictionary _tasks = new Dictionary(); private Instance _instance; private long _wakeUpTime = long.MaxValue; private int _tokenId = 0; private Thread _thread; // // We use a volatile to avoid synchronization when reading // _observer. Reference assignement is atomic in Java so it // also doesn't need to be synchronized. // private volatile Ice.Instrumentation.ThreadObserver _observer; } }