runtime: Reusing SmtpClient hangs in net50
Calling SendMailAsync multiple times on the same SmtpClient instance fails under some circumstances in net50.
Description
The following test succeeds when running under netcoreapp3.1, but fails by timing out in net50
[Fact(Timeout = 10000)]
public async Task Net50Repo()
{
var config = Configuration.Configure();
using var server = SimpleSmtpServer.Start(config.WithRandomPort().Port);
var inst = new SmtpClient
{
Host = "localhost",
Port = server.Configuration.Port,
EnableSsl = false
};
for (int messageNo = 0; messageNo < 2; messageNo++)
{
var mailMessage = new MailMessage("one@example.com", "two@example.com");
await inst.SendMailAsync(mailMessage);
Assert.Equal(messageNo + 1, server.ReceivedEmailCount);
}
}
The issue seems to be reusing the SmtpClient, the first SendMailAsync succeeds, but the second call always just hangs indefinitely.
Works in 3.1. Newing up a new client for each call also works. Testing against a different Smtp server (eg. Papercut) also works.
netDumpster just ends up waiting inside SmtpContext on this line:
count = this.socket.Receive(byteBuffer);
It’s like if the socket remains open it cannot be reused anymore for some reason. Any idea what changed?
Configuration
We are able to reproduce with netdumpster in unit tests, production is not yet running in net50 for us (partly because of this), so I’m unsure if other smtp server are effected, we are nervous about this.
Regression?
Yes, regression from 3.1 to 5
Other information
I originally raised the issue on netdumpster, there is some more discussion there, although not much.
I am aware that other issues mention SmtpClient is not under active development, and it seems MS recommends migrating to MailKit, but it would still be nice if existing functionality would not break.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 1
- Comments: 15 (14 by maintainers)
I’ve done a small investigation, and the problem was introduced with #683. Or, to be precise, it was uncovered by it. The main problem is the fact that SmptClient attempts to open another connection every time
SendAsyncis called, even if the connection is open.https://github.com/dotnet/runtime/blob/265988529080aa6e88f1d2ea12d9c080c93465e4/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs#L651-L653
https://github.com/dotnet/runtime/blob/265988529080aa6e88f1d2ea12d9c080c93465e4/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs#L119-L132
And compare this to
Send.https://github.com/dotnet/runtime/blob/265988529080aa6e88f1d2ea12d9c080c93465e4/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs#L499-L507
https://github.com/dotnet/runtime/blob/265988529080aa6e88f1d2ea12d9c080c93465e4/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs#L982-L988
Unlike sync implementation, async doesn’t check whether
_transport.IsConnectedistrueor not.FYI because possibly connected:
I’m reusing the same
SmtpClientinstance to send multiple e-mails via synchronousSend(MailMessage)method.EDIT: Changing the implementation to use a new instance for every e-mail fixed the timeouts. See comment below.
I/we are on windows testing against netdumpster. Easiest to repro with this: https://github.com/cmendible/netDumbster/tree/dotnet5 The test called “Reusing_Smtp_Client_Should_Not_Fail” succeeds in 3.1 and fails in 5.0
Windows. I can probably get some captures if needed, but you should be able to repro with the branch above.
Just to be clear again, we originally thought this was a netdumpster (a fake smtp server for tests) issue, however it functions perfectly in older frameworks, and against other smtp clients. Upgrading to dotnet 50 breaks when using SmtpClient.
Like mentioned before, there was a bit of discussion earlier here