File: system\runtime\interopservices\gchandlecookietable.cs
Project: ndp\clr\src\bcl\mscorlib.csproj (mscorlib)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
#if MDA_SUPPORTED
 
namespace System.Runtime.InteropServices
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
 
    using ObjectHandle = IntPtr;
    using GCHandleCookie = IntPtr;
 
    // Internal class used to map a GCHandle to an IntPtr. Instead of handing out the underlying CLR
    // handle, we now hand out a cookie that can later be converted back to the CLR handle it 
    // is associated with.
 
    // NOTE:
    // this implementation uses a single lock between FindOrAddHandle and RemoveHandleIfPresent which
    // could create some scalability issues when this MDA is turned on.  if this is affecting perf
    // then additional tuning work will be required.
 
    internal class GCHandleCookieTable
    {
        private const int InitialHandleCount = 10;
        private const int MaxListSize = 0xFFFFFF;
        private const uint CookieMaskIndex = 0x00FFFFFF;
        private const uint CookieMaskSentinal = 0xFF000000;
 
        internal GCHandleCookieTable()
        {
            m_HandleList = new ObjectHandle[InitialHandleCount];
            m_CycleCounts = new byte[InitialHandleCount];
            m_HandleToCookieMap = new Dictionary<ObjectHandle, GCHandleCookie>(InitialHandleCount);
            m_syncObject = new object();
 
            for (int i = 0; i < InitialHandleCount; i++)
            {
                m_HandleList[i] = ObjectHandle.Zero;
                m_CycleCounts[i] = 0;
            }
        }
 
        // Retrieve a cookie for the passed in handle. If no cookie has yet been allocated for 
        // this handle, one will be created. This method is thread safe.
        internal GCHandleCookie FindOrAddHandle(ObjectHandle handle)
        {
            // Don't accept a null object handle
            if (handle == ObjectHandle.Zero)
                return GCHandleCookie.Zero;
 
            GCHandleCookie cookie = GCHandleCookie.Zero;
 
            lock (m_syncObject)
            {
                // First see if we already have a cookie for this handle.
                if (m_HandleToCookieMap.ContainsKey(handle))
                    return m_HandleToCookieMap[handle];
 
                if ((m_FreeIndex < m_HandleList.Length) && (Volatile.Read(ref m_HandleList[m_FreeIndex]) == ObjectHandle.Zero))
                {
                    Volatile.Write(ref m_HandleList[m_FreeIndex],  handle);
                    cookie = GetCookieFromData((uint)m_FreeIndex, m_CycleCounts[m_FreeIndex]);
 
                    // Set our next guess just one higher as this index is now in use.
                    // it's ok if this sets m_FreeIndex > m_HandleList.Length as this condition is
                    // checked at the beginning of the if statement.
                    ++m_FreeIndex;
                }
                else
                {
                    for (m_FreeIndex = 0; m_FreeIndex < MaxListSize; ++m_FreeIndex)
                    {
                        if (m_HandleList[m_FreeIndex] == ObjectHandle.Zero)
                        {
                            Volatile.Write(ref m_HandleList[m_FreeIndex], handle);
                            cookie = GetCookieFromData((uint)m_FreeIndex, m_CycleCounts[m_FreeIndex]);
 
                            // this will be our next guess for a free index.
                            // it's ok if this sets m_FreeIndex > m_HandleList.Length
                            // since we check for this condition in the if statement.
                            ++m_FreeIndex;
                            break;
                        }
 
                        if (m_FreeIndex + 1 == m_HandleList.Length)
                            GrowArrays();
                    }
                }
 
                if (cookie == GCHandleCookie.Zero)
                    throw new OutOfMemoryException(Environment.GetResourceString("OutOfMemory_GCHandleMDA"));
 
                // This handle hasn't been added to the map yet so add it.
                m_HandleToCookieMap.Add(handle, cookie);
            }
 
            return cookie;
        }
 
        // Get a handle.
        internal ObjectHandle GetHandle(GCHandleCookie cookie)
        {
            ObjectHandle oh = ObjectHandle.Zero;
 
            if (!ValidateCookie(cookie))
                return ObjectHandle.Zero;
 
            oh = Volatile.Read(ref m_HandleList[GetIndexFromCookie(cookie)]);
 
            return oh;
        }
 
        // Remove the handle from the cookie table if it is present. 
        //
        internal void RemoveHandleIfPresent(ObjectHandle handle)
        {
            if (handle == ObjectHandle.Zero)
                return;
 
            lock (m_syncObject)
            {
                if (m_HandleToCookieMap.ContainsKey(handle))
                {
                    GCHandleCookie cookie = m_HandleToCookieMap[handle];
 
                    // Remove it from the array first
                    if (!ValidateCookie(cookie))
                        return;
 
                    int index = GetIndexFromCookie(cookie);
 
                    m_CycleCounts[index]++;
                    Volatile.Write(ref m_HandleList[index], ObjectHandle.Zero);
 
                    // Remove it from the hashtable last
                    m_HandleToCookieMap.Remove(handle);
 
                    // Update our guess
                    m_FreeIndex = index;
                }
            }
        }
 
        private bool ValidateCookie(GCHandleCookie cookie)
        {
            int index;
            byte xorData;
 
            GetDataFromCookie(cookie, out index, out xorData);
 
            // Validate the index
            if (index >= MaxListSize)
                return false;
 
            if (index >= m_HandleList.Length)
                return false;
 
            if (Volatile.Read(ref m_HandleList[index]) == ObjectHandle.Zero)
                return false;
 
            // Validate the xorData byte (this contains the cycle count and appdomain id).
            byte ADID = (byte)(AppDomain.CurrentDomain.Id % 0xFF);
            byte goodData = (byte)(Volatile.Read(ref m_CycleCounts[index]) ^ ADID);
            if (xorData != goodData)
                return false;
 
            return true;
        }
 
        // Double the size of our arrays - must be called with the lock taken.
        private void GrowArrays()
        {
            int CurrLength = m_HandleList.Length;
 
            ObjectHandle[] newHandleList = new ObjectHandle[CurrLength * 2];
            byte[] newCycleCounts = new byte[CurrLength * 2];
 
            Array.Copy(m_HandleList, newHandleList, CurrLength);
            Array.Copy(m_CycleCounts, newCycleCounts, CurrLength);
 
            m_HandleList = newHandleList;
            m_CycleCounts = newCycleCounts;
        }
 
        // Generate a cookie based on the index, cyclecount, and current domain id.
        private GCHandleCookie GetCookieFromData(uint index, byte cycleCount)
        {
            byte ADID = (byte)(AppDomain.CurrentDomain.Id % 0xFF);
            return (GCHandleCookie)(((cycleCount ^ ADID) << 24) + index + 1);
        }
 
        // Break down the cookie into its parts
        private void GetDataFromCookie(GCHandleCookie cookie, out int index, out byte xorData)
        {
            uint intCookie = (uint)cookie;
            index = (int)(intCookie & CookieMaskIndex) - 1;
            xorData = (byte)((intCookie & CookieMaskSentinal) >> 24);
        }
 
        // Just get the index from the cookie
        private int GetIndexFromCookie(GCHandleCookie cookie)
        {
            uint intCookie = (uint)cookie;
            return (int)(intCookie & CookieMaskIndex) - 1;
        }
 
        private Dictionary<ObjectHandle, GCHandleCookie> m_HandleToCookieMap;
        private volatile ObjectHandle[] m_HandleList;
        private volatile byte[] m_CycleCounts;
        private int m_FreeIndex;
        private readonly object m_syncObject;
    }
}
 
#endif