-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactors how Slic keeps connections alive #3670
Changes from 6 commits
e0d32d7
0043c48
50a7d46
e1afc92
5dff417
fdb6d6f
19e4f31
33aea40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,10 +55,9 @@ internal class SlicConnection : IMultiplexedConnection | |
private Task<TransportConnectionInformation>? _connectTask; | ||
private readonly CancellationTokenSource _disposedCts = new(); | ||
private Task? _disposeTask; | ||
private readonly IDuplexConnection _duplexConnection; | ||
private readonly SlicDuplexConnectionDecorator _duplexConnection; | ||
private readonly DuplexConnectionReader _duplexConnectionReader; | ||
private readonly SlicDuplexConnectionWriter _duplexConnectionWriter; | ||
private readonly Action<TimeSpan, Action?> _enableIdleTimeoutAndKeepAlive; | ||
private bool _isClosed; | ||
private ulong? _lastRemoteBidirectionalStreamId; | ||
private ulong? _lastRemoteUnidirectionalStreamId; | ||
|
@@ -265,9 +264,7 @@ async Task<TransportConnectionInformation> PerformConnectAsync() | |
|
||
if (idleTimeout != Timeout.InfiniteTimeSpan) | ||
{ | ||
// Only client connections send ping frames when idle to keep the server connection alive. The server | ||
// sends back a Pong frame in turn to keep alive the client connection. | ||
_enableIdleTimeoutAndKeepAlive(idleTimeout, IsServer ? null : KeepAlive); | ||
_duplexConnection.Enable(idleTimeout); | ||
} | ||
|
||
_readFramesTask = ReadFramesAsync(_disposedCts.Token); | ||
|
@@ -317,31 +314,6 @@ async Task<TransportConnectionInformation> PerformConnectAsync() | |
_ => throw new InvalidDataException($"Received unexpected Slic frame: '{frameType}'."), | ||
}; | ||
|
||
void KeepAlive() | ||
{ | ||
// _pendingPongCount can be < 0 if an unexpected pong is received. If it's the case, the connection is being | ||
// torn down and there's no point in sending a ping frame. | ||
if (Interlocked.Increment(ref _pendingPongCount) > 0) | ||
{ | ||
try | ||
{ | ||
// For now, the Ping frame payload is just a long which is always set to 0. In the future, it could | ||
// be a ping frame type value if the ping frame is used for different purpose (e.g: a KeepAlive or | ||
// RTT ping frame type). | ||
WriteConnectionFrame(FrameType.Ping, new PingBody(0L).Encode); | ||
} | ||
catch (IceRpcException) | ||
{ | ||
// Expected if the connection is closed. | ||
} | ||
catch (Exception exception) | ||
{ | ||
Debug.Fail($"The Slic keep alive timer failed with an unexpected exception: {exception}"); | ||
throw; | ||
} | ||
} | ||
} | ||
|
||
async ValueTask<T> ReadFrameAsync<T>( | ||
Func<FrameType?, ReadOnlySequence<byte>, T> decodeFunc, | ||
CancellationToken cancellationToken) | ||
|
@@ -577,10 +549,11 @@ internal SlicConnection( | |
|
||
_closedCancellationToken = _closedCts.Token; | ||
|
||
var duplexConnectionDecorator = new IdleTimeoutDuplexConnectionDecorator(duplexConnection); | ||
_enableIdleTimeoutAndKeepAlive = duplexConnectionDecorator.Enable; | ||
// Only the client-side sends pings to keep the connection alive when idle timeout (set later) is not infinite. | ||
_duplexConnection = IsServer ? | ||
new SlicDuplexConnectionDecorator(duplexConnection) : | ||
new SlicDuplexConnectionDecorator(duplexConnection, SendReadPing, SendWritePing); | ||
|
||
_duplexConnection = duplexConnectionDecorator; | ||
_duplexConnectionReader = new DuplexConnectionReader(_duplexConnection, options.Pool, options.MinSegmentSize); | ||
_duplexConnectionWriter = new SlicDuplexConnectionWriter( | ||
_duplexConnection, | ||
|
@@ -598,6 +571,47 @@ internal SlicConnection( | |
_nextBidirectionalId = 0; | ||
_nextUnidirectionalId = 2; | ||
} | ||
|
||
void SendPing(long payload) | ||
{ | ||
try | ||
{ | ||
WriteConnectionFrame(FrameType.Ping, new PingBody(payload).Encode); | ||
} | ||
catch (IceRpcException) | ||
{ | ||
// Expected if the connection is closed. | ||
} | ||
catch (Exception exception) | ||
{ | ||
Debug.Fail($"The sending of a Ping frame failed with an unexpected exception: {exception}"); | ||
throw; | ||
} | ||
} | ||
|
||
void SendReadPing() | ||
{ | ||
// ReadKeepAlive is no-op unless _pendingPongCount == 0 before the Increment below: we send a Ping only if | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is ReadKeepAlive there is no such operation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. It's the current local function, SendReadPing. |
||
// there is no outstanding Pong. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced by pending Pong, like the field name. |
||
if (Interlocked.Increment(ref _pendingPongCount) == 1) | ||
{ | ||
SendPing(1L); | ||
} | ||
else | ||
{ | ||
Interlocked.Decrement(ref _pendingPongCount); | ||
bernardnormier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
void SendWritePing() | ||
{ | ||
// _pendingPongCount can be <= 0 if an unexpected pong is received. If it's the case, the connection is | ||
// being torn down and there's no point in sending a ping frame. | ||
if (Interlocked.Increment(ref _pendingPongCount) > 0) | ||
{ | ||
SendPing(0L); | ||
} | ||
} | ||
} | ||
|
||
/// <summary>Fills the given writer with stream data received on the connection.</summary> | ||
|
@@ -1190,8 +1204,8 @@ async Task ReadPongFrameAsync(int size, CancellationToken cancellationToken) | |
(ref SliceDecoder decoder) => new PongBody(ref decoder), | ||
cancellationToken).ConfigureAwait(false); | ||
|
||
// For now, we only send a 0 payload value. | ||
if (pongBody.Payload != 0L) | ||
// For now, we only send a 0 or 1 payload value (0 for "write ping" and 1 for "read ping"). | ||
if (pongBody.Payload != 0L && pongBody.Payload != 1L) | ||
{ | ||
throw new InvalidDataException($"Received {nameof(FrameType.Pong)} with unexpected payload."); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be better to keep
IDuplexConnection
and then doThen we can make
SlicDuplexConnectionDecorator
timers non-nullable, and keep a single constructor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how this would work.
The difficulty here is the idle timeout is negotiated during connection establishment. At construction time, we don't know if the negotiated idle timeout is good to be infinite or some other value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can pass the
idleTimeout
I forget to add it there, and that is not the point. The point is to avoid creating this decorator for the Server connections.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the decorator for server connections too. It implements the idle timer.