Skip to content

Blazor Streaming Interop | JS to DotNet #33491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
Jun 27, 2021
Merged

Conversation

TanayParikh
Copy link
Contributor

@TanayParikh TanayParikh commented Jun 11, 2021

Streaming interop mechanism from JS to DotNet

API

        /// <summary>
        /// Opens a <see cref="Stream"/> with the <see cref="JSRuntime"/> for the current data reference.
        /// </summary>
        /// <param name="maxAllowedSize">Maximum number of bytes permitted to be read from JavaScript.</param>
        /// <param name="pauseIncomingBytesThreshold">
        /// The number of unconsumed bytes to accept from JS before blocking.
        /// Defaults to -1, which indicates use of the default <see cref="System.IO.Pipelines.PipeOptions.PauseWriterThreshold" />.
        /// Avoid specifying an excessively large value because this could allow clients to exhaust memory.
        /// A value of zero prevents JS from blocking, allowing .NET to receive an unlimited number of bytes.
        /// <para>
        /// This only has an effect when using Blazor Server.
        /// </para>
        /// </param>
        /// <param name="resumeIncomingBytesThreshold">
        /// The number of unflushed bytes at which point JS stops blocking.
        /// Defaults to -1, which indicates use of the default <see cref="System.IO.Pipelines.PipeOptions.PauseWriterThreshold" />.
        /// Must be less than the <paramref name="pauseIncomingBytesThreshold"/> to prevent thrashing at the limit.
        /// <para>
        /// This only has an effect when using Blazor Server.
        /// </para>
        /// </param>
        /// <param name="cancellationToken"><see cref="CancellationToken" /> for cancelling read.</param>
        /// <returns><see cref="Stream"/> which can provide data associated with the current data reference.</returns>
        ValueTask<Stream> OpenReadStreamAsync(long maxAllowedSize = 512000, long pauseIncomingBytesThreshold = -1, long resumeIncomingBytesThreshold = -1, CancellationToken cancellationToken = default);

[Code]

Usage

JS

function getSomeData() {
    return new Uint8Array(10000000);
}

DotNet

public async Task<MemoryStream> GetDataFromJS()
{
    var data = await JS.InvokeAsync<IJSDataReference>("getSomeData");
    using var stream = await data.OpenReadStreamAsync(
        maxAllowedSize: 10_000_000,
        cancellationToken: CancellationToken.None);

    var memoryStream = new MemoryStream();
    await stream.CopyToAsync(memoryStream);
    return memoryStream;
}

Upload 100MB

Upload100MB

Cancel Upload

CancelUpload

Upload Files

UploadFiles

Image Upload

ImageUpload

@TanayParikh TanayParikh marked this pull request as ready for review June 11, 2021 23:39
@Pilchie Pilchie added the area-blazor Includes: Blazor, Razor Components label Jun 13, 2021
@SteveSandersonMS
Copy link
Member

This is looking really good, @TanayParikh! Looking forwards to seeing the tests. No need to handle the .NET-to-JS direction in the same PR.

Copy link
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great

Copy link
Member

@SteveSandersonMS SteveSandersonMS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fantastic! I know it's been a long road to get here, but this is a super feature and the implementation looks excellent. Thanks for bearing with us through all the reviews.

I only found one minor detail to raise: https://github.com/dotnet/aspnetcore/pull/33491/files#r658898696, so please feel free to deal with this however you see fit and merge.

return false;
}

return await circuitHost.ReceiveJSDataChunk(streamId, chunkId, chunk, error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since reviews are wrapping up, just calling this out from an offline conversation, this await will block the entire Hub until the operation completes which affects user interactivity

Copy link
Contributor Author

@TanayParikh TanayParikh Jun 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify/confirm, "this await will block the entire Hub" is referring to blocking that specific circuit, or all circuits on the server? I was under the impression it was the former, but "entire Hub" made me want to double check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just that connection

Copy link
Contributor Author

@TanayParikh TanayParikh Jun 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification. I discussed this with @javiercn and we're acknowledging the blocking here, but are electing to keep it. This is because we're going to be blocking the circuit regardless when we call into circuitHost.ReceiveDataChunk which calls for Renderer.Dispatcher.InvokeAsync ensuring we're on the main circuit thread. This is done to ensure the server/client runs in the same synchronization context (as is done with all the other endpoints). Additionally the ReceiveJSDataChunk is a quick process, it should return near instantly in most cases. Lastly, we're utilizing the return value as a heartbeat for the transfer process, and without it would likely need to setup a separate endpoint to handle that functionality.

@TanayParikh
Copy link
Contributor Author

TanayParikh commented Jun 25, 2021

Thanks all for the reviews! I appreciate all the time you took and the feedback provided.

Merging this PR for now, with the following action items:

@TanayParikh TanayParikh disabled auto-merge June 26, 2021 07:12
@TanayParikh TanayParikh force-pushed the taparik/streamingInterop branch from 477f337 to e38913d Compare June 27, 2021 05:25
@TanayParikh TanayParikh force-pushed the taparik/streamingInterop branch from e38913d to 6e91d84 Compare June 27, 2021 05:27
@TanayParikh TanayParikh enabled auto-merge (squash) June 27, 2021 05:28
@TanayParikh
Copy link
Contributor Author

CI doesn't like the timeout related tests (passing locally), iterated through several different configs / potential issues. I suspect this is likely due to the time sensitive nature of the tests which doesn't really lend itself too well to CI. Creating a separate PR to add back in those tests.

@TanayParikh TanayParikh merged commit 689a08f into main Jun 27, 2021
@TanayParikh TanayParikh deleted the taparik/streamingInterop branch June 27, 2021 21:31
@ghost ghost added this to the 6.0-preview7 milestone Jun 27, 2021
@pranavkm pranavkm added the api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews label Jun 28, 2021
@ghost
Copy link

ghost commented Jun 28, 2021

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@pranavkm
Copy link
Contributor

API review feedback:

Remove the pause and resume parameters:

ValueTask<Stream> OpenReadStreamAsync(long maxAllowedSize = 512000, CancellationToken cancellationToken = default);

@pranavkm pranavkm added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Jul 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants