Correctly set SemaphoreSlim underlying kernel-based event state when it's changing...
[mono.git] / mcs / class / corlib / System.Threading / SemaphoreSlim.cs
1 // SemaphoreSlim.cs
2 //
3 // Copyright (c) 2008 Jérémie "Garuma" Laval
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
22 //
23 //
24
25 using System;
26 using System.Diagnostics;
27
28 #if NET_4_0
29 namespace System.Threading
30 {
31         public class SemaphoreSlim : IDisposable
32         {
33                 readonly int max;
34                 int currCount;
35                 bool isDisposed;
36
37                 EventWaitHandle handle;
38
39                 public SemaphoreSlim (int initial) : this (initial, int.MaxValue)
40                 {
41                 }
42
43                 public SemaphoreSlim (int initial, int max)
44                 {
45                         if (initial < 0 || initial > max || max < 0)
46                                 throw new ArgumentOutOfRangeException ("The initial  argument is negative, initial is greater than max, or max is not positive.");
47
48                         this.max = max;
49                         this.currCount = initial;
50                         this.handle = new ManualResetEvent (initial == 0);
51                 }
52
53                 ~SemaphoreSlim ()
54                 {
55                         Dispose(false);
56                 }
57
58                 public void Dispose ()
59                 {
60                         Dispose(true);
61                 }
62
63                 protected virtual void Dispose (bool managedRes)
64                 {
65                         isDisposed = true;
66                 }
67
68                 void CheckState ()
69                 {
70                         if (isDisposed)
71                                 throw new ObjectDisposedException ("The SemaphoreSlim has been disposed.");
72                 }
73
74                 public int CurrentCount {
75                         get {
76                                 return currCount;
77                         }
78                 }
79
80                 public int Release ()
81                 {
82                         return Release(1);
83                 }
84
85                 public int Release (int releaseCount)
86                 {
87                         CheckState ();
88                         if (releaseCount < 1)
89                                 throw new ArgumentOutOfRangeException ("releaseCount", "releaseCount is less than 1");
90
91                         // As we have to take care of the max limit we resort to CAS
92                         int oldValue, newValue;
93                         do {
94                                 oldValue = currCount;
95                                 newValue = (currCount + releaseCount);
96                                 newValue = newValue > max ? max : newValue;
97                         } while (Interlocked.CompareExchange (ref currCount, newValue, oldValue) != oldValue);
98
99                         handle.Set ();
100
101                         return oldValue;
102                 }
103
104                 public void Wait ()
105                 {
106                         Wait (CancellationToken.None);
107                 }
108
109                 public bool Wait (TimeSpan ts)
110                 {
111                         return Wait ((int)ts.TotalMilliseconds, CancellationToken.None);
112                 }
113
114                 public bool Wait (int millisecondsTimeout)
115                 {
116                         return Wait (millisecondsTimeout, CancellationToken.None);
117                 }
118
119                 public void Wait (CancellationToken token)
120                 {
121                         Wait (-1, token);
122                 }
123
124                 public bool Wait (TimeSpan ts, CancellationToken token)
125                 {
126                         CheckState();
127                         return Wait ((int)ts.TotalMilliseconds, token);
128                 }
129
130                 public bool Wait (int millisecondsTimeout, CancellationToken token)
131                 {
132                         CheckState ();
133                         if (millisecondsTimeout < -1)
134                                 throw new ArgumentOutOfRangeException ("millisecondsTimeout",
135                                                                        "millisecondsTimeout is a negative number other than -1");
136
137                         Watch sw = Watch.StartNew ();
138
139                         Func<bool> stopCondition =
140                                 () => token.IsCancellationRequested || (millisecondsTimeout >= 0 && sw.ElapsedMilliseconds > millisecondsTimeout);
141
142                         do {
143                                 bool shouldWait;
144                                 int result;
145
146                                 do {
147                                         if (stopCondition ())
148                                                 return false;
149
150                                         shouldWait = true;
151                                         result = currCount;
152
153                                         if (result > 0)
154                                                 shouldWait = false;
155                                         else
156                                                 break;
157                                 } while (Interlocked.CompareExchange (ref currCount, result - 1, result) != result);
158
159                                 if (!shouldWait) {
160                                         if (result == 1)
161                                                 handle.Reset ();
162                                         break;
163                                 }
164
165                                 SpinWait wait = new SpinWait ();
166
167                                 while (Thread.VolatileRead (ref currCount) <= 0) {
168                                         if (stopCondition ())
169                                                 return false;
170
171                                         wait.SpinOnce ();
172                                 }
173                         } while (true);
174
175                         return true;
176                 }
177
178                 public WaitHandle AvailableWaitHandle {
179                         get {
180                                 return handle;
181                         }
182                 }
183         }
184 }
185 #endif