Xamarin Public Jenkins (auto-signing) 6bdd276d05 Imported Upstream version 5.0.0.42
Former-commit-id: fd56571888259555122d8a0f58c68838229cea2b
2017-04-10 11:41:01 +00:00

379 lines
9.9 KiB
C#

//
// ConditionalWeakTable.cs
//
// Author:
// Rodrigo Kumpera (rkumpera@novell.com)
// Tautvydas Žilys <zilys@unity3d.com>
//
// Copyright (C) 2010 Novell, Inc (http://www.novell.com)
// Copyright (C) 2016 Unity Technologies (https://unity3d.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
namespace System.Runtime.CompilerServices
{
internal struct Ephemeron
{
internal object key;
internal object value;
}
/*
TODO:
The runtime need to inform the table about how many entries were expired.
Compact the table when there are too many tombstones.
Rehash to a smaller size when there are too few entries.
Change rehash condition check to use non-fp code.
Look into using quatratic probing/double hashing to reduce clustering problems.
Make reads and non-expanding writes (add/remove) lock free.
*/
public sealed class ConditionalWeakTable<TKey, TValue>
where TKey : class
where TValue : class
{
const int INITIAL_SIZE = 13;
const float LOAD_FACTOR = 0.7f;
const float COMPACT_FACTOR = 0.5f;
const float EXPAND_FACTOR = 1.1f;
Ephemeron[] data;
object _lock = new object ();
int size;
public delegate TValue CreateValueCallback (TKey key);
public ConditionalWeakTable ()
{
data = new Ephemeron [INITIAL_SIZE];
GC.register_ephemeron_array (data);
}
~ConditionalWeakTable ()
{
}
private void RehashWithoutResize ()
{
int len = data.Length;
for (int i = 0; i < len; i++) {
if (data [i].key == GC.EPHEMERON_TOMBSTONE)
data [i].key = null;
}
for (int i = 0; i < len; i++) {
object key = data [i].key;
if (key != null) {
int idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
while (true) {
if (data [idx].key == null) {
// The object was not stored in its normal slot. Rehash
data [idx].key = key;
data [idx].value = data [i].value;
// At this point we have this Ephemeron entry duplicated in the array. Shouldn't
// be a problem.
data [i].key = null;
data [i].value = null;
break;
} else if (data [idx].key == key) {
/* We already have the key in the first available position, finished */
break;
}
if (++idx == len) //Wrap around
idx = 0;
}
}
}
}
private void RecomputeSize ()
{
size = 0;
for (int i = 0; i < data.Length; i++) {
if (data [i].key != null)
size++;
}
}
/*LOCKING: _lock must be held*/
private void Rehash ()
{
// Size doesn't track elements that die without being removed. Before attempting
// to rehash we traverse the array to see how many entries are left alive. We
// rehash the array into a new one which has a capacity relative to the number of
// live entries.
RecomputeSize ();
uint newLength = (uint)HashHelpers.GetPrime (((int)(size / LOAD_FACTOR) << 1) | 1);
if (newLength > data.Length * COMPACT_FACTOR && newLength < data.Length * EXPAND_FACTOR) {
/* Avoid unnecessary LOS allocations */
RehashWithoutResize ();
return;
}
//Console.WriteLine ("--- resizing from {0} to {1}", data.Length, newLength);
Ephemeron[] tmp = new Ephemeron [newLength];
GC.register_ephemeron_array (tmp);
size = 0;
for (int i = 0; i < data.Length; ++i) {
object key = data[i].key;
object value = data[i].value;
if (key == null || key == GC.EPHEMERON_TOMBSTONE)
continue;
int len = tmp.Length;
int idx, initial_idx;
int free_slot = -1;
idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
do {
object k = tmp [idx].key;
//keys might be GC'd during Rehash
if (k == null || k == GC.EPHEMERON_TOMBSTONE) {
free_slot = idx;
break;
}
if (++idx == len) //Wrap around
idx = 0;
} while (idx != initial_idx);
tmp [free_slot].key = key;
tmp [free_slot].value = value;
++size;
}
data = tmp;
}
public void Add (TKey key, TValue value)
{
if (key == default (TKey))
throw new ArgumentNullException ("Null key", "key");
lock (_lock) {
if (size >= data.Length * LOAD_FACTOR)
Rehash ();
int len = data.Length;
int idx,initial_idx;
int free_slot = -1;
idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
do {
object k = data [idx].key;
if (k == null) {
if (free_slot == -1)
free_slot = idx;
break;
} else if (k == GC.EPHEMERON_TOMBSTONE && free_slot == -1) { //Add requires us to check for dupes :(
free_slot = idx;
} else if (k == key) {
throw new ArgumentException ("Key already in the list", "key");
}
if (++idx == len) //Wrap around
idx = 0;
} while (idx != initial_idx);
data [free_slot].key = key;
data [free_slot].value = value;
++size;
}
}
public bool Remove (TKey key)
{
if (key == default (TKey))
throw new ArgumentNullException ("Null key", "key");
lock (_lock) {
int len = data.Length;
int idx, initial_idx;
idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
do {
object k = data[idx].key;
if (k == key) {
data [idx].key = GC.EPHEMERON_TOMBSTONE;
data [idx].value = null;
--size;
return true;
}
if (k == null)
break;
if (++idx == len) //Wrap around
idx = 0;
} while (idx != initial_idx);
}
return false;
}
public bool TryGetValue (TKey key, out TValue value)
{
if (key == null)
throw new ArgumentNullException ("Null key", "key");
value = default (TValue);
lock (_lock) {
int len = data.Length;
int idx, initial_idx;
idx = initial_idx = (RuntimeHelpers.GetHashCode (key) & int.MaxValue) % len;
do {
object k = data [idx].key;
if (k == key) {
value = (TValue)data [idx].value;
return true;
}
if (k == null)
break;
if (++idx == len) //Wrap around
idx = 0;
} while (idx != initial_idx);
}
return false;
}
public TValue GetOrCreateValue (TKey key)
{
return GetValue (key, k => Activator.CreateInstance<TValue> ());
}
public TValue GetValue (TKey key, CreateValueCallback createValueCallback)
{
if (createValueCallback == null)
throw new ArgumentNullException ("Null create delegate", "createValueCallback");
TValue res;
lock (_lock) {
if (TryGetValue (key, out res))
return res;
res = createValueCallback (key);
Add (key, res);
}
return res;
}
//--------------------------------------------------------------------------------------------
// Find a key that equals (value equality) with the given key - don't use in perf critical path
// Note that it calls out to Object.Equals which may calls the override version of Equals
// and that may take locks and leads to deadlock
// Currently it is only used by WinRT event code and you should only use this function
// if you know for sure that either you won't run into dead locks or you need to live with the
// possiblity
//--------------------------------------------------------------------------------------------
[System.Security.SecuritySafeCritical]
[FriendAccessAllowed]
internal TKey FindEquivalentKeyUnsafe(TKey key, out TValue value)
{
lock (_lock)
{
for (int i = 0; i < data.Length; ++i)
{
var item = data[i];
if (Object.Equals(item.key, key))
{
value = (TValue)item.value;
return (TKey)item.key;
}
}
}
value = default(TValue);
return null;
}
//--------------------------------------------------------------------------------------------
// Clear all the key/value pairs
//--------------------------------------------------------------------------------------------
[System.Security.SecuritySafeCritical]
internal void Clear()
{
lock (_lock)
{
for (int i = 0; i < data.Length; i++)
{
data[i].key = GC.EPHEMERON_TOMBSTONE;
data[i].value = null;
}
size = 0;
}
}
// extracted from ../../../../external/referencesource/mscorlib/system/runtime/compilerservices/
internal ICollection<TKey> Keys
{
[System.Security.SecuritySafeCritical]
get
{
var tombstone = GC.EPHEMERON_TOMBSTONE;
List<TKey> list = new List<TKey>(data.Length);
lock (_lock)
{
for (int i = 0; i < data.Length; ++i)
{
TKey key = (TKey) data [i].key;
if (key != null && key != tombstone)
list.Add (key);
}
}
return list;
}
}
internal ICollection<TValue> Values
{
[System.Security.SecuritySafeCritical]
get
{
var tombstone = GC.EPHEMERON_TOMBSTONE;
List<TValue> list = new List<TValue>(data.Length);
lock (_lock)
{
for (int i = 0; i < data.Length; ++i)
{
var item = data[i];
if (item.key != null && item.key != tombstone)
list.Add((TValue)item.value);
}
}
return list;
}
}
}
}