diff --git a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/ProcessBasedCommandRunner.cs b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/ProcessBasedCommandRunner.cs index 464975ed3..2cc5eadaa 100644 --- a/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/ProcessBasedCommandRunner.cs +++ b/Neo4j.Driver/Neo4j.Driver.IntegrationTests/Internals/ProcessBasedCommandRunner.cs @@ -24,7 +24,7 @@ namespace Neo4j.Driver.IntegrationTests.Internals { public class ProcessBasedCommandRunner : ShellCommandRunner { - private const int DefaultTimeOut = 2 * 60 * 1000; // 2 minutes + private const int DefaultTimeOut = 4 * 60 * 1000; // 4 minutes private List _stdOut; private StringBuilder _stdErr; diff --git a/Neo4j.Driver/Neo4j.Driver.Metrics/ConnectionPoolMetrics.cs b/Neo4j.Driver/Neo4j.Driver.Metrics/ConnectionPoolMetrics.cs index 0ee1ef31d..876172252 100644 --- a/Neo4j.Driver/Neo4j.Driver.Metrics/ConnectionPoolMetrics.cs +++ b/Neo4j.Driver/Neo4j.Driver.Metrics/ConnectionPoolMetrics.cs @@ -33,15 +33,15 @@ internal class ConnectionPoolMetrics : IConnectionPoolMetrics, IConnectionPoolLi private long _timedOutToAcquire; public int Creating => _creating; - public long Created => _created; - public long FailedToCreate => _failedToCreate; + public long Created => Interlocked.Read(ref _created); + public long FailedToCreate => Interlocked.Read(ref _failedToCreate); public int Closing => _closing; - public long Closed => _closed; + public long Closed => Interlocked.Read(ref _closed); public int Acquiring => _acquiring; - public long Acquired => _acquired; - public long TimedOutToAcquire => _timedOutToAcquire; + public long Acquired => Interlocked.Read(ref _acquired); + public long TimedOutToAcquire => Interlocked.Read(ref _timedOutToAcquire); public string UniqueName { get; } diff --git a/Neo4j.Driver/Neo4j.Driver.Metrics/DefaultMetrics.cs b/Neo4j.Driver/Neo4j.Driver.Metrics/DefaultMetrics.cs index 1f6bd4dfb..b1fc99485 100644 --- a/Neo4j.Driver/Neo4j.Driver.Metrics/DefaultMetrics.cs +++ b/Neo4j.Driver/Neo4j.Driver.Metrics/DefaultMetrics.cs @@ -41,8 +41,7 @@ public IConnectionPoolListener CreateConnectionPoolListener(Uri poolUri, IConnec var poolMetrics = new ConnectionPoolMetrics(poolUri, pool, acquisitionTimeout); var key = poolMetrics.UniqueName; - _poolMetrics.AddOrUpdate(key, poolMetrics, (oldKey, oldValue) => poolMetrics); - return poolMetrics; + return (IConnectionPoolListener) _poolMetrics.GetOrAdd(key, poolMetrics); } public IConnectionListener CreateConnectionListener(Uri poolUri) @@ -51,8 +50,7 @@ public IConnectionListener CreateConnectionListener(Uri poolUri) var connMetrics = new ConnectionMetrics(poolUri, connectionTimeout); var key = connMetrics.UniqueName; - _connMetrics.AddOrUpdate(key, connMetrics, (oldKey, oldValue) => connMetrics); - return connMetrics; + return (IConnectionListener) _connMetrics.GetOrAdd(key, connMetrics); } public IDictionary ConnectionPoolMetrics => new ReadOnlyDictionary(_poolMetrics); diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/TcpSocketClientTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/TcpSocketClientTests.cs index 4055df197..7ce1efea8 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Connector/TcpSocketClientTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Connector/TcpSocketClientTests.cs @@ -55,19 +55,18 @@ public class ConnectSocketAsyncMethod [Fact] public async Task ShouldThrowExceptionIfConnectionTimedOut() { - using (var tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) - { - tcpServer.Bind(new IPEndPoint(IPAddress.Loopback, 9999)); - - var client = new TcpSocketClientWithDisposeDetection( - new SocketSettings {ConnectionTimeout = TimeSpan.FromSeconds(0)}); + var client = new TcpSocketClientWithDisposeDetection( + new SocketSettings {ConnectionTimeout = TimeSpan.FromSeconds(1)}); - var exception = await Record.ExceptionAsync( - () => client.ConnectSocketAsync(IPAddress.Parse("127.0.0.1"), 9999)); - exception.Should().BeOfType(exception.ToString()); - exception.Message.Should().Be("Failed to connect to server 127.0.0.1:9999 within 0ms."); - client.DisposeCalled.Should().BeTrue(); - } + // ReSharper disable once PossibleNullReferenceException + // use non-routable IP address to mimic a connect timeout + // https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error + var exception = await Record.ExceptionAsync( + () => client.ConnectSocketAsync(IPAddress.Parse("192.168.0.0"), 9999)); + exception.Should().NotBeNull(); + exception.Should().BeOfType(exception.ToString()); + exception.Message.Should().Be("Failed to connect to server 192.168.0.0:9999 within 1000ms."); + client.DisposeCalled.Should().BeTrue(); } [Fact] @@ -77,6 +76,7 @@ public async Task ShouldBeAbleToConnectAgainIfFirstFailed() var client = new TcpSocketClient(socketSettings); // We fail to connect the first time as there is no server to connect to + // ReSharper disable once PossibleNullReferenceException var exception = await Record.ExceptionAsync( async()=> await client.ConnectSocketAsync(IPAddress.Parse("127.0.0.1"), 20003)); // start a server on port 20003 @@ -101,22 +101,21 @@ public class ConnectAsyncMethod [Fact] public async Task ShouldThrowExceptionIfConnectionTimedOut() { - using (var tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) - { - tcpServer.Bind(new IPEndPoint(IPAddress.Loopback, 9998)); + var client = new TcpSocketClient(new SocketSettings {ConnectionTimeout = TimeSpan.FromSeconds(1)}); - var client = new TcpSocketClient(new SocketSettings {ConnectionTimeout = TimeSpan.FromSeconds(0)}); - - var exception = await Record.ExceptionAsync( - () => client.ConnectAsync(new Uri("bolt://127.0.0.1:9998"))); - exception.Should().BeOfType(); - exception.Message.Should().Be( - "Failed to connect to server 'bolt://127.0.0.1:9998/' via IP addresses'[127.0.0.1]' at port '9998'."); - - var baseException = exception.GetBaseException(); - baseException.Should().BeOfType(exception.ToString()); - baseException.Message.Should().Be("Failed to connect to server 127.0.0.1:9998 within 0ms."); - } + // ReSharper disable once PossibleNullReferenceException + // use non-routable IP address to mimic a connect timeout + // https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error + var exception = await Record.ExceptionAsync( + () => client.ConnectAsync(new Uri("bolt://192.168.0.0:9998"))); + exception.Should().NotBeNull(); + exception.Should().BeOfType(); + exception.Message.Should().Be( + "Failed to connect to server 'bolt://192.168.0.0:9998/' via IP addresses'[192.168.0.0]' at port '9998'."); + + var baseException = exception.GetBaseException(); + baseException.Should().BeOfType(exception.ToString()); + baseException.Message.Should().Be("Failed to connect to server 192.168.0.0:9998 within 1000ms."); } } diff --git a/Neo4j.Driver/Neo4j.Driver.sln b/Neo4j.Driver/Neo4j.Driver.sln index 2ab8871ba..c36544d5e 100644 --- a/Neo4j.Driver/Neo4j.Driver.sln +++ b/Neo4j.Driver/Neo4j.Driver.sln @@ -11,7 +11,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{622A94AD-BC4D-4162-AE43-A5CA055F24FC}" ProjectSection(SolutionItems) = preProject ..\LICENSE = ..\LICENSE - Neo4j.Driver.nuspec = Neo4j.Driver.nuspec ..\NOTICE = ..\NOTICE ..\README.md = ..\README.md EndProjectSection diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs index 9743c06df..ac616286e 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Connector/TcpSocketClient.cs @@ -212,38 +212,31 @@ internal async Task ConnectSocketAsync(IPAddress address, int port) { InitClient(); - var tcs = new TaskCompletionSource(); - using (var cts = new CancellationTokenSource(_connectionTimeout)) - { - using (cts.Token.Register(() => tcs.SetResult(true))) - { #if NET452 - var connectTask = Task.Factory.FromAsync(_client.BeginConnect, _client.EndConnect, address, port, null); + var connectTask = Task.Factory.FromAsync(_client.BeginConnect, _client.EndConnect, address, port, null); #else - var connectTask = _client.ConnectAsync(address, port); + var connectTask = _client.ConnectAsync(address, port); #endif - var finishedTask = await Task.WhenAny(connectTask, tcs.Task).ConfigureAwait(false); - if (connectTask != finishedTask) // timed out - { - try - { - // close client immediately when failed to connect within timeout - await DisconnectAsync().ConfigureAwait(false); - } - catch (Exception e) - { - _logger?.Error($"Failed to close connect to the server {address}:{port}" + - $" after connection timed out {_connectionTimeout.TotalMilliseconds}ms" + - $" due to error: {e.Message}.", e); - } - - throw new OperationCanceledException( - $"Failed to connect to server {address}:{port} within {_connectionTimeout.TotalMilliseconds}ms.", cts.Token); - } - - await connectTask.ConfigureAwait(false); + var finishedTask = await Task.WhenAny(connectTask, Task.Delay(_connectionTimeout)).ConfigureAwait(false); + if (connectTask != finishedTask) // timed out + { + try + { + // close client immediately when failed to connect within timeout + await DisconnectAsync().ConfigureAwait(false); } + catch (Exception e) + { + _logger?.Error($"Failed to close connect to the server {address}:{port}" + + $" after connection timed out {_connectionTimeout.TotalMilliseconds}ms" + + $" due to error: {e.Message}.", e); + } + + throw new OperationCanceledException( + $"Failed to connect to server {address}:{port} within {_connectionTimeout.TotalMilliseconds}ms."); } + + await connectTask.ConfigureAwait(false); } public virtual void Disconnect() @@ -274,7 +267,7 @@ public virtual Task DisconnectAsync() t => { _client.Dispose(); - _stream.Dispose(); + _stream?.Dispose(); _client = null; _stream = null;