public async Task CopyToAppliesStreamScheduling(int blocks, int blockSize) { var clock = new ManualClock(); var src = new MemoryStream(Enumerable.Range(0, blockSize * blocks).Select(x => (byte)x).ToArray()); var scheduler = new ShapeStreamAverage(BandwidthStreamCost.Default, blockSize, averageSpan: TimeSpan.FromSeconds(1), clock: clock); var dst = new MemoryStream(); var bw = new ReadMaxStream <Stream>(src.Shape(scheduler), blockSize); var cpTask = bw.CopyToAsync(dst, blockSize, CancellationToken); for (var i = 0; i < blocks; ++i) { await Waiter.TimesOut(cpTask.WaitAsync, cancellationToken : CancellationToken) .ConfigureAwait(false); dst.Length.Should().BeLessOrEqualTo((i + 1) * blockSize); await Task.WhenAll(clock.Advance(TimeSpan.FromSeconds(1))).ConfigureAwait(false); } await Waiter.WaitAsync(cpTask.WaitAsync, cancellationToken : CancellationToken) .ConfigureAwait(false); dst.Length.Should().Be(blocks * blockSize); dst.ToArray() .Should() .BeEquivalentTo(src.ToArray(), cfg => cfg.WithStrictOrdering()); }
public async Task StreamShedulingIsAppliedAccordingToClockAndSchedulers() { var clock = new ManualClock(new DateTime(2000, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)); var stream = new MemoryStream(); var scheduler = new ShapeStreamAverage(BandwidthStreamCost.Default, 1000, 2000, 10, averageSpan: TimeSpan.FromSeconds(5), clock: clock); var bw = stream.Shape(scheduler); // Writing (2000b/s over 5s = 10kb/5s) await WriteAsync(bw, 1, 9000).ConfigureAwait(false); stream.Length.Should().Be(9000); await WriteAsync(bw, 2, 1000).ConfigureAwait(false); stream.Length.Should().Be(10000); await Task.WhenAll(clock.Advance(TimeSpan.FromSeconds(4), CancellationToken)).ConfigureAwait(false); // Exceed the shaping: will timeout and not get invoked await Waiter.TimesOut(ct => WriteAsync(bw, 3, 2000, ct), cancellationToken : CancellationToken).ConfigureAwait(false); stream.Length.Should().Be(10000); // Try writing again var wtask = WriteAsync(bw, 4, 2000); // That won't run immediately await Waiter.TimesOut(wtask.WaitAsync, cancellationToken : CancellationToken).ConfigureAwait(false); // And one second is not enough, as we are accounted the 2000 from time timed out write await Task.WhenAll(clock.Advance(TimeSpan.FromSeconds(1), CancellationToken)).ConfigureAwait(false); await Waiter.TimesOut(wtask.WaitAsync, cancellationToken : CancellationToken).ConfigureAwait(false); // So we need another second await Task.WhenAll(clock.Advance(TimeSpan.FromSeconds(1), CancellationToken)); // Now, It will be completed (pretty quickly) await Waiter.WaitAsync(wtask.WaitAsync, cancellationToken : CancellationToken); // And now have the required state stream.Length.Should().Be(12000); stream.Position = 0; var streamBytes = stream.ToArray(); IList <IList <byte> > readParts = new List <IList <byte> >(); // ReSharper disable once ImplicitlyCapturedClosure - bw, this captured void AssertReadCorrect() => readParts.Aggregate(0, (offset, part) => { part.Should().BeEquivalentTo(streamBytes.Skip(offset).Take(part.Count)); return(offset + part.Count); }); // Reading (1000b/s over 5s = 50kb/5s) readParts.Add(await ReadAsync(bw, 4999, CancellationToken).ConfigureAwait(false)); AssertReadCorrect(); readParts.Add(await ReadAsync(bw, 1).ConfigureAwait(false)); AssertReadCorrect(); // Exceed the shaping: will timeout and not get invoked // ReSharper disable once ImplicitlyCapturedClosure -- streamBytes captured await Waiter.TimesOut(async ct => readParts.Add(await ReadAsync(bw, 1, ct).ConfigureAwait(false)), cancellationToken : CancellationToken).ConfigureAwait(false); AssertReadCorrect(); // Try reading again var rtask = ReadAsync(bw, 6000); // That won't run immediately await Waiter.TimesOut(rtask.WaitAsync, cancellationToken : CancellationToken).ConfigureAwait(false); // And 6 seconds is not enough, as we are exceeding average in the read itself and pay for the extra 1 failing byte await Task.WhenAll(clock.Advance(TimeSpan.FromSeconds(6), CancellationToken)).ConfigureAwait(false); await Waiter.TimesOut(rtask.WaitAsync, cancellationToken : CancellationToken).ConfigureAwait(false); // But a little more is await Task.WhenAll(clock.Advance(TimeSpan.FromSeconds(.1), CancellationToken)).ConfigureAwait(false); // Now, It will be completed (pretty quickly) readParts.Add(await Waiter.WaitAsync(rtask.WaitAsync, cancellationToken: CancellationToken)); AssertReadCorrect(); readParts.Sum(x => x.Count).Should().Be(4999 + 1 + 6000); }