From d384023babb7a7f2240c6a6fa460c5191ce77a01 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 2 Dec 2016 18:01:41 +0300 Subject: [PATCH 01/34] fix reconnect - introduce faulted state --- src/tarantool.client/ILogicalConnection.cs | 2 + src/tarantool.client/LogicalConnection.cs | 38 ++++++++++++++++--- .../NetworkStreamPhysicalConnection.cs | 28 +++++++++++++- src/tarantool.client/ResponseReader.cs | 30 +++++++-------- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index 4a7b6238..4b8f5d19 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -19,5 +19,7 @@ Task> SendRequest(TRequest reques TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream); IEnumerable> PopAllResponseCompletionSources(); + + void CancelAllPendingRequests(); } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 49f29a64..63924f45 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -29,6 +29,8 @@ internal class LogicalConnection : ILogicalConnection private readonly ILog _logWriter; + private bool isFaulted = false; + public LogicalConnection(ClientOptions options, INetworkStreamPhysicalConnection physicalConnection) { _msgPackContext = options.MsgPackContext; @@ -79,6 +81,18 @@ public IEnumerable> PopAllResponseCompletionS return result; } + public void CancelAllPendingRequests() + { + this.isFaulted = true; + + _logWriter?.WriteLine("Cancelling all pending requests..."); + var responses = PopAllResponseCompletionSources(); + foreach (var response in responses) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + } + private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { @@ -90,13 +104,22 @@ private async Task SendRequestImpl(TRequest requ long headerLength; var headerBuffer = CreateAndSerializeBuffer(request, requestId, bodyBuffer, out headerLength); - lock (_physicalConnection) + try { - _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); - _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int) headerLength); + lock (_physicalConnection) + { + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); + _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); - _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); - _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); + _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + } + } + catch (Exception ex) + { + CancelAllPendingRequests(); + _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); + throw; } try @@ -146,6 +169,11 @@ private RequestId GetRequestId() private Task GetResponseTask(RequestId requestId) { + if (!this.isFaulted) + { + throw new InvalidOperationException("Connection is in faulted state"); + } + var tcs = new TaskCompletionSource(); if (!_pendingRequests.TryAdd(requestId, tcs)) { diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 369b6b39..31e1b1e2 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -13,6 +13,8 @@ namespace ProGaudi.Tarantool.Client { + using System.Runtime.InteropServices; + internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnection { private Stream _stream; @@ -34,10 +36,24 @@ public async Task Connect(ClientOptions options) _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await ConnectAsync(_socket, singleNode.Uri.Host, singleNode.Uri.Port); + SetKeepAlive(true, 1000, 100); _stream = new NetworkStream(_socket, true); options.LogWriter?.WriteLine("Socket connection established."); } + private void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval) + { + int size = Marshal.SizeOf(new uint()); + + var inOptionValues = new byte[size * 3]; + + BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0); + BitConverter.GetBytes(keepAliveTime).CopyTo(inOptionValues, size); + BitConverter.GetBytes(keepAliveInterval).CopyTo(inOptionValues, size * 2); + + this._socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); + } + public void Write(byte[] buffer, int offset, int count) { CheckConnectionStatus(); @@ -47,7 +63,6 @@ public void Write(byte[] buffer, int offset, int count) public async Task Flush() { CheckConnectionStatus(); - await _stream.FlushAsync(); } @@ -96,8 +111,19 @@ private static Task ConnectAsync(Socket socket, string host, int port) } #endif + public bool IsConnected() + { + try + { + return !(this._socket.Poll(1, SelectMode.SelectRead) && this._socket.Available == 0); + } + catch (SocketException) { return false; } + } + private void CheckConnectionStatus() { + var fc2 = this.IsConnected(); + if (_stream == null) { throw ExceptionHelper.NotConnected(); diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index d0510f65..304ad810 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -51,16 +51,24 @@ private void EndReading(Task readWork) { if (!_disposed) { - var readBytesCount = readWork.Result; - _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); - - if (ProcessReadBytes(readBytesCount)) + if (readWork.IsCompleted) { - BeginReading(); + var readBytesCount = readWork.Result; + _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); + + if (ProcessReadBytes(readBytesCount)) + { + BeginReading(); + } + else + { + _logicalConnection.CancelAllPendingRequests(); + } } else { - CancelAllPendingRequests(); + _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); + _logicalConnection.CancelAllPendingRequests(); } } else @@ -69,16 +77,6 @@ private void EndReading(Task readWork) } } - private void CancelAllPendingRequests() - { - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); - var responses = _logicalConnection.PopAllResponseCompletionSources(); - foreach (var response in responses) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); - } - } - private bool ProcessReadBytes(int readBytesCount) { if (readBytesCount <= 0) From 497f446824fe87e32bcf4af7d73b0a79a4d2c6ee Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 2 Dec 2016 19:09:51 +0300 Subject: [PATCH 02/34] phys connection and resp reader into logical conn --- src/tarantool.client/Box.cs | 30 ++-------------- src/tarantool.client/ILogicalConnection.cs | 8 ++++- src/tarantool.client/LogicalConnection.cs | 42 ++++++++++++++++++++-- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index 97a7185e..e4c7e224 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -15,40 +15,17 @@ public class Box : IBox private readonly ILogicalConnection _logicalConnection; - private readonly IResponseReader _responseReader; - - private readonly INetworkStreamPhysicalConnection _physicalConnection; - public Box(ClientOptions options) { _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _physicalConnection = new NetworkStreamPhysicalConnection(); - _logicalConnection = new LogicalConnection(options, _physicalConnection); - _responseReader = new ResponseReader(_logicalConnection, options, _physicalConnection); + _logicalConnection = new LogicalConnection(options) { GreetingFunc = this.LoginIfNotGuest }; } public async Task Connect() { - await _physicalConnection.Connect(_clientOptions); - - var greetingsResponseBytes = new byte[128]; - var readCount = await _physicalConnection.ReadAsync(greetingsResponseBytes, 0, greetingsResponseBytes.Length); - if (readCount != greetingsResponseBytes.Length) - { - throw ExceptionHelper.UnexpectedGreetingBytesCount(readCount); - } - - var greetings = new GreetingsResponse(greetingsResponseBytes); - - _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); - - _responseReader.BeginReading(); - - _clientOptions.LogWriter?.WriteLine("Server responses reading started."); - - await LoginIfNotGuest(greetings); + await _logicalConnection.Connect(); } public static async Task Connect(string replicationSource) @@ -72,8 +49,7 @@ public void Dispose() { _clientOptions.LogWriter?.WriteLine("Box is disposing..."); _clientOptions.LogWriter?.Flush(); - _responseReader.Dispose(); - _physicalConnection.Dispose(); + _logicalConnection.Dispose(); } public ISchema GetSchema() diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index 4b8f5d19..7840f5c5 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -8,8 +8,14 @@ namespace ProGaudi.Tarantool.Client { - public interface ILogicalConnection + using System; + + public interface ILogicalConnection : IDisposable { + Func GreetingFunc { get; set; } + + Task Connect(); + Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest; diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 63924f45..bf6702e6 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -20,7 +20,11 @@ internal class LogicalConnection : ILogicalConnection { private readonly MsgPackContext _msgPackContext; - private readonly INetworkStreamPhysicalConnection _physicalConnection; + private readonly ClientOptions _clientOptions; + + private INetworkStreamPhysicalConnection _physicalConnection; + + private IResponseReader _responseReader; private long _currentRequestId; @@ -31,11 +35,43 @@ internal class LogicalConnection : ILogicalConnection private bool isFaulted = false; - public LogicalConnection(ClientOptions options, INetworkStreamPhysicalConnection physicalConnection) + public Func GreetingFunc { get; set; } + + public LogicalConnection(ClientOptions options) { + _clientOptions = options; _msgPackContext = options.MsgPackContext; _logWriter = options.LogWriter; - _physicalConnection = physicalConnection; + } + + public void Dispose() + { + _responseReader?.Dispose(); + _physicalConnection?.Dispose(); + } + + public async Task Connect() + { + _physicalConnection = new NetworkStreamPhysicalConnection(); + await _physicalConnection.Connect(_clientOptions); + + var greetingsResponseBytes = new byte[128]; + var readCount = await _physicalConnection.ReadAsync(greetingsResponseBytes, 0, greetingsResponseBytes.Length); + if (readCount != greetingsResponseBytes.Length) + { + throw ExceptionHelper.UnexpectedGreetingBytesCount(readCount); + } + + var greetings = new GreetingsResponse(greetingsResponseBytes); + + _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); + + _responseReader = new ResponseReader(this, _clientOptions, _physicalConnection); + _responseReader.BeginReading(); + + _clientOptions.LogWriter?.WriteLine("Server responses reading started."); + + await GreetingFunc(greetings); } public async Task SendRequestWithEmptyResponse(TRequest request) From 8252cf0ff29ad56ddfa17383e2a6e5acc6bddc83 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 2 Dec 2016 19:35:18 +0300 Subject: [PATCH 03/34] requests queue and faulted state inside resp reader --- src/tarantool.client/Box.cs | 2 +- src/tarantool.client/ILogicalConnection.cs | 8 +-- src/tarantool.client/IResponseReader.cs | 9 +++ src/tarantool.client/LogicalConnection.cs | 63 +++---------------- .../NetworkStreamPhysicalConnection.cs | 8 ++- src/tarantool.client/ResponseReader.cs | 63 +++++++++++++++++-- 6 files changed, 82 insertions(+), 71 deletions(-) diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index e4c7e224..218029c6 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -20,7 +20,7 @@ public Box(ClientOptions options) _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _logicalConnection = new LogicalConnection(options) { GreetingFunc = this.LoginIfNotGuest }; + _logicalConnection = new LogicalConnection(options) { _greetingFunc = this.LoginIfNotGuest }; } public async Task Connect() diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index 7840f5c5..a9e5e863 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -12,7 +12,7 @@ namespace ProGaudi.Tarantool.Client public interface ILogicalConnection : IDisposable { - Func GreetingFunc { get; set; } + Func _greetingFunc { get; set; } Task Connect(); @@ -21,11 +21,5 @@ Task SendRequestWithEmptyResponse(TRequest request) Task> SendRequest(TRequest request) where TRequest : IRequest; - - TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream); - - IEnumerable> PopAllResponseCompletionSources(); - - void CancelAllPendingRequests(); } } \ No newline at end of file diff --git a/src/tarantool.client/IResponseReader.cs b/src/tarantool.client/IResponseReader.cs index 092c78c4..151f97c7 100644 --- a/src/tarantool.client/IResponseReader.cs +++ b/src/tarantool.client/IResponseReader.cs @@ -2,8 +2,17 @@ namespace ProGaudi.Tarantool.Client { + using System.IO; + using System.Threading.Tasks; + + using ProGaudi.Tarantool.Client.Model; + public interface IResponseReader : IDisposable { void BeginReading(); + + Task GetResponseTask(RequestId requestId); + + void FaultedState(); } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index bf6702e6..f7aac841 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -28,14 +28,9 @@ internal class LogicalConnection : ILogicalConnection private long _currentRequestId; - private readonly ConcurrentDictionary> _pendingRequests = - new ConcurrentDictionary>(); - private readonly ILog _logWriter; - private bool isFaulted = false; - - public Func GreetingFunc { get; set; } + public Func _greetingFunc { get; set; } public LogicalConnection(ClientOptions options) { @@ -66,14 +61,16 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); - _responseReader = new ResponseReader(this, _clientOptions, _physicalConnection); + _responseReader = new ResponseReader(_clientOptions, _physicalConnection); _responseReader.BeginReading(); _clientOptions.LogWriter?.WriteLine("Server responses reading started."); - await GreetingFunc(greetings); + await this._greetingFunc(greetings); } + // TODO: reconnection func here with dropping/disposing old _physicalConnection and _responseReader + public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest { @@ -86,15 +83,6 @@ public async Task> SendRequest(TR return await SendRequestImpl>(request); } - public TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream) - { - TaskCompletionSource request; - - return _pendingRequests.TryRemove(requestId, out request) - ? request - : null; - } - public static byte[] ReadFully(Stream input) { input.Position = 0; @@ -110,32 +98,15 @@ public static byte[] ReadFully(Stream input) } } - public IEnumerable> PopAllResponseCompletionSources() - { - var result = _pendingRequests.Values.ToArray(); - _pendingRequests.Clear(); - return result; - } - - public void CancelAllPendingRequests() - { - this.isFaulted = true; - - _logWriter?.WriteLine("Cancelling all pending requests..."); - var responses = PopAllResponseCompletionSources(); - foreach (var response in responses) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); - } - } - private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { + // TODO: detect disconnects and reconnect here + var bodyBuffer = MsgPackSerializer.Serialize(request, _msgPackContext); var requestId = GetRequestId(); - var responseTask = GetResponseTask(requestId); + var responseTask = _responseReader.GetResponseTask(requestId); long headerLength; var headerBuffer = CreateAndSerializeBuffer(request, requestId, bodyBuffer, out headerLength); @@ -153,7 +124,7 @@ private async Task SendRequestImpl(TRequest requ } catch (Exception ex) { - CancelAllPendingRequests(); + _responseReader.FaultedState(); _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); throw; } @@ -202,21 +173,5 @@ private RequestId GetRequestId() var requestId = Interlocked.Increment(ref _currentRequestId); return (RequestId) (ulong) requestId; } - - private Task GetResponseTask(RequestId requestId) - { - if (!this.isFaulted) - { - throw new InvalidOperationException("Connection is in faulted state"); - } - - var tcs = new TaskCompletionSource(); - if (!_pendingRequests.TryAdd(requestId, tcs)) - { - throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); - } - - return tcs.Task; - } } } \ No newline at end of file diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 31e1b1e2..f4faf30f 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -51,7 +51,7 @@ private void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval) BitConverter.GetBytes(keepAliveTime).CopyTo(inOptionValues, size); BitConverter.GetBytes(keepAliveInterval).CopyTo(inOptionValues, size * 2); - this._socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); + _socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); } public void Write(byte[] buffer, int offset, int count) @@ -111,18 +111,20 @@ private static Task ConnectAsync(Socket socket, string host, int port) } #endif + // TODO: make this working together inside and outside + public bool IsConnected() { try { - return !(this._socket.Poll(1, SelectMode.SelectRead) && this._socket.Available == 0); + return !(_socket.Poll(1, SelectMode.SelectRead) && _socket.Available == 0); } catch (SocketException) { return false; } } private void CheckConnectionStatus() { - var fc2 = this.IsConnected(); + var fc2 = IsConnected(); if (_stream == null) { diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 304ad810..dcfcf8d9 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -13,11 +13,18 @@ namespace ProGaudi.Tarantool.Client { + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + internal class ResponseReader : IResponseReader { private readonly INetworkStreamPhysicalConnection _physicalConnection; - private readonly ILogicalConnection _logicalConnection; + private readonly ConcurrentDictionary> _pendingRequests = + new ConcurrentDictionary>(); + + private bool _faulted; private readonly ClientOptions _clientOptions; @@ -29,14 +36,58 @@ internal class ResponseReader : IResponseReader private bool _disposed; - public ResponseReader(ILogicalConnection logicalConnection, ClientOptions clientOptions, INetworkStreamPhysicalConnection physicalConnection) + public ResponseReader(ClientOptions clientOptions, INetworkStreamPhysicalConnection physicalConnection) { _physicalConnection = physicalConnection; - _logicalConnection = logicalConnection; _clientOptions = clientOptions; _buffer = new byte[clientOptions.ConnectionOptions.ReadStreamBufferSize]; } + public Task GetResponseTask(RequestId requestId) + { + if (!this._faulted) + { + throw new InvalidOperationException("Connection is in faulted state"); + } + + var tcs = new TaskCompletionSource(); + if (!_pendingRequests.TryAdd(requestId, tcs)) + { + throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); + } + + return tcs.Task; + } + + private TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream) + { + TaskCompletionSource request; + + return _pendingRequests.TryRemove(requestId, out request) + ? request + : null; + } + + private IEnumerable> PopAllResponseCompletionSources() + { + var result = _pendingRequests.Values.ToArray(); + _pendingRequests.Clear(); + return result; + } + + public void FaultedState() + { + this._faulted = true; + + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); + + var responses = PopAllResponseCompletionSources(); + foreach (var response in responses) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + } + public void BeginReading() { var freeBufferSpace = EnsureSpaceAndComputeBytesToRead(); @@ -62,13 +113,13 @@ private void EndReading(Task readWork) } else { - _logicalConnection.CancelAllPendingRequests(); + FaultedState(); } } else { _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - _logicalConnection.CancelAllPendingRequests(); + FaultedState(); } } else @@ -137,7 +188,7 @@ private void MatchResult(byte[] result) { var resultStream = new MemoryStream(result); var header= MsgPackSerializer.Deserialize(resultStream, _clientOptions.MsgPackContext); - var tcs = _logicalConnection.PopResponseCompletionSource(header.RequestId, resultStream); + var tcs = PopResponseCompletionSource(header.RequestId, resultStream); if (tcs == null) { From a6efd0b49e681f1f3e5164e3fbd29fc58c2d03d9 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 19:17:41 +0300 Subject: [PATCH 04/34] more distributed arcvitecture --- src/tarantool.client/Box.cs | 23 +---- src/tarantool.client/ILogicalConnection.cs | 6 +- .../INetworkStreamPhysicalConnection.cs | 1 + src/tarantool.client/LogicalConnection.cs | 46 ++++++---- .../LogicalConnectionWrapper.cs | 88 +++++++++++++++++++ .../NetworkStreamPhysicalConnection.cs | 16 ++-- src/tarantool.client/RequestIdCounter.cs | 17 ++++ src/tarantool.client/ResponseReader.cs | 68 ++++++++------ src/tarantool.client/Utils/ExceptionHelper.cs | 5 ++ 9 files changed, 191 insertions(+), 79 deletions(-) create mode 100644 src/tarantool.client/LogicalConnectionWrapper.cs create mode 100644 src/tarantool.client/RequestIdCounter.cs diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index 218029c6..c7c7613b 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -1,11 +1,8 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; -using ProGaudi.Tarantool.Client.Utils; namespace ProGaudi.Tarantool.Client { @@ -20,7 +17,7 @@ public Box(ClientOptions options) _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _logicalConnection = new LogicalConnection(options) { _greetingFunc = this.LoginIfNotGuest }; + _logicalConnection = new LogicalConnectionWrapper(options); } public async Task Connect() @@ -117,21 +114,5 @@ public Task> Eval(string expression) { return Eval(expression, TarantoolTuple.Empty); } - - private async Task LoginIfNotGuest(GreetingsResponse greetings) - { - var singleNode = _clientOptions.ConnectionOptions.Nodes.Single(); - - if (string.IsNullOrEmpty(singleNode.Uri.UserName)) - { - _clientOptions.LogWriter?.WriteLine("Guest mode, no authentication attempt."); - return; - } - - var authenticateRequest = AuthenticationRequest.Create(greetings, singleNode.Uri); - - await _logicalConnection.SendRequestWithEmptyResponse(authenticateRequest); - _clientOptions.LogWriter?.WriteLine($"Authentication request send: {authenticateRequest}"); - } } } \ No newline at end of file diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index a9e5e863..f942efb4 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -12,10 +12,10 @@ namespace ProGaudi.Tarantool.Client public interface ILogicalConnection : IDisposable { - Func _greetingFunc { get; set; } - Task Connect(); - + + bool IsConnected(); + Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest; diff --git a/src/tarantool.client/INetworkStreamPhysicalConnection.cs b/src/tarantool.client/INetworkStreamPhysicalConnection.cs index 32842f6b..bf2122d2 100644 --- a/src/tarantool.client/INetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/INetworkStreamPhysicalConnection.cs @@ -8,6 +8,7 @@ public interface INetworkStreamPhysicalConnection : IDisposable { Task Connect(ClientOptions options); Task Flush(); + bool IsConnected(); Task ReadAsync(byte[] buffer, int offset, int count); void Write(byte[] buffer, int offset, int count); } diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index f7aac841..e829da7f 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -22,27 +22,26 @@ internal class LogicalConnection : ILogicalConnection private readonly ClientOptions _clientOptions; + private readonly RequestIdCounter _requestIdCounter; + private INetworkStreamPhysicalConnection _physicalConnection; private IResponseReader _responseReader; - private long _currentRequestId; - private readonly ILog _logWriter; - public Func _greetingFunc { get; set; } - - public LogicalConnection(ClientOptions options) + public LogicalConnection(ClientOptions options, RequestIdCounter requestIdCounter) { _clientOptions = options; + _requestIdCounter = requestIdCounter; _msgPackContext = options.MsgPackContext; _logWriter = options.LogWriter; } public void Dispose() { - _responseReader?.Dispose(); - _physicalConnection?.Dispose(); + Interlocked.Exchange(ref _responseReader, null)?.Dispose(); + Interlocked.Exchange(ref _physicalConnection, null)?.Dispose(); } public async Task Connect() @@ -66,10 +65,29 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine("Server responses reading started."); - await this._greetingFunc(greetings); + await LoginIfNotGuest(greetings); } - // TODO: reconnection func here with dropping/disposing old _physicalConnection and _responseReader + public bool IsConnected() + { + return _physicalConnection?.IsConnected() != null; + } + + private async Task LoginIfNotGuest(GreetingsResponse greetings) + { + var singleNode = _clientOptions.ConnectionOptions.Nodes.Single(); + + if (string.IsNullOrEmpty(singleNode.Uri.UserName)) + { + _clientOptions.LogWriter?.WriteLine("Guest mode, no authentication attempt."); + return; + } + + var authenticateRequest = AuthenticationRequest.Create(greetings, singleNode.Uri); + + await SendRequestWithEmptyResponse(authenticateRequest); + _clientOptions.LogWriter?.WriteLine($"Authentication request send: {authenticateRequest}"); + } public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest @@ -101,11 +119,9 @@ public static byte[] ReadFully(Stream input) private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { - // TODO: detect disconnects and reconnect here - var bodyBuffer = MsgPackSerializer.Serialize(request, _msgPackContext); - var requestId = GetRequestId(); + var requestId = _requestIdCounter.GetRequestId(); var responseTask = _responseReader.GetResponseTask(requestId); long headerLength; @@ -167,11 +183,5 @@ private byte[] CreateAndSerializeBuffer( MsgPackSerializer.Serialize(packetLength, stream, _msgPackContext); return packetSizeBuffer; } - - private RequestId GetRequestId() - { - var requestId = Interlocked.Increment(ref _currentRequestId); - return (RequestId) (ulong) requestId; - } } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs new file mode 100644 index 00000000..7810686f --- /dev/null +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading.Tasks; +using ProGaudi.Tarantool.Client.Model.Requests; +using ProGaudi.Tarantool.Client.Model.Responses; + +namespace ProGaudi.Tarantool.Client +{ + using System.Threading; + + using ProGaudi.Tarantool.Client.Model; + using ProGaudi.Tarantool.Client.Utils; + + public class LogicalConnectionWrapper : ILogicalConnection + { + private readonly ClientOptions _clientOptions; + + private readonly RequestIdCounter _requestIdCounter = new RequestIdCounter(); + + private LogicalConnection _droppableLogicalConnection; + + private readonly ManualResetEvent _connected = new ManualResetEvent(false); + + private const int connectionTimeout = 1000; + + public LogicalConnectionWrapper(ClientOptions options) + { + _clientOptions = options; + } + + public void Dispose() + { + Interlocked.Exchange(ref _droppableLogicalConnection, null)?.Dispose(); + } + + public async Task Connect() + { + _connected.Reset(); + + var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); + await _newConnection.Connect(); + Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); + + _connected.Set(); + } + + public bool IsConnected() + { + return _droppableLogicalConnection?.IsConnected() != null; + } + + private async Task EnsureConnection() + { + if (_connected.WaitOne() && IsConnected()) + { + return; + } + + if (!Monitor.TryEnter(this, connectionTimeout)) + { + throw ExceptionHelper.NotConnected(); + } + + try + { + if (!IsConnected()) + { + await Connect(); + } + } + finally + { + Monitor.Exit(this); + } + } + + public async Task> SendRequest(TRequest request) where TRequest : IRequest + { + await EnsureConnection(); + return await _droppableLogicalConnection.SendRequest(request); + } + + public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest + { + await EnsureConnection(); + await _droppableLogicalConnection.SendRequestWithEmptyResponse(request); + } + } +} diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index f4faf30f..8e4fcee8 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -14,6 +14,7 @@ namespace ProGaudi.Tarantool.Client { using System.Runtime.InteropServices; + using System.Threading; internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnection { @@ -26,7 +27,8 @@ internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnectio public void Dispose() { _disposed = true; - _stream?.Dispose(); + Interlocked.Exchange(ref _stream, null)?.Dispose(); + Interlocked.Exchange(ref _socket, null)?.Dispose(); } public async Task Connect(ClientOptions options) @@ -111,8 +113,6 @@ private static Task ConnectAsync(Socket socket, string host, int port) } #endif - // TODO: make this working together inside and outside - public bool IsConnected() { try @@ -124,16 +124,14 @@ public bool IsConnected() private void CheckConnectionStatus() { - var fc2 = IsConnected(); - - if (_stream == null) + if (_disposed) { - throw ExceptionHelper.NotConnected(); + throw new ObjectDisposedException(nameof(NetworkStreamPhysicalConnection)); } - if (_disposed) + if (!IsConnected() || _stream == null) { - throw new ObjectDisposedException(nameof(NetworkStreamPhysicalConnection)); + throw ExceptionHelper.NotConnected(); } } } diff --git a/src/tarantool.client/RequestIdCounter.cs b/src/tarantool.client/RequestIdCounter.cs new file mode 100644 index 00000000..3c1d99f3 --- /dev/null +++ b/src/tarantool.client/RequestIdCounter.cs @@ -0,0 +1,17 @@ +namespace ProGaudi.Tarantool.Client +{ + using System.Threading; + + using ProGaudi.Tarantool.Client.Model; + + public class RequestIdCounter + { + private long _currentRequestId; + + public RequestId GetRequestId() + { + var requestId = Interlocked.Increment(ref _currentRequestId); + return (RequestId)(ulong)requestId; + } + } +} diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index dcfcf8d9..a78f05b7 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -16,16 +16,15 @@ namespace ProGaudi.Tarantool.Client using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; + using System.Threading; internal class ResponseReader : IResponseReader { private readonly INetworkStreamPhysicalConnection _physicalConnection; - private readonly ConcurrentDictionary> _pendingRequests = + private ConcurrentDictionary> _pendingRequests = new ConcurrentDictionary>(); - private bool _faulted; - private readonly ClientOptions _clientOptions; private byte[] _buffer; @@ -45,46 +44,58 @@ public ResponseReader(ClientOptions clientOptions, INetworkStreamPhysicalConnect public Task GetResponseTask(RequestId requestId) { - if (!this._faulted) + lock (this) { - throw new InvalidOperationException("Connection is in faulted state"); - } + if (_pendingRequests == null) + { + throw ExceptionHelper.FaultedState(); + } - var tcs = new TaskCompletionSource(); - if (!_pendingRequests.TryAdd(requestId, tcs)) - { - throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); - } + var tcs = new TaskCompletionSource(); + if (!_pendingRequests.TryAdd(requestId, tcs)) + { + throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); + } - return tcs.Task; + return tcs.Task; + } } private TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream) { - TaskCompletionSource request; + lock (this) + { + if (_pendingRequests == null) + { + throw ExceptionHelper.FaultedState(); + } - return _pendingRequests.TryRemove(requestId, out request) - ? request - : null; - } + TaskCompletionSource request; - private IEnumerable> PopAllResponseCompletionSources() - { - var result = _pendingRequests.Values.ToArray(); - _pendingRequests.Clear(); - return result; + return _pendingRequests.TryRemove(requestId, out request) + ? request + : null; + } } public void FaultedState() { - this._faulted = true; + lock (this) + { + var _pendingRequestsLocal = Interlocked.Exchange(ref _pendingRequests, null); + if (_pendingRequestsLocal == null) + { + return; + } - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); - var responses = PopAllResponseCompletionSources(); - foreach (var response in responses) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); + foreach (var response in _pendingRequestsLocal.Values) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + + _pendingRequestsLocal.Clear(); } } @@ -292,6 +303,7 @@ private int EnsureSpaceAndComputeBytesToRead() public void Dispose() { _disposed = true; + FaultedState(); } } } \ No newline at end of file diff --git a/src/tarantool.client/Utils/ExceptionHelper.cs b/src/tarantool.client/Utils/ExceptionHelper.cs index bef5714e..9770204a 100644 --- a/src/tarantool.client/Utils/ExceptionHelper.cs +++ b/src/tarantool.client/Utils/ExceptionHelper.cs @@ -41,6 +41,11 @@ public static Exception NotConnected() return new InvalidOperationException("Can't perform operation. Looks like we are not connected to tarantool. Call 'Connect' method before calling any other operations."); } + public static Exception FaultedState() + { + return new InvalidOperationException("Connection is in faulted state"); + } + public static ArgumentException TarantoolError(ResponseHeader header, ErrorResponse errorResponse) { var detailedMessage = GetDetailedTarantoolMessage(header.Code); From 3e9ae2b065183e32ba4dbbc70476aff4624c90b0 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 20:06:30 +0300 Subject: [PATCH 05/34] check resp reader fault state for connection alive --- src/tarantool.client/IResponseReader.cs | 4 +++- src/tarantool.client/LogicalConnection.cs | 4 ++-- src/tarantool.client/ResponseReader.cs | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/tarantool.client/IResponseReader.cs b/src/tarantool.client/IResponseReader.cs index 151f97c7..73c5c8f7 100644 --- a/src/tarantool.client/IResponseReader.cs +++ b/src/tarantool.client/IResponseReader.cs @@ -13,6 +13,8 @@ public interface IResponseReader : IDisposable Task GetResponseTask(RequestId requestId); - void FaultedState(); + void SetFaultedState(); + + bool IsFaultedState { get; } } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index e829da7f..3c2ade86 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -70,7 +70,7 @@ public async Task Connect() public bool IsConnected() { - return _physicalConnection?.IsConnected() != null; + return !(this._responseReader?.IsFaultedState == null) && _physicalConnection?.IsConnected() != null; } private async Task LoginIfNotGuest(GreetingsResponse greetings) @@ -140,7 +140,7 @@ private async Task SendRequestImpl(TRequest requ } catch (Exception ex) { - _responseReader.FaultedState(); + _responseReader.SetFaultedState(); _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); throw; } diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index a78f05b7..d3491148 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -78,7 +78,7 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId } } - public void FaultedState() + public void SetFaultedState() { lock (this) { @@ -99,6 +99,8 @@ public void FaultedState() } } + public bool IsFaultedState => _pendingRequests == null; + public void BeginReading() { var freeBufferSpace = EnsureSpaceAndComputeBytesToRead(); @@ -124,13 +126,13 @@ private void EndReading(Task readWork) } else { - FaultedState(); + this.SetFaultedState(); } } else { _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - FaultedState(); + this.SetFaultedState(); } } else @@ -303,7 +305,7 @@ private int EnsureSpaceAndComputeBytesToRead() public void Dispose() { _disposed = true; - FaultedState(); + this.SetFaultedState(); } } } \ No newline at end of file From 2ba2d7899cc26ef0e0b864b59baa3fa0aff1797a Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 20:40:51 +0300 Subject: [PATCH 06/34] more log records --- src/tarantool.client/LogicalConnectionWrapper.cs | 9 +++++++++ src/tarantool.client/ResponseReader.cs | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs index 7810686f..aeefe304 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -34,6 +34,8 @@ public void Dispose() public async Task Connect() { + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connecting..."); + _connected.Reset(); var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); @@ -41,6 +43,8 @@ public async Task Connect() Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); _connected.Set(); + + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connected..."); } public bool IsConnected() @@ -55,8 +59,11 @@ private async Task EnsureConnection() return; } + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection lost, wait for reconnect..."); + if (!Monitor.TryEnter(this, connectionTimeout)) { + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Failed to get lock for reconnect"); throw ExceptionHelper.NotConnected(); } @@ -66,6 +73,8 @@ private async Task EnsureConnection() { await Connect(); } + + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection reacquired"); } finally { diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index d3491148..8a43f622 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -48,6 +48,7 @@ public Task GetResponseTask(RequestId requestId) { if (_pendingRequests == null) { + _clientOptions.LogWriter?.WriteLine($"{nameof(GetResponseTask)}: Already in faulted state..."); throw ExceptionHelper.FaultedState(); } @@ -67,6 +68,7 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId { if (_pendingRequests == null) { + _clientOptions.LogWriter?.WriteLine($"{nameof(PopResponseCompletionSource)}: Already in faulted state..."); throw ExceptionHelper.FaultedState(); } @@ -88,7 +90,7 @@ public void SetFaultedState() return; } - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests and setting faulted state..."); foreach (var response in _pendingRequestsLocal.Values) { From 5ad8b6baf02150261d766107a2a49797e72df5b8 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 21:28:28 +0300 Subject: [PATCH 07/34] some fixes --- src/tarantool.client/LogicalConnection.cs | 2 +- src/tarantool.client/LogicalConnectionWrapper.cs | 4 ++-- src/tarantool.client/ResponseReader.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 3c2ade86..17838c59 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -70,7 +70,7 @@ public async Task Connect() public bool IsConnected() { - return !(this._responseReader?.IsFaultedState == null) && _physicalConnection?.IsConnected() != null; + return !(_responseReader?.IsFaultedState ?? true) && (_physicalConnection?.IsConnected() ?? false); } private async Task LoginIfNotGuest(GreetingsResponse greetings) diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs index aeefe304..2090c1cf 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -49,12 +49,12 @@ public async Task Connect() public bool IsConnected() { - return _droppableLogicalConnection?.IsConnected() != null; + return _droppableLogicalConnection?.IsConnected() ?? false; } private async Task EnsureConnection() { - if (_connected.WaitOne() && IsConnected()) + if (_connected.WaitOne(connectionTimeout) && IsConnected()) { return; } diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 8a43f622..6cc9624f 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -128,13 +128,13 @@ private void EndReading(Task readWork) } else { - this.SetFaultedState(); + SetFaultedState(); } } else { _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - this.SetFaultedState(); + SetFaultedState(); } } else From e98e31f7495d8603378f2b6c5848c61cff08b45e Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 22:30:25 +0300 Subject: [PATCH 08/34] some fixes --- src/tarantool.client/LogicalConnectionWrapper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs index 2090c1cf..25ccf188 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -20,6 +20,8 @@ public class LogicalConnectionWrapper : ILogicalConnection private readonly ManualResetEvent _connected = new ManualResetEvent(false); + private readonly AutoResetEvent _reconnectAvailable = new AutoResetEvent(true); + private const int connectionTimeout = 1000; public LogicalConnectionWrapper(ClientOptions options) @@ -61,7 +63,7 @@ private async Task EnsureConnection() _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection lost, wait for reconnect..."); - if (!Monitor.TryEnter(this, connectionTimeout)) + if (!_reconnectAvailable.WaitOne(connectionTimeout)) { _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Failed to get lock for reconnect"); throw ExceptionHelper.NotConnected(); @@ -78,7 +80,7 @@ private async Task EnsureConnection() } finally { - Monitor.Exit(this); + _reconnectAvailable.Set(); } } From a2a8b421a88050d339f2cecf61c5aadd48fae033 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Thu, 8 Dec 2016 13:55:41 +0300 Subject: [PATCH 09/34] disposed/state redesign --- src/tarantool.client/ILogicalConnection.cs | 6 +- src/tarantool.client/IResponseReader.cs | 4 +- src/tarantool.client/LogicalConnection.cs | 59 +++++++++---- .../NetworkStreamPhysicalConnection.cs | 19 ++-- src/tarantool.client/ResponseReader.cs | 86 ++++++++----------- src/tarantool.client/Utils/ExceptionHelper.cs | 5 -- 6 files changed, 93 insertions(+), 86 deletions(-) diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index f942efb4..3841690b 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -1,15 +1,11 @@ -using System.Collections.Generic; -using System.IO; +using System; using System.Threading.Tasks; -using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; namespace ProGaudi.Tarantool.Client { - using System; - public interface ILogicalConnection : IDisposable { Task Connect(); diff --git a/src/tarantool.client/IResponseReader.cs b/src/tarantool.client/IResponseReader.cs index 73c5c8f7..dccb103e 100644 --- a/src/tarantool.client/IResponseReader.cs +++ b/src/tarantool.client/IResponseReader.cs @@ -13,8 +13,6 @@ public interface IResponseReader : IDisposable Task GetResponseTask(RequestId requestId); - void SetFaultedState(); - - bool IsFaultedState { get; } + bool IsConnected(); } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 17838c59..84af3703 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using ProGaudi.MsgPack.Light; @@ -24,29 +21,43 @@ internal class LogicalConnection : ILogicalConnection private readonly RequestIdCounter _requestIdCounter; - private INetworkStreamPhysicalConnection _physicalConnection; + private readonly INetworkStreamPhysicalConnection _physicalConnection; - private IResponseReader _responseReader; + private readonly IResponseReader _responseReader; private readonly ILog _logWriter; + private bool _disposed; + public LogicalConnection(ClientOptions options, RequestIdCounter requestIdCounter) { _clientOptions = options; _requestIdCounter = requestIdCounter; _msgPackContext = options.MsgPackContext; _logWriter = options.LogWriter; + + _physicalConnection = new NetworkStreamPhysicalConnection(); + _responseReader = new ResponseReader(_clientOptions, _physicalConnection); } public void Dispose() { - Interlocked.Exchange(ref _responseReader, null)?.Dispose(); - Interlocked.Exchange(ref _physicalConnection, null)?.Dispose(); + lock (this) + { + if (_disposed) + { + return; + } + + _disposed = true; + + _responseReader.Dispose(); + _physicalConnection.Dispose(); + } } public async Task Connect() { - _physicalConnection = new NetworkStreamPhysicalConnection(); await _physicalConnection.Connect(_clientOptions); var greetingsResponseBytes = new byte[128]; @@ -60,7 +71,6 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); - _responseReader = new ResponseReader(_clientOptions, _physicalConnection); _responseReader.BeginReading(); _clientOptions.LogWriter?.WriteLine("Server responses reading started."); @@ -70,7 +80,18 @@ public async Task Connect() public bool IsConnected() { - return !(_responseReader?.IsFaultedState ?? true) && (_physicalConnection?.IsConnected() ?? false); + if (_disposed) + { + return false; + } + + if (!_responseReader.IsConnected() || !_physicalConnection.IsConnected()) + { + Dispose(); + return false; + } + + return true; } private async Task LoginIfNotGuest(GreetingsResponse greetings) @@ -119,6 +140,11 @@ public static byte[] ReadFully(Stream input) private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { + if (_disposed) + { + throw new ObjectDisposedException(nameof(LogicalConnection)); + } + var bodyBuffer = MsgPackSerializer.Serialize(request, _msgPackContext); var requestId = _requestIdCounter.GetRequestId(); @@ -129,19 +155,16 @@ private async Task SendRequestImpl(TRequest requ try { - lock (_physicalConnection) - { - _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); - _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); + _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); - _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); - _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); - } + _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); + _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); } catch (Exception ex) { - _responseReader.SetFaultedState(); _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); + Dispose(); throw; } diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 8e4fcee8..9075065c 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Threading.Tasks; using ProGaudi.Tarantool.Client.Model; @@ -13,9 +14,6 @@ namespace ProGaudi.Tarantool.Client { - using System.Runtime.InteropServices; - using System.Threading; - internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnection { private Stream _stream; @@ -26,9 +24,18 @@ internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnectio public void Dispose() { - _disposed = true; - Interlocked.Exchange(ref _stream, null)?.Dispose(); - Interlocked.Exchange(ref _socket, null)?.Dispose(); + lock (this) + { + if (_disposed) + { + return; + } + + _disposed = true; + + _stream?.Dispose(); + _socket?.Dispose(); + } } public async Task Connect(ClientOptions options) diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 6cc9624f..1d75328b 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -1,28 +1,25 @@ using System; +using System.Collections.Concurrent; using System.IO; using System.Text; -using ProGaudi.MsgPack.Light; +using System.Threading.Tasks; + +using JetBrains.Annotations; +using ProGaudi.MsgPack.Light; using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Enums; using ProGaudi.Tarantool.Client.Model.Headers; using ProGaudi.Tarantool.Client.Model.Responses; using ProGaudi.Tarantool.Client.Utils; -using System.Threading.Tasks; -using JetBrains.Annotations; namespace ProGaudi.Tarantool.Client { - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - internal class ResponseReader : IResponseReader { private readonly INetworkStreamPhysicalConnection _physicalConnection; - private ConcurrentDictionary> _pendingRequests = + private readonly ConcurrentDictionary> _pendingRequests = new ConcurrentDictionary>(); private readonly ClientOptions _clientOptions; @@ -46,10 +43,9 @@ public Task GetResponseTask(RequestId requestId) { lock (this) { - if (_pendingRequests == null) + if (_disposed) { - _clientOptions.LogWriter?.WriteLine($"{nameof(GetResponseTask)}: Already in faulted state..."); - throw ExceptionHelper.FaultedState(); + throw new ObjectDisposedException(nameof(ResponseReader)); } var tcs = new TaskCompletionSource(); @@ -66,10 +62,9 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId { lock (this) { - if (_pendingRequests == null) + if (_disposed) { - _clientOptions.LogWriter?.WriteLine($"{nameof(PopResponseCompletionSource)}: Already in faulted state..."); - throw ExceptionHelper.FaultedState(); + throw new ObjectDisposedException(nameof(ResponseReader)); } TaskCompletionSource request; @@ -80,29 +75,6 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId } } - public void SetFaultedState() - { - lock (this) - { - var _pendingRequestsLocal = Interlocked.Exchange(ref _pendingRequests, null); - if (_pendingRequestsLocal == null) - { - return; - } - - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests and setting faulted state..."); - - foreach (var response in _pendingRequestsLocal.Values) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); - } - - _pendingRequestsLocal.Clear(); - } - } - - public bool IsFaultedState => _pendingRequests == null; - public void BeginReading() { var freeBufferSpace = EnsureSpaceAndComputeBytesToRead(); @@ -125,17 +97,12 @@ private void EndReading(Task readWork) if (ProcessReadBytes(readBytesCount)) { BeginReading(); + return; } - else - { - SetFaultedState(); - } - } - else - { - _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - SetFaultedState(); } + + _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); + Dispose(); } else { @@ -304,10 +271,31 @@ private int EnsureSpaceAndComputeBytesToRead() return space; } + public bool IsConnected() + { + return !_disposed; + } + public void Dispose() { - _disposed = true; - this.SetFaultedState(); + lock (this) + { + if (_disposed) + { + return; + } + + _disposed = true; + + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests and setting faulted state..."); + + foreach (var response in _pendingRequests.Values) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + + _pendingRequests.Clear(); + } } } } \ No newline at end of file diff --git a/src/tarantool.client/Utils/ExceptionHelper.cs b/src/tarantool.client/Utils/ExceptionHelper.cs index 9770204a..bef5714e 100644 --- a/src/tarantool.client/Utils/ExceptionHelper.cs +++ b/src/tarantool.client/Utils/ExceptionHelper.cs @@ -41,11 +41,6 @@ public static Exception NotConnected() return new InvalidOperationException("Can't perform operation. Looks like we are not connected to tarantool. Call 'Connect' method before calling any other operations."); } - public static Exception FaultedState() - { - return new InvalidOperationException("Connection is in faulted state"); - } - public static ArgumentException TarantoolError(ResponseHeader header, ErrorResponse errorResponse) { var detailedMessage = GetDetailedTarantoolMessage(header.Code); From 4be0d2ca63c07b90d6c7d9449e3da7ebb0624b65 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Thu, 8 Dec 2016 14:02:30 +0300 Subject: [PATCH 10/34] task status check fixed --- src/tarantool.client/ResponseReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 1d75328b..9005cea8 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -89,7 +89,7 @@ private void EndReading(Task readWork) { if (!_disposed) { - if (readWork.IsCompleted) + if (readWork.Status == TaskStatus.RanToCompletion) { var readBytesCount = readWork.Result; _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); From 5c398f7ee06ac21c9c45d0875b1922a30f923cd7 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 16 Dec 2016 14:44:18 +0300 Subject: [PATCH 11/34] requested fixes --- src/tarantool.client/Box.cs | 2 +- src/tarantool.client/LogicalConnection.cs | 27 +++++++++---------- ...Wrapper.cs => LogicalConnectionManager.cs} | 17 ++++++------ .../NetworkStreamPhysicalConnection.cs | 20 +++++++------- 4 files changed, 33 insertions(+), 33 deletions(-) rename src/tarantool.client/{LogicalConnectionWrapper.cs => LogicalConnectionManager.cs} (86%) diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index c7c7613b..bd737e13 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -17,7 +17,7 @@ public Box(ClientOptions options) _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _logicalConnection = new LogicalConnectionWrapper(options); + _logicalConnection = new LogicalConnectionManager(options); } public async Task Connect() diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 84af3703..5e8ae1d1 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -42,18 +42,15 @@ public LogicalConnection(ClientOptions options, RequestIdCounter requestIdCounte public void Dispose() { - lock (this) + if (_disposed) { - if (_disposed) - { - return; - } + return; + } - _disposed = true; + _disposed = true; - _responseReader.Dispose(); - _physicalConnection.Dispose(); - } + _responseReader.Dispose(); + _physicalConnection.Dispose(); } public async Task Connect() @@ -87,7 +84,6 @@ public bool IsConnected() if (!_responseReader.IsConnected() || !_physicalConnection.IsConnected()) { - Dispose(); return false; } @@ -155,11 +151,14 @@ private async Task SendRequestImpl(TRequest requ try { - _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); - _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); + lock (_physicalConnection) + { + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); + _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); - _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); - _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); + _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + } } catch (Exception ex) { diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionManager.cs similarity index 86% rename from src/tarantool.client/LogicalConnectionWrapper.cs rename to src/tarantool.client/LogicalConnectionManager.cs index 25ccf188..b50ae69c 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; @@ -10,7 +9,7 @@ namespace ProGaudi.Tarantool.Client using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Utils; - public class LogicalConnectionWrapper : ILogicalConnection + public class LogicalConnectionManager : ILogicalConnection { private readonly ClientOptions _clientOptions; @@ -24,7 +23,7 @@ public class LogicalConnectionWrapper : ILogicalConnection private const int connectionTimeout = 1000; - public LogicalConnectionWrapper(ClientOptions options) + public LogicalConnectionManager(ClientOptions options) { _clientOptions = options; } @@ -36,7 +35,7 @@ public void Dispose() public async Task Connect() { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connecting..."); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connecting..."); _connected.Reset(); @@ -46,7 +45,7 @@ public async Task Connect() _connected.Set(); - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connected..."); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); } public bool IsConnected() @@ -61,11 +60,11 @@ private async Task EnsureConnection() return; } - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection lost, wait for reconnect..."); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connection lost, wait for reconnect..."); if (!_reconnectAvailable.WaitOne(connectionTimeout)) { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Failed to get lock for reconnect"); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Failed to get lock for reconnect"); throw ExceptionHelper.NotConnected(); } @@ -76,7 +75,7 @@ private async Task EnsureConnection() await Connect(); } - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection reacquired"); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connection reacquired"); } finally { diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 9075065c..75a0603a 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -24,18 +24,15 @@ internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnectio public void Dispose() { - lock (this) + if (_disposed) { - if (_disposed) - { - return; - } + return; + } - _disposed = true; + _disposed = true; - _stream?.Dispose(); - _socket?.Dispose(); - } + _stream?.Dispose(); + _socket?.Dispose(); } public async Task Connect(ClientOptions options) @@ -122,6 +119,11 @@ private static Task ConnectAsync(Socket socket, string host, int port) public bool IsConnected() { + if (_disposed) + { + return false; + } + try { return !(_socket.Poll(1, SelectMode.SelectRead) && _socket.Available == 0); From 2df6fa14a121e4eb69e4e134c01a76e49b9e4b59 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 19 Dec 2016 20:38:32 +0300 Subject: [PATCH 12/34] ping tarantool instead of socket ping, tbd --- .../Converters/PingPacketConverter.cs | 24 +++++++ .../LogicalConnectionManager.cs | 68 +++++++++++++++++-- .../Model/Requests/PingRequest.cs | 9 +++ .../NetworkStreamPhysicalConnection.cs | 3 +- .../TarantoolConvertersRegistrator.cs | 1 + 5 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/tarantool.client/Converters/PingPacketConverter.cs create mode 100644 src/tarantool.client/Model/Requests/PingRequest.cs diff --git a/src/tarantool.client/Converters/PingPacketConverter.cs b/src/tarantool.client/Converters/PingPacketConverter.cs new file mode 100644 index 00000000..114c40c6 --- /dev/null +++ b/src/tarantool.client/Converters/PingPacketConverter.cs @@ -0,0 +1,24 @@ +using System; + +using ProGaudi.MsgPack.Light; + +using ProGaudi.Tarantool.Client.Model.Requests; + +namespace ProGaudi.Tarantool.Client.Converters +{ + internal class PingPacketConverter : IMsgPackConverter + { + public void Initialize(MsgPackContext context) + { + } + + public void Write(PingRequest value, IMsgPackWriter writer) + { + } + + public PingRequest Read(IMsgPackReader reader) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index b50ae69c..c086c559 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -1,14 +1,14 @@ -using System.Threading.Tasks; +using System; +using System.Threading; +using System.Threading.Tasks; + +using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; +using ProGaudi.Tarantool.Client.Utils; namespace ProGaudi.Tarantool.Client { - using System.Threading; - - using ProGaudi.Tarantool.Client.Model; - using ProGaudi.Tarantool.Client.Utils; - public class LogicalConnectionManager : ILogicalConnection { private readonly ClientOptions _clientOptions; @@ -21,8 +21,14 @@ public class LogicalConnectionManager : ILogicalConnection private readonly AutoResetEvent _reconnectAvailable = new AutoResetEvent(true); + private Timer _timer; + + private int _disposing; + private const int connectionTimeout = 1000; + private const int connectionPingInterval = 1000; + public LogicalConnectionManager(ClientOptions options) { _clientOptions = options; @@ -30,7 +36,23 @@ public LogicalConnectionManager(ClientOptions options) public void Dispose() { + if (Interlocked.Exchange(ref _disposing, 1) > 0) + { + return; + } + Interlocked.Exchange(ref _droppableLogicalConnection, null)?.Dispose(); + + var savedTimer = Interlocked.Exchange(ref _timer, null); + if (savedTimer != null) + { + using (var timerDisposedEvent = new ManualResetEvent(false)) + { + savedTimer.Dispose(timerDisposedEvent); + // this will guarantee that all callbacks finished + timerDisposedEvent.WaitOne(); + } + } } public async Task Connect() @@ -46,6 +68,40 @@ public async Task Connect() _connected.Set(); _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); + + _timer = new Timer(x => CheckPing(), null, connectionPingInterval, Timeout.Infinite); + } + + private static readonly PingRequest pingRequest = new PingRequest(); + + private void CheckPing() + { + LogicalConnection savedConnection = _droppableLogicalConnection; + + if (savedConnection == null) + { + return; + } + + try + { + Task task = savedConnection.SendRequestWithEmptyResponse(pingRequest); + if (!task.Wait(connectionTimeout) || task.Status != TaskStatus.RanToCompletion) + { + savedConnection.Dispose(); + } + } + catch (AggregateException ae) + { + savedConnection.Dispose(); + } + finally + { + if (_disposing == 0) + { + _timer?.Change(connectionPingInterval, Timeout.Infinite); + } + } } public bool IsConnected() diff --git a/src/tarantool.client/Model/Requests/PingRequest.cs b/src/tarantool.client/Model/Requests/PingRequest.cs new file mode 100644 index 00000000..4c4d0c3a --- /dev/null +++ b/src/tarantool.client/Model/Requests/PingRequest.cs @@ -0,0 +1,9 @@ +using ProGaudi.Tarantool.Client.Model.Enums; + +namespace ProGaudi.Tarantool.Client.Model.Requests +{ + public class PingRequest : IRequest + { + public CommandCode Code => CommandCode.Ping; + } +} \ No newline at end of file diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 75a0603a..f75442ac 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -42,7 +42,8 @@ public async Task Connect(ClientOptions options) _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await ConnectAsync(_socket, singleNode.Uri.Host, singleNode.Uri.Port); - SetKeepAlive(true, 1000, 100); + // unfortunately does not worl under linux + // SetKeepAlive(true, 1000, 100); _stream = new NetworkStream(_socket, true); options.LogWriter?.WriteLine("Socket connection established."); } diff --git a/src/tarantool.client/TarantoolConvertersRegistrator.cs b/src/tarantool.client/TarantoolConvertersRegistrator.cs index 4184b2b9..92c22eb3 100644 --- a/src/tarantool.client/TarantoolConvertersRegistrator.cs +++ b/src/tarantool.client/TarantoolConvertersRegistrator.cs @@ -45,6 +45,7 @@ public static void Register(MsgPackContext context) context.RegisterGenericConverter(typeof(InsertReplacePacketConverter<>)); context.RegisterGenericConverter(typeof(SelectPacketConverter<>)); context.RegisterGenericConverter(typeof(UpsertPacketConverter<>)); + context.RegisterConverter(new PingPacketConverter()); context.RegisterGenericConverter(typeof(TupleConverter<>)); context.RegisterGenericConverter(typeof(TupleConverter<,>)); From d64e990a55d5bdb312d8afa1f0bad957d38fe6fd Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Tue, 20 Dec 2016 17:35:27 +0300 Subject: [PATCH 13/34] ping tarantool, done --- .../LogicalConnectionManager.cs | 43 +++++++------------ .../NetworkStreamPhysicalConnection.cs | 22 +--------- 2 files changed, 18 insertions(+), 47 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index c086c559..d01453c2 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -27,7 +27,7 @@ public class LogicalConnectionManager : ILogicalConnection private const int connectionTimeout = 1000; - private const int connectionPingInterval = 1000; + private const int pingInterval = 100; public LogicalConnectionManager(ClientOptions options) { @@ -42,17 +42,7 @@ public void Dispose() } Interlocked.Exchange(ref _droppableLogicalConnection, null)?.Dispose(); - - var savedTimer = Interlocked.Exchange(ref _timer, null); - if (savedTimer != null) - { - using (var timerDisposedEvent = new ManualResetEvent(false)) - { - savedTimer.Dispose(timerDisposedEvent); - // this will guarantee that all callbacks finished - timerDisposedEvent.WaitOne(); - } - } + Interlocked.Exchange(ref _timer, null)?.Dispose(); } public async Task Connect() @@ -69,37 +59,36 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); - _timer = new Timer(x => CheckPing(), null, connectionPingInterval, Timeout.Infinite); + _timer = new Timer(x => CheckPing(), null, pingInterval, Timeout.Infinite); } - private static readonly PingRequest pingRequest = new PingRequest(); + private static readonly PingRequest _pingRequest = new PingRequest(); private void CheckPing() { LogicalConnection savedConnection = _droppableLogicalConnection; - if (savedConnection == null) - { - return; - } - try { - Task task = savedConnection.SendRequestWithEmptyResponse(pingRequest); - if (!task.Wait(connectionTimeout) || task.Status != TaskStatus.RanToCompletion) + if (savedConnection == null || !savedConnection.IsConnected()) { - savedConnection.Dispose(); + return; + } + + using (Task task = savedConnection.SendRequestWithEmptyResponse(_pingRequest)) + { + if (Task.WaitAny(new[] { task }, connectionTimeout) != 0 || task.Status != TaskStatus.RanToCompletion) + { + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Ping failed, dropping logical conection..."); + savedConnection.Dispose(); + } } - } - catch (AggregateException ae) - { - savedConnection.Dispose(); } finally { if (_disposing == 0) { - _timer?.Change(connectionPingInterval, Timeout.Infinite); + _timer?.Change(pingInterval, Timeout.Infinite); } } } diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index f75442ac..67fe360e 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -42,25 +42,11 @@ public async Task Connect(ClientOptions options) _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await ConnectAsync(_socket, singleNode.Uri.Host, singleNode.Uri.Port); - // unfortunately does not worl under linux - // SetKeepAlive(true, 1000, 100); + _stream = new NetworkStream(_socket, true); options.LogWriter?.WriteLine("Socket connection established."); } - private void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval) - { - int size = Marshal.SizeOf(new uint()); - - var inOptionValues = new byte[size * 3]; - - BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0); - BitConverter.GetBytes(keepAliveTime).CopyTo(inOptionValues, size); - BitConverter.GetBytes(keepAliveInterval).CopyTo(inOptionValues, size * 2); - - _socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); - } - public void Write(byte[] buffer, int offset, int count) { CheckConnectionStatus(); @@ -125,11 +111,7 @@ public bool IsConnected() return false; } - try - { - return !(_socket.Poll(1, SelectMode.SelectRead) && _socket.Available == 0); - } - catch (SocketException) { return false; } + return true; } private void CheckConnectionStatus() From e2f4d3211b37bc887436ce873406618161e3876b Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 2 Dec 2016 18:01:41 +0300 Subject: [PATCH 14/34] fix reconnect - introduce faulted state --- src/tarantool.client/ILogicalConnection.cs | 2 + src/tarantool.client/LogicalConnection.cs | 38 ++++++++++++++++--- .../NetworkStreamPhysicalConnection.cs | 28 +++++++++++++- src/tarantool.client/ResponseReader.cs | 30 +++++++-------- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index 4a7b6238..4b8f5d19 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -19,5 +19,7 @@ Task> SendRequest(TRequest reques TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream); IEnumerable> PopAllResponseCompletionSources(); + + void CancelAllPendingRequests(); } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 49f29a64..63924f45 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -29,6 +29,8 @@ internal class LogicalConnection : ILogicalConnection private readonly ILog _logWriter; + private bool isFaulted = false; + public LogicalConnection(ClientOptions options, INetworkStreamPhysicalConnection physicalConnection) { _msgPackContext = options.MsgPackContext; @@ -79,6 +81,18 @@ public IEnumerable> PopAllResponseCompletionS return result; } + public void CancelAllPendingRequests() + { + this.isFaulted = true; + + _logWriter?.WriteLine("Cancelling all pending requests..."); + var responses = PopAllResponseCompletionSources(); + foreach (var response in responses) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + } + private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { @@ -90,13 +104,22 @@ private async Task SendRequestImpl(TRequest requ long headerLength; var headerBuffer = CreateAndSerializeBuffer(request, requestId, bodyBuffer, out headerLength); - lock (_physicalConnection) + try { - _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); - _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int) headerLength); + lock (_physicalConnection) + { + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); + _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); - _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); - _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); + _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + } + } + catch (Exception ex) + { + CancelAllPendingRequests(); + _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); + throw; } try @@ -146,6 +169,11 @@ private RequestId GetRequestId() private Task GetResponseTask(RequestId requestId) { + if (!this.isFaulted) + { + throw new InvalidOperationException("Connection is in faulted state"); + } + var tcs = new TaskCompletionSource(); if (!_pendingRequests.TryAdd(requestId, tcs)) { diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 369b6b39..31e1b1e2 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -13,6 +13,8 @@ namespace ProGaudi.Tarantool.Client { + using System.Runtime.InteropServices; + internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnection { private Stream _stream; @@ -34,10 +36,24 @@ public async Task Connect(ClientOptions options) _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await ConnectAsync(_socket, singleNode.Uri.Host, singleNode.Uri.Port); + SetKeepAlive(true, 1000, 100); _stream = new NetworkStream(_socket, true); options.LogWriter?.WriteLine("Socket connection established."); } + private void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval) + { + int size = Marshal.SizeOf(new uint()); + + var inOptionValues = new byte[size * 3]; + + BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0); + BitConverter.GetBytes(keepAliveTime).CopyTo(inOptionValues, size); + BitConverter.GetBytes(keepAliveInterval).CopyTo(inOptionValues, size * 2); + + this._socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); + } + public void Write(byte[] buffer, int offset, int count) { CheckConnectionStatus(); @@ -47,7 +63,6 @@ public void Write(byte[] buffer, int offset, int count) public async Task Flush() { CheckConnectionStatus(); - await _stream.FlushAsync(); } @@ -96,8 +111,19 @@ private static Task ConnectAsync(Socket socket, string host, int port) } #endif + public bool IsConnected() + { + try + { + return !(this._socket.Poll(1, SelectMode.SelectRead) && this._socket.Available == 0); + } + catch (SocketException) { return false; } + } + private void CheckConnectionStatus() { + var fc2 = this.IsConnected(); + if (_stream == null) { throw ExceptionHelper.NotConnected(); diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index d0510f65..304ad810 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -51,16 +51,24 @@ private void EndReading(Task readWork) { if (!_disposed) { - var readBytesCount = readWork.Result; - _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); - - if (ProcessReadBytes(readBytesCount)) + if (readWork.IsCompleted) { - BeginReading(); + var readBytesCount = readWork.Result; + _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); + + if (ProcessReadBytes(readBytesCount)) + { + BeginReading(); + } + else + { + _logicalConnection.CancelAllPendingRequests(); + } } else { - CancelAllPendingRequests(); + _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); + _logicalConnection.CancelAllPendingRequests(); } } else @@ -69,16 +77,6 @@ private void EndReading(Task readWork) } } - private void CancelAllPendingRequests() - { - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); - var responses = _logicalConnection.PopAllResponseCompletionSources(); - foreach (var response in responses) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); - } - } - private bool ProcessReadBytes(int readBytesCount) { if (readBytesCount <= 0) From 05f7d20b13d63df6d1539674b36fd9d63ceccc33 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 2 Dec 2016 19:09:51 +0300 Subject: [PATCH 15/34] phys connection and resp reader into logical conn --- src/tarantool.client/Box.cs | 30 ++-------------- src/tarantool.client/ILogicalConnection.cs | 8 ++++- src/tarantool.client/LogicalConnection.cs | 42 ++++++++++++++++++++-- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index 97a7185e..e4c7e224 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -15,40 +15,17 @@ public class Box : IBox private readonly ILogicalConnection _logicalConnection; - private readonly IResponseReader _responseReader; - - private readonly INetworkStreamPhysicalConnection _physicalConnection; - public Box(ClientOptions options) { _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _physicalConnection = new NetworkStreamPhysicalConnection(); - _logicalConnection = new LogicalConnection(options, _physicalConnection); - _responseReader = new ResponseReader(_logicalConnection, options, _physicalConnection); + _logicalConnection = new LogicalConnection(options) { GreetingFunc = this.LoginIfNotGuest }; } public async Task Connect() { - await _physicalConnection.Connect(_clientOptions); - - var greetingsResponseBytes = new byte[128]; - var readCount = await _physicalConnection.ReadAsync(greetingsResponseBytes, 0, greetingsResponseBytes.Length); - if (readCount != greetingsResponseBytes.Length) - { - throw ExceptionHelper.UnexpectedGreetingBytesCount(readCount); - } - - var greetings = new GreetingsResponse(greetingsResponseBytes); - - _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); - - _responseReader.BeginReading(); - - _clientOptions.LogWriter?.WriteLine("Server responses reading started."); - - await LoginIfNotGuest(greetings); + await _logicalConnection.Connect(); } public static async Task Connect(string replicationSource) @@ -72,8 +49,7 @@ public void Dispose() { _clientOptions.LogWriter?.WriteLine("Box is disposing..."); _clientOptions.LogWriter?.Flush(); - _responseReader.Dispose(); - _physicalConnection.Dispose(); + _logicalConnection.Dispose(); } public ISchema GetSchema() diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index 4b8f5d19..7840f5c5 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -8,8 +8,14 @@ namespace ProGaudi.Tarantool.Client { - public interface ILogicalConnection + using System; + + public interface ILogicalConnection : IDisposable { + Func GreetingFunc { get; set; } + + Task Connect(); + Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest; diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 63924f45..bf6702e6 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -20,7 +20,11 @@ internal class LogicalConnection : ILogicalConnection { private readonly MsgPackContext _msgPackContext; - private readonly INetworkStreamPhysicalConnection _physicalConnection; + private readonly ClientOptions _clientOptions; + + private INetworkStreamPhysicalConnection _physicalConnection; + + private IResponseReader _responseReader; private long _currentRequestId; @@ -31,11 +35,43 @@ internal class LogicalConnection : ILogicalConnection private bool isFaulted = false; - public LogicalConnection(ClientOptions options, INetworkStreamPhysicalConnection physicalConnection) + public Func GreetingFunc { get; set; } + + public LogicalConnection(ClientOptions options) { + _clientOptions = options; _msgPackContext = options.MsgPackContext; _logWriter = options.LogWriter; - _physicalConnection = physicalConnection; + } + + public void Dispose() + { + _responseReader?.Dispose(); + _physicalConnection?.Dispose(); + } + + public async Task Connect() + { + _physicalConnection = new NetworkStreamPhysicalConnection(); + await _physicalConnection.Connect(_clientOptions); + + var greetingsResponseBytes = new byte[128]; + var readCount = await _physicalConnection.ReadAsync(greetingsResponseBytes, 0, greetingsResponseBytes.Length); + if (readCount != greetingsResponseBytes.Length) + { + throw ExceptionHelper.UnexpectedGreetingBytesCount(readCount); + } + + var greetings = new GreetingsResponse(greetingsResponseBytes); + + _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); + + _responseReader = new ResponseReader(this, _clientOptions, _physicalConnection); + _responseReader.BeginReading(); + + _clientOptions.LogWriter?.WriteLine("Server responses reading started."); + + await GreetingFunc(greetings); } public async Task SendRequestWithEmptyResponse(TRequest request) From e3d9fa756d43876e7e58a506da9affbeb5a76dba Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 2 Dec 2016 19:35:18 +0300 Subject: [PATCH 16/34] requests queue and faulted state inside resp reader --- src/tarantool.client/Box.cs | 2 +- src/tarantool.client/ILogicalConnection.cs | 8 +-- src/tarantool.client/IResponseReader.cs | 9 +++ src/tarantool.client/LogicalConnection.cs | 63 +++---------------- .../NetworkStreamPhysicalConnection.cs | 8 ++- src/tarantool.client/ResponseReader.cs | 63 +++++++++++++++++-- 6 files changed, 82 insertions(+), 71 deletions(-) diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index e4c7e224..218029c6 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -20,7 +20,7 @@ public Box(ClientOptions options) _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _logicalConnection = new LogicalConnection(options) { GreetingFunc = this.LoginIfNotGuest }; + _logicalConnection = new LogicalConnection(options) { _greetingFunc = this.LoginIfNotGuest }; } public async Task Connect() diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index 7840f5c5..a9e5e863 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -12,7 +12,7 @@ namespace ProGaudi.Tarantool.Client public interface ILogicalConnection : IDisposable { - Func GreetingFunc { get; set; } + Func _greetingFunc { get; set; } Task Connect(); @@ -21,11 +21,5 @@ Task SendRequestWithEmptyResponse(TRequest request) Task> SendRequest(TRequest request) where TRequest : IRequest; - - TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream); - - IEnumerable> PopAllResponseCompletionSources(); - - void CancelAllPendingRequests(); } } \ No newline at end of file diff --git a/src/tarantool.client/IResponseReader.cs b/src/tarantool.client/IResponseReader.cs index 092c78c4..151f97c7 100644 --- a/src/tarantool.client/IResponseReader.cs +++ b/src/tarantool.client/IResponseReader.cs @@ -2,8 +2,17 @@ namespace ProGaudi.Tarantool.Client { + using System.IO; + using System.Threading.Tasks; + + using ProGaudi.Tarantool.Client.Model; + public interface IResponseReader : IDisposable { void BeginReading(); + + Task GetResponseTask(RequestId requestId); + + void FaultedState(); } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index bf6702e6..f7aac841 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -28,14 +28,9 @@ internal class LogicalConnection : ILogicalConnection private long _currentRequestId; - private readonly ConcurrentDictionary> _pendingRequests = - new ConcurrentDictionary>(); - private readonly ILog _logWriter; - private bool isFaulted = false; - - public Func GreetingFunc { get; set; } + public Func _greetingFunc { get; set; } public LogicalConnection(ClientOptions options) { @@ -66,14 +61,16 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); - _responseReader = new ResponseReader(this, _clientOptions, _physicalConnection); + _responseReader = new ResponseReader(_clientOptions, _physicalConnection); _responseReader.BeginReading(); _clientOptions.LogWriter?.WriteLine("Server responses reading started."); - await GreetingFunc(greetings); + await this._greetingFunc(greetings); } + // TODO: reconnection func here with dropping/disposing old _physicalConnection and _responseReader + public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest { @@ -86,15 +83,6 @@ public async Task> SendRequest(TR return await SendRequestImpl>(request); } - public TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream) - { - TaskCompletionSource request; - - return _pendingRequests.TryRemove(requestId, out request) - ? request - : null; - } - public static byte[] ReadFully(Stream input) { input.Position = 0; @@ -110,32 +98,15 @@ public static byte[] ReadFully(Stream input) } } - public IEnumerable> PopAllResponseCompletionSources() - { - var result = _pendingRequests.Values.ToArray(); - _pendingRequests.Clear(); - return result; - } - - public void CancelAllPendingRequests() - { - this.isFaulted = true; - - _logWriter?.WriteLine("Cancelling all pending requests..."); - var responses = PopAllResponseCompletionSources(); - foreach (var response in responses) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); - } - } - private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { + // TODO: detect disconnects and reconnect here + var bodyBuffer = MsgPackSerializer.Serialize(request, _msgPackContext); var requestId = GetRequestId(); - var responseTask = GetResponseTask(requestId); + var responseTask = _responseReader.GetResponseTask(requestId); long headerLength; var headerBuffer = CreateAndSerializeBuffer(request, requestId, bodyBuffer, out headerLength); @@ -153,7 +124,7 @@ private async Task SendRequestImpl(TRequest requ } catch (Exception ex) { - CancelAllPendingRequests(); + _responseReader.FaultedState(); _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); throw; } @@ -202,21 +173,5 @@ private RequestId GetRequestId() var requestId = Interlocked.Increment(ref _currentRequestId); return (RequestId) (ulong) requestId; } - - private Task GetResponseTask(RequestId requestId) - { - if (!this.isFaulted) - { - throw new InvalidOperationException("Connection is in faulted state"); - } - - var tcs = new TaskCompletionSource(); - if (!_pendingRequests.TryAdd(requestId, tcs)) - { - throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); - } - - return tcs.Task; - } } } \ No newline at end of file diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 31e1b1e2..f4faf30f 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -51,7 +51,7 @@ private void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval) BitConverter.GetBytes(keepAliveTime).CopyTo(inOptionValues, size); BitConverter.GetBytes(keepAliveInterval).CopyTo(inOptionValues, size * 2); - this._socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); + _socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); } public void Write(byte[] buffer, int offset, int count) @@ -111,18 +111,20 @@ private static Task ConnectAsync(Socket socket, string host, int port) } #endif + // TODO: make this working together inside and outside + public bool IsConnected() { try { - return !(this._socket.Poll(1, SelectMode.SelectRead) && this._socket.Available == 0); + return !(_socket.Poll(1, SelectMode.SelectRead) && _socket.Available == 0); } catch (SocketException) { return false; } } private void CheckConnectionStatus() { - var fc2 = this.IsConnected(); + var fc2 = IsConnected(); if (_stream == null) { diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 304ad810..dcfcf8d9 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -13,11 +13,18 @@ namespace ProGaudi.Tarantool.Client { + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + internal class ResponseReader : IResponseReader { private readonly INetworkStreamPhysicalConnection _physicalConnection; - private readonly ILogicalConnection _logicalConnection; + private readonly ConcurrentDictionary> _pendingRequests = + new ConcurrentDictionary>(); + + private bool _faulted; private readonly ClientOptions _clientOptions; @@ -29,14 +36,58 @@ internal class ResponseReader : IResponseReader private bool _disposed; - public ResponseReader(ILogicalConnection logicalConnection, ClientOptions clientOptions, INetworkStreamPhysicalConnection physicalConnection) + public ResponseReader(ClientOptions clientOptions, INetworkStreamPhysicalConnection physicalConnection) { _physicalConnection = physicalConnection; - _logicalConnection = logicalConnection; _clientOptions = clientOptions; _buffer = new byte[clientOptions.ConnectionOptions.ReadStreamBufferSize]; } + public Task GetResponseTask(RequestId requestId) + { + if (!this._faulted) + { + throw new InvalidOperationException("Connection is in faulted state"); + } + + var tcs = new TaskCompletionSource(); + if (!_pendingRequests.TryAdd(requestId, tcs)) + { + throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); + } + + return tcs.Task; + } + + private TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream) + { + TaskCompletionSource request; + + return _pendingRequests.TryRemove(requestId, out request) + ? request + : null; + } + + private IEnumerable> PopAllResponseCompletionSources() + { + var result = _pendingRequests.Values.ToArray(); + _pendingRequests.Clear(); + return result; + } + + public void FaultedState() + { + this._faulted = true; + + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); + + var responses = PopAllResponseCompletionSources(); + foreach (var response in responses) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + } + public void BeginReading() { var freeBufferSpace = EnsureSpaceAndComputeBytesToRead(); @@ -62,13 +113,13 @@ private void EndReading(Task readWork) } else { - _logicalConnection.CancelAllPendingRequests(); + FaultedState(); } } else { _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - _logicalConnection.CancelAllPendingRequests(); + FaultedState(); } } else @@ -137,7 +188,7 @@ private void MatchResult(byte[] result) { var resultStream = new MemoryStream(result); var header= MsgPackSerializer.Deserialize(resultStream, _clientOptions.MsgPackContext); - var tcs = _logicalConnection.PopResponseCompletionSource(header.RequestId, resultStream); + var tcs = PopResponseCompletionSource(header.RequestId, resultStream); if (tcs == null) { From 85931224c2a6da145526c01af027696f07811b44 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 19:17:41 +0300 Subject: [PATCH 17/34] more distributed arcvitecture --- src/tarantool.client/Box.cs | 23 +---- src/tarantool.client/ILogicalConnection.cs | 6 +- .../INetworkStreamPhysicalConnection.cs | 1 + src/tarantool.client/LogicalConnection.cs | 46 ++++++---- .../LogicalConnectionWrapper.cs | 88 +++++++++++++++++++ .../NetworkStreamPhysicalConnection.cs | 16 ++-- src/tarantool.client/RequestIdCounter.cs | 17 ++++ src/tarantool.client/ResponseReader.cs | 68 ++++++++------ src/tarantool.client/Utils/ExceptionHelper.cs | 5 ++ 9 files changed, 191 insertions(+), 79 deletions(-) create mode 100644 src/tarantool.client/LogicalConnectionWrapper.cs create mode 100644 src/tarantool.client/RequestIdCounter.cs diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index 218029c6..c7c7613b 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -1,11 +1,8 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; -using ProGaudi.Tarantool.Client.Utils; namespace ProGaudi.Tarantool.Client { @@ -20,7 +17,7 @@ public Box(ClientOptions options) _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _logicalConnection = new LogicalConnection(options) { _greetingFunc = this.LoginIfNotGuest }; + _logicalConnection = new LogicalConnectionWrapper(options); } public async Task Connect() @@ -117,21 +114,5 @@ public Task> Eval(string expression) { return Eval(expression, TarantoolTuple.Empty); } - - private async Task LoginIfNotGuest(GreetingsResponse greetings) - { - var singleNode = _clientOptions.ConnectionOptions.Nodes.Single(); - - if (string.IsNullOrEmpty(singleNode.Uri.UserName)) - { - _clientOptions.LogWriter?.WriteLine("Guest mode, no authentication attempt."); - return; - } - - var authenticateRequest = AuthenticationRequest.Create(greetings, singleNode.Uri); - - await _logicalConnection.SendRequestWithEmptyResponse(authenticateRequest); - _clientOptions.LogWriter?.WriteLine($"Authentication request send: {authenticateRequest}"); - } } } \ No newline at end of file diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index a9e5e863..f942efb4 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -12,10 +12,10 @@ namespace ProGaudi.Tarantool.Client public interface ILogicalConnection : IDisposable { - Func _greetingFunc { get; set; } - Task Connect(); - + + bool IsConnected(); + Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest; diff --git a/src/tarantool.client/INetworkStreamPhysicalConnection.cs b/src/tarantool.client/INetworkStreamPhysicalConnection.cs index 32842f6b..bf2122d2 100644 --- a/src/tarantool.client/INetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/INetworkStreamPhysicalConnection.cs @@ -8,6 +8,7 @@ public interface INetworkStreamPhysicalConnection : IDisposable { Task Connect(ClientOptions options); Task Flush(); + bool IsConnected(); Task ReadAsync(byte[] buffer, int offset, int count); void Write(byte[] buffer, int offset, int count); } diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index f7aac841..e829da7f 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -22,27 +22,26 @@ internal class LogicalConnection : ILogicalConnection private readonly ClientOptions _clientOptions; + private readonly RequestIdCounter _requestIdCounter; + private INetworkStreamPhysicalConnection _physicalConnection; private IResponseReader _responseReader; - private long _currentRequestId; - private readonly ILog _logWriter; - public Func _greetingFunc { get; set; } - - public LogicalConnection(ClientOptions options) + public LogicalConnection(ClientOptions options, RequestIdCounter requestIdCounter) { _clientOptions = options; + _requestIdCounter = requestIdCounter; _msgPackContext = options.MsgPackContext; _logWriter = options.LogWriter; } public void Dispose() { - _responseReader?.Dispose(); - _physicalConnection?.Dispose(); + Interlocked.Exchange(ref _responseReader, null)?.Dispose(); + Interlocked.Exchange(ref _physicalConnection, null)?.Dispose(); } public async Task Connect() @@ -66,10 +65,29 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine("Server responses reading started."); - await this._greetingFunc(greetings); + await LoginIfNotGuest(greetings); } - // TODO: reconnection func here with dropping/disposing old _physicalConnection and _responseReader + public bool IsConnected() + { + return _physicalConnection?.IsConnected() != null; + } + + private async Task LoginIfNotGuest(GreetingsResponse greetings) + { + var singleNode = _clientOptions.ConnectionOptions.Nodes.Single(); + + if (string.IsNullOrEmpty(singleNode.Uri.UserName)) + { + _clientOptions.LogWriter?.WriteLine("Guest mode, no authentication attempt."); + return; + } + + var authenticateRequest = AuthenticationRequest.Create(greetings, singleNode.Uri); + + await SendRequestWithEmptyResponse(authenticateRequest); + _clientOptions.LogWriter?.WriteLine($"Authentication request send: {authenticateRequest}"); + } public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest @@ -101,11 +119,9 @@ public static byte[] ReadFully(Stream input) private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { - // TODO: detect disconnects and reconnect here - var bodyBuffer = MsgPackSerializer.Serialize(request, _msgPackContext); - var requestId = GetRequestId(); + var requestId = _requestIdCounter.GetRequestId(); var responseTask = _responseReader.GetResponseTask(requestId); long headerLength; @@ -167,11 +183,5 @@ private byte[] CreateAndSerializeBuffer( MsgPackSerializer.Serialize(packetLength, stream, _msgPackContext); return packetSizeBuffer; } - - private RequestId GetRequestId() - { - var requestId = Interlocked.Increment(ref _currentRequestId); - return (RequestId) (ulong) requestId; - } } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs new file mode 100644 index 00000000..7810686f --- /dev/null +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading.Tasks; +using ProGaudi.Tarantool.Client.Model.Requests; +using ProGaudi.Tarantool.Client.Model.Responses; + +namespace ProGaudi.Tarantool.Client +{ + using System.Threading; + + using ProGaudi.Tarantool.Client.Model; + using ProGaudi.Tarantool.Client.Utils; + + public class LogicalConnectionWrapper : ILogicalConnection + { + private readonly ClientOptions _clientOptions; + + private readonly RequestIdCounter _requestIdCounter = new RequestIdCounter(); + + private LogicalConnection _droppableLogicalConnection; + + private readonly ManualResetEvent _connected = new ManualResetEvent(false); + + private const int connectionTimeout = 1000; + + public LogicalConnectionWrapper(ClientOptions options) + { + _clientOptions = options; + } + + public void Dispose() + { + Interlocked.Exchange(ref _droppableLogicalConnection, null)?.Dispose(); + } + + public async Task Connect() + { + _connected.Reset(); + + var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); + await _newConnection.Connect(); + Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); + + _connected.Set(); + } + + public bool IsConnected() + { + return _droppableLogicalConnection?.IsConnected() != null; + } + + private async Task EnsureConnection() + { + if (_connected.WaitOne() && IsConnected()) + { + return; + } + + if (!Monitor.TryEnter(this, connectionTimeout)) + { + throw ExceptionHelper.NotConnected(); + } + + try + { + if (!IsConnected()) + { + await Connect(); + } + } + finally + { + Monitor.Exit(this); + } + } + + public async Task> SendRequest(TRequest request) where TRequest : IRequest + { + await EnsureConnection(); + return await _droppableLogicalConnection.SendRequest(request); + } + + public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest + { + await EnsureConnection(); + await _droppableLogicalConnection.SendRequestWithEmptyResponse(request); + } + } +} diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index f4faf30f..8e4fcee8 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -14,6 +14,7 @@ namespace ProGaudi.Tarantool.Client { using System.Runtime.InteropServices; + using System.Threading; internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnection { @@ -26,7 +27,8 @@ internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnectio public void Dispose() { _disposed = true; - _stream?.Dispose(); + Interlocked.Exchange(ref _stream, null)?.Dispose(); + Interlocked.Exchange(ref _socket, null)?.Dispose(); } public async Task Connect(ClientOptions options) @@ -111,8 +113,6 @@ private static Task ConnectAsync(Socket socket, string host, int port) } #endif - // TODO: make this working together inside and outside - public bool IsConnected() { try @@ -124,16 +124,14 @@ public bool IsConnected() private void CheckConnectionStatus() { - var fc2 = IsConnected(); - - if (_stream == null) + if (_disposed) { - throw ExceptionHelper.NotConnected(); + throw new ObjectDisposedException(nameof(NetworkStreamPhysicalConnection)); } - if (_disposed) + if (!IsConnected() || _stream == null) { - throw new ObjectDisposedException(nameof(NetworkStreamPhysicalConnection)); + throw ExceptionHelper.NotConnected(); } } } diff --git a/src/tarantool.client/RequestIdCounter.cs b/src/tarantool.client/RequestIdCounter.cs new file mode 100644 index 00000000..3c1d99f3 --- /dev/null +++ b/src/tarantool.client/RequestIdCounter.cs @@ -0,0 +1,17 @@ +namespace ProGaudi.Tarantool.Client +{ + using System.Threading; + + using ProGaudi.Tarantool.Client.Model; + + public class RequestIdCounter + { + private long _currentRequestId; + + public RequestId GetRequestId() + { + var requestId = Interlocked.Increment(ref _currentRequestId); + return (RequestId)(ulong)requestId; + } + } +} diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index dcfcf8d9..a78f05b7 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -16,16 +16,15 @@ namespace ProGaudi.Tarantool.Client using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; + using System.Threading; internal class ResponseReader : IResponseReader { private readonly INetworkStreamPhysicalConnection _physicalConnection; - private readonly ConcurrentDictionary> _pendingRequests = + private ConcurrentDictionary> _pendingRequests = new ConcurrentDictionary>(); - private bool _faulted; - private readonly ClientOptions _clientOptions; private byte[] _buffer; @@ -45,46 +44,58 @@ public ResponseReader(ClientOptions clientOptions, INetworkStreamPhysicalConnect public Task GetResponseTask(RequestId requestId) { - if (!this._faulted) + lock (this) { - throw new InvalidOperationException("Connection is in faulted state"); - } + if (_pendingRequests == null) + { + throw ExceptionHelper.FaultedState(); + } - var tcs = new TaskCompletionSource(); - if (!_pendingRequests.TryAdd(requestId, tcs)) - { - throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); - } + var tcs = new TaskCompletionSource(); + if (!_pendingRequests.TryAdd(requestId, tcs)) + { + throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); + } - return tcs.Task; + return tcs.Task; + } } private TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream) { - TaskCompletionSource request; + lock (this) + { + if (_pendingRequests == null) + { + throw ExceptionHelper.FaultedState(); + } - return _pendingRequests.TryRemove(requestId, out request) - ? request - : null; - } + TaskCompletionSource request; - private IEnumerable> PopAllResponseCompletionSources() - { - var result = _pendingRequests.Values.ToArray(); - _pendingRequests.Clear(); - return result; + return _pendingRequests.TryRemove(requestId, out request) + ? request + : null; + } } public void FaultedState() { - this._faulted = true; + lock (this) + { + var _pendingRequestsLocal = Interlocked.Exchange(ref _pendingRequests, null); + if (_pendingRequestsLocal == null) + { + return; + } - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); - var responses = PopAllResponseCompletionSources(); - foreach (var response in responses) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); + foreach (var response in _pendingRequestsLocal.Values) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + + _pendingRequestsLocal.Clear(); } } @@ -292,6 +303,7 @@ private int EnsureSpaceAndComputeBytesToRead() public void Dispose() { _disposed = true; + FaultedState(); } } } \ No newline at end of file diff --git a/src/tarantool.client/Utils/ExceptionHelper.cs b/src/tarantool.client/Utils/ExceptionHelper.cs index bef5714e..9770204a 100644 --- a/src/tarantool.client/Utils/ExceptionHelper.cs +++ b/src/tarantool.client/Utils/ExceptionHelper.cs @@ -41,6 +41,11 @@ public static Exception NotConnected() return new InvalidOperationException("Can't perform operation. Looks like we are not connected to tarantool. Call 'Connect' method before calling any other operations."); } + public static Exception FaultedState() + { + return new InvalidOperationException("Connection is in faulted state"); + } + public static ArgumentException TarantoolError(ResponseHeader header, ErrorResponse errorResponse) { var detailedMessage = GetDetailedTarantoolMessage(header.Code); From a2c4c9ac66af04e22d3e31aef903844f12559043 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 20:06:30 +0300 Subject: [PATCH 18/34] check resp reader fault state for connection alive --- src/tarantool.client/IResponseReader.cs | 4 +++- src/tarantool.client/LogicalConnection.cs | 4 ++-- src/tarantool.client/ResponseReader.cs | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/tarantool.client/IResponseReader.cs b/src/tarantool.client/IResponseReader.cs index 151f97c7..73c5c8f7 100644 --- a/src/tarantool.client/IResponseReader.cs +++ b/src/tarantool.client/IResponseReader.cs @@ -13,6 +13,8 @@ public interface IResponseReader : IDisposable Task GetResponseTask(RequestId requestId); - void FaultedState(); + void SetFaultedState(); + + bool IsFaultedState { get; } } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index e829da7f..3c2ade86 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -70,7 +70,7 @@ public async Task Connect() public bool IsConnected() { - return _physicalConnection?.IsConnected() != null; + return !(this._responseReader?.IsFaultedState == null) && _physicalConnection?.IsConnected() != null; } private async Task LoginIfNotGuest(GreetingsResponse greetings) @@ -140,7 +140,7 @@ private async Task SendRequestImpl(TRequest requ } catch (Exception ex) { - _responseReader.FaultedState(); + _responseReader.SetFaultedState(); _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); throw; } diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index a78f05b7..d3491148 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -78,7 +78,7 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId } } - public void FaultedState() + public void SetFaultedState() { lock (this) { @@ -99,6 +99,8 @@ public void FaultedState() } } + public bool IsFaultedState => _pendingRequests == null; + public void BeginReading() { var freeBufferSpace = EnsureSpaceAndComputeBytesToRead(); @@ -124,13 +126,13 @@ private void EndReading(Task readWork) } else { - FaultedState(); + this.SetFaultedState(); } } else { _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - FaultedState(); + this.SetFaultedState(); } } else @@ -303,7 +305,7 @@ private int EnsureSpaceAndComputeBytesToRead() public void Dispose() { _disposed = true; - FaultedState(); + this.SetFaultedState(); } } } \ No newline at end of file From 8651c7e0b66916ef39b5a338621debaf7268c7ba Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 20:40:51 +0300 Subject: [PATCH 19/34] more log records --- src/tarantool.client/LogicalConnectionWrapper.cs | 9 +++++++++ src/tarantool.client/ResponseReader.cs | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs index 7810686f..aeefe304 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -34,6 +34,8 @@ public void Dispose() public async Task Connect() { + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connecting..."); + _connected.Reset(); var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); @@ -41,6 +43,8 @@ public async Task Connect() Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); _connected.Set(); + + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connected..."); } public bool IsConnected() @@ -55,8 +59,11 @@ private async Task EnsureConnection() return; } + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection lost, wait for reconnect..."); + if (!Monitor.TryEnter(this, connectionTimeout)) { + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Failed to get lock for reconnect"); throw ExceptionHelper.NotConnected(); } @@ -66,6 +73,8 @@ private async Task EnsureConnection() { await Connect(); } + + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection reacquired"); } finally { diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index d3491148..8a43f622 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -48,6 +48,7 @@ public Task GetResponseTask(RequestId requestId) { if (_pendingRequests == null) { + _clientOptions.LogWriter?.WriteLine($"{nameof(GetResponseTask)}: Already in faulted state..."); throw ExceptionHelper.FaultedState(); } @@ -67,6 +68,7 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId { if (_pendingRequests == null) { + _clientOptions.LogWriter?.WriteLine($"{nameof(PopResponseCompletionSource)}: Already in faulted state..."); throw ExceptionHelper.FaultedState(); } @@ -88,7 +90,7 @@ public void SetFaultedState() return; } - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests..."); + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests and setting faulted state..."); foreach (var response in _pendingRequestsLocal.Values) { From f919ed5cd599cf78cf5706cb67ce845b00c21e1c Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 21:28:28 +0300 Subject: [PATCH 20/34] some fixes --- src/tarantool.client/LogicalConnection.cs | 2 +- src/tarantool.client/LogicalConnectionWrapper.cs | 4 ++-- src/tarantool.client/ResponseReader.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 3c2ade86..17838c59 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -70,7 +70,7 @@ public async Task Connect() public bool IsConnected() { - return !(this._responseReader?.IsFaultedState == null) && _physicalConnection?.IsConnected() != null; + return !(_responseReader?.IsFaultedState ?? true) && (_physicalConnection?.IsConnected() ?? false); } private async Task LoginIfNotGuest(GreetingsResponse greetings) diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs index aeefe304..2090c1cf 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -49,12 +49,12 @@ public async Task Connect() public bool IsConnected() { - return _droppableLogicalConnection?.IsConnected() != null; + return _droppableLogicalConnection?.IsConnected() ?? false; } private async Task EnsureConnection() { - if (_connected.WaitOne() && IsConnected()) + if (_connected.WaitOne(connectionTimeout) && IsConnected()) { return; } diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 8a43f622..6cc9624f 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -128,13 +128,13 @@ private void EndReading(Task readWork) } else { - this.SetFaultedState(); + SetFaultedState(); } } else { _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - this.SetFaultedState(); + SetFaultedState(); } } else From 1818693470d9c44dc6f51b6563c11af14ff05889 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 5 Dec 2016 22:30:25 +0300 Subject: [PATCH 21/34] some fixes --- src/tarantool.client/LogicalConnectionWrapper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionWrapper.cs index 2090c1cf..25ccf188 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionWrapper.cs @@ -20,6 +20,8 @@ public class LogicalConnectionWrapper : ILogicalConnection private readonly ManualResetEvent _connected = new ManualResetEvent(false); + private readonly AutoResetEvent _reconnectAvailable = new AutoResetEvent(true); + private const int connectionTimeout = 1000; public LogicalConnectionWrapper(ClientOptions options) @@ -61,7 +63,7 @@ private async Task EnsureConnection() _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection lost, wait for reconnect..."); - if (!Monitor.TryEnter(this, connectionTimeout)) + if (!_reconnectAvailable.WaitOne(connectionTimeout)) { _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Failed to get lock for reconnect"); throw ExceptionHelper.NotConnected(); @@ -78,7 +80,7 @@ private async Task EnsureConnection() } finally { - Monitor.Exit(this); + _reconnectAvailable.Set(); } } From e327efbd7e85d59c6306e9ec2a9fc9d7666fc662 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Thu, 8 Dec 2016 13:55:41 +0300 Subject: [PATCH 22/34] disposed/state redesign --- src/tarantool.client/ILogicalConnection.cs | 6 +- src/tarantool.client/IResponseReader.cs | 4 +- src/tarantool.client/LogicalConnection.cs | 59 +++++++++---- .../NetworkStreamPhysicalConnection.cs | 19 ++-- src/tarantool.client/ResponseReader.cs | 86 ++++++++----------- src/tarantool.client/Utils/ExceptionHelper.cs | 5 -- 6 files changed, 93 insertions(+), 86 deletions(-) diff --git a/src/tarantool.client/ILogicalConnection.cs b/src/tarantool.client/ILogicalConnection.cs index f942efb4..3841690b 100644 --- a/src/tarantool.client/ILogicalConnection.cs +++ b/src/tarantool.client/ILogicalConnection.cs @@ -1,15 +1,11 @@ -using System.Collections.Generic; -using System.IO; +using System; using System.Threading.Tasks; -using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; namespace ProGaudi.Tarantool.Client { - using System; - public interface ILogicalConnection : IDisposable { Task Connect(); diff --git a/src/tarantool.client/IResponseReader.cs b/src/tarantool.client/IResponseReader.cs index 73c5c8f7..dccb103e 100644 --- a/src/tarantool.client/IResponseReader.cs +++ b/src/tarantool.client/IResponseReader.cs @@ -13,8 +13,6 @@ public interface IResponseReader : IDisposable Task GetResponseTask(RequestId requestId); - void SetFaultedState(); - - bool IsFaultedState { get; } + bool IsConnected(); } } \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 17838c59..84af3703 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using ProGaudi.MsgPack.Light; @@ -24,29 +21,43 @@ internal class LogicalConnection : ILogicalConnection private readonly RequestIdCounter _requestIdCounter; - private INetworkStreamPhysicalConnection _physicalConnection; + private readonly INetworkStreamPhysicalConnection _physicalConnection; - private IResponseReader _responseReader; + private readonly IResponseReader _responseReader; private readonly ILog _logWriter; + private bool _disposed; + public LogicalConnection(ClientOptions options, RequestIdCounter requestIdCounter) { _clientOptions = options; _requestIdCounter = requestIdCounter; _msgPackContext = options.MsgPackContext; _logWriter = options.LogWriter; + + _physicalConnection = new NetworkStreamPhysicalConnection(); + _responseReader = new ResponseReader(_clientOptions, _physicalConnection); } public void Dispose() { - Interlocked.Exchange(ref _responseReader, null)?.Dispose(); - Interlocked.Exchange(ref _physicalConnection, null)?.Dispose(); + lock (this) + { + if (_disposed) + { + return; + } + + _disposed = true; + + _responseReader.Dispose(); + _physicalConnection.Dispose(); + } } public async Task Connect() { - _physicalConnection = new NetworkStreamPhysicalConnection(); await _physicalConnection.Connect(_clientOptions); var greetingsResponseBytes = new byte[128]; @@ -60,7 +71,6 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"Greetings received, salt is {Convert.ToBase64String(greetings.Salt)} ."); - _responseReader = new ResponseReader(_clientOptions, _physicalConnection); _responseReader.BeginReading(); _clientOptions.LogWriter?.WriteLine("Server responses reading started."); @@ -70,7 +80,18 @@ public async Task Connect() public bool IsConnected() { - return !(_responseReader?.IsFaultedState ?? true) && (_physicalConnection?.IsConnected() ?? false); + if (_disposed) + { + return false; + } + + if (!_responseReader.IsConnected() || !_physicalConnection.IsConnected()) + { + Dispose(); + return false; + } + + return true; } private async Task LoginIfNotGuest(GreetingsResponse greetings) @@ -119,6 +140,11 @@ public static byte[] ReadFully(Stream input) private async Task SendRequestImpl(TRequest request) where TRequest : IRequest { + if (_disposed) + { + throw new ObjectDisposedException(nameof(LogicalConnection)); + } + var bodyBuffer = MsgPackSerializer.Serialize(request, _msgPackContext); var requestId = _requestIdCounter.GetRequestId(); @@ -129,19 +155,16 @@ private async Task SendRequestImpl(TRequest requ try { - lock (_physicalConnection) - { - _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); - _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); + _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); - _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); - _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); - } + _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); + _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); } catch (Exception ex) { - _responseReader.SetFaultedState(); _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); + Dispose(); throw; } diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 8e4fcee8..9075065c 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Threading.Tasks; using ProGaudi.Tarantool.Client.Model; @@ -13,9 +14,6 @@ namespace ProGaudi.Tarantool.Client { - using System.Runtime.InteropServices; - using System.Threading; - internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnection { private Stream _stream; @@ -26,9 +24,18 @@ internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnectio public void Dispose() { - _disposed = true; - Interlocked.Exchange(ref _stream, null)?.Dispose(); - Interlocked.Exchange(ref _socket, null)?.Dispose(); + lock (this) + { + if (_disposed) + { + return; + } + + _disposed = true; + + _stream?.Dispose(); + _socket?.Dispose(); + } } public async Task Connect(ClientOptions options) diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 6cc9624f..1d75328b 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -1,28 +1,25 @@ using System; +using System.Collections.Concurrent; using System.IO; using System.Text; -using ProGaudi.MsgPack.Light; +using System.Threading.Tasks; + +using JetBrains.Annotations; +using ProGaudi.MsgPack.Light; using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Enums; using ProGaudi.Tarantool.Client.Model.Headers; using ProGaudi.Tarantool.Client.Model.Responses; using ProGaudi.Tarantool.Client.Utils; -using System.Threading.Tasks; -using JetBrains.Annotations; namespace ProGaudi.Tarantool.Client { - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - internal class ResponseReader : IResponseReader { private readonly INetworkStreamPhysicalConnection _physicalConnection; - private ConcurrentDictionary> _pendingRequests = + private readonly ConcurrentDictionary> _pendingRequests = new ConcurrentDictionary>(); private readonly ClientOptions _clientOptions; @@ -46,10 +43,9 @@ public Task GetResponseTask(RequestId requestId) { lock (this) { - if (_pendingRequests == null) + if (_disposed) { - _clientOptions.LogWriter?.WriteLine($"{nameof(GetResponseTask)}: Already in faulted state..."); - throw ExceptionHelper.FaultedState(); + throw new ObjectDisposedException(nameof(ResponseReader)); } var tcs = new TaskCompletionSource(); @@ -66,10 +62,9 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId { lock (this) { - if (_pendingRequests == null) + if (_disposed) { - _clientOptions.LogWriter?.WriteLine($"{nameof(PopResponseCompletionSource)}: Already in faulted state..."); - throw ExceptionHelper.FaultedState(); + throw new ObjectDisposedException(nameof(ResponseReader)); } TaskCompletionSource request; @@ -80,29 +75,6 @@ private TaskCompletionSource PopResponseCompletionSource(RequestId } } - public void SetFaultedState() - { - lock (this) - { - var _pendingRequestsLocal = Interlocked.Exchange(ref _pendingRequests, null); - if (_pendingRequestsLocal == null) - { - return; - } - - _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests and setting faulted state..."); - - foreach (var response in _pendingRequestsLocal.Values) - { - response.SetException(new InvalidOperationException("Can't read from physical connection.")); - } - - _pendingRequestsLocal.Clear(); - } - } - - public bool IsFaultedState => _pendingRequests == null; - public void BeginReading() { var freeBufferSpace = EnsureSpaceAndComputeBytesToRead(); @@ -125,17 +97,12 @@ private void EndReading(Task readWork) if (ProcessReadBytes(readBytesCount)) { BeginReading(); + return; } - else - { - SetFaultedState(); - } - } - else - { - _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - SetFaultedState(); } + + _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); + Dispose(); } else { @@ -304,10 +271,31 @@ private int EnsureSpaceAndComputeBytesToRead() return space; } + public bool IsConnected() + { + return !_disposed; + } + public void Dispose() { - _disposed = true; - this.SetFaultedState(); + lock (this) + { + if (_disposed) + { + return; + } + + _disposed = true; + + _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests and setting faulted state..."); + + foreach (var response in _pendingRequests.Values) + { + response.SetException(new InvalidOperationException("Can't read from physical connection.")); + } + + _pendingRequests.Clear(); + } } } } \ No newline at end of file diff --git a/src/tarantool.client/Utils/ExceptionHelper.cs b/src/tarantool.client/Utils/ExceptionHelper.cs index 9770204a..bef5714e 100644 --- a/src/tarantool.client/Utils/ExceptionHelper.cs +++ b/src/tarantool.client/Utils/ExceptionHelper.cs @@ -41,11 +41,6 @@ public static Exception NotConnected() return new InvalidOperationException("Can't perform operation. Looks like we are not connected to tarantool. Call 'Connect' method before calling any other operations."); } - public static Exception FaultedState() - { - return new InvalidOperationException("Connection is in faulted state"); - } - public static ArgumentException TarantoolError(ResponseHeader header, ErrorResponse errorResponse) { var detailedMessage = GetDetailedTarantoolMessage(header.Code); From 92554bdbc273fca15320c03fe8c61281e2d7532e Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Thu, 8 Dec 2016 14:02:30 +0300 Subject: [PATCH 23/34] task status check fixed --- src/tarantool.client/ResponseReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 1d75328b..9005cea8 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -89,7 +89,7 @@ private void EndReading(Task readWork) { if (!_disposed) { - if (readWork.IsCompleted) + if (readWork.Status == TaskStatus.RanToCompletion) { var readBytesCount = readWork.Result; _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); From 09901fe7653eec3acaf5416a5766c45ea4b25ca2 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 16 Dec 2016 14:44:18 +0300 Subject: [PATCH 24/34] requested fixes --- src/tarantool.client/Box.cs | 2 +- src/tarantool.client/LogicalConnection.cs | 27 +++++++++---------- ...Wrapper.cs => LogicalConnectionManager.cs} | 17 ++++++------ .../NetworkStreamPhysicalConnection.cs | 20 +++++++------- 4 files changed, 33 insertions(+), 33 deletions(-) rename src/tarantool.client/{LogicalConnectionWrapper.cs => LogicalConnectionManager.cs} (86%) diff --git a/src/tarantool.client/Box.cs b/src/tarantool.client/Box.cs index c7c7613b..bd737e13 100644 --- a/src/tarantool.client/Box.cs +++ b/src/tarantool.client/Box.cs @@ -17,7 +17,7 @@ public Box(ClientOptions options) _clientOptions = options; TarantoolConvertersRegistrator.Register(options.MsgPackContext); - _logicalConnection = new LogicalConnectionWrapper(options); + _logicalConnection = new LogicalConnectionManager(options); } public async Task Connect() diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 84af3703..5e8ae1d1 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -42,18 +42,15 @@ public LogicalConnection(ClientOptions options, RequestIdCounter requestIdCounte public void Dispose() { - lock (this) + if (_disposed) { - if (_disposed) - { - return; - } + return; + } - _disposed = true; + _disposed = true; - _responseReader.Dispose(); - _physicalConnection.Dispose(); - } + _responseReader.Dispose(); + _physicalConnection.Dispose(); } public async Task Connect() @@ -87,7 +84,6 @@ public bool IsConnected() if (!_responseReader.IsConnected() || !_physicalConnection.IsConnected()) { - Dispose(); return false; } @@ -155,11 +151,14 @@ private async Task SendRequestImpl(TRequest requ try { - _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); - _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); + lock (_physicalConnection) + { + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); + _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); - _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); - _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); + _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); + } } catch (Exception ex) { diff --git a/src/tarantool.client/LogicalConnectionWrapper.cs b/src/tarantool.client/LogicalConnectionManager.cs similarity index 86% rename from src/tarantool.client/LogicalConnectionWrapper.cs rename to src/tarantool.client/LogicalConnectionManager.cs index 25ccf188..b50ae69c 100644 --- a/src/tarantool.client/LogicalConnectionWrapper.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; @@ -10,7 +9,7 @@ namespace ProGaudi.Tarantool.Client using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Utils; - public class LogicalConnectionWrapper : ILogicalConnection + public class LogicalConnectionManager : ILogicalConnection { private readonly ClientOptions _clientOptions; @@ -24,7 +23,7 @@ public class LogicalConnectionWrapper : ILogicalConnection private const int connectionTimeout = 1000; - public LogicalConnectionWrapper(ClientOptions options) + public LogicalConnectionManager(ClientOptions options) { _clientOptions = options; } @@ -36,7 +35,7 @@ public void Dispose() public async Task Connect() { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connecting..."); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connecting..."); _connected.Reset(); @@ -46,7 +45,7 @@ public async Task Connect() _connected.Set(); - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connected..."); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); } public bool IsConnected() @@ -61,11 +60,11 @@ private async Task EnsureConnection() return; } - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection lost, wait for reconnect..."); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connection lost, wait for reconnect..."); if (!_reconnectAvailable.WaitOne(connectionTimeout)) { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Failed to get lock for reconnect"); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Failed to get lock for reconnect"); throw ExceptionHelper.NotConnected(); } @@ -76,7 +75,7 @@ private async Task EnsureConnection() await Connect(); } - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionWrapper)}: Connection reacquired"); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connection reacquired"); } finally { diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 9075065c..75a0603a 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -24,18 +24,15 @@ internal class NetworkStreamPhysicalConnection : INetworkStreamPhysicalConnectio public void Dispose() { - lock (this) + if (_disposed) { - if (_disposed) - { - return; - } + return; + } - _disposed = true; + _disposed = true; - _stream?.Dispose(); - _socket?.Dispose(); - } + _stream?.Dispose(); + _socket?.Dispose(); } public async Task Connect(ClientOptions options) @@ -122,6 +119,11 @@ private static Task ConnectAsync(Socket socket, string host, int port) public bool IsConnected() { + if (_disposed) + { + return false; + } + try { return !(_socket.Poll(1, SelectMode.SelectRead) && _socket.Available == 0); From 20cac5c8285166645af3797de3622e1766c5a027 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 19 Dec 2016 20:38:32 +0300 Subject: [PATCH 25/34] ping tarantool instead of socket ping, tbd --- .../Converters/PingPacketConverter.cs | 24 +++++++ .../LogicalConnectionManager.cs | 68 +++++++++++++++++-- .../Model/Requests/PingRequest.cs | 9 +++ .../NetworkStreamPhysicalConnection.cs | 3 +- .../TarantoolConvertersRegistrator.cs | 1 + 5 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/tarantool.client/Converters/PingPacketConverter.cs create mode 100644 src/tarantool.client/Model/Requests/PingRequest.cs diff --git a/src/tarantool.client/Converters/PingPacketConverter.cs b/src/tarantool.client/Converters/PingPacketConverter.cs new file mode 100644 index 00000000..114c40c6 --- /dev/null +++ b/src/tarantool.client/Converters/PingPacketConverter.cs @@ -0,0 +1,24 @@ +using System; + +using ProGaudi.MsgPack.Light; + +using ProGaudi.Tarantool.Client.Model.Requests; + +namespace ProGaudi.Tarantool.Client.Converters +{ + internal class PingPacketConverter : IMsgPackConverter + { + public void Initialize(MsgPackContext context) + { + } + + public void Write(PingRequest value, IMsgPackWriter writer) + { + } + + public PingRequest Read(IMsgPackReader reader) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index b50ae69c..c086c559 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -1,14 +1,14 @@ -using System.Threading.Tasks; +using System; +using System.Threading; +using System.Threading.Tasks; + +using ProGaudi.Tarantool.Client.Model; using ProGaudi.Tarantool.Client.Model.Requests; using ProGaudi.Tarantool.Client.Model.Responses; +using ProGaudi.Tarantool.Client.Utils; namespace ProGaudi.Tarantool.Client { - using System.Threading; - - using ProGaudi.Tarantool.Client.Model; - using ProGaudi.Tarantool.Client.Utils; - public class LogicalConnectionManager : ILogicalConnection { private readonly ClientOptions _clientOptions; @@ -21,8 +21,14 @@ public class LogicalConnectionManager : ILogicalConnection private readonly AutoResetEvent _reconnectAvailable = new AutoResetEvent(true); + private Timer _timer; + + private int _disposing; + private const int connectionTimeout = 1000; + private const int connectionPingInterval = 1000; + public LogicalConnectionManager(ClientOptions options) { _clientOptions = options; @@ -30,7 +36,23 @@ public LogicalConnectionManager(ClientOptions options) public void Dispose() { + if (Interlocked.Exchange(ref _disposing, 1) > 0) + { + return; + } + Interlocked.Exchange(ref _droppableLogicalConnection, null)?.Dispose(); + + var savedTimer = Interlocked.Exchange(ref _timer, null); + if (savedTimer != null) + { + using (var timerDisposedEvent = new ManualResetEvent(false)) + { + savedTimer.Dispose(timerDisposedEvent); + // this will guarantee that all callbacks finished + timerDisposedEvent.WaitOne(); + } + } } public async Task Connect() @@ -46,6 +68,40 @@ public async Task Connect() _connected.Set(); _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); + + _timer = new Timer(x => CheckPing(), null, connectionPingInterval, Timeout.Infinite); + } + + private static readonly PingRequest pingRequest = new PingRequest(); + + private void CheckPing() + { + LogicalConnection savedConnection = _droppableLogicalConnection; + + if (savedConnection == null) + { + return; + } + + try + { + Task task = savedConnection.SendRequestWithEmptyResponse(pingRequest); + if (!task.Wait(connectionTimeout) || task.Status != TaskStatus.RanToCompletion) + { + savedConnection.Dispose(); + } + } + catch (AggregateException ae) + { + savedConnection.Dispose(); + } + finally + { + if (_disposing == 0) + { + _timer?.Change(connectionPingInterval, Timeout.Infinite); + } + } } public bool IsConnected() diff --git a/src/tarantool.client/Model/Requests/PingRequest.cs b/src/tarantool.client/Model/Requests/PingRequest.cs new file mode 100644 index 00000000..4c4d0c3a --- /dev/null +++ b/src/tarantool.client/Model/Requests/PingRequest.cs @@ -0,0 +1,9 @@ +using ProGaudi.Tarantool.Client.Model.Enums; + +namespace ProGaudi.Tarantool.Client.Model.Requests +{ + public class PingRequest : IRequest + { + public CommandCode Code => CommandCode.Ping; + } +} \ No newline at end of file diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 75a0603a..f75442ac 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -42,7 +42,8 @@ public async Task Connect(ClientOptions options) _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await ConnectAsync(_socket, singleNode.Uri.Host, singleNode.Uri.Port); - SetKeepAlive(true, 1000, 100); + // unfortunately does not worl under linux + // SetKeepAlive(true, 1000, 100); _stream = new NetworkStream(_socket, true); options.LogWriter?.WriteLine("Socket connection established."); } diff --git a/src/tarantool.client/TarantoolConvertersRegistrator.cs b/src/tarantool.client/TarantoolConvertersRegistrator.cs index 4184b2b9..92c22eb3 100644 --- a/src/tarantool.client/TarantoolConvertersRegistrator.cs +++ b/src/tarantool.client/TarantoolConvertersRegistrator.cs @@ -45,6 +45,7 @@ public static void Register(MsgPackContext context) context.RegisterGenericConverter(typeof(InsertReplacePacketConverter<>)); context.RegisterGenericConverter(typeof(SelectPacketConverter<>)); context.RegisterGenericConverter(typeof(UpsertPacketConverter<>)); + context.RegisterConverter(new PingPacketConverter()); context.RegisterGenericConverter(typeof(TupleConverter<>)); context.RegisterGenericConverter(typeof(TupleConverter<,>)); From 1e10433c5381e2857c1516b68e4b4b1a780242f9 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Tue, 20 Dec 2016 17:35:27 +0300 Subject: [PATCH 26/34] ping tarantool, done --- .../LogicalConnectionManager.cs | 43 +++++++------------ .../NetworkStreamPhysicalConnection.cs | 22 +--------- 2 files changed, 18 insertions(+), 47 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index c086c559..d01453c2 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -27,7 +27,7 @@ public class LogicalConnectionManager : ILogicalConnection private const int connectionTimeout = 1000; - private const int connectionPingInterval = 1000; + private const int pingInterval = 100; public LogicalConnectionManager(ClientOptions options) { @@ -42,17 +42,7 @@ public void Dispose() } Interlocked.Exchange(ref _droppableLogicalConnection, null)?.Dispose(); - - var savedTimer = Interlocked.Exchange(ref _timer, null); - if (savedTimer != null) - { - using (var timerDisposedEvent = new ManualResetEvent(false)) - { - savedTimer.Dispose(timerDisposedEvent); - // this will guarantee that all callbacks finished - timerDisposedEvent.WaitOne(); - } - } + Interlocked.Exchange(ref _timer, null)?.Dispose(); } public async Task Connect() @@ -69,37 +59,36 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); - _timer = new Timer(x => CheckPing(), null, connectionPingInterval, Timeout.Infinite); + _timer = new Timer(x => CheckPing(), null, pingInterval, Timeout.Infinite); } - private static readonly PingRequest pingRequest = new PingRequest(); + private static readonly PingRequest _pingRequest = new PingRequest(); private void CheckPing() { LogicalConnection savedConnection = _droppableLogicalConnection; - if (savedConnection == null) - { - return; - } - try { - Task task = savedConnection.SendRequestWithEmptyResponse(pingRequest); - if (!task.Wait(connectionTimeout) || task.Status != TaskStatus.RanToCompletion) + if (savedConnection == null || !savedConnection.IsConnected()) { - savedConnection.Dispose(); + return; + } + + using (Task task = savedConnection.SendRequestWithEmptyResponse(_pingRequest)) + { + if (Task.WaitAny(new[] { task }, connectionTimeout) != 0 || task.Status != TaskStatus.RanToCompletion) + { + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Ping failed, dropping logical conection..."); + savedConnection.Dispose(); + } } - } - catch (AggregateException ae) - { - savedConnection.Dispose(); } finally { if (_disposing == 0) { - _timer?.Change(connectionPingInterval, Timeout.Infinite); + _timer?.Change(pingInterval, Timeout.Infinite); } } } diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index f75442ac..67fe360e 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -42,25 +42,11 @@ public async Task Connect(ClientOptions options) _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); await ConnectAsync(_socket, singleNode.Uri.Host, singleNode.Uri.Port); - // unfortunately does not worl under linux - // SetKeepAlive(true, 1000, 100); + _stream = new NetworkStream(_socket, true); options.LogWriter?.WriteLine("Socket connection established."); } - private void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval) - { - int size = Marshal.SizeOf(new uint()); - - var inOptionValues = new byte[size * 3]; - - BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0); - BitConverter.GetBytes(keepAliveTime).CopyTo(inOptionValues, size); - BitConverter.GetBytes(keepAliveInterval).CopyTo(inOptionValues, size * 2); - - _socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); - } - public void Write(byte[] buffer, int offset, int count) { CheckConnectionStatus(); @@ -125,11 +111,7 @@ public bool IsConnected() return false; } - try - { - return !(_socket.Poll(1, SelectMode.SelectRead) && _socket.Available == 0); - } - catch (SocketException) { return false; } + return true; } private void CheckConnectionStatus() From 0497cfa998d0f7dc451ee53ae3fb2669f49701ed Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Tue, 20 Dec 2016 18:29:50 +0300 Subject: [PATCH 27/34] cosmetic fix --- src/tarantool.client/IResponseReader.cs | 8 ++++---- src/tarantool.client/RequestIdCounter.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tarantool.client/IResponseReader.cs b/src/tarantool.client/IResponseReader.cs index dccb103e..8f8c1154 100644 --- a/src/tarantool.client/IResponseReader.cs +++ b/src/tarantool.client/IResponseReader.cs @@ -1,11 +1,11 @@ using System; +using System.IO; +using System.Threading.Tasks; + +using ProGaudi.Tarantool.Client.Model; namespace ProGaudi.Tarantool.Client { - using System.IO; - using System.Threading.Tasks; - - using ProGaudi.Tarantool.Client.Model; public interface IResponseReader : IDisposable { diff --git a/src/tarantool.client/RequestIdCounter.cs b/src/tarantool.client/RequestIdCounter.cs index 3c1d99f3..e7103261 100644 --- a/src/tarantool.client/RequestIdCounter.cs +++ b/src/tarantool.client/RequestIdCounter.cs @@ -1,9 +1,9 @@ -namespace ProGaudi.Tarantool.Client -{ - using System.Threading; +using System.Threading; - using ProGaudi.Tarantool.Client.Model; +using ProGaudi.Tarantool.Client.Model; +namespace ProGaudi.Tarantool.Client +{ public class RequestIdCounter { private long _currentRequestId; From 968beb82c282ff71b8bf068205f24eaf1728250c Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Wed, 21 Dec 2016 17:45:35 +0300 Subject: [PATCH 28/34] slim locks and non-concurrent dict for requests --- src/tarantool.client/LogicalConnection.cs | 11 +++- .../LogicalConnectionManager.cs | 10 ++-- src/tarantool.client/ResponseReader.cs | 57 +++++++++++++------ 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index 5e8ae1d1..dada72af 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using ProGaudi.MsgPack.Light; @@ -23,6 +24,8 @@ internal class LogicalConnection : ILogicalConnection private readonly INetworkStreamPhysicalConnection _physicalConnection; + private readonly ReaderWriterLockSlim _physicalConnectionLock = new ReaderWriterLockSlim(); + private readonly IResponseReader _responseReader; private readonly ILog _logWriter; @@ -151,14 +154,20 @@ private async Task SendRequestImpl(TRequest requ try { - lock (_physicalConnection) + try { + _physicalConnectionLock.EnterWriteLock(); + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); } + finally + { + _physicalConnectionLock.ExitWriteLock(); + } } catch (Exception ex) { diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index d01453c2..0a067e48 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -75,13 +75,11 @@ private void CheckPing() return; } - using (Task task = savedConnection.SendRequestWithEmptyResponse(_pingRequest)) + Task task = savedConnection.SendRequestWithEmptyResponse(_pingRequest); + if (Task.WaitAny(new[] { task }, connectionTimeout) != 0 || task.Status != TaskStatus.RanToCompletion) { - if (Task.WaitAny(new[] { task }, connectionTimeout) != 0 || task.Status != TaskStatus.RanToCompletion) - { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Ping failed, dropping logical conection..."); - savedConnection.Dispose(); - } + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Ping failed, dropping logical conection..."); + savedConnection.Dispose(); } } finally diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 9005cea8..5d852787 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -1,7 +1,8 @@ using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -19,8 +20,10 @@ internal class ResponseReader : IResponseReader { private readonly INetworkStreamPhysicalConnection _physicalConnection; - private readonly ConcurrentDictionary> _pendingRequests = - new ConcurrentDictionary>(); + private readonly Dictionary> _pendingRequests = + new Dictionary>(); + + private readonly ReaderWriterLockSlim _pendingRequestsLock = new ReaderWriterLockSlim(); private readonly ClientOptions _clientOptions; @@ -41,37 +44,53 @@ public ResponseReader(ClientOptions clientOptions, INetworkStreamPhysicalConnect public Task GetResponseTask(RequestId requestId) { - lock (this) + try { + _pendingRequestsLock.EnterWriteLock(); + if (_disposed) { throw new ObjectDisposedException(nameof(ResponseReader)); } - var tcs = new TaskCompletionSource(); - if (!_pendingRequests.TryAdd(requestId, tcs)) + if (_pendingRequests.ContainsKey(requestId)) { throw ExceptionHelper.RequestWithSuchIdAlreadySent(requestId); } + var tcs = new TaskCompletionSource(); + _pendingRequests.Add(requestId, tcs); + return tcs.Task; } + finally + { + _pendingRequestsLock.ExitWriteLock(); + } } private TaskCompletionSource PopResponseCompletionSource(RequestId requestId, MemoryStream resultStream) { - lock (this) + try { + _pendingRequestsLock.EnterWriteLock(); + if (_disposed) { throw new ObjectDisposedException(nameof(ResponseReader)); } TaskCompletionSource request; + if (_pendingRequests.TryGetValue(requestId, out request)) + { + _pendingRequests.Remove(requestId); + } - return _pendingRequests.TryRemove(requestId, out request) - ? request - : null; + return request; + } + finally + { + _pendingRequestsLock.ExitWriteLock(); } } @@ -278,14 +297,16 @@ public bool IsConnected() public void Dispose() { - lock (this) + if (_disposed) { - if (_disposed) - { - return; - } + return; + } + + _disposed = true; - _disposed = true; + try + { + _pendingRequestsLock.EnterWriteLock(); _clientOptions.LogWriter?.WriteLine("Cancelling all pending requests and setting faulted state..."); @@ -296,6 +317,10 @@ public void Dispose() _pendingRequests.Clear(); } + finally + { + _pendingRequestsLock.ExitWriteLock(); + } } } } \ No newline at end of file From 9cd0659f9f36ca77ce0d7efaf653192e0bd0acb7 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Thu, 22 Dec 2016 15:19:33 +0300 Subject: [PATCH 29/34] ping without timeout, alive interval for ping --- .../LogicalConnectionManager.cs | 29 ++++++++++++------- .../NetworkStreamPhysicalConnection.cs | 4 +-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index 0a067e48..8b6d219d 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -25,9 +25,13 @@ public class LogicalConnectionManager : ILogicalConnection private int _disposing; - private const int connectionTimeout = 1000; + private const int _connectionTimeout = 1000; - private const int pingInterval = 100; + private const int _pingTimerInterval = 100; + + private const int _pingCheckInterval = 1000; + + private DateTimeOffset _nextPingTime = DateTimeOffset.MinValue; public LogicalConnectionManager(ClientOptions options) { @@ -59,24 +63,24 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); - _timer = new Timer(x => CheckPing(), null, pingInterval, Timeout.Infinite); + _timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); } private static readonly PingRequest _pingRequest = new PingRequest(); private void CheckPing() { - LogicalConnection savedConnection = _droppableLogicalConnection; - try { - if (savedConnection == null || !savedConnection.IsConnected()) + LogicalConnection savedConnection = _droppableLogicalConnection; + + if (_nextPingTime > DateTimeOffset.UtcNow || savedConnection == null || !savedConnection.IsConnected()) { return; } Task task = savedConnection.SendRequestWithEmptyResponse(_pingRequest); - if (Task.WaitAny(new[] { task }, connectionTimeout) != 0 || task.Status != TaskStatus.RanToCompletion) + if (Task.WaitAny(task) != 0 || task.Status != TaskStatus.RanToCompletion) { _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Ping failed, dropping logical conection..."); savedConnection.Dispose(); @@ -86,7 +90,7 @@ private void CheckPing() { if (_disposing == 0) { - _timer?.Change(pingInterval, Timeout.Infinite); + _timer?.Change(_pingTimerInterval, Timeout.Infinite); } } } @@ -98,14 +102,14 @@ public bool IsConnected() private async Task EnsureConnection() { - if (_connected.WaitOne(connectionTimeout) && IsConnected()) + if (_connected.WaitOne(_connectionTimeout) && IsConnected()) { return; } _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connection lost, wait for reconnect..."); - if (!_reconnectAvailable.WaitOne(connectionTimeout)) + if (!_reconnectAvailable.WaitOne(_connectionTimeout)) { _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Failed to get lock for reconnect"); throw ExceptionHelper.NotConnected(); @@ -129,13 +133,16 @@ private async Task EnsureConnection() public async Task> SendRequest(TRequest request) where TRequest : IRequest { await EnsureConnection(); - return await _droppableLogicalConnection.SendRequest(request); + var result = await _droppableLogicalConnection.SendRequest(request); + _nextPingTime = DateTimeOffset.UtcNow.AddMilliseconds(_pingCheckInterval); + return result; } public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest { await EnsureConnection(); await _droppableLogicalConnection.SendRequestWithEmptyResponse(request); + _nextPingTime = DateTimeOffset.UtcNow.AddMilliseconds(_pingCheckInterval); } } } diff --git a/src/tarantool.client/NetworkStreamPhysicalConnection.cs b/src/tarantool.client/NetworkStreamPhysicalConnection.cs index 67fe360e..1ad84c39 100644 --- a/src/tarantool.client/NetworkStreamPhysicalConnection.cs +++ b/src/tarantool.client/NetworkStreamPhysicalConnection.cs @@ -106,7 +106,7 @@ private static Task ConnectAsync(Socket socket, string host, int port) public bool IsConnected() { - if (_disposed) + if (_disposed || _stream == null) { return false; } @@ -121,7 +121,7 @@ private void CheckConnectionStatus() throw new ObjectDisposedException(nameof(NetworkStreamPhysicalConnection)); } - if (!IsConnected() || _stream == null) + if (!IsConnected()) { throw ExceptionHelper.NotConnected(); } From 81d78571992caff9728a0a8b3a9074f6b1346978 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Thu, 22 Dec 2016 15:35:59 +0300 Subject: [PATCH 30/34] ping check interval in ConnectionOptions --- src/tarantool.client/LogicalConnectionManager.cs | 7 ++++++- src/tarantool.client/Model/ConnectionOptions.cs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index 8b6d219d..1baac562 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -29,7 +29,7 @@ public class LogicalConnectionManager : ILogicalConnection private const int _pingTimerInterval = 100; - private const int _pingCheckInterval = 1000; + private int _pingCheckInterval = 1000; private DateTimeOffset _nextPingTime = DateTimeOffset.MinValue; @@ -63,6 +63,11 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); + if (_clientOptions.ConnectionOptions.PingCheckInterval > 0) + { + _pingCheckInterval = _clientOptions.ConnectionOptions.PingCheckInterval; + } + _timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); } diff --git a/src/tarantool.client/Model/ConnectionOptions.cs b/src/tarantool.client/Model/ConnectionOptions.cs index d1609056..bed3fdac 100644 --- a/src/tarantool.client/Model/ConnectionOptions.cs +++ b/src/tarantool.client/Model/ConnectionOptions.cs @@ -33,6 +33,7 @@ private void Parse(string replicationSource, ILog log) public int ReadStreamBufferSize { get; set; } = 4096; public int WriteNetworkTimeout { get; set; } = -1; public int ReadNetworkTimeout { get; set; } = -1; + public int PingCheckInterval { get; set; } = -1; public List Nodes { get; set; } = new List(); } } \ No newline at end of file From 6e40a4d88f95fdbb12300c6f1422ee7a6804c0d3 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 23 Dec 2016 16:06:34 +0300 Subject: [PATCH 31/34] minor fixes --- src/tarantool.client/LogicalConnection.cs | 24 +++++++------- .../LogicalConnectionManager.cs | 24 +++++++++++--- src/tarantool.client/ResponseReader.cs | 33 +++++++++---------- 3 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/tarantool.client/LogicalConnection.cs b/src/tarantool.client/LogicalConnection.cs index dada72af..eb6fc2ca 100644 --- a/src/tarantool.client/LogicalConnection.cs +++ b/src/tarantool.client/LogicalConnection.cs @@ -154,27 +154,25 @@ private async Task SendRequestImpl(TRequest requ try { - try - { - _physicalConnectionLock.EnterWriteLock(); + _physicalConnectionLock.EnterWriteLock(); - _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); - _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); + _logWriter?.WriteLine($"Begin sending request header buffer, requestId: {requestId}, code: {request.Code}, length: {headerBuffer.Length}"); + _physicalConnection.Write(headerBuffer, 0, Constants.PacketSizeBufferSize + (int)headerLength); - _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); - _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); - } - finally - { - _physicalConnectionLock.ExitWriteLock(); - } + _logWriter?.WriteLine($"Begin sending request body buffer, length: {bodyBuffer.Length}"); + _physicalConnection.Write(bodyBuffer, 0, bodyBuffer.Length); } catch (Exception ex) { - _logWriter?.WriteLine($"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); + _logWriter?.WriteLine( + $"Request with requestId {requestId} failed, header:\n{ToReadableString(headerBuffer)} \n body: \n{ToReadableString(bodyBuffer)}"); Dispose(); throw; } + finally + { + _physicalConnectionLock.ExitWriteLock(); + } try { diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index 1baac562..9a080707 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -63,12 +63,15 @@ public async Task Connect() _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); - if (_clientOptions.ConnectionOptions.PingCheckInterval > 0) + if (_clientOptions.ConnectionOptions.PingCheckInterval >= 0) { _pingCheckInterval = _clientOptions.ConnectionOptions.PingCheckInterval; } - _timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); + if (_pingCheckInterval > 0) + { + _timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); + } } private static readonly PingRequest _pingRequest = new PingRequest(); @@ -135,19 +138,32 @@ private async Task EnsureConnection() } } + private void ScheduleNextPing() + { + if (_pingCheckInterval > 0) + { + _nextPingTime = DateTimeOffset.UtcNow.AddMilliseconds(_pingCheckInterval); + } + } + public async Task> SendRequest(TRequest request) where TRequest : IRequest { await EnsureConnection(); + var result = await _droppableLogicalConnection.SendRequest(request); - _nextPingTime = DateTimeOffset.UtcNow.AddMilliseconds(_pingCheckInterval); + + ScheduleNextPing(); + return result; } public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest { await EnsureConnection(); + await _droppableLogicalConnection.SendRequestWithEmptyResponse(request); - _nextPingTime = DateTimeOffset.UtcNow.AddMilliseconds(_pingCheckInterval); + + ScheduleNextPing(); } } } diff --git a/src/tarantool.client/ResponseReader.cs b/src/tarantool.client/ResponseReader.cs index 5d852787..de8df893 100644 --- a/src/tarantool.client/ResponseReader.cs +++ b/src/tarantool.client/ResponseReader.cs @@ -106,27 +106,26 @@ public void BeginReading() private void EndReading(Task readWork) { - if (!_disposed) + if (_disposed) { - if (readWork.Status == TaskStatus.RanToCompletion) - { - var readBytesCount = readWork.Result; - _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); - - if (ProcessReadBytes(readBytesCount)) - { - BeginReading(); - return; - } - } - - _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); - Dispose(); + _clientOptions.LogWriter?.WriteLine("Attempt to end reading in disposed state... Exiting."); + return; } - else + + if (readWork.Status == TaskStatus.RanToCompletion) { - _clientOptions.LogWriter?.WriteLine("Attempt to end reading in disposed state... Exiting."); + var readBytesCount = readWork.Result; + _clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}"); + + if (ProcessReadBytes(readBytesCount)) + { + BeginReading(); + return; + } } + + _clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}"); + Dispose(); } private bool ProcessReadBytes(int readBytesCount) From 21597932ad38bea0b92c40a78727b5ba83c16928 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Fri, 23 Dec 2016 20:07:52 +0300 Subject: [PATCH 32/34] not working version with ReadWriteLock, tbd --- .../LogicalConnectionManager.cs | 95 +++++++++---------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index 9a080707..328154bc 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -17,9 +17,7 @@ public class LogicalConnectionManager : ILogicalConnection private LogicalConnection _droppableLogicalConnection; - private readonly ManualResetEvent _connected = new ManualResetEvent(false); - - private readonly AutoResetEvent _reconnectAvailable = new AutoResetEvent(true); + private readonly ReaderWriterLockSlim _connectionLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); private Timer _timer; @@ -29,13 +27,18 @@ public class LogicalConnectionManager : ILogicalConnection private const int _pingTimerInterval = 100; - private int _pingCheckInterval = 1000; + private readonly int _pingCheckInterval = 1000; private DateTimeOffset _nextPingTime = DateTimeOffset.MinValue; public LogicalConnectionManager(ClientOptions options) { _clientOptions = options; + + if (_clientOptions.ConnectionOptions.PingCheckInterval >= 0) + { + _pingCheckInterval = _clientOptions.ConnectionOptions.PingCheckInterval; + } } public void Dispose() @@ -51,26 +54,43 @@ public void Dispose() public async Task Connect() { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connecting..."); + if (!_connectionLock.TryEnterUpgradeableReadLock(_connectionTimeout)) + { + throw ExceptionHelper.NotConnected(); + } - _connected.Reset(); + try + { + if (this.IsConnected()) + { + return; + } - var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); - await _newConnection.Connect(); - Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connecting..."); - _connected.Set(); + _connectionLock.EnterWriteLock(); - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); + try + { + var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); + await _newConnection.Connect(); + Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); - if (_clientOptions.ConnectionOptions.PingCheckInterval >= 0) - { - _pingCheckInterval = _clientOptions.ConnectionOptions.PingCheckInterval; - } + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); - if (_pingCheckInterval > 0) + if (_pingCheckInterval > 0 && _timer == null) + { + //_timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); + } + } + finally + { + _connectionLock.ExitWriteLock(); + } + } + finally { - _timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); + _connectionLock.ExitUpgradeableReadLock(); } } @@ -80,19 +100,12 @@ private void CheckPing() { try { - LogicalConnection savedConnection = _droppableLogicalConnection; - - if (_nextPingTime > DateTimeOffset.UtcNow || savedConnection == null || !savedConnection.IsConnected()) + if (_nextPingTime > DateTimeOffset.UtcNow) { return; } - Task task = savedConnection.SendRequestWithEmptyResponse(_pingRequest); - if (Task.WaitAny(task) != 0 || task.Status != TaskStatus.RanToCompletion) - { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Ping failed, dropping logical conection..."); - savedConnection.Dispose(); - } + Task.WaitAny(SendRequestWithEmptyResponse(_pingRequest)); } finally { @@ -105,36 +118,18 @@ private void CheckPing() public bool IsConnected() { - return _droppableLogicalConnection?.IsConnected() ?? false; - } - - private async Task EnsureConnection() - { - if (_connected.WaitOne(_connectionTimeout) && IsConnected()) - { - return; - } - - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connection lost, wait for reconnect..."); - - if (!_reconnectAvailable.WaitOne(_connectionTimeout)) + if (!_connectionLock.TryEnterReadLock(_connectionTimeout)) { - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Failed to get lock for reconnect"); - throw ExceptionHelper.NotConnected(); + return false; } try { - if (!IsConnected()) - { - await Connect(); - } - - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connection reacquired"); + return _droppableLogicalConnection?.IsConnected() ?? false; } finally { - _reconnectAvailable.Set(); + _connectionLock.ExitReadLock(); } } @@ -148,7 +143,7 @@ private void ScheduleNextPing() public async Task> SendRequest(TRequest request) where TRequest : IRequest { - await EnsureConnection(); + await Connect(); var result = await _droppableLogicalConnection.SendRequest(request); @@ -159,7 +154,7 @@ public async Task> SendRequest(TR public async Task SendRequestWithEmptyResponse(TRequest request) where TRequest : IRequest { - await EnsureConnection(); + await Connect(); await _droppableLogicalConnection.SendRequestWithEmptyResponse(request); From ddd59f43dca2eff8ce57b835c3a0323fdc85e548 Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Mon, 26 Dec 2016 15:36:35 +0300 Subject: [PATCH 33/34] always reconnect with events --- .../LogicalConnectionManager.cs | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index 328154bc..b3873329 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -17,7 +17,11 @@ public class LogicalConnectionManager : ILogicalConnection private LogicalConnection _droppableLogicalConnection; - private readonly ReaderWriterLockSlim _connectionLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + private readonly ManualResetEvent _connected = new ManualResetEvent(false); + + private readonly AutoResetEvent _reconnectAvailable = new AutoResetEvent(true); + + private bool _onceConnected; private Timer _timer; @@ -54,43 +58,45 @@ public void Dispose() public async Task Connect() { - if (!_connectionLock.TryEnterUpgradeableReadLock(_connectionTimeout)) + if (IsConnectedInternal()) + { + return; + } + + if (!_reconnectAvailable.WaitOne(_connectionTimeout)) { throw ExceptionHelper.NotConnected(); } try { - if (this.IsConnected()) + if (IsConnectedInternal()) { return; } + _connected.Reset(); + + _onceConnected = true; + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connecting..."); - _connectionLock.EnterWriteLock(); + var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); + await _newConnection.Connect(); + Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); - try - { - var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); - await _newConnection.Connect(); - Interlocked.Exchange(ref _droppableLogicalConnection, _newConnection)?.Dispose(); + _connected.Set(); - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); + _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected..."); - if (_pingCheckInterval > 0 && _timer == null) - { - //_timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); - } - } - finally + if (_pingCheckInterval > 0 && _timer == null) { - _connectionLock.ExitWriteLock(); + _timer = new Timer(x => CheckPing(), null, _pingTimerInterval, Timeout.Infinite); } } finally { - _connectionLock.ExitUpgradeableReadLock(); + _reconnectAvailable.Set(); } } @@ -118,19 +124,17 @@ private void CheckPing() public bool IsConnected() { - if (!_connectionLock.TryEnterReadLock(_connectionTimeout)) + if (!_onceConnected || !_connected.WaitOne(_connectionTimeout)) { return false; } - try - { - return _droppableLogicalConnection?.IsConnected() ?? false; - } - finally - { - _connectionLock.ExitReadLock(); - } + return IsConnectedInternal(); + } + + private bool IsConnectedInternal() + { + return _droppableLogicalConnection?.IsConnected() ?? false; } private void ScheduleNextPing() From 0750eee56d4bde02a170807c6007dc56ffa7b46a Mon Sep 17 00:00:00 2001 From: Dmitry-Bryliuk Date: Tue, 27 Dec 2016 20:35:22 +0300 Subject: [PATCH 34/34] remove unnecessary flag, use async call for ping --- src/tarantool.client/LogicalConnectionManager.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tarantool.client/LogicalConnectionManager.cs b/src/tarantool.client/LogicalConnectionManager.cs index b3873329..26214e19 100644 --- a/src/tarantool.client/LogicalConnectionManager.cs +++ b/src/tarantool.client/LogicalConnectionManager.cs @@ -17,12 +17,10 @@ public class LogicalConnectionManager : ILogicalConnection private LogicalConnection _droppableLogicalConnection; - private readonly ManualResetEvent _connected = new ManualResetEvent(false); + private readonly ManualResetEvent _connected = new ManualResetEvent(true); private readonly AutoResetEvent _reconnectAvailable = new AutoResetEvent(true); - private bool _onceConnected; - private Timer _timer; private int _disposing; @@ -77,8 +75,6 @@ public async Task Connect() _connected.Reset(); - _onceConnected = true; - _clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connecting..."); var _newConnection = new LogicalConnection(_clientOptions, _requestIdCounter); @@ -111,7 +107,10 @@ private void CheckPing() return; } - Task.WaitAny(SendRequestWithEmptyResponse(_pingRequest)); + // suppress for fire and forget behaviour +#pragma warning disable 4014 + SendRequestWithEmptyResponse(_pingRequest); +#pragma warning restore 4014 } finally { @@ -124,7 +123,7 @@ private void CheckPing() public bool IsConnected() { - if (!_onceConnected || !_connected.WaitOne(_connectionTimeout)) + if (!_connected.WaitOne(_connectionTimeout)) { return false; }