Merge pull request #2955 from ludovic-henry/mono_msec_ticks-overflow
[mono.git] / mcs / class / referencesource / mscorlib / system / runtime / interopservices / gchandlecookietable.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 #if MDA_SUPPORTED
7
8 namespace System.Runtime.InteropServices
9 {
10     using System;
11     using System.Collections.Generic;
12     using System.Threading;
13
14     using ObjectHandle = IntPtr;
15     using GCHandleCookie = IntPtr;
16
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.
20
21     // NOTE:
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.
25
26     internal class GCHandleCookieTable
27     {
28         private const int InitialHandleCount = 10;
29         private const int MaxListSize = 0xFFFFFF;
30         private const uint CookieMaskIndex = 0x00FFFFFF;
31         private const uint CookieMaskSentinal = 0xFF000000;
32
33         internal GCHandleCookieTable()
34         {
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();
39
40             for (int i = 0; i < InitialHandleCount; i++)
41             {
42                 m_HandleList[i] = ObjectHandle.Zero;
43                 m_CycleCounts[i] = 0;
44             }
45         }
46
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)
50         {
51             // Don't accept a null object handle
52             if (handle == ObjectHandle.Zero)
53                 return GCHandleCookie.Zero;
54
55             GCHandleCookie cookie = GCHandleCookie.Zero;
56
57             lock (m_syncObject)
58             {
59                 // First see if we already have a cookie for this handle.
60                 if (m_HandleToCookieMap.ContainsKey(handle))
61                     return m_HandleToCookieMap[handle];
62
63                 if ((m_FreeIndex < m_HandleList.Length) && (Volatile.Read(ref m_HandleList[m_FreeIndex]) == ObjectHandle.Zero))
64                 {
65                     Volatile.Write(ref m_HandleList[m_FreeIndex],  handle);
66                     cookie = GetCookieFromData((uint)m_FreeIndex, m_CycleCounts[m_FreeIndex]);
67
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.
71                     ++m_FreeIndex;
72                 }
73                 else
74                 {
75                     for (m_FreeIndex = 0; m_FreeIndex < MaxListSize; ++m_FreeIndex)
76                     {
77                         if (m_HandleList[m_FreeIndex] == ObjectHandle.Zero)
78                         {
79                             Volatile.Write(ref m_HandleList[m_FreeIndex], handle);
80                             cookie = GetCookieFromData((uint)m_FreeIndex, m_CycleCounts[m_FreeIndex]);
81
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.
85                             ++m_FreeIndex;
86                             break;
87                         }
88
89                         if (m_FreeIndex + 1 == m_HandleList.Length)
90                             GrowArrays();
91                     }
92                 }
93
94                 if (cookie == GCHandleCookie.Zero)
95                     throw new OutOfMemoryException(Environment.GetResourceString("OutOfMemory_GCHandleMDA"));
96
97                 // This handle hasn't been added to the map yet so add it.
98                 m_HandleToCookieMap.Add(handle, cookie);
99             }
100
101             return cookie;
102         }
103
104         // Get a handle.
105         internal ObjectHandle GetHandle(GCHandleCookie cookie)
106         {
107             ObjectHandle oh = ObjectHandle.Zero;
108
109             if (!ValidateCookie(cookie))
110                 return ObjectHandle.Zero;
111
112             oh = Volatile.Read(ref m_HandleList[GetIndexFromCookie(cookie)]);
113
114             return oh;
115         }
116
117         // Remove the handle from the cookie table if it is present. 
118         //
119         internal void RemoveHandleIfPresent(ObjectHandle handle)
120         {
121             if (handle == ObjectHandle.Zero)
122                 return;
123
124             lock (m_syncObject)
125             {
126                 if (m_HandleToCookieMap.ContainsKey(handle))
127                 {
128                     GCHandleCookie cookie = m_HandleToCookieMap[handle];
129
130                     // Remove it from the array first
131                     if (!ValidateCookie(cookie))
132                         return;
133
134                     int index = GetIndexFromCookie(cookie);
135
136                     m_CycleCounts[index]++;
137                     Volatile.Write(ref m_HandleList[index], ObjectHandle.Zero);
138
139                     // Remove it from the hashtable last
140                     m_HandleToCookieMap.Remove(handle);
141
142                     // Update our guess
143                     m_FreeIndex = index;
144                 }
145             }
146         }
147
148         private bool ValidateCookie(GCHandleCookie cookie)
149         {
150             int index;
151             byte xorData;
152
153             GetDataFromCookie(cookie, out index, out xorData);
154
155             // Validate the index
156             if (index >= MaxListSize)
157                 return false;
158
159             if (index >= m_HandleList.Length)
160                 return false;
161
162             if (Volatile.Read(ref m_HandleList[index]) == ObjectHandle.Zero)
163                 return false;
164
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)
169                 return false;
170
171             return true;
172         }
173
174         // Double the size of our arrays - must be called with the lock taken.
175         private void GrowArrays()
176         {
177             int CurrLength = m_HandleList.Length;
178
179             ObjectHandle[] newHandleList = new ObjectHandle[CurrLength * 2];
180             byte[] newCycleCounts = new byte[CurrLength * 2];
181
182             Array.Copy(m_HandleList, newHandleList, CurrLength);
183             Array.Copy(m_CycleCounts, newCycleCounts, CurrLength);
184
185             m_HandleList = newHandleList;
186             m_CycleCounts = newCycleCounts;
187         }
188
189         // Generate a cookie based on the index, cyclecount, and current domain id.
190         private GCHandleCookie GetCookieFromData(uint index, byte cycleCount)
191         {
192             byte ADID = (byte)(AppDomain.CurrentDomain.Id % 0xFF);
193             return (GCHandleCookie)(((cycleCount ^ ADID) << 24) + index + 1);
194         }
195
196         // Break down the cookie into its parts
197         private void GetDataFromCookie(GCHandleCookie cookie, out int index, out byte xorData)
198         {
199             uint intCookie = (uint)cookie;
200             index = (int)(intCookie & CookieMaskIndex) - 1;
201             xorData = (byte)((intCookie & CookieMaskSentinal) >> 24);
202         }
203
204         // Just get the index from the cookie
205         private int GetIndexFromCookie(GCHandleCookie cookie)
206         {
207             uint intCookie = (uint)cookie;
208             return (int)(intCookie & CookieMaskIndex) - 1;
209         }
210
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;
216     }
217 }
218
219 #endif
220