Issue
I have a method where I expect the following sequence of calls:
- sendUpdateCallback
- sendSuccessCallback
Here's the place I'm calling from:
@Override
public String onPaymentInitiationAuthorizationSuccess(
@NotEmpty String userId,
@NotEmpty String paymentExtra,
@NotEmpty String paymentProduct
) {
log.error("onPaymentInitiationAuthorizationSuccess tag");
Map<String, String> paymentExtraMap = parseExtra(paymentExtra);
String sessionSecret = paymentExtraMap.get(SDKConstants.KEY_SESSION_SECRET);
String status = getStatusOfPaymentProduct(paymentProduct);
SessionSuccessCallbackRequest params = new SessionSuccessCallbackRequest(userId, status);
sessionsCallbackService.sendUpdateCallback(sessionSecret, params);
if (!StringUtils.isEmpty(sessionSecret)) sessionsCallbackService.sendSuccessCallback(sessionSecret, params);
return paymentExtraMap.getOrDefault(SDKConstants.KEY_RETURN_TO_URL, "");
}
Here is the code of my methods:
@Service
public class SessionsCallbackService extends CallbackRestClient {
private static final Logger log = LoggerFactory.getLogger(SessionsCallbackService.class);
@Async
public void sendUpdateCallback(String sessionSecret, BaseCallbackRequest params) {
log.error("sendUpdateCallback");
String url = createCallbackRequestUrl(createSessionPath(sessionSecret) + "/update");
sendSessionCallback(url, sessionSecret, params);
}
@Async
public void sendSuccessCallback(String sessionSecret, BaseCallbackRequest params) {
log.error("sendSuccessCallback");
String url = createCallbackRequestUrl(createSessionPath(sessionSecret) + "/success");
sendSessionCallback(url, sessionSecret, params);
}
As you can see from the screenshot, it is clear that I first call the sendUpdateCallback
method and after sendSuccessCallback
, but we see on requests that sendSuccessCallback
is implemented faster, how can this be fixed?
Solution
To fix the problem (make asynchronous (void) "service" (methods) synchronous), you could (if the effort is worth/not too big), refactor your void
(semi-blocking) method to (Completable)Future<Void>
like:
@Async
public CompletableFuture<Void> sendUpdateCallback(String sessionSecret, BaseCallbackRequest params) {
log.error("sendUpdateCallback");
String url = createCallbackRequestUrl(createSessionPath(sessionSecret) + "/update");
sendSessionCallback(url, sessionSecret, params);
return CompletableFuture.completedFuture(null);
}
... then to "block" it, you would:
CompletableFuture<Void> updateResult = sessionsCallbackService.sendUpdateCallback(sessionSecret, params);
if (!StringUtils.isEmpty(sessionSecret)) {
updateResult.get(); // also possible: get(long timeout, TimeUnit unit)
if (updateResult.isCancelled()) {
// ...will not happen in my answer, but for completeness..
} else if (updateResult.isCompletedExceptionally()) {
// handle/throw exception?
} else if (updateResult.isDone()) {
// finally send success
sessionsCallbackService.sendSuccessCallback(sessionSecret, params);
}
}
But easiest (& convincing) is: Ervin's comment
- CompletableFuture - javadoc v.17
- Using SpringFrameWork @Async for methods that return void
- Spring @Async with CompletableFuture
Answered By - xerx593