use of org.eclipse.hono.client.DisconnectListener in project hono by eclipse.
the class HonoConnectionImplTest method testOnRemoteCloseTriggeredReconnectChecksReconnectAttempts.
/**
* Verifies that when the client tries to re-connect to a server instance if the
* connection is closed by the peer, the configured number of reconnect attempts is taken
* into account, skipping the reconnect if that number is zero.
*
* @param ctx The test context.
*/
@Test
public void testOnRemoteCloseTriggeredReconnectChecksReconnectAttempts(final VertxTestContext ctx) {
// GIVEN a client that is connected to a server but should do no automatic reconnect
props.setReconnectAttempts(0);
honoConnection = new HonoConnectionImpl(vertx, connectionFactory, props);
final Promise<HonoConnection> connected = Promise.promise();
@SuppressWarnings("unchecked") final DisconnectListener<HonoConnection> disconnectListener = mock(DisconnectListener.class);
honoConnection.addDisconnectListener(disconnectListener);
final AtomicInteger reconnectListenerInvocations = new AtomicInteger();
honoConnection.addReconnectListener(con -> reconnectListenerInvocations.incrementAndGet());
honoConnection.connect(new ProtonClientOptions().setReconnectAttempts(0)).onComplete(connected);
connected.future().onComplete(ctx.succeeding(c -> {
// WHEN the peer closes the connection
connectionFactory.getCloseHandler().handle(Future.failedFuture("shutting down for maintenance"));
ctx.verify(() -> {
// THEN the client invokes the registered disconnect handler
verify(disconnectListener).onDisconnect(honoConnection);
// and the original connection has been closed locally
verify(con).close();
verify(con).disconnectHandler(null);
// and no further connect invocation has been done
assertThat(connectionFactory.getConnectInvocations()).isEqualTo(1);
assertThat(reconnectListenerInvocations.get()).isEqualTo(0);
});
ctx.completeNow();
}));
}
use of org.eclipse.hono.client.DisconnectListener in project hono by eclipse.
the class HonoConnectionImplTest method testRemoteSessionCloseTriggersReconnection.
/**
* Verifies that the client tries to reconnect to the peer if the peer
* closes the connection's session.
*
* @param ctx The test context.
*/
@Test
public void testRemoteSessionCloseTriggersReconnection(final VertxTestContext ctx) {
// GIVEN a client that is connected to a server
final Promise<HonoConnection> connected = Promise.promise();
@SuppressWarnings("unchecked") final DisconnectListener<HonoConnection> disconnectListener = mock(DisconnectListener.class);
honoConnection.addDisconnectListener(disconnectListener);
final AtomicInteger reconnectListenerInvocations = new AtomicInteger();
honoConnection.addReconnectListener(con -> reconnectListenerInvocations.incrementAndGet());
props.setServerRole("service-provider");
honoConnection.connect(new ProtonClientOptions()).onComplete(connected);
connectionFactory.setExpectedSucceedingConnectionAttempts(1);
connected.future().onComplete(ctx.succeeding(c -> {
ctx.verify(() -> {
// WHEN the peer closes the session
final ArgumentCaptor<Handler<AsyncResult<ProtonSession>>> sessionCloseHandler = VertxMockSupport.argumentCaptorHandler();
verify(session).closeHandler(sessionCloseHandler.capture());
sessionCloseHandler.getValue().handle(Future.succeededFuture(session));
// THEN the client invokes the registered disconnect handler
verify(disconnectListener).onDisconnect(honoConnection);
// and the original connection has been closed locally
verify(con).close();
verify(con).disconnectHandler(null);
// and the connection is re-established
assertThat(connectionFactory.await()).isTrue();
assertThat(reconnectListenerInvocations.get()).isEqualTo(1);
});
ctx.completeNow();
}));
}
use of org.eclipse.hono.client.DisconnectListener in project hono by eclipse.
the class HonoConnectionImpl method createReceiver.
@Override
public Future<ProtonReceiver> createReceiver(final String sourceAddress, final ProtonQoS qos, final ProtonMessageHandler messageHandler, final int preFetchSize, final boolean autoAccept, final Handler<String> remoteCloseHook) {
Objects.requireNonNull(sourceAddress);
Objects.requireNonNull(qos);
Objects.requireNonNull(messageHandler);
if (preFetchSize < 0) {
throw new IllegalArgumentException("pre-fetch size must be >= 0");
}
return executeOnContext(result -> {
checkConnected().compose(v -> {
final Promise<ProtonReceiver> receiverPromise = Promise.promise();
final ProtonReceiver receiver = session.createReceiver(sourceAddress);
if (clientConfigProperties.getMaxMessageSize() > ClientConfigProperties.MAX_MESSAGE_SIZE_UNLIMITED) {
receiver.setMaxMessageSize(new UnsignedLong(clientConfigProperties.getMaxMessageSize()));
}
receiver.setAutoAccept(autoAccept);
receiver.setQoS(qos);
receiver.setPrefetch(preFetchSize);
receiver.handler((delivery, message) -> {
HonoProtonHelper.onReceivedMessageDeliveryUpdatedFromRemote(delivery, d -> log.debug("got unexpected disposition update for received message [remote state: {}]", delivery.getRemoteState()));
try {
messageHandler.handle(delivery, message);
if (log.isTraceEnabled()) {
final int remainingCredits = receiver.getCredit() - receiver.getQueued();
log.trace("handling message [remotely settled: {}, queued messages: {}, remaining credit: {}]", delivery.remotelySettled(), receiver.getQueued(), remainingCredits);
}
} catch (final Exception ex) {
log.warn("error handling message", ex);
ProtonHelper.released(delivery, true);
}
});
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening receiver [{}] failed: got disconnected", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
receiver.openHandler(recvOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (receiverPromise.future().isComplete()) {
log.debug("ignoring server response for opening receiver [{}]: receiver creation already timed out", sourceAddress);
} else if (recvOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = receiver.getRemoteCondition();
if (error == null) {
log.debug("opening receiver [{}] failed", sourceAddress, recvOpen.cause());
receiverPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open receiver", recvOpen.cause()));
} else {
log.debug("opening receiver [{}] failed: {} - {}", sourceAddress, error.getCondition(), error.getDescription());
receiverPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(receiver)) {
log.debug("receiver open [source: {}]", sourceAddress);
receiverPromise.tryComplete(recvOpen.result());
} else {
// this means that the peer did not create a local terminus for the link
// and will send a detach frame for closing the link very shortly
// see AMQP 1.0 spec section 2.6.3
log.debug("peer did not create terminus for source [{}] and will detach the link", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(receiver, remoteDetached -> onRemoteDetach(receiver, connection.getRemoteContainer(), false, remoteCloseHook));
HonoProtonHelper.setCloseHandler(receiver, remoteClosed -> onRemoteDetach(receiver, connection.getRemoteContainer(), true, remoteCloseHook));
receiver.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(receiver, clientConfigProperties, receiverPromise);
}
});
return receiverPromise.future();
}).onComplete(result);
});
}
use of org.eclipse.hono.client.DisconnectListener in project hono by eclipse.
the class HonoConnectionImpl method createSender.
/**
* Creates a sender link.
*
* @param targetAddress The target address of the link. If the address is {@code null}, the
* sender link will be established to the 'anonymous relay' and each
* message must specify its destination address.
* @param qos The quality of service to use for the link.
* @param closeHook The handler to invoke when the link is closed by the peer (may be {@code null}).
* @return A future for the created link. The future will be completed once the link is open.
* The future will fail with a {@link ServiceInvocationException} if the link cannot be opened.
* @throws NullPointerException if qos is {@code null}.
*/
@Override
public final Future<ProtonSender> createSender(final String targetAddress, final ProtonQoS qos, final Handler<String> closeHook) {
Objects.requireNonNull(qos);
return executeOnContext(result -> {
checkConnected().compose(v -> {
if (targetAddress == null && !supportsCapability(Constants.CAP_ANONYMOUS_RELAY)) {
// before a client can use anonymous terminus
return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_NOT_IMPLEMENTED, "server does not support anonymous terminus"));
}
final Promise<ProtonSender> senderPromise = Promise.promise();
final ProtonSender sender = session.createSender(targetAddress);
sender.setQoS(qos);
sender.setAutoSettle(true);
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening sender [{}] failed: got disconnected", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
sender.openHandler(senderOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (senderPromise.future().isComplete()) {
log.debug("ignoring server response for opening sender [{}]: sender creation already timed out", targetAddress);
} else if (senderOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = sender.getRemoteCondition();
if (error == null) {
log.debug("opening sender [{}] failed", targetAddress, senderOpen.cause());
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open sender", senderOpen.cause()));
} else {
log.debug("opening sender [{}] failed: {} - {}", targetAddress, error.getCondition(), error.getDescription());
senderPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(sender)) {
log.debug("sender open [target: {}, sendQueueFull: {}, remote max-message-size: {}]", targetAddress, sender.sendQueueFull(), sender.getRemoteMaxMessageSize());
final long remoteMaxMessageSize = Optional.ofNullable(sender.getRemoteMaxMessageSize()).map(UnsignedLong::longValue).orElse(0L);
if (remoteMaxMessageSize > 0 && remoteMaxMessageSize < clientConfigProperties.getMinMaxMessageSize()) {
// peer won't accept our (biggest) messages
sender.close();
final String msg = String.format("peer does not support minimum max-message-size [required: %d, supported: %d", clientConfigProperties.getMinMaxMessageSize(), remoteMaxMessageSize);
log.debug(msg);
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_PRECON_FAILED, msg));
} else if (sender.getCredit() <= 0) {
// wait on credits a little time, if not already given
final long waitOnCreditsTimerId = vertx.setTimer(clientConfigProperties.getFlowLatency(), timerID -> {
log.debug("sender [target: {}] has {} credits after grace period of {}ms", targetAddress, sender.getCredit(), clientConfigProperties.getFlowLatency());
sender.sendQueueDrainHandler(null);
senderPromise.tryComplete(sender);
});
sender.sendQueueDrainHandler(replenishedSender -> {
log.debug("sender [target: {}] has received {} initial credits", targetAddress, replenishedSender.getCredit());
if (vertx.cancelTimer(waitOnCreditsTimerId)) {
result.tryComplete(replenishedSender);
replenishedSender.sendQueueDrainHandler(null);
}
// otherwise the timer has already completed the future and cleaned up
// sendQueueDrainHandler
});
} else {
senderPromise.tryComplete(sender);
}
} else {
// this means that the peer did not create a local terminus for the link
// and will send a detach frame for closing the link very shortly
// see AMQP 1.0 spec section 2.6.3
log.debug("peer did not create terminus for target [{}] and will detach the link", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(sender, remoteDetached -> onRemoteDetach(sender, connection.getRemoteContainer(), false, closeHook));
HonoProtonHelper.setCloseHandler(sender, remoteClosed -> onRemoteDetach(sender, connection.getRemoteContainer(), true, closeHook));
sender.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(sender, clientConfigProperties, senderPromise);
}
});
return senderPromise.future();
}).onComplete(result);
});
}
use of org.eclipse.hono.client.DisconnectListener in project hono by eclipse.
the class HonoConnectionImplTest method testOnRemoteCloseTriggersReconnection.
/**
* Verifies that the client tries to re-connect to a server instance if the
* connection is closed by the peer.
*
* @param ctx The test context.
*/
@Test
public void testOnRemoteCloseTriggersReconnection(final VertxTestContext ctx) {
// GIVEN a client that is connected to a server
final Promise<HonoConnection> connected = Promise.promise();
@SuppressWarnings("unchecked") final DisconnectListener<HonoConnection> disconnectListener = mock(DisconnectListener.class);
honoConnection.addDisconnectListener(disconnectListener);
final AtomicInteger reconnectListenerInvocations = new AtomicInteger();
honoConnection.addReconnectListener(con -> reconnectListenerInvocations.incrementAndGet());
honoConnection.connect(new ProtonClientOptions()).onComplete(connected);
connectionFactory.setExpectedSucceedingConnectionAttempts(1);
connected.future().onComplete(ctx.succeeding(c -> {
// WHEN the peer closes the connection
connectionFactory.getCloseHandler().handle(Future.failedFuture("shutting down for maintenance"));
ctx.verify(() -> {
// THEN the client invokes the registered disconnect handler
verify(disconnectListener).onDisconnect(honoConnection);
// and the original connection has been closed locally
verify(con).close();
verify(con).disconnectHandler(null);
// and the connection is re-established
assertThat(connectionFactory.await()).isTrue();
assertThat(reconnectListenerInvocations.get()).isEqualTo(1);
});
ctx.completeNow();
}));
}
Aggregations