diff --git a/src/ServiceStack.Text/Pools/ObjectPool.cs b/src/ServiceStack.Text/Pools/ObjectPool.cs
new file mode 100644
index 000000000..de9c19f52
--- /dev/null
+++ b/src/ServiceStack.Text/Pools/ObjectPool.cs
@@ -0,0 +1,278 @@
+namespace ServiceStack.Text.Pools
+{
+ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+ // define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will
+ // make everything about 2-3x slower
+ //
+ // #define TRACE_LEAKS
+
+ // define DETECT_LEAKS to detect possible leaks
+ // #if DEBUG
+ // #define DETECT_LEAKS //for now always enable DETECT_LEAKS in debug.
+ // #endif
+
+ using System;
+ using System.Diagnostics;
+ using System.Threading;
+
+#if DETECT_LEAKS
+using System.Runtime.CompilerServices;
+#endif
+ ///
+ /// Generic implementation of object pooling pattern with predefined pool size limit. The main
+ /// purpose is that limited number of frequently used objects can be kept in the pool for
+ /// further recycling.
+ ///
+ /// Notes:
+ /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
+ /// is no space in the pool, extra returned objects will be dropped.
+ ///
+ /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
+ /// a relatively short time. Keeping checked out objects for long durations is ok, but
+ /// reduces usefulness of pooling. Just new up your own.
+ ///
+ /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
+ /// Rationale:
+ /// If there is no intent for reusing the object, do not use pool - just use "new".
+ ///
+ public class ObjectPool where T : class
+ {
+ [DebuggerDisplay("{Value,nq}")]
+ private struct Element
+ {
+ internal T Value;
+ }
+
+ ///
+ /// Not using System.Func{T} because this file is linked into the (debugger) Formatter,
+ /// which does not have that type (since it compiles against .NET 2.0).
+ ///
+ internal delegate T Factory();
+
+ // Storage for the pool objects. The first item is stored in a dedicated field because we
+ // expect to be able to satisfy most requests from it.
+ private T _firstItem;
+ private readonly Element[] _items;
+
+ // factory is stored for the lifetime of the pool. We will call this only when pool needs to
+ // expand. compared to "new T()", Func gives more flexibility to implementers and faster
+ // than "new T()".
+ private readonly Factory _factory;
+
+#if DETECT_LEAKS
+ private static readonly ConditionalWeakTable leakTrackers = new ConditionalWeakTable();
+
+ private class LeakTracker : IDisposable
+ {
+ private volatile bool disposed;
+
+#if TRACE_LEAKS
+ internal volatile object Trace = null;
+#endif
+
+ public void Dispose()
+ {
+ disposed = true;
+ GC.SuppressFinalize(this);
+ }
+
+ private string GetTrace()
+ {
+#if TRACE_LEAKS
+ return Trace == null ? "" : Trace.ToString();
+#else
+ return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n";
+#endif
+ }
+
+ ~LeakTracker()
+ {
+ if (!this.disposed && !Environment.HasShutdownStarted)
+ {
+ var trace = GetTrace();
+
+ // If you are seeing this message it means that object has been allocated from the pool
+ // and has not been returned back. This is not critical, but turns pool into rather
+ // inefficient kind of "new".
+ Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END");
+ }
+ }
+ }
+#endif
+
+ internal ObjectPool(Factory factory)
+ : this(factory, Environment.ProcessorCount * 2)
+ { }
+
+ internal ObjectPool(Factory factory, int size)
+ {
+ Debug.Assert(size >= 1);
+ _factory = factory;
+ _items = new Element[size - 1];
+ }
+
+ private T CreateInstance()
+ {
+ var inst = _factory();
+ return inst;
+ }
+
+ ///
+ /// Produces an instance.
+ ///
+ ///
+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+ /// Note that Free will try to store recycled objects close to the start thus statistically
+ /// reducing how far we will typically search.
+ ///
+ internal T Allocate()
+ {
+ // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
+ // Note that the initial read is optimistically not synchronized. That is intentional.
+ // We will interlock only when we have a candidate. in a worst case we may miss some
+ // recently returned objects. Not a big deal.
+ T inst = _firstItem;
+ if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
+ {
+ inst = AllocateSlow();
+ }
+
+#if DETECT_LEAKS
+ var tracker = new LeakTracker();
+ leakTrackers.Add(inst, tracker);
+
+#if TRACE_LEAKS
+ var frame = CaptureStackTrace();
+ tracker.Trace = frame;
+#endif
+#endif
+ return inst;
+ }
+
+ private T AllocateSlow()
+ {
+ var items = _items;
+
+ for (int i = 0; i < items.Length; i++)
+ {
+ // Note that the initial read is optimistically not synchronized. That is intentional.
+ // We will interlock only when we have a candidate. in a worst case we may miss some
+ // recently returned objects. Not a big deal.
+ T inst = items[i].Value;
+ if (inst != null)
+ {
+ if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
+ {
+ return inst;
+ }
+ }
+ }
+
+ return CreateInstance();
+ }
+
+ ///
+ /// Returns objects to the pool.
+ ///
+ ///
+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+ /// Note that Free will try to store recycled objects close to the start thus statistically
+ /// reducing how far we will typically search in Allocate.
+ ///
+ internal void Free(T obj)
+ {
+ Validate(obj);
+ ForgetTrackedObject(obj);
+
+ if (_firstItem == null)
+ {
+ // Intentionally not using interlocked here.
+ // In a worst case scenario two objects may be stored into same slot.
+ // It is very unlikely to happen and will only mean that one of the objects will get collected.
+ _firstItem = obj;
+ }
+ else
+ {
+ FreeSlow(obj);
+ }
+ }
+
+ private void FreeSlow(T obj)
+ {
+ var items = _items;
+ for (int i = 0; i < items.Length; i++)
+ {
+ if (items[i].Value == null)
+ {
+ // Intentionally not using interlocked here.
+ // In a worst case scenario two objects may be stored into same slot.
+ // It is very unlikely to happen and will only mean that one of the objects will get collected.
+ items[i].Value = obj;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Removes an object from leak tracking.
+ ///
+ /// This is called when an object is returned to the pool. It may also be explicitly
+ /// called if an object allocated from the pool is intentionally not being returned
+ /// to the pool. This can be of use with pooled arrays if the consumer wants to
+ /// return a larger array to the pool than was originally allocated.
+ ///
+ [Conditional("DEBUG")]
+ internal void ForgetTrackedObject(T old, T replacement = null)
+ {
+#if DETECT_LEAKS
+ LeakTracker tracker;
+ if (leakTrackers.TryGetValue(old, out tracker))
+ {
+ tracker.Dispose();
+ leakTrackers.Remove(old);
+ }
+ else
+ {
+ var trace = CaptureStackTrace();
+ Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END");
+ }
+
+ if (replacement != null)
+ {
+ tracker = new LeakTracker();
+ leakTrackers.Add(replacement, tracker);
+ }
+#endif
+ }
+
+#if DETECT_LEAKS
+ private static Lazy _stackTraceType = new Lazy(() => Type.GetType("System.Diagnostics.StackTrace"));
+
+ private static object CaptureStackTrace()
+ {
+ return Activator.CreateInstance(_stackTraceType.Value);
+ }
+#endif
+
+ [Conditional("DEBUG")]
+ private void Validate(object obj)
+ {
+ Debug.Assert(obj != null, "freeing null?");
+
+ Debug.Assert(_firstItem != obj, "freeing twice?");
+
+ var items = _items;
+ for (int i = 0; i < items.Length; i++)
+ {
+ var value = items[i].Value;
+ if (value == null)
+ {
+ return;
+ }
+
+ Debug.Assert(value != obj, "freeing twice?");
+ }
+ }
+ }
+}
diff --git a/src/ServiceStack.Text/Pools/PooledObject.cs b/src/ServiceStack.Text/Pools/PooledObject.cs
new file mode 100644
index 000000000..016251c41
--- /dev/null
+++ b/src/ServiceStack.Text/Pools/PooledObject.cs
@@ -0,0 +1,136 @@
+namespace ServiceStack.Text.Pools
+{
+ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ ///
+ /// this is RAII object to automatically release pooled object when its owning pool
+ ///
+ public struct PooledObject : IDisposable where T : class
+ {
+ private readonly Action, T> _releaser;
+ private readonly ObjectPool _pool;
+ private T _pooledObject;
+
+ public PooledObject(ObjectPool pool, Func, T> allocator, Action, T> releaser) : this()
+ {
+ _pool = pool;
+ _pooledObject = allocator(pool);
+ _releaser = releaser;
+ }
+
+ public T Object
+ {
+ get
+ {
+ return _pooledObject;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_pooledObject != null)
+ {
+ _releaser(_pool, _pooledObject);
+ _pooledObject = null;
+ }
+ }
+
+ #region factory
+ public static PooledObject Create(ObjectPool pool)
+ {
+ return new PooledObject(pool, Allocator, Releaser);
+ }
+
+ public static PooledObject> Create(ObjectPool> pool)
+ {
+ return new PooledObject>(pool, Allocator, Releaser);
+ }
+
+ public static PooledObject> Create(ObjectPool> pool)
+ {
+ return new PooledObject>(pool, Allocator, Releaser);
+ }
+
+ public static PooledObject> Create(ObjectPool> pool)
+ {
+ return new PooledObject>(pool, Allocator, Releaser);
+ }
+
+ public static PooledObject> Create(ObjectPool> pool)
+ {
+ return new PooledObject>(pool, Allocator, Releaser);
+ }
+
+ public static PooledObject> Create(ObjectPool> pool)
+ {
+ return new PooledObject>(pool, Allocator, Releaser);
+ }
+ #endregion
+
+ #region allocators and releasers
+ private static StringBuilder Allocator(ObjectPool pool)
+ {
+ return pool.AllocateAndClear();
+ }
+
+ private static void Releaser(ObjectPool pool, StringBuilder sb)
+ {
+ pool.ClearAndFree(sb);
+ }
+
+ private static Stack Allocator(ObjectPool> pool)
+ {
+ return pool.AllocateAndClear();
+ }
+
+ private static void Releaser(ObjectPool> pool, Stack obj)
+ {
+ pool.ClearAndFree(obj);
+ }
+
+ private static Queue Allocator(ObjectPool> pool)
+ {
+ return pool.AllocateAndClear();
+ }
+
+ private static void Releaser(ObjectPool> pool, Queue obj)
+ {
+ pool.ClearAndFree(obj);
+ }
+
+ private static HashSet Allocator(ObjectPool> pool)
+ {
+ return pool.AllocateAndClear();
+ }
+
+ private static void Releaser(ObjectPool> pool, HashSet obj)
+ {
+ pool.ClearAndFree(obj);
+ }
+
+ private static Dictionary Allocator(ObjectPool> pool)
+ {
+ return pool.AllocateAndClear();
+ }
+
+ private static void Releaser(ObjectPool> pool, Dictionary obj)
+ {
+ pool.ClearAndFree(obj);
+ }
+
+ private static List Allocator(ObjectPool> pool)
+ {
+ return pool.AllocateAndClear();
+ }
+
+ private static void Releaser(ObjectPool> pool, List obj)
+ {
+ pool.ClearAndFree(obj);
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/ServiceStack.Text/Pools/SharedPoolExtensions.cs b/src/ServiceStack.Text/Pools/SharedPoolExtensions.cs
new file mode 100644
index 000000000..d485c2571
--- /dev/null
+++ b/src/ServiceStack.Text/Pools/SharedPoolExtensions.cs
@@ -0,0 +1,201 @@
+namespace ServiceStack.Text.Pools
+{
+ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+ using System.Collections.Generic;
+ using System.Text;
+
+ internal static class SharedPoolExtensions
+ {
+ private const int Threshold = 512;
+
+ public static PooledObject GetPooledObject(this ObjectPool pool)
+ {
+ return PooledObject.Create(pool);
+ }
+
+ public static PooledObject> GetPooledObject(this ObjectPool> pool)
+ {
+ return PooledObject>.Create(pool);
+ }
+
+ public static PooledObject> GetPooledObject(this ObjectPool> pool)
+ {
+ return PooledObject>.Create(pool);
+ }
+
+ public static PooledObject> GetPooledObject(this ObjectPool> pool)
+ {
+ return PooledObject>.Create(pool);
+ }
+
+ public static PooledObject> GetPooledObject(this ObjectPool> pool)
+ {
+ return PooledObject>.Create(pool);
+ }
+
+ public static PooledObject> GetPooledObject(this ObjectPool> pool)
+ {
+ return PooledObject>.Create(pool);
+ }
+
+ public static PooledObject GetPooledObject(this ObjectPool pool) where T : class
+ {
+ return new PooledObject(pool, p => p.Allocate(), (p, o) => p.Free(o));
+ }
+
+ public static StringBuilder AllocateAndClear(this ObjectPool pool)
+ {
+ var sb = pool.Allocate();
+ sb.Clear();
+
+ return sb;
+ }
+
+ public static Stack AllocateAndClear(this ObjectPool> pool)
+ {
+ var set = pool.Allocate();
+ set.Clear();
+
+ return set;
+ }
+
+ public static Queue AllocateAndClear(this ObjectPool> pool)
+ {
+ var set = pool.Allocate();
+ set.Clear();
+
+ return set;
+ }
+
+ public static HashSet AllocateAndClear(this ObjectPool> pool)
+ {
+ var set = pool.Allocate();
+ set.Clear();
+
+ return set;
+ }
+
+ public static Dictionary AllocateAndClear(this ObjectPool> pool)
+ {
+ var map = pool.Allocate();
+ map.Clear();
+
+ return map;
+ }
+
+ public static List AllocateAndClear(this ObjectPool> pool)
+ {
+ var list = pool.Allocate();
+ list.Clear();
+
+ return list;
+ }
+
+ public static void ClearAndFree(this ObjectPool pool, StringBuilder sb)
+ {
+ if (sb == null)
+ {
+ return;
+ }
+
+ sb.Clear();
+
+ if (sb.Capacity > Threshold)
+ {
+ sb.Capacity = Threshold;
+ }
+
+ pool.Free(sb);
+ }
+
+ public static void ClearAndFree(this ObjectPool> pool, HashSet set)
+ {
+ if (set == null)
+ {
+ return;
+ }
+
+ var count = set.Count;
+ set.Clear();
+
+ if (count > Threshold)
+ {
+ set.TrimExcess();
+ }
+
+ pool.Free(set);
+ }
+
+ public static void ClearAndFree(this ObjectPool> pool, Stack set)
+ {
+ if (set == null)
+ {
+ return;
+ }
+
+ var count = set.Count;
+ set.Clear();
+
+ if (count > Threshold)
+ {
+ set.TrimExcess();
+ }
+
+ pool.Free(set);
+ }
+
+ public static void ClearAndFree(this ObjectPool> pool, Queue set)
+ {
+ if (set == null)
+ {
+ return;
+ }
+
+ var count = set.Count;
+ set.Clear();
+
+ if (count > Threshold)
+ {
+ set.TrimExcess();
+ }
+
+ pool.Free(set);
+ }
+
+ public static void ClearAndFree(this ObjectPool> pool, Dictionary map)
+ {
+ if (map == null)
+ {
+ return;
+ }
+
+ // if map grew too big, don't put it back to pool
+ if (map.Count > Threshold)
+ {
+ pool.ForgetTrackedObject(map);
+ return;
+ }
+
+ map.Clear();
+ pool.Free(map);
+ }
+
+ public static void ClearAndFree(this ObjectPool> pool, List list)
+ {
+ if (list == null)
+ {
+ return;
+ }
+
+ list.Clear();
+
+ if (list.Capacity > Threshold)
+ {
+ list.Capacity = Threshold;
+ }
+
+ pool.Free(list);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ServiceStack.Text/Pools/SharedPools.cs b/src/ServiceStack.Text/Pools/SharedPools.cs
new file mode 100644
index 000000000..b5aaf77ff
--- /dev/null
+++ b/src/ServiceStack.Text/Pools/SharedPools.cs
@@ -0,0 +1,81 @@
+namespace ServiceStack.Text.Pools
+{
+ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+ using System;
+ using System.Collections.Generic;
+
+ ///
+ /// Shared object pool for roslyn
+ ///
+ /// Use this shared pool if only concern is reducing object allocations.
+ /// if perf of an object pool itself is also a concern, use ObjectPool directly.
+ ///
+ /// For example, if you want to create a million of small objects within a second,
+ /// use the ObjectPool directly. it should have much less overhead than using this.
+ ///
+ public static class SharedPools
+ {
+ ///
+ /// pool that uses default constructor with 100 elements pooled
+ ///
+ public static ObjectPool BigDefault() where T : class, new()
+ {
+ return DefaultBigPool.Instance;
+ }
+
+ ///
+ /// pool that uses default constructor with 20 elements pooled
+ ///
+ public static ObjectPool Default() where T : class, new()
+ {
+ return DefaultNormalPool.Instance;
+ }
+
+ ///
+ /// pool that uses string as key with StringComparer.OrdinalIgnoreCase as key comparer
+ ///
+ public static ObjectPool> StringIgnoreCaseDictionary()
+ {
+ return StringIgnoreCaseDictionaryNormalPool.Instance;
+ }
+
+ ///
+ /// pool that uses string as element with StringComparer.OrdinalIgnoreCase as element comparer
+ ///
+ public static readonly ObjectPool> StringIgnoreCaseHashSet =
+ new ObjectPool>(() => new HashSet(StringComparer.OrdinalIgnoreCase), 20);
+
+ ///
+ /// pool that uses string as element with StringComparer.Ordinal as element comparer
+ ///
+ public static readonly ObjectPool> StringHashSet =
+ new ObjectPool>(() => new HashSet(StringComparer.Ordinal), 20);
+
+ ///
+ /// Used to reduce the # of temporary byte[]s created to satisfy serialization and
+ /// other I/O requests
+ ///
+ public static readonly ObjectPool ByteArray = new ObjectPool(() => new byte[ByteBufferSize], ByteBufferCount);
+
+ /// pooled memory : 4K * 512 = 4MB
+ public const int ByteBufferSize = 4 * 1024;
+ private const int ByteBufferCount = 512;
+
+ private static class DefaultBigPool where T : class, new()
+ {
+ public static readonly ObjectPool Instance = new ObjectPool(() => new T(), 100);
+ }
+
+ private static class DefaultNormalPool where T : class, new()
+ {
+ public static readonly ObjectPool Instance = new ObjectPool(() => new T(), 20);
+ }
+
+ private static class StringIgnoreCaseDictionaryNormalPool
+ {
+ public static readonly ObjectPool> Instance =
+ new ObjectPool>(() => new Dictionary(StringComparer.OrdinalIgnoreCase), 20);
+ }
+ }
+}
diff --git a/src/ServiceStack.Text/Pools/StringBuilderPool.cs b/src/ServiceStack.Text/Pools/StringBuilderPool.cs
new file mode 100644
index 000000000..fdef6eb91
--- /dev/null
+++ b/src/ServiceStack.Text/Pools/StringBuilderPool.cs
@@ -0,0 +1,25 @@
+namespace ServiceStack.Text.Pools
+{
+ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+ using System.Text;
+
+ public static class StringBuilderPool
+ {
+ public static StringBuilder Allocate()
+ {
+ return SharedPools.Default().AllocateAndClear();
+ }
+
+ public static void Free(StringBuilder builder)
+ {
+ SharedPools.Default().ClearAndFree(builder);
+ }
+
+ public static string ReturnAndFree(StringBuilder builder)
+ {
+ SharedPools.Default().ForgetTrackedObject(builder);
+ return builder.ToString();
+ }
+ }
+}
\ No newline at end of file