private BufferPartition GetLatestBufferWithAvailableSpaceOrDefault() { BufferPartition latestBuffer = BufferSet.LastOrDefault(); if (latestBuffer == default || latestBuffer.DataLength >= latestBuffer.Buffer.Length) { return(default);
public override void Write(byte[] buffer, int offset, int count) { while (count > 0) { BufferPartition currentBuffer = GetLatestBufferWithAvailableSpaceOrDefault(); if (currentBuffer == default) { byte[] newBytes = ArrayPool.Rent(MaxArraySize); currentBuffer = new BufferPartition { Buffer = newBytes, DataLength = 0 }; BufferSet.Add(currentBuffer); } int copied = Math.Min(currentBuffer.Buffer.Length - currentBuffer.DataLength, count); Array.Copy(buffer, offset, currentBuffer.Buffer, currentBuffer.DataLength, copied); currentBuffer.DataLength += copied; count -= copied; offset += copied; Position += copied; } }
/// <summary> /// Buffers a portion of the given stream, returning the buffered stream partition. /// </summary> /// <param name="stream"> /// Stream to buffer from. /// </param> /// <param name="minCount"> /// Minimum number of bytes to buffer. This method will not return until at least this many bytes have been read from <paramref name="stream"/> or the stream completes. /// </param> /// <param name="maxCount"> /// Maximum number of bytes to buffer. /// </param> /// <param name="absolutePosition"> /// Current position of the stream, since <see cref="Stream.Position"/> throws if not seekable. /// </param> /// <param name="arrayPool"> /// Pool to rent buffer space from. /// </param> /// <param name="maxArrayPoolRentalSize"> /// Max size we can request from the array pool. /// </param> /// <param name="async"> /// Whether to perform this operation asynchronously. /// </param> /// <param name="cancellationToken"> /// Cancellation token. /// </param> /// <returns> /// The buffered stream partition with memory backed by an array pool. /// </returns> internal static async Task <PooledMemoryStream> BufferStreamPartitionInternal( Stream stream, long minCount, long maxCount, long absolutePosition, ArrayPool <byte> arrayPool, int?maxArrayPoolRentalSize, bool async, CancellationToken cancellationToken) { long totalRead = 0; var streamPartition = new PooledMemoryStream(arrayPool, absolutePosition, maxArrayPoolRentalSize ?? DefaultMaxArrayPoolRentalSize); // max count to write into a single array int maxCountIndividualBuffer; // min count to write into a single array int minCountIndividualBuffer; // the amount that was written into the current array int readIndividualBuffer; do { // buffer to write to byte[] buffer; // offset to start writing at int offset; BufferPartition latestBuffer = streamPartition.GetLatestBufferWithAvailableSpaceOrDefault(); // whether we got a brand new buffer to write into bool newbuffer; if (latestBuffer != default) { buffer = latestBuffer.Buffer; offset = latestBuffer.DataLength; newbuffer = false; } else { buffer = arrayPool.Rent((int)Math.Min(maxCount - totalRead, streamPartition.MaxArraySize)); offset = 0; newbuffer = true; } // limit max and min count for this buffer by buffer length maxCountIndividualBuffer = (int)Math.Min(maxCount - totalRead, buffer.Length - offset); // definitionally limited by max; we won't ever have a swapped min/max range minCountIndividualBuffer = (int)Math.Min(minCount - totalRead, maxCountIndividualBuffer); readIndividualBuffer = await ReadLoopInternal( stream, buffer, offset : offset, minCountIndividualBuffer, maxCountIndividualBuffer, async, cancellationToken).ConfigureAwait(false); // if nothing was placed in a brand new array if (readIndividualBuffer == 0 && newbuffer) { arrayPool.Return(buffer); } // if brand new array and we did place data in it else if (newbuffer) { streamPartition.BufferSet.Add(new BufferPartition { Buffer = buffer, DataLength = readIndividualBuffer }); } // added to an existing array that was not entirely filled else { latestBuffer.DataLength += readIndividualBuffer; } totalRead += readIndividualBuffer; /* If we filled the buffer this loop, then quitting on min count is pointless. The point of quitting * on min count is when the source stream doesn't have available bytes and we've reached an amount worth * sending instead of blocking on. If we filled the available array, we don't actually know whether more * data is available yet, as we limited our read for reasons outside the stream state. We should therefore * try another read regardless of whether we hit min count. */ } while ( // stream is done if this value is zero; no other check matters readIndividualBuffer != 0 && // stop filling the partition if we've hit the max size of the partition totalRead < maxCount && // stop filling the partition if we've reached min count and we know we've hit at least a pause in the stream (totalRead < minCount || readIndividualBuffer == maxCountIndividualBuffer)); return(streamPartition); }