1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections; 6 using System.Security.Permissions; 7 using System.Diagnostics; 8 9 using Microsoft.Build.Framework; 10 using Microsoft.Build.BuildEngine.Shared; 11 12 namespace Microsoft.Build.BuildEngine 13 { 14 15 /// <summary> 16 /// A hashtable wrapper that defers copying until the data is written. 17 /// </summary> 18 internal sealed class CopyOnWriteHashtable : IDictionary, ICloneable 19 { 20 // Either of these can act as the true backing writeableData. 21 private Hashtable writeableData = null; 22 23 // These are references to someone else's backing writeableData. 24 private Hashtable readonlyData = null; 25 26 // This is used to synchronize access to the readonlyData and writeableData fields. 27 private object sharedLock; 28 29 // Carry around the StringComparer when possible to make Clear less expensive. 30 StringComparer stringComparer = null; 31 32 #region Construct 33 /// <summary> 34 /// Construct as a traditional data-backed hashtable. 35 /// </summary> 36 /// <param name="stringComparer"></param> CopyOnWriteHashtable(StringComparer stringComparer)37 internal CopyOnWriteHashtable(StringComparer stringComparer) 38 : this(0, stringComparer) 39 { 40 this.sharedLock = new object(); 41 } 42 43 /// <summary> 44 /// Construct with specified initial capacity. If the capacity is known 45 /// up front, specifying it avoids unnecessary rehashing operations 46 /// </summary> CopyOnWriteHashtable(int capacity, StringComparer stringComparer)47 internal CopyOnWriteHashtable(int capacity, StringComparer stringComparer) 48 { 49 ErrorUtilities.VerifyThrowArgumentNull(stringComparer, "stringComparer"); 50 this.sharedLock = new object(); 51 52 if (capacity == 0) 53 { 54 // Actually 0 tells the Hashtable to use its default, but let's not rely on that 55 writeableData = new Hashtable(stringComparer); 56 } 57 else 58 { 59 writeableData = new Hashtable(capacity, stringComparer); 60 } 61 readonlyData = null; 62 this.stringComparer = stringComparer; 63 } 64 65 /// <summary> 66 /// Construct over an IDictionary instance. 67 /// </summary> 68 /// <param name="dictionary"></param> 69 /// <param name="stringComparer">The string comparer to use.</param> CopyOnWriteHashtable(IDictionary dictionary, StringComparer stringComparer)70 internal CopyOnWriteHashtable(IDictionary dictionary, StringComparer stringComparer) 71 { 72 ErrorUtilities.VerifyThrowArgumentNull(dictionary, "dictionary"); 73 ErrorUtilities.VerifyThrowArgumentNull(stringComparer, "stringComparer"); 74 75 this.sharedLock = new object(); 76 CopyOnWriteHashtable source = dictionary as CopyOnWriteHashtable; 77 if (source != null) 78 { 79 if (source.stringComparer.GetHashCode() == stringComparer.GetHashCode()) 80 { 81 // If we're copying another CopyOnWriteHashtable then we can defer the clone until later. 82 ConstructFrom(source); 83 return; 84 } 85 else 86 { 87 // Technically, it would be legal to fall through here and let a new hashtable be constructed. 88 // However, Engine is supposed to use consistent case comparisons everywhere and so, for us, 89 // this means a bug in the engine code somewhere. 90 throw new InternalErrorException("Bug: Changing the case-sensitiveness of a copied hash-table."); 91 } 92 93 } 94 95 // Can't defer this because we don't control what gets written to the dictionary exogenously. 96 writeableData = new Hashtable(dictionary, stringComparer); 97 readonlyData = null; 98 this.stringComparer = stringComparer; 99 } 100 101 /// <summary> 102 /// Construct a shallow copy over another instance of this class. 103 /// </summary> 104 /// <param name="that"></param> CopyOnWriteHashtable(CopyOnWriteHashtable that)105 private CopyOnWriteHashtable(CopyOnWriteHashtable that) 106 { 107 this.sharedLock = new object(); 108 ConstructFrom(that); 109 } 110 111 /// <summary> 112 /// Implementation of construction logic. 113 /// </summary> 114 /// <param name="that"></param> ConstructFrom(CopyOnWriteHashtable that)115 private void ConstructFrom(CopyOnWriteHashtable that) 116 { 117 lock (that.sharedLock) 118 { 119 this.writeableData = null; 120 121 // If the source it was writeable, need to transform it into 122 // read-only because we don't want subsequent writes to bleed through. 123 if (that.writeableData != null) 124 { 125 that.readonlyData = that.writeableData; 126 that.writeableData = null; 127 } 128 129 this.readonlyData = that.readonlyData; 130 this.stringComparer = that.stringComparer; 131 } 132 } 133 134 /// <summary> 135 /// Whether or not this CopyOnWriteHashtable is currently a shallow or deep copy. 136 /// This state can change from true->false when this hashtable is written to. 137 /// </summary> 138 internal bool IsShallowCopy 139 { 140 get 141 { 142 return this.readonlyData != null; 143 } 144 } 145 #endregion 146 #region Pass-through Hashtable methods. Contains(Object key)147 public bool Contains(Object key) {return ReadOperation.Contains(key);} Add(Object key, Object value)148 public void Add(Object key, Object value) {WriteOperation.Add(key, value);} Clear()149 public void Clear() 150 { 151 lock (sharedLock) 152 { 153 ErrorUtilities.VerifyThrow(stringComparer != null, "Should have a valid string comparer."); 154 155 writeableData = new Hashtable(stringComparer); 156 readonlyData = null; 157 } 158 } 159 IEnumerable.GetEnumerator()160 IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)ReadOperation).GetEnumerator(); } GetEnumerator()161 public IDictionaryEnumerator GetEnumerator() {return ReadOperation.GetEnumerator();} Remove(Object key)162 public void Remove(Object key) {WriteOperation.Remove(key);} 163 public bool IsFixedSize { get { return ReadOperation.IsFixedSize; }} 164 public bool IsReadOnly {get {return ReadOperation.IsFixedSize;}} 165 public ICollection Keys {get {return ReadOperation.Keys;}} 166 public ICollection Values {get {return ReadOperation.Values;}} CopyTo(Array array, int arrayIndex)167 public void CopyTo(Array array, int arrayIndex) { ReadOperation.CopyTo(array, arrayIndex); } 168 public int Count{get { return ReadOperation.Count; }} 169 public bool IsSynchronized {get { return ReadOperation.IsSynchronized; }} 170 public Object SyncRoot {get { return ReadOperation.SyncRoot; }} ContainsKey(Object key)171 public bool ContainsKey(Object key) {return ReadOperation.Contains(key);} 172 173 public Object this[Object key] 174 { 175 get 176 { 177 return ReadOperation[key]; 178 } 179 set 180 { 181 lock (sharedLock) 182 { 183 if(writeableData != null) 184 { 185 writeableData[key] = value; 186 } 187 else 188 { 189 // Setting to exactly the same value? Skip the the Clone in this case. 190 if (readonlyData[key] != value || (!readonlyData.ContainsKey(key))) 191 { 192 WriteOperation[key] = value; 193 } 194 } 195 } 196 197 } 198 } 199 #endregion 200 201 /// <summary> 202 /// Clone this. 203 /// </summary> 204 /// <returns></returns> Clone()205 public Object Clone() 206 { 207 return new CopyOnWriteHashtable(this); 208 } 209 210 /// <summary> 211 /// Returns a hashtable instance for reading from. 212 /// </summary> 213 private Hashtable ReadOperation 214 { 215 get 216 { 217 lock (sharedLock) 218 { 219 if (readonlyData != null) 220 { 221 return readonlyData; 222 } 223 224 return writeableData; 225 } 226 } 227 } 228 229 /// <summary> 230 /// Returns a hashtable instance for writting to. 231 /// Clones the readonly hashtable if necessary to create a writeable version. 232 /// </summary> 233 private Hashtable WriteOperation 234 { 235 get 236 { 237 lock (sharedLock) 238 { 239 if (writeableData == null) 240 { 241 writeableData = (Hashtable)readonlyData.Clone(); 242 readonlyData = null; 243 } 244 245 return writeableData; 246 } 247 } 248 } 249 } 250 } 251