View | Details | Raw Unified | Return to bug 315999
Collapse All | Expand All

(-)System.Threading/Timer.cs (-117 / +317 lines)
Lines 4-9 Link Here
4
// Authors:
4
// Authors:
5
// 	Dick Porter (dick@ximian.com)
5
// 	Dick Porter (dick@ximian.com)
6
// 	Gonzalo Paniagua Javier (gonzalo@ximian.com)
6
// 	Gonzalo Paniagua Javier (gonzalo@ximian.com)
7
// 	Rafael Ferreira (raf@ophion.org)
7
//
8
//
8
// (C) 2001, 2002 Ximian, Inc.  http://www.ximian.com
9
// (C) 2001, 2002 Ximian, Inc.  http://www.ximian.com
9
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
10
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
Lines 29-34 Link Here
29
//
30
//
30
31
31
using System.Runtime.InteropServices;
32
using System.Runtime.InteropServices;
33
using System.Collections;
34
using System;
32
35
33
namespace System.Threading
36
namespace System.Threading
34
{
37
{
Lines 37-161 Link Here
37
#endif
40
#endif
38
	public sealed class Timer : MarshalByRefObject, IDisposable
41
	public sealed class Timer : MarshalByRefObject, IDisposable
39
	{
42
	{
40
		sealed class Runner : MarshalByRefObject
43
		/*
41
		{
44
		
42
			ManualResetEvent wait;
45
		Timer Scheduler
43
			AutoResetEvent start_event;
46
		---------------
44
			TimerCallback callback;
47
		Author: Rafael Ferreira (raf@ophion.org)
45
			object state;
48
		
46
			int dueTime;
49
		The code below implements a single thread scheduler that fires
47
			int period;
50
		events using the runtime's built-in thread pool.
48
			bool disposed;
49
			bool aborted;
50
51
51
			public Runner (TimerCallback callback, object state, AutoResetEvent start_event)
52
		Key Features:
52
			{
53
			Single thread scheduler:
53
				this.callback = callback;
54
				A single thread handles firing all timer jobs thus allowing a 
54
				this.state = state;
55
				much greater number of Timers to be defined
55
				this.start_event = start_event;
56
			Lazy init:
56
				this.wait = new ManualResetEvent (false);
57
				Timer scheduler is only started after the first System.Threading.Timer is created
57
			}
58
			Early termination:
59
				Timer scheduler thread dies if there are no more timer jobs in its Job queue
60
				
61
			
62
		In a nutshell the scheduler works like this:
63
			1 - The main scheduler thread (TimerScheduler) wakes up and finds out what time it is
64
			2 - The scheduler iterates over the list of timer jobs (Jobs) to find out when the next job is
65
			as well as fires all timers that were scheduled to run now or in the past
66
			3 - The Scheduler then calculates the mutiplier for its sleep algorithm. The multiplier
67
			is basically MSEC_UNTIL_NEXT_JOB / TIME_SLICE where TIME_SLICE is the minimum amount of 
68
			time the scheduler thread is allowed to sleep for
69
			4 - Sleep for multiplier * TIME_SLICE
70
			5 - Goto 1
58
71
59
			public int DueTime {
72
		Possible improvements:
60
				get { return dueTime; }
73
			* Convert the big for-loop into a sorted data structure. This will speed up the time
61
				set { dueTime = value; }
74
			it takes the scheduler to iterate over the timer jobs and lower CPU usage under insane 
75
			amounts of Timers 
76
77
		Possible issues:
78
			* Overflow issues with the multiplier
79
			* Race conditions with lazy-init of the scheduler thread.
80
81
		Note:
82
			* MONO_TIMER_DEBUG environment variable can be used to turn on the scheduler's debug log
83
			* MONO_TIMER_USETP environment variable can be used to force the timer scheduler to use 
84
			the CLI's built in thread pool
85
			* sync_obj is used to make signaling the scheduler class sane so only one signal is sent at a
86
			time and a decision can be made on the signal so signals are "batched" or discarded 
87
88
		*/
89
90
		// timer metadata:
91
		internal long NextRun;
92
		internal long LastRun;
93
		internal long Period;
94
		internal bool Enabled;
95
		internal TimerCallback Callback = null;
96
		internal int ID;
97
		internal object State = null;
98
		
99
		// flag that turns on "verbose logging"
100
		static bool debug_enabled = false;
101
		
102
		// flag that tells scheduler to use the cli's thread pool
103
		static bool use_threadpool = false;
104
		
105
		// mininum sleep time
106
		const int TIME_SLICE = 10 ; // 5 msec
107
		
108
		// next Timer ID;
109
		static int next_id = 0;
110
111
		// timer scheduler - there can only be one
112
		static Thread scheduler = null;
113
		
114
		// used to enforce thread safety
115
		static object sync_obj = new object();
116
		
117
		static Hashtable Jobs =  new Hashtable();
118
119
		static bool scheduler_ready = false;
120
		
121
		// this enum is used to signal the scheduler thread of the reason for the Abort() call
122
		// abort() is used to signal the timer thread since Interrupt() is not implemented
123
		enum AbortSignals { TimerAdded, TimerRemoved, TimerChanged };
124
125
		sealed class Runner: MarshalByRefObject
126
		{
127
			Timer timer;
128
			public Runner(Timer t) {
129
				this.timer = t;
130
			
62
			}
131
			}
132
			public void Run() {
133
				// dispatching callback
134
				timer.Callback(timer.State);
135
			}
136
		}
63
137
64
			public int Period {
138
		void SetProperties (int dueTime, int period)
65
				get { return period; }
139
		{
66
				set { period = value == 0 ? Timeout.Infinite : value; }
140
			if (dueTime == Timeout.Infinite) {
141
				//disables the job
142
				if (debug_enabled)
143
					log("disabling timer " + ID);
144
145
				Enabled = false;
146
			}else {
147
				NextRun = DateTime.Now.Ticks + TimeSpan.TicksPerMillisecond * dueTime;
148
				Enabled = true;
67
			}
149
			}
150
			if (period == Timeout.Infinite) {
151
				if (debug_enabled)
152
					log("timer " + ID  + " will only run once");
153
				Period = -1;
154
			}else {
155
				Period = TimeSpan.TicksPerMillisecond * period;
156
			}
68
157
69
			bool WaitForDueTime ()
158
			if (debug_enabled)
70
			{
159
				log(String.Format("timer configured, id {2} delay {0} msec period {1} msec", dueTime, period, ID));
71
				if (dueTime > 0) {
160
		}
72
					bool signaled;
161
		// prorperly handles signaling the scheduler thread
73
					do {
162
		// it will retry for 500 msec (.5 sec) if it can't properly signal scheduler
74
						wait.Reset ();
163
		// NOT THREAD SAFE
75
						signaled = wait.WaitOne (dueTime, false);
164
		void SendSchedulerSignal (AbortSignals signal) 
76
					} while (signaled == true && !disposed && !aborted);
165
		{
166
			for (int i = 0; i < 100; i++) {
77
167
78
					if (!signaled)
168
				// we don't start a new scheduler if the signal is TimerRemoved
79
						callback (state);
169
				if (scheduler == null) {
170
					log("Scheduler not currently running... new scheduler will be initiated");
171
					scheduler = new Thread( new ThreadStart(SchedulerThread));
172
					scheduler.IsBackground = true;
173
					scheduler.Start();
174
					return;
175
					
176
				} 
80
177
81
					if (disposed)
178
				if (scheduler.ThreadState == ThreadState.AbortRequested || scheduler.ThreadState == ThreadState.Aborted) {
82
						return false;
179
					return;
83
				}
180
				}
84
				else
85
					callback (state);
86
181
87
				return true;
182
				if (scheduler_ready) {
88
			}
183
					// we batch send Abort() calls
184
					// Abort is used since Thread.Interrupt is not supported.
185
					scheduler.Abort(signal);
186
					return;
187
					
188
				}
189
				if (debug_enabled)
190
					log("could not properly signal timer-scheduler, waiting...");
89
191
90
			public void Abort ()
192
				Thread.Sleep(5); 
91
			{
92
				lock (this) {
93
					aborted = true;
94
					wait.Set ();
95
				}
96
			}
193
			}
97
			
194
			
98
			public void Dispose ()
195
			throw new Exception("Could not properly abort timer-scheduler thread");
99
			{
196
			
100
				lock (this) {
197
			
101
					disposed = true;
198
		}
102
					Abort ();
103
				}
104
			}
105
199
106
			public void Start ()
200
		public void SchedulerThread () 
107
			{
201
		{
108
				while (!disposed && start_event.WaitOne ()) {
202
			Thread.CurrentThread.Name = "Timer-Scheduler";
109
					if (disposed)
203
			long tick = 0;
110
						return;
204
			long next_job = Int64.MaxValue;
205
			Timer tj = null;
206
			int multiplier = 1;
207
			
208
			// big scary for-loop that iterates over the jobs
209
			while(Jobs.Count > 0) {
210
				if (!scheduler_ready)  {
211
					scheduler_ready=true;
111
212
112
					aborted = false;
213
					if (debug_enabled)
214
						log("Scheduler is ready");
215
				}
113
216
114
					if (dueTime == Timeout.Infinite)
217
				try {
115
						continue;
218
					tick = DateTime.Now.Ticks;
116
219
117
					if (!WaitForDueTime ())
220
					// tries to lock the job queue:
118
						return;
221
					Monitor.Enter(Jobs);
119
222
120
					if (aborted || (period == Timeout.Infinite))
223
					foreach (DictionaryEntry entry in Jobs) {
121
						continue;
224
						tj = entry.Value as Timer;
122
225
123
					bool signaled = false;
226
						if (tj.Enabled == false) {
124
					while (true) {
227
							continue;
125
						if (disposed)
228
						}
126
							return;
229
						if ( tj.NextRun <= tick) {
230
							
231
							if (debug_enabled)
232
								log("Firing job " + tj.ID);
233
							
234
							// Firing job 
235
							dispatch(tj);
236
							
237
							if (tj.Period == - 1) {
238
								// it is a run-once job, so we disable it
239
								tj.Enabled = false;
240
							}
241
							else {
242
								tj.NextRun = tick + tj.Period;
243
							}
244
							
245
							tj.LastRun = tick;
127
246
128
						if (aborted)
247
							// we reset the next_job to the max possible value so the real next job
129
							break;
248
							// can be figured out
130
249
							next_job = Int64.MaxValue;
131
						try {
132
							wait.Reset ();
133
						} catch (ObjectDisposedException) {
134
							// FIXME: There is some race condition
135
							//        here when the thread is being
136
							//        aborted on exit.
137
							return;
138
						}
250
						}
251
						if ( next_job > tj.NextRun) {
252
							next_job = tj.NextRun;
253
						}
254
					}
255
					Monitor.Exit(Jobs);
139
256
140
						signaled = wait.WaitOne (period, false);
257
					// no other jobs are available and all timers
258
					// are disabled
259
					if (next_job == Int64.MaxValue) {
260
						if (debug_enabled)
261
							log("no active timers found, going into infinite sleep");
262
						Thread.Sleep(Timeout.Infinite);
263
						
264
					}else {
265
						multiplier = (int) ((next_job - tick) / TimeSpan.TicksPerMillisecond);
266
						multiplier = multiplier / TIME_SLICE;
267
						if (multiplier > 0 ) {
268
							//TODO there are some edgy race conditions between the abort signal and telling a thread 
269
							// to sleep
270
							if (debug_enabled)
271
								log("gong to sleep for " + multiplier + " times the time slice");
141
272
142
						if (aborted || disposed)
273
							Thread.Sleep(multiplier * TIME_SLICE);
143
							break;
274
						}
275
					}
276
					
277
				} catch (ThreadAbortException ex) {
278
					if (ex.ExceptionState is AbortSignals) {
279
						if (debug_enabled)
280
							log(String.Format("abort signal received: {0}",ex.ExceptionState));
144
281
145
						if (!signaled) {
282
						switch((AbortSignals)ex.ExceptionState) {
146
							callback (state);
283
							default:
147
						} else if (!WaitForDueTime ()) {
284
								Thread.ResetAbort();
148
							return;
285
								break;
149
						}
286
						}
287
					}else {
288
						log(ex.Message);
150
					}
289
					}
290
				}catch (Exception ex) {
291
					if (debug_enabled) {
292
						log("generic exception caught by the scheduler");
293
						log(ex.Message);
294
					}
295
				}finally {
296
					// making sure jobs lock gets released 
297
					Monitor.Exit(Jobs);
151
				}
298
				}
299
					
152
			}
300
			}
301
			scheduler_ready = false;
302
			scheduler = null;
303
	
304
			if (debug_enabled)
305
				log("timer scheduler is shutting down");
153
		}
306
		}
154
307
155
		Runner runner;
308
		internal void log (string str)
156
		AutoResetEvent start_event;
309
		{
157
		Thread t;
310
			if (debug_enabled)
311
				Console.Error.WriteLine(String.Format("{0}.{1} TIMER SCHEDULER: {2}",DateTime.Now,DateTime.Now.Millisecond,str));
312
		}
158
313
314
		void dispatch(Timer timer) {
315
			
316
			// should we use the thread pool? 
317
			if(use_threadpool) {
318
				ThreadPool.QueueUserWorkItem(new WaitCallback(timer.Callback),timer.State);
319
				return;
320
			}
321
			
322
			// let's just fire up a new thread to handle running the timer
323
			Runner runner = new Runner(timer);
324
325
			try {
326
				Thread t = new Thread(new ThreadStart(runner.Run));
327
				t.IsBackground = true;
328
				t.Start();
329
330
			} catch (Exception ex) {
331
				// most likely the thread creation failed due to an out of memory exception...
332
				// we can't let it kill the scheduler thread
333
				if (debug_enabled)
334
					log(ex.Message);
335
336
				Console.Error.WriteLine(ex);
337
			}
338
			
339
		}
159
		public Timer (TimerCallback callback, object state, int dueTime, int period)
340
		public Timer (TimerCallback callback, object state, int dueTime, int period)
160
		{
341
		{
161
			if (dueTime < -1)
342
			if (dueTime < -1)
Lines 198-209 Link Here
198
379
199
		void Init (TimerCallback callback, object state, int dueTime, int period)
380
		void Init (TimerCallback callback, object state, int dueTime, int period)
200
		{
381
		{
201
			start_event = new AutoResetEvent (false);
382
			if (!debug_enabled) {
202
			runner = new Runner (callback, state, start_event);
383
				if(Environment.GetEnvironmentVariable("MONO_TIMER_DEBUG") != null)
203
			Change (dueTime, period);
384
					debug_enabled = true;
204
			t = new Thread (new ThreadStart (runner.Start));
385
			}
205
			t.IsBackground = true;
386
			
206
			t.Start ();
387
			if (!use_threadpool) {
388
				if(Environment.GetEnvironmentVariable("MONO_TIMER_USETP") != null) {
389
					if(debug_enabled)
390
						log("timer will dispatch using the thread pool");
391
392
					use_threadpool = true;
393
				}
394
			}			
395
			ID = Interlocked.Increment(ref next_id);
396
		
397
			// first run take into consideration the delay metric only
398
			SetProperties(dueTime,period);
399
			Callback = callback;
400
			State = state;
401
		
402
			// lock job Q
403
			lock(Jobs) {
404
				Jobs.Add(ID,this);
405
			}
406
			lock(sync_obj) {
407
				SendSchedulerSignal(AbortSignals.TimerAdded);
408
			}
207
		}
409
		}
208
410
209
		public bool Change (int dueTime, int period)
411
		public bool Change (int dueTime, int period)
Lines 214-230 Link Here
214
			if (period < -1)
416
			if (period < -1)
215
				throw new ArgumentOutOfRangeException ("period");
417
				throw new ArgumentOutOfRangeException ("period");
216
418
217
			if (runner == null)
419
			// modifying the job is actually quicker (lock wise) than doing a Remove / Add combo
218
				return false;
420
			lock (Jobs) {
421
				if (!Jobs.Contains(ID)) {
422
					return(false);
423
				}
424
				SetProperties(dueTime,period);
425
				if (debug_enabled)
426
					log("job " + ID +" changed");
219
427
220
			start_event.Reset ();
428
			}
221
			runner.Abort ();
429
			lock (sync_obj) {
222
			runner.DueTime = dueTime;
430
				SendSchedulerSignal(AbortSignals.TimerChanged);
223
			runner.Period = period;
431
			}
224
			start_event.Set ();
225
			return true;
432
			return true;
226
		}
433
		}
227
228
		public bool Change (long dueTime, long period)
434
		public bool Change (long dueTime, long period)
229
		{
435
		{
230
			if(dueTime > 4294967294)
436
			if(dueTime > 4294967294)
Lines 255-269 Link Here
255
461
256
		public void Dispose ()
462
		public void Dispose ()
257
		{
463
		{
258
			if (t != null && t.IsAlive) {
464
			lock (Jobs) {
259
				if (t != Thread.CurrentThread)
465
				if (Jobs.Contains(ID)) { 
260
					t.Abort ();
466
					Jobs.Remove(ID);
261
				t = null;
467
				}
262
			}
468
			}
263
			if (runner != null) {
469
			lock (sync_obj) {
264
				runner.Dispose ();
470
				SendSchedulerSignal(AbortSignals.TimerRemoved);
265
				runner = null;
266
			}
471
			}
472
			if (debug_enabled)
473
				log(String.Format("Job {0} removed",ID));
474
267
			GC.SuppressFinalize (this);
475
			GC.SuppressFinalize (this);
268
		}
476
		}
269
477
Lines 274-287 Link Here
274
			return true;
482
			return true;
275
		}
483
		}
276
484
277
		~Timer ()
278
		{
279
			if (t != null && t.IsAlive)
280
				t.Abort ();
281
282
			if (runner != null)
283
				runner.Abort ();
284
		}
285
	}
485
	}
286
}
486
}
287
487
(-)System.Threading/ChangeLog (+3 lines)
Lines 1-3 Link Here
1
2006-11-04  Rafael Ferreira <raf@ophion.org>
2
	* Timer.cs: changed timer logic to be single threaded as opposed to n:n
3
1
2006-11-02  Dick Porter  <dick@ximian.com>
4
2006-11-02  Dick Porter  <dick@ximian.com>
2
5
3
	* Thread.cs: Use the new Interrupt and SpinWait icalls.
6
	* Thread.cs: Use the new Interrupt and SpinWait icalls.
(-)Test/System.Threading/TimerTest.cs (-54 / +122 lines)
Lines 3-8 Link Here
3
//
3
//
4
// Author:
4
// Author:
5
//   Zoltan Varga (vargaz@freemail.hu)
5
//   Zoltan Varga (vargaz@freemail.hu)
6
//   Rafael Ferreira (raf@ophion.org)
6
//
7
//
7
// (C) 2004 Novell, Inc (http://www.novell.com)
8
// (C) 2004 Novell, Inc (http://www.novell.com)
8
//
9
//
Lines 10-19 Link Here
10
using NUnit.Framework;
11
using NUnit.Framework;
11
using System;
12
using System;
12
using System.Threading;
13
using System.Threading;
14
using System.Collections;
13
15
14
namespace MonoTests.System.Threading {
16
namespace MonoTests.System.Threading {
15
17
16
	[TestFixture]
17
	//
18
	//
18
	// This whole test seems to fail randomly. Either
19
	// This whole test seems to fail randomly. Either
19
	// - It is relying on a race it might not win (that the timer code runs)
20
	// - It is relying on a race it might not win (that the timer code runs)
Lines 22-132 Link Here
22
	// Am going with door #1, but it would be nice to investigate this.
23
	// Am going with door #1, but it would be nice to investigate this.
23
	// -- Ben
24
	// -- Ben
24
	//
25
	//
25
	public class TimerTest : Assertion {
26
	[TestFixture]
27
	public class TimerTest {
28
		// this bucket is used to avoid non-theadlocal issues
29
		class Bucket {
30
			public int count;
31
		}
32
		[SetUp]
33
		public void setup() {
34
			//creating a timer that will never run just to make sure the
35
			// scheduler is warm for the unit tests
36
			// this makes fair for the "DueTime" test since it 
37
			// doesn't have to wait for the scheduler thread to be 
38
			// created. 
39
			new Timer(null,null,Timeout.Infinite,0);
40
		}
41
		
26
		[Test]
42
		[Test]
27
		[Category ("NotWorking")]
28
		public void TestDueTime ()
43
		public void TestDueTime ()
29
		{
44
		{
30
			counter = 0;
45
			Bucket bucket = new Bucket();
31
			Timer t = new Timer (new TimerCallback (Callback), null, 200, Timeout.Infinite);
46
			Timer t = new Timer (new TimerCallback (Callback), bucket, 200, Timeout.Infinite);
32
			Thread.Sleep (50);
47
			Thread.Sleep (50);
33
			AssertEquals ("t0", 0, counter);
48
			Assert.AreEqual (0, bucket.count);
34
			Thread.Sleep (200);
49
			Thread.Sleep (200);
35
			AssertEquals ("t1", 1, counter);
50
			Assert.AreEqual (1, bucket.count);
36
			Thread.Sleep (500);
51
			Thread.Sleep (500);
37
			AssertEquals ("t2", 1, counter);
52
			Assert.AreEqual (1, bucket.count);
38
			
39
			t.Change (10, 10);
53
			t.Change (10, 10);
40
			Thread.Sleep (500);
54
			Thread.Sleep (1000);
41
			Assert ("t3", counter > 20);
55
			Assert.IsTrue(bucket.count > 20);
42
			t.Dispose ();
56
			t.Dispose ();
43
		}
57
		}
44
58
45
		[Test]
59
		[Test]
46
		[Category ("NotWorking")]
47
		public void TestChange ()
60
		public void TestChange ()
48
		{
61
		{
49
			counter = 0;
62
			Bucket bucket = new Bucket();
50
			Timer t = new Timer (new TimerCallback (Callback), null, 1, 1);
63
			Timer t = new Timer (new TimerCallback (Callback), bucket, 1, 1);
51
			Thread.Sleep (500);
64
			Thread.Sleep (500);
52
			int c = counter;
65
			int c = bucket.count;
53
			Assert ("t1", c > 20);
66
			Assert.IsTrue(c > 20);
54
			t.Change (100, 100);
67
			t.Change (100, 100);
55
			Thread.Sleep (500);
68
			Thread.Sleep (500);
56
			Assert ("t2", counter <= c + 6);
69
			Assert.IsTrue(bucket.count <= c + 6);
57
			t.Dispose ();
70
			t.Dispose ();
58
		}
71
		}
59
72
60
		[Test]
73
		[Test]
61
		[Category ("NotWorking")]
62
		public void TestZeroDueTime () {
74
		public void TestZeroDueTime () {
63
			counter = 0;
75
			Bucket bucket = new Bucket();
64
76
65
			Timer t = new Timer (new TimerCallback (Callback), null, 0, Timeout.Infinite);
77
			Timer t = new Timer (new TimerCallback (Callback), bucket, 0, Timeout.Infinite);
66
			Thread.Sleep (100);
78
			Thread.Sleep (100);
67
			AssertEquals (1, counter);
79
			Assert.AreEqual (1, bucket.count);
68
			t.Change (0, Timeout.Infinite);
80
			t.Change (0, Timeout.Infinite);
69
			Thread.Sleep (100);
81
			Thread.Sleep (100);
70
			AssertEquals (2, counter);
82
			Assert.AreEqual (2, bucket.count);
71
			t.Dispose ();
83
			t.Dispose ();
72
		}
84
		}
73
74
		[Test]
85
		[Test]
75
		[Category ("NotWorking")]
76
		public void TestDispose ()
86
		public void TestDispose ()
77
		{
87
		{	
78
			counter = 0;
88
			Bucket bucket = new Bucket();
79
			Timer t = new Timer (new TimerCallback (CallbackTestDispose), null, 10, 10);
89
			Timer t = new Timer (new TimerCallback (Callback), bucket, 10, 10);
80
			Thread.Sleep (200);
90
			Thread.Sleep (200);
81
			t.Dispose ();
91
			t.Dispose ();
82
			Thread.Sleep (20);
92
			Thread.Sleep (20);
83
			int c = counter;
93
			int c = bucket.count;
84
			Assert (counter > 5);
94
			Assert.IsTrue(bucket.count > 5);
85
			Thread.Sleep (200);
95
			Thread.Sleep (200);
86
			AssertEquals (c, counter);
96
			Assert.AreEqual (c, bucket.count);
87
		}
97
		}
88
98
89
		[Test] // bug #78208
99
		[Test] // bug #78208
90
		public void TestDispose2 ()
100
		public void TestDispose2 ()
91
		{
101
		{
92
			Timer t = new Timer (new TimerCallback (CallbackTestDispose), null, 10, 10);
102
			Timer t = new Timer (new TimerCallback (Callback), null, 10, 10);
93
			t.Dispose ();
103
			t.Dispose ();
94
			t.Dispose ();
104
			t.Dispose ();
95
		}
105
		}
96
		
106
		
97
		[Test]
107
		[Test]
98
		[Category ("NotWorking")]
108
		public void TestHeavyCreationLoad() {
109
			int i = 0;
110
			Bucket b = new Bucket();
111
			while (i < 500) {
112
				new Timer(new TimerCallback(Callback),b,10,Timeout.Infinite);
113
				i++;
114
			}
115
			// 1000 * 10 msec = 10,000 msec or 10 sec - if everything goes well
116
			Thread.Sleep(12*500);
117
			Assert.AreEqual(500,b.count);
118
			
119
		}
120
		[Test]
121
		public void TestQuickDisposeDeadlockBug() {
122
			int i = 0;
123
			Bucket b = new Bucket();
124
			ArrayList timers = new ArrayList();
125
			while (i < 500) {
126
				Timer t = new Timer(new TimerCallback(Callback),b,10,Timeout.Infinite);
127
				timers.Add(t);
128
				i++;
129
				t.Dispose();
130
			}
131
			Thread.Sleep(11*500);
132
		}
133
		[Test]
134
		public void TestInt32MaxDelay() {
135
			Bucket b = new Bucket();
136
			new Timer(new TimerCallback(Callback),b,Int32.MaxValue,Timeout.Infinite);
137
			Thread.Sleep(50);
138
			Assert.AreEqual(0,b.count);
139
			
140
		}
141
		[Test]
142
		public void TestInt32MaxPeriod() {
143
			Bucket b = new Bucket();
144
			new Timer(new TimerCallback(Callback),b,0,Int32.MaxValue);
145
			Thread.Sleep(50);
146
			Assert.AreEqual(1,b.count);
147
			
148
		}
149
		[Test]
150
		public void TestNegativeDelay() {
151
			Bucket b = new Bucket();
152
			try {
153
				new Timer(new TimerCallback(Callback),b,-10,Timeout.Infinite);
154
			} catch (ArgumentOutOfRangeException) {
155
				return;
156
			}
157
			Assert.Fail();
158
			
159
		}
160
		[Test]
161
		public void TestNegativePeriod() {
162
			Bucket b = new Bucket();
163
			try {
164
				new Timer(new TimerCallback(Callback),b,0,-10);
165
			} catch (ArgumentOutOfRangeException) {
166
				return;
167
			}
168
			Assert.Fail();
169
		}
170
171
		[Test]
172
		public void TestDelayZeroPeriodZero() {
173
			Bucket b = new Bucket();
174
			new Timer(new TimerCallback(Callback),b,0,0);
175
			Thread.Sleep(50);
176
			Assert.IsTrue(b.count > 45);
177
			
178
		}
179
180
		[Category("NotWorking")]
99
		public void TestDisposeOnCallback () {
181
		public void TestDisposeOnCallback () {
100
			counter = 0;
182
		
101
			t1 = new Timer (new TimerCallback (CallbackTestDisposeOnCallback), null, 0, 10);
183
			Timer t1 = null;
184
			t1 = new Timer (new TimerCallback (CallbackTestDisposeOnCallback), t1, 0, 10);
102
			Thread.Sleep (200);
185
			Thread.Sleep (200);
103
			AssertNull (t1);
186
			Assert.IsNull(t1);
104
			
187
			
105
			counter = 2;
106
			t1 = new Timer (new TimerCallback (CallbackTestDisposeOnCallback), null, 50, 0);
107
			Thread.Sleep (200);
108
			AssertNull (t1);
109
		}
188
		}
110
		
111
		private void CallbackTestDisposeOnCallback (object foo)
189
		private void CallbackTestDisposeOnCallback (object foo)
112
		{
190
		{
113
			if (++counter == 3) {
191
			((Timer)foo).Dispose();
114
				t1.Dispose ();
115
				t1 = null;
116
			}
117
		}
192
		}
118
193
		
119
		private void CallbackTestDispose (object foo)
120
		{
121
			counter++;
122
		}
123
124
		private void Callback (object foo)
194
		private void Callback (object foo)
125
		{
195
		{
126
			counter++;
196
			Bucket b = foo as Bucket;
197
			b.count++;
127
		}
198
		}
128
129
		Timer t1;
130
		int counter;
131
	}
199
	}
132
}
200
}
(-)Test/System.Threading/ChangeLog (+5 lines)
Lines 1-3 Link Here
1
2006-11-04 Rafael Ferreira <raf@ophion.org>
2
	* TimerTest.cs: Added a bucket object to avoid thread-local issues on failing tests
3
	* TimerTest.cs: updated to user Attributes
4
	* TimerTest.cs: Added new test cases
5
1
2006-06-14  Sebastien Pouliot  <sebastien@ximian.com>
6
2006-06-14  Sebastien Pouliot  <sebastien@ximian.com>
2
7
3
	* ExecutionContextTest.cs: Changed Run test to execute only under
8
	* ExecutionContextTest.cs: Changed Run test to execute only under

Return to bug 315999