Description
As described in documentation ICorProfilerInfo::ForceGC requires separate native thread for working. The thread will be finished by my profiler during detaching procedure. And I see it in lldb. However, after attaching again to the process the ICorProfilerInfo4::EnumThreads call returns me the already dead thread where ICorProfilerInfo::ForceGC was called sometime ago .
It's the important problem because the thread id can be already reused for a new native thread.
My investigation shows the CoreCLR keeps the static list of managed threads. and only alive threads will be returned in ICorProfilerInfo4::EnumThreads. On the other hand the ICorProfilerInfo::ForceGC adds current thread in this list.
Reproduction Steps
- Create following "hello world" application:
using System;
namespace test { class Program { static void Main(string[] args) {
Console.WriteLine("Hello World!");
Console.ReadLine();
}}}
- Publish it as a self contained application and run it in separate console
- Attach and detach a profiler which use
ICorProfilerInfo::ForceGC and dump somewhere the thread id after the call.
- Attach lldb and dump the thread list. Check that the thread id is not in the list.
- Attach a profiler which use
ICorProfilerInfo4::EnumThreads and see that the dead thread id is in the list of managed threads.
Actual behavior
The native thread id which interesting us is 1BCF69.
The part of the first profiler log;
00000000001BCF69 0 0000000000000000 GCCQ::ForceGcThread
00000000001BCF69 0 0000000000000000 GCCQ::ForceGc // <-- ICorProfilerInfo::ForceGC started
00000000001BCF69 1 000000014180AA00 MProf::GarbageCollectionStarted gcNumber=0 reason=induced c=5
00000000001BCF69 1 000000014180AA00 GCCQ::OnStarted gcNumber=0 gcState=normal
00000000001BCF69 1 000000014180AA00 MDG2::FlushMetadata
00000000001BCF69 1 000000014180AA00 MDG2::FlushMetadata res=ok
00000000001BCF69 1 000000014180AA00 MProf::GarbageCollectionStarted gcNumber=0 res=ok
00000000001BCF69 1 000000014180AA00 MProf::FinalizeableObjectQueued f=C oid=000000010122F650
00000000001BCF69 1 000000014180AA00 MProf::FinalizeableObjectQueued f=C oid=000000010122FA50
00000000001BCF69 1 000000014180AA00 MProf::FinalizeableObjectQueued f=C oid=000000010122FC40
00000000001BCF69 1 000000014180AA00 MProf::FinalizeableObjectQueued f=C oid=000000010122FCC0
00000000001BCF69 1 000000014180AA00 MProf::FinalizeableObjectQueued f=C oid=000000010122FD40
00000000001BCF69 1 000000014180AA00 MProf::FinalizeableObjectQueued f=C oid=000000010122FDC0
00000000001BCF69 1 000000014180AA00 MProf::FinalizeableObjectQueued f=C oid=000000010122FE48
00000000001BCF69 1 000000014180AA00 MProf::SurvivingReferences2 c=1
00000000001BCF69 1 000000014180AA00 MProf::SurvivingReferences2 c=91
00000000001BCF69 1 000000014180AA00 MProf::RootReferences2 c=42
00000000001BCF69 1 000000014180AA00 BProf::RootReferences c=42
00000000001BCF69 1 000000014180AA00 MProf::ObjectReferences oid=0000000101220048 cid=0000000280E48C80 c=0
00000000001BCF69 1 000000014180AA00 MProf::GarbageCollectionFinished gcNumber=0
00000000001BCF69 1 000000014180AA00 MProf::GarbageCollectionFinished gcNumber=0 res=ok
00000000001BCF69 1 000000014180AA00 GCCQ::OnFinished gcNumber=0 gcState=normal
00000000001BCF69 0 0000000000000000 GCCQ::ForceGc res=ok // <-- ICorProfilerInfo::ForceGC finished
00000000001BCF69 0 0000000000000000 GCCQ::ForceGcThread res=ok
lldb check:
(lldb) thread list
Process 52251 stopped
* thread #1: tid = 0x1bce01, 0x0000000194e1a538 libsystem_kernel.dylib`read + 8, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
thread #2: tid = 0x1bce03, 0x0000000194e19954 libsystem_kernel.dylib`mach_msg_trap + 8
thread #3: tid = 0x1bce04, 0x0000000194e1f8cc libsystem_kernel.dylib`kevent + 8
thread #4: tid = 0x1bce05, 0x0000000194e21ad0 libsystem_kernel.dylib`poll + 8
thread #5: tid = 0x1bce06, 0x0000000194e1a3d4 libsystem_kernel.dylib`__open + 8
thread #6: tid = 0x1bce07, 0x0000000194e1d0c0 libsystem_kernel.dylib`__psynch_cvwait + 8
thread #7: tid = 0x1bce08, 0x0000000194e1d0c0 libsystem_kernel.dylib`__psynch_cvwait + 8
thread #8: tid = 0x1bce0a, 0x0000000194e1a538 libsystem_kernel.dylib`read + 8
thread #9: tid = 0x1bcf5c, 0x0000000194e1d0c0 libsystem_kernel.dylib`__psynch_cvwait + 8
thread #10: tid = 0x1bcf5d, 0x0000000194e1b604 libsystem_kernel.dylib`__workq_kernreturn + 8
The part of the second profiler log:
00000000001BCE05 1 0000000000000000 BProf::ProfilerAttachComplete // <--- ICorProfilerCallback3::ProfilerAttachComplete
00000000001BCE05 1 0000000000000000 BProf::ProfilerAttachComplete tid=000000015200C000
00000000001BCE05 1 0000000000000000 BPPool::DoThreadRecovered tid=000000015200C000 osTid=001BCE01
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=000000015200C000 osTid=001BCE01 (1822209)
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=000000015200C000 id=00000001
00000000001BCE05 1 0000000000000000 BProf::ProfilerAttachComplete tid=000000015200C600
00000000001BCE05 1 0000000000000000 BPPool::DoThreadRecovered tid=000000015200C600 osTid=001BCE08
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=000000015200C600 osTid=001BCE08 (1822216)
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=000000015200C600 id=00000002
00000000001BCE05 1 0000000000000000 BProf::ProfilerAttachComplete tid=0000000131808200
00000000001BCE05 1 0000000000000000 BPPool::DoThreadRecovered tid=0000000131808200 osTid=001BCE0A
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=0000000131808200 osTid=001BCE0A (1822218)
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=0000000131808200 id=00000003
00000000001BCE05 1 0000000000000000 BProf::ProfilerAttachComplete tid=000000014180AA00 // <--- The dead thread reported here!!!
00000000001BCE05 1 0000000000000000 BPPool::DoThreadRecovered tid=000000014180AA00 osTid=001BCF69
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=000000014180AA00 osTid=001BCF69 (1822569)
00000000001BCE05 1 0000000000000000 BPool::DoThreadRecovered tid=000000014180AA00 id=00000004
00000000001BCE05 1 0000000000000000 ERROR: No such thread
Configuration
I reproduced the issue on macOS:
- Catalina x64: .NET v5.0.402 x64
- Big Sur x64: .NET v5.0.402 x64 / v6.0-rc2 x64
- Monterey arm64: .NET v5.0.402 x64 + Rosetta2 / v6.0-rc2 x64 + Rosetta2 / v6.0-rc2 arm64
The Linux x64/arm64 versions work properly.
Description
As described in documentation
ICorProfilerInfo::ForceGCrequires separate native thread for working. The thread will be finished by my profiler during detaching procedure. And I see it in lldb. However, after attaching again to the process theICorProfilerInfo4::EnumThreadscall returns me the already dead thread whereICorProfilerInfo::ForceGCwas called sometime ago .It's the important problem because the thread id can be already reused for a new native thread.
My investigation shows the CoreCLR keeps the static list of managed threads. and only alive threads will be returned in
ICorProfilerInfo4::EnumThreads. On the other hand theICorProfilerInfo::ForceGCadds current thread in this list.Reproduction Steps
ICorProfilerInfo::ForceGCand dump somewhere the thread id after the call.ICorProfilerInfo4::EnumThreadsand see that the dead thread id is in the list of managed threads.Actual behavior
The native thread id which interesting us is
1BCF69.The part of the first profiler log;
lldb check:
The part of the second profiler log:
Configuration
I reproduced the issue on macOS:
The Linux x64/arm64 versions work properly.