ConditionalWeakTable.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. //
  2. // ConditionalWeakTable.cs
  3. //
  4. // Author:
  5. // Rodrigo Kumpera ([email protected])
  6. // Tautvydas Žilys <[email protected]>
  7. //
  8. // Copyright (C) 2010 Novell, Inc (http://www.novell.com)
  9. // Copyright (C) 2016 Unity Technologies (https://unity3d.com)
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining
  12. // a copy of this software and associated documentation files (the
  13. // "Software"), to deal in the Software without restriction, including
  14. // without limitation the rights to use, copy, modify, merge, publish,
  15. // distribute, sublicense, and/or sell copies of the Software, and to
  16. // permit persons to whom the Software is furnished to do so, subject to
  17. // the following conditions:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  22. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. //
  30. using System;
  31. using System.Collections;
  32. using System.Collections.Generic;
  33. using System.Diagnostics;
  34. using System.Threading;
  35. namespace System.Runtime.CompilerServices
  36. {
  37. internal struct Ephemeron
  38. {
  39. internal object key;
  40. internal object value;
  41. }
  42. /*
  43. TODO:
  44. The runtime need to inform the table about how many entries were expired.
  45. Compact the table when there are too many tombstones.
  46. Rehash to a smaller size when there are too few entries.
  47. Change rehash condition check to use non-fp code.
  48. Look into using quatratic probing/double hashing to reduce clustering problems.
  49. Make reads and non-expanding writes (add/remove) lock free.
  50. */
  51. public sealed class ConditionalWeakTable<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
  52. where TKey : class
  53. where TValue : class
  54. {
  55. const int INITIAL_SIZE = 13;
  56. const float LOAD_FACTOR = 0.7f;
  57. const float COMPACT_FACTOR = 0.5f;
  58. const float EXPAND_FACTOR = 1.1f;
  59. Ephemeron[] data;
  60. object _lock = new object ();
  61. int size;
  62. public delegate TValue CreateValueCallback (TKey key);
  63. public ConditionalWeakTable ()
  64. {
  65. data = new Ephemeron [INITIAL_SIZE];
  66. GC.register_ephemeron_array (data);
  67. }
  68. ~ConditionalWeakTable ()
  69. {
  70. }
  71. private void RehashWithoutResize ()
  72. {
  73. int len = data.Length;
  74. for (int i = 0; i < len; i++) {
  75. if (data [i].key == GC.EPHEMERON_TOMBSTONE)
  76. data [i].key = null;
  77. }
  78. for (int i = 0; i < len; i++) {
  79. object key = data [i].key;
  80. if (key != null) {
  81. int idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
  82. while (true) {
  83. if (data [idx].key == null) {
  84. // The object was not stored in its normal slot. Rehash
  85. data [idx].key = key;
  86. data [idx].value = data [i].value;
  87. // At this point we have this Ephemeron entry duplicated in the array. Shouldn't
  88. // be a problem.
  89. data [i].key = null;
  90. data [i].value = null;
  91. break;
  92. } else if (data [idx].key == key) {
  93. /* We already have the key in the first available position, finished */
  94. break;
  95. }
  96. if (++idx == len) //Wrap around
  97. idx = 0;
  98. }
  99. }
  100. }
  101. }
  102. private void RecomputeSize ()
  103. {
  104. size = 0;
  105. for (int i = 0; i < data.Length; i++) {
  106. if (data [i].key != null)
  107. size++;
  108. }
  109. }
  110. /*LOCKING: _lock must be held*/
  111. private void Rehash ()
  112. {
  113. // Size doesn't track elements that die without being removed. Before attempting
  114. // to rehash we traverse the array to see how many entries are left alive. We
  115. // rehash the array into a new one which has a capacity relative to the number of
  116. // live entries.
  117. RecomputeSize ();
  118. uint newLength = (uint)HashHelpers.GetPrime (((int)(size / LOAD_FACTOR) << 1) | 1);
  119. if (newLength > data.Length * COMPACT_FACTOR && newLength < data.Length * EXPAND_FACTOR) {
  120. /* Avoid unnecessary LOS allocations */
  121. RehashWithoutResize ();
  122. return;
  123. }
  124. //Console.WriteLine ("--- resizing from {0} to {1}", data.Length, newLength);
  125. Ephemeron[] tmp = new Ephemeron [newLength];
  126. GC.register_ephemeron_array (tmp);
  127. size = 0;
  128. for (int i = 0; i < data.Length; ++i) {
  129. object key = data[i].key;
  130. object value = data[i].value;
  131. if (key == null || key == GC.EPHEMERON_TOMBSTONE)
  132. continue;
  133. int len = tmp.Length;
  134. int idx, initial_idx;
  135. int free_slot = -1;
  136. idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
  137. do {
  138. object k = tmp [idx].key;
  139. //keys might be GC'd during Rehash
  140. if (k == null || k == GC.EPHEMERON_TOMBSTONE) {
  141. free_slot = idx;
  142. break;
  143. }
  144. if (++idx == len) //Wrap around
  145. idx = 0;
  146. } while (idx != initial_idx);
  147. tmp [free_slot].key = key;
  148. tmp [free_slot].value = value;
  149. ++size;
  150. }
  151. data = tmp;
  152. }
  153. // the whole method is just a copy of `public void Add (TKey key, TValue value)`
  154. // the only difference it doesn't throw exceptions if the given key exists
  155. // both methods will be merged once a wierd issue (broken acceptence test dev10_535767.cs) is resolved
  156. public void AddOrUpdate (TKey key, TValue value)
  157. {
  158. if (key == default (TKey))
  159. throw new ArgumentNullException ("Null key", "key");
  160. lock (_lock) {
  161. if (size >= data.Length * LOAD_FACTOR)
  162. Rehash ();
  163. int len = data.Length;
  164. int idx,initial_idx;
  165. int free_slot = -1;
  166. idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
  167. do {
  168. object k = data [idx].key;
  169. if (k == null) {
  170. if (free_slot == -1)
  171. free_slot = idx;
  172. break;
  173. } else if (k == GC.EPHEMERON_TOMBSTONE && free_slot == -1) { //Add requires us to check for dupes :(
  174. free_slot = idx;
  175. } else if (k == key) {
  176. free_slot = idx;
  177. }
  178. if (++idx == len) //Wrap around
  179. idx = 0;
  180. } while (idx != initial_idx);
  181. data [free_slot].key = key;
  182. data [free_slot].value = value;
  183. ++size;
  184. }
  185. }
  186. public void Add (TKey key, TValue value)
  187. {
  188. if (key == default (TKey))
  189. throw new ArgumentNullException ("Null key", "key");
  190. lock (_lock) {
  191. if (size >= data.Length * LOAD_FACTOR)
  192. Rehash ();
  193. int len = data.Length;
  194. int idx,initial_idx;
  195. int free_slot = -1;
  196. idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
  197. do {
  198. object k = data [idx].key;
  199. if (k == null) {
  200. if (free_slot == -1)
  201. free_slot = idx;
  202. break;
  203. } else if (k == GC.EPHEMERON_TOMBSTONE && free_slot == -1) { //Add requires us to check for dupes :(
  204. free_slot = idx;
  205. } else if (k == key) {
  206. throw new ArgumentException ("Key already in the list", "key");
  207. }
  208. if (++idx == len) //Wrap around
  209. idx = 0;
  210. } while (idx != initial_idx);
  211. data [free_slot].key = key;
  212. data [free_slot].value = value;
  213. ++size;
  214. }
  215. }
  216. public bool Remove (TKey key)
  217. {
  218. if (key == default (TKey))
  219. throw new ArgumentNullException ("Null key", "key");
  220. lock (_lock) {
  221. int len = data.Length;
  222. int idx, initial_idx;
  223. idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
  224. do {
  225. object k = data[idx].key;
  226. if (k == key) {
  227. data [idx].key = GC.EPHEMERON_TOMBSTONE;
  228. data [idx].value = null;
  229. return true;
  230. }
  231. if (k == null)
  232. break;
  233. if (++idx == len) //Wrap around
  234. idx = 0;
  235. } while (idx != initial_idx);
  236. }
  237. return false;
  238. }
  239. public bool TryGetValue (TKey key, out TValue value)
  240. {
  241. if (key == null)
  242. throw new ArgumentNullException ("Null key", "key");
  243. value = default (TValue);
  244. lock (_lock) {
  245. int len = data.Length;
  246. int idx, initial_idx;
  247. idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
  248. do {
  249. object k = data [idx].key;
  250. if (k == key) {
  251. value = (TValue)data [idx].value;
  252. return true;
  253. }
  254. if (k == null)
  255. break;
  256. if (++idx == len) //Wrap around
  257. idx = 0;
  258. } while (idx != initial_idx);
  259. }
  260. return false;
  261. }
  262. public TValue GetOrCreateValue (TKey key)
  263. {
  264. return GetValue (key, k => Activator.CreateInstance<TValue> ());
  265. }
  266. public TValue GetValue (TKey key, CreateValueCallback createValueCallback)
  267. {
  268. if (createValueCallback == null)
  269. throw new ArgumentNullException ("Null create delegate", "createValueCallback");
  270. TValue res;
  271. lock (_lock) {
  272. if (TryGetValue (key, out res))
  273. return res;
  274. res = createValueCallback (key);
  275. Add (key, res);
  276. }
  277. return res;
  278. }
  279. //--------------------------------------------------------------------------------------------
  280. // Find a key that equals (value equality) with the given key - don't use in perf critical path
  281. // Note that it calls out to Object.Equals which may calls the override version of Equals
  282. // and that may take locks and leads to deadlock
  283. // Currently it is only used by WinRT event code and you should only use this function
  284. // if you know for sure that either you won't run into dead locks or you need to live with the
  285. // possiblity
  286. //--------------------------------------------------------------------------------------------
  287. #if !MONO
  288. [System.Security.SecuritySafeCritical]
  289. [FriendAccessAllowed]
  290. #endif
  291. internal TKey FindEquivalentKeyUnsafe(TKey key, out TValue value)
  292. {
  293. lock (_lock)
  294. {
  295. for (int i = 0; i < data.Length; ++i)
  296. {
  297. var item = data[i];
  298. if (Object.Equals(item.key, key))
  299. {
  300. value = (TValue)item.value;
  301. return (TKey)item.key;
  302. }
  303. }
  304. }
  305. value = default(TValue);
  306. return null;
  307. }
  308. //--------------------------------------------------------------------------------------------
  309. // Clear all the key/value pairs
  310. //--------------------------------------------------------------------------------------------
  311. [System.Security.SecuritySafeCritical]
  312. public void Clear()
  313. {
  314. lock (_lock)
  315. {
  316. for (int i = 0; i < data.Length; i++)
  317. {
  318. data[i].key = null;
  319. data[i].value = null;
  320. }
  321. size = 0;
  322. }
  323. }
  324. // extracted from ../../../../external/referencesource/mscorlib/system/runtime/compilerservices/
  325. internal ICollection<TKey> Keys
  326. {
  327. [System.Security.SecuritySafeCritical]
  328. get
  329. {
  330. var tombstone = GC.EPHEMERON_TOMBSTONE;
  331. List<TKey> list = new List<TKey>(data.Length);
  332. lock (_lock)
  333. {
  334. for (int i = 0; i < data.Length; ++i)
  335. {
  336. TKey key = (TKey) data [i].key;
  337. if (key != null && key != tombstone)
  338. list.Add (key);
  339. }
  340. }
  341. return list;
  342. }
  343. }
  344. internal ICollection<TValue> Values
  345. {
  346. [System.Security.SecuritySafeCritical]
  347. get
  348. {
  349. var tombstone = GC.EPHEMERON_TOMBSTONE;
  350. List<TValue> list = new List<TValue>(data.Length);
  351. lock (_lock)
  352. {
  353. for (int i = 0; i < data.Length; ++i)
  354. {
  355. var item = data[i];
  356. if (item.key != null && item.key != tombstone)
  357. list.Add((TValue)item.value);
  358. }
  359. }
  360. return list;
  361. }
  362. }
  363. // IEnumerable implementation was copied from CoreCLR
  364. IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator ()
  365. {
  366. lock (_lock)
  367. {
  368. return size == 0 ?
  369. ((IEnumerable<KeyValuePair<TKey, TValue>>)Array.Empty<KeyValuePair<TKey, TValue>>()).GetEnumerator() :
  370. new Enumerator(this);
  371. }
  372. }
  373. IEnumerator IEnumerable.GetEnumerator () => ((IEnumerable<KeyValuePair<TKey, TValue>>)this).GetEnumerator ();
  374. /// <summary>Provides an enumerator for the table.</summary>
  375. private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
  376. {
  377. // The enumerator would ideally hold a reference to the Container and the end index within that
  378. // container. However, the safety of the CWT depends on the only reference to the Container being
  379. // from the CWT itself; the Container then employs a two-phase finalization scheme, where the first
  380. // phase nulls out that parent CWT's reference, guaranteeing that the second time it's finalized there
  381. // can be no other existing references to it in use that would allow for concurrent usage of the
  382. // native handles with finalization. We would break that if we allowed this Enumerator to hold a
  383. // reference to the Container. Instead, the Enumerator holds a reference to the CWT rather than to
  384. // the Container, and it maintains the CWT._activeEnumeratorRefCount field to track whether there
  385. // are outstanding enumerators that have yet to be disposed/finalized. If there aren't any, the CWT
  386. // behaves as it normally does. If there are, certain operations are affected, in particular resizes.
  387. // Normally when the CWT is resized, it enumerates the contents of the table looking for indices that
  388. // contain entries which have been collected or removed, and it frees those up, effectively moving
  389. // down all subsequent entries in the container (not in the existing container, but in a replacement).
  390. // This, however, would cause the enumerator's understanding of indices to break. So, as long as
  391. // there is any outstanding enumerator, no compaction is performed.
  392. private ConditionalWeakTable<TKey, TValue> _table; // parent table, set to null when disposed
  393. private int _currentIndex = -1; // the current index into the container
  394. private KeyValuePair<TKey, TValue> _current; // the current entry set by MoveNext and returned from Current
  395. public Enumerator(ConditionalWeakTable<TKey, TValue> table)
  396. {
  397. Debug.Assert(table != null, "Must provide a valid table");
  398. Debug.Assert(Monitor.IsEntered(table._lock), "Must hold the _lock lock to construct the enumerator");
  399. // Store a reference to the parent table and increase its active enumerator count.
  400. _table = table;
  401. _currentIndex = -1;
  402. }
  403. ~Enumerator() { Dispose(); }
  404. public void Dispose()
  405. {
  406. // Use an interlocked operation to ensure that only one thread can get access to
  407. // the _table for disposal and thus only decrement the ref count once.
  408. ConditionalWeakTable<TKey, TValue> table = Interlocked.Exchange(ref _table, null);
  409. if (table != null)
  410. {
  411. // Ensure we don't keep the last current alive unnecessarily
  412. _current = default;
  413. // Finalization is purely to decrement the ref count. We can suppress it now.
  414. GC.SuppressFinalize(this);
  415. }
  416. }
  417. public bool MoveNext()
  418. {
  419. // Start by getting the current table. If it's already been disposed, it will be null.
  420. ConditionalWeakTable<TKey, TValue> table = _table;
  421. if (table != null)
  422. {
  423. // Once have the table, we need to lock to synchronize with other operations on
  424. // the table, like adding.
  425. lock (table._lock)
  426. {
  427. var tombstone = GC.EPHEMERON_TOMBSTONE;
  428. while (_currentIndex < table.data.Length - 1)
  429. {
  430. _currentIndex++;
  431. var currentDataItem = table.data[_currentIndex];
  432. if (currentDataItem.key != null && currentDataItem.key != tombstone)
  433. {
  434. _current = new KeyValuePair<TKey, TValue>((TKey)currentDataItem.key, (TValue)currentDataItem.value);
  435. return true;
  436. }
  437. }
  438. }
  439. }
  440. // Nothing more to enumerate.
  441. return false;
  442. }
  443. public KeyValuePair<TKey, TValue> Current
  444. {
  445. get
  446. {
  447. if (_currentIndex < 0)
  448. {
  449. ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
  450. }
  451. return _current;
  452. }
  453. }
  454. object IEnumerator.Current => Current;
  455. public void Reset() { }
  456. }
  457. }
  458. }