2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / corlib / System.Threading / Timer.cs
1 //
2 // System.Threading.Timer.cs
3 //
4 // Authors:
5 //      Dick Porter (dick@ximian.com)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (C) 2001, 2002 Ximian, Inc.  http://www.ximian.com
9 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Runtime.InteropServices;
32 using System.Collections;
33
34 namespace System.Threading
35 {
36 #if NET_2_0
37         [ComVisible (true)]
38 #endif
39         public sealed class Timer : MarshalByRefObject, IDisposable
40         {
41 #region Timer instance fields
42                 TimerCallback callback;
43                 object state;
44                 long due_time_ms;
45                 long period_ms;
46                 long next_run; // in ticks
47                 bool disposed;
48 #endregion
49
50                 // timers that expire after FutureTime will be put in future_jobs
51                 // 5 seconds seems reasonable, this must be at least 1 second
52                 const long FutureTime = 5 * 1000;
53                 const long FutureTimeTicks = FutureTime * TimeSpan.TicksPerMillisecond;
54
55 #region Timer static fields
56                 static Thread scheduler;
57                 static Hashtable jobs;
58                 static Hashtable future_jobs;
59                 static Timer future_checker;
60                 static AutoResetEvent change_event;
61                 static object locker;
62 #endregion
63
64                 /* we use a static initializer to avoid race issues with the thread creation */
65                 static Timer ()
66                 {
67                         change_event = new AutoResetEvent (false);
68                         jobs = new Hashtable ();
69                         future_jobs = new Hashtable ();
70                         locker = new object ();
71                         scheduler = new Thread (SchedulerThread);
72                         scheduler.IsBackground = true;
73                         scheduler.Start ();
74                 }
75
76                 static long Ticks ()
77                 {
78                         return DateTime.GetTimeMonotonic ();
79                 }
80
81                 static private void SchedulerThread ()
82                 {
83                         Thread.CurrentThread.Name = "Timer-Scheduler";
84                         while (true) {
85                                 long min_next_run = long.MaxValue;
86                                 lock (locker) {
87                                         ArrayList expired = null;
88                                         long ticks = Ticks ();
89                                         bool future_queue_activated = false;
90                                         foreach (Timer t1 in jobs.Keys) {
91                                                 if (t1.next_run <= ticks) {
92                                                         ThreadPool.QueueUserWorkItem (new WaitCallback (t1.callback), t1.state);
93                                                         if (t1.period_ms == -1 || ((t1.period_ms == 0 | t1.period_ms == Timeout.Infinite) && t1.due_time_ms != Timeout.Infinite)) {
94                                                                 t1.next_run = long.MaxValue;
95                                                                 if (expired == null)
96                                                                         expired = new ArrayList ();
97                                                                 expired.Add (t1);
98                                                         } else {
99                                                                 t1.next_run = Ticks () + TimeSpan.TicksPerMillisecond * t1.period_ms;
100                                                                 // if it expires too late, postpone to future_jobs
101                                                                 if (t1.period_ms >= FutureTime) {
102                                                                         if (future_jobs.Count == 0)
103                                                                                 future_queue_activated = true;
104                                                                         future_jobs [t1] = t1;
105                                                                         if (expired == null)
106                                                                                 expired = new ArrayList ();
107                                                                         expired.Add (t1);
108                                                                 }
109                                                         }
110                                                 }
111                                                 if (t1.next_run != long.MaxValue) {
112                                                         min_next_run = Math.Min (min_next_run, t1.next_run);
113                                                 }
114                                         }
115                                         if (future_queue_activated) {
116                                                 StartFutureHandler ();
117                                                 min_next_run = Math.Min (min_next_run, future_checker.next_run);
118                                         }
119                                         if (expired != null) {
120                                                 int count = expired.Count;
121                                                 for (int i = 0; i < count; ++i) {
122                                                         jobs.Remove (expired [i]);
123                                                 }
124                                                 expired.Clear ();
125                                                 if (count > 50)
126                                                         expired = null;
127                                         }
128                                 }
129                                 if (min_next_run != long.MaxValue) {
130                                         long diff = min_next_run - Ticks ();
131                                         if (diff >= 0)
132                                                 change_event.WaitOne ((int)(diff / TimeSpan.TicksPerMillisecond), true);
133                                 } else {
134                                         change_event.WaitOne (Timeout.Infinite, true);
135                                 }
136                         }
137                 }
138
139                 public Timer (TimerCallback callback, object state, int dueTime, int period)
140                 {
141                         if (dueTime < -1)
142                                 throw new ArgumentOutOfRangeException ("dueTime");
143
144                         if (period < -1)
145                                 throw new ArgumentOutOfRangeException ("period");
146
147                         Init (callback, state, dueTime, period);
148                 }
149
150                 public Timer (TimerCallback callback, object state, long dueTime, long period)
151                 {
152                         if (dueTime < -1)
153                                 throw new ArgumentOutOfRangeException ("dueTime");
154
155                         if (period < -1)
156                                 throw new ArgumentOutOfRangeException ("period");
157
158                         Init (callback, state, dueTime, period);
159                 }
160
161                 public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
162                         : this (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds)
163                 {
164                 }
165
166                 [CLSCompliant(false)]
167                 public Timer (TimerCallback callback, object state, uint dueTime, uint period)
168                         : this (callback, state, (long) dueTime, (long) period)
169                 {
170                 }
171
172 #if NET_2_0
173                 public Timer (TimerCallback callback)
174                 {
175                         Init (callback, this, Timeout.Infinite, Timeout.Infinite);
176                 }
177 #endif
178
179                 void Init (TimerCallback callback, object state, long dueTime, long period)
180                 {
181                         if (callback == null)
182                                 throw new ArgumentNullException ("callback");
183                         
184                         this.callback = callback;
185                         this.state = state;
186
187                         Change (dueTime, period);
188                 }
189
190                 public bool Change (int dueTime, int period)
191                 {
192                         return Change ((long)dueTime, (long)period);
193                 }
194
195                 // FIXME: handle this inside the scheduler, so no additional timer is ever active
196                 static void CheckFuture (object state) {
197                         lock (locker) {
198                                 ArrayList moved = null;
199                                 long now = Ticks ();
200                                 foreach (Timer t1 in future_jobs.Keys) {
201                                         if (t1.next_run <= now + FutureTimeTicks) {
202                                                 if (moved == null)
203                                                         moved = new ArrayList ();
204                                                 moved.Add (t1);
205                                                 jobs [t1] = t1;
206                                         }
207                                 }
208                                 if (moved != null) {
209                                         int count = moved.Count;
210                                         for (int i = 0; i < count; ++i) {
211                                                 future_jobs.Remove (moved [i]);
212                                         }
213                                         moved.Clear ();
214                                         change_event.Set ();
215                                 }
216                                 // no point in keeping this helper timer running
217                                 if (future_jobs.Count == 0) {
218                                         future_checker.Dispose ();
219                                         future_checker = null;
220                                 }
221                         }
222                 }
223
224                 static void StartFutureHandler ()
225                 {
226                         if (future_checker == null)
227                                 future_checker = new Timer (CheckFuture, null, FutureTime - 500, FutureTime - 500);
228                 }
229
230                 public bool Change (long dueTime, long period)
231                 {
232                         if(dueTime > 4294967294)
233                                 throw new NotSupportedException ("Due time too large");
234
235                         if(period > 4294967294)
236                                 throw new NotSupportedException ("Period too large");
237
238                         if (dueTime < -1)
239                                 throw new ArgumentOutOfRangeException ("dueTime");
240
241                         if (period < -1)
242                                 throw new ArgumentOutOfRangeException ("period");
243
244                         if (disposed)
245                                 return false;
246
247                         due_time_ms = dueTime;
248                         period_ms = period;
249                         long now = Ticks ();
250                         if (dueTime == 0) {
251                                 next_run = now;
252                         } else if (dueTime == Timeout.Infinite) {
253                                 next_run = long.MaxValue;
254                         } else {
255                                 next_run = dueTime * TimeSpan.TicksPerMillisecond + now;
256                         }
257                         lock (locker) {
258                                 if (next_run != long.MaxValue) {
259                                         bool is_future = next_run - now > FutureTimeTicks;
260                                         Timer t = jobs [this] as Timer;
261                                         if (t == null) {
262                                                 t = future_jobs [this] as Timer;
263                                         } else {
264                                                 if (is_future) {
265                                                         future_jobs [this] = this;
266                                                         jobs.Remove (this);
267                                                 }
268                                         }
269                                         if (t == null) {
270                                                 if (is_future)
271                                                         future_jobs [this] = this;
272                                                 else
273                                                         jobs [this] = this;
274                                         }
275                                         if (is_future)
276                                                 StartFutureHandler ();
277                                         change_event.Set ();
278                                 } else {
279                                         jobs.Remove (this);
280                                         future_jobs.Remove (this);
281                                 }
282                         }
283                         return true;
284                 }
285
286                 public bool Change (TimeSpan dueTime, TimeSpan period)
287                 {
288                         return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
289                 }
290
291                 [CLSCompliant(false)]
292                 public bool Change (uint dueTime, uint period)
293                 {
294                         if (dueTime > Int32.MaxValue)
295                                 throw new NotSupportedException ("Due time too large");
296
297                         if (period > Int32.MaxValue)
298                                 throw new NotSupportedException ("Period too large");
299
300                         return Change ((long) dueTime, (long) period);
301                 }
302
303                 public void Dispose ()
304                 {
305                         disposed = true;
306                         lock (locker) {
307                                 jobs.Remove (this);
308                                 future_jobs.Remove (this);
309                         }
310                 }
311
312                 public bool Dispose (WaitHandle notifyObject)
313                 {
314                         Dispose ();
315                         NativeEventCalls.SetEvent_internal (notifyObject.Handle);
316                         return true;
317                 }
318
319         }
320 }
321