Skip to content

Commit 83b41e5

Browse files
committed
Fixed concatenation of unbounded number of streams
1 parent 781ad68 commit 83b41e5

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

src/DotNext.IO/IO/SparseStream.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics;
12
using System.Runtime.CompilerServices;
23
using System.Runtime.InteropServices;
34

@@ -207,9 +208,20 @@ protected override ReadOnlySpan<Stream> Streams
207208
=> MemoryMarshal.CreateReadOnlySpan(in Unsafe.As<T, Stream>(ref Unsafe.AsRef(in streams)), streams.Length);
208209
}
209210

210-
internal sealed class UnboundedSparseStream(ReadOnlySpan<Stream> streams, bool leaveOpen) : SparseStream(leaveOpen)
211+
internal sealed class UnboundedSparseStream : SparseStream
211212
{
212-
private MemoryOwner<Stream> streams = streams.Copy();
213+
private MemoryOwner<Stream> streams;
214+
215+
internal UnboundedSparseStream(Stream stream, ReadOnlySpan<Stream> streams, bool leaveOpen)
216+
: base(leaveOpen)
217+
{
218+
Debug.Assert(streams.Length < int.MaxValue);
219+
220+
this.streams = Memory.AllocateExactly<Stream>(streams.Length + 1);
221+
var output = this.streams.Span;
222+
output[0] = stream;
223+
streams.CopyTo(output.Slice(1));
224+
}
213225

214226
protected override ReadOnlySpan<Stream> Streams => streams.Span;
215227

src/DotNext.IO/IO/StreamExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ private static Stream Combine(Stream stream, ReadOnlySpan<Stream> others, bool l
3131
[var s1, var s2, var s3, var s4] => new SparseStream<(Stream, Stream, Stream, Stream, Stream)>((stream, s1, s2, s3, s4), leaveOpen),
3232
[var s1, var s2, var s3, var s4, var s5] => new SparseStream<(Stream, Stream, Stream, Stream, Stream, Stream)>((stream, s1, s2, s3, s4,
3333
s5), leaveOpen),
34-
_ => new UnboundedSparseStream(others, leaveOpen),
34+
[var s1, var s2, var s3, var s4, var s5, var s6] => new SparseStream<(Stream, Stream, Stream, Stream, Stream, Stream, Stream)>((stream, s1, s2, s3, s4,
35+
s5, s6), leaveOpen),
36+
{ Length: int.MaxValue } => throw new InsufficientMemoryException(),
37+
_ => new UnboundedSparseStream(stream, others, leaveOpen),
3538
};
3639

3740
/// <summary>

src/DotNext.Tests/IO/StreamExtensionsTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,36 @@ public static void ReadBytesFromCombinedStream()
200200
Equal(-1, combined.ReadByte());
201201
}
202202

203+
[Theory]
204+
[InlineData(2)]
205+
[InlineData(3)]
206+
[InlineData(4)]
207+
[InlineData(5)]
208+
[InlineData(6)]
209+
[InlineData(7)]
210+
[InlineData(8)]
211+
[InlineData(9)]
212+
[InlineData(10)]
213+
public static void CombineManyStreams(byte streamCount)
214+
{
215+
using var stream = GetStreams(streamCount).Combine(leaveOpen: false);
216+
var actual = new byte[streamCount];
217+
stream.ReadExactly(actual);
218+
var expected = Set.Range<byte, EnclosedEndpoint<byte>, DisclosedEndpoint<byte>>(0, streamCount);
219+
Equal(expected, actual);
220+
221+
static IEnumerable<Stream> GetStreams(byte streamCount)
222+
{
223+
for (var i = 0; i < streamCount; i++)
224+
{
225+
var ms = new MemoryStream();
226+
ms.WriteByte((byte)i);
227+
ms.Seek(0L, SeekOrigin.Begin);
228+
yield return ms;
229+
}
230+
}
231+
}
232+
203233
[Fact]
204234
public static async Task UnsupportedMethodsOfSparseStream()
205235
{

0 commit comments

Comments
 (0)