提交 67c7faf2 编写于 作者: M Marek Habersack

[asp.net] Get rid of a possible (although unlikely) race condition when acquiring locks

The patch removes a small race condition where a boolean flag is set after acquiring a lock to
indicate to code executing in the finally {} block that it should release the lock. The boolean
variable is now removed and the lock is released unconditionally. It carries a potential to
throw an exception when the lock is not held, but it's better than to fail to release it and
lead the application to a deadlock.
上级 7757931e
...@@ -42,6 +42,21 @@ namespace System.Web.Caching ...@@ -42,6 +42,21 @@ namespace System.Web.Caching
{ {
public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue; public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero; public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
// cacheLock will be released in the code below without checking whether it was
// actually acquired. The API doesn't offer a reliable way to check whether the lock
// is being held by the current thread and since Mono does't implement CER
// (Constrained Execution Regions -
// http://msdn.microsoft.com/en-us/library/ms228973.aspx) currently, we have no
// reliable way of recording the information that the lock has been successfully
// acquired.
// It can happen that a Thread.Abort occurs while acquiring the lock and the lock
// isn't actually held. In this case the attempt to release a lock will throw an
// exception. It's better than a race of setting a boolean flag after acquiring the
// lock and then relying upon it here to release it - that may cause a deadlock
// should we fail to release the lock which was successfully acquired but
// Thread.Abort happened right after that during the stloc instruction to set the
// boolean flag. Once CERs are supported we can use the boolean flag reliably.
ReaderWriterLockSlim cacheLock; ReaderWriterLockSlim cacheLock;
Dictionary <string, CacheItem> cache; Dictionary <string, CacheItem> cache;
CacheItemPriorityQueue timedItems; CacheItemPriorityQueue timedItems;
...@@ -153,19 +168,17 @@ namespace System.Web.Caching ...@@ -153,19 +168,17 @@ namespace System.Web.Caching
{ {
if (key == null) if (key == null)
throw new ArgumentNullException ("key"); throw new ArgumentNullException ("key");
bool locked = false;
try { try {
cacheLock.EnterWriteLock (); cacheLock.EnterWriteLock ();
locked = true;
CacheItem it = GetCacheItem (key); CacheItem it = GetCacheItem (key);
if (it != null) if (it != null)
return it.Value; return it.Value;
Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, false); Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, false);
} finally { } finally {
if (locked) // See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
} }
return null; return null;
...@@ -173,10 +186,8 @@ namespace System.Web.Caching ...@@ -173,10 +186,8 @@ namespace System.Web.Caching
public object Get (string key) public object Get (string key)
{ {
bool locked = false;
try { try {
cacheLock.EnterUpgradeableReadLock (); cacheLock.EnterUpgradeableReadLock ();
locked = true;
CacheItem it = GetCacheItem (key); CacheItem it = GetCacheItem (key);
if (it == null) if (it == null)
return null; return null;
...@@ -187,6 +198,7 @@ namespace System.Web.Caching ...@@ -187,6 +198,7 @@ namespace System.Web.Caching
if (!NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false)) if (!NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true); Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
} finally { } finally {
// See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
} }
...@@ -212,6 +224,7 @@ namespace System.Web.Caching ...@@ -212,6 +224,7 @@ namespace System.Web.Caching
if (!NeedsUpdate (it, CacheItemUpdateReason.Expired, false)) if (!NeedsUpdate (it, CacheItemUpdateReason.Expired, false))
Remove (key, CacheItemRemovedReason.Expired, false, true); Remove (key, CacheItemRemovedReason.Expired, false, true);
} finally { } finally {
// See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
} }
...@@ -221,9 +234,8 @@ namespace System.Web.Caching ...@@ -221,9 +234,8 @@ namespace System.Web.Caching
return it.Value; return it.Value;
} finally { } finally {
if (locked) { // See comment at the top of the file, above cacheLock declaration
cacheLock.ExitUpgradeableReadLock (); cacheLock.ExitUpgradeableReadLock ();
}
} }
} }
...@@ -282,20 +294,17 @@ namespace System.Web.Caching ...@@ -282,20 +294,17 @@ namespace System.Web.Caching
internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock) internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock)
{ {
CacheItem ci = null; CacheItem ci = null;
bool locked = false;
try { try {
if (doLock) { if (doLock)
cacheLock.EnterWriteLock (); cacheLock.EnterWriteLock ();
locked = true;
}
ci = GetCacheItem (key); ci = GetCacheItem (key);
if (ci != null) if (ci != null)
SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, key, false); SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, key, false);
} finally { } finally {
if (locked) { if (doLock) {
// See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
} }
} }
...@@ -317,12 +326,9 @@ namespace System.Web.Caching ...@@ -317,12 +326,9 @@ namespace System.Web.Caching
ci.OnRemoveCallback = onRemoveCallback; ci.OnRemoveCallback = onRemoveCallback;
ci.OnUpdateCallback = onUpdateCallback; ci.OnUpdateCallback = onUpdateCallback;
bool locked = false;
try { try {
if (doLock) { if (doLock)
cacheLock.EnterWriteLock (); cacheLock.EnterWriteLock ();
locked = true;
}
if (ci.Timer != null) { if (ci.Timer != null) {
ci.Timer.Dispose (); ci.Timer.Dispose ();
...@@ -336,7 +342,8 @@ namespace System.Web.Caching ...@@ -336,7 +342,8 @@ namespace System.Web.Caching
if (!disableExpiration && ci.AbsoluteExpiration != NoAbsoluteExpiration) if (!disableExpiration && ci.AbsoluteExpiration != NoAbsoluteExpiration)
EnqueueTimedItem (ci); EnqueueTimedItem (ci);
} finally { } finally {
if (locked) { if (doLock) {
// See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
} }
} }
...@@ -376,16 +383,14 @@ namespace System.Web.Caching ...@@ -376,16 +383,14 @@ namespace System.Web.Caching
object Remove (string key, CacheItemRemovedReason reason, bool doLock, bool invokeCallback) object Remove (string key, CacheItemRemovedReason reason, bool doLock, bool invokeCallback)
{ {
CacheItem it = null; CacheItem it = null;
bool locked = false;
try { try {
if (doLock) { if (doLock)
cacheLock.EnterWriteLock (); cacheLock.EnterWriteLock ();
locked = true;
}
it = RemoveCacheItem (key); it = RemoveCacheItem (key);
} finally { } finally {
if (locked) { if (doLock) {
// See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
} }
} }
...@@ -424,10 +429,8 @@ namespace System.Web.Caching ...@@ -424,10 +429,8 @@ namespace System.Web.Caching
internal void InvokePrivateCallbacks () internal void InvokePrivateCallbacks ()
{ {
CacheItemRemovedReason reason = CacheItemRemovedReason.Removed; CacheItemRemovedReason reason = CacheItemRemovedReason.Removed;
bool locked = false;
try { try {
cacheLock.EnterReadLock (); cacheLock.EnterReadLock ();
locked = true;
foreach (string key in cache.Keys) { foreach (string key in cache.Keys) {
CacheItem item = GetCacheItem (key); CacheItem item = GetCacheItem (key);
if (item.Disabled) if (item.Disabled)
...@@ -442,25 +445,21 @@ namespace System.Web.Caching ...@@ -442,25 +445,21 @@ namespace System.Web.Caching
} }
} }
} finally { } finally {
if (locked) { // See comment at the top of the file, above cacheLock declaration
cacheLock.ExitReadLock (); cacheLock.ExitReadLock ();
}
} }
} }
public IDictionaryEnumerator GetEnumerator () public IDictionaryEnumerator GetEnumerator ()
{ {
ArrayList list = new ArrayList (); ArrayList list = new ArrayList ();
bool locked = false;
try { try {
cacheLock.EnterReadLock (); cacheLock.EnterReadLock ();
locked = true;
foreach (CacheItem it in cache.Values) foreach (CacheItem it in cache.Values)
list.Add (it); list.Add (it);
} finally { } finally {
if (locked) { // See comment at the top of the file, above cacheLock declaration
cacheLock.ExitReadLock (); cacheLock.ExitReadLock ();
}
} }
return new CacheItemEnumerator (list); return new CacheItemEnumerator (list);
...@@ -478,13 +477,9 @@ namespace System.Web.Caching ...@@ -478,13 +477,9 @@ namespace System.Web.Caching
bool NeedsUpdate (CacheItem item, CacheItemUpdateReason reason, bool needLock) bool NeedsUpdate (CacheItem item, CacheItemUpdateReason reason, bool needLock)
{ {
bool locked = false;
try { try {
if (needLock) { if (needLock)
cacheLock.EnterWriteLock (); cacheLock.EnterWriteLock ();
locked = true;
}
if (item == null || item.OnUpdateCallback == null) if (item == null || item.OnUpdateCallback == null)
return false; return false;
...@@ -525,8 +520,10 @@ namespace System.Web.Caching ...@@ -525,8 +520,10 @@ namespace System.Web.Caching
} catch (Exception) { } catch (Exception) {
return false; return false;
} finally { } finally {
if (locked) if (needLock) {
// See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
}
} }
} }
...@@ -534,11 +531,9 @@ namespace System.Web.Caching ...@@ -534,11 +531,9 @@ namespace System.Web.Caching
{ {
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
CacheItem item = timedItems.Peek (); CacheItem item = timedItems.Peek ();
bool locked = false;
try { try {
cacheLock.EnterWriteLock (); cacheLock.EnterWriteLock ();
locked = true;
while (item != null) { while (item != null) {
if (!item.Disabled && item.ExpiresAt > now.Ticks) if (!item.Disabled && item.ExpiresAt > now.Ticks)
...@@ -554,8 +549,8 @@ namespace System.Web.Caching ...@@ -554,8 +549,8 @@ namespace System.Web.Caching
item = timedItems.Peek (); item = timedItems.Peek ();
} }
} finally { } finally {
if (locked) // See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
} }
if (item != null) { if (item != null) {
...@@ -574,10 +569,8 @@ namespace System.Web.Caching ...@@ -574,10 +569,8 @@ namespace System.Web.Caching
internal void CheckDependencies () internal void CheckDependencies ()
{ {
IList list; IList list;
bool locked = false;
try { try {
cacheLock.EnterWriteLock (); cacheLock.EnterWriteLock ();
locked = true;
list = new List <CacheItem> (); list = new List <CacheItem> ();
foreach (CacheItem it in cache.Values) foreach (CacheItem it in cache.Values)
list.Add (it); list.Add (it);
...@@ -587,18 +580,15 @@ namespace System.Web.Caching ...@@ -587,18 +580,15 @@ namespace System.Web.Caching
Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true); Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
} }
} finally { } finally {
if (locked) { // See comment at the top of the file, above cacheLock declaration
cacheLock.ExitWriteLock (); cacheLock.ExitWriteLock ();
}
} }
} }
internal DateTime GetKeyLastChange (string key) internal DateTime GetKeyLastChange (string key)
{ {
bool locked = false;
try { try {
cacheLock.EnterReadLock (); cacheLock.EnterReadLock ();
locked = true;
CacheItem it = GetCacheItem (key); CacheItem it = GetCacheItem (key);
if (it == null) if (it == null)
...@@ -606,9 +596,8 @@ namespace System.Web.Caching ...@@ -606,9 +596,8 @@ namespace System.Web.Caching
return it.LastChange; return it.LastChange;
} finally { } finally {
if (locked) { // See comment at the top of the file, above cacheLock declaration
cacheLock.ExitReadLock (); cacheLock.ExitReadLock ();
}
} }
} }
......
...@@ -41,6 +41,8 @@ namespace System.Web.Caching ...@@ -41,6 +41,8 @@ namespace System.Web.Caching
CacheItem[] heap; CacheItem[] heap;
int heapSize = 0; int heapSize = 0;
int heapCount = 0; int heapCount = 0;
// See comment for the cacheLock field at top of System.Web.Caching/Cache.cs
ReaderWriterLockSlim queueLock; ReaderWriterLockSlim queueLock;
public int Count { public int Count {
...@@ -94,20 +96,18 @@ namespace System.Web.Caching ...@@ -94,20 +96,18 @@ namespace System.Web.Caching
if (item == null) if (item == null)
return; return;
bool locked = false;
CacheItem[] heap; CacheItem[] heap;
try { try {
queueLock.EnterWriteLock (); queueLock.EnterWriteLock ();
locked = true;
heap = GetHeapWithGrow (); heap = GetHeapWithGrow ();
heap [heapCount++] = item; heap [heapCount++] = item;
BubbleUp (heap); BubbleUp (heap);
AddSequenceEntry (item, EDSequenceEntryType.Enqueue); AddSequenceEntry (item, EDSequenceEntryType.Enqueue);
} finally { } finally {
if (locked) // See comment at the top of the file, above queueLock declaration
queueLock.ExitWriteLock (); queueLock.ExitWriteLock ();
} }
} }
...@@ -115,12 +115,10 @@ namespace System.Web.Caching ...@@ -115,12 +115,10 @@ namespace System.Web.Caching
{ {
CacheItem ret = null; CacheItem ret = null;
CacheItem[] heap; CacheItem[] heap;
bool locked = false;
int index; int index;
try { try {
queueLock.EnterWriteLock (); queueLock.EnterWriteLock ();
locked = true;
heap = GetHeapWithShrink (); heap = GetHeapWithShrink ();
if (heap == null || heapCount == 0) if (heap == null || heapCount == 0)
return null; return null;
...@@ -136,19 +134,17 @@ namespace System.Web.Caching ...@@ -136,19 +134,17 @@ namespace System.Web.Caching
AddSequenceEntry (ret, EDSequenceEntryType.Dequeue); AddSequenceEntry (ret, EDSequenceEntryType.Dequeue);
return ret; return ret;
} finally { } finally {
if (locked) // See comment at the top of the file, above queueLock declaration
queueLock.ExitWriteLock (); queueLock.ExitWriteLock ();
} }
} }
public CacheItem Peek () public CacheItem Peek ()
{ {
bool locked = false;
CacheItem ret; CacheItem ret;
try { try {
queueLock.EnterReadLock (); queueLock.EnterReadLock ();
locked = true;
if (heap == null || heapCount == 0) if (heap == null || heapCount == 0)
return null; return null;
...@@ -157,8 +153,8 @@ namespace System.Web.Caching ...@@ -157,8 +153,8 @@ namespace System.Web.Caching
return ret; return ret;
} finally { } finally {
if (locked) // See comment at the top of the file, above queueLock declaration
queueLock.ExitReadLock (); queueLock.ExitReadLock ();
} }
} }
......
...@@ -94,6 +94,7 @@ namespace System.Web.Compilation ...@@ -94,6 +94,7 @@ namespace System.Web.Compilation
// This is here _only_ for the purpose of unit tests! // This is here _only_ for the purpose of unit tests!
internal static bool suppressDebugModeMessages; internal static bool suppressDebugModeMessages;
// See comment for the cacheLock field at top of System.Web.Caching/Cache.cs
#if SYSTEMCORE_DEP #if SYSTEMCORE_DEP
static ReaderWriterLockSlim buildCacheLock; static ReaderWriterLockSlim buildCacheLock;
#else #else
...@@ -817,14 +818,12 @@ namespace System.Web.Compilation ...@@ -817,14 +818,12 @@ namespace System.Web.Compilation
// to the assembly builder and, in effect, no assembly is produced but there are STILL types that need // to the assembly builder and, in effect, no assembly is produced but there are STILL types that need
// to be added to the cache. // to be added to the cache.
Assembly compiledAssembly = results != null ? results.CompiledAssembly : null; Assembly compiledAssembly = results != null ? results.CompiledAssembly : null;
bool locked = false;
try { try {
#if SYSTEMCORE_DEP #if SYSTEMCORE_DEP
buildCacheLock.EnterWriteLock (); buildCacheLock.EnterWriteLock ();
#else #else
buildCacheLock.AcquireWriterLock (-1); buildCacheLock.AcquireWriterLock (-1);
#endif #endif
locked = true;
if (compiledAssembly != null) if (compiledAssembly != null)
referencedAssemblies.Add (compiledAssembly); referencedAssemblies.Add (compiledAssembly);
...@@ -835,13 +834,11 @@ namespace System.Web.Compilation ...@@ -835,13 +834,11 @@ namespace System.Web.Compilation
StoreInCache (bp, compiledAssembly, results); StoreInCache (bp, compiledAssembly, results);
} }
} finally { } finally {
if (locked) {
#if SYSTEMCORE_DEP #if SYSTEMCORE_DEP
buildCacheLock.ExitWriteLock (); buildCacheLock.ExitWriteLock ();
#else #else
buildCacheLock.ReleaseWriterLock (); buildCacheLock.ReleaseWriterLock ();
#endif #endif
}
} }
} }
...@@ -883,24 +880,19 @@ namespace System.Web.Compilation ...@@ -883,24 +880,19 @@ namespace System.Web.Compilation
#endif #endif
static BuildManagerCacheItem GetCachedItem (string vp) static BuildManagerCacheItem GetCachedItem (string vp)
{ {
bool locked = false;
try { try {
#if SYSTEMCORE_DEP #if SYSTEMCORE_DEP
buildCacheLock.EnterReadLock (); buildCacheLock.EnterReadLock ();
#else #else
buildCacheLock.AcquireReaderLock (-1); buildCacheLock.AcquireReaderLock (-1);
#endif #endif
locked = true;
return GetCachedItemNoLock (vp); return GetCachedItemNoLock (vp);
} finally { } finally {
if (locked) {
#if SYSTEMCORE_DEP #if SYSTEMCORE_DEP
buildCacheLock.ExitReadLock (); buildCacheLock.ExitReadLock ();
#else #else
buildCacheLock.ReleaseReaderLock (); buildCacheLock.ReleaseReaderLock ();
#endif #endif
}
} }
} }
...@@ -1290,27 +1282,22 @@ namespace System.Web.Compilation ...@@ -1290,27 +1282,22 @@ namespace System.Web.Compilation
else else
return; return;
bool locked = false;
try { try {
#if SYSTEMCORE_DEP #if SYSTEMCORE_DEP
buildCacheLock.EnterWriteLock (); buildCacheLock.EnterWriteLock ();
#else #else
buildCacheLock.AcquireWriterLock (-1); buildCacheLock.AcquireWriterLock (-1);
#endif #endif
locked = true;
if (HasCachedItemNoLock (virtualPath)) { if (HasCachedItemNoLock (virtualPath)) {
buildCache [virtualPath] = null; buildCache [virtualPath] = null;
OnEntryRemoved (virtualPath); OnEntryRemoved (virtualPath);
} }
} finally { } finally {
if (locked) {
#if SYSTEMCORE_DEP #if SYSTEMCORE_DEP
buildCacheLock.ExitWriteLock (); buildCacheLock.ExitWriteLock ();
#else #else
buildCacheLock.ReleaseWriterLock (); buildCacheLock.ReleaseWriterLock ();
#endif #endif
}
} }
} }
......
...@@ -67,6 +67,8 @@ namespace System.Web.Configuration { ...@@ -67,6 +67,8 @@ namespace System.Web.Configuration {
static readonly char[] pathTrimChars = { '/' }; static readonly char[] pathTrimChars = { '/' };
static readonly object suppressAppReloadLock = new object (); static readonly object suppressAppReloadLock = new object ();
static readonly object saveLocationsCacheLock = new object (); static readonly object saveLocationsCacheLock = new object ();
// See comment for the cacheLock field at top of System.Web.Caching/Cache.cs
static readonly ReaderWriterLockSlim sectionCacheLock; static readonly ReaderWriterLockSlim sectionCacheLock;
#if !TARGET_J2EE #if !TARGET_J2EE
...@@ -225,14 +227,11 @@ namespace System.Web.Configuration { ...@@ -225,14 +227,11 @@ namespace System.Web.Configuration {
static void ConfigurationSaveHandler (_Configuration sender, ConfigurationSaveEventArgs args) static void ConfigurationSaveHandler (_Configuration sender, ConfigurationSaveEventArgs args)
{ {
bool locked = false;
try { try {
sectionCacheLock.EnterWriteLock (); sectionCacheLock.EnterWriteLock ();
locked = true;
sectionCache.Clear (); sectionCache.Clear ();
} finally { } finally {
if (locked) sectionCacheLock.ExitWriteLock ();
sectionCacheLock.ExitWriteLock ();
} }
lock (suppressAppReloadLock) { lock (suppressAppReloadLock) {
...@@ -442,7 +441,6 @@ namespace System.Web.Configuration { ...@@ -442,7 +441,6 @@ namespace System.Web.Configuration {
int cacheKey; int cacheKey;
bool pathPresent = !String.IsNullOrEmpty (path); bool pathPresent = !String.IsNullOrEmpty (path);
string locationPath = null; string locationPath = null;
bool locked = false;
if (pathPresent) if (pathPresent)
locationPath = "location_" + path; locationPath = "location_" + path;
...@@ -453,7 +451,6 @@ namespace System.Web.Configuration { ...@@ -453,7 +451,6 @@ namespace System.Web.Configuration {
try { try {
sectionCacheLock.EnterReadLock (); sectionCacheLock.EnterReadLock ();
locked = true;
object o; object o;
if (pathPresent) { if (pathPresent) {
...@@ -469,8 +466,7 @@ namespace System.Web.Configuration { ...@@ -469,8 +466,7 @@ namespace System.Web.Configuration {
if (sectionCache.TryGetValue (baseCacheKey, out o)) if (sectionCache.TryGetValue (baseCacheKey, out o))
return o; return o;
} finally { } finally {
if (locked) sectionCacheLock.ExitReadLock ();
sectionCacheLock.ExitReadLock ();
} }
string cachePath = null; string cachePath = null;
...@@ -687,27 +683,21 @@ namespace System.Web.Configuration { ...@@ -687,27 +683,21 @@ namespace System.Web.Configuration {
static void AddSectionToCache (int key, object section) static void AddSectionToCache (int key, object section)
{ {
object cachedSection; object cachedSection;
bool locked = false;
try { try {
sectionCacheLock.EnterUpgradeableReadLock (); sectionCacheLock.EnterUpgradeableReadLock ();
locked = true;
if (sectionCache.TryGetValue (key, out cachedSection) && cachedSection != null) if (sectionCache.TryGetValue (key, out cachedSection) && cachedSection != null)
return; return;
bool innerLocked = false;
try { try {
sectionCacheLock.EnterWriteLock (); sectionCacheLock.EnterWriteLock ();
innerLocked = true;
sectionCache.Add (key, section); sectionCache.Add (key, section);
} finally { } finally {
if (innerLocked) sectionCacheLock.ExitWriteLock ();
sectionCacheLock.ExitWriteLock ();
} }
} finally { } finally {
if (locked) sectionCacheLock.ExitUpgradeableReadLock ();
sectionCacheLock.ExitUpgradeableReadLock ();
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册