给CompletableFuture的orTimeout方法添加描述。

huangapple go评论68阅读模式
英文:

Add a description to CompletableFuture orTimeout

问题

以下是翻译好的部分:

这是我目前在调用该方法的调用者试图执行`get()`时得到的全部内容

java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException
    at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
    at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)

我需要添加更多信息 - 哪个操作已经超时超时多长时间等等换句话说我需要在异常中添加一些消息例如:“在15秒内发生了超时”,最好使用供应商supplier如下所示

CompletableFuture<ResutType> getSomething()
{
   return someFuture.orTimeout(15, SECONDS, () -> new TimeoutException("在15秒内发生了超时"));
}
英文:

What is the easy way to add some addition info into the timeout exception? Suppose I have a code like that:

CompletableFuture&lt;ResutType&gt; getSomething()
{
  ... return someFuture.orTimeout(15, SECONDS);
}

This is all I'm getting currently downstream later on when callers of the method trying to do get():

java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException
    at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
	at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)

I need to add more info - which operation has timed out, how long timeout was, etc.. In other words I need some message to the exception like "The Something timed-out in 15s", ideally use a supplier like:

CompletableFuture&lt;ResutType&gt; getSomething()
{
   return someFuture.orTimeout(15, SECONDS, () -&gt; new TimeoutException(&quot;The Something timed-out in 15s&quot;));
}

答案1

得分: 2

你可以使用与 CompletableFuture 内部的超时处理相同的设施:

public static <T> CompletableFuture<T> customTimeOut(
    CompletableFuture<T> cf, long time, TimeUnit unit,
    Supplier<? extends Exception> s) {

    CompletableFuture.delayedExecutor(time, unit, cf.defaultExecutor())
        .execute(() -> { if(!cf.isDone()) cf.completeExceptionally(s.get()); });

  return cf;
}

请注意,isDone() 检查并不是严格必要的,因为如果未来已经完成,completeExceptionally 不会执行任何操作。检查只是为了在不必要时防止潜在的昂贵异常构造。虽然未来在 isDone()completeExceptionally 调用之间被完成的可能性仍然存在,但这时不会发生任何不良情况,因为正如前面所说,completeExceptionally 会执行正确的操作。

你可以像这样进行测试:

public static void main(String[] args) {
    for(int timeOut = 1; timeOut < 5; timeOut += 2) {
        System.out.println("time-out " + timeOut + " sec");
  
        CompletableFuture<String> cf = customTimeOut(
            CompletableFuture.supplyAsync(() -> {
                LockSupport.parkNanos(2_000_000_000);
                return "result";
            }),
            timeOut, TimeUnit.SECONDS,
            () -> new TimeoutException("timeout with my custom message")
        );
    
        try {
            System.out.println("ordinary result: " + cf.join());
        }
        catch(Exception ex) {
            ex.printStackTrace(System.out);
        }
    }
}

Try it online!

英文:

You can use the same facility as the CompletableFuture’s internal timeout handling:

public static &lt;T&gt; CompletableFuture&lt;T&gt; customTimeOut(
    CompletableFuture&lt;T&gt; cf, long time, TimeUnit unit,
    Supplier&lt;? extends Exception&gt; s) {

    CompletableFuture.delayedExecutor(time, unit, cf.defaultExecutor())
        .execute(() -&gt; { if(!cf.isDone()) cf.completeExceptionally(s.get()); });

  return cf;
}

Note that the isDone() check is not strictly necessary, as completeExceptionally does nothing if the future is already completed. The check is only there to prevent the potential expensive exception construction when not necessary. It’s still possible, but rather unlikely that the future gets completed right between the isDone() and the completeExceptionally call, but nothing bad will happen then, as said, because completeExceptionally does the right thing anyway.

You can test it like

public static void main(String[] args) {
    for(int timeOut = 1; timeOut &lt; 5; timeOut += 2) {
        System.out.println(&quot;time-out &quot; + timeOut + &quot; sec&quot;);
  
        CompletableFuture&lt;String&gt; cf = customTimeOut(
            CompletableFuture.supplyAsync(() -&gt; {
                LockSupport.parkNanos(2_000_000_000);
                return &quot;result&quot;;
            }),
            timeOut, TimeUnit.SECONDS,
            () -&gt; new TimeoutException(&quot;timeout with my custom message&quot;)
        );
    
        try {
            System.out.println(&quot;ordinary result: &quot; + cf.join());
        }
        catch(Exception ex) {
            ex.printStackTrace(System.out);
        }
    }
}
time-out 1 sec
java.util.concurrent.CompletionException: java.util.concurrent.TimeoutException: timeout with my custom message
	at java.base/java.util.concurrent.CompletableFuture.reportJoin(CompletableFuture.java:413)
	at java.base/java.util.concurrent.CompletableFuture.join(CompletableFuture.java:2118)
	at CF.main(CF.java:32)
Caused by: java.util.concurrent.TimeoutException: timeout with my custom message
	at CF.lambda$2(CF.java:28)
	at CF.lambda$0(CF.java:13)
	at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
time-out 3 sec
ordinary result: result

[Try it online!](https://tio.run/##hVPBbtswDL37K7icbDQxsgK7zGmHoe1OW3tIexqGQVWYVIksGRLVxijy7RkVO07TJB0ByxD1SD49UnPxLAbzyWK9VmVlHcGcHXkgpXNpjQzOoaH8ypaVRhKPGn8ECg6L5CP4vSrxwSj6P8oGullKrEhZ8zFaW7nw@U9ex6GKsCPwaTAyZsojRCt0RZJU4VErCVIL7@GXUCZ5TYCt9XsSxL/R/SUcXDI6ZfBky0j1LlC6iYx2HDvtg7ZmBsTwPmxVgMBLH7acRt8Al4Rm4qG7@iX4DCKv0wXyCWpR4@RmiTKQdWlTpMktp3w8FUFTd5xlXa5oOW4OME0zGFzCK6hp@onDlL@2Bhkdc8imKHa8hNZ16vMZEiMKWGXFjqNDpmU4rNg4VskRWZ@tmkDJoqdjcsrMfv8B4WbtXRubMldlaKMZSwwX8LnoNiP4stucXcD528ho49oTljmPUV5xAdIm7UX8gD3Qg7NdMO88yl5WtPF7aQ7b2fCNPWVGJ4bgdK987HX93ddGbgU/iIr2ZpzzSrjFrTDWp@d/h8Ph9usI71urfs@h5673DkGrrH/ga8XYjWY@vrm6u70eH0Ib1gZf4P0zbQSO@r4oeoKybuWBEr0XM@ztD94b/nt@cvURUY7107qJMoLhzV2/bvrK0zq3PFfZO31WezspSD6lHXV@eNmRorhsio1JyMW9ExLTHY@T@Vft2K@S9fof "Java (JDK) – Try It Online")

答案2

得分: 1

你可以使用 exceptionallyCompose(或者这个方法的任何异步变体)来实现这个目标:

public final class Example {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                    sleep(3000);
                    return "DONE";
                })
                .orTimeout(1000, TimeUnit.MILLISECONDS)
                .exceptionallyCompose(throwable -> {
                    if (throwable instanceof TimeoutException) {
                        var tex = new TimeoutException("The Something timed-out in 15s");
                        return CompletableFuture.failedFuture(tex);
                    }
                    return CompletableFuture.failedFuture(throwable);
                });

        sleep(2000);
        future.get();
        sleep(5000);
    }

    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

如果你的代码因为 TimeoutException 而失败,你可以通过一个失败的 CompletableFuture 返回一个带有更多详细信息的 TimeoutException(或任何其他可抛出对象)。否则,你可以简单地返回引发异常的前一个阶段之一引发的相同的可抛出对象。

例如,如果在 supplyAsync 中出现了某个 RuntimeExceptionfuture.get() 仍然会引发一个 RuntimeException(包装在 ExecutionException 中)。如果没有异常(即也没有超时),则可以正常地调用 get() 获取结果。

有了这个知识,你可以编写自己的小型 orTimeout 包装器,例如:

public static <T> CompletableFuture<T> orTimeout(
        CompletableFuture<T> cf,
        long timeout, TimeUnit unit
) {
    return cf.orTimeout(timeout, unit).exceptionallyCompose(throwable -> {
        if (throwable instanceof TimeoutException) {
            final var msg = String.format(
                    "The Something timed-out in %d %s.",
                    timeout, unit
            );
            return CompletableFuture.failedFuture(new TimeoutException(msg));
        }
        return CompletableFuture.failedFuture(throwable);
    });
}

然后像这样为所有 CompletableFuture 使用它:

CompletableFuture<String> future = orTimeout(CompletableFuture.supplyAsync(() -> {
    sleep(10000);
    return "DONE";
}), 1000, TimeUnit.MILLISECONDS);
英文:

You can use exceptionallyCompose (or any of the async variants of this method) for that:

public final class Example {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; {
                    sleep(3000);
                    return &quot;DONE&quot;;
                })
                .orTimeout(1000, TimeUnit.MILLISECONDS)
                .exceptionallyCompose(throwable -&gt; {
                    if (throwable instanceof TimeoutException) {
                        var tex = new TimeoutException(&quot;The Something timed-out in 15s&quot;);
                        return CompletableFuture.failedFuture(tex);
                    }
                    return CompletableFuture.failedFuture(throwable);
                });

        sleep(2000);
        future.get();
        sleep(5000);
    }

    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

If your code fails due to a TimeoutException, you can return a TimeoutException (or any other throwable) that has more detail via a failed CompletableFuture. Otherwise, you can just return the same throwable that caused one of the previous stages to be completed exceptionally.

For example, if there had been some RuntimeException in supplyAsync, future.get() would still cause a RuntimeException (wrapped within an ExecutionException) to be thrown. If there is no exception (i.e. also no timeout), you get() your result normally.

With that knowledge you can write your own little orTimeout wrapper, for example:

public static &lt;T&gt; CompletableFuture&lt;T&gt; orTimeout(
        CompletableFuture&lt;T&gt; cf,
        long timeout, TimeUnit unit
) {
    return cf.orTimeout(timeout, unit).exceptionallyCompose(throwable -&gt; {
        if (throwable instanceof TimeoutException) {
            final var msg = String.format(
                    &quot;The Something timed-out in %d %s.&quot;,
                    timeout, unit
            );
            return CompletableFuture.failedFuture(new TimeoutException(msg));
        }
        return CompletableFuture.failedFuture(throwable);
    });
}

and then use it for all CompletableFutures like so:

CompletableFuture&lt;String&gt; future = orTimeout(CompletableFuture.supplyAsync(() -&gt; {
    sleep(10000);
    return &quot;DONE&quot;;
}), 1000, TimeUnit.MILLISECONDS);

答案3

得分: 0

Since you are expecting a timeout, consider making it part of the return type instead of dealing with the TimeoutException. One way is to change the result type to Either<Timeout, Result> using the Either type of the Vavr library.

var future = CompletableFuture.<Either<Timeout, Result>>supplyAsync(() ->
    Either.right(lengthyCalculation()))
.completeOnTimeout(
    Either.left(new Timeout("Lengthy Calculation timed out", 12)), 1, SECONDS);

Once the future completed and you got hold of its value, use one of the many methods of Either to proceed. In the simplest case:

Result result = future.get()
    .getOrElseThrow(timeout -> new RuntimeException(...));
英文:

Since you are expecting a timeout, consider making it part of the return type instead of dealing with the TimeoutException. One way is to change the result type to Either&lt;Timeout, Result&gt; using the Either type of the Vavr library.

var future = CompletableFuture.&lt;Either&lt;Timeout, Result&gt;&gt;supplyAsync(() -&gt;
    Either.right(lengthyCalculation()))
.completeOnTimeout(
    Either.left(new Timeout(&quot;Lengthy Calculation timed out&quot;, 12)), 1, SECONDS);

Once the future completed and you got hold of its value, use one of the many methods of Either to proceed. In the simplest case

Result result = future.get()
    .getOrElseThrow(timeout -&gt; new RuntimeException(...));

答案4

得分: 0

我会这样说。

CompletableFuture future = CompletableFuture.runAsync(() -> {
    try {
        someFuture.get(15, TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        throw new RuntimeException("详细消息", e);
    }
});

当然,你应该对异常进行更详细的说明,但我认为这是问题的要点。

英文:

Naively I would say.

CompletableFuture future = CompletableFuture.runAsync( () -&gt;{
    try{
        someFuture.get(15, TimeUnit.MILLISECONDS);
    } catch(Exception e){
        throw new RuntimeException(&quot;detailed message&quot;, e);
    }
});

Of course you should be a bit more specific about the exceptions, but I think this is the gist of the issue.

huangapple
  • 本文由 发表于 2023年6月19日 18:25:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76505733.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定