1 // 2 // Copyright (c) ZeroC, Inc. All rights reserved. 3 // 4 5 // 6 // NOTE: We don't use C# timers, the API is quite a bit different from 7 // the C++ & Java timers and it's not clear what is the cost of 8 // scheduling and cancelling timers. 9 // 10 11 namespace IceInternal 12 { 13 using System.Diagnostics; 14 using System.Threading; 15 using System.Collections.Generic; 16 17 public interface TimerTask 18 { runTimerTask()19 void runTimerTask(); 20 } 21 22 public sealed class Timer 23 { destroy()24 public void destroy() 25 { 26 lock(this) 27 { 28 if(_instance == null) 29 { 30 return; 31 } 32 33 _instance = null; 34 Monitor.Pulse(this); 35 36 _tokens.Clear(); 37 _tasks.Clear(); 38 } 39 40 _thread.Join(); 41 } 42 schedule(TimerTask task, long delay)43 public void schedule(TimerTask task, long delay) 44 { 45 lock(this) 46 { 47 if(_instance == null) 48 { 49 throw new Ice.CommunicatorDestroyedException(); 50 } 51 52 Token token = new Token(Time.currentMonotonicTimeMillis() + delay, ++_tokenId, 0, task); 53 54 try 55 { 56 _tasks.Add(task, token); 57 _tokens.Add(token, null); 58 } 59 catch(System.ArgumentException) 60 { 61 Debug.Assert(false); 62 } 63 64 if(token.scheduledTime < _wakeUpTime) 65 { 66 Monitor.Pulse(this); 67 } 68 } 69 } 70 scheduleRepeated(TimerTask task, long period)71 public void scheduleRepeated(TimerTask task, long period) 72 { 73 lock(this) 74 { 75 if(_instance == null) 76 { 77 throw new Ice.CommunicatorDestroyedException(); 78 } 79 80 Token token = new Token(Time.currentMonotonicTimeMillis() + period, ++_tokenId, period, task); 81 82 try 83 { 84 _tasks.Add(task, token); 85 _tokens.Add(token, null); 86 } 87 catch(System.ArgumentException) 88 { 89 Debug.Assert(false); 90 } 91 92 if(token.scheduledTime < _wakeUpTime) 93 { 94 Monitor.Pulse(this); 95 } 96 } 97 } 98 cancel(TimerTask task)99 public bool cancel(TimerTask task) 100 { 101 lock(this) 102 { 103 if(_instance == null) 104 { 105 return false; 106 } 107 108 Token token; 109 if(!_tasks.TryGetValue(task, out token)) 110 { 111 return false; 112 } 113 _tasks.Remove(task); 114 _tokens.Remove(token); 115 return true; 116 } 117 } 118 119 // 120 // Only for use by Instance. 121 // Timer(Instance instance, ThreadPriority priority = ThreadPriority.Normal)122 internal Timer(Instance instance, ThreadPriority priority = ThreadPriority.Normal) 123 { 124 init(instance, priority, true); 125 } 126 init(Instance instance, ThreadPriority priority, bool hasPriority)127 internal void init(Instance instance, ThreadPriority priority, bool hasPriority) 128 { 129 _instance = instance; 130 131 string threadName = _instance.initializationData().properties.getProperty("Ice.ProgramName"); 132 if(threadName.Length > 0) 133 { 134 threadName += "-"; 135 } 136 137 _thread = new Thread(new ThreadStart(Run)); 138 _thread.IsBackground = true; 139 _thread.Name = threadName + "Ice.Timer"; 140 if(hasPriority) 141 { 142 _thread.Priority = priority; 143 } 144 _thread.Start(); 145 } 146 updateObserver(Ice.Instrumentation.CommunicatorObserver obsv)147 internal void updateObserver(Ice.Instrumentation.CommunicatorObserver obsv) 148 { 149 lock(this) 150 { 151 Debug.Assert(obsv != null); 152 _observer = obsv.getThreadObserver("Communicator", 153 _thread.Name, 154 Ice.Instrumentation.ThreadState.ThreadStateIdle, 155 _observer); 156 if(_observer != null) 157 { 158 _observer.attach(); 159 } 160 } 161 } 162 Run()163 public void Run() 164 { 165 Token token = null; 166 while(true) 167 { 168 lock(this) 169 { 170 if(_instance != null) 171 { 172 // 173 // If the task we just ran is a repeated task, schedule it 174 // again for execution if it wasn't canceled. 175 // 176 if(token != null && token.delay > 0) 177 { 178 if(_tasks.ContainsKey(token.task)) 179 { 180 token.scheduledTime = Time.currentMonotonicTimeMillis() + token.delay; 181 _tokens.Add(token, null); 182 } 183 } 184 } 185 token = null; 186 187 if(_instance == null) 188 { 189 break; 190 } 191 192 if(_tokens.Count == 0) 193 { 194 _wakeUpTime = long.MaxValue; 195 Monitor.Wait(this); 196 } 197 198 if(_instance == null) 199 { 200 break; 201 } 202 203 while(_tokens.Count > 0 && _instance != null) 204 { 205 long now = Time.currentMonotonicTimeMillis(); 206 207 Token first = null; 208 foreach(Token t in _tokens.Keys) 209 { 210 first = t; 211 break; 212 } 213 Debug.Assert(first != null); 214 215 if(first.scheduledTime <= now) 216 { 217 _tokens.Remove(first); 218 token = first; 219 if(token.delay == 0) 220 { 221 _tasks.Remove(token.task); 222 } 223 break; 224 } 225 226 _wakeUpTime = first.scheduledTime; 227 Monitor.Wait(this, (int)(first.scheduledTime - now)); 228 } 229 230 if(_instance == null) 231 { 232 break; 233 } 234 } 235 236 if(token != null) 237 { 238 try 239 { 240 Ice.Instrumentation.ThreadObserver threadObserver = _observer; 241 if(threadObserver != null) 242 { 243 threadObserver.stateChanged(Ice.Instrumentation.ThreadState.ThreadStateIdle, 244 Ice.Instrumentation.ThreadState.ThreadStateInUseForOther); 245 try 246 { 247 token.task.runTimerTask(); 248 } 249 finally 250 { 251 threadObserver.stateChanged(Ice.Instrumentation.ThreadState.ThreadStateInUseForOther, 252 Ice.Instrumentation.ThreadState.ThreadStateIdle); 253 } 254 } 255 else 256 { 257 token.task.runTimerTask(); 258 } 259 } 260 catch(System.Exception ex) 261 { 262 lock(this) 263 { 264 if(_instance != null) 265 { 266 string s = "unexpected exception from task run method in timer thread:\n" + ex; 267 _instance.initializationData().logger.error(s); 268 } 269 } 270 } 271 } 272 } 273 } 274 275 private class Token : System.IComparable 276 { 277 public Token(long scheduledTime, int id, long delay, TimerTask task)278 Token(long scheduledTime, int id, long delay, TimerTask task) 279 { 280 this.scheduledTime = scheduledTime; 281 this.id = id; 282 this.delay = delay; 283 this.task = task; 284 } 285 CompareTo(object o)286 public int CompareTo(object o) 287 { 288 // 289 // Token are sorted by scheduled time and token id. 290 // 291 Token r = (Token)o; 292 if(scheduledTime < r.scheduledTime) 293 { 294 return -1; 295 } 296 else if(scheduledTime > r.scheduledTime) 297 { 298 return 1; 299 } 300 301 if(id < r.id) 302 { 303 return -1; 304 } 305 else if(id > r.id) 306 { 307 return 1; 308 } 309 310 return 0; 311 } 312 Equals(object o)313 public override bool Equals(object o) 314 { 315 if(ReferenceEquals(this, o)) 316 { 317 return true; 318 } 319 Token t = o as Token; 320 return t == null ? false : CompareTo(t) == 0; 321 } 322 GetHashCode()323 public override int GetHashCode() 324 { 325 int h = 5381; 326 HashUtil.hashAdd(ref h, id); 327 HashUtil.hashAdd(ref h, scheduledTime); 328 return h; 329 } 330 331 public long scheduledTime; 332 public int id; // Since we can't compare references, we need to use another id. 333 public long delay; 334 public TimerTask task; 335 } 336 337 private IDictionary<Token, object> _tokens = new SortedDictionary<Token, object>(); 338 private IDictionary<TimerTask, Token> _tasks = new Dictionary<TimerTask, Token>(); 339 private Instance _instance; 340 private long _wakeUpTime = long.MaxValue; 341 private int _tokenId = 0; 342 private Thread _thread; 343 344 // 345 // We use a volatile to avoid synchronization when reading 346 // _observer. Reference assignement is atomic in Java so it 347 // also doesn't need to be synchronized. 348 // 349 private volatile Ice.Instrumentation.ThreadObserver _observer; 350 } 351 352 } 353