forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[mono] Fix deadlock in static constructor initialization (dotnet#93875)
* [mono] Fix deadlock in static constructor initialization If two threads (A and B) need to call the static constructors for 3 classes X, Y, Z in this order: Thread A: X, Z, Y Thread B: Y, Z, X where the cctor for X in thread A invokes the cctor for Z and for Y, and the cctor for Y in thread B invokes the cctor for Z and X, then we can get into a situation where A and B both start the cctors for X and Y (so they will be in the type_initialization_hash for those two classes) and then both will try to init Z. In that case it could be that A will be responsible for initializing Z and B will block. Then A could finish initializing Z but B may not have woken up yet (and so it will be in blocked_thread_hash waiting for Z). At that point A (who is at this point already need to init Y) may think that it can wait for B to finish initializing Y. That is we get to `mono_coop_cond_timedwait (&lock->cond, &lock->mutex, timeout_ms)` with "lock" being the lock for `Y`. But in fact thread B will not be able to complete initializing Y because it will attempt to init X next - but meanwhile we did not indicate that thread A is blocked. As a result in thread A the timed wait will eventually timeout. And in this case we need to go back to the top and now correctly detect that A is waiting for Y and B is waiting for X. (At that point there is a cctor deadlock and ECMA rules allow one of the threads to return without calling the cctor) The old code here used to do an infinite wait: while (!lock->done) mono_coop_cond_wait (&lock->cond, &lock->mutex) This cannot succeed because "lock" (in thread A it's the lock for Y) will not be signaled since B (who is supposed to init Y) will instead block on the cctor for X. Fixes dotnet#93778 * Add test case * remove prototyping log messages * disable mt test on wasm * code review feedback
- Loading branch information
1 parent
cfe5ab5
commit 3cd6455
Showing
4 changed files
with
243 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
...ts/Loader/classloader/TypeInitialization/CircularCctors/CircularCctorTwoThreadsThreeCC.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
// 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.Threading; | ||
|
||
using Xunit; | ||
|
||
// Regression test for https://github.com/dotnet/runtime/issues/93778 | ||
namespace CircularCctorTwoThreadsThreeCC; | ||
|
||
[Flags] | ||
public enum SlotConstants : int | ||
{ | ||
ZInited = 1, | ||
YInitedFromX = 2, | ||
XInitedFromY = 4, | ||
|
||
Thread1 = 1 << 16, | ||
Thread2 = 2 << 16, | ||
} | ||
|
||
/// X and Y both try to use each other, and also both use Z. | ||
/// We expect to see exactly one thread initialize Z and | ||
/// either X inited from Y or Y inited from X. | ||
public class X | ||
{ | ||
public static X Singleton = new(); | ||
private X() { | ||
Z.Singleton.Ping(); | ||
Y.Singleton?.Pong(); | ||
} | ||
|
||
public void Pong() => Coordinator.Note(SlotConstants.XInitedFromY); | ||
} | ||
|
||
public class Y | ||
{ | ||
public static Y Singleton = new(); | ||
private Y() { | ||
Z.Singleton.Ping(); | ||
X.Singleton?.Pong(); | ||
} | ||
|
||
public void Pong() => Coordinator.Note(SlotConstants.YInitedFromX); | ||
} | ||
|
||
public class Z | ||
{ | ||
public static Z Singleton = new(); | ||
|
||
private Z() { | ||
Coordinator.Note(SlotConstants.ZInited); | ||
} | ||
|
||
public void Ping() { } | ||
|
||
} | ||
|
||
public class Coordinator | ||
{ | ||
[ThreadStatic] | ||
private static SlotConstants t_threadTag; | ||
|
||
private static int s_NextNote; | ||
private static readonly SlotConstants[] Notes = new SlotConstants[12]; | ||
|
||
private static SlotConstants DecorateWithThread(SlotConstants c) | ||
{ | ||
return c | t_threadTag; | ||
} | ||
|
||
public static void Note(SlotConstants s) { | ||
int idx = Interlocked.Increment(ref s_NextNote); | ||
idx--; | ||
Notes[idx] = DecorateWithThread (s); | ||
} | ||
|
||
public static Coordinator CreateThread(bool xThenY, SlotConstants threadTag) | ||
{ | ||
return new Coordinator(xThenY, threadTag); | ||
} | ||
|
||
public readonly Thread Thread; | ||
private static readonly Barrier s_barrier = new (3); | ||
|
||
private Coordinator(bool xThenY, SlotConstants threadTag) | ||
{ | ||
var t = new Thread(() => { | ||
t_threadTag = threadTag; | ||
// Log("started"); | ||
NextPhase(); | ||
// Log("racing"); | ||
DoConstructions(xThenY); | ||
NextPhase(); | ||
// Log("done"); | ||
}); | ||
Thread = t; | ||
t.Start(); | ||
} | ||
|
||
public static void NextPhase() { s_barrier.SignalAndWait(); } | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
public static void DoConstructions(bool xThenY) | ||
{ | ||
if (xThenY) { | ||
XCreate(); | ||
} else { | ||
YCreate(); | ||
} | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
private static void XCreate() | ||
{ | ||
var _ = X.Singleton; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
private static void YCreate() | ||
{ | ||
var _ = Y.Singleton; | ||
} | ||
|
||
public static void Log(string msg) | ||
{ | ||
Console.WriteLine ($"{Thread.CurrentThread.ManagedThreadId}: {msg}"); | ||
} | ||
|
||
[Fact] | ||
public static void RunTestCase() | ||
{ | ||
var c1 = CreateThread(xThenY: true, threadTag: SlotConstants.Thread1); | ||
var c2 = CreateThread(xThenY: false, threadTag: SlotConstants.Thread2); | ||
// created all threads | ||
NextPhase(); | ||
// racing | ||
NextPhase(); | ||
// done | ||
|
||
// one second should be plenty for all these threads, but it's arbitrary | ||
int threadJoinTimeoutMS = 1000; | ||
var j1 = c1.Thread.Join(threadJoinTimeoutMS); | ||
var j2 = c2.Thread.Join(threadJoinTimeoutMS); | ||
Assert.True(j1); | ||
Assert.True(j2); | ||
// all joined | ||
|
||
// exactly one thread inited Z | ||
Assert.Equal(1, CountNotes(SlotConstants.ZInited)); | ||
// either X was inited or Y, not both. | ||
Assert.Equal(1, Count2Notes(SlotConstants.XInitedFromY, SlotConstants.YInitedFromX)); | ||
} | ||
|
||
private static int CountNotes(SlotConstants mask) | ||
{ | ||
int found = 0; | ||
foreach (var note in Notes) { | ||
if ((note & mask) != (SlotConstants)0) { | ||
found++; | ||
} | ||
} | ||
return found; | ||
} | ||
|
||
private static int Count2Notes(SlotConstants mask1, SlotConstants mask2) | ||
{ | ||
int found = 0; | ||
foreach (var note in Notes) { | ||
if ((note & mask1) != (SlotConstants)0) { | ||
found++; | ||
} | ||
if ((note & mask2) != (SlotConstants)0) { | ||
found++; | ||
} | ||
} | ||
return found; | ||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
...oader/classloader/TypeInitialization/CircularCctors/CircularCctorTwoThreadsThreeCC.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||
<CLRTestPriority>0</CLRTestPriority> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="CircularCctorTwoThreadsThreeCC.cs" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters