Skip to content

Avoid trying to use _hotReloadAgent variable before it is initialized #46916

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 6 commits into from
Mar 1, 2023

Conversation

thaystg
Copy link
Member

@thaystg thaystg commented Feb 27, 2023

Fixes #45190

@thaystg thaystg requested a review from a team as a code owner February 27, 2023 21:47
@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Feb 27, 2023
@@ -50,14 +45,22 @@ internal static async Task InitializeAsync()
[JSInvokable(nameof(ApplyHotReloadDelta))]
public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDelta, byte[] ilDelta, byte[] pdbBytes)
{
// Analyzer has a bug where it doesn't handle ConditionalAttribute: https://github.com/dotnet/roslyn/issues/63464
#pragma warning disable IDE0200 // Remove unnecessary lambda expression
if (_hotReloadAgent == null)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be Interlocked.CompareExchange to avoid multiple threads ending up with different instances?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that if it has multiple initialization, it is not an issue for the runtime.
But I will change as you suggested to avoid any issue.

@@ -50,14 +45,19 @@ internal static async Task InitializeAsync()
[JSInvokable(nameof(ApplyHotReloadDelta))]
public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDelta, byte[] ilDelta, byte[] pdbBytes)
{
// Analyzer has a bug where it doesn't handle ConditionalAttribute: https://github.com/dotnet/roslyn/issues/63464
#pragma warning disable IDE0200 // Remove unnecessary lambda expression
Interlocked.CompareExchange(ref _hotReloadAgent, new HotReloadAgent(m => Debug.WriteLine(m)), null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Will a new HotReloadAgent be initialized even in case if the value shouldn't be replaced here? I assume the compiler is smart enough not to do that, but I'm not sure, hence my question.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interlocked.CompareExchange is thread safe and will avoid creating two HotReloadAgent in two different threads.
It compares two values for equality and, if they are equal, replaces the first value.

https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked.compareexchange?view=net-7.0#system-threading-interlocked-compareexchange(system-object@-system-object-system-object)

@@ -50,14 +45,19 @@ internal static async Task InitializeAsync()
[JSInvokable(nameof(ApplyHotReloadDelta))]
public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDelta, byte[] ilDelta, byte[] pdbBytes)
{
// Analyzer has a bug where it doesn't handle ConditionalAttribute: https://github.com/dotnet/roslyn/issues/63464
#pragma warning disable IDE0200 // Remove unnecessary lambda expression
Interlocked.CompareExchange(ref _hotReloadAgent, new HotReloadAgent(m => Debug.WriteLine(m)), null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does the initialization in the InitializeAsync method is not enough? Does the value gets discarded through some other codepath?

Copy link
Member Author

Choose a reason for hiding this comment

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

As the InitializeAsync is an async method what was happening is that in a race condition before _hotReloadAgent is initialized on InitializeAsync it was trying to be used on ApplyHotReloadDelta method and throwing the NRE exception.

Copy link
Contributor

Choose a reason for hiding this comment

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

That sounds like a bug in the consuming code then, rather than here, isn't it? So the consumder then should just make sure they wait for the initialization before trying to apply changes.

Copy link
Member Author

@thaystg thaystg Feb 28, 2023

Choose a reason for hiding this comment

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

I'm not sure if it's possible to check if InitializeAsync is done before calling the ApplyHotReloadDelta as it's called by dotnet watch.

image

Copy link
Member

Choose a reason for hiding this comment

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

It is possible that there is an issue with initialization. I am not sure what exactly the initialization sequence for WASM is supposed to do. It'd be great if anyone knows and can review the code paths.

Copy link
Member Author

Choose a reason for hiding this comment

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

Here is where InitializeAsync is called:

await WebAssemblyHotReload.InitializeAsync();

And here is where it's calling the applyHotReload function on JS from dotnet watch:
https://github.com/dotnet/sdk/blob/fbc089ea96f0187c31e9ae176278334c8d5defc6/src/BuiltInTools/BrowserRefresh/BlazorHotReload.js#L16

Which will call the ApplyHotReloadDelta on C# side:

DotNet.invokeMethod('Microsoft.AspNetCore.Components.WebAssembly', 'ApplyHotReloadDelta', id, metadataDelta, ilDelta, pdbDelta);

I think there is no way to guarantee that InitializeAsync is done before calling the ApplyHotReloadDelta, I guess that this is the correct fix.

Copy link
Contributor

@mkArtakMSFT mkArtakMSFT left a comment

Choose a reason for hiding this comment

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

Discussed my comments with @thaystg.
Looks like fixing the initialization code on the dotnet-watch is a compromise which had been thought about already.

@thaystg thaystg merged commit c3f903f into dotnet:main Mar 1, 2023
@ghost ghost added this to the 8.0-preview3 milestone Mar 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CLI Hot reload for Blazor wasm still doesn't work in .NET 7
3 participants