[Android] Enable access to up-to-date tzdata on Android 10+ (#20350)
[mono-project.git] / mcs / class / corlib / System.Threading / Timer.cs
blob47c5754669410d127d99e1b5a6e4ea3a74c51e6e
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-2009 Novell, Inc (http://www.novell.com)
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.
31 using System.Runtime.InteropServices;
32 using System.Collections.Generic;
33 using System.Collections;
34 using System.Runtime.CompilerServices;
35 using System.Threading.Tasks;
38 namespace System.Threading
40 #if WASM
41 internal static class WasmRuntime {
42 static Dictionary<int, Action> callbacks;
43 static int next_id;
45 [MethodImplAttribute(MethodImplOptions.InternalCall)]
46 static extern void SetTimeout (int timeout, int id);
48 internal static void ScheduleTimeout (int timeout, Action action) {
49 if (callbacks == null)
50 callbacks = new Dictionary<int, Action> ();
51 int id = ++next_id;
52 callbacks [id] = action;
53 SetTimeout (timeout, id);
56 //XXX Keep this in sync with mini-wasm.c:mono_set_timeout_exec
57 static void TimeoutCallback (int id) {
58 var cb = callbacks [id];
59 callbacks.Remove (id);
60 cb ();
63 #endif
66 [ComVisible (true)]
67 public sealed class Timer
68 : MarshalByRefObject, IDisposable, IAsyncDisposable
70 static Scheduler scheduler => Scheduler.Instance;
71 #region Timer instance fields
72 TimerCallback callback;
73 object state;
74 long due_time_ms;
75 long period_ms;
76 long next_run; // in ticks. Only 'Scheduler' can change it except for new timers without due time.
77 bool disposed;
78 bool is_dead, is_added;
79 #endregion
80 public Timer (TimerCallback callback, object state, int dueTime, int period)
82 Init (callback, state, dueTime, period);
85 public Timer (TimerCallback callback, object state, long dueTime, long period)
87 Init (callback, state, dueTime, period);
90 public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
92 Init (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
95 [CLSCompliant(false)]
96 public Timer (TimerCallback callback, object state, uint dueTime, uint period)
98 // convert all values to long - with a special case for -1 / 0xffffffff
99 long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime;
100 long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period;
101 Init (callback, state, d, p);
104 public Timer (TimerCallback callback)
106 Init (callback, this, Timeout.Infinite, Timeout.Infinite);
109 void Init (TimerCallback callback, object state, long dueTime, long period)
111 if (callback == null)
112 throw new ArgumentNullException ("callback");
114 this.callback = callback;
115 this.state = state;
116 this.is_dead = false;
117 this.is_added = false;
119 Change (dueTime, period, true);
122 public bool Change (int dueTime, int period)
124 return Change (dueTime, period, false);
127 public bool Change (TimeSpan dueTime, TimeSpan period)
129 return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds, false);
132 [CLSCompliant(false)]
133 public bool Change (uint dueTime, uint period)
135 // convert all values to long - with a special case for -1 / 0xffffffff
136 long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime;
137 long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period;
138 return Change (d, p, false);
141 public void Dispose ()
143 if (disposed)
144 return;
146 disposed = true;
147 scheduler.Remove (this);
150 public bool Change (long dueTime, long period)
152 return Change (dueTime, period, false);
155 const long MaxValue = UInt32.MaxValue - 1;
157 bool Change (long dueTime, long period, bool first)
159 if (dueTime > MaxValue)
160 throw new ArgumentOutOfRangeException ("dueTime", "Due time too large");
162 if (period > MaxValue)
163 throw new ArgumentOutOfRangeException ("period", "Period too large");
165 // Timeout.Infinite == -1, so this accept everything greater than -1
166 if (dueTime < Timeout.Infinite)
167 throw new ArgumentOutOfRangeException ("dueTime");
169 if (period < Timeout.Infinite)
170 throw new ArgumentOutOfRangeException ("period");
172 if (disposed)
173 throw new ObjectDisposedException (null, Environment.GetResourceString ("ObjectDisposed_Generic"));
175 due_time_ms = dueTime;
176 period_ms = period;
177 long nr;
178 if (dueTime == 0) {
179 nr = 0; // Due now
180 } else if (dueTime < 0) { // Infinite == -1
181 nr = long.MaxValue;
182 /* No need to call Change () */
183 if (first) {
184 next_run = nr;
185 return true;
187 } else {
188 nr = dueTime * TimeSpan.TicksPerMillisecond + GetTimeMonotonic ();
191 scheduler.Change (this, nr);
192 return true;
195 public bool Dispose (WaitHandle notifyObject)
197 if (notifyObject == null)
198 throw new ArgumentNullException ("notifyObject");
199 Dispose ();
200 NativeEventCalls.SetEvent (notifyObject.SafeWaitHandle);
201 return true;
204 public ValueTask DisposeAsync ()
206 Dispose ();
207 return new ValueTask (Task.FromResult<object> (null));
210 // extracted from ../../../../external/referencesource/mscorlib/system/threading/timer.cs
211 internal void KeepRootedWhileScheduled()
215 // TODO: Environment.TickCount should be enough as is everywhere else
216 [MethodImplAttribute(MethodImplOptions.InternalCall)]
217 static extern long GetTimeMonotonic ();
219 struct TimerComparer : IComparer, IComparer<Timer> {
220 int IComparer.Compare (object x, object y)
222 if (x == y)
223 return 0;
224 Timer tx = (x as Timer);
225 if (tx == null)
226 return -1;
227 Timer ty = (y as Timer);
228 if (ty == null)
229 return 1;
230 return Compare(tx, ty);
233 public int Compare (Timer tx, Timer ty)
235 long result = tx.next_run - ty.next_run;
236 return (int)Math.Sign(result);
240 sealed class Scheduler {
241 static readonly Scheduler instance = new Scheduler ();
243 volatile bool needReSort = true;
244 List<Timer> list;
245 long current_next_run = Int64.MaxValue;
247 #if WASM
248 bool scheduled_zero;
250 void InitScheduler () {
253 void WakeupScheduler () {
254 if (!scheduled_zero) {
255 WasmRuntime.ScheduleTimeout (0, this.RunScheduler);
256 scheduled_zero = true;
260 void RunScheduler() {
261 scheduled_zero = false;
262 int ms_wait = RunSchedulerLoop ();
263 if (ms_wait >= 0) {
264 WasmRuntime.ScheduleTimeout (ms_wait, this.RunScheduler);
265 if (ms_wait == 0)
266 scheduled_zero = true;
269 #else
270 ManualResetEvent changed;
272 void InitScheduler () {
273 changed = new ManualResetEvent (false);
274 Thread thread = new Thread (SchedulerThread);
275 thread.IsBackground = true;
276 thread.Start ();
279 void WakeupScheduler () {
280 changed.Set ();
283 void SchedulerThread ()
285 Thread.CurrentThread.Name = "Timer-Scheduler";
286 while (true) {
287 int ms_wait = -1;
288 lock (this) {
289 changed.Reset ();
290 ms_wait = RunSchedulerLoop ();
292 // Wait until due time or a timer is changed and moves from/to the first place in the list.
293 changed.WaitOne (ms_wait);
297 #endif
298 public static Scheduler Instance {
299 get { return instance; }
302 private Scheduler ()
304 list = new List<Timer> (1024);
305 InitScheduler ();
308 public void Remove (Timer timer)
310 lock (this) {
311 // If this is the next item due (index = 0), the scheduler will wake up and find nothing.
312 // No need to Pulse ()
313 InternalRemove (timer);
317 public void Change (Timer timer, long new_next_run)
319 if (timer.is_dead)
320 timer.is_dead = false;
322 bool wake = false;
323 lock (this) {
324 needReSort = true;
326 if (!timer.is_added) {
327 timer.next_run = new_next_run;
328 Add(timer);
329 wake = current_next_run > new_next_run;
330 } else {
331 if (new_next_run == Int64.MaxValue) {
332 timer.next_run = new_next_run;
333 InternalRemove (timer);
334 return;
337 if (!timer.disposed) {
338 // We should only change next_run after removing and before adding
339 timer.next_run = new_next_run;
340 // FIXME
341 wake = current_next_run > new_next_run;
345 if (wake)
346 WakeupScheduler();
349 // This should be the only caller to list.Add!
350 void Add (Timer timer)
352 timer.is_added = true;
353 needReSort = true;
354 list.Add (timer);
355 if (list.Count == 1)
356 WakeupScheduler();
357 //PrintList ();
360 void InternalRemove (Timer timer)
362 timer.is_dead = true;
363 needReSort = true;
366 static void TimerCB (object o)
368 Timer timer = (Timer) o;
369 timer.callback (timer.state);
372 void FireTimer (Timer timer) {
373 long period = timer.period_ms;
374 long due_time = timer.due_time_ms;
375 bool no_more = (period == -1 || ((period == 0 || period == Timeout.Infinite) && due_time != Timeout.Infinite));
376 if (no_more) {
377 timer.next_run = Int64.MaxValue;
378 timer.is_dead = true;
379 } else {
380 timer.next_run = GetTimeMonotonic () + TimeSpan.TicksPerMillisecond * timer.period_ms;
381 timer.is_dead = false;
383 ThreadPool.UnsafeQueueUserWorkItem (TimerCB, timer);
386 int RunSchedulerLoop () {
387 int ms_wait = -1;
388 int i;
389 long ticks = GetTimeMonotonic ();
390 var comparer = new TimerComparer();
392 if (needReSort) {
393 list.Sort(comparer);
394 needReSort = false;
397 long min_next_run = Int64.MaxValue;
399 for (i = 0; i < list.Count; i++) {
400 Timer timer = list[i];
401 if (timer.is_dead)
402 continue;
404 if (timer.next_run <= ticks) {
405 FireTimer(timer);
408 min_next_run = Math.Min(min_next_run, timer.next_run);
410 if ((timer.next_run > ticks) && (timer.next_run < Int64.MaxValue))
411 timer.is_dead = false;
414 for (i = 0; i < list.Count; i++) {
415 Timer timer = list[i];
416 if (!timer.is_dead)
417 continue;
419 timer.is_added = false;
420 needReSort = true;
421 list[i] = list[list.Count - 1];
422 i--;
423 list.RemoveAt(list.Count - 1);
425 if (list.Count == 0)
426 break;
429 if (needReSort) {
430 list.Sort(comparer);
431 needReSort = false;
434 //PrintList ();
435 ms_wait = -1;
436 current_next_run = min_next_run;
437 if (min_next_run != Int64.MaxValue) {
438 long diff = (min_next_run - GetTimeMonotonic ()) / TimeSpan.TicksPerMillisecond;
439 if (diff > Int32.MaxValue)
440 ms_wait = Int32.MaxValue - 1;
441 else {
442 ms_wait = (int)(diff);
443 if (ms_wait < 0)
444 ms_wait = 0;
447 return ms_wait;
451 void PrintList ()
453 Console.WriteLine ("BEGIN--");
454 for (int i = 0; i < list.Count; i++) {
455 Timer timer = (Timer) list.GetByIndex (i);
456 Console.WriteLine ("{0}: {1}", i, timer.next_run);
458 Console.WriteLine ("END----");