Example of asynchronous timeout of CompletableFuture processing in Java

Source: Internet
Author: User
Tags memory usage throwable

Java is an object-oriented programming language that can write cross-platform applications. Java technology is widely used in PC, data center, game console, scientific supercomputer, mobile phone and internet with excellent versatility, efficiency, platform portability and security, it also has the largest developer community in the world.

Public void serve () throws InterruptedException, ExecutionException, TimeoutException {
Final Future <Response> responseFuture = asyncCode ();
Final Response response = responseFuture. get (1, SECONDS );
Send (response );
}
Private void send (Response response ){
//...
}

This is an Akka application written in Java and uses a thread pool containing 1000 threads (originally !) -- All threads are blocked in this get. The processing speed of the system cannot keep up with the number of concurrent requests. After reconstruction, we have killed all of these threads and kept only one, greatly reducing the memory usage. Here is a simple example of Java 8. The first step is to use CompletableFuture to replace a simple Future (see Tip 9 ).

To submit a task to ExecutorService, you only need to use CompletableFuture. supplyAsync (..., ExecutorService) to replace executorService. submit (...) You can.
Process callback-based APIs: Use promises
Otherwise (if you have used a blocked API or Future <T>), many threads will be blocked. That's why so many asynchronous APIs are annoying. So, let's rewrite the previous code to receive

Public void serve () throws InterruptedException, ExecutionException, TimeoutException {
Final CompletableFuture <Response> responseFuture = asyncCode ();
Final Response response = responseFuture. get (1, SECONDS );
Send (response );
}

Obviously, this cannot solve any problem. We must also use the new style for programming:


Public void serve (){
Final CompletableFuture <Response> responseFuture = asyncCode ();
ResponseFuture. thenAccept (this: send );
}

This function is equivalent, but serve () only runs for a short period of time (does not block or wait ). Remember: this: send will be executed in the same thread that completes responseFuture. If you don't want to spend too much time reload an existing thread pool or the send () method, you can consider using thenAcceptAsync (this: send, sendPool, however, we lose two important attributes: exception propagation and timeout. Exception propagation is hard to implement because we have changed the API. When serve () exists, asynchronous operations may not be completed yet. If you are concerned about exceptions, you can consider returning responseFutureor or other optional mechanisms. At least, there should be abnormal logs, otherwise the exception will be swallowed up.


Final CompletableFuture <Response> responseFuture = asyncCode ();
ResponseFuture. exceptionally (throwable-> {
Log. error ("Unrecoverable error", throwable );
Return null;
});

Please be careful when the above code: predictionally () tries to recover from failure and returns an optional result. Although this part can work normally, if you use chained call to predictionally () and withthenAccept (), the send () method will still be called even if the call fails, and a null parameter will be returned, or any other value returned from the predictionally () method.


ResponseFuture
. Exceptionally (throwable-> {
Log. error ("Unrecoverable error", throwable );
Return null;
})
. ThenAccept (this: send); // probably not what you think

The problem of one-second timeout loss is very clever. The original code can be waited (blocked) for at most 1 second before the Future is completed. Otherwise, a TimeoutException will be thrown. We have lost this function. Even worse, unit test timeout is not very convenient and we often skip this step. To maintain the timeout mechanism without disrupting the event-driven principle, we need to establish an additional module: a Future that will inevitably fail after a given time.


Public static <T> CompletableFuture <T> failAfter (Duration duration ){
Final CompletableFuture <T> promise = new CompletableFuture <> ();
Schedle. schedule ()-> {
Final TimeoutException ex = new TimeoutException ("Timeout after" + duration );
Return promise. completeExceptionally (ex );
}, Duration. toMillis (), MILLISECONDS );
Return promise;
}

Private static final ScheduledExecutorService scheduler =
Executors. newScheduledThreadPool (
1,
New ThreadFactoryBuilder ()
. SetDaemon (true)
. SetNameFormat ("failAfter-% d ")
. Build ());

This is simple: we create a promise (there is no Future for background tasks or thread pools), and then a TimeoutException exception will be thrown after the given java. time. Duration. If get () is called Somewhere to obtain the Future, TimeoutException will be thrown when the blocking time reaches the specified time.

In fact, it is an ExecutionException that encapsulates TimeoutException. Note that I use a thread pool with a fixed thread. This is not just for the purpose of teaching: this is a scenario where "one thread should be able to meet the needs of anyone. FailAfter () is not very useful, but if it is used with ourresponseFuture, we can solve this problem.


Final CompletableFuture <Response> responseFuture = asyncCode ();
Final CompletableFuture <Response> oneSecondTimeout = failAfter (Duration. ofSeconds (1 ));
ResponseFuture
. AcceptEither (oneSecondTimeout, this: send)
. Exceptionally (throwable-> {
Log. error ("Problem", throwable );
Return null;
});

Many other things have been done here. When a background task receives responseFuture, we also create a "merged" oneSecondTimeout future. This will never be executed when the task is successful, but it will fail in 1 second. Now we join these two called acceptEither. This operation will first complete the code block of the Future, and simply ignore the slow running of responseFuture or oneSecondTimeout. If the asyncCode () code is executed within one second, this: send will be called, and the oneSecondTimeout exception will not be thrown. However, if the execution of asyncCode () is really slow, the oneSecondTimeout exception will be thrown first. If a task fails due to an exception, the exceptionallyerror processor is called instead of the this: send method. You can choose to execute send () or predictionally, but not both. For example, if two "normal" Future operations are completed normally, the first response will call the send () method, and the subsequent ones will be discarded.

This is not the clearest solution. A clearer solution is to wrap the original Future and ensure that it can be executed within a given period of time. This operation applies to com. twitter. util. the Future is feasible (Scala is called within (), but scala. concurrent. this feature is not available in Future (presumably to encourage the previous approach ). We will not discuss how to execute Scala at the moment. We will first implement a CompletableFuture-like operation. It accepts a Future as the input and returns a Future, which is completed when the background task is completed. However, if the underlying Future execution takes too long, an exception is thrown:


Public static <T> CompletableFuture <T> within (CompletableFuture <T> future, Duration duration ){
Final CompletableFuture <T> timeout = failAfter (duration );
Return future. applyToEither (timeout, Function. identity ());
}

This leads us to achieve the ultimate, clear, and flexible method:


Final CompletableFuture <Response> responseFuture = (
AsyncCode (), Duration. ofSeconds (1 ));
ResponseFuture
. ThenAccept (this: send)
. Exceptionally (throwable-> {
Log. error ("Unrecoverable error", throwable );
Return null;
});

I hope you like this article because you already know that implementing responsive programming in Java is no longer a problem.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.