using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace XFESystem.Kernel;
/// <summary>Actual single-CPU IRQ preemption, independent EH chains and live roots during collection.</summary>
internal static class NativeThreadRegression
{
// Direct NativeAOT symbols supplied by Tools/GcRuntimePatch, rather than a dynamic library.
[DllImport("*", EntryPoint = "XfeRuntimeThreadEhContractVersion")]
[SuppressGCTransition]
private static extern int ThreadEhContractVersion();
[DllImport("*", EntryPoint = "XfeRuntimeCurrentSchedulerThreadId")]
[SuppressGCTransition]
internal static extern int CurrentThreadId();
private sealed class Probe
{
public int ThreadId, ManagedThreadId, InCatch, Steps, BusyStarted, BusyObserved, BusyProgress, Failure, Complete;
public Thread ManagedThread = null!;
public Probe Peer = null!;
}
private static Probe first = null!, second = null!;
private static int collections, preemptionChecks, threadStaticChecks, cancel;
[ThreadStatic] private static int threadStaticToken;
[ThreadStatic] private static byte[]? threadStaticPayload;
private static long checksum;
public static bool Passed { get; private set; }
public static int Steps => Volatile.Read(ref first.Steps) + Volatile.Read(ref second.Steps);
public static int Collections => Volatile.Read(ref collections);
public static int FirstThreadId => Volatile.Read(ref first.ThreadId);
public static int SecondThreadId => Volatile.Read(ref second.ThreadId);
public static int PreemptionChecks => Volatile.Read(ref preemptionChecks);
public static int ThreadStaticChecks => Volatile.Read(ref threadStaticChecks);
public static int FirstManagedThreadId => Volatile.Read(ref first.ManagedThreadId);
public static int SecondManagedThreadId => Volatile.Read(ref second.ManagedThreadId);
public static int FirstFailure => Volatile.Read(ref first.Failure);
public static int SecondFailure => Volatile.Read(ref second.Failure);
public static long SleepElapsedTicks { get; private set; }
public static bool Run()
{
Passed = false;
first = new(); second = new(); first.Peer = second; second.Peer = first;
collections = preemptionChecks = threadStaticChecks = cancel = 0;
if (ThreadEhContractVersion() != 1) return false;
var firstThread = new Thread(() => Exercise(first), NativeManagedWorkScheduler.StackBytes);
var secondThread = new Thread(() => Exercise(second), NativeManagedWorkScheduler.StackBytes);
firstThread.Start(); secondThread.Start();
long deadline = Stopwatch.GetTimestamp() + Stopwatch.Frequency * 20;
while ((Volatile.Read(ref first.Complete) == 0 || Volatile.Read(ref second.Complete) == 0) && Stopwatch.GetTimestamp() < deadline) Thread.Sleep(1);
if (Volatile.Read(ref first.Complete) == 0 || Volatile.Read(ref second.Complete) == 0) Volatile.Write(ref cancel, 1);
// A faster hardware scheduler tick must not speed up managed waits.
// Measure the real native Sleep against the independently read TSC
// clock, rather than treating unchanged wait IL as timing evidence.
long sleepBegin = Stopwatch.GetTimestamp();
Thread.Sleep(25);
SleepElapsedTicks = Stopwatch.GetTimestamp() - sleepBegin;
Passed = Volatile.Read(ref first.Complete) != 0 && Volatile.Read(ref second.Complete) != 0 &&
FirstFailure == 0 && SecondFailure == 0 && FirstThreadId != 0 && SecondThreadId != 0 && FirstThreadId != SecondThreadId &&
FirstManagedThreadId != 0 && SecondManagedThreadId != 0 && FirstManagedThreadId != SecondManagedThreadId && !ReferenceEquals(first.ManagedThread, second.ManagedThread) &&
Steps == 32 && Collections == 4 && PreemptionChecks == 32 && ThreadStaticChecks == 32 &&
SleepElapsedTicks >= Stopwatch.Frequency / 50 && SleepElapsedTicks <= Stopwatch.Frequency;
return Passed;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Exercise(Probe state)
{
try
{
Volatile.Write(ref state.ThreadId, CurrentThreadId());
state.ManagedThread = Thread.CurrentThread;
Volatile.Write(ref state.ManagedThreadId, state.ManagedThread.ManagedThreadId);
if (state.ManagedThread.IsThreadPoolThread) { Volatile.Write(ref state.Failure, 6); Volatile.Write(ref state.Complete, 1); return; }
threadStaticToken = state.ThreadId;
threadStaticPayload = new byte[17]; threadStaticPayload[0] = (byte)state.ThreadId; threadStaticPayload[^1] = 211;
var token = new InvalidOperationException("XFE independent thread exception");
var payload = new byte[8192]; payload[0] = 91; payload[^1] = 113;
for (int round = 0; round < 16 && Volatile.Read(ref cancel) == 0; round++)
{
try { ThrowToken(token); }
catch (InvalidOperationException caught)
{
if (!ReferenceEquals(token, caught)) { Volatile.Write(ref state.Failure, 1); break; }
Volatile.Write(ref state.InCatch, round + 1);
while (Volatile.Read(ref state.Peer.InCatch) < round + 1 && Volatile.Read(ref cancel) == 0 && Volatile.Read(ref state.Peer.Complete) == 0) Thread.Sleep(1);
if (Volatile.Read(ref cancel) != 0 || Volatile.Read(ref state.Peer.Complete) != 0) break;
int before = Volatile.Read(ref state.Peer.BusyProgress), progress = Volatile.Read(ref state.BusyProgress);
int phase = round + 1;
long started = Stopwatch.GetTimestamp(), minimum = started + Stopwatch.Frequency / 20;
long deadline = started + Stopwatch.Frequency * 2;
bool observedPeer = false;
// Take the peer snapshot BEFORE publishing readiness. The
// peer cannot leave this round until we have observed a
// counter change after this point and published that fact.
Volatile.Write(ref state.BusyStarted, phase);
long value = round;
// Fifty milliseconds exceeds the scheduler quantum. No sleep,
// yield, wait, allocation, lock or logging occurs in this span.
while (true)
{
value = unchecked(value * 6364136223846793005L + 1);
Volatile.Write(ref state.BusyProgress, ++progress);
if (!observedPeer && Volatile.Read(ref state.Peer.BusyStarted) >= phase && Volatile.Read(ref state.Peer.BusyProgress) != before)
{
observedPeer = true;
Volatile.Write(ref state.BusyObserved, phase);
}
long now = Stopwatch.GetTimestamp();
if (now >= deadline) { Volatile.Write(ref state.Failure, 2); break; }
if (Volatile.Read(ref cancel) != 0) { Volatile.Write(ref state.Failure, 10); break; }
if (Volatile.Read(ref state.Peer.Failure) != 0) { Volatile.Write(ref state.Failure, 11); break; }
// Complete publishes every earlier peer store. Acquire
// Observed AFTER Complete: a cached earlier false can
// become stale while an IRQ lets the peer finish.
if (Volatile.Read(ref state.Peer.Complete) != 0 &&
(!observedPeer || Volatile.Read(ref state.Peer.BusyObserved) < phase))
{ Volatile.Write(ref state.Failure, 12); break; }
if (observedPeer && Volatile.Read(ref state.Peer.BusyObserved) >= phase && now >= minimum) break;
}
Interlocked.Exchange(ref checksum, value);
if (Volatile.Read(ref state.Failure) != 0) break;
Interlocked.Increment(ref preemptionChecks);
Thread.Sleep(1);
if (round == 4 || round == 12) { GC.Collect(); Interlocked.Increment(ref collections); }
try { throw; }
catch (InvalidOperationException rethrown)
{ if (!ReferenceEquals(token, rethrown)) { Volatile.Write(ref state.Failure, 3); break; } }
if (payload[0] != 91 || payload[^1] != 113) { Volatile.Write(ref state.Failure, 4); break; }
if (threadStaticToken != state.ThreadId || threadStaticPayload is null || threadStaticPayload[0] != (byte)state.ThreadId || threadStaticPayload[^1] != 211)
{ Volatile.Write(ref state.Failure, 7); break; }
if (!ReferenceEquals(Thread.CurrentThread, state.ManagedThread) || Thread.CurrentThread.IsThreadPoolThread || Thread.CurrentThread.ManagedThreadId != state.ManagedThreadId)
{ Volatile.Write(ref state.Failure, 8); break; }
if (ReferenceEquals(state.ManagedThread, state.Peer.ManagedThread) || state.ManagedThreadId == Volatile.Read(ref state.Peer.ManagedThreadId))
{ Volatile.Write(ref state.Failure, 9); break; }
Interlocked.Increment(ref threadStaticChecks);
Volatile.Write(ref state.Steps, round + 1);
GC.KeepAlive(payload); GC.KeepAlive(token);
}
}
}
catch (Exception) { Volatile.Write(ref state.Failure, 5); }
Volatile.Write(ref state.Complete, 1);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowToken(InvalidOperationException token) => throw token;
}
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace XFESystem.Kernel;
/// <summary>Actual single-CPU IRQ preemption, independent EH chains and live roots during collection.</summary>
internal static class NativeThreadRegression
{
// Direct NativeAOT symbols supplied by Tools/GcRuntimePatch, rather than a dynamic library.
[DllImport("*", EntryPoint = "XfeRuntimeThreadEhContractVersion")]
[SuppressGCTransition]
private static extern int ThreadEhContractVersion();
[DllImport("*", EntryPoint = "XfeRuntimeCurrentSchedulerThreadId")]
[SuppressGCTransition]
internal static extern int CurrentThreadId();
private sealed class Probe
{
public int ThreadId, ManagedThreadId, InCatch, Steps, BusyStarted, BusyObserved, BusyProgress, Failure, Complete;
public Thread ManagedThread = null!;
public Probe Peer = null!;
}
private static Probe first = null!, second = null!;
private static int collections, preemptionChecks, threadStaticChecks, cancel;
[ThreadStatic] private static int threadStaticToken;
[ThreadStatic] private static byte[]? threadStaticPayload;
private static long checksum;
public static bool Passed { get; private set; }
public static int Steps => Volatile.Read(ref first.Steps) + Volatile.Read(ref second.Steps);
public static int Collections => Volatile.Read(ref collections);
public static int FirstThreadId => Volatile.Read(ref first.ThreadId);
public static int SecondThreadId => Volatile.Read(ref second.ThreadId);
public static int PreemptionChecks => Volatile.Read(ref preemptionChecks);
public static int ThreadStaticChecks => Volatile.Read(ref threadStaticChecks);
public static int FirstManagedThreadId => Volatile.Read(ref first.ManagedThreadId);
public static int SecondManagedThreadId => Volatile.Read(ref second.ManagedThreadId);
public static int FirstFailure => Volatile.Read(ref first.Failure);
public static int SecondFailure => Volatile.Read(ref second.Failure);
public static long SleepElapsedTicks { get; private set; }
public static bool Run()
{
Passed = false;
first = new(); second = new(); first.Peer = second; second.Peer = first;
collections = preemptionChecks = threadStaticChecks = cancel = 0;
if (ThreadEhContractVersion() != 1) return false;
var firstThread = new Thread(() => Exercise(first), NativeManagedWorkScheduler.StackBytes);
var secondThread = new Thread(() => Exercise(second), NativeManagedWorkScheduler.StackBytes);
firstThread.Start(); secondThread.Start();
long deadline = Stopwatch.GetTimestamp() + Stopwatch.Frequency * 20;
while ((Volatile.Read(ref first.Complete) == 0 || Volatile.Read(ref second.Complete) == 0) && Stopwatch.GetTimestamp() < deadline) Thread.Sleep(1);
if (Volatile.Read(ref first.Complete) == 0 || Volatile.Read(ref second.Complete) == 0) Volatile.Write(ref cancel, 1);
// A faster hardware scheduler tick must not speed up managed waits.
// Measure the real native Sleep against the independently read TSC
// clock, rather than treating unchanged wait IL as timing evidence.
long sleepBegin = Stopwatch.GetTimestamp();
Thread.Sleep(25);
SleepElapsedTicks = Stopwatch.GetTimestamp() - sleepBegin;
Passed = Volatile.Read(ref first.Complete) != 0 && Volatile.Read(ref second.Complete) != 0 &&
FirstFailure == 0 && SecondFailure == 0 && FirstThreadId != 0 && SecondThreadId != 0 && FirstThreadId != SecondThreadId &&
FirstManagedThreadId != 0 && SecondManagedThreadId != 0 && FirstManagedThreadId != SecondManagedThreadId && !ReferenceEquals(first.ManagedThread, second.ManagedThread) &&
Steps == 32 && Collections == 4 && PreemptionChecks == 32 && ThreadStaticChecks == 32 &&
SleepElapsedTicks >= Stopwatch.Frequency / 50 && SleepElapsedTicks <= Stopwatch.Frequency;
return Passed;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Exercise(Probe state)
{
try
{
Volatile.Write(ref state.ThreadId, CurrentThreadId());
state.ManagedThread = Thread.CurrentThread;
Volatile.Write(ref state.ManagedThreadId, state.ManagedThread.ManagedThreadId);
if (state.ManagedThread.IsThreadPoolThread) { Volatile.Write(ref state.Failure, 6); Volatile.Write(ref state.Complete, 1); return; }
threadStaticToken = state.ThreadId;
threadStaticPayload = new byte[17]; threadStaticPayload[0] = (byte)state.ThreadId; threadStaticPayload[^1] = 211;
var token = new InvalidOperationException("XFE independent thread exception");
var payload = new byte[8192]; payload[0] = 91; payload[^1] = 113;
for (int round = 0; round < 16 && Volatile.Read(ref cancel) == 0; round++)
{
try { ThrowToken(token); }
catch (InvalidOperationException caught)
{
if (!ReferenceEquals(token, caught)) { Volatile.Write(ref state.Failure, 1); break; }
Volatile.Write(ref state.InCatch, round + 1);
while (Volatile.Read(ref state.Peer.InCatch) < round + 1 && Volatile.Read(ref cancel) == 0 && Volatile.Read(ref state.Peer.Complete) == 0) Thread.Sleep(1);
if (Volatile.Read(ref cancel) != 0 || Volatile.Read(ref state.Peer.Complete) != 0) break;
int before = Volatile.Read(ref state.Peer.BusyProgress), progress = Volatile.Read(ref state.BusyProgress);
int phase = round + 1;
long started = Stopwatch.GetTimestamp(), minimum = started + Stopwatch.Frequency / 20;
long deadline = started + Stopwatch.Frequency * 2;
bool observedPeer = false;
// Take the peer snapshot BEFORE publishing readiness. The
// peer cannot leave this round until we have observed a
// counter change after this point and published that fact.
Volatile.Write(ref state.BusyStarted, phase);
long value = round;
// Fifty milliseconds exceeds the scheduler quantum. No sleep,
// yield, wait, allocation, lock or logging occurs in this span.
while (true)
{
value = unchecked(value * 6364136223846793005L + 1);
Volatile.Write(ref state.BusyProgress, ++progress);
if (!observedPeer && Volatile.Read(ref state.Peer.BusyStarted) >= phase && Volatile.Read(ref state.Peer.BusyProgress) != before)
{
observedPeer = true;
Volatile.Write(ref state.BusyObserved, phase);
}
long now = Stopwatch.GetTimestamp();
if (now >= deadline) { Volatile.Write(ref state.Failure, 2); break; }
if (Volatile.Read(ref cancel) != 0) { Volatile.Write(ref state.Failure, 10); break; }
if (Volatile.Read(ref state.Peer.Failure) != 0) { Volatile.Write(ref state.Failure, 11); break; }
// Complete publishes every earlier peer store. Acquire
// Observed AFTER Complete: a cached earlier false can
// become stale while an IRQ lets the peer finish.
if (Volatile.Read(ref state.Peer.Complete) != 0 &&
(!observedPeer || Volatile.Read(ref state.Peer.BusyObserved) < phase))
{ Volatile.Write(ref state.Failure, 12); break; }
if (observedPeer && Volatile.Read(ref state.Peer.BusyObserved) >= phase && now >= minimum) break;
}
Interlocked.Exchange(ref checksum, value);
if (Volatile.Read(ref state.Failure) != 0) break;
Interlocked.Increment(ref preemptionChecks);
Thread.Sleep(1);
if (round == 4 || round == 12) { GC.Collect(); Interlocked.Increment(ref collections); }
try { throw; }
catch (InvalidOperationException rethrown)
{ if (!ReferenceEquals(token, rethrown)) { Volatile.Write(ref state.Failure, 3); break; } }
if (payload[0] != 91 || payload[^1] != 113) { Volatile.Write(ref state.Failure, 4); break; }
if (threadStaticToken != state.ThreadId || threadStaticPayload is null || threadStaticPayload[0] != (byte)state.ThreadId || threadStaticPayload[^1] != 211)
{ Volatile.Write(ref state.Failure, 7); break; }
if (!ReferenceEquals(Thread.CurrentThread, state.ManagedThread) || Thread.CurrentThread.IsThreadPoolThread || Thread.CurrentThread.ManagedThreadId != state.ManagedThreadId)
{ Volatile.Write(ref state.Failure, 8); break; }
if (ReferenceEquals(state.ManagedThread, state.Peer.ManagedThread) || state.ManagedThreadId == Volatile.Read(ref state.Peer.ManagedThreadId))
{ Volatile.Write(ref state.Failure, 9); break; }
Interlocked.Increment(ref threadStaticChecks);
Volatile.Write(ref state.Steps, round + 1);
GC.KeepAlive(payload); GC.KeepAlive(token);
}
}
}
catch (Exception) { Volatile.Write(ref state.Failure, 5); }
Volatile.Write(ref state.Complete, 1);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowToken(InvalidOperationException token) => throw token;
}