Skip to content

Conversation

@Dmitry-Bryliuk
Copy link
Contributor

@Dmitry-Bryliuk Dmitry-Bryliuk commented Dec 2, 2016

done:

  • logical connection/response reader understands it is in faulted state and accepts no more requests
  • physical connection and response reader are moved into logical connection
  • requests queue is a part of response reader and fully managed by it, including faulted state
  • new logical connection wrapper, which is checking connection availability before each request and dropping underlying logical connection and reconnects when no connection
  • ping tarantool directly (socket keepAlive options not available under linux) and drop connection on ping failed

q:

  • cyclical reconnect attempts? now only one attempt to reconnect per request (if not reconnecting in other request)

This change is Reviewable


void SetFaultedState();

bool IsFaultedState { get; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make IsFaultedState writeable instead of SetFaultedState method?


public bool IsConnected()
{
return !(_responseReader?.IsFaultedState ?? true) && (_physicalConnection?.IsConnected() ?? false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite hard to read. Maybe better to split in several expressions\extract some variable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be redesigned.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialization of ResponseReader and PhysicalConnection should be moved to constructor. Then all fields could be made readonly and there will be no reason for testing them for null.

_clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}");

if (ProcessReadBytes(readBytesCount))
if (readWork.IsCompleted)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if readWork.IsCompleted == false? Can it be if reading is in progress?

{
Task Connect();

bool IsConnected();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be state, not only boolean flag.


public bool IsConnected()
{
return !(_responseReader?.IsFaultedState ?? true) && (_physicalConnection?.IsConnected() ?? false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be redesigned.

namespace ProGaudi.Tarantool.Client
{
public interface ILogicalConnection
using System;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, respect our coding guidelines: usings should be outside of namespace.


public bool IsConnected()
{
return !(_responseReader?.IsFaultedState ?? true) && (_physicalConnection?.IsConnected() ?? false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialization of ResponseReader and PhysicalConnection should be moved to constructor. Then all fields could be made readonly and there will be no reason for testing them for null.

using ProGaudi.Tarantool.Client.Model;
using ProGaudi.Tarantool.Client.Utils;

public class LogicalConnectionWrapper : ILogicalConnection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this class? Maybe we should move all the code to Box?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefix all calls in Box to check connection and reconnect, or implement something like ILogicalConnection (with ability to drop logical connection and reconnect) inside box and make calls to it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it manages logical connections then this is a manager, not a wrapper.

{
_disposed = true;
_stream?.Dispose();
Interlocked.Exchange(ref _stream, null)?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't replace stream with null. This is bad design decision.

{
lock (this)
{
if (_pendingRequests == null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we introduced lock there?

{
if (_disposed)
{
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we should throw exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_clientOptions.LogWriter?.WriteLine($"End reading from connection, read bytes count: {readBytesCount}");

if (ProcessReadBytes(readBytesCount))
if (readWork.Status == TaskStatus.RanToCompletion)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if Status == Ready?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@aensidhe aensidhe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of locking introduced without clear reason. Necessary lock was removed.

return false;
}

if (!_responseReader.IsConnected() || !_physicalConnection.IsConnected())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsConnected should not change state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

using ProGaudi.Tarantool.Client.Model;
using ProGaudi.Tarantool.Client.Utils;

public class LogicalConnectionWrapper : ILogicalConnection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it manages logical connections then this is a manager, not a wrapper.

long headerLength;
var headerBuffer = CreateAndSerializeBuffer(request, requestId, bodyBuffer, out headerLength);

lock (_physicalConnection)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lock is necessary. Otherwise you'll end up with two threads mixing requests and broken pipeline

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

public void Dispose()
{
_disposed = true;
_stream?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this lock?

_disposed = true;

_stream?.Dispose();
_socket?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Socket is already disposed. Look to constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how it may be already disposed when we call dispose manually when disconnect detected?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream owns socket and will dispose it, because we pass True to stream parameter.


public Task<MemoryStream> GetResponseTask(RequestId requestId)
{
lock (this)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this lock?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid concurrent access to _pendingRequests, while it perhaps may be destroying and should not accept requests any more

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better make _pendingRequests not readonly and set it to null in Dispose via InterlockedExchange.

Then you can copy it here via Interlocked.Exchange to local variable and check for null-ness.

Then we can rid of _disposed flag or made it calculatable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still minor posibility of race condition without explicit lock

Copy link
Member

@aensidhe aensidhe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, we discussed changes.

_physicalConnectionLock.ExitWriteLock();
}
}
catch (Exception ex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge this into one try-catch-finally, please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


_clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connected...");

if (_clientOptions.ConnectionOptions.PingCheckInterval > 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we treat 0 as disabled ping?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


public async Task Connect()
{
_clientOptions.LogWriter?.WriteLine($"{nameof(LogicalConnectionManager)}: Connecting...");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed it in person.

  • rewrite it to ReadWriteLockSlim.

What we miss in our discussion:

  • cleanup of old connection and old timer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup of _droppableLogicalConnection is in Connect or in LogicalConnectionManager.Dispose
cleanup of timer is in LogicalConnectionManager.Dispose

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using ReadWriteLock, not Slim - the only wait point is Connect and it is long, no reason for spin wait

TaskCompletionSource<MemoryStream> request;
if (_pendingRequests.TryGetValue(requestId, out request))
{
_pendingRequests.Remove(requestId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove does not throw exceptions if key is missing. We can remove check for TryGetValue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the check we avoid redundant call when value is missing. minor performance gain.

_clientOptions.LogWriter?.WriteLine($"Connection read failed: {readWork.Exception}");
Dispose();
}
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, reduce nesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@aensidhe
Copy link
Member

Reviewed 13 of 13 files at r1.
Review status: all files reviewed at latest revision, 3 unresolved discussions, some commit checks failed.


src/tarantool.client/LogicalConnectionManager.cs, line 42 at r1 (raw file):

            _clientOptions = options;

            if (_clientOptions.ConnectionOptions.PingCheckInterval >= 0)

If zero is bad value for PingCheckInterval - incorporate validation logic into setter of it.


src/tarantool.client/LogicalConnectionManager.cs, line 80 at r1 (raw file):

                _connected.Reset();

                _onceConnected = true;

Why do we need this flag? It is not resetted to false ever.


src/tarantool.client/LogicalConnectionManager.cs, line 114 at r1 (raw file):

                }

                Task.WaitAny(SendRequestWithEmptyResponse(_pingRequest));

Why do we need to wait on it at all?


Comments from Reviewable

@roman-kozachenko
Copy link
Contributor

Review status: all files reviewed at latest revision, 4 unresolved discussions, some commit checks failed.


src/tarantool.client/LogicalConnection.cs, line 157 at r1 (raw file):

            try
            {
                _physicalConnectionLock.EnterWriteLock();

How about send headerBuffer and bodyBuffer as 1 buffer (concat arrays befor sent?) and remove lock?


src/tarantool.client/LogicalConnectionManager.cs, line 42 at r1 (raw file):

Previously, aensidhe (Anatoly Popov) wrote…

If zero is bad value for PingCheckInterval - incorporate validation logic into setter of it.

As i understand - 0 is good value for PingCheckInterval. If it is zero, ping packets will not be sent.
But I agree, negative values can be validated in setter.


Comments from Reviewable

@aensidhe aensidhe merged commit 9132a64 into master Dec 28, 2016
@aensidhe aensidhe deleted the fix/reconnect branch December 28, 2016 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants