refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz
, '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

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz
, '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

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz
, '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

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz
, '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

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz
, '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

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz
, '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

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz
, '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

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well - #51212

Merged
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm
Apr 26, 2021
Merged

refactor old APM [Begin/End]Accept methods on top of Task APIs, and enable for Unix as well#51212
geoffkizer merged 7 commits into
dotnet:mainfrom
geoffkizer:acceptapm

Conversation

@geoffkizer

Copy link
Copy Markdown
Contributor

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

@ghost

Copy link
Copy Markdown

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

Issue Details

Note, the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation. It's also not currently supported on non-Windows platforms. So, replace this with a helper routine that performs an accept followed by a receive, and works across all platforms.

Contributes to #43845

Author:geoffkizer
Assignees:-
Labels:

area-System.Net.Sockets

Milestone:-

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

Comment threadsrc/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs Outdated
@stephentoub

Copy link
Copy Markdown
Member

the old Begin/EndAccept methods support doing an accept and receive in a single operation. Unfortunately the API for this is terrible and forces allocation

In theory there's a perf benefit possible from the existing accept+receive on Windows, but as we only expose it from an old APM-based API we discourage using, it incurs non-trivial allocation overheads, and it's complicated, non-portable logic, I'm fine seeing it go away.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

I also pushed a change to remove the CallbackClosure cache. This is now only used for BeginSendFile, and that will go away eventually as well.

Saves 8 bytes on the Socket object.

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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

return null; // unreachable
}
public IAsyncResult BeginAccept(AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(AcceptAsync(), callback, state);

@stephentoubstephentoubApr 18, 2021

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.

When was this added? I don't remember seeing it. Is this actually providing compatibility with what was there before? It's not just throwing exceptions that may have occurred on the initiation path, it's throwing all exceptions from the whole operation if it happens to complete quickly, by the time we get to the IsFaulted check. I don't have a strong opinion, but if our goal is compatibility, i think this likely makes it worse, potentially throwing new exceptions that previously weren't possible from Begin. I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this. If you want to keep it, though, ok.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This got added in #51213 (which I just merged last night) to address the compat concerns from @antonfirsov. I didn't think it would be controversial, so I merged it without additional review.

It provides compat with what was there before in the sense that sync exceptions now propagate synchronously. It also, as you point out, will throw synchronously if the task completes asynchronously but before we hit the IsFaulted check. AFAIK there is no way to avoid this (right?).

I suggest we just accept the small difference in behavior we've accepted everywhere else we've used TaskToApm, rather than trying to special-case this.

I'm fine with this. I'll just rip this code out. @antonfirsov any concerns here? Based on this I think we should close #47905 without action as well.

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.

@stephentoub le me know, if my definition of breaking changes, and/or my expectations around maintaining docs are too strict, or if I'm misunderstanding the rules in other means, but this is how I view it:

AFAIK we do not document async exceptions the same way we do with we sync exceptions since they are not being actually thrown by calling the method, but rather by awaiting it.

This means that if we close #47905 as wontfix, we need to add a "breaking change" label to all these PR-s, and make sure we propagate and address all the related documentation work in 6.0. If the goal is to save time, I'm don't think this path will result in less work. I would just add a tiny layer of compat code instead.

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.

Whether the behavior was documented or not, it can still be a breaking change. And my point is that in this case, it's a breaking change whether or not this TaskToApmBeginWithSyncExceptions is used, and from my perspective, it's a larger break with TaskToApmBeginWithSyncExceptions (it's non-deterministically causing more exceptions to be thrown out of the BeginXx method). I'm not convinced either way it rises to the level that requires being documented as a breaking change, but if you feel it needs to be, feel free. To my knowledge we haven't in the past when we've ripped out APM implementations and replaced them with TaskToApm wrappers around XxAsync methods.

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.

I'm not convinced either way it rises to the level that requires being documented as a breaking change

Do we have any formal guidelines about the bar for breaking changes?

My main concern is around the API docs. What is our process then for making sure that the exceptions which are no longer thrown synchronously will be removed from the list? If there is none, we will create inconsistency in our docs which doesn't look good, even if these particular API-s are marginal.

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.

We can still accomplish it with a trick like the one below

Can you share a full example? I think it's much more complicated than that, and would deoptimize the XxAsync methods we're trying to reuse.

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.

Opened #51693 to show a more complete example.

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.

Thanks. I left a few comments. That only partially addresses the issue, and adds non-trivial complication. I don't believe it's worth it. If you believe this rises to the level of requiring a breaking change notification, please feel free to do so. But FYI we've made such changes throughout .NET Core incrementally over the last several years, ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

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.

Thanks! I see now the more complicated cases, and agree with your analysis, that it's not worth it.

ripping out lots of custom IAsyncResult code across lots of libraries, and I don't believe we've documented any of them as being breaking.

When it comes to the process that is triggered by applying the "breaking change" labels today, I don't think it makes sense to deviate from an established practice in this particular case of sockets. I opened #6658 to track the API docs work.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I opened #6658 to track the API docs work.

This looks like it's linked to the wrong repo, perhaps?

@geoffkizer

Copy link
Copy Markdown
ContributorAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

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


public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback? callback, object? state) =>
TaskToApmBeginWithSyncExceptions(DisconnectAsync(reuseSocket).AsTask(), callback, state);
TaskToApm.Begin(DisconnectAsync(reuseSocket).AsTask(), callback, state);

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.

I don't understand how does this work without TaskToApmBeginWithSyncExceptions now.

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.

Ah ok, missed the discussion above.

server.BindToAnonymousPort(IPAddress.Loopback);

Assert.Throws<InvalidOperationException>(() => { AcceptAsync(listener, server); });
await Assert.ThrowsAsync<InvalidOperationException>(() => AcceptAsync(listener, server));

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.

In case of APM, shouldn't this throw without awaiting?

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.

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.

4 participants

@geoffkizer@stephentoub@antonfirsov@karelz