add comment
[mono.git] / mcs / class / corlib / System.Threading / Timer.cs
index a46b0381110ee71a1c826068166f6462b2dc9b61..82422ebb18f1896bd68e4a2f41b2331a7831f0ae 100644 (file)
@@ -29,6 +29,7 @@
 //
 
 using System.Runtime.InteropServices;
+using System.Collections;
 
 namespace System.Threading
 {
@@ -37,125 +38,114 @@ namespace System.Threading
 #endif
        public sealed class Timer : MarshalByRefObject, IDisposable
        {
-               sealed class Runner : MarshalByRefObject
+#region Timer instance fields
+               TimerCallback callback;
+               object state;
+               long due_time_ms;
+               long period_ms;
+               long next_run; // in ticks
+               bool disposed;
+#endregion
+
+               // timers that expire after FutureTime will be put in future_jobs
+               // 5 seconds seems reasonable, this must be at least 1 second
+               const long FutureTime = 5 * 1000;
+               const long FutureTimeTicks = FutureTime * TimeSpan.TicksPerMillisecond;
+
+#region Timer static fields
+               static Thread scheduler;
+               static Hashtable jobs;
+               static Hashtable future_jobs;
+               static Timer future_checker;
+               static AutoResetEvent change_event;
+               static object locker;
+#endregion
+
+               /* we use a static initializer to avoid race issues with the thread creation */
+               static Timer ()
                {
-                       ManualResetEvent wait;
-                       AutoResetEvent start_event;
-                       TimerCallback callback;
-                       object state;
-                       int dueTime;
-                       int period;
-                       bool disposed;
-                       bool aborted;
-
-                       public Runner (TimerCallback callback, object state, AutoResetEvent start_event)
-                       {
-                               this.callback = callback;
-                               this.state = state;
-                               this.start_event = start_event;
-                               this.wait = new ManualResetEvent (false);
-                       }
-
-                       public int DueTime {
-                               get { return dueTime; }
-                               set { dueTime = value; }
-                       }
-
-                       public int Period {
-                               get { return period; }
-                               set { period = value == 0 ? Timeout.Infinite : value; }
-                       }
-
-                       bool WaitForDueTime ()
-                       {
-                               if (dueTime > 0) {
-                                       bool signaled;
-                                       do {
-                                               wait.Reset ();
-                                               signaled = wait.WaitOne (dueTime, false);
-                                       } while (signaled == true && !disposed && !aborted);
-
-                                       if (!signaled)
-                                               callback (state);
-
-                                       if (disposed)
-                                               return false;
-                               }
-                               else
-                                       callback (state);
-
-                               return true;
-                       }
-
-                       public void Abort ()
-                       {
-                               lock (this) {
-                                       aborted = true;
-                                       wait.Set ();
-                               }
-                       }
-                       
-                       public void Dispose ()
-                       {
-                               lock (this) {
-                                       disposed = true;
-                                       Abort ();
-                               }
-                       }
-
-                       public void Start ()
-                       {
-                               while (!disposed && start_event.WaitOne ()) {
-                                       if (disposed)
-                                               return;
-
-                                       aborted = false;
-
-                                       if (dueTime == Timeout.Infinite)
-                                               continue;
-
-                                       if (!WaitForDueTime ())
-                                               return;
-
-                                       if (aborted || (period == Timeout.Infinite))
-                                               continue;
-
-                                       bool signaled = false;
-                                       while (true) {
-                                               if (disposed)
-                                                       return;
+                       change_event = new AutoResetEvent (false);
+                       jobs = new Hashtable ();
+                       future_jobs = new Hashtable ();
+                       locker = new object ();
+                       scheduler = new Thread (SchedulerThread);
+                       scheduler.IsBackground = true;
+                       scheduler.Start ();
+               }
 
-                                               if (aborted)
-                                                       break;
+               static long Ticks ()
+               {
+                       return DateTime.GetTimeMonotonic ();
+               }
 
-                                               try {
-                                                       wait.Reset ();
-                                               } catch (ObjectDisposedException) {
-                                                       // FIXME: There is some race condition
-                                                       //        here when the thread is being
-                                                       //        aborted on exit.
-                                                       return;
+               static private void SchedulerThread ()
+               {
+                       Thread.CurrentThread.Name = "Timer-Scheduler";
+                       while (true) {
+                               long min_next_run = long.MaxValue;
+                               lock (locker) {
+                                       ArrayList expired = null;
+                                       long ticks = Ticks ();
+                                       bool future_queue_activated = false;
+                                       foreach (Timer t1 in jobs.Keys) {
+                                               if (t1.next_run <= ticks) {
+                                                       ThreadPool.QueueUserWorkItem (new WaitCallback (t1.callback), t1.state);
+                                                       if (t1.period_ms == -1 || ((t1.period_ms == 0 | t1.period_ms == Timeout.Infinite) && t1.due_time_ms != Timeout.Infinite)) {
+                                                               t1.next_run = long.MaxValue;
+                                                               if (expired == null)
+                                                                       expired = new ArrayList ();
+                                                               expired.Add (t1);
+                                                       } else {
+                                                               t1.next_run = Ticks () + TimeSpan.TicksPerMillisecond * t1.period_ms;
+                                                               // if it expires too late, postpone to future_jobs
+                                                               if (t1.period_ms >= FutureTime) {
+                                                                       if (future_jobs.Count == 0)
+                                                                               future_queue_activated = true;
+                                                                       future_jobs [t1] = t1;
+                                                                       if (expired == null)
+                                                                               expired = new ArrayList ();
+                                                                       expired.Add (t1);
+                                                               }
+                                                       }
                                                }
-
-                                               signaled = wait.WaitOne (period, false);
-
-                                               if (aborted || disposed)
-                                                       break;
-
-                                               if (!signaled) {
-                                                       callback (state);
-                                               } else if (!WaitForDueTime ()) {
-                                                       return;
+                                               if (t1.next_run != long.MaxValue) {
+                                                       min_next_run = Math.Min (min_next_run, t1.next_run);
                                                }
                                        }
+                                       if (future_queue_activated) {
+                                               StartFutureHandler ();
+                                               min_next_run = Math.Min (min_next_run, future_checker.next_run);
+                                       }
+                                       if (expired != null) {
+                                               int count = expired.Count;
+                                               for (int i = 0; i < count; ++i) {
+                                                       jobs.Remove (expired [i]);
+                                               }
+                                               expired.Clear ();
+                                               if (count > 50)
+                                                       expired = null;
+                                       }
+                               }
+
+                               const bool exit_context =
+#if MONOTOUCH
+                                       // MonoTouch doesn't support remoting,
+                                       // so avoid calling into the remoting infrastructure.
+                                       false;
+#else
+                                       true;
+#endif
+
+                               if (min_next_run != long.MaxValue) {
+                                       long diff = min_next_run - Ticks ();
+                                       if (diff >= 0)
+                                               change_event.WaitOne ((int)(diff / TimeSpan.TicksPerMillisecond), exit_context);
+                               } else {
+                                       change_event.WaitOne (Timeout.Infinite, exit_context);
                                }
                        }
                }
 
-               Runner runner;
-               AutoResetEvent start_event;
-               Thread t;
-
                public Timer (TimerCallback callback, object state, int dueTime, int period)
                {
                        if (dueTime < -1)
@@ -175,11 +165,11 @@ namespace System.Threading
                        if (period < -1)
                                throw new ArgumentOutOfRangeException ("period");
 
-                       Init (callback, state, (int) dueTime, (int) period);
+                       Init (callback, state, dueTime, period);
                }
 
                public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
-                       : this (callback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
+                       : this (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds)
                {
                }
 
@@ -196,33 +186,55 @@ namespace System.Threading
                }
 #endif
 
-               void Init (TimerCallback callback, object state, int dueTime, int period)
+               void Init (TimerCallback callback, object state, long dueTime, long period)
                {
-                       start_event = new AutoResetEvent (false);
-                       runner = new Runner (callback, state, start_event);
+                       if (callback == null)
+                               throw new ArgumentNullException ("callback");
+                       
+                       this.callback = callback;
+                       this.state = state;
+
                        Change (dueTime, period);
-                       t = new Thread (new ThreadStart (runner.Start));
-                       t.IsBackground = true;
-                       t.Start ();
                }
 
                public bool Change (int dueTime, int period)
                {
-                       if (dueTime < -1)
-                               throw new ArgumentOutOfRangeException ("dueTime");
-
-                       if (period < -1)
-                               throw new ArgumentOutOfRangeException ("period");
+                       return Change ((long)dueTime, (long)period);
+               }
 
-                       if (runner == null)
-                               return false;
+               // FIXME: handle this inside the scheduler, so no additional timer is ever active
+               static void CheckFuture (object state) {
+                       lock (locker) {
+                               ArrayList moved = null;
+                               long now = Ticks ();
+                               foreach (Timer t1 in future_jobs.Keys) {
+                                       if (t1.next_run <= now + FutureTimeTicks) {
+                                               if (moved == null)
+                                                       moved = new ArrayList ();
+                                               moved.Add (t1);
+                                               jobs [t1] = t1;
+                                       }
+                               }
+                               if (moved != null) {
+                                       int count = moved.Count;
+                                       for (int i = 0; i < count; ++i) {
+                                               future_jobs.Remove (moved [i]);
+                                       }
+                                       moved.Clear ();
+                                       change_event.Set ();
+                               }
+                               // no point in keeping this helper timer running
+                               if (future_jobs.Count == 0) {
+                                       future_checker.Dispose ();
+                                       future_checker = null;
+                               }
+                       }
+               }
 
-                       start_event.Reset ();
-                       runner.Abort ();
-                       runner.DueTime = dueTime;
-                       runner.Period = period;
-                       start_event.Set ();
-                       return true;
+               static void StartFutureHandler ()
+               {
+                       if (future_checker == null)
+                               future_checker = new Timer (CheckFuture, null, FutureTime - 500, FutureTime - 500);
                }
 
                public bool Change (long dueTime, long period)
@@ -233,12 +245,57 @@ namespace System.Threading
                        if(period > 4294967294)
                                throw new NotSupportedException ("Period too large");
 
-                       return Change ((int) dueTime, (int) period);
+                       if (dueTime < -1)
+                               throw new ArgumentOutOfRangeException ("dueTime");
+
+                       if (period < -1)
+                               throw new ArgumentOutOfRangeException ("period");
+
+                       if (disposed)
+                               return false;
+
+                       due_time_ms = dueTime;
+                       period_ms = period;
+                       long now = Ticks ();
+                       if (dueTime == 0) {
+                               next_run = now;
+                       } else if (dueTime == Timeout.Infinite) {
+                               next_run = long.MaxValue;
+                       } else {
+                               next_run = dueTime * TimeSpan.TicksPerMillisecond + now;
+                       }
+                       lock (locker) {
+                               if (next_run != long.MaxValue) {
+                                       bool is_future = next_run - now > FutureTimeTicks;
+                                       Timer t = jobs [this] as Timer;
+                                       if (t == null) {
+                                               t = future_jobs [this] as Timer;
+                                       } else {
+                                               if (is_future) {
+                                                       future_jobs [this] = this;
+                                                       jobs.Remove (this);
+                                               }
+                                       }
+                                       if (t == null) {
+                                               if (is_future)
+                                                       future_jobs [this] = this;
+                                               else
+                                                       jobs [this] = this;
+                                       }
+                                       if (is_future)
+                                               StartFutureHandler ();
+                                       change_event.Set ();
+                               } else {
+                                       jobs.Remove (this);
+                                       future_jobs.Remove (this);
+                               }
+                       }
+                       return true;
                }
 
                public bool Change (TimeSpan dueTime, TimeSpan period)
                {
-                       return Change (Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
+                       return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
                }
 
                [CLSCompliant(false)]
@@ -250,21 +307,16 @@ namespace System.Threading
                        if (period > Int32.MaxValue)
                                throw new NotSupportedException ("Period too large");
 
-                       return Change ((int) dueTime, (int) period);
+                       return Change ((long) dueTime, (long) period);
                }
 
                public void Dispose ()
                {
-                       if (t != null && t.IsAlive) {
-                               if (t != Thread.CurrentThread)
-                                       t.Abort ();
-                               t = null;
+                       disposed = true;
+                       lock (locker) {
+                               jobs.Remove (this);
+                               future_jobs.Remove (this);
                        }
-                       if (runner != null) {
-                               runner.Dispose ();
-                               runner = null;
-                       }
-                       GC.SuppressFinalize (this);
                }
 
                public bool Dispose (WaitHandle notifyObject)
@@ -274,14 +326,6 @@ namespace System.Threading
                        return true;
                }
 
-               ~Timer ()
-               {
-                       if (t != null && t.IsAlive)
-                               t.Abort ();
-
-                       if (runner != null)
-                               runner.Abort ();
-               }
        }
 }