@Test public void propagatesTimeoutExceptionIfRequestTimesOut() { AsyncResult<HeldLocks> timedOutResult = new AsyncResult<>(); timedOutResult.timeout(); when(acquirer.acquireLocks(any(), any(), any())).thenReturn(timedOutResult); AsyncResult<?> result = lockService.lock(REQUEST_ID, descriptors(LOCK_A), DEADLINE); assertThat(result.isTimedOut()).isTrue(); }
@GuardedBy("this") private void processQueue() { while (!queue.isEmpty() && currentHolder == null) { LockRequest head = queue.dequeue(); if (!head.releaseImmediately) { currentHolder = head.requestId; } head.result.complete(null); } }
private void acquireLocks() { try { AsyncResult<Void> lockResult = AsyncResult.completedResult(); for (AsyncLock lock : locks.get()) { lockResult = lockResult.concatWith(() -> lockFunction.apply(lock)); } this.result = lockResult; } catch (Throwable t) { log.error("Error while acquiring locks"); unlockAll(); throw Throwables.propagate(t); } }
private boolean shouldRemove(AsyncResult<HeldLocks> lockResult) { return lockResult.isFailed() || lockResult.isTimedOut() || lockResult.test(HeldLocks::unlockIfExpired); }
/** * Tests the provided predicate against the value of this result, if it has completed successfully. If it has * not yet completed, or has failed or timed out, the predicate is not executed and {@code false} is returned. */ public boolean test(Predicate<T> predicateIfCompletedSuccessfully) { if (isCompletedSuccessfully()) { return predicateIfCompletedSuccessfully.test(get()); } return false; }
/** * Returns the error that caused this result to fail. * * @throws {@link IllegalStateException} if not failed. **/ public Throwable getError() { Preconditions.checkState(isFailed()); return getExceptionInternal(); }
@POST @Path("lock") public void lock(@Suspended final AsyncResponse response, LockRequest request) { AsyncResult<LockToken> result = timelock.lock(request); lockLog.registerRequest(request, result); result.onComplete(() -> { if (result.isFailed()) { response.resume(result.getError()); } else if (result.isTimedOut()) { response.resume(LockResponse.timedOut()); } else { response.resume(LockResponse.successful(result.get())); } }); }
@Test public void multipleQueuedRequestsCanObtainLock() { lockSynchronously(REQUEST_1); AsyncResult<Void> result2 = lockAsync(REQUEST_2); AsyncResult<Void> result3 = lockAsync(REQUEST_3); unlock(REQUEST_1); assertThat(result2.isCompletedSuccessfully()).isTrue(); assertThat(result3.isComplete()).isFalse(); unlock(REQUEST_2); assertThat(result3.isCompletedSuccessfully()).isTrue(); }
@Test public void removesRequestWhenFailedOrTimesOut() { AsyncResult<Void> result1 = new AsyncResult<>(); AsyncResult<Void> result2 = new AsyncResult<>(); awaitedLocks.getExistingOrAwait(REQUEST_1, () -> result1); awaitedLocks.getExistingOrAwait(REQUEST_1, () -> result2); result1.fail(new RuntimeException("test")); result2.timeout(); assertRequestsWereRemoved(REQUEST_1, REQUEST_2); }
/** * @return an AsyncResult whose value will be set to the value of this instance transformed by {@code mapper}, if * and when this instance completes successfully. If this instance fails or times out, {@code mapper} is never * called, and the returned AsyncResult will contain the error or timeout status associated with this instance. */ public <U> AsyncResult<U> map(Function<T, U> mapper) { return new AsyncResult<U>(future.thenApply(mapper)); }
@Test public void propagatesExceptionIfSynchronousLockAcquisitionFails() { AsyncResult<Void> lockResult = new AsyncResult<>(); RuntimeException error = new RuntimeException("foo"); lockResult.fail(error); doReturn(lockResult).when(lockA).lock(any()); AsyncResult<HeldLocks> acquisitions = acquire(lockA); assertThat(acquisitions.getError()).isEqualTo(error); }
@Test public void unlocksOnAsyncFailure() { AsyncResult<Void> lockResult = new AsyncResult<>(); lockResult.fail(new RuntimeException("foo")); doReturn(lockResult).when(lockC).lock(any()); AsyncResult<HeldLocks> acquisitions = acquire(lockA, lockB, lockC); assertThat(acquisitions.isFailed()).isTrue(); assertNotLocked(lockA); assertNotLocked(lockB); }
private LockToken mockHeldLocksForNewRequest(Consumer<HeldLocks> mockApplier) { LockToken request = LockToken.of(UUID.randomUUID()); HeldLocks heldLocks = mock(HeldLocks.class); mockApplier.accept(heldLocks); AsyncResult<HeldLocks> completedResult = new AsyncResult<>(); completedResult.complete(heldLocks); heldLocksCollection.getExistingOrAcquire(request.getRequestId(), () -> completedResult); return request; }
private LockToken mockTimedOutRequest() { LockToken request = LockToken.of(UUID.randomUUID()); AsyncResult timedOutResult = new AsyncResult(); timedOutResult.timeout(); heldLocksCollection.getExistingOrAcquire(request.getRequestId(), () -> timedOutResult); return request; }
/** Returns whether this result has failed. Use {@link #getError} to retrieve the associated exception. */ public boolean isFailed() { return future.isCompletedExceptionally() && !isTimedOut(); }
private void requestComplete( RequestInfo requestInfo, AsyncResult<?> result, long blockingTimeMillis) { events.requestComplete(blockingTimeMillis); if (blockingTimeMillis == 0 || blockingTimeMillis < thresholdMillis.get()) { return; } if (result.isCompletedSuccessfully()) { events.successfulSlowAcquisition(requestInfo, blockingTimeMillis); } else if (result.isTimedOut()) { events.timedOutSlowAcquisition(requestInfo, blockingTimeMillis); } }
private LockToken mockFailedRequest() { LockToken request = LockToken.of(UUID.randomUUID()); AsyncResult failedLocks = new AsyncResult(); failedLocks.fail(new RuntimeException()); heldLocksCollection.getExistingOrAcquire(request.getRequestId(), () -> failedLocks); return request; }
@Test public void outstandingRequestsReceiveNotCurrentLeaderExceptionOnClose() { lockSynchronously(REQUEST_1, LOCK_A); AsyncResult<LockToken> request2 = lock(REQUEST_2, LOCK_A); service.close(); assertThat(request2.isFailed()).isTrue(); assertThat(request2.getError()).isInstanceOf(NotCurrentLeaderException.class); }
private void assertLocked(String... locks) { AsyncResult<LockToken> result = lock(UUID.randomUUID(), locks); assertFalse(result.isComplete()); result.map(token -> service.unlock(token)); }
/** * Returns the successfully completed value immediately. * * @throws {@link IllegalStateException} if not completed successfully. **/ public T get() { Preconditions.checkState(isCompletedSuccessfully()); return future.join(); }