diff --git a/CHANGELOG.md b/CHANGELOG.md index d642a6c..b7791a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # Changelog for CodeProject.ObjectPool # -### v3.0.4 (2017-06-24) ### +### v3.1.0 (2017-06-24) ### * Removed dependency on Thrower. +* Pooled objects can now specify a validation step (PR#4 by uliian). +* Removed CannotResetStateException class, not needed with new validation step. ### v3.0.3 (2017-04-08) ### diff --git a/README.md b/README.md index 20ecfe4..f4cecdc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Summary ## -* Latest release version: `v3.0.4` +* Latest release version: `v3.1.0` * Build status on [AppVeyor](https://ci.appveyor.com): [![Build status](https://ci.appveyor.com/api/projects/status/r4qnqaqj9ri6cicn?svg=true)](https://ci.appveyor.com/project/pomma89/objectpool) * [Doxygen](http://www.stack.nl/~dimitri/doxygen/index.html) documentation: + [HTML](http://pomma89.altervista.org/objectpool/doc/html/index.html) diff --git a/appveyor.yml b/appveyor.yml index 510cd1d..c7df853 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ #---------------------------------# # version format -version: 3.0.4.{build} +version: 3.1.0.{build} # branches to build branches: @@ -25,7 +25,7 @@ branches: assembly_info: patch: true file: AssemblyInfo.* - assembly_version: "3.0.4.{build}" + assembly_version: "3.1.0.{build}" assembly_file_version: "{version}" assembly_informational_version: "{version}" diff --git a/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj b/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj index 195679d..93f9b05 100644 --- a/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj +++ b/src/CodeProject.ObjectPool/CodeProject.ObjectPool.csproj @@ -2,7 +2,7 @@ CodeProject.ObjectPool Generic and concurrent Object Pool - 3.0.4 + 3.1.0 netstandard1.0;netstandard1.3;net35;net40;net45 true ../../pomma89.snk diff --git a/src/CodeProject.ObjectPool/Core/CannotResetStateException.cs b/src/CodeProject.ObjectPool/Core/PooledObjectDirection.cs similarity index 73% rename from src/CodeProject.ObjectPool/Core/CannotResetStateException.cs rename to src/CodeProject.ObjectPool/Core/PooledObjectDirection.cs index a30310e..516dcb1 100644 --- a/src/CodeProject.ObjectPool/Core/CannotResetStateException.cs +++ b/src/CodeProject.ObjectPool/Core/PooledObjectDirection.cs @@ -1,4 +1,4 @@ -// File name: CannotResetStateException.cs +// File name: PooledObjectDirection.cs // // Author(s): Alessio Parma // @@ -21,23 +21,21 @@ // 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; - namespace CodeProject.ObjectPool.Core { /// - /// This exception can be used to signal that the object whose state is being reset cannot be - /// added back to the pool for some reason. + /// Direction of a pooled object. /// - public sealed class CannotResetStateException : Exception + public enum PooledObjectDirection { /// - /// Builds the exception using given message. + /// An object is returning to the pool. + /// + Inbound, + + /// + /// An object is getting out of the pool. /// - /// The message. - public CannotResetStateException(string message) - : base(message) - { - } + Outbound } } \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/Core/PooledObjectValidationContext.cs b/src/CodeProject.ObjectPool/Core/PooledObjectValidationContext.cs new file mode 100644 index 0000000..8add3fd --- /dev/null +++ b/src/CodeProject.ObjectPool/Core/PooledObjectValidationContext.cs @@ -0,0 +1,52 @@ +// File name: PooledObjectValidationContext.cs +// +// Author(s): Alessio Parma +// +// The MIT License (MIT) +// +// Copyright (c) 2013-2018 Alessio Parma +// +// 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. + +namespace CodeProject.ObjectPool.Core +{ + /// + /// Contains additional info which might be useful when performing an object validation step. + /// + public sealed class PooledObjectValidationContext + { + /// + /// Used when an object is returning to the pool. + /// + internal static PooledObjectValidationContext Inbound { get; } = new PooledObjectValidationContext + { + Direction = PooledObjectDirection.Inbound + }; + + /// + /// Used when an object is going out of the pool. + /// + internal static PooledObjectValidationContext Outbound { get; } = new PooledObjectValidationContext + { + Direction = PooledObjectDirection.Outbound + }; + + /// + /// Whether an object is going out of the pool or into the pool. + /// + public PooledObjectDirection Direction { get; private set; } + } +} \ No newline at end of file diff --git a/src/CodeProject.ObjectPool/ObjectPool.cs b/src/CodeProject.ObjectPool/ObjectPool.cs index 9356ad6..10feb17 100644 --- a/src/CodeProject.ObjectPool/ObjectPool.cs +++ b/src/CodeProject.ObjectPool/ObjectPool.cs @@ -176,24 +176,30 @@ public void Clear() /// A monitored object from the pool. public T GetObject() { - if (PooledObjects.TryDequeue(out T pooledObject)) + while (true) { - // Object found in pool. - if (Diagnostics.Enabled) Diagnostics.IncrementPoolObjectHitCount(); - } - else - { - // This should not happen normally, but could be happening when there is stress on - // the pool. No available objects in pool, create a new one and return it to the caller. - if (Diagnostics.Enabled) Diagnostics.IncrementPoolObjectMissCount(); - pooledObject = CreatePooledObject(); - } - - // Change the state of the pooled object, marking it as reserved. We will mark it as - // available as soon as the object will return to the pool. - pooledObject.PooledObjectInfo.State = PooledObjectState.Reserved; + if (PooledObjects.TryDequeue(out T pooledObject)) + { + // Object found in pool. + if (Diagnostics.Enabled) Diagnostics.IncrementPoolObjectHitCount(); + } + else + { + // This should not happen normally, but could be happening when there is stress + // on the pool. No available objects in pool, create a new one and return it to + // the caller. + if (Diagnostics.Enabled) Diagnostics.IncrementPoolObjectMissCount(); + pooledObject = CreatePooledObject(); + } + if (pooledObject.ValidateObject(PooledObjectValidationContext.Outbound)) + { + // Change the state of the pooled object, marking it as reserved. We will mark it + // as available as soon as the object will return to the pool. + pooledObject.PooledObjectInfo.State = PooledObjectState.Reserved; - return pooledObject; + return pooledObject; + } + } } void IObjectPoolHandle.ReturnObjectToPool(PooledObject objectToReturnToPool, bool reRegisterForFinalization) diff --git a/src/CodeProject.ObjectPool/PooledObject.cs b/src/CodeProject.ObjectPool/PooledObject.cs index cc800da..c498e32 100644 --- a/src/CodeProject.ObjectPool/PooledObject.cs +++ b/src/CodeProject.ObjectPool/PooledObject.cs @@ -10,7 +10,6 @@ using CodeProject.ObjectPool.Core; using System; -using System.Collections.Generic; #if !NET35 @@ -48,6 +47,33 @@ public abstract class PooledObject : IDisposable, IEquatable #region Internal Methods - resource and state management + /// + /// Validates pooled object state. An invalid object will not get into the pool and it will + /// not be returned to consumers. + /// + /// The validation context. + /// True if current pooled object is valid, false otherwise. + internal bool ValidateObject(PooledObjectValidationContext validationContext) + { + if (OnValidateObject != null) + { + try + { + return OnValidateObject(validationContext); + } + catch (Exception ex) + { +#if !NET35 + if (Log.IsWarnEnabled()) Log.WarnException("[ObjectPool] An unexpected error occurred while validating an object", ex); +#else + System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings +#endif + return false; + } + } + return true; + } + /// /// Releases the object resources. This method will be called by the pool manager when /// there is no need for this object anymore (decreasing pooled objects count, pool is @@ -55,8 +81,6 @@ public abstract class PooledObject : IDisposable, IEquatable /// internal bool ReleaseResources() { - var successFlag = true; - if (OnReleaseResources != null) { try @@ -66,18 +90,14 @@ internal bool ReleaseResources() catch (Exception ex) { #if !NET35 - if (Log.IsWarnEnabled()) - { - Log.WarnException("[ObjectPool] An unexpected error occurred while releasing resources", ex); - } + if (Log.IsWarnEnabled()) Log.WarnException("[ObjectPool] An unexpected error occurred while releasing resources", ex); #else System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings #endif - successFlag = false; + return false; } } - - return successFlag; + return true; } /// @@ -86,47 +106,39 @@ internal bool ReleaseResources() /// internal bool ResetState() { - var successFlag = true; - + if (!ValidateObject(PooledObjectValidationContext.Inbound)) + { + return false; + } if (OnResetState != null) { try { OnResetState(); } - catch (CannotResetStateException crsex) - { -#if !NET35 - if (Log.IsDebugEnabled()) - { - Log.DebugException("[ObjectPool] Object state could not be reset", crsex); - } -#else - System.Diagnostics.Debug.Assert(crsex != null); // Placeholder to avoid warnings -#endif - successFlag = false; - } catch (Exception ex) { #if !NET35 - if (Log.IsWarnEnabled()) - { - Log.WarnException("[ObjectPool] An unexpected error occurred while resetting state", ex); - } + if (Log.IsWarnEnabled()) Log.WarnException("[ObjectPool] An unexpected error occurred while resetting state", ex); #else System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings #endif - successFlag = false; + return false; } } - - return successFlag; + return true; } #endregion Internal Methods - resource and state management #region Events - extending resource and state management + /// + /// Validates pooled object state. An invalid object will not get into the pool and it will + /// not be returned to consumers. + /// + public Func OnValidateObject { get; set; } + /// /// Reset the object state to allow this object to be re-used by other parts of the application. /// @@ -167,10 +179,7 @@ private void HandleReAddingToPool(bool reRegisterForFinalization) catch (Exception ex) { #if !NET35 - if (Log.IsWarnEnabled()) - { - Log.WarnException("[ObjectPool] An error occurred while re-adding to pool", ex); - } + if (Log.IsWarnEnabled()) Log.WarnException("[ObjectPool] An error occurred while re-adding to pool", ex); #else System.Diagnostics.Debug.Assert(ex != null); // Placeholder to avoid warnings #endif diff --git a/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs b/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs index bde80d6..6ae3a8a 100644 --- a/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs +++ b/src/CodeProject.ObjectPool/Specialized/PooledMemoryStream.cs @@ -22,9 +22,14 @@ // OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using CodeProject.ObjectPool.Core; -using System; using System.IO; +#if !NET35 + +using CodeProject.ObjectPool.Logging; + +#endif + namespace CodeProject.ObjectPool.Specialized { /// @@ -32,6 +37,14 @@ namespace CodeProject.ObjectPool.Specialized /// public class PooledMemoryStream : PooledObject { + #region Logging + +#if !NET35 + private static readonly ILog Log = LogProvider.GetLogger(typeof(PooledMemoryStream)); +#endif + + #endregion Logging + /// /// The tracked memory stream. /// @@ -48,23 +61,44 @@ public PooledMemoryStream(int capacity) Parent = this }; - OnResetState += () => + OnValidateObject += (ctx) => { + if (ctx.Direction == PooledObjectDirection.Outbound) + { + // We validate only inbound objects, because when they are in the pool they + // cannot change their state. + return true; + } + if (!_trackedMemoryStream.CanRead || !_trackedMemoryStream.CanWrite || !_trackedMemoryStream.CanSeek) { - throw new CannotResetStateException($"Memory stream has already been disposed"); +#if !NET35 + if (Log.IsWarnEnabled()) Log.Warn("[ObjectPool] Memory stream has already been disposed"); +#endif + return false; } var memoryStreamPool = PooledObjectInfo.Handle as IMemoryStreamPool; if (_trackedMemoryStream.Capacity < memoryStreamPool.MinimumMemoryStreamCapacity) { - throw new CannotResetStateException($"Memory stream capacity is {_trackedMemoryStream.Capacity}, while minimum required capacity is {memoryStreamPool.MinimumMemoryStreamCapacity}"); +#if !NET35 + if (Log.IsWarnEnabled()) Log.Warn($"[ObjectPool] Memory stream capacity is {_trackedMemoryStream.Capacity}, while minimum required capacity is {memoryStreamPool.MinimumMemoryStreamCapacity}"); +#endif + return false; } if (_trackedMemoryStream.Capacity > memoryStreamPool.MaximumMemoryStreamCapacity) { - throw new CannotResetStateException($"Memory stream capacity is {_trackedMemoryStream.Capacity}, while maximum allowed capacity is {memoryStreamPool.MaximumMemoryStreamCapacity}"); +#if !NET35 + if (Log.IsWarnEnabled()) Log.Warn($"[ObjectPool] Memory stream capacity is {_trackedMemoryStream.Capacity}, while maximum allowed capacity is {memoryStreamPool.MaximumMemoryStreamCapacity}"); +#endif + return false; } + return true; // Object is valid. + }; + + OnResetState += () => + { _trackedMemoryStream.Position = 0L; _trackedMemoryStream.SetLength(0L); }; diff --git a/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs b/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs index 755fbb6..e28e6c4 100644 --- a/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs +++ b/src/CodeProject.ObjectPool/Specialized/PooledStringBuilder.cs @@ -22,9 +22,14 @@ // OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using CodeProject.ObjectPool.Core; -using System; using System.Text; +#if !NET35 + +using CodeProject.ObjectPool.Logging; + +#endif + namespace CodeProject.ObjectPool.Specialized { /// @@ -32,6 +37,14 @@ namespace CodeProject.ObjectPool.Specialized /// public class PooledStringBuilder : PooledObject { + #region Logging + +#if !NET35 + private static readonly ILog Log = LogProvider.GetLogger(typeof(PooledStringBuilder)); +#endif + + #endregion Logging + /// /// The string builder. /// @@ -45,14 +58,29 @@ public PooledStringBuilder(int capacity) { StringBuilder = new StringBuilder(capacity); - OnResetState += () => + OnValidateObject += (ctx) => { + if (ctx.Direction == PooledObjectDirection.Outbound) + { + // We validate only inbound objects, because when they are in the pool they + // cannot change their state. + return true; + } + var stringBuilderPool = PooledObjectInfo.Handle as IStringBuilderPool; if (StringBuilder.Capacity > stringBuilderPool.MaximumStringBuilderCapacity) { - throw new CannotResetStateException($"String builder capacity is {StringBuilder.Capacity}, while maximum allowed capacity is {stringBuilderPool.MaximumStringBuilderCapacity}"); +#if !NET35 + if (Log.IsWarnEnabled()) Log.Warn($"[ObjectPool] String builder capacity is {StringBuilder.Capacity}, while maximum allowed capacity is {stringBuilderPool.MaximumStringBuilderCapacity}"); +#endif + return false; } + return true; // Object is valid. + }; + + OnResetState += () => + { ClearStringBuilder(); }; diff --git a/src/CodeProject.ObjectPool/doxyfile.txt b/src/CodeProject.ObjectPool/doxyfile.txt index 487a6f4..bcbf193 100644 --- a/src/CodeProject.ObjectPool/doxyfile.txt +++ b/src/CodeProject.ObjectPool/doxyfile.txt @@ -38,7 +38,7 @@ PROJECT_NAME = "Generic and concurrent Object Pool" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.0.4 +PROJECT_NUMBER = 3.1.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a