Skip to content
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

[browser][mt] JSWebWorker crashes the runtime #102010

Open
kekekeks opened this issue May 8, 2024 · 3 comments
Open

[browser][mt] JSWebWorker crashes the runtime #102010

kekekeks opened this issue May 8, 2024 · 3 comments
Assignees
Labels
arch-wasm WebAssembly architecture area-System.Runtime.InteropServices.JavaScript os-browser Browser variant of arch-wasm
Milestone

Comments

@kekekeks
Copy link

kekekeks commented May 8, 2024

Description

Upcoming JSWebWorker API crashes the runtime when used.

Reproduction Steps

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

public static class Program
{
    class JSWebWorkerWrapper
    {
        [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", 
            "System.Runtime.InteropServices.JavaScript")]
        [UnconditionalSuppressMessage("Trimming", 
            "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
            Justification = "WIP runtime API")]
        static JSWebWorkerWrapper()
        {
            var type = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
                .Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker");
#pragma warning disable IL2075
            var m = type!

                .GetMethods(BindingFlags.Static | BindingFlags.Public
                ).First(m => m.Name == "RunAsync"
                             && m.ReturnType == typeof(Task)
                             && m.GetParameters() is { } parameters
                             && parameters.Length == 1
                             && parameters[0].ParameterType == typeof(Func<Task>));

#pragma warning restore IL2075
            RunAsync = (Func<Func<Task>, Task>) Delegate.CreateDelegate(typeof(Func<Func<Task>, Task>), m);

        }

        public static Func<Func<Task>, Task> RunAsync { get; set; }
    }

    public static async Task Main()
    {
        Console.WriteLine("Entering infinite loop on the main thread");

        _ = JSWebWorkerWrapper.RunAsync(() =>
        {
            Console.WriteLine("Worker started, returning an never-finishing task");
            return new TaskCompletionSource().Task;
        }).ContinueWith(_ =>
        {
            Console.WriteLine("Worker exited");
        });

        while (true)
        {
            for (int c = 0; c < 1000; c++)
                Alloc(60000);
            GC.Collect();
            await Task.Delay(1);
        }
    }

    static byte[] Alloc(int size) => new byte[size];
    
}

Expected behavior

JSWebWorker spawns a WebWorker that is allowed to await on tasks.

Actual behavior

JSWebWorker instance gets finalized and crashes the runtime

[MONO] Process terminated.
[MONO] JSWebWorker was disposed while running, 
ManagedThreadId: 3. at System.Environment.get_StackTrace() 
at System.Runtime.InteropServices.JavaScript.JSWebWorker.JSWebWorkerInstance`1[[System.Int32, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Dispose(Boolean disposing) 
at System.Runtime.InteropServices.JavaScript.JSWebWorker.JSWebWorkerInstance`1[[System.Int32, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Finalize()

Uncaught ExitStatus

Regression?

No response

Known Workarounds

Manually call JSHostImplementation.SetHasExternalEventLoop and JSSynchronizationContext.InstallWebWorkerInterop from user code

Configuration

.NET 9 nightlies, browser-wasm, MT

Other information

@pavelsavara

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 8, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label May 8, 2024
@pavelsavara pavelsavara self-assigned this May 9, 2024
@pavelsavara pavelsavara added this to the 9.0.0 milestone May 9, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label May 9, 2024
@pavelsavara pavelsavara added arch-wasm WebAssembly architecture area-System.Runtime.InteropServices.JavaScript os-browser Browser variant of arch-wasm labels May 9, 2024
@maraf maraf removed the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 9, 2024
@pavelsavara
Copy link
Member

What is supposed to keep the instance of JSWebWorker from being collected ?

@kekekeks
Copy link
Author

The worker is supposed to run until the Task returned from Func<Task> passed to JSWebWorker.RunAsync() is finished.

The returned Task has a subscriber in a form if ContinueWith. So the API usage is definitely correct.

Is the API user supposed to keep the task returned from JSWebWorker.RunAsync GC-rooted? It's not the case for other task-based APIs in the BCL, I think

@kekekeks
Copy link
Author

    private static Task SavedTask;
    public static async Task Main()
    {
        Console.WriteLine("Entering infinite loop on the main thread");

        _ = (SavedTask = JSWebWorkerWrapper.RunAsync(() =>
        {
            Console.WriteLine("Worker started, returning an never-finishing task");
            return new TaskCompletionSource().Task;
        })).ContinueWith(_ =>
        {
            Console.WriteLine("Worker exited");
        });

        while (true)
        {
            for (int c = 0; c < 1000; c++)
                Alloc(60000);
            GC.Collect();
            await Task.Delay(1);
        }
    }

This code still crashes the runtime, so it seems that the API consumer has no way to ensure that JSWorkerInstance is GC-rooted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-wasm WebAssembly architecture area-System.Runtime.InteropServices.JavaScript os-browser Browser variant of arch-wasm
Projects
None yet
Development

No branches or pull requests

3 participants