use of io.pravega.segmentstore.contracts.tables.BadKeyVersionException in project pravega by pravega.
the class ContainerKeyIndex method update.
/**
* Performs a Batch Update or Removal.
*
* If {@link TableKeyBatch#isConditional()} returns true, this will execute an atomic Conditional Update/Removal based
* on the condition items in the batch ({@link TableKeyBatch#getVersionedItems}. The entire TableKeyBatch will be
* conditioned on those items, including those items that do not have a condition set. The entire TableKeyBatch will
* either all be committed as one unit or not at all.
*
* Otherwise this will perform an Unconditional Update/Removal, where all the {@link TableKeyBatch.Item}s in
* {@link TableKeyBatch#getItems()} will be applied regardless of whether they already exist or what their versions are.
*
* @param segment The Segment to perform the update/removal on.
* @param batch The {@link TableKeyBatch} to apply.
* @param persist A Supplier that, when invoked, will persist the contents of the batch to the Segment and return
* a CompletableFuture to indicate when the operation is done, containing the offset at which the
* batch has been written to the Segment. This Future must complete successfully before the effects
* of the {@link TableKeyBatch} are applied to the in-memory Index or before downstream conditional
* updates on the affected keys are initiated.
* @param timer Timer for the operation.
* @return A CompletableFuture that, when completed, will contain a list of offsets (within the Segment) where each
* of the items in the batch has been persisted. If the update failed, it will be failed with the appropriate exception.
* Notable exceptions:
* <ul>
* <li>{@link KeyNotExistsException} If a Key in the TableKeyBatch does not exist and was conditioned as having to exist.
* <li>{@link BadKeyVersionException} If a Key does exist but had a version mismatch.
* </ul>
*/
CompletableFuture<List<Long>> update(DirectSegmentAccess segment, TableKeyBatch batch, Supplier<CompletableFuture<Long>> persist, TimeoutTimer timer) {
Exceptions.checkNotClosed(this.closed.get(), this);
Supplier<CompletableFuture<List<Long>>> update;
if (batch.isConditional()) {
// Conditional update.
// Collect all Cache Keys for the Update Items that have a condition on them; we need this on order to
// serialize execution across them.
val keys = batch.getVersionedItems().stream().map(item -> Maps.immutableEntry(segment.getSegmentId(), item.getHash())).collect(Collectors.toList());
// Serialize the execution (queue it up to run only after all other currently queued up conditional updates
// for touched keys have finished).
update = () -> this.conditionalUpdateProcessor.add(keys, () -> validateConditionalUpdate(segment, batch, timer).thenComposeAsync(v -> persist.get(), this.executor).thenApplyAsync(batchOffset -> updateCache(segment, batch, batchOffset), this.executor));
} else {
// Unconditional update: persist the entries and update the cache.
update = () -> persist.get().thenApplyAsync(batchOffset -> updateCache(segment, batch, batchOffset), this.executor);
}
// Throttle any requests, if needed.
return this.segmentTracker.throttleIfNeeded(segment, update, batch.getLength());
}
use of io.pravega.segmentstore.contracts.tables.BadKeyVersionException in project pravega by pravega.
the class PravegaRequestProcessor method handleException.
private Void handleException(long requestId, String segment, long offset, String operation, Throwable u) {
if (u == null) {
IllegalStateException exception = new IllegalStateException("No exception to handle.");
log.error(requestId, "Error (Segment = '{}', Operation = '{}')", segment, operation, exception);
throw exception;
}
u = Exceptions.unwrap(u);
String clientReplyStackTrace = replyWithStackTraceOnError ? Throwables.getStackTraceAsString(u) : EMPTY_STACK_TRACE;
final Consumer<Throwable> failureHandler = t -> {
log.error(requestId, "Error (Segment = '{}', Operation = '{}')", segment, "handling result of " + operation, t);
connection.close();
};
if (u instanceof StreamSegmentExistsException) {
log.info(requestId, "Segment '{}' already exists and cannot perform operation '{}'.", segment, operation);
invokeSafely(connection::send, new SegmentAlreadyExists(requestId, segment, clientReplyStackTrace), failureHandler);
} else if (u instanceof StreamSegmentNotExistsException) {
log.warn(requestId, "Segment '{}' does not exist and cannot perform operation '{}'.", segment, operation);
invokeSafely(connection::send, new NoSuchSegment(requestId, segment, clientReplyStackTrace, offset), failureHandler);
} else if (u instanceof StreamSegmentSealedException) {
log.info(requestId, "Segment '{}' is sealed and cannot perform operation '{}'.", segment, operation);
invokeSafely(connection::send, new SegmentIsSealed(requestId, segment, clientReplyStackTrace, offset), failureHandler);
} else if (u instanceof ContainerNotFoundException) {
int containerId = ((ContainerNotFoundException) u).getContainerId();
log.warn(requestId, "Wrong host. Segment = '{}' (Container {}) is not owned. Operation = '{}').", segment, containerId, operation);
invokeSafely(connection::send, new WrongHost(requestId, segment, "", clientReplyStackTrace), failureHandler);
} else if (u instanceof ReadCancellationException) {
log.info(requestId, "Sending empty response on connection {} while reading segment {} due to CancellationException.", connection, segment);
invokeSafely(connection::send, new SegmentRead(segment, offset, true, false, EMPTY_BUFFER, requestId), failureHandler);
} else if (u instanceof CancellationException) {
log.info(requestId, "Closing connection {} while performing {} due to {}.", connection, operation, u.toString());
connection.close();
} else if (u instanceof TokenExpiredException) {
log.warn(requestId, "Expired token during operation {}", operation);
invokeSafely(connection::send, new AuthTokenCheckFailed(requestId, clientReplyStackTrace, AuthTokenCheckFailed.ErrorCode.TOKEN_EXPIRED), failureHandler);
} else if (u instanceof TokenException) {
log.warn(requestId, "Token exception encountered during operation {}.", operation, u);
invokeSafely(connection::send, new AuthTokenCheckFailed(requestId, clientReplyStackTrace, AuthTokenCheckFailed.ErrorCode.TOKEN_CHECK_FAILED), failureHandler);
} else if (u instanceof UnsupportedOperationException) {
log.warn(requestId, "Unsupported Operation '{}'.", operation, u);
invokeSafely(connection::send, new OperationUnsupported(requestId, operation, clientReplyStackTrace), failureHandler);
} else if (u instanceof BadOffsetException) {
BadOffsetException badOffset = (BadOffsetException) u;
log.info(requestId, "Segment '{}' is truncated and cannot perform operation '{}' at offset '{}'", segment, operation, offset);
invokeSafely(connection::send, new SegmentIsTruncated(requestId, segment, badOffset.getExpectedOffset(), clientReplyStackTrace, offset), failureHandler);
} else if (u instanceof TableSegmentNotEmptyException) {
log.warn(requestId, "Table segment '{}' is not empty to perform '{}'.", segment, operation);
invokeSafely(connection::send, new TableSegmentNotEmpty(requestId, segment, clientReplyStackTrace), failureHandler);
} else if (u instanceof KeyNotExistsException) {
log.warn(requestId, "Conditional update on Table segment '{}' failed as the key does not exist.", segment);
invokeSafely(connection::send, new WireCommands.TableKeyDoesNotExist(requestId, segment, clientReplyStackTrace), failureHandler);
} else if (u instanceof BadKeyVersionException) {
log.warn(requestId, "Conditional update on Table segment '{}' failed due to bad key version.", segment);
invokeSafely(connection::send, new WireCommands.TableKeyBadVersion(requestId, segment, clientReplyStackTrace), failureHandler);
} else if (errorCodeExists(u)) {
log.warn(requestId, "Operation on segment '{}' failed due to a {}.", segment, u.getClass());
invokeSafely(connection::send, new WireCommands.ErrorMessage(requestId, segment, u.getMessage(), WireCommands.ErrorMessage.ErrorCode.valueOf(u.getClass())), failureHandler);
} else {
logError(requestId, segment, operation, u);
// Closing connection should reinitialize things, and hopefully fix the problem
connection.close();
throw new IllegalStateException("Unknown exception.", u);
}
return null;
}
use of io.pravega.segmentstore.contracts.tables.BadKeyVersionException in project pravega by pravega.
the class FixedKeyLengthTableSegmentLayout method handleConditionalUpdateException.
private <T> CompletableFuture<T> handleConditionalUpdateException(CompletableFuture<T> update, SegmentProperties segmentInfo) {
return update.exceptionally(ex -> {
ex = Exceptions.unwrap(ex);
if (ex instanceof BadAttributeUpdateException) {
val bau = (BadAttributeUpdateException) ex;
ex = bau.isPreviousValueMissing() ? new KeyNotExistsException(segmentInfo.getName(), bau.getAttributeId().toBuffer()) : new BadKeyVersionException(segmentInfo.getName(), Collections.emptyMap());
}
throw new CompletionException(ex);
});
}
use of io.pravega.segmentstore.contracts.tables.BadKeyVersionException in project pravega by pravega.
the class ContainerKeyIndexTests method testConditionalUpdateFailure.
/**
* Tests the ability of the {@link ContainerKeyIndex} to reject conditional updates if the condition does not match
* the state of the Key (version mismatch or key not exists).
*/
@Test
public void testConditionalUpdateFailure() {
@Cleanup val context = new TestContext();
Supplier<CompletableFuture<Long>> noPersist = () -> Futures.failedFuture(new AssertionError("Not expecting persist to be invoked."));
val keyData = generateUnversionedKeys(1, context).get(0).getKey();
// Key not exists, but we conditioned on it existing.
AssertExtensions.assertSuppliedFutureThrows("update() allowed conditional update on inexistent key conditioned on existing.", () -> context.index.update(context.segment, toUpdateBatch(TableKey.versioned(keyData, 0L)), noPersist, context.timer), ex -> ex instanceof KeyNotExistsException && ((KeyNotExistsException) ex).getKey().equals(keyData));
// Create the key. We must actually write something to the segment here as this will be used in the subsequent
// calls to validate the key.
val s = new EntrySerializer();
val toUpdate = TableEntry.versioned(keyData, new ByteArraySegment(new byte[100]), TableKey.NOT_EXISTS);
val toWrite = s.serializeUpdate(Collections.singleton(toUpdate));
context.index.update(context.segment, toUpdateBatch(toUpdate.getKey()), () -> context.segment.append(toWrite, null, TIMEOUT), context.timer).join();
// Key exists, but we conditioned on it not existing.
AssertExtensions.assertSuppliedFutureThrows("update() allowed conditional update on existent key conditioned on not existing.", () -> context.index.update(context.segment, toUpdateBatch(TableKey.versioned(keyData, TableKey.NOT_EXISTS)), noPersist, context.timer), ex -> ex instanceof BadKeyVersionException && keyMatches(((BadKeyVersionException) ex).getExpectedVersions(), keyData));
// Key exists, but we conditioned on the wrong value.
AssertExtensions.assertSuppliedFutureThrows("update() allowed conditional update on inexistent key conditioned on existing.", () -> context.index.update(context.segment, toUpdateBatch(TableKey.versioned(keyData, 123L)), noPersist, context.timer), ex -> ex instanceof BadKeyVersionException && keyMatches(((BadKeyVersionException) ex).getExpectedVersions(), keyData));
}
use of io.pravega.segmentstore.contracts.tables.BadKeyVersionException in project pravega by pravega.
the class ContainerKeyIndexTests method testConditionalUpdateFailureReconciliation.
/**
* Tests the ability of the {@link ContainerKeyIndex} to reconcile conditional updates if the condition does not match
* the given Key's Bucket offset, but it matches the Key's offset (this is a corner scenario in a situation with multiple
* Keys sharing the same bucket).
*/
@Test
public void testConditionalUpdateFailureReconciliation() {
val hasher = KeyHashers.CONSTANT_HASHER;
@Cleanup val context = new TestContext();
Supplier<CompletableFuture<Long>> noPersist = () -> Futures.failedFuture(new AssertionError("Not expecting persist to be invoked."));
val keys = generateUnversionedKeys(2, context);
// First, write two keys with the same hash (and serialize them to the Segment).
val versions = new HashMap<BufferView, Long>();
for (val key : keys) {
val s = new EntrySerializer();
val toUpdate = TableEntry.unversioned(key.getKey(), new ByteArraySegment(new byte[100]));
val toWrite = s.serializeUpdate(Collections.singleton(toUpdate));
val r = context.index.update(context.segment, toUpdateBatch(hasher, Collections.singletonList(toUpdate.getKey())), () -> context.segment.append(toWrite, null, TIMEOUT), context.timer).join();
versions.put(key.getKey(), r.get(0));
}
// We want to remove the first key.
val keyToRemove = keys.get(0);
val validVersion = versions.get(keyToRemove.getKey());
val invalidVersion = validVersion + 1;
// Verify that a bad version won't work.
AssertExtensions.assertSuppliedFutureThrows("update() allowed conditional update with invalid version.", () -> context.index.update(context.segment, toUpdateBatch(hasher, Collections.singletonList(TableKey.versioned(keyToRemove.getKey(), invalidVersion))), noPersist, context.timer), ex -> ex instanceof BadKeyVersionException);
// Verify that the key's version (which is different than the bucket's version), works.
AtomicBoolean persisted = new AtomicBoolean();
context.index.update(context.segment, toUpdateBatch(hasher, Collections.singletonList(TableKey.versioned(keyToRemove.getKey(), validVersion))), () -> {
persisted.set(true);
return CompletableFuture.completedFuture(context.segment.getInfo().getLength());
}, context.timer).join();
Assert.assertTrue("Expected persisted to have been invoked after reconciled conditional update.", persisted.get());
}
Aggregations