Skip to content

Commit

Permalink
Merge common code base for DBReferenceCollection
Browse files Browse the repository at this point in the history
  • Loading branch information
Javad Rahnama (SIMBA TECHNOLOGIES INC) committed Mar 12, 2024
1 parent 769b982 commit 34805b4
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@
<Compile Include="$(CommonSourceRoot)System\Diagnostics\CodeAnalysis.cs">
<Link>Common\System\Diagnostics\CodeAnalysis.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)\Microsoft\Data\ProviderBase\DbReferenceCollection.cs">
<Link>Microsoft\Data\ProviderBase\DbReferenceCollection.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netstandard' OR '$(TargetGroup)' == 'netcoreapp' OR '$(IsUAPAssembly)' == 'true'">
<Compile Include="Microsoft.Data.SqlClient.TypeForwards.cs" />
Expand Down Expand Up @@ -616,9 +619,6 @@
<Compile Include="$(CommonPath)\Microsoft\Data\ProviderBase\DbConnectionInternal.cs">
<Link>Common\Microsoft\Data\ProviderBase\DbConnectionInternal.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Microsoft\Data\ProviderBase\DbReferenceCollection.cs">
<Link>Common\Microsoft\Data\ProviderBase\DbReferenceCollection.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\NotImplemented.cs" />
<Compile Include="Interop\SNINativeMethodWrapper.Common.cs" />
<Compile Include="Microsoft\Data\Common\DbConnectionOptions.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,9 @@
<Compile Include="$(CommonSourceRoot)Resources\ResDescriptionAttribute.cs">
<Link>Resources\ResDescriptionAttribute.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)\Microsoft\Data\ProviderBase\DbReferenceCollection.cs">
<Link>Microsoft\Data\ProviderBase\DbReferenceCollection.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Common\src\Microsoft\Data\Common\NameValuePermission.cs" />
Expand All @@ -631,7 +634,6 @@
<Compile Include="Microsoft\Data\ProviderBase\DbConnectionPool.cs" />
<Compile Include="Microsoft\Data\ProviderBase\DbConnectionPoolCounters.cs" />
<Compile Include="Microsoft\Data\ProviderBase\DbConnectionPoolIdentity.cs" />
<Compile Include="Microsoft\Data\ProviderBase\DbReferenceCollection.cs" />
<Compile Include="Microsoft\Data\RelationshipConverter.cs" />
<Compile Include="Microsoft\Data\SqlClient\AlwaysEncryptedKeyConverter.Cng.cs" />
<Compile Include="Microsoft\Data\SqlClient\assemblycache.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.


using System;
using System.Diagnostics;
using System.Threading;

namespace Microsoft.Data.ProviderBase
{
internal abstract class DbReferenceCollection
{
#region Constants
// Time to wait (in ms) between attempting to get the _itemLock
private const int LockPollTime = 100;

// Default size for the collection, and the amount to grow every time the collection is full
private const int DefaultCollectionSize = 20;
#endregion

#region Fields
// The collection of items we are keeping track of
private CollectionEntry[] _items;

// Used to synchronize access to the _items collection
private readonly object _itemLock;

// (#ItemsAdded - #ItemsRemoved) - This estimates the number of items that we should have
// (but doesn't take into account item targets being GC'd)
private int _estimatedCount;

// Location of the last item in _items
private int _lastItemIndex;

// Indicates that the collection is currently being notified (and, therefore, about to be cleared)
private volatile bool _isNotifying;
#endregion

private struct CollectionEntry
{
private int _refInfo; // information about the reference
private WeakReference<object> _weakReference; // the reference itself.

public void SetTNewTarget(int refInfo, object target)
{
Debug.Assert(!TryGetTarget(out object _), "Entry already has a valid target");
Debug.Assert(refInfo != 0, "Bad reference info");
Debug.Assert(target != null, "Invalid target");

if (_weakReference == null)
{
_weakReference = new WeakReference<object>(target, false);
}
else
{
_weakReference.SetTarget(target);
}
_refInfo = refInfo;
}

public void RemoveTarget()
{
_refInfo = 0;
_weakReference.SetTarget(null);
}

public readonly int RefInfo => _refInfo;

public readonly bool TryGetTarget(out object target)
{
target = null;
return _refInfo != 0 && _weakReference.TryGetTarget(out target);
}
}

protected DbReferenceCollection()
{
_items = new CollectionEntry[DefaultCollectionSize];
_itemLock = new object();
_estimatedCount = 0;
_lastItemIndex = 0;
}

abstract public void Add(object value, int refInfo);

protected void AddItem(object value, int refInfo)
{
Debug.Assert(value != null && 0 != refInfo, "AddItem with null value or 0 reference info");
bool itemAdded = false;

lock (_itemLock)
{
// Try to find a free spot
for (int i = 0; i <= _lastItemIndex; ++i)
{
if (_items[i].RefInfo == 0)
{
_items[i].SetTNewTarget(refInfo, value);
Debug.Assert(_items[i].TryGetTarget(out object _), "missing expected target");
itemAdded = true;
break;
}
}

// No free spots, can we just add on to the end?
if ((!itemAdded) && (_lastItemIndex + 1 < _items.Length))
{
_lastItemIndex++;
_items[_lastItemIndex].SetTNewTarget(refInfo, value);
itemAdded = true;
}

// If no free spots and no space at the end, try to find a dead item
if (!itemAdded)
{
for (int i = 0; i <= _lastItemIndex; ++i)
{
if (!_items[i].TryGetTarget(out object _))
{
_items[i].SetTNewTarget(refInfo, value);
Debug.Assert(_items[i].TryGetTarget(out object _), "missing expected target");
itemAdded = true;
break;
}
}
}

// If nothing was free, then resize and add to the end
if (!itemAdded)
{
Array.Resize<CollectionEntry>(ref _items, _items.Length * 2);
_lastItemIndex++;
_items[_lastItemIndex].SetTNewTarget(refInfo, value);
}

_estimatedCount++;
}
}

internal T FindItem<T>(int refInfo, Func<T, bool> filterMethod) where T : class
{
bool lockObtained = false;
try
{
TryEnterItemLock(ref lockObtained);
if (lockObtained)
{
if (_estimatedCount > 0)
{
for (int counter = 0; counter <= _lastItemIndex; counter++)
{
// Check reference info (should be easiest and quickest)
if (_items[counter].RefInfo == refInfo)
{
if (_items[counter].TryGetTarget(out object value))
{
// Make sure the item has the correct type and passes the filtering
if (value is T tempItem && filterMethod(tempItem))
{
return tempItem;
}
}
}
}
}
}
}
finally
{
ExitItemLockIfNeeded(lockObtained);
}

// If we got to here, then no item was found, so return null
return null;
}

public void Notify(int message)
{
bool lockObtained = false;
try
{
TryEnterItemLock(ref lockObtained);
if (lockObtained)
{
try
{
_isNotifying = true;

// Loop through each live item and notify it
if (_estimatedCount > 0)
{
for (int index = 0; index <= _lastItemIndex; ++index)
{
if (_items[index].TryGetTarget(out object value))
{
NotifyItem(message, _items[index].RefInfo, value);
_items[index].RemoveTarget();
}
Debug.Assert(!_items[index].TryGetTarget(out object _), "Unexpected target after notifying");
}
_estimatedCount = 0;
}

// Shrink collection (if needed)
if (_items.Length > 100)
{
_lastItemIndex = 0;
_items = new CollectionEntry[DefaultCollectionSize];
}
}
finally
{
_isNotifying = false;
}
}
}
finally
{
ExitItemLockIfNeeded(lockObtained);
}
}

abstract protected void NotifyItem(int message, int refInfo, object value);

abstract public void Remove(object value);

protected void RemoveItem(object value)
{
Debug.Assert(null != value, "RemoveItem with null");

bool lockObtained = false;
try
{
TryEnterItemLock(ref lockObtained);

if (lockObtained)
{
// Find the value, and then remove the target from our collection
if (_estimatedCount > 0)
{
for (int index = 0; index <= _lastItemIndex; ++index)
{
if (_items[index].TryGetTarget(out object target) && value == target)
{
_items[index].RemoveTarget();
_estimatedCount--;
break;
}
}
}
}
}
finally
{
ExitItemLockIfNeeded(lockObtained);
}
}

// This is polling lock that will abandon getting the lock if _isNotifying is set to true
private void TryEnterItemLock(ref bool lockObtained)
{
// Assume that we couldn't take the lock
lockObtained = false;
// Keep trying to take the lock until either we've taken it, or the collection is being notified
while ((!_isNotifying) && (!lockObtained))
{
Monitor.TryEnter(_itemLock, LockPollTime, ref lockObtained);
}
}

private void ExitItemLockIfNeeded(bool lockObtained)
{
if (lockObtained)
{
Monitor.Exit(_itemLock);
}
}
}
}

0 comments on commit 34805b4

Please sign in to comment.