|
21 | 21 | import java.util.Collections; |
22 | 22 | import java.util.List; |
23 | 23 | import java.util.Objects; |
| 24 | +import java.util.Optional; |
| 25 | +import java.util.concurrent.Callable; |
24 | 26 | import java.util.concurrent.CompletableFuture; |
25 | 27 | import java.util.concurrent.CompletionStage; |
| 28 | +import java.util.concurrent.Executor; |
26 | 29 | import java.util.concurrent.Executors; |
27 | 30 | import java.util.concurrent.Future; |
28 | 31 | import java.util.concurrent.ScheduledExecutorService; |
29 | 32 | import java.util.concurrent.ThreadFactory; |
30 | 33 | import java.util.concurrent.TimeUnit; |
31 | 34 | import java.util.concurrent.TimeoutException; |
| 35 | +import java.util.concurrent.atomic.AtomicReference; |
32 | 36 | import java.util.function.BiConsumer; |
33 | 37 | import java.util.function.Consumer; |
34 | 38 | import java.util.function.Function; |
@@ -131,6 +135,14 @@ public static <T> DependentPromise<T> dependent(CompletionStage<T> stage) { |
131 | 135 | public static <T> DependentPromise<T> dependent(Promise<T> stage) { |
132 | 136 | return DependentPromise.from(stage); |
133 | 137 | } |
| 138 | + |
| 139 | + public static Promise<Void> task(Executor executor) { |
| 140 | + return CompletableTask.asyncOn(executor); |
| 141 | + } |
| 142 | + |
| 143 | + public static <T> Promise<T> task(CompletionStage<T> stage, Executor executor) { |
| 144 | + return dependent(task(executor)).thenCombineAsync(stage, (u, v) -> v, PromiseOrigin.PARAM_ONLY); |
| 145 | + } |
134 | 146 |
|
135 | 147 | private static <T, R> CompletablePromise<R> createLinkedPromise(CompletionStage<T> stage) { |
136 | 148 | return new CompletablePromise<R>() { |
@@ -517,6 +529,78 @@ public static <T> Promise<T> failAfter(long delay, TimeUnit timeUnit) { |
517 | 529 | return failAfter( toDuration(delay, timeUnit) ); |
518 | 530 | } |
519 | 531 |
|
| 532 | + |
| 533 | + public static <T> Promise<T> poll(Callable<? extends T> codeBlock, Executor executor, RetryPolicy retryPolicy) { |
| 534 | + return pollOptional(() -> Optional.ofNullable(codeBlock.call()), executor, retryPolicy); |
| 535 | + } |
| 536 | + |
| 537 | + public static <T> Promise<T> pollOptional(Callable<Optional<? extends T>> codeBlock, Executor executor, RetryPolicy retryPolicy) { |
| 538 | + final CompletablePromise<T> promise = new CompletablePromise<>(); |
| 539 | + final AtomicReference<Promise<?>> callPromiseRef = new AtomicReference<>(); |
| 540 | + // Cleanup latest timeout on completion; |
| 541 | + promise.whenComplete( |
| 542 | + (r, e) -> |
| 543 | + Optional |
| 544 | + .of(callPromiseRef) |
| 545 | + .map(AtomicReference::get) |
| 546 | + .ifPresent( p -> p.cancel(true) ) |
| 547 | + ); |
| 548 | + RetryContext ctx = RetryContext.initial(retryPolicy); |
| 549 | + pollOnce(codeBlock, executor, ctx, promise, callPromiseRef); |
| 550 | + return promise; |
| 551 | + } |
| 552 | + |
| 553 | + private static <T> void pollOnce(Callable<Optional<? extends T>> codeBlock, |
| 554 | + Executor executor, RetryContext ctx, |
| 555 | + CompletablePromise<T> resultPromise, |
| 556 | + AtomicReference<Promise<?>> callPromiseRef) { |
| 557 | + |
| 558 | + // Promise may be cancelled outside of polling |
| 559 | + if (resultPromise.isDone()) { |
| 560 | + return; |
| 561 | + } |
| 562 | + |
| 563 | + long executionDelayMillis = ctx.executionDelayMillis(); |
| 564 | + if (executionDelayMillis >= 0) { |
| 565 | + Runnable doCall = () -> { |
| 566 | + long startTime = System.currentTimeMillis(); |
| 567 | + try { |
| 568 | + Optional<? extends T> result = codeBlock.call(); |
| 569 | + if (result.isPresent()) { |
| 570 | + resultPromise.onSuccess(result.get()); |
| 571 | + } else { |
| 572 | + long finishTime = System.currentTimeMillis(); |
| 573 | + RetryContext nextCtx = ctx.getNextRetry(finishTime - startTime); |
| 574 | + pollOnce(codeBlock, executor, nextCtx, resultPromise, callPromiseRef); |
| 575 | + } |
| 576 | + } catch (Exception ex) { |
| 577 | + long finishTime = System.currentTimeMillis(); |
| 578 | + RetryContext nextCtx = ctx.getNextRetry(finishTime - startTime, ex); |
| 579 | + pollOnce(codeBlock, executor, nextCtx, resultPromise, callPromiseRef); |
| 580 | + } |
| 581 | + }; |
| 582 | + |
| 583 | + Promise<?> callPromise; |
| 584 | + if (executionDelayMillis > 0) { |
| 585 | + // Timeout itself |
| 586 | + Promise<?> timeout = delay(Duration.ofMillis(executionDelayMillis)); |
| 587 | + // Call should be done via CompletableTask to let it be interruptible |
| 588 | + callPromise = dependent(task(executor)).runAfterBothAsync(timeout, doCall, PromiseOrigin.PARAM_ONLY); |
| 589 | + } else { |
| 590 | + // Immediately send to executor |
| 591 | + callPromise = CompletableTask.runAsync(doCall, executor); |
| 592 | + } |
| 593 | + callPromiseRef.set(callPromise); |
| 594 | + // If result promise is cancelled after callPromise was set need to stop; |
| 595 | + if (resultPromise.isDone()) { |
| 596 | + callPromise.cancel(true); |
| 597 | + return; |
| 598 | + } |
| 599 | + |
| 600 | + } else { |
| 601 | + resultPromise.onFailure(ctx.getLastThrowable()); |
| 602 | + } |
| 603 | + } |
520 | 604 |
|
521 | 605 | private static <T> Promise<T> unwrap(CompletionStage<List<T>> original, boolean unwrapException) { |
522 | 606 | return from( |
|
0 commit comments