Dingus Pingus IV
Dingus Pingus IV

Reputation: 11

MudBlazor MudDialog not properly freeing memory/disposing

I have a medium-sized application with a table and one feature of the table is the ability to click on a button which opens a view of that row in the form of a MudDialog. The dialog loads data in from an API and displays it. Most notably, it also loads in audio which is then converted to base64 for the embedded html audio player to work.

The problem is that this approach takes a significant amount of memory as I am loading audio directly into the heap and this can take up considerable space in the system memory. This wouldn't be a problem however if MudDialog had properly disposed of all of its resources on close. What happens is that every time a Dialog is opened, new memory seems to be allocated to the dialog and upon close, does not dispose as seen in my memory heap: Memory Heap. This leads to a memory leak and subsequent crash after enough dialogs are opened, especially large ones.

Here are some snippets from the source code:

Parent.razor

@inject IDialogService DialogService

    private readonly DialogOptions _dialogOptions = new() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true, CloseOnEscapeKey = true, NoHeader = false };
    private async Task OpenDialogViewAsync(int id, string name)
    {
        JsonDocument text = await GetText(textId);
        var parameters = new DialogParameters
        {
            { "name", name},
            { "jsonContent", text},
            { "id", transcriptionId}
        };
        var completeTitle = $"File: {name}";

        var dialog = await DialogService.ShowAsync<TextViewDialog>(completeTitle, parameters, _dialogOptions);
        var result = await dialog.Result;
    }

TextViewDialog.razor

@implements IDisposable

    private string audioUrl = string.Empty;
    protected override async Task OnInitializedAsync()
    {
        _loading = true;
        await PerformSearch();
        _loading = false;
        await LoadAudioUrl();
        StateHasChanged();
    }
    private async Task LoadAudioUrl()
    {
        try
        {
            audioBytes = await getAudio(id);
            if (audioBytes != null && audioBytes.Length > 0)
            {
                audioUrl = $"data:audio/mpeg;base64,{Convert.ToBase64String(audioBytes)}";
                Logger.LogInformation("Audio URL set successfully.");
            }
            else
            {
                Logger.LogWarning("Audio file is empty or null.");
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error loading audio URL");
            Snackbar.Add($"Error loading audio URL: {ex.Message}", MudBlazor.Severity.Error);
        }
        StateHasChanged();
    }

    private void Close()
    {
        Dispose();
        MudDialog.Close(DialogResult.Ok(true));
    }

I have tried implementing IDisposable and clearing the variables defined in that dialog:

    public void Dispose()
    {
        JSRuntime.InvokeVoidAsync("disposeAudioPlayer");
        JsonContent = null;
        TextContent = null;
        audioBytes = null;
    }

But that had no effect. I read that MudBlazor calls the Dispose() method once the dialog is closed but I don't seem to be able to understand what the root cause of the issue is or if I am setting something up wrong. I have a running theory that this may be due to Mudblazor not being able to properly handle to closing and disposing of their dialogues since in the Heap, the Dialog instance/object seems to be amassing more memory every time it is opened/revealed.

Upvotes: 1

Views: 33

Answers (1)

Qiang Fu
Qiang Fu

Reputation: 8811

You could try await the js interop to be finished.

    public void Dispose(){
        JSRuntime.InvokeVoidAsync("disposeAudioPlayer").GetAwaiter().GetResult();
        JsonContent = null;
        TextContent = null;
        if (AudioBytes != null)
        {
            Array.Clear(AudioBytes, 0, AudioBytes.Length);
            AudioBytes = null;
        }
        GC.Collect(); // Forcing GC for debugging (don't normally do this in production)
    }

Upvotes: 0

Related Questions