-
Notifications
You must be signed in to change notification settings - Fork 38.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce Future or void return declaration for each asynchronously executed method (e.g. with class-level @Async) #27734
Comments
The documentation states the limitations in the return types only So I the return type isn't a As a solution an exception would be better imho with a clear message stating that only |
As the documentation states and as @mdeinum pointed out, the return type must I think this can be closed, unless @jhoeller you see some opportunity to bypass methods that don't return void or Future. |
I'm inclined to explicitly throw an exception for non-Future/void return type declarations whenever we attempt to execute a method asynchronously. While this may not be much of an issue with an explicit annotated method, a class-level |
Noob question @jhoeller since I assume you systematically do an instanceof/reflection check, and that could incur a slowdown, wouldn't it be better to have this check only enabled on dev/debug mode et disabled on release mode? |
@LifeIsStrange as you can see in the code, what is done is a As for your question about the
Nevertheless, it doesn't apply to this fix. |
spring-framework/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
Lines 113 to 127 in 79d3f5c
I have found an odd behaviour working with
@Async
-annotated classes in Spring. Please note that there is a fundamental error in my code. Unfortunately, this post has to be long and detailed.Let's say I have already made a synchronous REST API generated by Swagger generator. Following code omits all documentation-level annotations
This API is easily implemented via
RestTemplate
, but I won't discuss the inner details.Now, suppose I want to provide an async version to developers consuming the API. What I have done is to create another interface with some search&replace-fu 🥋🥋
With the search&replace, I basically created an async-ish version of every method that should be backed by Spring's
@Async
annotation. My original idea was that synchronous methods can be invoked as they are, but if you instantiateTaxonomiesApiAsync
you also have access to the async version.I have discovered I made a fundamental mistake by applying the
@Async
annotation at interface level when the class contains both sync and async methods. I found that synchronousdisableItem
was performed in the same@Async
context. Accoding to design (correctly), Spring found the@Async
annotation at interface level so every method, including inherited ones, was invoked asynchronously.But the method always returned null. By debugging and looking at the code, I found that Spring tries to resolve the return value of the invoked method only if it's a
Future
. What if the returned value is a Present object?That means that if the returned value is not a
Future<ResponseEntity<GenericTaxonomyItem>>
but rather just aResponseEntity<GenericTaxonomyItem>
Spring neither throws an exception nor returns that value directly.Example of working calling code (invoking a different method)
Example of non-working code; the result of the CompletableFuture is always null.
In this code, I decided not to use the executor embedded in the API service, but rather the executor injected in the consuming service. So I ran a sync method in an executor, expecting the same result.
Since I spent one hour debugging that problem, I decided to spend more of my after-work time to document the issue here.
Proposed fix
In the code I linked, if the
instanceof
check fails the returned value is simply null. I don't yet understand the implications, but what about not unwrapping the value from Future if that's not a future? I meanreturn result
The text was updated successfully, but these errors were encountered: