Skip to content

Replace ATP pattern with async/await in SmtpClient (part 1) - #115366

Merged
rzikm merged 5 commits into
dotnet:mainfrom
rzikm:smtpclient-atp2async-part1
May 16, 2025
Merged

Replace ATP pattern with async/await in SmtpClient (part 1)#115366
rzikm merged 5 commits into
dotnet:mainfrom
rzikm:smtpclient-atp2async-part1

Conversation

@rzikm

@rzikm rzikm commented May 7, 2025

Copy link
Copy Markdown
Member

This PR replaces some of the ATP pattern (begin + end methods) with async/await equivalent. It also utilizes the IReadWriteAdapter trick that is used in SslStream to deduplicate sync and async code paths.

This PR focuses on the easy picks where the pattern can be clearly replaced, Following PR will focus on refactoring SmtpClient, SmtpConnection and SmtpTransport classes themselves, where the ATP pattern usage is more complicated.

Lastly, this PR improves test infrastructure by listening on dual sockets (if supported), this greatly speeds up the test suite on Windows (4s instead of 3 minutes, due to 2s timeout per test case because the test code attempted IPv6 first)

Copilot AI review requested due to automatic review settings May 7, 2025 13:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR modernizes the asynchronous programming model in SmtpClient and its related classes by replacing the legacy ATP (Begin/End) pattern with async/await. It also refactors test infrastructure to improve runtime performance and updates various Mime- and SMTP‑related components for a cleaner, task-based async workflow.

  • Replaces legacy async patterns with generic async methods using IReadWriteAdapter.
  • Refactors stream flushing and writing for simpler, synchronous and asynchronous paths.
  • Improves test reliability by incorporating dual socket handling and timeout enforcement.

Reviewed Changes

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

Show a summary per file
File Description
MailMessageTest.cs Updates the send routine to use SendAsync with a dynamically determined adapter type.
LoopbackSmtpServer.cs Refactors socket binding to support dual-stack (IPv6/IPv4) based on socket address family.
LoopbackServerTestBase.cs Applies WaitAsync for improved test-case timeouts.
LoggingTest.cs Adjusts test invocation to explicitly pass a non-null argument.
MimeWriter.cs, MimePart.cs, MimeMultiPart.cs, MimeBasePart.cs Eliminates ATP methods and replaces them with async/await and generic adapter calls.
BaseWriter.cs Simplifies flushing logic by removing multi-result support in favor of direct stream writes.
SmtpTransport.cs, SmtpReplyReaderFactory.cs, SmtpReplyReader.cs, SmtpConnection.cs Migrates legacy async IAsyncResult-based methods to task-based async methods.
SmtpClient.cs, MailMessage.cs Updates send methods to leverage SendAsync and adapt Begin/End flows into task-based calls.
ReadWriteAdapter.cs Introduces IReadWriteAdapter with implementations for both async and sync operations.
MailPriority.cs, MailWriter.cs, BufferBuilder.cs Applies minor adjustments in error handling and stream management in line with the async model.
Files not reviewed (1)
  • src/libraries/System.Net.Mail/src/System.Net.Mail.csproj: Language not supported
Comments suppressed due to low confidence (2)

src/libraries/System.Net.Mail/tests/Functional/MailMessageTest.cs:264

  • Consider adding a null check for 'syncSendAdapterType' before passing it to MakeGenericMethod to prevent a potential NullReferenceException if the type is not found.
var syncSendAdapterType = typeof(MailMessage).Assembly.GetTypes().FirstOrDefault(t => t.Name == "SyncReadWriteAdapter");

src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs:144

  • [nitpick] Remove the redundant semicolon after the empty catch block to improve code clarity.
catch (ArgumentException) { };

Comment thread src/libraries/System.Net.Mail/src/System/Net/Mail/MailMessage.cs
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@wfurt wfurt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. I know this is large change but it is step right direction.

@rokonec rokonec left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have noticed few internal methods without any usage. Please consider to clean it, if you do not have plan for future use of it.
Excellent job.

Comment thread src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs Outdated
Comment thread src/libraries/System.Net.Mail/src/System/Net/Mail/MailPriority.cs Outdated
Comment thread src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs Outdated
Comment thread src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs Outdated
@rzikm
rzikm enabled auto-merge (squash) May 16, 2025 07:09
@rzikm
rzikm merged commit dcf79a4 into dotnet:main May 16, 2025
public static Task WaitAsync(TaskCompletionSource<bool> waiter)
{
waiter.Task.GetAwaiter().GetResult();
return Task.CompletedTask;

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.

return waiter.Task; ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The idea of the SyncReadWriteAdapter is that it always returns a completed task, so that calling code can be fully synchronous (and can assert that task.IsCompleted). This file was copied from SslStream implementation, where it is used to deduplicate sync/async logic (in fact, it was the inspiration for this refactoring).

Followup PR removes this copy of the ReadWriteAdapters and shares the same file.

@karelz karelz added this to the 10.0.0 milestone May 20, 2025
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 20, 2025
Sign up for free to 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.

6 participants