dotnet / dotnet/wcf

UnobservedTaskException occurs when an idle connection pool connection fails to shutdown properly

Open
#5,331 1 comment 0 reactions 1 assignee Claimed by @imcarolwang View on GitHub
triaged
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.