Skip to content

Blazor server mode: calling jsinterop to cleanup clientside before disposing component causes memory leak in page close/refresh #33535

@alanma75

Description

@alanma75

Description

while developing a custom blazor component, some clientside object need to be cleared before the component being disposed. below sample is a simplified version taken from blazor-university:
https://blazor-university.com/javascript-interop/calling-dotnet-from-javascript/lifetimes-and-memory-leaks/

Dispose method on the page tries to invoke a js method to clear its clientside objects, then DotnetReference is disposed.

what we have noticed is the jsinterop call throws exception while page refresh/close, because jsruntime at that moment is already closed. this exception cause all objects on serverside from the closed circuit to stay in memory. (Take memory snapshot using diagnostic tool, and check ViewModel object, the viewmodel object created from previous circuits stays in memory after page refresh, and with jsinterop call removed from Dispose method, there is always just one Viewmodel in memory.

Configuration

refer this script block in _host.html

    <script>
        var BlazorUniversity = BlazorUniversity || {};
        BlazorUniversity.startRandomGenerator = function(dotNetObject) {
            setInterval(function () {
                let text = Math.random() * 1000;
                console.log("JS: Generated " + text);
                dotNetObject.invokeMethodAsync('AddText', text.toString());
            }, 1000);
            return 1;
        };
    </script>

index.razor

@page "/"
@inject IJSRuntime JSRuntime
@implements IDisposable

<span>Text received</span>

<ul>
    @foreach (string text in TextHistory)
    {
        <li>@text</li>
    }
</ul>

@code
{
    List<string> TextHistory = new List<string>();
    int GeneratorHandle = -1;
    DotNetObjectReference<Index> ObjectReference;
    ViewModel model = new ViewModel() {Name = "Test person"};

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);
        if (firstRender)
        {
            ObjectReference = DotNetObjectReference.Create(this);
            GeneratorHandle = await JSRuntime.InvokeAsync<int>("BlazorUniversity.startRandomGenerator", ObjectReference);
        }
    }

    [JSInvokable("AddText")]
    public void AddTextToTextHistory(string text)
    {
        TextHistory.Add(text.ToString());
        while (TextHistory.Count > 10) 
            TextHistory.RemoveAt(0);
        StateHasChanged();
        System.Diagnostics.Debug.WriteLine("DotNet: Received " + text);
    }

    public async void Dispose()
    {
        if (GeneratorHandle != -1)
        {
    //Cancel our callback before disposing our object reference, this call cause exception in page close/refresh and block the GC
            await JSRuntime.InvokeVoidAsync("BlazorUniversity.stopRandomGenerator", GeneratorHandle);
        }

        if (ObjectReference != null)
        {
    //Now dispose our object reference so our component can be garbage collected
            ObjectReference.Dispose();
        }
    }

    public class ViewModel
    {
        public string Name { get; set; }
    }
}

Regression?

I have also tried in .net 3.1 and got the same issue.

Other information

what is the correct way to dispose client and server side resources? if this is a razor component, the dispose is called from both partial render when the component is removed from render tree (in this case, page is still active so this jsinterop call works fine), or when page closed (in this case, jsinterop call throw exception, even with try catch, we still observe the locking of all the closed page's object locked by renderer root object.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ✔️ Resolution: AnsweredResolved because the question asked by the original author has been answered.Status: Resolvedarea-blazorIncludes: Blazor, Razor Components

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions