BufferPool.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using System.Runtime.CompilerServices;
  5. using Best.HTTP.Shared.Logger;
  6. using System.Collections.Concurrent;
  7. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  8. using System.Linq;
  9. #endif
  10. namespace Best.HTTP.Shared.PlatformSupport.Memory
  11. {
  12. /// <summary>
  13. /// Light-weight user-mode lock for code blocks that has rare contentions and doesn't take a long time to finish.
  14. /// </summary>
  15. internal sealed class UserModeLock
  16. {
  17. private int _locked = 0;
  18. public void Acquire()
  19. {
  20. SpinWait spinWait = new SpinWait();
  21. while (Interlocked.CompareExchange(ref _locked, 1, 0) != 0)
  22. spinWait.SpinOnce();
  23. }
  24. public bool TryAcquire()
  25. {
  26. SpinWait spinWait = new SpinWait();
  27. while (Interlocked.CompareExchange(ref _locked, 1, 0) != 0)
  28. {
  29. if (spinWait.NextSpinWillYield)
  30. return false;
  31. spinWait.SpinOnce();
  32. }
  33. return true;
  34. }
  35. public void Release()
  36. {
  37. Interlocked.Exchange(ref _locked, 0);
  38. }
  39. }
  40. #if BESTHTTP_PROFILE
  41. public struct BufferStats
  42. {
  43. public long Size;
  44. public int Count;
  45. }
  46. public struct BufferPoolStats
  47. {
  48. public long GetBuffers;
  49. public long ReleaseBuffers;
  50. public long PoolSize;
  51. public long MaxPoolSize;
  52. public long MinBufferSize;
  53. public long MaxBufferSize;
  54. public long Borrowed;
  55. public long ArrayAllocations;
  56. public int FreeBufferCount;
  57. public List<BufferStats> FreeBufferStats;
  58. public TimeSpan NextMaintenance;
  59. }
  60. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  61. public readonly struct BorrowedBuffer
  62. {
  63. public readonly string StackTrace;
  64. public readonly LoggingContext Context;
  65. public BorrowedBuffer(string stackTrace, LoggingContext context)
  66. {
  67. this.StackTrace = stackTrace;
  68. this.Context = context;
  69. }
  70. }
  71. #endif
  72. #endif
  73. /// <summary>
  74. /// The BufferPool is a foundational element of the Best HTTP package, aiming to reduce dynamic memory allocation overheads by reusing byte arrays. The concept is elegantly simple: rather than allocating and deallocating memory for every requirement, byte arrays can be "borrowed" and "returned" within this pool. Once returned, these arrays are retained for subsequent use, minimizing repetitive memory operations.
  75. /// <para>While the BufferPool is housed within the Best HTTP package, its benefits are not limited to just HTTP operations. All protocols and packages integrated with or built upon the Best HTTP package utilize and benefit from the BufferPool. This ensures that memory is used efficiently and performance remains optimal across all integrated components.</para>
  76. /// </summary>
  77. [Best.HTTP.Shared.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
  78. public static class BufferPool
  79. {
  80. /// <summary>
  81. /// Represents an empty byte array that can be returned for zero-length requests.
  82. /// </summary>
  83. public static readonly byte[] NoData = new byte[0];
  84. /// <summary>
  85. /// Gets or sets a value indicating whether the buffer pooling mechanism is enabled or disabled.
  86. /// Disabling will also clear all stored entries.
  87. /// </summary>
  88. public static bool IsEnabled {
  89. get { return _isEnabled; }
  90. set
  91. {
  92. _isEnabled = value;
  93. // When set to non-enabled remove all stored entries
  94. if (!_isEnabled)
  95. Clear();
  96. }
  97. }
  98. private static volatile bool _isEnabled = true;
  99. /// <summary>
  100. /// Specifies the duration after which buffer entries, once released back to the pool, are deemed old and will be
  101. /// considered for removal in the next maintenance cycle.
  102. /// </summary>
  103. public static TimeSpan RemoveOlderThan = TimeSpan.FromSeconds(30);
  104. /// <summary>
  105. /// Specifies how frequently the maintenance cycle should run to manage old buffers.
  106. /// </summary>
  107. public static TimeSpan RunMaintenanceEvery = TimeSpan.FromSeconds(10);
  108. /// <summary>
  109. /// Specifies the minimum buffer size that will be allocated. If a request is made for a size smaller than this and canBeLarger is <c>true</c>,
  110. /// this size will be used.
  111. /// </summary>
  112. public static long MinBufferSize = 32;
  113. /// <summary>
  114. /// Specifies the maximum size of a buffer that the system will consider storing back into the pool.
  115. /// </summary>
  116. public static long MaxBufferSize = long.MaxValue;
  117. /// <summary>
  118. /// Specifies the maximum total size of all stored buffers. When the buffer reach this threshold, new releases will be declined.
  119. /// </summary>
  120. public static long MaxPoolSize = 30 * 1024 * 1024;
  121. /// <summary>
  122. /// Indicates whether to remove buffer stores that don't hold any buffers from the free list.
  123. /// </summary>
  124. public static bool RemoveEmptyLists = false;
  125. /// <summary>
  126. /// If set to <c>true</c>, and a byte array is released back to the pool more than once, an error will be logged.
  127. /// </summary>
  128. /// <remarks>Error checking is expensive and has a very large overhead! Turn it on with caution!</remarks>
  129. public static bool IsDoubleReleaseCheckEnabled = false;
  130. // It must be sorted by buffer size!
  131. private readonly static List<BufferStore> FreeBuffers = new List<BufferStore>();
  132. private static DateTime lastMaintenance = DateTime.MinValue;
  133. // Statistics
  134. private static long PoolSize = 0;
  135. private static long GetBuffers = 0;
  136. private static long ReleaseBuffers = 0;
  137. private static long Borrowed = 0;
  138. private static long ArrayAllocations = 0;
  139. #if BESTHTTP_PROFILE && BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  140. private static Dictionary<byte[], BorrowedBuffer> BorrowedBuffers = new Dictionary<byte[], BorrowedBuffer>();
  141. #endif
  142. //private readonly static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
  143. private readonly static UserModeLock _lock = new UserModeLock();
  144. static BufferPool()
  145. {
  146. #if UNITY_EDITOR
  147. IsDoubleReleaseCheckEnabled = true;
  148. #else
  149. IsDoubleReleaseCheckEnabled = false;
  150. #endif
  151. #if UNITY_ANDROID || UNITY_IOS
  152. UnityEngine.Application.lowMemory -= OnLowMemory;
  153. UnityEngine.Application.lowMemory += OnLowMemory;
  154. #endif
  155. }
  156. #if UNITY_EDITOR
  157. [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
  158. public static void ResetSetup()
  159. {
  160. HTTPManager.Logger.Information("BufferPool", "Reset called!");
  161. PoolSize = 0;
  162. GetBuffers = 0;
  163. ReleaseBuffers = 0;
  164. Borrowed = 0;
  165. ArrayAllocations = 0;
  166. FreeBuffers.Clear();
  167. lastMaintenance = DateTime.MinValue;
  168. #if UNITY_ANDROID || UNITY_IOS
  169. UnityEngine.Application.lowMemory -= OnLowMemory;
  170. UnityEngine.Application.lowMemory += OnLowMemory;
  171. #endif
  172. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  173. BorrowedBuffers.Clear();
  174. #endif
  175. }
  176. #endif
  177. #if UNITY_ANDROID || UNITY_IOS
  178. private static void OnLowMemory()
  179. {
  180. HTTPManager.Logger.Warning(nameof(BufferPool), nameof(OnLowMemory));
  181. Clear();
  182. }
  183. #endif
  184. /// <summary>
  185. /// Fetches a byte array from the pool.
  186. /// </summary>
  187. /// <remarks>Depending on the `canBeLarger` parameter, the returned buffer may be larger than the requested size!</remarks>
  188. /// <param name="size">Requested size of the buffer.</param>
  189. /// <param name="canBeLarger">If <c>true</c>, the returned buffer can be larger than the requested size.</param>
  190. /// <param name="context">Optional context for logging purposes.</param>
  191. /// <returns>A byte array from the pool or a newly allocated one if suitable size is not available.</returns>
  192. public static byte[] Get(long size, bool canBeLarger, LoggingContext context = null)
  193. {
  194. if (!_isEnabled)
  195. return new byte[size];
  196. // Return a fix reference for 0 length requests. Any resize call (even Array.Resize) creates a new reference
  197. // so we are safe to expose it to multiple callers.
  198. if (size == 0)
  199. return BufferPool.NoData;
  200. if (canBeLarger)
  201. {
  202. if (size < MinBufferSize)
  203. size = MinBufferSize;
  204. else if (!IsPowerOfTwo(size))
  205. size = NextPowerOf2(size);
  206. }
  207. else
  208. {
  209. if (size < MinBufferSize)
  210. return new byte[size];
  211. }
  212. if (FreeBuffers.Count == 0)
  213. {
  214. Interlocked.Add(ref Borrowed, size);
  215. Interlocked.Increment(ref ArrayAllocations);
  216. var result = new byte[size];
  217. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  218. lock (FreeBuffers)
  219. BorrowedBuffers.Add(result, new BorrowedBuffer(ProcessStackTrace(Environment.StackTrace), context));
  220. #endif
  221. return result;
  222. }
  223. BufferDesc bufferDesc = FindFreeBuffer(size, canBeLarger);
  224. if (bufferDesc.buffer == null)
  225. {
  226. Interlocked.Add(ref Borrowed, size);
  227. Interlocked.Increment(ref ArrayAllocations);
  228. var result = new byte[size];
  229. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  230. lock (FreeBuffers)
  231. BorrowedBuffers.Add(result, new BorrowedBuffer(ProcessStackTrace(Environment.StackTrace), context));
  232. #endif
  233. return result;
  234. }
  235. else
  236. {
  237. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  238. lock (FreeBuffers)
  239. BorrowedBuffers.Add(bufferDesc.buffer, new BorrowedBuffer(ProcessStackTrace(Environment.StackTrace), context));
  240. #endif
  241. Interlocked.Increment(ref GetBuffers);
  242. }
  243. Interlocked.Add(ref Borrowed, bufferDesc.buffer.Length);
  244. Interlocked.Add(ref PoolSize, -bufferDesc.buffer.Length);
  245. return bufferDesc.buffer;
  246. }
  247. /// <summary>
  248. /// Releases a list of buffer segments back to the pool in a bulk operation.
  249. /// </summary>
  250. /// <param name="segments">List of buffer segments to release.</param>
  251. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  252. public static void ReleaseBulk(ConcurrentQueue<BufferSegment> segments)
  253. {
  254. if (!_isEnabled || segments == null)
  255. return;
  256. //using var _ = new WriteLock(rwLock);
  257. _lock.Acquire();
  258. try
  259. {
  260. while (segments.TryDequeue(out var segment))
  261. Release(segment, false);
  262. }
  263. finally
  264. {
  265. _lock.Release();
  266. }
  267. }
  268. /// <summary>
  269. /// Releases a list of buffer segments back to the pool in a bulk operation.
  270. /// </summary>
  271. /// <param name="segments">List of buffer segments to release.</param>
  272. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  273. public static void ReleaseBulk(List<BufferSegment> segments)
  274. {
  275. if (!_isEnabled || segments == null)
  276. return;
  277. //using var _ = new WriteLock(rwLock);
  278. _lock.Acquire();
  279. try
  280. {
  281. while (segments.Count > 0)
  282. {
  283. var segment = segments[0];
  284. Release(segment, false);
  285. segments.RemoveAt(0);
  286. }
  287. }
  288. finally
  289. {
  290. _lock.Release();
  291. }
  292. }
  293. /// <summary>
  294. /// Releases a byte array back to the pool.
  295. /// </summary>
  296. /// <param name="buffer">Buffer to be released back to the pool.</param>
  297. public static void Release(byte[] buffer) => Release(buffer, true);
  298. private static void Release(byte[] buffer, bool acquireLock)
  299. {
  300. if (!_isEnabled || buffer == null)
  301. return;
  302. int size = buffer.Length;
  303. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  304. lock (FreeBuffers)
  305. BorrowedBuffers.Remove(buffer);
  306. #endif
  307. Interlocked.Add(ref Borrowed, -size);
  308. if (size == 0 || size < MinBufferSize || size > MaxBufferSize)
  309. return;
  310. if (!IsPowerOfTwo(size))
  311. return;
  312. //using (new WriteLock(rwLock))
  313. if (acquireLock)
  314. _lock.Acquire();
  315. try
  316. {
  317. var ps = Interlocked.Read(ref PoolSize);
  318. if (ps + size > MaxPoolSize)
  319. return;
  320. Interlocked.Add(ref PoolSize, size);
  321. ReleaseBuffers++;
  322. AddFreeBuffer(buffer);
  323. }
  324. finally
  325. {
  326. if (acquireLock)
  327. _lock.Release();
  328. }
  329. }
  330. /// <summary>
  331. /// Resizes a byte array by returning the old one to the pool and fetching (or creating) a new one of the specified size.
  332. /// </summary>
  333. /// <param name="buffer">Buffer to resize.</param>
  334. /// <param name="newSize">New size for the buffer.</param>
  335. /// <param name="canBeLarger">If <c>true</c>, the new buffer can be larger than the specified size.</param>
  336. /// <param name="clear">If <c>true</c>, the new buffer will be cleared (set to all zeros).</param>
  337. /// <param name="context">Optional context for logging purposes.</param>
  338. /// <returns>A resized buffer.</returns>
  339. public static byte[] Resize(ref byte[] buffer, int newSize, bool canBeLarger, bool clear, LoggingContext context = null)
  340. {
  341. if (!_isEnabled)
  342. {
  343. Array.Resize<byte>(ref buffer, newSize);
  344. return buffer;
  345. }
  346. byte[] newBuf = BufferPool.Get(newSize, canBeLarger, context);
  347. if (buffer != null)
  348. {
  349. if (!clear)
  350. Array.Copy(buffer, 0, newBuf, 0, Math.Min(newBuf.Length, buffer.Length));
  351. BufferPool.Release(buffer);
  352. }
  353. if (clear)
  354. Array.Clear(newBuf, 0, newSize);
  355. return buffer = newBuf;
  356. }
  357. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  358. public static KeyValuePair<byte[], BorrowedBuffer>[] GetBorrowedBuffers()
  359. {
  360. lock (FreeBuffers)
  361. return BorrowedBuffers.ToArray();
  362. }
  363. #endif
  364. #if BESTHTTP_PROFILE
  365. public static void GetStatistics(ref BufferPoolStats stats)
  366. {
  367. //using (new ReadLock(rwLock))
  368. if (!_lock.TryAcquire())
  369. return;
  370. try
  371. {
  372. stats.GetBuffers = GetBuffers;
  373. stats.ReleaseBuffers = ReleaseBuffers;
  374. stats.PoolSize = PoolSize;
  375. stats.MinBufferSize = MinBufferSize;
  376. stats.MaxBufferSize = MaxBufferSize;
  377. stats.MaxPoolSize = MaxPoolSize;
  378. stats.Borrowed = Borrowed;
  379. stats.ArrayAllocations = ArrayAllocations;
  380. stats.FreeBufferCount = FreeBuffers.Count;
  381. if (stats.FreeBufferStats == null)
  382. stats.FreeBufferStats = new List<BufferStats>(FreeBuffers.Count);
  383. else
  384. stats.FreeBufferStats.Clear();
  385. for (int i = 0; i < FreeBuffers.Count; ++i)
  386. {
  387. BufferStore store = FreeBuffers[i];
  388. List<BufferDesc> buffers = store.buffers;
  389. BufferStats bufferStats = new BufferStats();
  390. bufferStats.Size = store.Size;
  391. bufferStats.Count = buffers.Count;
  392. stats.FreeBufferStats.Add(bufferStats);
  393. }
  394. stats.NextMaintenance = (lastMaintenance + RunMaintenanceEvery) - DateTime.Now;
  395. }
  396. finally
  397. {
  398. _lock.Release();
  399. }
  400. }
  401. #endif
  402. /// <summary>
  403. /// Clears all stored entries in the buffer pool instantly, releasing memory.
  404. /// </summary>
  405. public static void Clear()
  406. {
  407. //using (new WriteLock(rwLock))
  408. _lock.Acquire();
  409. try
  410. {
  411. FreeBuffers.Clear();
  412. Interlocked.Exchange(ref PoolSize, 0);
  413. }
  414. finally
  415. {
  416. _lock.Release();
  417. }
  418. }
  419. /// <summary>
  420. /// Internal method called by the plugin to remove old, non-used buffers.
  421. /// </summary>
  422. internal static void Maintain()
  423. {
  424. DateTime now = DateTime.Now;
  425. if (!_isEnabled || lastMaintenance + RunMaintenanceEvery > now)
  426. return;
  427. DateTime olderThan = now - RemoveOlderThan;
  428. //using (new WriteLock(rwLock))
  429. if (!_lock.TryAcquire())
  430. return;
  431. lastMaintenance = now;
  432. try
  433. {
  434. for (int i = 0; i < FreeBuffers.Count; ++i)
  435. {
  436. BufferStore store = FreeBuffers[i];
  437. List<BufferDesc> buffers = store.buffers;
  438. for (int cv = buffers.Count - 1; cv >= 0; cv--)
  439. {
  440. BufferDesc desc = buffers[cv];
  441. if (desc.released < olderThan)
  442. {
  443. // buffers stores available buffers ascending by age. So, when we find an old enough, we can
  444. // delete all entries in the [0..cv] range.
  445. int removeCount = cv + 1;
  446. buffers.RemoveRange(0, removeCount);
  447. PoolSize -= (int)(removeCount * store.Size);
  448. break;
  449. }
  450. }
  451. if (RemoveEmptyLists && buffers.Count == 0)
  452. FreeBuffers.RemoveAt(i--);
  453. }
  454. }
  455. finally
  456. {
  457. _lock.Release();
  458. }
  459. }
  460. #region Private helper functions
  461. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  462. public static bool IsPowerOfTwo(long x)
  463. {
  464. return (x & (x - 1)) == 0;
  465. }
  466. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  467. public static long NextPowerOf2(long x)
  468. {
  469. long pow = 1;
  470. while (pow <= x)
  471. pow *= 2;
  472. return pow;
  473. }
  474. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  475. private static BufferDesc FindFreeBuffer(long size, bool canBeLarger)
  476. {
  477. // Previously it was an upgradable read lock, and later a write lock around store.buffers.RemoveAt.
  478. // However, checking store.buffers.Count in the if statement, and then get the last buffer and finally write lock the RemoveAt call
  479. // has plenty of time for race conditions.
  480. // Another thread could change store.buffers after checking count and getting the last element and before the write lock,
  481. // so in theory we could return with an element and remove another one from the buffers list.
  482. // A new FindFreeBuffer call could return it again causing malformed data and/or releasing it could duplicate it in the store.
  483. // I tried to reproduce both issues (malformed data, duble entries) with a test where creating growin number of threads getting buffers writing to them, check the buffers and finally release them
  484. // would fail _only_ if i used a plain Enter/Exit ReadLock pair, or no locking at all.
  485. // But, because there's quite a few different platforms and unity's implementation can be different too, switching from an upgradable lock to a more stricter write lock seems safer.
  486. //
  487. // An interesting read can be found here: https://stackoverflow.com/questions/21411018/readerwriterlockslim-enterupgradeablereadlock-always-a-deadlock
  488. //using (new WriteLock(rwLock))
  489. _lock.Acquire();
  490. try
  491. {
  492. for (int i = 0; i < FreeBuffers.Count; ++i)
  493. {
  494. BufferStore store = FreeBuffers[i];
  495. if (store.buffers.Count > 0 && (store.Size == size || (canBeLarger && store.Size > size)))
  496. {
  497. // Getting the last one has two desired effect:
  498. // 1.) RemoveAt should be quicker as it don't have to move all the remaining entries
  499. // 2.) Old, non-used buffers will age. Getting a buffer and putting it back will not keep buffers fresh.
  500. BufferDesc lastFree = store.buffers[store.buffers.Count - 1];
  501. store.buffers.RemoveAt(store.buffers.Count - 1);
  502. return lastFree;
  503. }
  504. }
  505. }
  506. finally
  507. {
  508. _lock.Release();
  509. }
  510. return BufferDesc.Empty;
  511. }
  512. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  513. private static void AddFreeBuffer(byte[] buffer)
  514. {
  515. int bufferLength = buffer.Length;
  516. for (int i = 0; i < FreeBuffers.Count; ++i)
  517. {
  518. BufferStore store = FreeBuffers[i];
  519. if (store.Size == bufferLength)
  520. {
  521. // We highly assume here that every buffer will be released only once.
  522. // Checking for double-release would mean that we have to do another O(n) operation, where n is the
  523. // count of the store's elements.
  524. if (IsDoubleReleaseCheckEnabled)
  525. for (int cv = 0; cv < store.buffers.Count; ++cv)
  526. {
  527. var entry = store.buffers[cv];
  528. if (System.Object.ReferenceEquals(entry.buffer, buffer))
  529. {
  530. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  531. if (BorrowedBuffers.TryGetValue(buffer, out var bb))
  532. {
  533. HTTPManager.Logger.Error("BufferPool", $"Buffer ({entry}) already added to the pool! BorrowedBuffer: {bb.StackTrace}", bb.Context);
  534. }
  535. else
  536. #endif
  537. HTTPManager.Logger.Error("BufferPool", $"Buffer ({entry}) already added to the pool!");
  538. //throw new Exception($"Buffer ({entry}) already added to the pool!");
  539. return;
  540. }
  541. }
  542. store.buffers.Add(new BufferDesc(buffer));
  543. return;
  544. }
  545. if (store.Size > bufferLength)
  546. {
  547. FreeBuffers.Insert(i, new BufferStore(bufferLength, buffer));
  548. return;
  549. }
  550. }
  551. // When we reach this point, there's no same sized or larger BufferStore present, so we have to add a new one
  552. // to the end of our list.
  553. FreeBuffers.Add(new BufferStore(bufferLength, buffer));
  554. }
  555. #if BESTHTTP_ENABLE_BUFFERPOOL_BORROWED_BUFFERS_COLLECTION
  556. private static System.Text.StringBuilder stacktraceBuilder;
  557. private static string ProcessStackTrace(string stackTrace)
  558. {
  559. if (string.IsNullOrEmpty(stackTrace))
  560. return string.Empty;
  561. var lines = stackTrace.Split('\n');
  562. if (stacktraceBuilder == null)
  563. stacktraceBuilder = new System.Text.StringBuilder(lines.Length);
  564. else
  565. stacktraceBuilder.Length = 0;
  566. // skip top 4 lines that would show the logger.
  567. for (int i = 0; i < lines.Length; ++i)
  568. if (!lines[i].Contains(".Memory.BufferPool") &&
  569. !lines[i].Contains("Environment") &&
  570. !lines[i].Contains("System.Threading"))
  571. stacktraceBuilder.Append(lines[i].Replace("Best.HTTP.", ""));
  572. return stacktraceBuilder.ToString();
  573. }
  574. #endif
  575. #endregion
  576. }
  577. }