@@ -35,7 +35,7 @@ public static void PerformModuleReload(PythonContext/*!*/ context, PythonDiction
3535
3636 #region Public API Surface
3737
38- public static double TIMEOUT_MAX = 0 ; // TODO: fill this with a proper value
38+ public static double TIMEOUT_MAX = Math . Floor ( TimeSpan . MaxValue . TotalSeconds ) ;
3939
4040 [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Microsoft.Security" , "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes" ) ]
4141 public static readonly PythonType LockType = DynamicHelpers . GetPythonTypeFromType ( typeof ( @lock ) ) ;
@@ -138,23 +138,33 @@ public static object _set_sentinel(CodeContext context) {
138138
139139 #endregion
140140
141+ #nullable enable
142+
141143 [ PythonType , PythonHidden ]
142144 public sealed class @lock {
143- private AutoResetEvent blockEvent ;
144- private Thread curHolder ;
145+ private AutoResetEvent ? blockEvent ;
146+ private Thread ? curHolder ;
145147
146148 public object __enter__ ( ) {
147- acquire ( true , - 1 ) ;
149+ acquire ( ) ;
148150 return this ;
149151 }
150152
151153 public void __exit__ ( CodeContext /*!*/ context , [ NotNone ] params object [ ] args ) {
152154 release ( context ) ;
153155 }
154156
155- public bool acquire ( bool blocking = true , float timeout = - 1 ) {
157+ public bool acquire ( bool blocking = true , double timeout = - 1 ) {
158+ var timespan = Timeout . InfiniteTimeSpan ;
159+
160+ if ( timeout != - 1 ) {
161+ if ( ! blocking ) throw PythonOps . ValueError ( "can't specify a timeout for a non-blocking call" ) ;
162+ if ( timeout < 0 ) throw PythonOps . ValueError ( "timeout value must be a non-negative number" ) ;
163+ timespan = TimeSpan . FromSeconds ( timeout ) ;
164+ }
165+
156166 for ( ; ; ) {
157- if ( Interlocked . CompareExchange < Thread > ( ref curHolder , Thread . CurrentThread , null ) == null ) {
167+ if ( Interlocked . CompareExchange ( ref curHolder , Thread . CurrentThread , null ) is null ) {
158168 return true ;
159169 }
160170 if ( ! blocking ) {
@@ -166,16 +176,16 @@ public bool acquire(bool blocking = true, float timeout = -1) {
166176 CreateBlockEvent ( ) ;
167177 continue ;
168178 }
169- if ( ! blockEvent . WaitOne ( timeout < 0 ? Timeout . InfiniteTimeSpan : TimeSpan . FromSeconds ( timeout ) ) ) {
179+ if ( ! blockEvent . WaitOne ( timespan ) ) {
170180 return false ;
171181 }
172182 GC . KeepAlive ( this ) ;
173183 }
174184 }
175185
176186 public void release ( CodeContext /*!*/ context ) {
177- if ( Interlocked . Exchange < Thread > ( ref curHolder , null ) == null ) {
178- throw PythonExceptions . CreateThrowable ( ( PythonType ) context . LanguageContext . GetModuleState ( "threaderror" ) , "lock isn't held" , null ) ;
187+ if ( Interlocked . Exchange ( ref curHolder , null ) is null ) {
188+ throw PythonOps . RuntimeError ( "release unlocked lock" ) ;
179189 }
180190 if ( blockEvent != null ) {
181191 // if this isn't set yet we race, it's handled in Acquire()
@@ -184,18 +194,135 @@ public void release(CodeContext/*!*/ context) {
184194 }
185195 }
186196
187- public bool locked ( ) {
188- return curHolder != null ;
197+ public bool locked ( )
198+ => curHolder is not null ;
199+
200+ public string __repr__ ( ) {
201+ if ( curHolder is null ) {
202+ return $ "<unlocked _thread.lock object at 0x{ IdDispenser . GetId ( this ) : X16} >";
203+ }
204+ return $ "<locked _thread.lock object at 0x{ IdDispenser . GetId ( this ) : X16} >";
189205 }
190206
191207 private void CreateBlockEvent ( ) {
192208 AutoResetEvent are = new AutoResetEvent ( false ) ;
193- if ( Interlocked . CompareExchange < AutoResetEvent > ( ref blockEvent , are , null ) != null ) {
209+ if ( Interlocked . CompareExchange ( ref blockEvent , are , null ) is not null ) {
194210 are . Close ( ) ;
195211 }
196212 }
197213 }
198214
215+ [ PythonType ]
216+ public sealed class RLock {
217+ private AutoResetEvent ? blockEvent ;
218+ private Thread ? curHolder ;
219+ private int count ;
220+
221+ public object __enter__ ( ) {
222+ acquire ( ) ;
223+ return this ;
224+ }
225+
226+ public void __exit__ ( CodeContext /*!*/ context , [ NotNone ] params object [ ] args ) {
227+ release ( ) ;
228+ }
229+
230+ public bool acquire ( bool blocking = true , double timeout = - 1 ) {
231+ var timespan = Timeout . InfiniteTimeSpan ;
232+
233+ if ( timeout != - 1 ) {
234+ if ( ! blocking ) throw PythonOps . ValueError ( "can't specify a timeout for a non-blocking call" ) ;
235+ if ( timeout < 0 ) throw PythonOps . ValueError ( "timeout value must be a non-negative number" ) ;
236+ timespan = TimeSpan . FromSeconds ( timeout ) ;
237+ }
238+
239+ var currentThread = Thread . CurrentThread ;
240+
241+ for ( ; ; ) {
242+ var previousThread = Interlocked . CompareExchange ( ref curHolder , currentThread , null ) ;
243+ if ( previousThread == currentThread ) {
244+ count ++ ;
245+ return true ;
246+ }
247+ if ( previousThread is null ) {
248+ count = 1 ;
249+ return true ;
250+ }
251+ if ( ! blocking ) {
252+ return false ;
253+ }
254+ if ( blockEvent is null ) {
255+ // try again in case someone released us, checked the block
256+ // event and discovered it was null so they didn't set it.
257+ CreateBlockEvent ( ) ;
258+ continue ;
259+ }
260+ if ( ! blockEvent . WaitOne ( timespan ) ) {
261+ return false ;
262+ }
263+ GC . KeepAlive ( this ) ;
264+ }
265+ }
266+
267+ public void release ( ) {
268+ var currentThread = Thread . CurrentThread ;
269+
270+ if ( curHolder != currentThread ) {
271+ throw PythonOps . RuntimeError ( "cannot release un-acquired lock" ) ;
272+ }
273+ if ( -- count > 0 ) {
274+ return ;
275+ }
276+
277+ if ( Interlocked . Exchange ( ref curHolder , null ) is null ) {
278+ throw PythonOps . RuntimeError ( "release unlocked lock" ) ;
279+ }
280+ if ( blockEvent is not null ) {
281+ // if this isn't set yet we race, it's handled in acquire()
282+ blockEvent . Set ( ) ;
283+ GC . KeepAlive ( this ) ;
284+ }
285+ }
286+
287+ public string __repr__ ( ) {
288+ if ( curHolder is null ) {
289+ return $ "<unlocked _thread.RLock object owner=0 count=0 at 0x{ IdDispenser . GetId ( this ) : X16} >";
290+ }
291+ return $ "<locked _thread.RLock object owner={ curHolder ? . ManagedThreadId } count={ count } at 0x{ IdDispenser . GetId ( this ) : X16} >";
292+ }
293+
294+ public void _acquire_restore ( [ NotNone ] PythonTuple state ) {
295+ acquire ( ) ;
296+ count = ( int ) state [ 0 ] ! ;
297+ curHolder = ( Thread ? ) state [ 1 ] ;
298+ }
299+
300+ public PythonTuple _release_save ( ) {
301+ var count = Interlocked . Exchange ( ref this . count , 0 ) ;
302+ if ( count == 0 ) {
303+ throw PythonOps . RuntimeError ( "cannot release un-acquired lock" ) ;
304+ }
305+
306+ // release
307+ var owner = Interlocked . Exchange ( ref curHolder , null ) ;
308+ blockEvent ? . Set ( ) ;
309+
310+ return PythonTuple . MakeTuple ( count , owner ) ;
311+ }
312+
313+ public bool _is_owned ( )
314+ => curHolder == Thread . CurrentThread ;
315+
316+ private void CreateBlockEvent ( ) {
317+ AutoResetEvent are = new AutoResetEvent ( false ) ;
318+ if ( Interlocked . CompareExchange ( ref blockEvent , are , null ) != null ) {
319+ are . Close ( ) ;
320+ }
321+ }
322+ }
323+
324+ #nullable restore
325+
199326 #region Internal Implementation details
200327
201328 private static Thread CreateThread ( CodeContext /*!*/ context , ThreadStart start ) {
0 commit comments