JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11
, '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

JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11
, '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

JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11
, '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

JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11
, '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

JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11
, '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

JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11
, '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

JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11
, '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

JIT: Clear out return value references from continuations - #129157

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values
Jun 10, 2026
Merged

JIT: Clear out return value references from continuations#129157
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:runtime-async-clear-return-values

Conversation

@jakobbotsch

Copy link
Copy Markdown
Member

Async1 deterministically clears out awaiters on resumption, meaning that the callee's Task and its result do not stay alive. This change similarly makes it so that we do not keep results alive in async2.

Async1 has similar clearing for locals based on lexical scope. I am hoping we can get away with not implementing something similar for runtime async (it would be expensive and impossible to guarantee similar behavior as async1).

Fix#126735

Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
CopilotAI review requested due to automatic review settings June 9, 2026 08:42
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 9, 2026

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 updates the CoreCLR JIT’s runtime-async transformation to proactively clear GC references from the continuation’s stored return value after resumption copies the result out, reducing unintended object rooting when continuations are reused.

Changes:

  • Adds AsyncTransformation::ClearReturnValueOnResumption and invokes it after copying the awaited call’s return value when continuation reuse is enabled.
  • Implements two clearing strategies for struct returns with GC pointers: per-GC-slot clearing for small/ref-sparse structs, otherwise a bulk zeroing store.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/jit/async.hDeclares the new helper used to clear return-value GC references on resumption.
src/coreclr/jit/async.cppCalls the helper after copying results and implements logic to clear GC refs in ref/struct return slots when continuations are reused.

Comment threadsrc/coreclr/jit/async.cpp
Comment threadsrc/coreclr/jit/async.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @EgorBo

Diffs

@jakobbotsch
jakobbotsch requested a review from EgorBoJune 9, 2026 15:11
Comment threadsrc/coreclr/jit/async.cpp Outdated

@VSadovVSadov 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. Thanks!

CopilotAI review requested due to automatic review settings June 9, 2026 19:03

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

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

Comment threadsrc/coreclr/jit/async.cpp Outdated
Comment threadsrc/coreclr/jit/async.cpp
@jakobbotsch

jakobbotsch commented Jun 10, 2026

Copy link
Copy Markdown
MemberAuthor

Validated locally that this fixes #126735 after the following part of the test is extracted into a NoInlining method:
https://github.com/dotnet/aspnetcore/blob/df9f19afc8db182a48528d3e06d67344ac6670e6/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs#L58-L65

For reference, this is the diff applied to the test:

diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs
index da38bd2c5b..1638f258f3 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpConnectionManagerTests.cs@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -56,14 +57,10 @@ public class HttpConnectionManagerTests : LoggedTest
},
testContext))
{
- using (var connection = server.CreateConnection())- {- await connection.SendEmptyGet();-- Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));-- // Close connection without waiting for a response- }+ // Create the connection in a separate non-inlined method so that the connection+ // and any locals it references aren't kept rooted on this method's stack frame,+ // which would prevent HttpConnection from being garbage collected.+ await CreateConnectionAndSendRequest(server, appStartedWh);
var logWaitAttempts = 0;
@@ -77,6 +74,19 @@ public class HttpConnectionManagerTests : LoggedTest
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]+ private static async Task CreateConnectionAndSendRequest(TestServer server, SemaphoreSlim appStartedWh)+ {+ using (var connection = server.CreateConnection())+ {+ await connection.SendEmptyGet();++ Assert.True(await appStartedWh.WaitAsync(TestConstants.DefaultTimeout));++ // Close connection without waiting for a response+ }+ }+
private class CallbackLoggerProvider : ILoggerProvider
{
private readonly Action<EventId> _logAction;

@jakobbotsch
jakobbotsch merged commit a3bf498 into dotnet:mainJun 10, 2026
137 of 139 checks passed
@jakobbotsch
jakobbotsch deleted the runtime-async-clear-return-values branch June 10, 2026 18:29
BoyBaykiller pushed a commit to BoyBaykiller/runtime that referenced this pull request Jun 11, 2026
)
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
wtgodbe added a commit to dotnet/aspnetcore that referenced this pull request Jun 29, 2026
See dotnet/runtime#129157 for some context.
Co-authored-by: William Godbe <wigodbe@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Async1 deterministically clears out awaiters on resumption, meaning that
the callee's `Task` and its result do not stay alive. This change
similarly makes it so that we do not keep results alive in async2.
Async1 has similar clearing for locals based on lexical scope. I am
hoping we can get away with not implementing something similar for
runtime async (it would be expensive and impossible to guarantee similar
behavior as async1).
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible object rooting issue with runtime-async

5 participants

@jakobbotsch@EgorBo@VSadov@am11