Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n
, '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

Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n
, '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

Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n
, '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

Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n
, '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

Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n
, '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

Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n
, '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

Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n
, '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

Avoid rare deadlocks when using TypeDescriptor - #103835

Merged
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading
Jun 26, 2024
Merged

Avoid rare deadlocks when using TypeDescriptor#103835
steveharter merged 1 commit into
dotnet:mainfrom
steveharter:TypeDescriptorThreading

Conversation

@steveharter

@stevehartersteveharter commented Jun 21, 2024

Copy link
Copy Markdown
Contributor

Fixes#103265

Running the full suite of ComponentModel.TypeConverter.Tests (7,852 tests) results in a deadlock in a median of 1 in about of 150 cases when running locally (sample size of 4).

The cause is having two lock objects that can be locked in different orders. The fix here is to combine the locks, instead of fixing the one known case that cause a lock to be out of order compared to the other cases. Changing to a single lock avoids any other potentially unknown cases and helps prevent new cases. Combining the locks increased perf ~5% of the unit tests likely due to the same thread now only needing one lock instead of two in many scenarios; in a real-world scenario with many threads there may be a minor decrease in throughput during warmup \ startup. These lock objects are only used to add or update cache due to cache misses.

For testing, there was not a reliable way to add a unit test to trigger the rare case. With the fixes here, the verification included running the full test suite 4,000 times without a deadlock vs. ~150 times without the fix before encountering the deadlock. This was done by running a .bat file in the test artifacts folder (e.g. artifacts\bin\System.ComponentModel.TypeConverter.Tests\Release\net9.0) of the following:

@echo off
FOR /L %%A IN (1,1,2000) DO (
echo run# %%A
call ..\..\..\..\..\artifacts\bin\testhost\net9.0-windows-Release-x64\dotnet exec --runtimeconfig System.ComponentModel.TypeConverter.Tests.runtimeconfig.json --depsfile System.ComponentModel.TypeConverter.Tests.deps.json xunit.console.dll System.ComponentModel.TypeConverter.Tests.dll -xml testResults.xml -nologo -nocolor -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing >result.txt
findstr /c:"Failed: 0" result.txt
if errorlevel 1 goto Fail
)
goto End
:Fail
echo FAILED
call type result.txt
:End
@echo on

The single known culprit is a call to TypeDescriptor.GetAttributes() when there is already a lock on s_internalSyncObject. This may cause a lock on TypeProvider.s_providerTable in a different lock ordering than other cases.

Sample call stacks:

// This locks one way:TypeDescriptor.GetProperties(newXElement("someElement1"));// * ReflectTypeDescriptionProvider.GetTypeData(System.Type, bool). // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.IsPopulated(System.Type)// - TypeDescriptor.Refresh(System.Type)// - TypeDescriptor.AddProvider(System.ComponentModel.TypeDescriptionProvider, System.Type)// - TypeDescriptor.AddDefaultProvider(System.Type)// * TypeDescriptor.CheckDefaultProvider(System.Type). // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.NodeFor(object, bool)// - TypeDescriptor.NodeFor(object)// - TypeDescriptor.GetDescriptor(object, bool)// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)// This locks another way:usingTestComponenttestComponent=newTestComponent();testComponent.Site=newTestSiteWithService();testComponent.Disposed+=(objectobj,EventArgsargs)=>{};TypeDescriptor.GetProperties(testComponent);// * TypeDescriptor.CheckDefaultProvider(System.Type) // s_providerTable lock// - TypeDescriptor.NodeFor(System.Type, bool)// - TypeDescriptor.NodeFor(System.Type)// - TypeDescriptor.GetDescriptor(System.Type, string)// - TypeDescriptor.GetAttributes(System.Type)// - ReflectTypeDescriptionProvider.ReflectedTypeData.GetAttributes()// - ReflectTypeDescriptionProvider.GetAttributes(System.Type)// - TypeDescriptor.DefaultTypeDescriptor.GetAttributes()// - TypeDescriptor.GetAttributes(System.Type)// * ReflectTypeDescriptionProvider.ReflectGetExtendedProperties(System.ComponentModel.IExtenderProvider) // s_internalSyncObject lock// - ReflectTypeDescriptionProvider.GetExtendedProperties(object)// - TypeDescriptor.TypeDescriptionNode.DefaultExtendedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties()// - TypeDescriptor.GetPropertiesImpl(object, System.Attribute[], bool, bool)// - TypeDescriptor.GetProperties(object, bool)// - TypeDescriptor.GetProperties(object)

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-componentmodel
See info in area-owners.md if you want to be subscribed.

@steveharter

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@buyaa-nbuyaa-n 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.

With the PR description the fix makes sense to me

@steveharter
steveharter merged commit c241cc9 into dotnet:mainJun 26, 2024
@steveharter
steveharter deleted the TypeDescriptorThreading branch June 27, 2024 15:08
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 28, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Long running System.ComponentModel tests

2 participants

@steveharter@buyaa-n