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