runtime: assert `Failed to SetThreadContext in RedirectThreadAtHandledJITCase - aborting redirect.`

I was trying to write a repro case for #13041 but hit another VM assert: Assert failure(PID 105560 [0x00019c58], Thread: 99252 [0x183b4]): bRes && "Failed to SetThreadContext in RedirectThreadAtHandledJITCase - aborting redirect."

The test runs many threads with stack-allocated buffer and there is a separate thread that is calling GC.Collect, I do not think it does anything illegal.

Test code
// 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.Numerics;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Runtime.Intrinsics;
using System.Security.Cryptography;
using System.Threading;

class Runtime_13041 
{

    struct s8
    {
        long f;
    }

    struct s32
    {
        s8 f1;
        s8 f2;
        s8 f3;
        s8 f4;
    }

    struct s128
    {
        s32 f1;
        s32 f2;
        s32 f3;
        s32 f4;
    }

    struct s512
    {
        s128 f1;
        s128 f2;
        s128 f3;
        s128 f4;
    }

    static int StrangeMethod(int length, object a, object b, object c, object d)
    {
        s512 s = new s512();
        string e = new string("abe");
        string f = new string("abf");
        string g = new string("abg");
        string h = new string("abh");

        if (length == 1000)
        {
            return 1000 + e[0] + f[0] + g[0] + h[0];
        }
        Span<int> numbers = stackalloc int[length];
        int sum = 0;

        for (int i = 0; i < length; ++i)
        {
            numbers[i] = i;
        }
        for (int i = 0; i < length; ++i)
        {
            sum += numbers[i];
            if (sum == 4000)
            {
                return 4000;
            }
        }
        return sum;
    }

    static void CallGC()
    {
        while (true)
        {
            System.GC.Collect();
        }
    }

    public static int Main()
    {
        Thread gcThread = new Thread(() => CallGC());
        gcThread.Start();

        const int iterCount = 1000;

        for (int iter = 0; iter < iterCount; ++iter)
        {
            int sum = 0;
            const int threadCount = 128;
            Thread[] threads = new Thread[threadCount];
            int[] results = new int[threadCount];
            for (int i = 0; i < threadCount; ++i)
            {
                int threadId = i;
                int length = threadCount * i + iter + 1;
                threads[threadId] = new Thread(() => { results[threadId] = StrangeMethod(length, null, null, null, null); });
                threads[threadId].Start();
            }


            for (int i = 0; i < threadCount; ++i)
            {

                threads[i].Join();
                sum += results[i];
            }

            Console.WriteLine("Finished an iteration, the sum is: " + sum);
        }
        return 100;
    }
}

it fails with the same error with a ~30% repro rate in a ~2-minute run, I could hit it in VS and collect info that is necessary. Windows x64 last update, (amd ryzen), checked CoreCLR, release libraries, compiled with \git\runtime\.dotnet\dotnet.exe msbuild /p:TargetArchitecture=x64 /p:Configuration=Checked /p:LibrariesConfiguration=Release.

stack in the thread where the assert happens
 	KernelBase.dll!wil::details::DebugBreak(void)	Unknown
 	coreclr.dll!DbgAssertDialog(const char * szFile, int iLine, const char * szExpr) Line 700	C++
>	coreclr.dll!Thread::RedirectThreadAtHandledJITCase(void(*)() pTgt) Line 3200	C++
 	coreclr.dll!Thread::CheckForAndDoRedirect(void(*)() pRedirectTarget) Line 3228	C++
 	coreclr.dll!Thread::CheckForAndDoRedirectForGC() Line 3335	C++
 	coreclr.dll!ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason) Line 3982	C++
 	coreclr.dll!ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_REASON reason) Line 6137	C++
 	coreclr.dll!GCToEEInterface::SuspendEE(SUSPEND_REASON reason) Line 28	C++
 	coreclr.dll!WKS::GCHeap::GarbageCollectGeneration(unsigned int gen, gc_reason reason) Line 37395	C++
 	coreclr.dll!WKS::GCHeap::GarbageCollect(int generation, bool low_memory_p, int mode) Line 36619	C++
 	coreclr.dll!GCInterface::Collect(int generation, int mode) Line 1011	C++
 	00007ff9a3c6b53a()	Unknown
 	System.Private.CoreLib.dll!00007ffa1c4a206a()	Unknown
 	System.Private.CoreLib.dll!00007ffa1c4acba0()	Unknown
 	System.Private.CoreLib.dll!00007ffa1c4a21fd()	Unknown
 	coreclr.dll!CallDescrWorkerInternal() Line 100	Unknown
 	coreclr.dll!MethodDescCallSite::CallTargetWorker(const unsigned __int64 * pArguments, unsigned __int64 * pReturnValue, int cbReturnValue) Line 552	C++
 	coreclr.dll!ThreadNative::KickOffThread_Worker(void * ptr) Line 247	C++
 	coreclr.dll!ManagedThreadBase_DispatchInner(ManagedThreadCallState * pCallState) Line 7337	C++
 	coreclr.dll!ManagedThreadBase_DispatchMiddle(ManagedThreadCallState * pCallState) Line 7381	C++
 	coreclr.dll!``ManagedThreadBase_DispatchOuter'::`11'::__Body::Run'::`5'::__Body::Run(Param * pParam) Line 7540	C++
 	coreclr.dll!`ManagedThreadBase_DispatchOuter'::`11'::__Body::Run(ManagedThreadBase_DispatchOuter::__l2::TryArgs * pArgs) Line 7542	C++
 	coreclr.dll!ManagedThreadBase_DispatchOuter(ManagedThreadCallState * pCallState) Line 7564	C++
 	coreclr.dll!ManagedThreadBase_FullTransition(void(*)(void *) pTarget, void * args, UnhandledExceptionLocation filterType) Line 7588	C++
 	coreclr.dll!ThreadNative::KickOffThread(void * pass) Line 327	C++
 	kernel32.dll!BaseThreadInitThunk()	Unknown
 	ntdll.dll!RtlUserThreadStart()	Unknown
threads
Not Flagged		110804	0	Worker Thread	coreclr.dll!DebuggerRCThread::ThreadProcStatic	coreclr.dll!DebuggerRCThread::MainLoop
Not Flagged		57628	0	Main Thread	Main Thread	coreclr.dll!CrstBase::Enter
Not Flagged		99244	0	Worker Thread	ntdll.dll!TppWorkerThread	ntdll.dll!NtWaitForWorkViaWorkerFactory
Not Flagged		88748	0	Worker Thread	ntdll.dll!TppWorkerThread	ntdll.dll!NtWaitForWorkViaWorkerFactory
Not Flagged		112384	0	Worker Thread	ntdll.dll!TppWorkerThread	ntdll.dll!NtWaitForWorkViaWorkerFactory
Not Flagged		38804	0	Worker Thread	coreclr.dll!DiagnosticServer::DiagnosticsServerThread	coreclr.dll!IpcStream::DiagnosticsIpc::Poll
Not Flagged		52320	0	Worker Thread	.NET Finalizer	[Inline Frame] coreclr.dll!CLREventWaitHelper2
Not Flagged	>	99252	0	Worker Thread	coreclr.dll!ThreadNative::KickOffThread	coreclr.dll!DbgAssertDialog
Not Flagged		26848	0	Worker Thread	coreclr.dll!ThreadNative::KickOffThread	[Inline Frame] coreclr.dll!CLREventWaitHelper2
Not Flagged		12148	0	Worker Thread	coreclr.dll!ThreadNative::KickOffThread	[Inline Frame] coreclr.dll!CLREventWaitHelper2
Not Flagged		111308	0	Worker Thread	coreclr.dll!ThreadNative::KickOffThread	[Inline Frame] coreclr.dll!CLREventWaitHelper2
Not Flagged		10540	0	Worker Thread	coreclr.dll!ThreadNative::KickOffThread	00007ff9a3c6bb7d

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 16 (16 by maintainers)

Most upvoted comments

@janvorli discovered that this may be caused by using SP that points to uncommitted stack page.

Here is a more reliable repro:

using System;
using System.Threading;
using System.Runtime.CompilerServices;

class Program 
{
    static volatile bool never;

    static int stackAlloc = 10000;

    [MethodImplAttribute(MethodImplOptions.NoInlining)]
    static void Dummy(long a0, long a1, long a2, long a3, long a4, long a5, long a6, long a7, long a8, long a9,
        long a10, long a11, long a12, long a13, long a14, long a15, long a16, long a17, long a18, long a19,
        long a20, long a21, long a22, long a23, long a24, long a25, long a26, long a27, long a28, long a29,
        long a30, long a31)
    {
    }

    static int Loop(int count)
    {
        int fact = 0;
        for (int i = 0; i < count; i++) fact *= i;

        if (never) Dummy(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

        return fact;
    }

    static void Work()
    {
        Span<byte> s = stackalloc byte[Interlocked.Add(ref stackAlloc, 16*8)];
        s[0] = 42;

        Loop(1000000000);

        new Thread(Work).Start();
    }

    public static int Main()
    {
        new Thread(() => { for (;;) { GC.Collect(); Thread.Sleep(1); } });

        for (int i = 0; i < 100; i++)
            new Thread(Work).Start();
        Work();

        return 100;
    }
}