Synchronize the background scan and closing of connections
- It was possible for the application to be torn down during a background scan. When that happened, the timer would be disposed before the end of the scan and would throw an ObjectDisposedException when timer resumption happened. This change introduces a lock that avoids that race. Fixes #161
This commit is contained in:
parent
162cd1fc06
commit
05cc7792df
|
|
@ -17,6 +17,8 @@ namespace Microsoft.AspNetCore.Sockets
|
||||||
private readonly ConcurrentDictionary<string, ConnectionState> _connections = new ConcurrentDictionary<string, ConnectionState>();
|
private readonly ConcurrentDictionary<string, ConnectionState> _connections = new ConcurrentDictionary<string, ConnectionState>();
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
private readonly ILogger<ConnectionManager> _logger;
|
private readonly ILogger<ConnectionManager> _logger;
|
||||||
|
private object _executionLock = new object();
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
public ConnectionManager(ILogger<ConnectionManager> logger)
|
public ConnectionManager(ILogger<ConnectionManager> logger)
|
||||||
{
|
{
|
||||||
|
|
@ -25,9 +27,17 @@ namespace Microsoft.AspNetCore.Sockets
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if (_timer == null)
|
lock (_executionLock)
|
||||||
{
|
{
|
||||||
_timer = new Timer(Scan, this, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_timer == null)
|
||||||
|
{
|
||||||
|
_timer = new Timer(Scan, this, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,58 +87,76 @@ namespace Microsoft.AspNetCore.Sockets
|
||||||
|
|
||||||
private void Scan()
|
private void Scan()
|
||||||
{
|
{
|
||||||
// Pause the timer while we're running
|
lock (_executionLock)
|
||||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// Scan the registered connections looking for ones that have timed out
|
if (_disposed)
|
||||||
foreach (var c in _connections)
|
|
||||||
{
|
{
|
||||||
var status = ConnectionState.ConnectionStatus.Inactive;
|
return;
|
||||||
var lastSeenUtc = DateTimeOffset.UtcNow;
|
}
|
||||||
|
|
||||||
try
|
// Pause the timer while we're running
|
||||||
|
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Scan the registered connections looking for ones that have timed out
|
||||||
|
foreach (var c in _connections)
|
||||||
{
|
{
|
||||||
c.Value.Lock.Wait();
|
var status = ConnectionState.ConnectionStatus.Inactive;
|
||||||
|
var lastSeenUtc = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
// Capture the connection state
|
try
|
||||||
status = c.Value.Status;
|
{
|
||||||
|
c.Value.Lock.Wait();
|
||||||
|
|
||||||
lastSeenUtc = c.Value.LastSeenUtc;
|
// Capture the connection state
|
||||||
}
|
status = c.Value.Status;
|
||||||
finally
|
|
||||||
{
|
|
||||||
c.Value.Lock.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once the decision has been made to to dispose we don't check the status again
|
lastSeenUtc = c.Value.LastSeenUtc;
|
||||||
if (status == ConnectionState.ConnectionStatus.Inactive && (DateTimeOffset.UtcNow - lastSeenUtc).TotalSeconds > 5)
|
}
|
||||||
{
|
finally
|
||||||
var ignore = DisposeAndRemoveAsync(c.Value);
|
{
|
||||||
|
c.Value.Lock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the decision has been made to to dispose we don't check the status again
|
||||||
|
if (status == ConnectionState.ConnectionStatus.Inactive && (DateTimeOffset.UtcNow - lastSeenUtc).TotalSeconds > 5)
|
||||||
|
{
|
||||||
|
var ignore = DisposeAndRemoveAsync(c.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
finally
|
||||||
finally
|
{
|
||||||
{
|
// Resume once we finished processing all connections
|
||||||
// Resume once we finished processing all connections
|
_timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
||||||
_timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseConnections()
|
public void CloseConnections()
|
||||||
{
|
{
|
||||||
// Stop firing the timer
|
lock (_executionLock)
|
||||||
_timer?.Dispose();
|
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
|
|
||||||
foreach (var c in _connections)
|
|
||||||
{
|
{
|
||||||
tasks.Add(DisposeAndRemoveAsync(c.Value));
|
if (_disposed)
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(5));
|
_disposed = true;
|
||||||
|
|
||||||
|
// Stop firing the timer
|
||||||
|
_timer?.Dispose();
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
|
foreach (var c in _connections)
|
||||||
|
{
|
||||||
|
tasks.Add(DisposeAndRemoveAsync(c.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DisposeAndRemoveAsync(ConnectionState state)
|
public async Task DisposeAndRemoveAsync(ConnectionState state)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue