RecyclableMemoryStreamManager.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. // ---------------------------------------------------------------------
  2. // Copyright (c) 2015-2016 Microsoft
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. // ---------------------------------------------------------------------
  22. namespace Microsoft.IO
  23. {
  24. using System;
  25. using System.Collections.Concurrent;
  26. using System.Collections.Generic;
  27. using System.Diagnostics.CodeAnalysis;
  28. using System.IO;
  29. using System.Linq;
  30. using System.Threading;
  31. /// <summary>
  32. /// Manages pools of RecyclableMemoryStream objects.
  33. /// </summary>
  34. /// <remarks>
  35. /// There are two pools managed in here. The small pool contains same-sized buffers that are handed to streams
  36. /// as they write more data.
  37. ///
  38. /// For scenarios that need to call GetBuffer(), the large pool contains buffers of various sizes, all
  39. /// multiples of LargeBufferMultiple (1 MB by default). They are split by size to avoid overly-wasteful buffer
  40. /// usage. There should be far fewer 8 MB buffers than 1 MB buffers, for example.
  41. /// </remarks>
  42. public partial class RecyclableMemoryStreamManager
  43. {
  44. /// <summary>
  45. /// Generic delegate for handling events without any arguments.
  46. /// </summary>
  47. public delegate void EventHandler();
  48. /// <summary>
  49. /// Delegate for handling large buffer discard reports.
  50. /// </summary>
  51. /// <param name="reason">Reason the buffer was discarded.</param>
  52. public delegate void LargeBufferDiscardedEventHandler(Events.MemoryStreamDiscardReason reason);
  53. /// <summary>
  54. /// Delegate for handling reports of stream size when streams are allocated
  55. /// </summary>
  56. /// <param name="bytes">Bytes allocated.</param>
  57. public delegate void StreamLengthReportHandler(long bytes);
  58. /// <summary>
  59. /// Delegate for handling periodic reporting of memory use statistics.
  60. /// </summary>
  61. /// <param name="smallPoolInUseBytes">Bytes currently in use in the small pool.</param>
  62. /// <param name="smallPoolFreeBytes">Bytes currently free in the small pool.</param>
  63. /// <param name="largePoolInUseBytes">Bytes currently in use in the large pool.</param>
  64. /// <param name="largePoolFreeBytes">Bytes currently free in the large pool.</param>
  65. public delegate void UsageReportEventHandler(
  66. long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes);
  67. public const int DefaultBlockSize = 128 * 1024;
  68. public const int DefaultLargeBufferMultiple = 1024 * 1024;
  69. public const int DefaultMaximumBufferSize = 128 * 1024 * 1024;
  70. private readonly int blockSize;
  71. private readonly long[] largeBufferFreeSize;
  72. private readonly long[] largeBufferInUseSize;
  73. private readonly int largeBufferMultiple;
  74. /// <summary>
  75. /// pools[0] = 1x largeBufferMultiple buffers
  76. /// pools[1] = 2x largeBufferMultiple buffers
  77. /// etc., up to maximumBufferSize
  78. /// </summary>
  79. private readonly ConcurrentStack<byte[]>[] largePools;
  80. private readonly int maximumBufferSize;
  81. private readonly ConcurrentStack<byte[]> smallPool;
  82. private long smallPoolFreeSize;
  83. private long smallPoolInUseSize;
  84. /// <summary>
  85. /// Initializes the memory manager with the default block/buffer specifications.
  86. /// </summary>
  87. public RecyclableMemoryStreamManager()
  88. : this(DefaultBlockSize, DefaultLargeBufferMultiple, DefaultMaximumBufferSize) { }
  89. /// <summary>
  90. /// Initializes the memory manager with the given block requiredSize.
  91. /// </summary>
  92. /// <param name="blockSize">Size of each block that is pooled. Must be > 0.</param>
  93. /// <param name="largeBufferMultiple">Each large buffer will be a multiple of this value.</param>
  94. /// <param name="maximumBufferSize">Buffers larger than this are not pooled</param>
  95. /// <exception cref="ArgumentOutOfRangeException">blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize.</exception>
  96. /// <exception cref="ArgumentException">maximumBufferSize is not a multiple of largeBufferMultiple</exception>
  97. public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize)
  98. {
  99. if (blockSize <= 0)
  100. {
  101. throw new ArgumentOutOfRangeException(nameof(blockSize), blockSize, "blockSize must be a positive number");
  102. }
  103. if (largeBufferMultiple <= 0)
  104. {
  105. throw new ArgumentOutOfRangeException(nameof(largeBufferMultiple),
  106. "largeBufferMultiple must be a positive number");
  107. }
  108. if (maximumBufferSize < blockSize)
  109. {
  110. throw new ArgumentOutOfRangeException(nameof(maximumBufferSize),
  111. "maximumBufferSize must be at least blockSize");
  112. }
  113. this.blockSize = blockSize;
  114. this.largeBufferMultiple = largeBufferMultiple;
  115. this.maximumBufferSize = maximumBufferSize;
  116. if (!this.IsLargeBufferMultiple(maximumBufferSize))
  117. {
  118. throw new ArgumentException("maximumBufferSize is not a multiple of largeBufferMultiple",
  119. nameof(maximumBufferSize));
  120. }
  121. this.smallPool = new ConcurrentStack<byte[]>();
  122. var numLargePools = maximumBufferSize / largeBufferMultiple;
  123. // +1 to store size of bytes in use that are too large to be pooled
  124. this.largeBufferInUseSize = new long[numLargePools + 1];
  125. this.largeBufferFreeSize = new long[numLargePools];
  126. this.largePools = new ConcurrentStack<byte[]>[numLargePools];
  127. for (var i = 0; i < this.largePools.Length; ++i)
  128. {
  129. this.largePools[i] = new ConcurrentStack<byte[]>();
  130. }
  131. Events.Writer.MemoryStreamManagerInitialized(blockSize, largeBufferMultiple, maximumBufferSize);
  132. }
  133. /// <summary>
  134. /// The size of each block. It must be set at creation and cannot be changed.
  135. /// </summary>
  136. public int BlockSize => this.blockSize;
  137. /// <summary>
  138. /// All buffers are multiples of this number. It must be set at creation and cannot be changed.
  139. /// </summary>
  140. public int LargeBufferMultiple => this.largeBufferMultiple;
  141. /// <summary>
  142. /// Gets or sets the maximum buffer size.
  143. /// </summary>
  144. /// <remarks>Any buffer that is returned to the pool that is larger than this will be
  145. /// discarded and garbage collected.</remarks>
  146. public int MaximumBufferSize => this.maximumBufferSize;
  147. /// <summary>
  148. /// Number of bytes in small pool not currently in use
  149. /// </summary>
  150. public long SmallPoolFreeSize => this.smallPoolFreeSize;
  151. /// <summary>
  152. /// Number of bytes currently in use by stream from the small pool
  153. /// </summary>
  154. public long SmallPoolInUseSize => this.smallPoolInUseSize;
  155. /// <summary>
  156. /// Number of bytes in large pool not currently in use
  157. /// </summary>
  158. public long LargePoolFreeSize => this.largeBufferFreeSize.Sum();
  159. /// <summary>
  160. /// Number of bytes currently in use by streams from the large pool
  161. /// </summary>
  162. public long LargePoolInUseSize => this.largeBufferInUseSize.Sum();
  163. /// <summary>
  164. /// How many blocks are in the small pool
  165. /// </summary>
  166. public long SmallBlocksFree => this.smallPool.Count;
  167. /// <summary>
  168. /// How many buffers are in the large pool
  169. /// </summary>
  170. public long LargeBuffersFree
  171. {
  172. get
  173. {
  174. long free = 0;
  175. foreach (var pool in this.largePools)
  176. {
  177. free += pool.Count;
  178. }
  179. return free;
  180. }
  181. }
  182. /// <summary>
  183. /// How many bytes of small free blocks to allow before we start dropping
  184. /// those returned to us.
  185. /// </summary>
  186. public long MaximumFreeSmallPoolBytes { get; set; }
  187. /// <summary>
  188. /// How many bytes of large free buffers to allow before we start dropping
  189. /// those returned to us.
  190. /// </summary>
  191. public long MaximumFreeLargePoolBytes { get; set; }
  192. /// <summary>
  193. /// Maximum stream capacity in bytes. Attempts to set a larger capacity will
  194. /// result in an exception.
  195. /// </summary>
  196. /// <remarks>A value of 0 indicates no limit.</remarks>
  197. public long MaximumStreamCapacity { get; set; }
  198. /// <summary>
  199. /// Whether to save callstacks for stream allocations. This can help in debugging.
  200. /// It should NEVER be turned on generally in production.
  201. /// </summary>
  202. public bool GenerateCallStacks { get; set; }
  203. /// <summary>
  204. /// Whether dirty buffers can be immediately returned to the buffer pool. E.g. when GetBuffer() is called on
  205. /// a stream and creates a single large buffer, if this setting is enabled, the other blocks will be returned
  206. /// to the buffer pool immediately.
  207. /// Note when enabling this setting that the user is responsible for ensuring that any buffer previously
  208. /// retrieved from a stream which is subsequently modified is not used after modification (as it may no longer
  209. /// be valid).
  210. /// </summary>
  211. public bool AggressiveBufferReturn { get; set; }
  212. /// <summary>
  213. /// Removes and returns a single block from the pool.
  214. /// </summary>
  215. /// <returns>A byte[] array</returns>
  216. internal byte[] GetBlock()
  217. {
  218. byte[] block;
  219. if (!this.smallPool.TryPop(out block))
  220. {
  221. // We'll add this back to the pool when the stream is disposed
  222. // (unless our free pool is too large)
  223. block = new byte[this.BlockSize];
  224. Events.Writer.MemoryStreamNewBlockCreated(this.smallPoolInUseSize);
  225. ReportBlockCreated();
  226. }
  227. else
  228. {
  229. Interlocked.Add(ref this.smallPoolFreeSize, -this.BlockSize);
  230. }
  231. Interlocked.Add(ref this.smallPoolInUseSize, this.BlockSize);
  232. return block;
  233. }
  234. /// <summary>
  235. /// Returns a buffer of arbitrary size from the large buffer pool. This buffer
  236. /// will be at least the requiredSize and always be a multiple of largeBufferMultiple.
  237. /// </summary>
  238. /// <param name="requiredSize">The minimum length of the buffer</param>
  239. /// <param name="tag">The tag of the stream returning this buffer, for logging if necessary.</param>
  240. /// <returns>A buffer of at least the required size.</returns>
  241. internal byte[] GetLargeBuffer(int requiredSize, string tag)
  242. {
  243. requiredSize = this.RoundToLargeBufferMultiple(requiredSize);
  244. var poolIndex = requiredSize / this.largeBufferMultiple - 1;
  245. byte[] buffer;
  246. if (poolIndex < this.largePools.Length)
  247. {
  248. if (!this.largePools[poolIndex].TryPop(out buffer))
  249. {
  250. buffer = new byte[requiredSize];
  251. Events.Writer.MemoryStreamNewLargeBufferCreated(requiredSize, this.LargePoolInUseSize);
  252. ReportLargeBufferCreated();
  253. }
  254. else
  255. {
  256. Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], -buffer.Length);
  257. }
  258. }
  259. else
  260. {
  261. // Buffer is too large to pool. They get a new buffer.
  262. // We still want to track the size, though, and we've reserved a slot
  263. // in the end of the inuse array for nonpooled bytes in use.
  264. poolIndex = this.largeBufferInUseSize.Length - 1;
  265. // We still want to round up to reduce heap fragmentation.
  266. buffer = new byte[requiredSize];
  267. string callStack = null;
  268. if (this.GenerateCallStacks)
  269. {
  270. // Grab the stack -- we want to know who requires such large buffers
  271. callStack = Environment.StackTrace;
  272. }
  273. Events.Writer.MemoryStreamNonPooledLargeBufferCreated(requiredSize, tag, callStack);
  274. ReportLargeBufferCreated();
  275. }
  276. Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], buffer.Length);
  277. return buffer;
  278. }
  279. private int RoundToLargeBufferMultiple(int requiredSize)
  280. {
  281. return ((requiredSize + this.LargeBufferMultiple - 1) / this.LargeBufferMultiple) * this.LargeBufferMultiple;
  282. }
  283. private bool IsLargeBufferMultiple(int value)
  284. {
  285. return (value != 0) && (value % this.LargeBufferMultiple) == 0;
  286. }
  287. /// <summary>
  288. /// Returns the buffer to the large pool
  289. /// </summary>
  290. /// <param name="buffer">The buffer to return.</param>
  291. /// <param name="tag">The tag of the stream returning this buffer, for logging if necessary.</param>
  292. /// <exception cref="ArgumentNullException">buffer is null</exception>
  293. /// <exception cref="ArgumentException">buffer.Length is not a multiple of LargeBufferMultiple (it did not originate from this pool)</exception>
  294. internal void ReturnLargeBuffer(byte[] buffer, string tag)
  295. {
  296. if (buffer == null)
  297. {
  298. throw new ArgumentNullException(nameof(buffer));
  299. }
  300. if (!this.IsLargeBufferMultiple(buffer.Length))
  301. {
  302. throw new ArgumentException(
  303. "buffer did not originate from this memory manager. The size is not a multiple of " +
  304. this.LargeBufferMultiple);
  305. }
  306. var poolIndex = buffer.Length / this.largeBufferMultiple - 1;
  307. if (poolIndex < this.largePools.Length)
  308. {
  309. if ((this.largePools[poolIndex].Count + 1) * buffer.Length <= this.MaximumFreeLargePoolBytes ||
  310. this.MaximumFreeLargePoolBytes == 0)
  311. {
  312. this.largePools[poolIndex].Push(buffer);
  313. Interlocked.Add(ref this.largeBufferFreeSize[poolIndex], buffer.Length);
  314. }
  315. else
  316. {
  317. Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag,
  318. Events.MemoryStreamDiscardReason.EnoughFree);
  319. ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.EnoughFree);
  320. }
  321. }
  322. else
  323. {
  324. // This is a non-poolable buffer, but we still want to track its size for inuse
  325. // analysis. We have space in the inuse array for this.
  326. poolIndex = this.largeBufferInUseSize.Length - 1;
  327. Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag,
  328. Events.MemoryStreamDiscardReason.TooLarge);
  329. ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.TooLarge);
  330. }
  331. Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], -buffer.Length);
  332. ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize,
  333. this.LargePoolFreeSize);
  334. }
  335. /// <summary>
  336. /// Returns the blocks to the pool
  337. /// </summary>
  338. /// <param name="blocks">Collection of blocks to return to the pool</param>
  339. /// <param name="tag">The tag of the stream returning these blocks, for logging if necessary.</param>
  340. /// <exception cref="ArgumentNullException">blocks is null</exception>
  341. /// <exception cref="ArgumentException">blocks contains buffers that are the wrong size (or null) for this memory manager</exception>
  342. internal void ReturnBlocks(ICollection<byte[]> blocks, string tag)
  343. {
  344. if (blocks == null)
  345. {
  346. throw new ArgumentNullException(nameof(blocks));
  347. }
  348. var bytesToReturn = blocks.Count * this.BlockSize;
  349. Interlocked.Add(ref this.smallPoolInUseSize, -bytesToReturn);
  350. foreach (var block in blocks)
  351. {
  352. if (block == null || block.Length != this.BlockSize)
  353. {
  354. throw new ArgumentException("blocks contains buffers that are not BlockSize in length");
  355. }
  356. }
  357. foreach (var block in blocks)
  358. {
  359. if (this.MaximumFreeSmallPoolBytes == 0 || this.SmallPoolFreeSize < this.MaximumFreeSmallPoolBytes)
  360. {
  361. Interlocked.Add(ref this.smallPoolFreeSize, this.BlockSize);
  362. this.smallPool.Push(block);
  363. }
  364. else
  365. {
  366. Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Small, tag,
  367. Events.MemoryStreamDiscardReason.EnoughFree);
  368. ReportBlockDiscarded();
  369. break;
  370. }
  371. }
  372. ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize,
  373. this.LargePoolFreeSize);
  374. }
  375. internal void ReportBlockCreated()
  376. {
  377. this.BlockCreated?.Invoke();
  378. }
  379. internal void ReportBlockDiscarded()
  380. {
  381. this.BlockDiscarded?.Invoke();
  382. }
  383. internal void ReportLargeBufferCreated()
  384. {
  385. this.LargeBufferCreated?.Invoke();
  386. }
  387. internal void ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason reason)
  388. {
  389. this.LargeBufferDiscarded?.Invoke(reason);
  390. }
  391. internal void ReportStreamCreated()
  392. {
  393. this.StreamCreated?.Invoke();
  394. }
  395. internal void ReportStreamDisposed()
  396. {
  397. this.StreamDisposed?.Invoke();
  398. }
  399. internal void ReportStreamFinalized()
  400. {
  401. this.StreamFinalized?.Invoke();
  402. }
  403. internal void ReportStreamLength(long bytes)
  404. {
  405. this.StreamLength?.Invoke(bytes);
  406. }
  407. internal void ReportStreamToArray()
  408. {
  409. this.StreamConvertedToArray?.Invoke();
  410. }
  411. internal void ReportUsageReport(
  412. long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes)
  413. {
  414. this.UsageReport?.Invoke(smallPoolInUseBytes, smallPoolFreeBytes, largePoolInUseBytes, largePoolFreeBytes);
  415. }
  416. /// <summary>
  417. /// Retrieve a new MemoryStream object with no tag and a default initial capacity.
  418. /// </summary>
  419. /// <returns>A MemoryStream.</returns>
  420. public MemoryStream GetStream()
  421. {
  422. return new RecyclableMemoryStream(this);
  423. }
  424. /// <summary>
  425. /// Retrieve a new MemoryStream object with the given tag and a default initial capacity.
  426. /// </summary>
  427. /// <param name="tag">A tag which can be used to track the source of the stream.</param>
  428. /// <returns>A MemoryStream.</returns>
  429. public MemoryStream GetStream(string tag)
  430. {
  431. return new RecyclableMemoryStream(this, tag);
  432. }
  433. /// <summary>
  434. /// Retrieve a new MemoryStream object with the given tag and at least the given capacity.
  435. /// </summary>
  436. /// <param name="tag">A tag which can be used to track the source of the stream.</param>
  437. /// <param name="requiredSize">The minimum desired capacity for the stream.</param>
  438. /// <returns>A MemoryStream.</returns>
  439. public MemoryStream GetStream(string tag, int requiredSize)
  440. {
  441. return new RecyclableMemoryStream(this, tag, requiredSize);
  442. }
  443. /// <summary>
  444. /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using
  445. /// a single continugous underlying buffer.
  446. /// </summary>
  447. /// <remarks>Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations
  448. /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying
  449. /// buffers to a single large one. This is most helpful when you know that you will always call GetBuffer
  450. /// on the underlying stream.</remarks>
  451. /// <param name="tag">A tag which can be used to track the source of the stream.</param>
  452. /// <param name="requiredSize">The minimum desired capacity for the stream.</param>
  453. /// <param name="asContiguousBuffer">Whether to attempt to use a single contiguous buffer.</param>
  454. /// <returns>A MemoryStream.</returns>
  455. public MemoryStream GetStream(string tag, int requiredSize, bool asContiguousBuffer)
  456. {
  457. if (!asContiguousBuffer || requiredSize <= this.BlockSize)
  458. {
  459. return this.GetStream(tag, requiredSize);
  460. }
  461. return new RecyclableMemoryStream(this, tag, requiredSize, this.GetLargeBuffer(requiredSize, tag));
  462. }
  463. /// <summary>
  464. /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided
  465. /// buffer. The provided buffer is not wrapped or used after construction.
  466. /// </summary>
  467. /// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
  468. /// <param name="tag">A tag which can be used to track the source of the stream.</param>
  469. /// <param name="buffer">The byte buffer to copy data from.</param>
  470. /// <param name="offset">The offset from the start of the buffer to copy from.</param>
  471. /// <param name="count">The number of bytes to copy from the buffer.</param>
  472. /// <returns>A MemoryStream.</returns>
  473. [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
  474. public MemoryStream GetStream(string tag, byte[] buffer, int offset, int count)
  475. {
  476. var stream = new RecyclableMemoryStream(this, tag, count);
  477. stream.Write(buffer, offset, count);
  478. stream.Position = 0;
  479. return stream;
  480. }
  481. /// <summary>
  482. /// Triggered when a new block is created.
  483. /// </summary>
  484. public event EventHandler BlockCreated;
  485. /// <summary>
  486. /// Triggered when a new block is created.
  487. /// </summary>
  488. public event EventHandler BlockDiscarded;
  489. /// <summary>
  490. /// Triggered when a new large buffer is created.
  491. /// </summary>
  492. public event EventHandler LargeBufferCreated;
  493. /// <summary>
  494. /// Triggered when a new stream is created.
  495. /// </summary>
  496. public event EventHandler StreamCreated;
  497. /// <summary>
  498. /// Triggered when a stream is disposed.
  499. /// </summary>
  500. public event EventHandler StreamDisposed;
  501. /// <summary>
  502. /// Triggered when a stream is finalized.
  503. /// </summary>
  504. public event EventHandler StreamFinalized;
  505. /// <summary>
  506. /// Triggered when a stream is finalized.
  507. /// </summary>
  508. public event StreamLengthReportHandler StreamLength;
  509. /// <summary>
  510. /// Triggered when a user converts a stream to array.
  511. /// </summary>
  512. public event EventHandler StreamConvertedToArray;
  513. /// <summary>
  514. /// Triggered when a large buffer is discarded, along with the reason for the discard.
  515. /// </summary>
  516. public event LargeBufferDiscardedEventHandler LargeBufferDiscarded;
  517. /// <summary>
  518. /// Periodically triggered to report usage statistics.
  519. /// </summary>
  520. public event UsageReportEventHandler UsageReport;
  521. }
  522. }