/** * Closes the Operation Queue and fails all Operations in it with the given exception. * * @param causingException The exception to fail with. If null, it will default to ObjectClosedException. */ private void closeQueue(Throwable causingException) { // Close the operation queue and extract any outstanding Operations from it. Collection<CompletableOperation> remainingOperations = this.operationQueue.close(); if (remainingOperations != null && remainingOperations.size() > 0) { // If any outstanding Operations were left in the queue, they need to be failed. // If no other cause was passed, assume we are closing the queue because we are shutting down. Throwable failException = causingException != null ? causingException : new CancellationException(); cancelIncompleteOperations(remainingOperations, failException); } // The commit queue will auto-close when we are done and it itself is empty. We just need to unblock it in case // it was idle and waiting on a pending take() operation. this.commitQueue.cancelPendingTake(); }
@Override protected CompletableFuture<Void> doRun() { // The QueueProcessor is responsible with the processing of externally added Operations. It starts when the // OperationProcessor starts and is shut down as soon as doStop() is invoked. val queueProcessor = Futures .loop(this::isRunning, () -> throttle() .thenComposeAsync(v -> this.operationQueue.take(MAX_READ_AT_ONCE), this.executor) .thenAcceptAsync(this::processOperations, this.executor), this.executor); // The CommitProcessor is responsible with the processing of those Operations that have already been committed to // DurableDataLong and now need to be added to the in-memory State. // As opposed from the QueueProcessor, this needs to process all pending commits and not discard them, even when // we receive a stop signal (from doStop()), otherwise we could be left with an inconsistent in-memory state. val commitProcessor = Futures .loop(() -> isRunning() || this.commitQueue.size() > 0, () -> this.commitQueue.take(MAX_COMMIT_QUEUE_SIZE) .thenAcceptAsync(this::processCommits, this.executor), this.executor) .whenComplete((r, ex) -> { // The CommitProcessor is done. Safe to close its queue now, regardless of whether it failed or // shut down normally. this.commitQueue.close(); if (ex != null) { throw new CompletionException(ex); } }); return CompletableFuture.allOf(queueProcessor, commitProcessor) .exceptionally(this::iterationErrorHandler); }
/** * Tests the ability of the queue to cancel a take() request if it is closed. */ @Test public void testCloseCancel() throws Exception { @Cleanup BlockingDrainingQueue<Integer> queue = new BlockingDrainingQueue<>(); CompletableFuture<Queue<Integer>> result = queue.take(MAX_READ_COUNT); Collection<Integer> queueContents = queue.close(); // Verify result. AssertExtensions.assertThrows( "Future was not cancelled with the correct exception.", () -> result.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS), ex -> ex instanceof CancellationException); Assert.assertEquals("Queue.close() returned an item even though it was empty.", 0, queueContents.size()); }
private CompletableFuture<Void> processCatchupRead(StoreReader.ReadItem toValidate) { if (toValidate instanceof EndItem) { this.catchupQueue.close(); return CompletableFuture.completedFuture(null); } final Timer timer = new Timer(); return this.reader .readExact(this.streamName, toValidate.getAddress()) .handleAsync((actualRead, ex) -> { ValidationResult validationResult; try { if (ex == null) { validationResult = compareReads(toValidate, actualRead.getEvent()); Event e = toValidate.getEvent(); this.testState.recordDuration(ConsumerOperationType.CATCHUP_READ, timer.getElapsed().toMillis()); this.testState.recordCatchupRead(e.getTotalLength()); } else { validationResult = ValidationResult.failed(ex.getMessage()); } } catch (Throwable ex2) { validationResult = ValidationResult.failed(String.format("General failure: Ex = %s.", ex2)); } if (!validationResult.isSuccess()) { validationResult.setAddress(toValidate.getAddress()); validationFailed(ValidationSource.CatchupRead, validationResult); } return null; }, this.executorService); }
/** * Tests the ability of the queue to return its contents when it is closed. */ @Test public void testCloseResult() throws Exception { @Cleanup BlockingDrainingQueue<Integer> queue = new BlockingDrainingQueue<>(); populate(queue); Collection<Integer> queueContents = queue.close(); // Verify result. Assert.assertEquals("Unexpected result size from Queue.close().", ITEM_COUNT, queueContents.size()); int expectedValue = 0; for (int value : queueContents) { Assert.assertEquals("Unexpected value in Queue result.", expectedValue, value); expectedValue++; } }