+ // State transition completed, now run any additional processing
+ helloFuture.addListener(this::onHelloWriteComplete);
+ }
+
+ private void onHelloWriteComplete(final Future<?> future) {
+ final var cause = future.cause();
+ if (cause != null) {
+ LOG.info("Failed to send message {} on channel {}", localHello, channel, cause);
+ negotiationFailed(cause);
+ } else {
+ LOG.trace("Message {} sent to socket on channel {}", localHello, channel);
+ }
+ }
+
+ private synchronized void timeoutExpired() {
+ if (timeoutTask == null) {
+ // cancelTimeout() between expiry and execution on the loop
+ return;
+ }
+ timeoutTask = null;
+
+ if (state != State.ESTABLISHED) {
+ LOG.debug("Connection timeout after {}ms, session backed by channel {} is in state {}",
+ connectionTimeoutMillis, channel, state);
+
+ // Do not fail negotiation if promise is done or canceled
+ // It would result in setting result of the promise second time and that throws exception
+ if (!promise.isDone() && !promise.isCancelled()) {
+ LOG.warn("Netconf session backed by channel {} was not established after {}", channel,
+ connectionTimeoutMillis);
+ failAndClose();