We have several .NET App Services running on Linux App Service plans. They are all connected via Hybrid Connect to different on-prem Windows Servers. I started noticing that after an application has been started up and I send several requests that open a TCP connection to the on-prem servers, they do not close fully and are indefinitely stuck in the CLOSE_WAIT state when using netstat in the App Service SSH console.
Originally I thought it was an issue with SqlClient, but I put together a bare-bones TCP socket server program, and a web application that connects to it, receives data from the server, and closes the connection. And sure enough, pure TCP socket communication is having the same issue.
On the index page of the web application:
var ipHostInfo = await Dns.GetHostEntryAsync("******");
var ipEndpoint = new IPEndPoint(ipHostInfo.AddressList[0], 5000);
var sb = new StringBuilder();
// establish connection
using var client = new Socket(ipEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
await client.ConnectAsync(ipEndpoint);
// send message
var message = "Hi friends! <|EOM|>";
var messageBytes = Encoding.UTF8.GetBytes(message);
await client.SendAsync(messageBytes, SocketFlags.None);
_logger.LogInformation("Sent message: {message}", message);
// response is broken into multiple messages. use sb to build response into a single string
while (true)
{
var buffer = new byte[1024];
var receivedLength = await client.ReceiveAsync(buffer, SocketFlags.None);
var response = Encoding.UTF8.GetString(buffer, 0, receivedLength);
sb.Append(response);
if (response.Contains("<|ACK|>"))
{
_logger.LogInformation("Received acknowledgement: {response}", response);
break;
}
else
{
_logger.LogInformation("Received response: {response}", response);
}
}
Server code:
var ipEndpoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 5000);
using var listener = new Socket(ipEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(ipEndpoint);
listener.Listen(100);
while (true)
{
Console.WriteLine($"Listening on {ipEndpoint.Address}:{ipEndpoint.Port}");
using var handler = await listener.AcceptAsync();
Console.WriteLine($"{handler.RemoteEndPoint} connected");
while (true)
{
// Receive message.
var buffer = new byte[1_024];
var received = await handler.ReceiveAsync(buffer, SocketFlags.None);
var response = Encoding.UTF8.GetString(buffer, 0, received);
var eom = "<|EOM|>";
if (response.IndexOf(eom) > -1 /* is end of message */)
{
Console.WriteLine(
$"Socket server received message: \"{response.Replace(eom, "")}\"");
var ackMessage = "<|ACK|>";
var echoBytes = Encoding.UTF8.GetBytes(ackMessage);
await handler.SendAsync(Encoding.UTF8.GetBytes(DateTime.UtcNow.ToString("s")));
await handler.SendAsync(echoBytes, 0);
Console.WriteLine(
$"Socket server sent acknowledgment: \"{ackMessage}\"");
break;
}
}
handler.Close();
}
Approximately 5 minutes after sending a few requests to the web application after freshly restarting it, this is the result of ss -tnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
CLOSE-WAIT 0 0 127.0.0.14:5000 127.0.0.1:33672
CLOSE-WAIT 0 0 127.0.0.14:5000 127.0.0.1:33674
FIN-WAIT-2 0 0 127.0.0.1:57714 127.0.0.14:5000
CLOSE-WAIT 0 0 127.0.0.14:5000 127.0.0.1:57714
CLOSE-WAIT 0 0 127.0.0.14:5000 127.0.0.1:59332
ESTAB 0 0 169.254.136.2:2222 169.254.136.3:58672 users:(("sshd",pid=1935,fd=3))
CLOSE-WAIT 0 0 127.0.0.14:5000 127.0.0.1:40104
CLOSE-WAIT 0 0 127.0.0.14:5000 127.0.0.1:53386
ESTAB 0 0 [::ffff:169.254.136.2]:58030 [::ffff:13.73.253.115]:443 users:(("dotnet",pid=1874,fd=292))
There are no exceptions thrown in the application, and I cannot find anything in the logs or diagnostic tools provided by Azure.
In fact, if I replace the last handler.close() statement in the server code with var len = await handler.ReceiveAsync(new byte[1024], SocketFlags.None);, in a healthy system, the task resolves with 0 bytes read into the buffer when the client code closes the connection from its side. But through the hybrid connection, the task never resolves when the connection is closed.
Here is the full reproducible demo: https://github.com/DrOverbuild/HybridConnectionLeak/tree/pure-tcp-sockets
I found a couple other users in this forum experiencing the same issue: