Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Release debugger thread tracker on the win32 event thread - #119776

Open
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup
Open

Release debugger thread tracker on the win32 event thread#119776
tommcdon wants to merge 2 commits into
dotnet:mainfrom
tommcdon:dev/tommcdon/fix_debug_tracker_cleanup

Conversation

@tommcdon

Copy link
Copy Markdown
Member

The m_unmanagedThreadHashTable in CordbProcess is used to track unmanaged threads for out-of-process setthreadcontext, suspending/resuming threads for single-stepping over call instructions. The table is accessed view the win32 event thread. When neutering CordbProcess, it's still possible to be receiving win32 events in parallel, however, we currently iterate and release objects in m_unmanagedThreadHashTable on a separate thread. This could cause issues with the objects, or even a crash. This change moves the deallocation of the objects in m_unmanagedThreadHashTable to the win32 event thread, eliminating thread unsafeness.

@tommcdontommcdon self-assigned this Sep 16, 2025
@tommcdontommcdon added this to the 11.0.0 milestone Sep 16, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses a thread safety issue in the CoreCLR debugger by moving the cleanup of unmanaged thread trackers from the CordbProcess::Neuter() method to the win32 event thread during process exit. The change prevents potential crashes that could occur when neutering the process while win32 events are still being processed in parallel.

Key changes:

  • Moved thread tracker cleanup logic from Neuter() to win32 event thread execution
  • Added a new ClearThreadTrackers() method to encapsulate the cleanup logic
  • Ensured thread-safe access to the unmanaged thread hash table

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
src/coreclr/debug/di/rspriv.hAdded declaration for new ClearThreadTrackers() method
src/coreclr/debug/di/process.cppRemoved thread tracker cleanup from Neuter(), added new method implementation, and called it from win32 event thread

m_pProcess->m_exiting = true;

#ifdef OUT_OF_PROCESS_SETTHREADCONTEXT
m_pProcess->ClearThreadTrackers(); // we must release the thread trackers on the Win32EventThread

@hoyosjshoyosjsSep 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CordbProcess::Neuter ensured that threads were resumed and let go from the tracker. Now, on detach we won't clean them out. Could this leave threads in suspended state on detach?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For process exit, the threads are gone anyway. I have a separate PR solves the problem for detach. #118849

@xtqqczze

Copy link
Copy Markdown
Contributor

Build failures were resolved by 579bd79.

@thaystgthaystg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_reviewed_commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "3fc2c4afb24f6b005cf91bfdc3d1ed06e956563e",
"last_recorded_worker_run_id": "29673722851",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "a3f00614b9eb05422e61f2ebc8eb18cc736d8e80",
"review_id": 4730106436
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: m_unmanagedThreadHashTable on CordbProcess tracks unmanaged threads used for out-of-process SetThreadContext single-stepping. It is normally accessed on the Win32 event thread. Previously CordbProcess::Neuter() iterated the table and deleted the UnmanagedThreadTracker objects, but Neuter() can run on a different thread while the Win32 event thread is still processing native debug events, creating a data race that can corrupt these objects or crash the debugger.

Approach: The deletion logic is extracted from Neuter() into a new CordbProcess::ClearThreadTrackers() method, which is now invoked from CordbWin32EventThread::ExitProcess() under the process lock, guaranteeing it runs on the Win32 event thread. The moved code is otherwise identical (Close + delete each tracker, RemoveAll(), reset m_dwOutOfProcessStepping), and both the old and new sites are guarded by OUT_OF_PROCESS_SETTHREADCONTEXT. The header declares the new method inside the same conditional block.

Summary: The change is minimal, correct, and directly addresses the described race. The cleanup now happens on the owning thread and under m_pProcess->Lock(), which is the right synchronization boundary. I found no correctness or resource-management regressions in the changed lines. One non-blocking observation: ExitProcess() has early-return paths (the DebugActiveProcessStop failure path and the EP(detach) after EP(exit) path) that occur before ClearThreadTrackers() is reached. The detach-failure early return leaves the process alive so no leak results there, and the second case is a duplicate call whose first invocation already cleared the table, so neither introduces a leak in practice. Verdict: LGTM.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 48.1 AIC · ⌖ 10.4 AIC · ⊞ 10K

@tommcdontommcdon modified the milestones: 11.0.0, 12.0.0Aug 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tommcdon@xtqqczze@thaystg@hoyosjs