Skip to content

Commit 3836301

Browse files
committed
Streaming Interop Followup Items
1 parent 86ea8e0 commit 3836301

File tree

9 files changed

+31
-22
lines changed

9 files changed

+31
-22
lines changed

src/Components/Server/src/Circuits/RemoteJSDataStream.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ public static async ValueTask<RemoteJSDataStream> CreateRemoteJSDataStreamAsync(
4040
RemoteJSRuntime runtime,
4141
IJSStreamReference jsStreamReference,
4242
long totalLength,
43-
long maxBufferSize,
4443
long maximumIncomingBytes,
4544
TimeSpan jsInteropDefaultCallTimeout,
46-
CancellationToken cancellationToken = default)
45+
long pauseIncomingBytesThreshold,
46+
long resumeIncomingBytesThreshold,
47+
CancellationToken cancellationToken)
4748
{
4849
// Enforce minimum 1 kb, maximum 50 kb, SignalR message size.
4950
// We budget 512 bytes overhead for the transfer, thus leaving at least 512 bytes for data
@@ -54,7 +55,7 @@ public static async ValueTask<RemoteJSDataStream> CreateRemoteJSDataStreamAsync(
5455
throw new ArgumentException($"SignalR MaximumIncomingBytes must be at least 1 kb.");
5556

5657
var streamId = runtime.RemoteJSDataStreamNextInstanceId++;
57-
var remoteJSDataStream = new RemoteJSDataStream(runtime, streamId, totalLength, maxBufferSize, jsInteropDefaultCallTimeout, cancellationToken);
58+
var remoteJSDataStream = new RemoteJSDataStream(runtime, streamId, totalLength, jsInteropDefaultCallTimeout, pauseIncomingBytesThreshold, resumeIncomingBytesThreshold, cancellationToken);
5859
await runtime.InvokeVoidAsync("Blazor._internal.sendJSDataStream", jsStreamReference, streamId, chunkSize);
5960
return remoteJSDataStream;
6061
}
@@ -63,8 +64,9 @@ private RemoteJSDataStream(
6364
RemoteJSRuntime runtime,
6465
long streamId,
6566
long totalLength,
66-
long maxBufferSize,
6767
TimeSpan jsInteropDefaultCallTimeout,
68+
long pauseIncomingBytesThreshold,
69+
long resumeIncomingBytesThreshold,
6870
CancellationToken cancellationToken)
6971
{
7072
_runtime = runtime;
@@ -78,7 +80,7 @@ private RemoteJSDataStream(
7880

7981
_runtime.RemoteJSDataStreamInstances.Add(_streamId, this);
8082

81-
_pipe = new Pipe(new PipeOptions(pauseWriterThreshold: maxBufferSize, resumeWriterThreshold: maxBufferSize / 2));
83+
_pipe = new Pipe(new PipeOptions(pauseWriterThreshold: pauseIncomingBytesThreshold, resumeWriterThreshold: resumeIncomingBytesThreshold));
8284
_pipeReaderStream = _pipe.Reader.AsStream();
8385
}
8486

src/Components/Server/src/Circuits/RemoteJSRuntime.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ public void MarkPermanentlyDisconnected()
157157
_clientProxy = null;
158158
}
159159

160-
protected override async Task<Stream> ReadJSDataAsStreamAsync(IJSStreamReference jsStreamReference, long totalLength, long maxBufferSize, CancellationToken cancellationToken)
161-
=> await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(this, jsStreamReference, totalLength, maxBufferSize, _maximumIncomingBytes, _options.JSInteropDefaultCallTimeout, cancellationToken);
160+
protected override async Task<Stream> ReadJSDataAsStreamAsync(IJSStreamReference jsStreamReference, long totalLength, long pauseIncomingBytesThreshold, long resumeIncomingBytesThreshold, CancellationToken cancellationToken)
161+
=> await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(this, jsStreamReference, totalLength, _maximumIncomingBytes, _options.JSInteropDefaultCallTimeout, pauseIncomingBytesThreshold, resumeIncomingBytesThreshold, cancellationToken);
162162

163163
public static class Log
164164
{

src/Components/Server/test/Circuits/RemoteJSDataStreamTest.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.IO;
66
using System.Linq;
7+
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.AspNetCore.SignalR;
910
using Microsoft.Extensions.Logging;
@@ -25,7 +26,7 @@ public async void CreateRemoteJSDataStreamAsync_CreatesStream()
2526
var jsStreamReference = Mock.Of<IJSStreamReference>();
2627

2728
// Act
28-
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(_jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
29+
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(_jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None);
2930

3031
// Assert
3132
Assert.NotNull(remoteJSDataStream);
@@ -116,7 +117,7 @@ public async void ReceiveData_ProvidedWithMoreBytesThanRemaining()
116117
// Arrange
117118
var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
118119
var jsStreamReference = Mock.Of<IJSStreamReference>();
119-
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
120+
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None);
120121
var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
121122
var chunk = new byte[110]; // 100 byte totalLength for stream
122123

@@ -136,7 +137,7 @@ public async void ReceiveData_ProvidedWithOutOfOrderChunk_SimulatesSignalRDiscon
136137
// Arrange
137138
var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>());
138139
var jsStreamReference = Mock.Of<IJSStreamReference>();
139-
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
140+
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None);
140141
var streamId = GetStreamId(remoteJSDataStream, jsRuntime);
141142
var chunk = new byte[5];
142143

@@ -157,7 +158,7 @@ public async void ReceiveData_ProvidedWithOutOfOrderChunk_SimulatesSignalRDiscon
157158
private static async Task<RemoteJSDataStream> CreateRemoteJSDataStreamAsync(TestRemoteJSRuntime jsRuntime = null)
158159
{
159160
var jsStreamReference = Mock.Of<IJSStreamReference>();
160-
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime ?? _jsRuntime, jsStreamReference, totalLength: 100, maxBufferSize: 50, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1));
161+
var remoteJSDataStream = await RemoteJSDataStream.CreateRemoteJSDataStreamAsync(jsRuntime ?? _jsRuntime, jsStreamReference, totalLength: 100, maximumIncomingBytes: 10_000, jsInteropDefaultCallTimeout: TimeSpan.FromMinutes(1), pauseIncomingBytesThreshold: 50, resumeIncomingBytesThreshold: 25, cancellationToken: CancellationToken.None);
161162
return remoteJSDataStream;
162163
}
163164

src/Components/Web/src/Forms/InputFile/RemoteBrowserFileStream.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ private async Task<Stream> OpenReadStreamAsync(RemoteBrowserFileStreamOptions op
4545
_inputFileElement,
4646
File.Id);
4747

48-
return await dataReference.OpenReadStreamAsync(_maxAllowedSize, options.MaxBufferSize, cancellationToken);
48+
return await dataReference.OpenReadStreamAsync(
49+
_maxAllowedSize,
50+
pauseIncomingBytesThreshold: options.MaxBufferSize,
51+
resumeIncomingBytesThreshold: options.MaxBufferSize / 2,
52+
cancellationToken);
4953
}
5054

5155
protected override async ValueTask<int> CopyFileDataIntoBuffer(long sourceOffset, Memory<byte> destination, CancellationToken cancellationToken)

src/JSInterop/Microsoft.JSInterop/src/IJSStreamReference.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ public interface IJSStreamReference : IAsyncDisposable
1616
/// <summary>
1717
/// Length of the <see cref="Stream"/> provided by JavaScript.
1818
/// </summary>
19-
public long Length { get; }
19+
long Length { get; }
2020

2121
/// <summary>
2222
/// Opens a <see cref="Stream"/> with the <see cref="JSRuntime"/> for the current data reference.
2323
/// </summary>
2424
/// <param name="maxAllowedSize">Maximum number of bytes permitted to be read from JavaScript.</param>
25-
/// <param name="maxBufferSize">Maximum number of bytes that are allowed to be buffered.</param>
25+
/// <param name="pauseIncomingBytesThreshold">The number of unconsumed bytes to accept from JS before blocking. A value of zero prevents JS from blocking, allowing .NET to receive an unlimited number of bytes.</param>
26+
/// <param name="resumeIncomingBytesThreshold">The number of unflushed bytes at which point JS stops blocking.</param>
2627
/// <param name="cancellationToken"><see cref="CancellationToken" /> for cancelling read.</param>
2728
/// <returns><see cref="Stream"/> which can provide data associated with the current data reference.</returns>
28-
ValueTask<Stream> OpenReadStreamAsync(long maxAllowedSize = 512000, long maxBufferSize = 100 * 1024, CancellationToken cancellationToken = default);
29+
ValueTask<Stream> OpenReadStreamAsync(long maxAllowedSize = 512000, long pauseIncomingBytesThreshold = -1, long resumeIncomingBytesThreshold = -1, CancellationToken cancellationToken = default);
2930
}
3031
}

src/JSInterop/Microsoft.JSInterop/src/Implementation/JSStreamReference.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ internal JSStreamReference(JSRuntime jsRuntime, long id, long totalLength) : bas
3636
}
3737

3838
/// <inheritdoc />
39-
async ValueTask<Stream> IJSStreamReference.OpenReadStreamAsync(long maxLength, long maxBufferSize, CancellationToken cancellationToken)
39+
async ValueTask<Stream> IJSStreamReference.OpenReadStreamAsync(long maxLength, long pauseIncomingBytesThreshold, long resumeIncomingBytesThreshold, CancellationToken cancellationToken)
4040
{
4141
if (Length > maxLength)
4242
{
4343
throw new ArgumentOutOfRangeException(nameof(maxLength), $"The incoming data stream of length {Length} exceeds the maximum length {maxLength}.");
4444
}
4545

46-
return await _jsRuntime.ReadJSDataAsStreamAsync(this, Length, maxBufferSize, cancellationToken);
46+
return await _jsRuntime.ReadJSDataAsStreamAsync(this, Length, pauseIncomingBytesThreshold, resumeIncomingBytesThreshold, cancellationToken);
4747
}
4848
}
4949
}

src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,11 @@ protected internal virtual void ReceiveByteArray(int id, byte[] data)
215215
/// </summary>
216216
/// <param name="jsStreamReference"><see cref="IJSStreamReference"/> to produce a data stream for.</param>
217217
/// <param name="totalLength">Expected length of the incoming data stream.</param>
218-
/// <param name="maxBufferSize">Amount of bytes to buffer before flushing.</param>
218+
/// <param name="pauseIncomingBytesThreshold">The number of unconsumed bytes to accept from JS before blocking. A value of zero prevents JS from blocking, allowing .NET to receive an unlimited number of bytes.</param>
219+
/// <param name="resumeIncomingBytesThreshold">The number of unflushed bytes at which point JS stops blocking.</param>
219220
/// <param name="cancellationToken"><see cref="CancellationToken" /> for cancelling read.</param>
220221
/// <returns><see cref="Stream"/> for the data reference represented by <paramref name="jsStreamReference"/>.</returns>
221-
protected internal virtual Task<Stream> ReadJSDataAsStreamAsync(IJSStreamReference jsStreamReference, long totalLength, long maxBufferSize, CancellationToken cancellationToken)
222+
protected internal virtual Task<Stream> ReadJSDataAsStreamAsync(IJSStreamReference jsStreamReference, long totalLength, long pauseIncomingBytesThreshold, long resumeIncomingBytesThreshold, CancellationToken cancellationToken)
222223
{
223224
// The reason it's virtual and not abstract is just for back-compat
224225

src/JSInterop/Microsoft.JSInterop/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#nullable enable
22
Microsoft.JSInterop.IJSStreamReference
33
Microsoft.JSInterop.IJSStreamReference.Length.get -> long
4-
Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync(long maxAllowedSize = 512000, long maxBufferSize = 102400, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<System.IO.Stream!>
4+
Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync(long maxAllowedSize = 512000, long pauseIncomingBytesThreshold = -1, long resumeIncomingBytesThreshold = -1, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<System.IO.Stream!>
55
Microsoft.JSInterop.Implementation.JSStreamReference
66
Microsoft.JSInterop.Implementation.JSStreamReference.Length.get -> long
77
Microsoft.JSInterop.Implementation.JSObjectReferenceJsonWorker
@@ -36,7 +36,7 @@ static Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(this Microsoft.JS
3636
static Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime! jsRuntime, string! identifier, System.TimeSpan timeout, params object?[]? args) -> System.Threading.Tasks.ValueTask
3737
*REMOVED*static Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime! jsRuntime, string! identifier, params object![]! args) -> System.Threading.Tasks.ValueTask
3838
static Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime! jsRuntime, string! identifier, params object?[]? args) -> System.Threading.Tasks.ValueTask
39-
virtual Microsoft.JSInterop.JSRuntime.ReadJSDataAsStreamAsync(Microsoft.JSInterop.IJSStreamReference! jsStreamReference, long totalLength, long maxBufferSize, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.IO.Stream!>!
39+
virtual Microsoft.JSInterop.JSRuntime.ReadJSDataAsStreamAsync(Microsoft.JSInterop.IJSStreamReference! jsStreamReference, long totalLength, long pauseIncomingBytesThreshold, long resumeIncomingBytesThreshold, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.IO.Stream!>!
4040
virtual Microsoft.JSInterop.JSRuntime.ReceiveByteArray(int id, byte[]! data) -> void
4141
virtual Microsoft.JSInterop.JSRuntime.SendByteArray(int id, byte[]! data) -> void
4242
Microsoft.JSInterop.JSDisconnectedException

src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ public async void ReadJSDataAsStreamAsync_ThrowsNotSupportedException()
404404
var dataReference = new JSStreamReference(runtime, 10, 10);
405405

406406
// Act
407-
var exception = await Assert.ThrowsAsync<NotSupportedException>(async () => await runtime.ReadJSDataAsStreamAsync(dataReference, 10, 10, CancellationToken.None));
407+
var exception = await Assert.ThrowsAsync<NotSupportedException>(async () => await runtime.ReadJSDataAsStreamAsync(dataReference, 10, 10, 10, CancellationToken.None));
408408

409409
// Assert
410410
Assert.Equal("The current JavaScript runtime does not support reading data streams.", exception.Message);

0 commit comments

Comments
 (0)