1 //------------------------------------------------------------------------------
2 // <copyright file="CompilationLock.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 //#define MUTEXINSTRUMENTATION
9 namespace System.Web.Compilation {
12 using System.Threading;
13 using System.Globalization;
14 using System.Security.Principal;
15 using System.Web.Util;
16 using System.Web.Configuration;
17 using System.Runtime.InteropServices;
18 using System.Web.Management;
19 using System.Runtime.Versioning;
20 using System.Diagnostics;
21 using Debug = System.Web.Util.Debug;
23 internal sealed class CompilationMutex : IDisposable {
26 private String _comment;
27 #if MUTEXINSTRUMENTATION
28 // Used to keep track of the stack when the mutex is obtained
29 private string _stackTrace;
32 // ROTORTODO: replace unmanaged aspnet_isapi mutex with managed implementation
33 #if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis
34 private HandleRef _mutexHandle;
36 // Lock Status is used to drain out all worker threads out of Mutex ownership on
37 // app domain shutdown: -1 locked for good, 0 unlocked, N locked by a worker thread(s)
38 private int _lockStatus;
39 private bool _draining = false;
40 #endif // !FEATURE_PAL
42 internal CompilationMutex(String name, String comment) {
44 #if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis
46 // Attempt to get the mutex string from the registry (VSWhidbey 415795)
47 string mutexRandomName = (string) Misc.GetAspNetRegValue("CompilationMutexName",
48 null /*valueName*/, null /*defaultValue*/);
50 if (mutexRandomName != null) {
51 // If we were able to use the registry value, use it. Also, we need to prepend "Global\"
52 // to the mutex name, to make sure it can be shared between a terminal server session
53 // and IIS (VSWhidbey 307523).
54 _name += @"Global\" + name + "-" + mutexRandomName;
57 // If we couldn't get the reg value, don't use it, and prepend "Local\" to the mutex
58 // name to make it local to the session (and hence prevent hijacking)
59 _name += @"Local\" + name;
64 Debug.Trace("Mutex", "Creating Mutex " + MutexDebugName);
66 _mutexHandle = new HandleRef(this, UnsafeNativeMethods.InstrumentedMutexCreate(_name));
68 if (_mutexHandle.Handle == IntPtr.Zero) {
69 Debug.Trace("Mutex", "Failed to create Mutex " + MutexDebugName);
71 throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Create));
74 Debug.Trace("Mutex", "Successfully created Mutex " + MutexDebugName);
75 #endif // !FEATURE_PAL
82 void IDisposable.Dispose() {
84 System.GC.SuppressFinalize(this);
87 internal /*public*/ void Close() {
89 #if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis
91 if (_mutexHandle.Handle != IntPtr.Zero) {
92 UnsafeNativeMethods.InstrumentedMutexDelete(_mutexHandle);
93 _mutexHandle = new HandleRef(this, IntPtr.Zero);
95 #endif // !FEATURE_PAL
98 [ResourceExposure(ResourceScope.None)]
99 internal /*public*/ void WaitOne() {
101 #if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis
103 if (_mutexHandle.Handle == IntPtr.Zero)
104 throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Null));
106 // check the lock status
108 int lockStatus = _lockStatus;
110 if (lockStatus == -1 || _draining)
111 throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Drained));
113 if (Interlocked.CompareExchange(ref _lockStatus, lockStatus+1, lockStatus) == lockStatus)
114 break; // got the lock
117 Debug.Trace("Mutex", "Waiting for mutex " + MutexDebugName);
119 if (UnsafeNativeMethods.InstrumentedMutexGetLock(_mutexHandle, -1) == -1) {
120 // failed to get the lock
121 Interlocked.Decrement(ref _lockStatus);
122 throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Failed));
125 #if MUTEXINSTRUMENTATION
126 // Remember the stack trace for debugging purpose
127 _stackTrace = (new StackTrace()).ToString();
130 Debug.Trace("Mutex", "Got mutex " + MutexDebugName);
131 #endif // !FEATURE_PAL
134 internal /*public*/ void ReleaseMutex() {
136 #if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis
137 if (_mutexHandle.Handle == IntPtr.Zero)
138 throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Null));
140 Debug.Trace("Mutex", "Releasing mutex " + MutexDebugName);
142 #if MUTEXINSTRUMENTATION
143 // Clear out the stack trace
147 if (UnsafeNativeMethods.InstrumentedMutexReleaseLock(_mutexHandle) != 0)
148 Interlocked.Decrement(ref _lockStatus);
149 #endif // !FEATURE_PAL
153 private String MutexDebugName {
156 return (_comment != null) ? _name + " (" + _comment + ")" : _name;
164 internal static class CompilationLock {
166 private static CompilationMutex _mutex;
168 static CompilationLock() {
170 // Create the mutex (or just get it if another process created it).
171 // Make the mutex unique per application
172 int hashCode = StringUtil.GetNonRandomizedHashCode("CompilationLock" + HttpRuntime.AppDomainAppId.ToLower(CultureInfo.InvariantCulture));
175 _mutex = new CompilationMutex(
176 "CL" + hashCode.ToString("x", CultureInfo.InvariantCulture),
177 "CompilationLock for " + HttpRuntime.AppDomainAppVirtualPath);
180 internal static void GetLock(ref bool gotLock) {
182 // The idea of this try/finally is to make sure that the statements are always
183 // executed together (VSWhidbey 319154)
184 // This code should be using a constrained execution region.
188 // Always take the BuildManager lock *before* taking the mutex, to avoid possible
189 // deadlock situations (VSWhidbey 530732)
190 #pragma warning disable 0618
191 //@TODO: This overload of Monitor.Enter is obsolete. Please change this to use Monitor.Enter(ref bool), and remove the pragmas -- Microsoft
192 Monitor.Enter(BuildManager.TheBuildManager);
193 #pragma warning restore 0618
199 internal static void ReleaseLock() {
200 _mutex.ReleaseMutex();
201 Monitor.Exit(BuildManager.TheBuildManager);