use of com.yahoo.pulsar.broker.service.BrokerServiceException.SubscriptionInvalidCursorPosition in project pulsar by yahoo.
the class PersistentTopics method resetCursor.
@POST
@Path("/{property}/{cluster}/{namespace}/{destination}/subscription/{subName}/resetcursor/{timestamp}")
@ApiOperation(value = "Reset subscription to message position closest to absolute timestamp (in ms).", notes = "There should not be any active consumers on the subscription.")
@ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission"), @ApiResponse(code = 404, message = "Topic does not exist"), @ApiResponse(code = 405, message = "Not supported for global topics"), @ApiResponse(code = 412, message = "Subscription has active consumers") })
public void resetCursor(@PathParam("property") String property, @PathParam("cluster") String cluster, @PathParam("namespace") String namespace, @PathParam("destination") @Encoded String destination, @PathParam("subName") String subName, @PathParam("timestamp") long timestamp, @QueryParam("authoritative") @DefaultValue("false") boolean authoritative) {
destination = decode(destination);
DestinationName dn = DestinationName.get(domain(), property, cluster, namespace, destination);
PartitionedTopicMetadata partitionMetadata = getPartitionedTopicMetadata(property, cluster, namespace, destination, authoritative);
if (partitionMetadata.partitions > 0) {
int numParts = partitionMetadata.partitions;
int numPartException = 0;
Exception partitionException = null;
try {
for (int i = 0; i < numParts; i++) {
pulsar().getAdminClient().persistentTopics().resetCursor(dn.getPartition(i).toString(), subName, timestamp);
}
} catch (PreconditionFailedException pfe) {
// throw the last exception if all partitions get this error
// any other exception on partition is reported back to user
++numPartException;
partitionException = pfe;
} catch (Exception e) {
log.warn("[{}] [{}] Failed to reset cursor on subscription {} to time {}", clientAppId(), dn, subName, timestamp, e);
throw new RestException(e);
}
// report an error to user if unable to reset for all partitions
if (numPartException == numParts) {
log.warn("[{}] [{}] Failed to reset cursor on subscription {} to time {}", clientAppId(), dn, subName, timestamp, partitionException);
throw new RestException(Status.PRECONDITION_FAILED, partitionException.getMessage());
} else if (numPartException > 0 && log.isDebugEnabled()) {
log.debug("[{}][{}] partial errors for reset cursor on subscription {} to time {} - ", clientAppId(), destination, subName, timestamp, partitionException);
}
} else {
validateAdminOperationOnDestination(dn, authoritative);
log.info("[{}][{}] received reset cursor on subscription {} to time {}", clientAppId(), destination, subName, timestamp);
PersistentTopic topic = getTopicReference(dn);
try {
PersistentSubscription sub = topic.getPersistentSubscription(subName);
checkNotNull(sub);
sub.resetCursor(timestamp).get();
log.info("[{}][{}] reset cursor on subscription {} to time {}", clientAppId(), dn, subName, timestamp);
} catch (Exception e) {
Throwable t = e.getCause();
log.warn("[{}] [{}] Failed to reset cursor on subscription {} to time {}", clientAppId(), dn, subName, timestamp, e);
if (e instanceof NullPointerException) {
throw new RestException(Status.NOT_FOUND, "Subscription not found");
} else if (e instanceof NotAllowedException) {
throw new RestException(Status.METHOD_NOT_ALLOWED, e.getMessage());
} else if (t instanceof SubscriptionBusyException) {
throw new RestException(Status.PRECONDITION_FAILED, "Subscription has active connected consumers");
} else if (t instanceof SubscriptionInvalidCursorPosition) {
throw new RestException(Status.PRECONDITION_FAILED, "Unable to find position for timestamp specified -" + t.getMessage());
} else {
throw new RestException(e);
}
}
}
}
use of com.yahoo.pulsar.broker.service.BrokerServiceException.SubscriptionInvalidCursorPosition in project pulsar by yahoo.
the class PersistentSubscription method resetCursor.
@Override
public CompletableFuture<Void> resetCursor(long timestamp) {
CompletableFuture<Void> future = new CompletableFuture<>();
PersistentMessageFinder persistentMessageFinder = new PersistentMessageFinder(topicName, cursor);
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Resetting subscription to timestamp {}", topicName, subName, timestamp);
}
persistentMessageFinder.findMessages(timestamp, new AsyncCallbacks.FindEntryCallback() {
@Override
public void findEntryComplete(Position position, Object ctx) {
final Position finalPosition;
if (position == null) {
// this should not happen ideally unless a reset is requested for a time
// that spans beyond the retention limits (time/size)
finalPosition = cursor.getFirstPosition();
if (finalPosition == null) {
log.warn("[{}][{}] Unable to find position for timestamp {}. Unable to reset cursor to first position", topicName, subName, timestamp);
future.completeExceptionally(new SubscriptionInvalidCursorPosition("Unable to find position for specified timestamp"));
return;
}
log.info("[{}][{}] Unable to find position for timestamp {}. Resetting cursor to first position {} in ledger", topicName, subName, timestamp, finalPosition);
} else {
finalPosition = position;
}
if (!IS_FENCED_UPDATER.compareAndSet(PersistentSubscription.this, FALSE, TRUE)) {
future.completeExceptionally(new SubscriptionBusyException("Failed to fence subscription"));
return;
}
final CompletableFuture<Void> disconnectFuture;
if (dispatcher != null && dispatcher.isConsumerConnected()) {
disconnectFuture = dispatcher.disconnectAllConsumers();
} else {
disconnectFuture = CompletableFuture.completedFuture(null);
}
disconnectFuture.whenComplete((aVoid, throwable) -> {
if (throwable != null) {
log.error("[{}][{}] Failed to disconnect consumer from subscription", topicName, subName, throwable);
IS_FENCED_UPDATER.set(PersistentSubscription.this, FALSE);
future.completeExceptionally(new SubscriptionBusyException("Failed to disconnect consumers from subscription"));
return;
}
log.info("[{}][{}] Successfully disconnected consumers from subscription, proceeding with cursor reset", topicName, subName);
try {
cursor.asyncResetCursor(finalPosition, new AsyncCallbacks.ResetCursorCallback() {
@Override
public void resetComplete(Object ctx) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Successfully reset subscription to timestamp {}", topicName, subName, timestamp);
}
IS_FENCED_UPDATER.set(PersistentSubscription.this, FALSE);
future.complete(null);
}
@Override
public void resetFailed(ManagedLedgerException exception, Object ctx) {
log.error("[{}][{}] Failed to reset subscription to timestamp {}", topicName, subName, timestamp, exception);
IS_FENCED_UPDATER.set(PersistentSubscription.this, FALSE);
// or should we just ask user to retry one more time?
if (exception instanceof InvalidCursorPositionException) {
future.completeExceptionally(new SubscriptionInvalidCursorPosition(exception.getMessage()));
} else if (exception instanceof ConcurrentFindCursorPositionException) {
future.completeExceptionally(new SubscriptionBusyException(exception.getMessage()));
} else {
future.completeExceptionally(new BrokerServiceException(exception));
}
}
});
} catch (Exception e) {
log.error("[{}][{}] Error while resetting cursor", topicName, subName, e);
IS_FENCED_UPDATER.set(PersistentSubscription.this, FALSE);
future.completeExceptionally(new BrokerServiceException(e));
}
});
}
@Override
public void findEntryFailed(ManagedLedgerException exception, Object ctx) {
// todo - what can go wrong here that needs to be retried?
if (exception instanceof ConcurrentFindCursorPositionException) {
future.completeExceptionally(new SubscriptionBusyException(exception.getMessage()));
} else {
future.completeExceptionally(new BrokerServiceException(exception));
}
}
});
return future;
}
Aggregations