Skip to content

[wasm-mt] Remove two-phase suspend; fix state transitions in DS server; fix merge #73305

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 12 commits into from
Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

<!-- Samples that require a multi-threaded runtime -->
<ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(WasmEnableThreads)' != 'true'" >
<ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\browser-threads\Wasm.Browser.Threads.Sample.csproj" />
<ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\browser-mt-eventpipe\Wasm.Browser.ThreadsEP.Sample.csproj" />
</ItemGroup>

Expand Down
35 changes: 32 additions & 3 deletions src/mono/mono/component/diagnostics_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ ds_websocket_url;

extern void mono_wasm_diagnostic_server_on_server_thread_created (char *websocket_url);

/*
* diagnostic server pthread lifetime:
*
* server_thread called: no runtime yet
* server_thread calls emscripten_exit_with_live_runtime - thread is live in JS; no C stack.
* {runtime starts}
* server_thread_attach_to_runtime called from JS: MonoThreadInfo* for server_thread is set,
* thread transitions to GC Unsafe mode and immediately transitions to GC Safe before returning to JS.
* server loop (diagnostics/server_pthread/index.ts serverLoop) starts
* - diagnostic server calls into the runtime
*/
static void*
server_thread (void* unused_arg G_GNUC_UNUSED)
{
Expand Down Expand Up @@ -158,17 +169,24 @@ mono_wasm_diagnostic_server_thread_attach_to_runtime (void)
ds_thread_id = pthread_self();
MonoThread *thread = mono_thread_internal_attach (mono_get_root_domain ());
mono_thread_set_state (thread, ThreadState_Background);
mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_SAMPLE);
/* diagnostic server thread is now in GC Unsafe mode */
mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NO_GC | MONO_THREAD_INFO_FLAGS_NO_SAMPLE);
/* diagnostic server thread is now in GC Unsafe mode
* we're returning to JS, so switch to GC Safe mode
*/
gpointer stackdata;
mono_threads_enter_gc_safe_region_unbalanced (&stackdata);

}

void
mono_wasm_diagnostic_server_post_resume_runtime (void)
{
MONO_ENTER_GC_UNSAFE;
if (wasm_ds_options.suspend) {
/* wake the main thread */
mono_coop_sem_post (&wasm_ds_options.suspend_resume);
}
MONO_EXIT_GC_UNSAFE;
}

#define QUEUE_CLOSE_SENTINEL ((uint8_t*)(intptr_t)-1)
Expand All @@ -195,8 +213,10 @@ queue_wake_reader (void *ptr) {
static void
queue_wake_reader_now (WasmIpcStreamQueue *q)
{
MONO_ENTER_GC_SAFE;
// call only from the diagnostic server thread!
mono_wasm_diagnostic_server_stream_signal_work_available (q, 1);
MONO_EXIT_GC_SAFE;
}

static int32_t
Expand Down Expand Up @@ -236,9 +256,13 @@ queue_push_sync (WasmIpcStreamQueue *q, const uint8_t *buf, uint32_t buf_size, u
if (G_UNLIKELY (is_browser_thread)) {
/* can't use memory.atomic.wait32 on the main thread, spin instead */
/* this lets Emscripten run queued calls on the main thread */
MONO_ENTER_GC_SAFE;
emscripten_thread_sleep (1);
MONO_EXIT_GC_SAFE;
} else {
MONO_ENTER_GC_SAFE;
r = mono_wasm_atomic_wait_i32 (&q->buf_full, 1, -1);
MONO_EXIT_GC_SAFE;
if (G_UNLIKELY (r == 2)) {
/* timed out with infinite wait?? */
return -1;
Expand Down Expand Up @@ -279,17 +303,22 @@ static IpcStreamVtable wasm_ipc_stream_vtable = {
EMSCRIPTEN_KEEPALIVE IpcStream *
mono_wasm_diagnostic_server_create_stream (void)
{
IpcStream *result = NULL;
MONO_ENTER_GC_UNSAFE;
g_assert (G_STRUCT_OFFSET(WasmIpcStream, queue) == 4); // keep in sync with mono_wasm_diagnostic_server_get_stream_queue
WasmIpcStream *stream = g_new0 (WasmIpcStream, 1);
ep_ipc_stream_init (&stream->stream, &wasm_ipc_stream_vtable);
return &stream->stream;
result = &stream->stream;
MONO_EXIT_GC_UNSAFE;
return result;
}

static void
wasm_ipc_stream_free (void *self)
{
g_free (self);
}

static bool
wasm_ipc_stream_read (void *self, uint8_t *buffer, uint32_t bytes_to_read, uint32_t *bytes_read, uint32_t timeout_ms)
{
Expand Down
2 changes: 1 addition & 1 deletion src/mono/mono/metadata/sgen-stw.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ unified_suspend_stop_world (MonoThreadInfoFlags flags, unified_suspend_thread_st
info->client_info.suspend_done = FALSE;
} else {
/* skip threads suspended by previous phase. */
/* threads with info->client_info->skip set to TRUE will be skipped by unified_is_thread_in_current_stw. */
/* threads with info->client_info->skip set to TRUE will be skipped by is_thread_in_current_stw. */
if (info->client_info.suspend_done)
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/mono/mono/utils/mono-threads-state-machine.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ check_thread_state (MonoThreadInfo* info)
g_assert (!no_safepoints);
/* fallthru */
case STATE_ASYNC_SUSPEND_REQUESTED:
g_assert (suspend_count > 0);
g_assertf (suspend_count > 0, "expected suspend_count > 0 in current state: %s, suspend_count == %d", state_name(cur_state), suspend_count);
break;
case STATE_BLOCKING:
g_assert (!no_safepoints);
Expand Down Expand Up @@ -286,7 +286,7 @@ mono_threads_transition_request_suspension (MonoThreadInfo *info)
if (no_safepoints)
mono_fatal_with_history ("no_safepoints = TRUE, but should be FALSE");
if (!(suspend_count > 0 && suspend_count < THREAD_SUSPEND_COUNT_MAX))
mono_fatal_with_history ("suspend_count = %d, but should be > 0 and < THREAD_SUSPEND_COUNT_MAX", suspend_count);
mono_fatal_with_history ("suspend_count = %d, but should be > 0 and < THREAD_SUSPEND_COUNT_MAX, for thread %d", suspend_count, mono_thread_info_get_tid (info));
if (thread_state_cas (&info->thread_state, build_thread_state (cur_state, suspend_count + 1, no_safepoints), raw_state) != raw_state)
goto retry_state_change;
trace_state_change ("SUSPEND_INIT_REQUESTED", info, raw_state, cur_state, no_safepoints, 1);
Expand Down
7 changes: 0 additions & 7 deletions src/mono/mono/utils/mono-threads-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -404,13 +404,6 @@ mono_threads_wasm_browser_thread_tid (void)
#endif
}

gboolean
mono_threads_platform_stw_defer_initial_suspend (MonoThreadInfo *info)
{
/* Suspend the browser thread after all the other threads are suspended already. */
return mono_native_thread_id_equals (mono_thread_info_get_tid (info), mono_threads_wasm_browser_thread_tid ());
}

#ifndef DISABLE_THREADS
extern void
mono_wasm_pthread_on_pthread_attached (gpointer pthread_id);
Expand Down
9 changes: 0 additions & 9 deletions src/mono/mono/utils/mono-threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -1103,8 +1103,6 @@ begin_suspend_peek_and_preempt (MonoThreadInfo *info);
MonoThreadBeginSuspendResult
mono_thread_info_begin_suspend (MonoThreadInfo *info, MonoThreadSuspendPhase phase)
{
if (phase == MONO_THREAD_SUSPEND_PHASE_INITIAL && mono_threads_platform_stw_defer_initial_suspend (info))
return MONO_THREAD_BEGIN_SUSPEND_NEXT_PHASE;
if (phase == MONO_THREAD_SUSPEND_PHASE_MOPUP && mono_threads_is_hybrid_suspension_enabled ())
return begin_suspend_peek_and_preempt (info);
else
Expand Down Expand Up @@ -2176,10 +2174,3 @@ mono_thread_info_get_tools_data (void)
return info ? info->tools_data : NULL;
}

#ifndef HOST_WASM
gboolean
mono_threads_platform_stw_defer_initial_suspend (MonoThreadInfo *info)
{
return FALSE;
}
#endif
1 change: 0 additions & 1 deletion src/mono/mono/utils/mono-threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,6 @@ void mono_threads_platform_init (void);
gboolean mono_threads_platform_in_critical_region (THREAD_INFO_TYPE *info);
gboolean mono_threads_platform_yield (void);
void mono_threads_platform_exit (gsize exit_code);
gboolean mono_threads_platform_stw_defer_initial_suspend (THREAD_INFO_TYPE *info);

void mono_threads_coop_begin_global_suspend (void);
void mono_threads_coop_end_global_suspend (void);
Expand Down
11 changes: 11 additions & 0 deletions src/mono/sample/wasm/browser-threads/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
TOP=../../../../..

include ../wasm.mk

ifneq ($(AOT),)
override MSBUILD_ARGS+=/p:RunAOTCompilation=true
endif

PROJECT_NAME=Wasm.Browser.Threads.Sample.csproj

run: run-browser
151 changes: 151 additions & 0 deletions src/mono/sample/wasm/browser-threads/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Sample
{
public partial class Test
{
public static int Main(string[] args)
{
Console.WriteLine("Hello, World!");
return 0;
}

[JSImport("Sample.Test.updateProgress", "main.js")]
static partial void updateProgress(string status);

internal static void UpdateProgress(string status) => updateProgress(status);

static Demo _demo = null;

[JSExport]
public static void Start(int n)
{
var comp = new ExpensiveComputation(n);
comp.Start();
_demo = new Demo(UpdateProgress, comp);
}

[JSExport]
public static int Progress()
{
if (_demo.Progress())
return 0; /* done */
else
return 1; /* continue */
}

[JSExport]
public static int GetAnswer() { return _demo.Result; }
}

}

public class ExpensiveComputation
{
private readonly TaskCompletionSource<int> _tcs = new();
private readonly int UpTo;
public ExpensiveComputation(int n) { UpTo = n; }
public long CallCounter { get; private set; }
public Task<int> Completion => _tcs.Task;

public void Start()
{
new Thread((o) => ((ExpensiveComputation)o).Run()).Start(this);
}

public void Run()
{
long result = Fib(UpTo);
if (result < (long)int.MaxValue)
_tcs.SetResult((int)result);
else
_tcs.SetException(new Exception("Fibonacci computation exceeded Int32.MaxValue"));
}
public long Fib(int n)
{
CallCounter++;
// make some garbage every 1000 calls
if (CallCounter % 1000 == 0)
{
AllocateGarbage();
}
// and collect it every once in a while
if (CallCounter % 500000 == 0)
GC.Collect();

if (n < 2)
return n;
return Fib(n - 1) + Fib(n - 2);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void AllocateGarbage()
{
object[] garbage = new object[200];
garbage[12] = new object();
garbage[197] = garbage;
}

}

public class Demo
{
public class Animation
{
private readonly Action<string> _updateProgress;
private int _counter = 0;

private readonly IReadOnlyList<string> _animations = new string[] { "⚀", "⚁", "⚂", "⚃", "⚄", "⚅" };

public void Step(string suffix = "")
{
_updateProgress(_animations[_counter++] + suffix);
if (_counter >= _animations.Count)
{
_counter = 0;
}
}

public Animation(Action<string> updateProgress)
{
_updateProgress = updateProgress;
}


}

private readonly Action<string> _updateProgress;
private readonly Animation _animation;
private readonly ExpensiveComputation _expensiveComputation;

public Demo(Action<string> updateProgress, ExpensiveComputation comp)
{
_updateProgress = updateProgress;
_animation = new Animation(updateProgress);
_expensiveComputation = comp;
}

public bool Progress()
{
_animation.Step($"{_expensiveComputation.CallCounter} calls");
if (_expensiveComputation.Completion.IsCompleted)
{
_updateProgress("✌︎");
return true;
}
else
{
return false;
}
}

public int Result => _expensiveComputation.Completion.Result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<WasmCopyAppZipToHelixTestDir Condition="'$(ArchiveTests)' == 'true'">true</WasmCopyAppZipToHelixTestDir>
<WasmMainJSPath>main.js</WasmMainJSPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<WasmDebugLevel>1</WasmDebugLevel>
<GenerateRunScriptForSample Condition="'$(ArchiveTests)' == 'true'">true</GenerateRunScriptForSample>
<RunScriptCommand>$(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll</RunScriptCommand>
</PropertyGroup>
<ItemGroup>
<WasmExtraFilesToDeploy Include="index.html" />
</ItemGroup>
<PropertyGroup>
<_SampleProject>Wasm.Browser.Sample.csproj</_SampleProject>
</PropertyGroup>
<Target Name="RunSample" DependsOnTargets="RunSampleWithBrowser" />

<!-- set the condition to false and you will get a CA1416 error about the call to Thread.Start from a browser-wasm project -->
<ItemGroup Condition="true">
<!-- TODO: some .props file that automates this. Unfortunately just adding a ProjectReference to Microsoft.NET.WebAssembly.Threading.proj doesn't work - it ends up bundling the ref assemblies into the publish directory and breaking the app. -->
<!-- it's a reference assembly, but the project system doesn't know that - include it during compilation, but don't publish it -->
<ProjectReference Include="$(LibrariesProjectRoot)\System.Threading.Thread.WebAssembly.Threading\ref\System.Threading.Thread.WebAssembly.Threading.csproj" IncludeAssets="compile" PrivateAssets="none" ExcludeAssets="runtime" Private="false" />
<ProjectReference Include="$(LibrariesProjectRoot)\System.Threading.ThreadPool.WebAssembly.Threading\ref\System.Threading.ThreadPool.WebAssembly.Threading.csproj" IncludeAssets="compile" PrivateAssets="none" ExcludeAssets="runtime" Private="false" />
<ProjectReference Include="$(LibrariesProjectRoot)\System.Diagnostics.Tracing.WebAssembly.PerfTracing\ref\System.Diagnostics.Tracing.WebAssembly.PerfTracing.csproj" IncludeAssets="compile" PrivateAssets="none" ExcludeAssets="runtime" Private="false" />
</ItemGroup>

</Project>
26 changes: 26 additions & 0 deletions src/mono/sample/wasm/browser-threads/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>

<head>
<title>Wasm Browser Sample</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="modulepreload" href="./main.js" />
<link rel="modulepreload" href="./dotnet.js" />
</head>

<body>
<h3 id="header">Wasm Browser Threading Sample</h3>
<div>
<label for="inputN">N:</label> <input type="number" id="inputN" value="29" disabled />
</div>
This calculation will take a long time: <span id="out"></span>
<div>
Progress: <span id="progressElement" style="font-size: 500%"> </span>
</div>
<script type='module' src="./main.js"></script>
</body>

</html>
Loading