Skip to content

[browser] OutOfMemoryException after bad allocation ratio #108510

Open

Description

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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions