use of org.eclipse.hono.client.SendMessageTimeoutException in project hono by eclipse.
the class CommandAndControlMqttIT method testSendCommandFailsForCommandNotAcknowledgedByDevice.
/**
* Verifies that the adapter forwards the <em>released</em> disposition back to the
* application if the device hasn't sent an acknowledgement for the command message
* published to the device.
*
* @param endpointConfig The endpoints to use for sending/receiving commands.
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("allCombinations")
@Timeout(timeUnit = TimeUnit.SECONDS, value = 20)
public void testSendCommandFailsForCommandNotAcknowledgedByDevice(final MqttCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
final MqttQoS subscribeQos = MqttQoS.AT_LEAST_ONCE;
final VertxTestContext setup = new VertxTestContext();
final Checkpoint ready = setup.checkpoint(2);
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final int totalNoOfCommandsToSend = 2;
final CountDownLatch commandsFailed = new CountDownLatch(totalNoOfCommandsToSend);
final AtomicInteger receivedMessagesCounter = new AtomicInteger(0);
final AtomicInteger counter = new AtomicInteger();
final Handler<MqttPublishMessage> commandConsumer = msg -> {
LOGGER.trace("received command [{}] - no response sent here", msg.topicName());
final ResourceIdentifier topic = ResourceIdentifier.fromString(msg.topicName());
ctx.verify(() -> {
endpointConfig.assertCommandPublishTopicStructure(topic, commandTargetDeviceId, false, "setValue");
});
receivedMessagesCounter.incrementAndGet();
};
final Function<Buffer, Future<Void>> commandSender = payload -> {
counter.incrementAndGet();
return helper.sendCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain", payload, helper.getSendCommandTimeout(counter.get() == 1)).mapEmpty();
};
helper.registry.addDeviceToTenant(tenantId, deviceId, password).compose(ok -> connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), password)).compose(ok -> injectMqttClientPubAckBlocker(new AtomicBoolean(true))).compose(ok -> createConsumer(tenantId, msg -> {
// expect empty notification with TTD -1
setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
LOGGER.info("received notification [{}]", notification);
setup.verify(() -> assertThat(notification).isNotNull());
if (notification.getTtd() == -1) {
ready.flag();
}
})).compose(conAck -> subscribeToCommands(commandTargetDeviceId, commandConsumer, endpointConfig, subscribeQos)).onComplete(setup.succeeding(ok -> ready.flag()));
assertWithMessage("setup of adapter finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
final AtomicInteger commandsSent = new AtomicInteger(0);
final AtomicLong lastReceivedTimestamp = new AtomicLong(0);
final long start = System.currentTimeMillis();
while (commandsSent.get() < totalNoOfCommandsToSend) {
final CountDownLatch commandSent = new CountDownLatch(1);
context.runOnContext(go -> {
final Buffer msg = Buffer.buffer("value: " + commandsSent.getAndIncrement());
commandSender.apply(msg).onComplete(sendAttempt -> {
if (sendAttempt.succeeded()) {
LOGGER.debug("sending command {} succeeded unexpectedly", commandsSent.get());
} else {
if (sendAttempt.cause() instanceof ServerErrorException && ((ServerErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_UNAVAILABLE && !(sendAttempt.cause() instanceof SendMessageTimeoutException)) {
LOGGER.debug("sending command {} failed as expected: {}", commandsSent.get(), sendAttempt.cause().toString());
lastReceivedTimestamp.set(System.currentTimeMillis());
commandsFailed.countDown();
if (commandsFailed.getCount() % 20 == 0) {
LOGGER.info("commands failed as expected: {}", totalNoOfCommandsToSend - commandsFailed.getCount());
}
} else {
LOGGER.debug("sending command {} failed with an unexpected error", commandsSent.get(), sendAttempt.cause());
}
}
commandSent.countDown();
});
});
commandSent.await();
}
// have to wait an extra MqttAdapterProperties.DEFAULT_COMMAND_ACK_TIMEOUT (100ms) for each command message
final long timeToWait = totalNoOfCommandsToSend * 300;
if (!commandsFailed.await(timeToWait, TimeUnit.MILLISECONDS)) {
LOGGER.info("Timeout of {} milliseconds reached, stop waiting for commands", timeToWait);
}
assertThat(receivedMessagesCounter.get()).isEqualTo(totalNoOfCommandsToSend);
final long commandsCompleted = totalNoOfCommandsToSend - commandsFailed.getCount();
LOGGER.info("commands sent: {}, commands failed: {} after {} milliseconds", commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
if (commandsCompleted == commandsSent.get()) {
ctx.completeNow();
} else {
ctx.failNow(new java.lang.IllegalStateException("did not complete all commands sent"));
}
}
use of org.eclipse.hono.client.SendMessageTimeoutException in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandFailsForCommandNotAcknowledgedByDevice.
/**
* Verifies that the adapter forwards the <em>released</em> disposition back to the
* application if the device hasn't sent a disposition update for the delivery of
* the command message sent to the device.
* <p>
* If Kafka is used, this means a corresponding error command response is published.
*
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@Test
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
public void testSendCommandFailsForCommandNotAcknowledgedByDevice(final VertxTestContext ctx) throws InterruptedException {
final AmqpCommandEndpointConfiguration endpointConfig = new AmqpCommandEndpointConfiguration(SubscriberRole.DEVICE);
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final AtomicInteger receivedMessagesCounter = new AtomicInteger(0);
final int totalNoOfCommandsToSend = 2;
// command handler won't send a disposition update
connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig, (cmdReceiver, cmdResponseSender) -> createNotSendingDeliveryUpdateCommandConsumer(ctx, cmdReceiver, receivedMessagesCounter), totalNoOfCommandsToSend);
if (ctx.failed()) {
return;
}
final String replyId = "reply-id";
final CountDownLatch commandsFailed = new CountDownLatch(totalNoOfCommandsToSend);
final AtomicInteger commandsSent = new AtomicInteger(0);
final AtomicLong lastReceivedTimestamp = new AtomicLong();
final long start = System.currentTimeMillis();
final long commandTimeout = IntegrationTestSupport.getSendCommandTimeout();
final Handler<Void> failureNotificationReceivedHandler = v -> {
lastReceivedTimestamp.set(System.currentTimeMillis());
commandsFailed.countDown();
};
final VertxTestContext setup = new VertxTestContext();
final Future<MessageConsumer> kafkaAsyncErrorResponseConsumer = IntegrationTestSupport.isUsingKafkaMessaging() ? helper.createDeliveryFailureCommandResponseConsumer(ctx, tenantId, HttpURLConnection.HTTP_UNAVAILABLE, response -> failureNotificationReceivedHandler.handle(null), null) : Future.succeededFuture(null);
kafkaAsyncErrorResponseConsumer.onComplete(setup.succeedingThenComplete());
assertWithMessage("setup of command response consumer finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
while (commandsSent.get() < totalNoOfCommandsToSend) {
final CountDownLatch commandSent = new CountDownLatch(1);
context.runOnContext(go -> {
final String correlationId = String.valueOf(commandsSent.getAndIncrement());
final Buffer msg = Buffer.buffer("value: " + commandsSent.get());
helper.applicationClient.sendAsyncCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain", msg, correlationId, replyId, null).onComplete(sendAttempt -> {
if (IntegrationTestSupport.isUsingAmqpMessaging()) {
if (sendAttempt.succeeded()) {
log.debug("sending command {} succeeded unexpectedly", commandsSent.get());
} else {
if (sendAttempt.cause() instanceof ServerErrorException && ((ServerErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_UNAVAILABLE && !(sendAttempt.cause() instanceof SendMessageTimeoutException)) {
log.debug("sending command {} failed as expected: {}", commandsSent.get(), sendAttempt.cause().toString());
failureNotificationReceivedHandler.handle(null);
} else {
log.debug("sending command {} failed with an unexpected error", commandsSent.get(), sendAttempt.cause());
}
}
} else if (sendAttempt.failed()) {
log.debug("sending command {} via Kafka failed unexpectedly", commandsSent.get(), sendAttempt.cause());
}
commandSent.countDown();
});
});
commandSent.await();
}
final long timeToWait = 300 + (totalNoOfCommandsToSend * commandTimeout);
if (!commandsFailed.await(timeToWait, TimeUnit.MILLISECONDS)) {
log.info("Timeout of {} milliseconds reached, stop waiting for commands", timeToWait);
}
assertThat(receivedMessagesCounter.get()).isEqualTo(totalNoOfCommandsToSend);
final long commandsCompleted = totalNoOfCommandsToSend - commandsFailed.getCount();
log.info("commands sent: {}, commands failed: {} after {} milliseconds", commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
Optional.ofNullable(kafkaAsyncErrorResponseConsumer.result()).map(MessageConsumer::close).orElseGet(Future::succeededFuture).onComplete(ar -> {
if (commandsCompleted == commandsSent.get()) {
ctx.completeNow();
} else {
ctx.failNow(new IllegalStateException("did not complete all commands sent"));
}
});
}
use of org.eclipse.hono.client.SendMessageTimeoutException in project hono by eclipse.
the class GenericSenderLink method handleSendMessageTimeout.
/**
* Handles a timeout waiting for a delivery update after sending a message.
*
* @param message The message for which the timeout occurred.
* @param sendMessageTimeout The timeout value.
* @param delivery The message delivery to release (may be {@code null}).
* @param sample The sample to log a timeout on (may be {@code null}).
* @param resultPromise The promise to fail with an exception (may be {@code null}).
* @param spanToLogToAndFinish The span to log the error to and finish (may be {@code null})
* @throws NullPointerException if message is {@code null}.
*/
protected void handleSendMessageTimeout(final Message message, final long sendMessageTimeout, final ProtonDelivery delivery, final SendMessageSampler.Sample sample, final Promise<ProtonDelivery> resultPromise, final Span spanToLogToAndFinish) {
Objects.requireNonNull(message);
final String linkOrConnectionClosedInfo = HonoProtonHelper.isLinkOpenAndConnected(sender) ? "" : " (link or connection already closed)";
final String exceptionMsg = "waiting for delivery update timed out after " + sendMessageTimeout + "ms";
final ServerErrorException exception = HonoProtonHelper.isLinkOpenAndConnected(sender) ? new SendMessageTimeoutException(exceptionMsg) : new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, exceptionMsg + linkOrConnectionClosedInfo);
logMessageSendingError("waiting for delivery update timed out for message [ID: {}, address: {}] after {}ms{}", message.getMessageId(), getMessageAddress(message), sendMessageTimeout, linkOrConnectionClosedInfo);
// (it would be enough to just settle the delivery without an outcome but that cannot be done with proton-j as of now)
if (delivery != null) {
ProtonHelper.released(delivery, true);
}
if (spanToLogToAndFinish != null) {
TracingHelper.logError(spanToLogToAndFinish, exception.getMessage());
Tags.HTTP_STATUS.set(spanToLogToAndFinish, HttpURLConnection.HTTP_UNAVAILABLE);
spanToLogToAndFinish.finish();
}
if (resultPromise != null) {
resultPromise.fail(exception);
}
if (sample != null) {
sample.timeout();
}
}
Aggregations