Skip to content

Commit

Permalink
Idle timeout fixes in C++/Java/JS (#3159)
Browse files Browse the repository at this point in the history
  • Loading branch information
bernardnormier authored Nov 18, 2024
1 parent 892f92b commit 51ca2d1
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 37 deletions.
6 changes: 6 additions & 0 deletions cpp/src/Ice/ConnectionI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2684,6 +2684,12 @@ Ice::ConnectionI::validate(SocketOperation operation)
"received ValidateConnection message with unexpected size " + to_string(size)};
}
traceRecv(_readStream, this, _logger, _traceLevels);

// Client connection starts sending heartbeats once it has received the ValidateConnection message.
if (_idleTimeoutTransceiver)
{
_idleTimeoutTransceiver->scheduleHeartbeat();
}
}
}

Expand Down
18 changes: 9 additions & 9 deletions cpp/src/Ice/IdleTimeoutTransceiverDecorator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,7 @@ IdleTimeoutTransceiverDecorator::decoratorInit(const ConnectionIPtr& connection,
SocketOperation
IdleTimeoutTransceiverDecorator::initialize(Buffer& readBuffer, Buffer& writeBuffer)
{
SocketOperation op = _decoratee->initialize(readBuffer, writeBuffer);

if (op == SocketOperationNone) // connected
{
// reschedule because Ice often writes to a client connection before it's connected.
_timer->reschedule(_heartbeatTimerTask, chrono::milliseconds(_idleTimeout) / 2);
}

return op;
return _decoratee->initialize(readBuffer, writeBuffer);
}

IdleTimeoutTransceiverDecorator::~IdleTimeoutTransceiverDecorator()
Expand Down Expand Up @@ -173,3 +165,11 @@ IdleTimeoutTransceiverDecorator::disableIdleCheck()
_idleCheckEnabled = false;
}
}

void
IdleTimeoutTransceiverDecorator::scheduleHeartbeat()
{
// Reschedule because the connection establishment may have already written to the connection and scheduled a
// heartbeat.
_timer->reschedule(_heartbeatTimerTask, chrono::milliseconds(_idleTimeout) / 2);
}
1 change: 1 addition & 0 deletions cpp/src/Ice/IdleTimeoutTransceiverDecorator.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ namespace IceInternal
bool idleCheckEnabled() const noexcept { return _idleCheckEnabled; }
void enableIdleCheck();
void disableIdleCheck();
void scheduleHeartbeat();

private:
const TransceiverPtr _decoratee;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1740,8 +1740,7 @@ private boolean validate(int operation) {
+ messageType
+ " over a connection that is not yet validated.");
}
_readStream.readByte(); // Ignore compression status for
// validate connection.
_readStream.readByte(); // Ignore compression status for validate connection.
int size = _readStream.readInt();
if (size != Protocol.headerSize) {
throw new MarshalException(
Expand All @@ -1750,6 +1749,12 @@ private boolean validate(int operation) {
+ ".");
}
TraceUtil.traceRecv(_readStream, this, _logger, _traceLevels);

// Client connection starts sending heartbeats once it has received the
// ValidateConnection message.
if (_idleTimeoutTransceiver != null) {
_idleTimeoutTransceiver.scheduleHeartbeat();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// Decorates Transceiver to send heartbeats and optionally detect when no byte is received/read for
// a while.
// This decorator must not be applied on UDP connections.
class IdleTimeoutTransceiverDecorator implements Transceiver {
final class IdleTimeoutTransceiverDecorator implements Transceiver {
private final Transceiver _decoratee;
private final int _idleTimeout;
private final ScheduledExecutorService _scheduledExecutorService;
Expand All @@ -36,13 +36,7 @@ public void setReadyCallback(ReadyCallback callback) {

@Override
public int initialize(Buffer readBuffer, Buffer writeBuffer) {
int op = _decoratee.initialize(readBuffer, writeBuffer);

if (op == SocketOperation.None) { // connected
rescheduleWriteTimer();
}

return op;
return _decoratee.initialize(readBuffer, writeBuffer);
}

@Override
Expand Down Expand Up @@ -112,7 +106,7 @@ public void setBufferSize(int rcvSize, int sndSize) {
_decoratee.setBufferSize(rcvSize, sndSize);
}

public IdleTimeoutTransceiverDecorator(
IdleTimeoutTransceiverDecorator(
Transceiver decoratee,
ConnectionI connection,
int idleTimeout,
Expand All @@ -126,24 +120,30 @@ public IdleTimeoutTransceiverDecorator(
_sendHeartbeat = () -> connection.sendHeartbeat();
}

public boolean isIdleCheckEnabled() {
boolean isIdleCheckEnabled() {
return _idleCheckEnabled;
}

public void enableIdleCheck() {
void enableIdleCheck() {
if (!_idleCheckEnabled && _idleCheck != null) {
rescheduleReadTimer();
_idleCheckEnabled = true;
}
}

public void disableIdleCheck() {
void disableIdleCheck() {
if (_idleCheckEnabled && _idleCheck != null) {
cancelReadTimer();
_idleCheckEnabled = false;
}
}

void scheduleHeartbeat() {
// Reschedule because the connection establishment may have already written to the
// connection and scheduled a heartbeat.
rescheduleWriteTimer();
}

private void cancelReadTimer() {
if (_readTimerFuture != null) {
_readTimerFuture.cancel(false);
Expand Down
4 changes: 4 additions & 0 deletions js/src/Ice/ConnectionI.js
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,10 @@ export class ConnectionI {
this._logger.trace(traceLevels.networkCat, `established ${this._endpoint.protocol()} connection\n${this}`);
}

if (this._transceiver instanceof IdleTimeoutTransceiverDecorator) {
// Client connection starts sending heartbeats once it has received the ValidateConnection message.
this._transceiver.scheduleHeartbeat();
}
return true;
}

Expand Down
28 changes: 14 additions & 14 deletions js/src/Ice/IdleTimeoutTransceiverDecorator.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
//
// Copyright (c) ZeroC, Inc. All rights reserved.
//

import { SocketOperation } from "./SocketOperation.js";
// Copyright (c) ZeroC, Inc.

export class IdleTimeoutTransceiverDecorator {
constructor(decoratee, connection, timer, idleTimeout, enableIdleCheck) {
Expand All @@ -11,22 +7,20 @@ export class IdleTimeoutTransceiverDecorator {
this._decoratee = decoratee;
this._idleTimeout = idleTimeout * 1000; // Convert seconds to milliseconds
this._timer = timer;
this._enableIdleCheck = enableIdleCheck;
this._connection = connection;

// _idleCheckEnabled is initially enableIdleCheck (by default, true) unlike C++/C#/Java.
// Since JS supports only client connections, we know the connection will read at a minimum the initial
// ValidateConnection message, and this reading will start or reset the read timer.
this._idleCheckEnabled = enableIdleCheck;
}

setCallbacks(connectedCallback, bytesAvailableCallback, bytesWrittenCallback) {
this._decoratee.setCallbacks(connectedCallback, bytesAvailableCallback, bytesWrittenCallback);
}

initialize(readBuffer, writeBuffer) {
const op = this._decoratee.initialize(readBuffer, writeBuffer);
if (op == SocketOperation.None) {
// connected
this.rescheduleReadTimer();
this.rescheduleWriteTimer();
}
return op;
return this._decoratee.initialize(readBuffer, writeBuffer);
}

register() {
Expand Down Expand Up @@ -79,6 +73,12 @@ export class IdleTimeoutTransceiverDecorator {
return this._decoratee.toString();
}

scheduleHeartbeat() {
// Reschedule because the connection establishment may have already written to the connection and scheduled a
// heartbeat.
this.rescheduleWriteTimer();
}

cancelReadTimer() {
if (this._readTimerToken !== undefined) {
this._timer.cancel(this._readTimerToken);
Expand All @@ -94,7 +94,7 @@ export class IdleTimeoutTransceiverDecorator {
}

rescheduleReadTimer() {
if (this._enableIdleCheck) {
if (this._idleCheckEnabled) {
this.cancelReadTimer();
this._readTimerToken = this._timer.schedule(() => {
this._connection.idleCheck(this._idleTimeout);
Expand Down

0 comments on commit 51ca2d1

Please sign in to comment.