Skip to content

JIT: enable inlining methods with EH #112998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Mar 20, 2025

Conversation

AndyAyersMS
Copy link
Member

Enable inlining of method with EH. Inlinee EH clauses are integrated into the root method EH table at the appropriate point (mid-table if the call site is in an EH region; at table end otherwise).

Contributes to #108900.

Enable inlining of method with EH. Inlinee EH clauses are integrated
into the root method EH table at the appropriate point (mid-table if
the call site is in an EH region; at table end otherwise).

Contributes to dotnet#108900.
@ghost ghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Feb 27, 2025
@AndyAyersMS
Copy link
Member Author

SPMI is not going to be helpful here. PMI may not be either, as callees with EH may not look like attractive candidates w/o PGO.

No special work done on profitability, we just use whatever rules the JIT has already.

Also expecting (though perhaps not in our tests) the usual slew of surprises as certain methods no longer show up in stack traces.

@EgorBo
Copy link
Member

EgorBo commented Feb 27, 2025

No special work done on profitability, we just use whatever rules the JIT has already.

But technically, inliner should resist a bit more for such methods? I think it's fine for this PR not to care about it

@AndyAyersMS
Copy link
Member Author

Seems like the x86 crossgen issues are that we have managed methods with both EH and unmanaged calling conventions and we try and inline these and get confused by the call argument reversal.

For now I'll just block inlining of managed methods with unmanaged conventions.

@AndyAyersMS
Copy link
Member Author

Found a couple places where we rely on info.compXcptnsCount > 0 which will be wrong if the root method has no EH but some inlinees do. Also it can be wrong if we manage to remove EH from the method. Will try using compHndBBtabCount instead.

@AndyAyersMS
Copy link
Member Author

Various folks have reminded me that the EH clause reporting for catches is token based, and this will need to be generalized somehow to handle cross-module inlines. The assumption today is that the class token in the clause is from the same module as the root method.

For runtime dependent types we resolve these tokens into code via fgCreateFiltersForGenericExceptions, though there we also assume the token is from the root method. Perhaps we could extend this to cover all callee-inserted EH clauses (or all clauses?).

For now we can perhaps restrict inlines with EH to be same module.

@AndyAyersMS
Copy link
Member Author

AndyAyersMS commented Feb 28, 2025

Also looks like we need to update ebdHandlerNestingLevel and ehMaxHndNestingCount for x86.

@AndyAyersMS
Copy link
Member Author

Hard to say what fraction of the failures are catch-type related, but likely quite a few. So am going to restrict to just try-finally for now.

Also x86 still not happy.

@AndyAyersMS
Copy link
Member Author

For x86, we need to remove the shadow SP slots var if we manage to remove all EH. Seems like what we ought to do is defer allocating this until we're done changing EH regions, then determine if we need this and the max nesting level.

Going to spin that off as a separate change as it leads to some diffs on its own.

@AndyAyersMS
Copy link
Member Author

Split off some of the enabling changes here: #113023.

Don't try an inline managed methods with unmanaged calling conventions.
This mainly copes with x86 where unmanaged calling conventions use reversed
arg order, but I've disabled it in general. No diffs as these methods seem
to always include EH.

Remove uses of `compXcptnsCount` as this goes stale whenever we clone or
remove EH, or (eventually) inline methods with EH. Instead, rely on
`compHndBBtabCount`.

Defer allocating x86's shadow SP var and area until later in jitting,
so this reflects any changes in EH table structure. In particular we
often are able to eliminate EH in part or all together and this saves
a low-offset allocation and so leads to some nice code size savings on
x86.

Also on x86 remove the runtime-dependent catch class case from the
computation for keeping this alive, as we now transform such into
runtime lookups in filters (that may well keep this alive).
@AndyAyersMS
Copy link
Member Author

Down to 300-ish failures

Libraries:

Assert failure(PID 3800 [0x00000ed8], Thread: 5684 [0x1634]): Assertion failed 'block->bbTryIndex == blockTryIndex[block->bbNum]' in 'Microsoft.Cci.MetadataVisitor:Visit(System.Collections.Generic.IEnumerable`1[Microsoft.Cci.IFieldDefinition]):this' during 'Create EH funclets' (IL size 44; hash 0x7157c8c3; Tier1)

X86 (widespread)

Assert failure(PID 36136 [0x00008d28], Thread: 33712 [0x83b0]): Assertion failed 'isFramePointerRequired()' in 'System.IO.FileStream:Dispose(ubyte):this' during 'Generate code' (IL size 18; hash 0xfa74c913; Tier1)

R2R

compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(((EcmaMethod)resultMethod).OwningType)
at System.Diagnostics.DebugProvider.Fail(String, String) + 0x37
at System.Diagnostics.Debug.Fail(String, String) + 0x44
at Internal.JitInterface.CorInfoImpl.HandleToModuleToken(CORINFO_RESOLVED_TOKEN&) + 0x4e5
at Internal.JitInterface.CorInfoImpl.ComputeMethodWithToken(MethodDesc, CORINFO_RESOLVED_TOKEN&, TypeDesc, Boolean) + 0x58
at Internal.JitInterface.CorInfoImpl.getCallInfo(CORINFO_RESOLVED_TOKEN&, CORINFO_RESOLVED_TOKEN*, CORINFO_METHOD_STRUCT, CORINFO_CALLINFO_FLAGS, CORINFO_CALL_INFO) + 0x7ac
at Internal.JitInterface.CorInfoImpl.getCallInfo(IntPtr, IntPtr*, CORINFO_RESOLVED_TOKEN*, CORINFO_RESOLVED_TOKEN*, CORINFO_METHOD_STRUCT, CORINFO_CALLINFO_FLAGS, CORINFO_CALL_INFO) + 0x5d
at Internal.JitInterface.CorInfoImpl.JitCompileMethod(IntPtr&, IntPtr, IntPtr, IntPtr, CORINFO_METHOD_INFO&, UInt32, IntPtr&, UInt32&) + 0xc5

@AndyAyersMS
Copy link
Member Author

AndyAyersMS commented Mar 2, 2025

For the first assert. Before funclet splitting we have

BB176 [0022]  1  1    BB09                  0.27  4504 [???..???)-> BB177(1)                  (always) T1                  i IBC internal hascall gcsafe bwd
BB177 [0020]  2  1    BB175,BB176           3.87 64340 [017..01F)-> BB10(0.742),BB178(0.258)  ( cond ) T1                  i IBC internal bwd bwd-src
BB165 [0160]  1  1  0                       0        0 [010..011)-> BB169(0.48),BB166(0.52)   ( cond ) T1 H0   fault {     i IBC rare keep xentry bwd
BB166 [0161]  1  1  0 BB165                 0        0 [010..011)-> BB169(0.73),BB168(0.27)   ( cond ) T1 H0               i IBC rare xentry bwd
BB168 [0177]  1  1  0 BB166                 0        0 [010..011)-> BB169(1)                  (always) T1 H0               i IBC rare internal hascall xentry gcsafe bwd
BB169 [0175]  3  1  0 BB165,BB166,BB168     0        0 [010..011)                             (falret) T1 H0   } }         i IBC rare xentry bwd
BB178 [0009]  1       BB177                 1    16612 [021..024)-> BB181(0.22),BB180(0.78)   ( cond )                     i IBC
BB180 [0338]  1       BB178                 0.77 12728 [???..???)-> BB182(1)                  (always)                     i IBC internal hascall gcsafe
BB181 [0339]  1       BB178                 0.22  3590 [???..???)-> BB182(1)                  (always)                     i IBC internal hascall gcsafe
BB182 [0340]  2       BB180,BB181           0.98 16318 [02A..02C)                             (return)                     i IBC
BB183 [0005]  1     1                       0.00     0 [021..024)-> BB187(0.0177),BB184(0.982)  ( cond )    H1   fault {     i IBC keep xentry
BB184 [0006]  1     1 BB183                 0.00     0 [024..???)-> BB186(0.22),BB185(0.78)   ( cond )    H1               i IBC xentry
BB185 [0024]  1     1 BB184                 0.00     0 [???..???)-> BB187(1)                  (always)    H1               i IBC internal hascall xentry gcsafe
BB186 [0025]  1     1 BB184                 0.00     0 [???..???)-> BB187(1)                  (always)    H1               i IBC internal hascall xentry gcsafe
BB187 [0023]  3     1 BB183,BB185,BB186     0.00     0 [02A..02B)                             (falret)    H1   }           i IBC xentry

and afterwards we've broken up T1:

BB175 [0021]  1  1    BB09                  3.60 59836 [???..???)-> BB177(1)                  (always) T1                  i IBC internal hascall gcsafe bwd
BB176 [0022]  1  1    BB09                  0.27  4504 [???..???)-> BB177(1)                  (always) T1                  i IBC internal hascall gcsafe bwd
BB177 [0020]  2  1    BB175,BB176           3.87 64340 [017..01F)-> BB10(0.742),BB178(0.258)  ( cond ) T1      }           i IBC internal bwd bwd-src
BB178 [0009]  1       BB177                 1    16612 [021..024)-> BB181(0.22),BB180(0.78)   ( cond )                     i IBC
BB180 [0338]  1       BB178                 0.77 12728 [???..???)-> BB182(1)                  (always)                     i IBC internal hascall gcsafe
BB181 [0339]  1       BB178                 0.22  3590 [???..???)-> BB182(1)                  (always)                     i IBC internal hascall gcsafe
BB182 [0340]  2       BB180,BB181           0.98 16318 [02A..02C)                             (return)                     i IBC
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ funclets follow
BB165 [0160]  1  1  0                       0        0 [010..011)-> BB169(0.48),BB166(0.52)   ( cond ) T1 H0 F fault {     i IBC rare keep xentry flet bwd
BB166 [0161]  1  1  0 BB165                 0        0 [010..011)-> BB169(0.73),BB168(0.27)   ( cond ) T1 H0               i IBC rare xentry bwd
BB168 [0177]  1  1  0 BB166                 0        0 [010..011)-> BB169(1)                  (always) T1 H0               i IBC rare internal hascall xentry gcsafe bwd
BB169 [0175]  3  1  0 BB165,BB166,BB168     0        0 [010..011)                             (falret) T1 H0   }           i IBC rare xentry bwd
BB183 [0005]  1     1                       0.00     0 [021..024)-> BB187(0.0177),BB184(0.982)  ( cond )    H1 F fault {     i IBC keep xentry flet
BB184 [0006]  1     1 BB183                 0.00     0 [024..???)-> BB186(0.22),BB185(0.78)   ( cond )    H1               i IBC xentry
BB185 [0024]  1     1 BB184                 0.00     0 [???..???)-> BB187(1)                  (always)    H1               i IBC internal hascall xentry gcsafe
BB186 [0025]  1     1 BB184                 0.00     0 [???..???)-> BB187(1)                  (always)    H1               i IBC internal hascall xentry gcsafe
BB187 [0023]  3     1 BB183,BB185,BB186     0.00     0 [02A..02B)                             (falret)    H1   }           i IBC xentry

Likely fallout from using ehTrueEnclosingTryIndexIL which I believe we need to phase out (at least once we reach inlining), as there is no good way to interpret inlinee IL offsets vs inliner IL offsets.

Here we have

***************  Exception Handling table
index  eTry, eHnd
  0  ::   1        - Try at BB189..BB133 [007..021), Fault   at BB165..BB169 [021..02B)
  1  ::            - Try at BB188..BB177 [007..021), Fault   at BB183..BB187 [021..02B)
Relocated blocks [BB183..BB187] inserted after BB169 at the end of method
Create funclets: moved region

***************  Exception Handling table
index  eTry, eHnd
  0  ::   1        - Try at BB189..BB133 [007..021), Fault   at BB165..BB169 [021..02B)
  1  ::            - Try at BB188..BB177 [007..021), Fault   at BB183..BB187 [021..02B)

But EH0 comes from an inlinee, so these are not mutual-protect regions.

Once we can inline methods with EH, IL ranges are no longer a reliable indicator
of a mutual-protect try regions. Instead, after importation, we can rely on
mutual-protect trys having the same start and end blocks.

Also update other case where we were using `info.compXcptnsCount` in morph
to decide if we needed a frame pointer. This lets us simplify the logic around
frame pointers and EH (though I still think we're making up our minds too early).

Contributes to dotnet#108900.
@AndyAyersMS
Copy link
Member Author

Latest batch of issues

x86:

Assert failure(PID 5348 [0x000014e4], Thread: 5532 [0x159c]): Assertion failed 'bbNumTryLast <= bbNumOuterHndLast' in 'System.Threading.Tasks.Task:InternalWaitCore(int,System.Threading.CancellationToken):ubyte:this' during 'Optimize control flow' (IL size 239; hash 0xbcf79fbe; Tier1)

R2R: as above

Arm64 Linux: widespread crypto failures

@AndyAyersMS
Copy link
Member Author

@jkotas @davidwrighton The jit is triggering an assert in crossgen2 inside getCallInfo when assessing inlining for a native library wrapper method. Previously we wouldn't try inlining these because they contain EH, now we think we can.

I suspect there's a missing check somewhere but not sure where it should be or what form it should take.

call_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(((EcmaMethod)resultMethod).OwningType)
   at System.Diagnostics.DebugProvider.Fail(String, String) + 0x25
   at System.Diagnostics.Debug.Fail(String, String) + 0x2e
   at Internal.JitInterface.CorInfoImpl.HandleToModuleToken(CORINFO_RESOLVED_TOKEN&) + 0x3c6
   at Internal.JitInterface.CorInfoImpl.HandleToModuleToken(CORINFO_RESOLVED_TOKEN&, MethodDesc, Object&, TypeDesc&) + 0xdb
   at Internal.JitInterface.CorInfoImpl.ComputeMethodWithToken(MethodDesc, CORINFO_RESOLVED_TOKEN&, TypeDesc, Boolean) + 0x2f
   at Internal.JitInterface.CorInfoImpl.getCallInfo(CORINFO_RESOLVED_TOKEN&, CORINFO_RESOLVED_TOKEN*, CORINFO_METHOD_STRUCT_*, CORINFO_CALLINFO_FLAGS, CORINFO_CALL_INFO*) + 0x626
   at Internal.JitInterface.CorInfoImpl._getCallInfo(IntPtr, IntPtr*, CORINFO_RESOLVED_TOKEN*, CORINFO_RESOLVED_TOKEN*, CORINFO_METHOD_STRUCT_*, CORINFO_CALLINFO_FLAGS, CORINFO_CALL_INFO*) + 0x39
   at Internal.JitInterface.CorInfoImpl.JitCompileMethod(IntPtr&, IntPtr, IntPtr, IntPtr, CORINFO_METHOD_INFO&, UInt32, IntPtr&, UInt32&) + 0x6c
   at Internal.JitInterface.CorInfoImpl.CompileMethodInternal(IMethodNode, MethodIL) + 0x9c
   at Internal.JitInterface.CorInfoImpl.CompileMethod(MethodWithGCInfo, Logger) + 0x2f4
   at ILCompiler.ReadyToRunCodegenCompilation.<>c__DisplayClass50_0.<ComputeDependencyNodeDependencies>g__CompileOneMethod|5(DependencyNodeCore`1, Int32) + 0x306
   at ILCompiler.ReadyToRunCodegenCompilation.<>c__DisplayClass50_0.<ComputeDependencyNodeDependencies>g__CompileOnThread|4(Int32) + 0x27
   at ILCompiler.ReadyToRunCodegenCompilation.<>c__DisplayClass50_0.<ComputeDependencyNodeDependencies>g__CompilationThread|3(Object) + 0x35
   at System.Threading.Thread.StartHelper.RunWorker() + 0x47
   at System.Threading.Thread.StartThread(IntPtr) + 0x6f
   at System.Threading.Thread.ThreadEntryPoint(IntPtr) + 0x15 0A000020

This comes up in the R2R version resilience tests.

@AndyAyersMS
Copy link
Member Author

Seems like maybe these stubs are not safely inlinable. Perhaps they have some residual state that does not get reset?

The jit doesn't seem to have a way to detect that an inlinee is one of these stubs, it's only told if the root method is one (via jit flags). Perhaps it makes sense to mark these as noinline for now.

@AndyAyersMS
Copy link
Member Author

@mihu-bot

@AndyAyersMS
Copy link
Member Author

I think this is good to go, but am going to hold off merging until after the Preview3 snap.

@janvorli
Copy link
Member

@AndyAyersMS let me take a look at the dump. This is a case on unhandled exception where we reraise the exception when there are no more managed frames on the stack that would handle it and we want to give a native caller a chance to catch that unhandled exception. There is a special handling in the ProcessCLRException personality routine for managed frames to just continue searching in this case. Maybe I have just missed the fact that the HijackHandler can also be on the stack and thus needs the same treatment.

return;
}

// Do not inline pinvoke stubs with EH.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this comment describe why we are doing this and/or should we get an issue opened to fix this? The problem looked like a bug in R2R compiler to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will open an issue.

A few more details above: #112998 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkotas
Copy link
Member

jkotas commented Mar 17, 2025

This is a case on unhandled exception where we reraise the exception when there are no more managed frames on the stack that would handle it and we want to give a native caller a chance to catch that unhandled exception. There is a special handling in the ProcessCLRException personality routine for managed frames to just continue searching in this case. Maybe I have just missed the fact that the HijackHandler can also be on the stack and thus needs the same treatment.

HijackHandler is implementation detail of Thread.Abort (exposed as ControlledExecution API). All exceptions in ControlledExecution tests should be handled. If we are seeing unhandled exception in ControlledExecution tests, it likely means that some subtle invariants around Thread.Abort handling got broken.

@AndyAyersMS
Copy link
Member Author

/azp run runtime-coreclr libraries-pgo

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@janvorli
Copy link
Member

@AndyAyersMS, @jkotas I have found the culprit. It was introduced by my #112666, the added check at this line https://github.com/dotnet/runtime/pull/112666/files#diff-7769667aa99181e4e0ee0226f639f9a3aad3cd351fe15e883e765cda13e63764R8607
(!((DebuggerU2MCatchHandlerFrame*)pFrame)->CatchesAllExceptions())) causes the ThreadAbortException to be considered unhandled in secondary threads.

@AndyAyersMS
Copy link
Member Author

@janvorli let me know when you've fixed the issue. I'll hold off merging this until then.

@janvorli
Copy link
Member

I was trying to repro the issue locally, but so far I was not successful. However, the culprit I have mentioned should not cause the problem we are seeing, because the ThreadAbortException should not escape the ControlledExecution.Run. So the fact that it gets reported as unhandled exception is a secondary effect, the primary issue is that it escapes that function. That's what @jkotas likely meant by his comment above. At the crash time, the ControlledExecution.Run is not on the stack at all. But I can see another instance of ThreadAbortException that has a stack trace in the test:

0:011> !pe 0196d509e3a0
Exception object: 00000196d509e3a0
Exception type:   System.Threading.ThreadAbortException
Message:          System error.
InnerException:   <none>
StackTrace (generated):
    SP               IP               Function
    0000007F3467B660 00007FF7DA088A98 System_Runtime_Tests!System.Runtime.Tests.ControlledExecutionTests.RunInfiniteLoop()+0xe8
    0000007F3467B6B0 00007FF7DA088944 System_Runtime_Tests!System.Runtime.Tests.ControlledExecutionTests+<>c__DisplayClass8_0.<CancelInTryAndExitCatchNormally>g__Test|1()+0x44

While there are 24 distinct ThreadAbortException instances on the stack, no other has ControlledExecutionTests stuff on it. It almost seems as if the thread accidentally remained marked for thread abort and it kicked in later after the test exited.

So I am starting to think that the issue has nothing to do with @AndyAyersMS's change, but it is rather a rare intermittent issue that just kicked in in this PR.

@janvorli
Copy link
Member

janvorli commented Mar 20, 2025

Another interesting thing is that the Thread::m_abortController of the failing thread is set to 1, but none of the threads in the process has any of the functions that use the AbortRequestLockHolder (which increments and decrements that value) on their stack.
Ah, no, I've confused this with the AbortControlHolder

@janvorli
Copy link
Member

janvorli commented Mar 20, 2025

Actually, in the crash dump, the thread abort was issued by other thread than the one running the test. I haven't noticed that before.
This is the stack trace of the thread that initiated the abort:

OS Thread Id: 0x26f4 (14)
        Child SP               IP Call Site
0000007F34AFE960 00007FF8ACE015E4 ntdll!ZwWaitForMultipleObjects + 4 at minkernel\ntdll\daytona\objfre\arm64\usrstubs.asm:1092
0000007F34AFE960 00007FF8A8519A90 KERNELBASE!WaitForMultipleObjectsEx + 224 at minkernel\kernelbase\synch.c:1551
0000007F34AFEC40 00007FF834E3C26C coreclr!Thread::DoAppropriateAptStateWait + 268 at D:\a\_work\1\s\src\coreclr\vm\threads.cpp:3120
0000007F34AFEC90 00007FF834E3C940 coreclr!Thread::DoAppropriateWaitWorker + 1256 at D:\a\_work\1\s\src\coreclr\vm\threads.cpp:3300
0000007F34AFEDE0 00007FF834E3C3F0 coreclr!Thread::DoAppropriateWait + 336 at D:\a\_work\1\s\src\coreclr\vm\threads.cpp:3017
0000007F34AFEE90 00007FF834E40104 coreclr!Thread::Join + 292 at D:\a\_work\1\s\src\coreclr\vm\threads.cpp:402
0000007F34AFEEE0 00007FF834F8B584 coreclr!Thread::UserAbort + 1668 at D:\a\_work\1\s\src\coreclr\vm\threadsuspend.cpp:1633
0000007F34AFF020 00007FF834E94778 coreclr!ThreadNative_Abort + 184 at D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp:878
0000007F34AFF0A0 00007FF833A47DFC System_Private_CoreLib!System.Runtime.ControlledExecution+Canceler.Cancel() + 124 at /_/src/coreclr/System.Private.CoreLib/src/System/Runtime/ControlledExecution.CoreCLR.cs:145
0000007F34AFF0D0                  [InlinedCallFrame: 0000007f34aff0d0] 
0000007F34AFF0D0                  [InlinedCallFrame: 0000007f34aff0d0] 
0000007F34AFF0A0 00007ff833a47de4 System.Private.CoreLib.dll!System.Runtime.ControlledExecution+Canceler.Cancel() + 100 [/_/src/coreclr/System.Private.CoreLib/src/System/Runtime/ControlledExecution.CoreCLR.cs @ 145]
0000007F34AFF180 00007ff8339be080 System.Private.CoreLib.dll!System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean) + 464 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs @ 814]
0000007F34AFF200 00007ff8339bddb4 System.Private.CoreLib.dll!System.Threading.CancellationTokenSource.NotifyCancellation(Boolean) + 68 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs @ 700]
0000007F34AFF220 00007ff7da073284 System.Runtime.Tests.dll!System.Runtime.Tests.ControlledExecutionTests.CancelWhenTestIsReady(System.Threading.CancellationTokenSource) + 164
0000007F34AFF250 00007ff7da0888e0 System.Runtime.Tests.dll!System.Runtime.Tests.ControlledExecutionTests+c__DisplayClass8_0.b__0() + 48
0000007F34AFF270 00007ff7d9840a24 System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) + 260 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 264]
0000007F34AFF2D0 00007ff7d97bbd30 System.Private.CoreLib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) + 216 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2347]
0000007F34AFF350 00007ff8339d7200 System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() + 1856 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @ 1154]
0000007F34AFF3D0 00007ff8339eea6c System.Private.CoreLib.dll!System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart() + 364 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @ 127]
0000007F34AFF4C0 00007ff7d97b6cf4 System.Private.CoreLib.dll!System.Threading.Thread.StartCallback() + 236
0000007F34AFF848                  [DebuggerU2MCatchHandlerFrame: 0000007f34aff848] 

That is actually interesting. The System.Runtime.Tests.ControlledExecutionTests+c__DisplayClass8_0.b__0() + 48 is the following inner Test method in the test:
That is the method that is supposed to be aborted. However, the Thread::UserAbort above is initiating abortion of a completely different thread! That's actually the problem here.

Ah no, there are two similar looking lambdas and I have mistakenly seen them as the same.
System.Runtime.Tests.ControlledExecutionTests+<>c__DisplayClass8_0.b__0() + 0x48
and
System.Runtime.Tests.ControlledExecutionTests+<>c__DisplayClass8_0.g__Test|1()+0x48

@AndyAyersMS
Copy link
Member Author

After some offline discussion with @janvorli the issue above seems hard to repro and may well be pre-existing. So I'm going to merge this and see if the problem pops up in subsequent testing.

@AndyAyersMS AndyAyersMS merged commit dc88476 into dotnet:main Mar 20, 2025
126 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Apr 20, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants