Skip to content

[browser] OutOfMemoryException after bad allocation ratio #108510

@pavelsavara

Description

@pavelsavara
MONO_WASM: Out of memory
   at System.MulticastDelegate.RemoveImpl(Delegate value)
   at System.Delegate.Remove(Delegate source, Delegate value)
   at Sample.ParentClass.remove_PropertyChanged(PropertyChangedEventHandler value)
   at Sample.ChildClass.Dispose()
   at Sample.TestClass.DisposeObjects()
   at Sample.TestClass.__Wrapper_DisposeObjects_19325221(JSMarshalerArgument* __arguments_buffer)
Error: Out of memory
    at marshal_exception_to_js (https://localhost:8000/_framework/dotnet.runtime.js:2384:18)
    at invoke_sync_jsexport (https://localhost:8000/_framework/dotnet.runtime.js:3571:15)
    at Object.bound_fn_0V (https://localhost:8000/_framework/dotnet.runtime.js:4626:13)
    at Object.JSExport_DisposeObjects (https://dotnet/JSExport/DisposeObjects:4:55)
    at https://localhost:8000/main.js:20:16

Probably related #107215

Repro

I'm able to reproduce it on latest Net10 main, but customer is reporting similar issues on Net8.

using System;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Collections.Generic;

#pragma warning disable CS8632

namespace Sample;

public partial class TestClass
{
    private static readonly ParentClass _parent = new();
    private static readonly HashSet<ChildClass> _objects = [];

    public static int Main(string[] args)
    {
        return 0;
    }

    [JSExport]
    public static string AllocateObjects(int count)
    {
        for (int i = 0; i < count; i++)
        {
            var child = new ChildClass(_parent);
            _objects.Add(child);
        }

        return GC.GetTotalMemory(forceFullCollection: false).ToString();
    }

    [JSExport]
    public static void DisposeObjects()
    {
        foreach (var child in _objects)
        {
            child.Dispose();
        }
    }
}

public sealed class ChildClass : IDisposable
{
    private readonly ParentClass _parent;
    private readonly byte[] _junk = new byte[250_000];

    public ChildClass(ParentClass parent)
    {
        _parent = parent;
        _parent.PropertyChanged += OnPropertyChanged;
    }

    public void Dispose()
    {
        _parent.PropertyChanged -= OnPropertyChanged;
        GC.SuppressFinalize(this);
    }

    private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
    }
}

public sealed class ParentClass
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public void NotifyChilderen()
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Foo"));
    }
}
import { dotnet, exit } from './_framework/dotnet.js'

try {
    const { getAssemblyExports, runMain } = await dotnet
        .withElementOnExit()
        .withExitOnUnhandledError()
        .create();

    await runMain("Wasm.Browser.Sample", []);

    const library = await getAssemblyExports("Wasm.Browser.Sample");
    const testClass = library.Sample.TestClass;
    console.log("Start allocating objects...");
    const allocatedBytes = testClass?.AllocateObjects(3890);
    console.log(`Allocated ${allocatedBytes} bytes`);
    console.log("Disposing allocated objects...");
    testClass?.DisposeObjects();
}
catch (err) {
    exit(2, err);
}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions