Skip to content

Commit

Permalink
Disable duplicate checks by default to improve perf/reduce allocations.
Browse files Browse the repository at this point in the history
Added ErrorOnIncorrectUsage to PoolPolicy and change Pool to only perform Contains check when enabled.
Keep local, thread safe, count of items in pool to avoid Count method on concurrent bag, which is not good for perf/allocations.
Fixed Xamarin projects so they actually contain the Pool implementation class.
Fix some XML comments.
Fix some CA warnings.
Increase version number to 2.0.
  • Loading branch information
Yortw committed May 8, 2016
1 parent d32b79d commit ad0a42f
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 33 deletions.
6 changes: 6 additions & 0 deletions .nuget/NuGet.Config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
</configuration>
Binary file added .nuget/NuGet.exe
Binary file not shown.
144 changes: 144 additions & 0 deletions .nuget/NuGet.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>

<!-- Enable the restore command to run before builds -->
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>

<!-- Property that enables building a package from a project -->
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>

<!-- Determines if package restore consent is required to restore packages -->
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>

<!-- Download NuGet.exe if it does not already exist -->
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
</PropertyGroup>

<ItemGroup Condition=" '$(PackageSources)' == '' ">
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
<!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
<!--
<PackageSource Include="https://www.nuget.org/api/v2/" />
<PackageSource Include="https://my-nuget-source/nuget/" />
-->
</ItemGroup>

<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
<!-- Windows specific commands -->
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
</PropertyGroup>

<PropertyGroup>
<PackagesProjectConfig Condition=" '$(OS)' == 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config</PackagesProjectConfig>
<PackagesProjectConfig Condition=" '$(OS)' != 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config</PackagesProjectConfig>
</PropertyGroup>

<PropertyGroup>
<PackagesConfig Condition="Exists('$(MSBuildProjectDirectory)\packages.config')">$(MSBuildProjectDirectory)\packages.config</PackagesConfig>
<PackagesConfig Condition="Exists('$(PackagesProjectConfig)')">$(PackagesProjectConfig)</PackagesConfig>
</PropertyGroup>

<PropertyGroup>
<!-- NuGet command -->
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>

<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 "$(NuGetExePath)"</NuGetCommand>

<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>

<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>

<PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
<PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>

<!-- Commands -->
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>

<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
RestorePackages;
$(BuildDependsOn);
</BuildDependsOn>

<!-- Make the build depend on restore packages -->
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
$(BuildDependsOn);
BuildPackage;
</BuildDependsOn>
</PropertyGroup>

<Target Name="CheckPrerequisites">
<!-- Raise an error if we're unable to locate nuget.exe -->
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
<!--
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
This effectively acts as a lock that makes sure that the download operation will only happen once and all
parallel builds will have to wait for it to complete.
-->
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
</Target>

<Target Name="_DownloadNuGet">
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
</Target>

<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(RestoreCommand)"
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />

<Exec Command="$(RestoreCommand)"
LogStandardErrorAsError="true"
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
</Target>

<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(BuildCommand)"
Condition=" '$(OS)' != 'Windows_NT' " />

<Exec Command="$(BuildCommand)"
LogStandardErrorAsError="true"
Condition=" '$(OS)' == 'Windows_NT' " />
</Target>

<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputFilename ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Net" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
OutputFilename = Path.GetFullPath(OutputFilename);
Log.LogMessage("Downloading latest version of NuGet.exe...");
WebClient webClient = new WebClient();
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
</Project>
1 change: 1 addition & 0 deletions src/PoolSharp.Android/PoolSharp.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<AndroidResource Include="Resources\Values\Strings.xml" />
</ItemGroup>
<Import Project="..\PoolSharp.Shared\PoolSharp.Shared.projitems" Label="Shared" />
<Import Project="..\PoolSharp.Shared.ConcurrentBagImplementation\PoolSharp.Shared.ConcurrentBagImplementation.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
1 change: 0 additions & 1 deletion src/PoolSharp.Android/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("PoolSharp.Android")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]
10 changes: 5 additions & 5 deletions src/PoolSharp.Bait/Pool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ namespace PoolSharp
/// </summary>
/// <remarks>
/// <para>This pool does not block when a new item is requested and the pool is empty, instead a new will be allocated and returned.</para>
/// <para>By default the pool starts empty and items are allocated as needed. The <see cref="Expand()"/> method can be used to pre-load the pool if required./para>
/// </remarks>
/// <para>By default the pool starts empty and items are allocated as needed. The <see cref="Expand()"/> method can be used to pre-load the pool if required.</para>
/// <para>Objects returned to the pool are taken on a first come first serve basis. If the pool is full when an object is returned, it is ignored (and will be garbage collected if there are no other references to it). In this case, if the item implements <see cref="IDisposable"/> the pool will ensure it is disposed before being 'ignored'.</para>
/// <para>The pool makes a best effort attempt to avoid going over the specified <see cref="PoolPolicy{T}.MaximumPoolSize"/>, but does not strictly enforce it. Under certain multi-threaded scenarios it's possible for a few items more than the maximum to be kept in the pool.</para>
/// <para>Disposing the pool will also dispose all objects currently in the pool, if they support <see cref="IDisposable"/>.</para>
/// </remarks>
/// <typeparam name="T">The type of value being pooled.</typeparam>
/// <seealso cref="PoolPolicy{T}"/>
/// <seealso cref="IPool{T}"/>
Expand Down Expand Up @@ -102,7 +102,7 @@ public void Expand(int increment)
#region Public Methods

/// <summary>
/// Throws a <see cref="ObjectDisposedException"/> if the <see cref="Dispose"/> method has been called.
/// Throws a <see cref="ObjectDisposedException"/> if the <see cref="Dispose()"/> method has been called.
/// </summary>
protected void CheckDisposed()
{
Expand All @@ -116,7 +116,7 @@ protected void CheckDisposed()
/// <summary>
/// Returns a boolean indicating if this pool is disposed or not.
/// </summary>
/// <seealso cref="Dispose"/>
/// <seealso cref="Dispose()"/>
/// <seealso cref="Dispose(bool)"/>
public bool IsDisposed
{
Expand Down Expand Up @@ -151,7 +151,7 @@ public void Dispose()
/// Performs dispose logic, can be overridden by derivded types.
/// </summary>
/// <param name="disposing">True if the pool is being explicitly disposed, false if it is being disposed from a finalizer.</param>
/// <seealso cref="Dispose"/>
/// <seealso cref="Dispose()"/>
/// <seealso cref="IsDisposed"/>
protected virtual void Dispose(bool disposing)
{
Expand Down
3 changes: 1 addition & 2 deletions src/PoolSharp.Bait/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("PoolSharp.Bait")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyTitle("PoolSharp.Bait")]
1 change: 0 additions & 1 deletion src/PoolSharp.Net40/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ccd27104-a79b-4454-9d8b-0153b3ffa37e")]

[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(true)]
49 changes: 36 additions & 13 deletions src/PoolSharp.Shared.ConcurrentBagImplementation/Pool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;

namespace PoolSharp
{
Expand All @@ -11,7 +12,7 @@ namespace PoolSharp
/// </summary>
/// <remarks>
/// <para>This pool does not block when a new item is requested and the pool is empty, instead a new will be allocated and returned.</para>
/// <para>By default the pool starts empty and items are allocated as needed. The <see cref="Expand()"/> method can be used to pre-load the pool if required./para>
/// <para>By default the pool starts empty and items are allocated as needed. The <see cref="Expand()"/> method can be used to pre-load the pool if required.</para>
/// <para>Objects returned to the pool are added on a first come first serve basis. If the pool is full when an object is returned, it is ignored (and will be garbage collected if there are no other references to it). In this case, if the item implements <see cref="IDisposable"/> the pool will ensure the item is disposed before being 'ignored'.</para>
/// <para>The pool makes a best effort attempt to avoid going over the specified <see cref="PoolPolicy{T}.MaximumPoolSize"/>, but does not strictly enforce it. Under certain multi-threaded scenarios it's possible for a few items more than the maximum to be kept in the pool.</para>
/// <para>Disposing the pool will also dispose all objects currently in the pool, if they support <see cref="IDisposable"/>.</para>
Expand All @@ -32,6 +33,8 @@ public class Pool<T> : IPool<T>
private readonly bool _IsPooledTypeWrapped;
private bool _IsDisposed;

private long _PoolInstancesCount;

private PropertyInfo _PooledObjectValueProperty;

#if SUPPORTS_THREADS
Expand Down Expand Up @@ -94,6 +97,8 @@ public T Take()

if (_Pool.TryTake(out retVal))
{
Interlocked.Decrement(ref _PoolInstancesCount);

if (_PoolPolicy.InitializationPolicy == PooledItemInitialization.Take && _PoolPolicy.ReinitializeObject != null)
_PoolPolicy.ReinitializeObject(retVal);
}
Expand Down Expand Up @@ -127,14 +132,15 @@ public void Add(T value)

if (ShouldReturnToPool(value))
{
if (_PoolPolicy.InitializationPolicy == PooledItemInitialization.Take || _PoolPolicy.ReinitializeObject == null)
_Pool.Add(value);
else if (_PoolPolicy.InitializationPolicy == PooledItemInitialization.AsyncReturn)
if (_PoolPolicy.InitializationPolicy == PooledItemInitialization.AsyncReturn)
SafeAddToReinitialiseQueue(value);
else if (_PoolPolicy.InitializationPolicy == PooledItemInitialization.Return)
else
{
_PoolPolicy.ReinitializeObject(value);
if (_PoolPolicy.InitializationPolicy == PooledItemInitialization.Return && _PoolPolicy.ReinitializeObject != null)
_PoolPolicy.ReinitializeObject(value);

_Pool.Add(value);
Interlocked.Increment(ref _PoolInstancesCount);
}
}
else
Expand All @@ -160,7 +166,7 @@ public void Expand()
/// <param name="increment">The maximum number of items to pre-allocate and add to the pool.</param>
/// <remarks>
/// <para>This method is 'thread safe', though it is possible under certain race conditons for the pool to go beyond it's configured maximum size by a few items.</para>
/// <para>If <paramref name="incremenet"/> is zero or less the method returns without doing anything</para>
/// <para>If <paramref name="increment"/> is zero or less the method returns without doing anything</para>
/// </remarks>
/// <exception cref="System.ObjectDisposedException">Thrown if this method is called on a disposed pool.</exception>
public void Expand(int increment)
Expand All @@ -170,19 +176,21 @@ public void Expand(int increment)
if (increment <= 0) return;

int createdCount = 0;
while (createdCount < increment && (_Pool.Count < _PoolPolicy.MaximumPoolSize || _PoolPolicy.MaximumPoolSize <= 0))
while (createdCount < increment && !IsPoolFull())
{
_Pool.Add(_PoolPolicy.Factory(this));
Interlocked.Increment(ref _PoolInstancesCount);
createdCount++;
}

}

#endregion

#region Public Methods

/// <summary>
/// Throws a <see cref="ObjectDisposedException"/> if the <see cref="Dispose"/> method has been called.
/// Throws a <see cref="ObjectDisposedException"/> if the <see cref="Dispose()"/> method has been called.
/// </summary>
protected void CheckDisposed()
{
Expand All @@ -196,7 +204,7 @@ protected void CheckDisposed()
/// <summary>
/// Returns a boolean indicating if this pool is disposed or not.
/// </summary>
/// <seealso cref="Dispose"/>
/// <seealso cref="Dispose()"/>
/// <seealso cref="Dispose(bool)"/>
public bool IsDisposed
{
Expand Down Expand Up @@ -231,7 +239,7 @@ public void Dispose()
/// Performs dispose logic, can be overridden by derivded types.
/// </summary>
/// <param name="disposing">True if the pool is being explicitly disposed, false if it is being disposed from a finalizer.</param>
/// <seealso cref="Dispose"/>
/// <seealso cref="Dispose()"/>
/// <seealso cref="IsDisposed"/>
protected virtual void Dispose(bool disposing)
{
Expand Down Expand Up @@ -279,8 +287,12 @@ private void BackgroundReinitialise()
if (_PoolPolicy.ReinitializeObject != null)
_PoolPolicy.ReinitializeObject(item);

if (_PoolPolicy.MaximumPoolSize <= 0 || _Pool.Count < _PoolPolicy.MaximumPoolSize)
if (ShouldReturnToPool(item))
{
_Pool.Add(item);

Interlocked.Increment(ref _PoolInstancesCount);
}
}
}
}
Expand Down Expand Up @@ -321,7 +333,10 @@ private void ReinitialiseAndReturnToPoolOrDispose(T value)
{
_PoolPolicy.ReinitializeObject(value);
if (ShouldReturnToPool(value))
{
_Pool.Add(value);
Interlocked.Increment(ref _PoolInstancesCount);
}
else
SafeDispose(value);
}
Expand Down Expand Up @@ -357,7 +372,15 @@ private void SafeDispose(object pooledObject)

private bool ShouldReturnToPool(T pooledObject)
{
return (_PoolPolicy.MaximumPoolSize == 0 || _Pool.Count < _PoolPolicy.MaximumPoolSize) && !_Pool.Contains(pooledObject);
if (_PoolPolicy.ErrorOnIncorrectUsage && _Pool.Contains(pooledObject))
throw new InvalidOperationException("Object already exists in pool. Duplicate add detected.");

return !IsPoolFull();
}

private bool IsPoolFull()
{
return (_PoolPolicy.MaximumPoolSize > 0 && _PoolInstancesCount >= _PoolPolicy.MaximumPoolSize);
}

private static bool IsTypeWrapped(Type type)
Expand Down
3 changes: 2 additions & 1 deletion src/PoolSharp.Shared/AssemblyInfoCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
[assembly: CLSCompliant(true)]
[assembly: NeutralResourcesLanguage("en")]

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
4 changes: 2 additions & 2 deletions src/PoolSharp.Shared/IPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace PoolSharp
/// Interface for a simple object pool.
/// </summary>
/// <typeparam name="T">The type of value being pooled.</typeparam>
/// <seealso cref="Pool{T}"/>
/// <seealso cref="PoolSharp.Pool{T}"/>
/// <seealso cref="PooledObject{T}"/>
/// <seealso cref="PoolPolicy{T}"/>
public interface IPool<T> : IDisposable
Expand Down Expand Up @@ -51,7 +51,7 @@ public interface IPool<T> : IDisposable
/// <param name="increment">The maximum number of items to pre-allocate and add to the pool.</param>
/// <remarks>
/// <para>This method is 'thread safe', though it is possible under certain race conditons for the pool to go beyond it's configured maximum size by a few items.</para>
/// <para>If <paramref name="incremenet"/> is zero or less the method returns without doing anything</para>
/// <para>If <paramref name="increment"/> is zero or less the method returns without doing anything</para>
/// </remarks>
/// <exception cref="System.ObjectDisposedException">Thrown if this method is called on a disposed pool.</exception>
void Expand(int increment);
Expand Down
Loading

0 comments on commit ad0a42f

Please sign in to comment.