Skip to content

[browser] tests and fixes for heap larger than 2GB #104662

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 11 commits into from
Jul 16, 2024
1 change: 1 addition & 0 deletions eng/testing/scenarios/BuildWasmAppsJobsList.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Wasm.Build.Tests.TestAppScenarios.LazyLoadingTests
Wasm.Build.Tests.TestAppScenarios.LibraryInitializerTests
Wasm.Build.Tests.TestAppScenarios.SatelliteLoadingTests
Wasm.Build.Tests.TestAppScenarios.ModuleConfigTests
Wasm.Build.Tests.TestAppScenarios.MemoryTests
Wasm.Build.Tests.AspNetCore.SignalRClientTests
Wasm.Build.Tests.WasmBuildAppTest
Wasm.Build.Tests.WasmNativeDefaultsTests
Expand Down
37 changes: 37 additions & 0 deletions src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit;

#nullable enable

namespace Wasm.Build.Tests.TestAppScenarios;

public class MemoryTests : AppTestBase
{
public MemoryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}

[Theory]
[InlineData("Release", true)]
[InlineData("Release", false)]
public async Task AllocateLargeHeapThenRepeatedlyInterop(string config, bool buildNative)
{
// native build triggers passing value form EmccMaximumHeapSize to MAXIMUM_MEMORY that is set in emscripten
// in non-native build EmccMaximumHeapSize does not have an effect, so the test will fail with "out of memory"
CopyTestAsset("WasmBasicTestApp", "MemoryTests", "App");
string extraArgs = $"-p:EmccMaximumHeapSize=4294901760 -p:WasmBuildNative={buildNative}";
BuildProject(config, assertAppBundle: false, extraArgs: extraArgs);

var result = await RunSdkStyleAppForBuild(new (Configuration: config, TestScenario: "AllocateLargeHeapThenInterop", ExpectedExitCode: buildNative ? 0 : 1));
if (!buildNative)
Assert.Contains(result.TestOutput, item => item.Contains("Exception System.OutOfMemoryException: Out of memory"));
}
}
74 changes: 74 additions & 0 deletions src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Text.Json;
using System.Text;
using System.Runtime.InteropServices.JavaScript;

public partial class MemoryTest // ?test=AllocateLargeHeapThenInterop
{
[JSImport("countChars", "main.js")]
internal static partial int CountChars(string testArray);

[JSExport]
internal static void Run()
{
// Allocate over 2GB space, 2 621 440 000 bytes
const int arrayCnt = 25;
int[][] arrayHolder = new int[arrayCnt][];
string errors = "";
TestOutput.WriteLine("Starting over 2GB array allocation");
for (int i = 0; i < arrayCnt; i++)
{
try
{
arrayHolder[i] = new int[1024 * 1024 * 25];
}
catch (Exception ex)
{
errors += $"Exception {ex} was thrown on i={i}";
}
}
TestOutput.WriteLine("Finished over 2GB array allocation");

// call a method many times to trigger tier-up optimization
string randomString = GenerateRandomString(1000);
try
{
for (int i = 0; i < 10000; i++)
{
int count = CountChars(randomString);
if (count != randomString.Length)
errors += $"CountChars returned {count} instead of {randomString.Length} for {i}-th string.";
}
}
catch (Exception ex)
{
errors += $"Exception {ex} was thrown when CountChars was called in a loop";
}
if (!string.IsNullOrEmpty(errors))
{
TestOutput.WriteLine(errors);
throw new Exception(errors);
}
else
{
TestOutput.WriteLine("Great success, MemoryTest finished without errors.");
}
}

private static Random random = new Random();

private static string GenerateRandomString(int stringLength)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var stringBuilder = new StringBuilder(stringLength);
for (int i = 0; i < stringLength; i++)
{
stringBuilder.Append(chars[random.Next(chars.Length)]);
}
return stringBuilder.ToString();
}
}
13 changes: 13 additions & 0 deletions src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ function testOutput(msg) {
console.log(`TestOutput -> ${msg}`);
}

function countChars(str) {
const length = str.length;
testOutput(`JS received str of ${length} length`);
return length;
}

// Prepare base runtime parameters
dotnet
.withElementOnExit()
Expand Down Expand Up @@ -168,6 +174,13 @@ try {
case "MaxParallelDownloads":
exit(0);
break;
case "AllocateLargeHeapThenInterop":
setModuleImports('main.js', {
countChars
});
exports.MemoryTest.Run();
exit(0);
break;
default:
console.error(`Unknown test case: ${testCase}`);
exit(3);
Expand Down
Loading