using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace XFESystem.Kernel; /// Actual single-CPU IRQ preemption, independent EH chains and live roots during collection. internal static class NativeThreadRegression { [DllImport("libSystem.Native", EntryPoint = "XfeRuntimeThreadEhContractVersion")] [SuppressGCTransition] private static extern int ThreadEhContractVersion(); [DllImport("libSystem.Native", 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 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); 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; 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; }