//------------------------------------------------------------------------------
// <copyright file="CompilationLock.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------

//#define MUTEXINSTRUMENTATION

namespace System.Web.Compilation {

using System;
using System.Threading;
using System.Globalization;
using System.Security.Principal;
using System.Web.Util;
using System.Web.Configuration;
using System.Runtime.InteropServices;
using System.Web.Management;
using System.Runtime.Versioning;
using System.Diagnostics;
using Debug = System.Web.Util.Debug;

internal sealed class CompilationMutex : IDisposable {

    private String  _name;
    private String  _comment;
#if MUTEXINSTRUMENTATION
    // Used to keep track of the stack when the mutex is obtained
    private string _stackTrace;
#endif

    // ROTORTODO: replace unmanaged aspnet_isapi mutex with managed implementation
#if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis
    private HandleRef   _mutexHandle;

    // Lock Status is used to drain out all worker threads out of Mutex ownership on
    // app domain shutdown: -1 locked for good, 0 unlocked, N locked by a worker thread(s)
    private int     _lockStatus;  
    private bool    _draining = false;
#endif // !FEATURE_PAL

    internal CompilationMutex(String name, String comment) {

#if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis

        // Attempt to get the mutex string from the registry (VSWhidbey 415795)
        string mutexRandomName = (string) Misc.GetAspNetRegValue("CompilationMutexName",
            null /*valueName*/, null /*defaultValue*/);

        if (mutexRandomName != null) {
            // If we were able to use the registry value, use it.  Also, we need to prepend "Global\"
            // to the mutex name, to make sure it can be shared between a terminal server session
            // and IIS (VSWhidbey 307523).
            _name += @"Global\" + name + "-" + mutexRandomName;
        }
        else {
            // If we couldn't get the reg value, don't use it, and prepend "Local\" to the mutex
            // name to make it local to the session (and hence prevent hijacking)
            _name += @"Local\" + name;
        }

        _comment = comment;

        Debug.Trace("Mutex", "Creating Mutex " + MutexDebugName);

        _mutexHandle = new HandleRef(this, UnsafeNativeMethods.InstrumentedMutexCreate(_name));

        if (_mutexHandle.Handle == IntPtr.Zero) {
            Debug.Trace("Mutex", "Failed to create Mutex " + MutexDebugName);

            throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Create));
        }

        Debug.Trace("Mutex", "Successfully created Mutex " + MutexDebugName);
#endif // !FEATURE_PAL
    }

    ~CompilationMutex() {
        Close();
    }

    void IDisposable.Dispose() {
        Close();
        System.GC.SuppressFinalize(this);
    }

    internal /*public*/ void Close() {

#if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis

        if (_mutexHandle.Handle != IntPtr.Zero) {
            UnsafeNativeMethods.InstrumentedMutexDelete(_mutexHandle);
            _mutexHandle = new HandleRef(this, IntPtr.Zero);
        }
#endif // !FEATURE_PAL
    }

    [ResourceExposure(ResourceScope.None)]
    internal /*public*/ void WaitOne() {

#if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis

        if (_mutexHandle.Handle == IntPtr.Zero)
            throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Null));

        // check the lock status
        for (;;) {
            int lockStatus = _lockStatus;

            if (lockStatus == -1 || _draining)
                throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Drained));

            if (Interlocked.CompareExchange(ref _lockStatus, lockStatus+1, lockStatus) == lockStatus)
                break; // got the lock
        }

        Debug.Trace("Mutex", "Waiting for mutex " + MutexDebugName);

        if (UnsafeNativeMethods.InstrumentedMutexGetLock(_mutexHandle, -1) == -1) {
            // failed to get the lock
            Interlocked.Decrement(ref _lockStatus);
            throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Failed));
        }

#if MUTEXINSTRUMENTATION
        // Remember the stack trace for debugging purpose
        _stackTrace = (new StackTrace()).ToString();
#endif

        Debug.Trace("Mutex", "Got mutex " + MutexDebugName);
#endif // !FEATURE_PAL
    }

    internal /*public*/ void ReleaseMutex() {

#if !FEATURE_PAL // No unmanaged aspnet_isapi mutex in Coriolis
        if (_mutexHandle.Handle == IntPtr.Zero)
            throw new InvalidOperationException(SR.GetString(SR.CompilationMutex_Null));

        Debug.Trace("Mutex", "Releasing mutex " + MutexDebugName);

#if MUTEXINSTRUMENTATION
        // Clear out the stack trace
        _stackTrace = null;
#endif

        if (UnsafeNativeMethods.InstrumentedMutexReleaseLock(_mutexHandle) != 0)
            Interlocked.Decrement(ref _lockStatus);
#endif // !FEATURE_PAL
    }


    private String MutexDebugName {
        get {
#if DBG
            return (_comment != null) ? _name + " (" + _comment + ")" : _name;
#else
            return _name;
#endif
        }
    }
}

internal static class CompilationLock {

    private static CompilationMutex _mutex;

    static CompilationLock() {

        // Create the mutex (or just get it if another process created it).
        // Make the mutex unique per application
        int hashCode = StringUtil.GetNonRandomizedHashCode("CompilationLock" + HttpRuntime.AppDomainAppId.ToLower(CultureInfo.InvariantCulture));


        _mutex = new CompilationMutex(
                        "CL" + hashCode.ToString("x", CultureInfo.InvariantCulture), 
                        "CompilationLock for " + HttpRuntime.AppDomainAppVirtualPath);
    }

    internal static void GetLock(ref bool gotLock) {

        // The idea of this try/finally is to make sure that the statements are always
        // executed together (VSWhidbey 319154)
        // This code should be using a constrained execution region.
        try {
        }
        finally {
            // Always take the BuildManager lock *before* taking the mutex, to avoid possible
            // deadlock situations (VSWhidbey 530732)
#pragma warning disable 0618
            //@TODO: This overload of Monitor.Enter is obsolete.  Please change this to use Monitor.Enter(ref bool), and remove the pragmas   -- [....]
            Monitor.Enter(BuildManager.TheBuildManager);
#pragma warning restore 0618
            _mutex.WaitOne();
            gotLock = true;
        }
    }

    internal static void ReleaseLock() {
        _mutex.ReleaseMutex();
        Monitor.Exit(BuildManager.TheBuildManager);
    }

}

}
