|
1 /* |
|
2 * Copyright (c) 2004-2008 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 |
|
18 using System; |
|
19 using System.Threading; |
|
20 |
|
21 namespace SymBuildParsingLib.Utils |
|
22 { |
|
23 // <summary> |
|
24 // Author: William Stacey (staceyw@mvps.org) |
|
25 // Date: 06/10/04 |
|
26 // The Dijkstra semaphore (also called a counting |
|
27 // semaphore) is used to control access to |
|
28 // a set of resources. A Dijkstra semaphore |
|
29 // has a count associated with it and each |
|
30 // Acquire() call reduces the count. A thread |
|
31 // that tries to Acquire() the semaphore |
|
32 // with a zero count blocks until someone else |
|
33 // calls Release() to increase the count. |
|
34 // <seealso cref="http://www.fawcette.com/javapro/ |
|
35 // 2002_02/magazine/features/krangaraju/"/> |
|
36 // <seealso cref="http://www.mcs.drexel.edu/~shartley/ |
|
37 // MCS361/Lectures/designingJavaSemaphore.html"/> |
|
38 // </summary> |
|
39 public sealed class SymSemaphore |
|
40 { |
|
41 #region Constructors |
|
42 // <summary> |
|
43 // Creates semaphore object with a maxCount |
|
44 // and set initial count to maxCount. |
|
45 // </summary> |
|
46 // <param name="maxCount"> |
|
47 // Maximum count for the semaphore object. |
|
48 // This value must be greater than zero. |
|
49 // </param> |
|
50 public SymSemaphore(int maxCount) : this(maxCount, maxCount) |
|
51 { |
|
52 } |
|
53 |
|
54 // <summary> |
|
55 // Creates semaphore object with |
|
56 // a maximum count and initial count. |
|
57 // </summary> |
|
58 // <param name="maxCount"> |
|
59 // Maximum count for the semaphore object. |
|
60 // This value must be greater than zero. |
|
61 // </param> |
|
62 // <param name="initialCount"> |
|
63 // Initial count for the semaphore object. |
|
64 // This value must be zero or greater |
|
65 // and less than or equal to maximumCount. |
|
66 // </param> |
|
67 public SymSemaphore(int initialCount, int maxCount) |
|
68 { |
|
69 if ( initialCount < 0 ) |
|
70 throw new |
|
71 ArgumentOutOfRangeException("initialCount must be >= 0."); |
|
72 if ( maxCount < 1 ) |
|
73 throw new ArgumentOutOfRangeException("maxCount must be >= 1."); |
|
74 if ( initialCount > maxCount) |
|
75 throw new |
|
76 ArgumentOutOfRangeException("initialCount" + |
|
77 " must be <= maxCount."); |
|
78 count = initialCount; |
|
79 this.maxCount = maxCount; |
|
80 syncLock = new object(); |
|
81 starvationLock = new object(); |
|
82 } |
|
83 |
|
84 #endregion |
|
85 |
|
86 #region Properties |
|
87 // <summary> |
|
88 // Gets the current available count (or slots) |
|
89 // in the semaphore. A count of zero means that no slots |
|
90 // are available and calls to Acquire will block until |
|
91 // other thread(s) call Release. |
|
92 // Example: |
|
93 // A semaphore with a count of 2 will allow |
|
94 // 2 more Acquire calls before blocking. |
|
95 // </summary> |
|
96 public int Count |
|
97 { |
|
98 get |
|
99 { |
|
100 lock(syncLock) |
|
101 { |
|
102 return count; |
|
103 } |
|
104 } |
|
105 } |
|
106 |
|
107 // <summary> |
|
108 // Gets the maximum count of the semaphore |
|
109 // set during construction. |
|
110 // </summary> |
|
111 public int MaxCount |
|
112 { |
|
113 get { return maxCount; } |
|
114 } |
|
115 |
|
116 #endregion |
|
117 |
|
118 #region Public Methods |
|
119 |
|
120 // <summary> |
|
121 // Acquires semaphore and decrements count by 1. |
|
122 // If count is zero, this will |
|
123 // block indefinitely until another thread executes |
|
124 // a Release() to increase the count. |
|
125 // </summary> |
|
126 // <returns>true if the call returned because |
|
127 // the caller reacquired the lock for the |
|
128 // specified object. This method does not return |
|
129 // if the lock is not reacquired.</returns> |
|
130 public bool Wait() |
|
131 { |
|
132 return Wait(Timeout.Infinite); |
|
133 } |
|
134 |
|
135 // <summary> |
|
136 // Returns a value indicating if Semephore |
|
137 // can be acquired within the timeout period. |
|
138 // </summary> |
|
139 // <returns>true if the lock was acquired before |
|
140 // the specified time elapsed; otherwise, false.</returns> |
|
141 // <exception cref="ArgumentOutOfRangeException"> |
|
142 // The value of the millisecondsTimeout parameter |
|
143 // is negative, and is not equal to Infinite. |
|
144 // </exception> |
|
145 public bool Wait(int millisecondsTimeout) |
|
146 { |
|
147 lock(syncLock) |
|
148 { |
|
149 // Use spin lock instead of an if test, to handle |
|
150 // rogue/barging threads that can enter |
|
151 // syncLock before a thread that was notified by a pulse. |
|
152 // That rogue thread would |
|
153 // decrease the count, then our "Pulsed" thread |
|
154 // would regain the lock and continue and |
|
155 // decrease the count to -1 which is an error. |
|
156 // The while loop/test prevents this. |
|
157 while ( count == 0 ) |
|
158 try |
|
159 { |
|
160 if (!Monitor.Wait(syncLock, millisecondsTimeout)) |
|
161 return false; |
|
162 } |
|
163 catch |
|
164 { |
|
165 // If we get interupted or aborted, |
|
166 // we may have been pulsed before. |
|
167 // If we just exit, that pulse would get lost and |
|
168 // possibly result in a "live" lock |
|
169 // where other threads are waiting |
|
170 // on syncLock, and never get a pulse. |
|
171 // Regenerate a Pulse as we consumed it. |
|
172 // Even if we did not get |
|
173 // pulsed, this does not hurt as any thread |
|
174 // will check again for count = 0. |
|
175 Monitor.Pulse(syncLock); |
|
176 // Rethrow the exception for caller. |
|
177 // Now semaphore state is same as if |
|
178 // this call never happened. Caller must |
|
179 // decide how to handle exception. |
|
180 throw; |
|
181 } |
|
182 count--; |
|
183 if ( count == 0 ) |
|
184 lock(starvationLock) { Monitor.PulseAll(starvationLock); } |
|
185 return true; |
|
186 } |
|
187 } |
|
188 |
|
189 // <summary> |
|
190 // Acquires all the semaphores and brings |
|
191 // count to zero. This has the effect |
|
192 // of block other threads until we release one or more slots. |
|
193 // <seealso cref="Acquire()"/> |
|
194 // <seealso cref="ReleaseAll()"/> |
|
195 // </summary> |
|
196 // <returns>true if the acquired maxCount slots. |
|
197 // This method does not return until |
|
198 // all slots are acquired.</returns> |
|
199 public bool WaitAll() |
|
200 { |
|
201 // Aquires all slots or blocks for Timeout.Infinite. |
|
202 return WaitAll(Timeout.Infinite); |
|
203 } |
|
204 |
|
205 // <summary> |
|
206 // Tries to acquire (maxCount) slots |
|
207 // in semaphore. If any single attempt to |
|
208 // acquire a semaphore slot exceeds |
|
209 // millisecondsTimeout, then return is false. |
|
210 // Return is true if we acquire maxCount slots. |
|
211 // Normally this method would be paired |
|
212 // with the ReleaseAll method. |
|
213 // </summary> |
|
214 // <param name="millisecondsTimeout"></param> |
|
215 // <returns>true if maxCount slots are acquired |
|
216 // before the specified time elapsed; |
|
217 // otherwise, false.</returns> |
|
218 public bool WaitAll(int millisecondsTimeout) |
|
219 { |
|
220 int slotsGot = 0; |
|
221 int elapsedMS = 0; |
|
222 DateTime start = DateTime.Now; |
|
223 int timeout = millisecondsTimeout; |
|
224 for (int i = 0; i < maxCount; i++) |
|
225 { |
|
226 try |
|
227 { |
|
228 if (! Wait(timeout) ) |
|
229 { |
|
230 // Could not acquire all slots, |
|
231 // release any we may already have got. |
|
232 if ( slotsGot > 0 ) |
|
233 Signal(slotsGot); |
|
234 return false; |
|
235 } |
|
236 else |
|
237 { |
|
238 elapsedMS = (int)((TimeSpan) |
|
239 (DateTime.Now - start)).TotalMilliseconds; |
|
240 timeout = millisecondsTimeout - elapsedMS; |
|
241 // Next wait will be a smaller timeout. |
|
242 |
|
243 if ( timeout < 0 ) |
|
244 timeout = 0; |
|
245 // Next Acquire will return |
|
246 // false if we have to wait; |
|
247 |
|
248 slotsGot++; |
|
249 // If we get all remaining slots |
|
250 // with no timeout, we just keep going. |
|
251 } |
|
252 } |
|
253 catch |
|
254 { |
|
255 // Catch any exception during Acquire wait. |
|
256 if ( slotsGot > 0 ) |
|
257 Signal(slotsGot); |
|
258 throw; |
|
259 } |
|
260 } // end for. |
|
261 // Count is not zero, so notify any/all starvation consumers. |
|
262 lock(starvationLock) { Monitor.PulseAll(starvationLock); } |
|
263 return true; |
|
264 } |
|
265 |
|
266 // <summary> |
|
267 // Increases the count of the semaphore object by one. |
|
268 // </summary> |
|
269 public void Signal() |
|
270 { |
|
271 Signal(1); |
|
272 } |
|
273 |
|
274 // <summary> |
|
275 // Increases the count of the semaphore |
|
276 // object by a specified amount. |
|
277 // </summary> |
|
278 // <param name="count">Amount by which the semaphore |
|
279 // object's current count is to be increased.</param> |
|
280 // <exception cref="ArgumentOutOfRangeException"> |
|
281 // The releaseCount must be one or greater. |
|
282 // </exception> |
|
283 // <exception cref="ArgumentOutOfRangeException"> |
|
284 // The releaseCount would cause |
|
285 // the semaphore's count to exceed maxCount. |
|
286 // </exception> |
|
287 public void Signal(int releaseCount) |
|
288 { |
|
289 if ( releaseCount < 1 ) |
|
290 throw new |
|
291 ArgumentOutOfRangeException("releaseCount must be >= 1."); |
|
292 |
|
293 lock(syncLock) |
|
294 { |
|
295 if ( (count + releaseCount) > maxCount ) |
|
296 throw new |
|
297 ArgumentOutOfRangeException("releaseCount" + |
|
298 " would cause the semaphore's count to exceed maxCount."); |
|
299 count += releaseCount; |
|
300 Monitor.PulseAll(syncLock); |
|
301 } |
|
302 } |
|
303 |
|
304 // <summary> |
|
305 // Returns indication if we could |
|
306 // release one slot in the semaphore. |
|
307 // </summary> |
|
308 // <returns>true if we released |
|
309 // one slot; otherwise false.</returns> |
|
310 public bool TryRelease() |
|
311 { |
|
312 return TryRelease(1); |
|
313 } |
|
314 |
|
315 // <summary> |
|
316 // Returns indication if we could release |
|
317 // releaseCount slots in the semaphore. |
|
318 // </summary> |
|
319 // <param name="releaseCount"></param> |
|
320 // <returns>true if we released releaseCount |
|
321 // slots; otherwise false.</returns> |
|
322 public bool TryRelease(int releaseCount) |
|
323 { |
|
324 if ( releaseCount <= 0 ) |
|
325 return false; |
|
326 |
|
327 lock(syncLock) |
|
328 { |
|
329 if ( (count + releaseCount) > maxCount ) |
|
330 return false; |
|
331 else |
|
332 count += releaseCount; |
|
333 Monitor.PulseAll(syncLock); |
|
334 return true; |
|
335 } |
|
336 } |
|
337 |
|
338 // <summary> |
|
339 // Releases all remaining semaphores |
|
340 // not currently owned. This would normally be |
|
341 // called by a thread that previously |
|
342 // called AcquireAll(). Note: Be carefull when |
|
343 // using this method as it will release |
|
344 // all threads waiting on an Aquire method, |
|
345 // which may or may not be what you want. |
|
346 // An alternative would be to spin on |
|
347 // TryRelease() until it returns false. |
|
348 // </summary> |
|
349 public void ReleaseAll() |
|
350 { |
|
351 lock(syncLock) |
|
352 { |
|
353 count = maxCount; |
|
354 Monitor.PulseAll(syncLock); |
|
355 // We PulseAll instead of calling pulse |
|
356 // with exact number of times needed. |
|
357 // This can be slightly inefficient, |
|
358 // but is safe and simple. |
|
359 // See http://www.mcs.drexel.edu/~shartley/ |
|
360 // MCS361/Lectures/designingJavaSemaphore.html |
|
361 } |
|
362 } |
|
363 |
|
364 // <summary> |
|
365 // This method blocks the calling thread |
|
366 // until the semaphore count drops to zero. |
|
367 // A drop to zero will not be recognized |
|
368 // if a release happens before this call. |
|
369 // You can use this to get notified when |
|
370 // semephore's count reaches zero. This |
|
371 // is also known as a "reverse-sensing" semaphore. |
|
372 // </summary> |
|
373 public void WaitForStarvation() |
|
374 { |
|
375 lock(starvationLock) |
|
376 { |
|
377 // We will block until count is 0. |
|
378 // We use Interlocked just to be sure |
|
379 // we test for zero correctly as we |
|
380 // are not in the syncLock context. |
|
381 if ( Interlocked.CompareExchange(ref count, 0, 0) != 0 ) |
|
382 Monitor.Wait(starvationLock); |
|
383 // Any Exception during wait will |
|
384 // just go to caller. Do not need to signal |
|
385 // any other threads as PulseAll(starvationLock) is used. |
|
386 // Also note we don't do a spin |
|
387 // while() test as we only care that |
|
388 // count *did go to zero at some instant. |
|
389 } |
|
390 } |
|
391 #endregion |
|
392 |
|
393 #region Data members |
|
394 // Current count available. |
|
395 private int count; |
|
396 // Max slots in the semaphore. |
|
397 private int maxCount; |
|
398 // Object used for sync. |
|
399 private readonly object syncLock; |
|
400 // Object used for starvation sync. |
|
401 private readonly object starvationLock; |
|
402 #endregion |
|
403 } |
|
404 } |