UnobservedTaskException occurs when an idle connection pool connection fails to shutdown properly
- Dominant language
- C#
- Stars
- 1.8k
- Forks
- 576
- Avg merge
- 6d 9h
- Merged PRs (30d)
- 2
Description
**Describe the bug**
System.ServiceModel.Channels.CommunicationPool.EndpointConnectionPool.CloseIdleConnection calls CloseItemAsync async method which in turn calls System.ServiceModel.Channels.SocketConnection.CloseAsync async method, without awaiting it. If an exception occurs, it's unhandled and causes UnobservedTaskException.
**To Reproduce**
Steps to reproduce the behavior:
Create a simple WCF interface:
```
[ServiceContract]
public interface IServer
{
[OperationContract]
void Call();
}
```
Create a simple WCF server implementing that interface (singleton in this case, run for instance using ServiceHost):
```
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Server : IServer
{
public void Call()
{ }
}
```
Create a simple WCF client based on ClientBase:
```
public class Client : ClientBase, IServer
{
public Client(NetTcpBinding binding, EndpointAddress remoteAddress)
: base(binding, remoteAddress)
{
}
public void Call()
{
base.Channel.Call();
}
}
```
When creating server NetTcpBinding, use short timeouts (Open/Close/Send), also to expedite the exception;
On the Server side, run a timer that creates and destroys a server every 10 sec:
```
void onServerTimer(object state)
{
var _server = new Server();
var _host = new ServiceHost(_server);
NetTcpBinding binding = new NetTcpBinding();
binding.SendTimeout = TimeSpan.FromSeconds(3);
binding.OpenTimeout = TimeSpan.FromSeconds(3);
binding.CloseTimeout = TimeSpan.FromSeconds(3);
_host.AddServiceEndpoint(typeof(IServer), binding, "net.tcp://localhost:5555/SVC");
_host.Open();
Task.Delay(5000).Wait();
_host.Close();
}
```
When creating client NetTcpBinding, use binding.MaxConnections=1 to expedite the occurrence of the exception.
On the Client side , run a timer that creates a client and call the server every 500 ms:
```
void onClientTimer(object state)
{
NetTcpBinding binding = new NetTcpBinding();
binding.SendTimeout = TimeSpan.FromSeconds(3);
binding.OpenTimeout = TimeSpan.FromSeconds(3);
binding.CloseTimeout = TimeSpan.FromSeconds(3);
binding.MaxConnections = 1;
EndpointAddress address = new EndpointAddress(new Uri("net.tcp://localhost:5555/SVC"));
try
{
using (var client = new Client(binding, address))
{
client.Call();
}
}
catch (Exception e)
{
}
}
```
**Expected behavior**
When an exception occurs on the client side, it should be caught by the try/catch, same as in System.ServiceModel 4.10.3.
As of System.ServiceModel 6.0.0 there was a change in behavior:
```
public void CloseIdleConnection(TItem connection, TimeSpan timeout)
{
bool throwing = true;
try
{
CloseItemAsync(connection, timeout);
throwing = false;
}
catch (Exception e)
{
if (Fx.IsFatal(e))
{
throw;
}
}
finally
{
if (throwing)
{
AbortItem(connection);
}
}
}
```
calling =>
```
protected override ValueTask CloseItemAsync(IConnection item, TimeSpan timeout)
{
return item.CloseAsync(timeout);
}
```
calling =>
```
public async ValueTask CloseAsync(TimeSpan timeout)
{ ... }
```
Because the call to **CloseItemAsync(connection, timeout)** is not awaited, exceptions occurring during shutdown of idle connections are not caught properly.
Contributor guide
Assessment
This issue has not been assessed yet.