Skip to content

Commit

Permalink
[wasm][mt] throw from blocking wait on JS interop threads (dotnet#97052)
Browse files Browse the repository at this point in the history
* [wasm][mt] throw from blocking wait on JS interop threads

Also add test

* Fix typo

* Add JSProxyContextBase conditionally

* Fix non-mt browser build

* Fix tests build

* Do not add JS interop project reference

To not mix intree references and source project

* Add new flag to Monitor

This replaces the previous context base and makes it possible to disable
throw for blocking calls in JS interop

* Remove old file

* Changes from unsaved file

* Wrap another blocking wait in JS interop

* Do not reference src/System.Runtime.InteropServices.JavaScript.csproj

* Disable failing test with blocking Wait

* Update the test condition

* Add missing line after conflict resolution

* Fix build

* Update the active issue and don't use define
  • Loading branch information
radekdoulik committed Feb 1, 2024
1 parent e5cebac commit 9b7aa3d
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 32 deletions.
22 changes: 15 additions & 7 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -446,37 +446,45 @@

<ItemGroup>
<Reference Include="Microsoft.Win32.Primitives" />
<Reference Include="System.Collections" />
<Reference Include="System.Collections.Concurrent" />
<Reference Include="System.Collections.NonGeneric" />
<Reference Include="System.Console" Condition="'$(Configuration)' == 'Debug'" />
<Reference Include="System.Diagnostics.DiagnosticSource" />
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.NameResolution" />
<Reference Include="System.Net.NetworkInformation" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Net.Quic" />
<Reference Include="System.Net.Security" />
<Reference Include="System.Net.Sockets" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.InteropServices" />
<Reference Include="System.Security.Claims" Condition="'$(TargetPlatformIdentifier)' == 'windows'" />
<Reference Include="System.Security.Cryptography" />
<Reference Include="System.Security.Principal.Windows" />
<Reference Include="System.Threading" />
<Reference Include="System.Threading.Channels" />
<Reference Include="System.Threading.ThreadPool" />
<Reference Include="System.IO.Compression.Brotli" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'browser'">
<Reference Include="System.Collections" />
<Reference Include="System.Collections.Concurrent" />
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.Memory" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.InteropServices" />
<Reference Include="System.Threading" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'browser'">
<Reference Include="System.Diagnostics.StackTrace" />
<Reference Include="System.Security.Cryptography" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser'">
<ProjectReference Include="$(CoreLibProject)" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\src\System.Runtime.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading\src\System.Threading.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Private.Uri\src\System.Private.Uri.csproj" PrivateAssets="all" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Collections.Concurrent\src\System.Collections.Concurrent.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,29 @@

<ItemGroup>
<Reference Include="Microsoft.Win32.Primitives" />
<Reference Include="System.Collections" />
<Reference Include="System.Collections.NonGeneric" />
<Reference Include="System.Collections.Specialized" />
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Net.Security" />
<Reference Include="System.Net.WebHeaderCollection" />
<Reference Include="System.Net.WebSockets" />
<Reference Include="System.Runtime" />
<Reference Include="System.Threading" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Security.Cryptography" />
<Reference Include="System.Threading.Channels" Condition="'$(TargetPlatformIdentifier)' == 'browser'" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'browser'">
<Reference Include="System.Collections" />
<Reference Include="System.Runtime" />
<Reference Include="System.Threading" />
<Reference Include="System.Memory" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser'">
<Reference Include="System.Threading.Channels" />
<ProjectReference Include="$(CoreLibProject)" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Private.Uri\src\System.Private.Uri.csproj" PrivateAssets="all" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\src\System.Runtime.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,14 @@ public static partial class Debug
public static System.Diagnostics.DebugProvider SetProvider(System.Diagnostics.DebugProvider provider) { throw null; }
}
}

#if FEATURE_WASM_THREADS
namespace System.Threading
{
public partial class Monitor
{
[ThreadStatic]
public static bool ThrowOnBlockingWaitOnJSInteropThread;
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ T:System.Runtime.Serialization.DeserializationToken
M:System.Runtime.Serialization.SerializationInfo.StartDeserialization
T:System.Diagnostics.DebugProvider
M:System.Diagnostics.Debug.SetProvider(System.Diagnostics.DebugProvider)
F:System.Threading.Monitor.ThrowOnBlockingWaitOnJSInteropThread
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,10 @@
</ItemGroup>

<ItemGroup>
<Reference Include="System.Collections" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.InteropServices" />
<Reference Include="System.Runtime.Loader" />
<Reference Include="System.Threading" />
<Reference Include="System.Threading.Thread" />
<Reference Include="System.Threading.Channels" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(CoreLibProject)" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\src\System.Runtime.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading\src\System.Threading.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading.Channels\src\System.Threading.Channels.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,12 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
}
if (holder.CallbackReady != null)
{
var threadFlag = Monitor.ThrowOnBlockingWaitOnJSInteropThread;
Monitor.ThrowOnBlockingWaitOnJSInteropThread = false;
#pragma warning disable CA1416 // Validate platform compatibility
holder.CallbackReady?.Wait();
#pragma warning restore CA1416 // Validate platform compatibility
Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag;
}
#endif
var callback = holder.Callback!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static JSSynchronizationContext InstallWebWorkerInterop(bool isMainThread
var ctx = new JSSynchronizationContext(isMainThread, cancellationToken);
ctx.previousSynchronizationContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(ctx);
Monitor.ThrowOnBlockingWaitOnJSInteropThread = true;

var proxyContext = ctx.ProxyContext;
JSProxyContext.CurrentThreadContext = proxyContext;
Expand Down Expand Up @@ -215,7 +216,10 @@ public override void Send(SendOrPostCallback d, object? state)
Environment.FailFast($"JSSynchronizationContext.Send failed, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
}

var threadFlag = Monitor.ThrowOnBlockingWaitOnJSInteropThread;
Monitor.ThrowOnBlockingWaitOnJSInteropThread = false;
signal.Wait();
Monitor.ThrowOnBlockingWaitOnJSInteropThread = threadFlag;

if (_isCancellationRequested)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.js" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\timers.mjs" />
<ProjectReference Include="$(CoreLibProject)" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Private.Uri\src\System.Private.Uri.csproj" PrivateAssets="all" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Collections\src\System.Collections.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\src\System.Runtime.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Memory\src\System.Memory.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading\src\System.Threading.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading.Thread\src\System.Threading.Thread.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureWasmThreads)' != 'true'">
Expand All @@ -46,6 +53,5 @@
<None Include="System\Runtime\InteropServices\JavaScript\test.json" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\WebWorkerTestHelper.mjs" />
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\test.json" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,25 @@ await executor.Execute(async () =>
}, cts.Token);
}

[Theory, MemberData(nameof(GetTargetThreads))]
public async Task WaitAssertsOnJSInteropThreads(Executor executor)
{
var cts = CreateTestCaseTimeoutSource();
await executor.Execute(Task () =>
{
Exception? exception = null;
try {
Task.Delay(10, cts.Token).Wait();
} catch (Exception ex) {
exception = ex;
}
executor.AssertBlockingWait(exception);
return Task.CompletedTask;
}, cts.Token);
}

[Theory, MemberData(nameof(GetTargetThreads))]
public async Task ManagedYield(Executor executor)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,22 @@ public void AssertAwaitCapturedContext()
}
}

public void AssertBlockingWait(Exception? exception)
{
switch (Type)
{
case ExecutorType.Main:
case ExecutorType.JSWebWorker:
Assert.NotNull(exception);
Assert.IsType<PlatformNotSupportedException>(exception);
break;
case ExecutorType.NewThread:
case ExecutorType.ThreadPool:
Assert.Null(exception);
break;
}
}

public void AssertInteropThread()
{
switch (Type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,8 @@
<DiagnosticId>CP0014</DiagnosticId>
<Target>M:System.Threading.Monitor.Wait(System.Object):[T:System.Runtime.Versioning.UnsupportedOSPlatformAttribute]</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:System.Threading.Monitor.ThrowOnBlockingWaitOnJSInteropThread</Target>
</Suppression>
</Suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ namespace System.Threading
{
public static partial class Monitor
{
#if FEATURE_WASM_THREADS
[ThreadStatic]
public static bool ThrowOnBlockingWaitOnJSInteropThread;
#endif

[Intrinsic]
[MethodImplAttribute(MethodImplOptions.InternalCall)] // Interpreter is missing this intrinsic
public static void Enter(object obj) => Enter(obj);
Expand Down Expand Up @@ -77,6 +82,12 @@ public static bool IsEntered(object obj)
public static bool Wait(object obj, int millisecondsTimeout)
{
ArgumentNullException.ThrowIfNull(obj);
#if FEATURE_WASM_THREADS
if (ThrowOnBlockingWaitOnJSInteropThread)
{
throw new PlatformNotSupportedException("blocking Wait is not supported on the JS interop threads.");
}
#endif
return ObjWait(millisecondsTimeout, obj);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,7 @@ await EvaluateAndCheck(
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/97652", typeof(DebuggerTests), nameof(DebuggerTests.WasmMultiThreaded))]
public async Task StepOverWithMoreThanOneCommandInSameLineAsync()
{
await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 710, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,5 @@
</ItemGroup>
</Target>

<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
</ItemGroup>

<Import Project="$(BrowserProjectRoot)\build\WasmApp.InTree.targets" />
</Project>
4 changes: 0 additions & 4 deletions src/tests/FunctionalTests/WebAssembly/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
<ExpectedExitCode>42</ExpectedExitCode>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
</ItemGroup>

<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props, '$(MSBuildThisFileDirectory)..'))" />

<ItemGroup Condition="'$(IsWasmTestProject)' != 'false'">
Expand Down

0 comments on commit 9b7aa3d

Please sign in to comment.