添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

What I am trying to do:
Background: Latency is critical. I have 2 methods that return basically the same information, but will execute differently. One will be on average faster, but may by chance sometimes be slower. The second is on average slower, but more reliable.

So the requirements are:

  • I have 2 CompletableFutures. I want to take the first one that returns.
  • When an exception occurs in the first future, I want to fallback to the second.
  • When an exception occurs in the second future, I want to generate an exceptional response.
  • Because of #2 and #3, if an exception occurs in both, I want to generate an exceptional response.
  • Existing Code Attempt:

    public Foo getFoo() {
        CompletableFuture<Foo> firstFuture = CompletableFuture
            .supplyAsync(this::doOptimisticWork, this.executor)
            .timeout(this.timeout, TimeUnit.MILLISECONDS));
        CompletableFuture<Foo> secondFuture = CompletableFuture
            .supplyAsync(this::doFallbackWork, this.executor)
            .timeout(this.timeout, TimeUnit.MILLISECONDS))
            .exceptionally(this::getExceptionalResponse);
        CompletableFuture<Foo> compositeFuture = CompletableFuture
            .anyOf(firstFuture, secondFuture)
            .exceptionally(throwable -> secondFuture.get())
            .thenApply(Foo.class::cast);
        return compositeFuture.join;
    

    This works exactly as expected for the following cases:

  • If firstFuture returns first, that is the overall result.
  • If secondFuture returns first, that is the overall result.
  • If firstFuture encounters an exception (by doOptimisticWork) then we fallback to secondFuture.
  • If secondFuture encounters an exception, we directly generate the exceptional response.
  • If firstFuture times out, then already we returned secondFuture so this is actually the same case as #2.
  • If secondFuture times out, then already we returned firstFuture so this is actually the same case as #1.
  • This does not work for the following case:

  • Both firstFuture and secondFuture timeout. In this case,
  • I expect that the compositeFuture completes exceptionally (some random chance on which future triggers this, since they both have the same timeout). This triggers the exceptionally clause in teh completableFuture, which then waits on firstFuture to complete. Then the firstFuture should encounter a timeout exception, which triggers it to fall back to its own (secondFuture) exceptionally clause, where we generate an exceptional response.

    I added a bunch of println to check the state of each CompletableFuture at each step. In actuality what is happening is that the compositeFuture completes exceptionally (as expected), and then we wait on the secondFuture to complete. The secondFuture then seems to ignore its own timeout, and simply waits indefinitely for its supplier to complete. This is a problem since now the whole sequence is waiting on this as the compositeFuture join method is blocked.

    I am suspicious about the close timing of both of firstFuture and secondFuture, but am not able to reproduce any conclusive behavior by injecting different timeouts. Interestingly I can ensure with unit tests that this works as expected when the do<>Work methods throw exceptions, the only problem seems to be with the CF configured timeouts.

    Question:
    How can we nest 2 CompleteableFutures with their own timeouts into a composite CompletableFuture, and get the overall sequence to honor the timeout?

    I have a doubt. If second CF is the fallback scenario, then is not like you need both or any result at once, you need to evaluate the result from optimistic work, and if that failed evaluate the result from fallback work. I mean this because you're using CompletableFuture#anyOf which defies this idea. Or there's something I'm not getting right. – Luiggi Mendoza Aug 10, 2021 at 18:12 I will update the description with more information. I agree, I am not 100% sure that anyOf is the right thing to use. The overarching idea is that I want very fast execution, and I have 2 methods (doOptimisticWork and doFallbackWork) to execute in parallel. So for the sake of speed, I want to take the first that returns. So the anyOf (and really the reason for wrapping two CF into a composite one) is in order to take the first that returns. – Mike Aug 10, 2021 at 18:18 That's what I don't understand. You say you want to get any of those results, but then you state that if it happens to be doOptimisticWork the first method to finish, then you need to evaluate if is a proper result or an exception. What happens if doFallbackWork finishes first with an exception and no timeout was given? Would you wait to see the result of doOptimisticWork to see if there was an exception or would you rethrow the exception from doFallbackWork? – Luiggi Mendoza Aug 10, 2021 at 18:22 If doOptimisticWork returns successfully then I want to take that. If it returns exceptionally, then I want to fallback to doFallbackWork. If doFallbackWork returns exceptionally, then I want to accept that as the final answer. In this case, we can simply ignore the result of doOptimisticWork. – Mike Aug 10, 2021 at 18:35 Does this answer your question? CompletableFuture: Waiting for first one normally return? – Tim Moore Aug 11, 2021 at 6:19

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.