3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 namespace System.Runtime.InteropServices
11 using System.Collections.Generic;
12 using System.Threading;
14 using ObjectHandle = IntPtr;
15 using GCHandleCookie = IntPtr;
17 // Internal class used to map a GCHandle to an IntPtr. Instead of handing out the underlying CLR
18 // handle, we now hand out a cookie that can later be converted back to the CLR handle it
19 // is associated with.
22 // this implementation uses a single lock between FindOrAddHandle and RemoveHandleIfPresent which
23 // could create some scalability issues when this MDA is turned on. if this is affecting perf
24 // then additional tuning work will be required.
26 internal class GCHandleCookieTable
28 private const int InitialHandleCount = 10;
29 private const int MaxListSize = 0xFFFFFF;
30 private const uint CookieMaskIndex = 0x00FFFFFF;
31 private const uint CookieMaskSentinal = 0xFF000000;
33 internal GCHandleCookieTable()
35 m_HandleList = new ObjectHandle[InitialHandleCount];
36 m_CycleCounts = new byte[InitialHandleCount];
37 m_HandleToCookieMap = new Dictionary<ObjectHandle, GCHandleCookie>(InitialHandleCount);
38 m_syncObject = new object();
40 for (int i = 0; i < InitialHandleCount; i++)
42 m_HandleList[i] = ObjectHandle.Zero;
47 // Retrieve a cookie for the passed in handle. If no cookie has yet been allocated for
48 // this handle, one will be created. This method is thread safe.
49 internal GCHandleCookie FindOrAddHandle(ObjectHandle handle)
51 // Don't accept a null object handle
52 if (handle == ObjectHandle.Zero)
53 return GCHandleCookie.Zero;
55 GCHandleCookie cookie = GCHandleCookie.Zero;
59 // First see if we already have a cookie for this handle.
60 if (m_HandleToCookieMap.ContainsKey(handle))
61 return m_HandleToCookieMap[handle];
63 if ((m_FreeIndex < m_HandleList.Length) && (Volatile.Read(ref m_HandleList[m_FreeIndex]) == ObjectHandle.Zero))
65 Volatile.Write(ref m_HandleList[m_FreeIndex], handle);
66 cookie = GetCookieFromData((uint)m_FreeIndex, m_CycleCounts[m_FreeIndex]);
68 // Set our next guess just one higher as this index is now in use.
69 // it's ok if this sets m_FreeIndex > m_HandleList.Length as this condition is
70 // checked at the beginning of the if statement.
75 for (m_FreeIndex = 0; m_FreeIndex < MaxListSize; ++m_FreeIndex)
77 if (m_HandleList[m_FreeIndex] == ObjectHandle.Zero)
79 Volatile.Write(ref m_HandleList[m_FreeIndex], handle);
80 cookie = GetCookieFromData((uint)m_FreeIndex, m_CycleCounts[m_FreeIndex]);
82 // this will be our next guess for a free index.
83 // it's ok if this sets m_FreeIndex > m_HandleList.Length
84 // since we check for this condition in the if statement.
89 if (m_FreeIndex + 1 == m_HandleList.Length)
94 if (cookie == GCHandleCookie.Zero)
95 throw new OutOfMemoryException(Environment.GetResourceString("OutOfMemory_GCHandleMDA"));
97 // This handle hasn't been added to the map yet so add it.
98 m_HandleToCookieMap.Add(handle, cookie);
105 internal ObjectHandle GetHandle(GCHandleCookie cookie)
107 ObjectHandle oh = ObjectHandle.Zero;
109 if (!ValidateCookie(cookie))
110 return ObjectHandle.Zero;
112 oh = Volatile.Read(ref m_HandleList[GetIndexFromCookie(cookie)]);
117 // Remove the handle from the cookie table if it is present.
119 internal void RemoveHandleIfPresent(ObjectHandle handle)
121 if (handle == ObjectHandle.Zero)
126 if (m_HandleToCookieMap.ContainsKey(handle))
128 GCHandleCookie cookie = m_HandleToCookieMap[handle];
130 // Remove it from the array first
131 if (!ValidateCookie(cookie))
134 int index = GetIndexFromCookie(cookie);
136 m_CycleCounts[index]++;
137 Volatile.Write(ref m_HandleList[index], ObjectHandle.Zero);
139 // Remove it from the hashtable last
140 m_HandleToCookieMap.Remove(handle);
148 private bool ValidateCookie(GCHandleCookie cookie)
153 GetDataFromCookie(cookie, out index, out xorData);
155 // Validate the index
156 if (index >= MaxListSize)
159 if (index >= m_HandleList.Length)
162 if (Volatile.Read(ref m_HandleList[index]) == ObjectHandle.Zero)
165 // Validate the xorData byte (this contains the cycle count and appdomain id).
166 byte ADID = (byte)(AppDomain.CurrentDomain.Id % 0xFF);
167 byte goodData = (byte)(Volatile.Read(ref m_CycleCounts[index]) ^ ADID);
168 if (xorData != goodData)
174 // Double the size of our arrays - must be called with the lock taken.
175 private void GrowArrays()
177 int CurrLength = m_HandleList.Length;
179 ObjectHandle[] newHandleList = new ObjectHandle[CurrLength * 2];
180 byte[] newCycleCounts = new byte[CurrLength * 2];
182 Array.Copy(m_HandleList, newHandleList, CurrLength);
183 Array.Copy(m_CycleCounts, newCycleCounts, CurrLength);
185 m_HandleList = newHandleList;
186 m_CycleCounts = newCycleCounts;
189 // Generate a cookie based on the index, cyclecount, and current domain id.
190 private GCHandleCookie GetCookieFromData(uint index, byte cycleCount)
192 byte ADID = (byte)(AppDomain.CurrentDomain.Id % 0xFF);
193 return (GCHandleCookie)(((cycleCount ^ ADID) << 24) + index + 1);
196 // Break down the cookie into its parts
197 private void GetDataFromCookie(GCHandleCookie cookie, out int index, out byte xorData)
199 uint intCookie = (uint)cookie;
200 index = (int)(intCookie & CookieMaskIndex) - 1;
201 xorData = (byte)((intCookie & CookieMaskSentinal) >> 24);
204 // Just get the index from the cookie
205 private int GetIndexFromCookie(GCHandleCookie cookie)
207 uint intCookie = (uint)cookie;
208 return (int)(intCookie & CookieMaskIndex) - 1;
211 private Dictionary<ObjectHandle, GCHandleCookie> m_HandleToCookieMap;
212 private volatile ObjectHandle[] m_HandleList;
213 private volatile byte[] m_CycleCounts;
214 private int m_FreeIndex;
215 private readonly object m_syncObject;