Spring #Transactional not working with CompletableFuture [duplicate] - java

Idea
I have a processing method which takes in a list of items and processes them asynchronously using external web service. The process steps also persist data while processing. At the end of whole process, I want to persist the whole process along with each processed results as well.
Problem
I convert each item in the list into CompletableFuture and run a processing task on them, and put them back into an array of futures. Now using its .ofAll method (in sequence method) to complete future when all the submitted tasks are completed and return another CompletableFuture which holds the result.
When I want to get that result, I call .whenComplete(..), and would want to set the returned result into my entity as data, and then persist to the database, however the repository save call just does nothing and continues threads just continue running, it's not going past the repository save call.
#Transactional
public void process(List<Item> items) {
List<Item> savedItems = itemRepository.save(items);
final Process process = createNewProcess();
final List<CompletableFuture<ProcessData>> futures = savedItems.stream()
.map(item -> CompletableFuture.supplyAsync(() -> doProcess(item, process), executor))
.collect(Collectors.toList());
sequence(futures).whenComplete((data, throwable) -> {
process.setData(data);
processRepository.save(process); // <-- transaction lost?
log.debug("Process DONE"); // <-- never reached
});
}
Sequence method
private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList())
);
}
What is happening? Why is the persist call not passing. Is the thread that started the transaction not able to commit the transaction or where does it get lost? All the processed data returns fine and is all good. I've tried different transaction strategies, but how is it possible to control which thread is gonna finish the transaction, if it's the case?
Any advice?

The reason of your problem is, as said above, that the transaction ends
when the return of method process(..) is reached.
What you can do, is create the transaction manually, that gives you full
control over when it starts and ends.
Remove #Transactional
Autowire the TransactionManager then in process(..) :
TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(txDef);
try {
//do your stuff here like
doWhateverAsync().then(transactionManager.commit(txStatus);)
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}

In case of Spring Boot Application , you need following configurations.
The main application method should be annotated with #EnableAsync.
#Async annotation should be on the top of method having #Transactional annotation. This is necessary to indicate processing will be taking place in child thread.

Related

Thousands of rest calls with spring boot

Let's say that we have the following entities: Project and Release, which is a one to many relationship.
Upon an event consumption from an SQS queue where a release id is sent as part of the event, there might be scenarios where we might have to create thousands of releases in our DB, where for each release we have to make a rest call to a 3rd party service in order to get some information for each release.
That means that we might have to make thousands of calls, in some cases more than 20k calls just to retrieve the information for the different releases and store it in the DB.
Obviously this is not scalable, so I'm not really sure what's the way to go in this scenario.
I know I might use a CompletableFuture, but I'm not sure how to use that with spring.
The http client that I am using is WebClient.
Any ideas?
You can make the save queries in a method transactional by adding the annotation #Transactional above the method signature. The method should also be public, or else this annotation is ignored.
As for using CompletableFuture in spring; You could make a http call method asynchronous by adding the #Async annotation above its signature and by letting it return a CompletableFuture as a return type. You should return a completed future holding the response value from the http call. You can easily make a completed future with the method CompletableFuture.completedFuture(yourValue). Spring will only return the completed future once the asynchronous method is done executing everything int its code block. For #Async to work you must also add the #EnableAsync annotation to one of your configuration classes. On top of that the #Async annotated method must be public and cannot be called by a method from within the same class. If the method is private or is called from within the same class then the #Async annotation will be ignored and instead the method will be executed in the same thread as the calling method is executed.
Next to an #Async annotated method you could also use a parallelStream to execute all 20K http calls in parallel. For example:
List<Long> releaseIds = new ArrayList<>();
Map<Long,ReleaseInfo> releaseInfo = releaseIds.parallelStream().map(releaseId -> new AbstractMap.SimpleEntry<>(releaseId, webClient.getReleaseInfo(releaseId)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Lastly you could also use a ThreadPoolExecutor to execute the http calls in parallel. An example:
List<Long> releaseIds = new ArrayList<>();
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //I've made the amount of threads in the pool equal to the amount of available CPU processors on the machine.
//Submit tasks to the executor
List<Future<ReleaseInfo>> releaseInfoFutures = releaseIds.stream().map(releaseId -> executor.submit(() -> webClient.getReleaseInfo(releaseId)).collect(Collectors.toList());
//Wait for all futures to complete and map all non-null values to ReleaseInfo list.
List<ReleaseInfo> releaseInfo = releaseInfoFutures.stream().map(this::getValueAfterFutureCompletion).filter(releaseInfo -> releaseInfo != null).collect(Collectors.toList());
private ReleaseInfo getValueAfterFutureCompletion(Future<ReleaseInfo> future){
ReleaseInfo releaseInfo = null;
try {
releaseInfo = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
return releaseInfo;
}
}
Make sure to call shutdownNow() on ThreadPoolExecutor after you're done with it to avoid memory leaks.

Spring Boot Webclient - Merge

I want to merge 2 responses and return a Flux.
private Flux<Response<List<Company>, Error>> loopGet(List<Entity> registries, Boolean status) {
return Flux.fromIterable(registries)
.flatMap(this::sendGetRequest)
.mergeWith(Mono.just(fetch(status)));
}
This is what I am doing, is working but I would like the merge to wait before calling the Mono.just (fetch (status)).
I'll explain, sendGetRequest returns a Mono that makes an API call and from the result saves things to db. Subsequently the merge goes to call the db with the fetch method, but that data is not updated yet. If I then make the call again, I get the updated data.
You need concatWith and fromCallable to ensure that fetch is called lazily after the get requests are finished.
private Flux<Response<List<Company>, Error>> loopGet(List<Entity> registries, Boolean status) {
return Flux.fromIterable(registries)
.flatMap(this::sendGetRequest)
.concatWith(Mono.fromCallable(() -> fetch(status)));
}

Spring Cassandra Repository - Saving a record in a background thread

I've been working on creating a record in my database on a background thread but I don't get any response in the console (No errors, exceptions or logs).
Below is the code
In my spring component I have:
ExecutorService tPool = Executors.newFixedThreadPool(15);
//This is a repository that extends CassandraRepository
#Autowired
MyRepository myRepository;
CompletableFuture<Boolean> myBool = CompletableFuture.supplyAsync(() -> {
//processing here
return new doSomeProcessing(arg1, arg2, arg3).process();
}, tPool);
myBool.whenComplete((isTrue, throwThis) -> {
if(isTrue) {
// do something
}
});
In my Class doSomeProcessing, I have the method process():
public boolean process() {
//This appears in the console
LOG.info("About to try and save the record on the background thread");
//After setting the repository in the thread class constructor
myRepository.save(arg1, arg2, arg3);
//This doesn't appear in the console
LOG.info("The record should be saved");
}
But the database doesn't show any new records and the console doesn't show any errors or exceptions or the last log statement.
How would you go about saving a record on a background thread using Spring with Cassandra?
Any explanation is greatly appreciated.
I've seen and tried the below with the async service and transactional as well as a few others:
How do I use JpaRepository in a backend thread?
How do I properly do a background thread when using Spring Data and Hibernate?
When a CompletableFuture is completed exceptionally, there's no stacktrace because the exception is still unhandled. It's stored until the user does something that "activates" that exception. For example calling get() would directly throw that exception.
When doing more complex things with CompletableFuture the exception is stored along the results of the future (hence BiConsumer to have result and exception as parameters), but it's up to you to check if there is an exception and handle it.
Since you can chain the futures and therefore encounter multiple exceptions, you end up with documentation like the following:
If the supplied action itself encounters an exception, then the
returned stage exceptionally completes with this exception unless this
stage also completed exceptionally.
If you understand that on the first read, you're talented.

Wrapping blocking I/O in project reactor

I have a spring-webflux API which, at a service layer, needs to read from an existing repository which uses JDBC.
Having done some reading on the subject, I would like to keep the execution of the blocking database call separate from the rest of my non-blocking async code.
I have defined a dedicated jdbcScheduler:
#Bean
public Scheduler jdbcScheduler() {
return Schedulers.fromExecutor(Executors.newFixedThreadPool(maxPoolSize));
}
And an AsyncWrapper utility to use it:
#Component
public class AsyncJdbcWrapper {
private final Scheduler jdbcScheduler;
#Autowired
public AsyncJdbcWrapper(Scheduler jdbcScheduler) {
this.jdbcScheduler = jdbcScheduler;
}
public <T> Mono<T> async(Callable<T> callable) {
return Mono.fromCallable(callable)
.subscribeOn(jdbcScheduler)
.publishOn(Schedulers.parallel());
}
}
Which is then used to wrap jdbc calls like so:
Mono<Integer> userIdMono = asyncWrapper.async(() -> userDao.getUserByUUID(request.getUserId()))
.map(userOption -> userOption.map(u -> u.getId())
.orElseThrow(() -> new IllegalArgumentException("Unable to find user with ID " + request.getUserId())));
I've got two questions:
1) Am I correctly pushing the execution of blocking calls to another set of threads? Being fairly new to this stuff I'm struggling with the intricacies of subscribeOn()/publishOn().
2) Say I want to make use of the resulting mono, e.g call an API with the result of the userIdMono, on which scheduler will that be executed? The one specifically created for the jdbc calls, or the main(?) thread that reactor usually operates within? e.g.
userIdMono.map(id -> someApiClient.call(id));
1) Use of subscribeOn is correctly putting the JDBC work on the jdbcScheduler
2) Neither, the results of the Callable - while computed on the jdbcScheduler, are publishOn the parallel Scheduler, so your map will be executed on a thread from the Schedulers.parallel() pool (rather than hogging the jdbcScheduler).

Does DeferredResult have a race condition when returning a potentially already-set instance?

I'm using DeferredResult in my Spring MVC application to handle some server-side processing of a potentially long-running action. It might be very fast, or it could take a second or two.
But in either case, the incoming HTTP request causes an action to be pushed to a queue, which a separate thread (via an ExecutorService) is responsible for consuming. A callback is then called, notifying the pusher that the operation has completed.
I refactored some of this behavior into a utility method:
public static DeferredResult<String> toResponse(GameManager gameManager, final Player player, Action action) {
DeferredResult<String> deferredResult = new DeferredResult<>();
gameManager.execute(action, new Handler<Result>() {
#Override
public void handle(Result result) {
JSONObject obj;
try {
obj = gameManager.getGameJSON(player);
obj.put("success", result.getResult());
obj.put("message", result.getMessage());
deferredResult.setResult(obj.toString()); // POINT B
} catch (JSONException e) {
deferredResult.setErrorResult(e);
}
}
});
return deferredResult; // POINT A
}
But I'm wondering what happens if the execution of the action happens so quickly that the setResult() method is called (POINT B) on the DeferredResult before it has been returned (POINT A) to the calling method.
Will Spring see the returned DeferredResult already has a value and handle it, or does it only begin "watching" for the setter to be called after the instance has been provided?
I've not used Spring but would say that Class DeferredResult<> would be a pretty poor implementation of a Deferred if settlement timing made any difference to the downstream behaviour.
It seems safe to assume that the behaviour would be identical regardless of asynchronous process' timing - milliseconds, seconds or whatever, with the only proviso that a timeout didn't occur in which case the onTimeout handler would run (if set). Even if the Deferred was settled synchronously, in the same code block that created it, the caller function should act on the outcome as expected.
If this assumption is not valid then the Class DeferredResult<> is not fit for purpose and shouldn't be used.

Categories