blob: 6967c27bfaa8c28c6546269f305cb185e9e29f0a [file] [log] [blame]
= Introduction Resilience
Fineract had handcrafted retry loops in place for the longest time. A typical retry code would have looked like this:
.Legacy retry code
[source,java]
----
@Override
@SuppressWarnings("AvoidHidingCauseException")
@SuppressFBWarnings(value = {
"DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once")
public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
boolean isApprovedByChecker = false;
// check if is update of own account details
if (wrapper.isUpdateOfOwnUserDetails(this.context.authenticatedUser(wrapper).getId())) {
// then allow this operation to proceed.
// maker checker doesn't mean anything here.
isApprovedByChecker = true; // set to true in case permissions have
// been maker-checker enabled by
// accident.
} else {
// if not user changing their own details - check user has
// permission to perform specific task.
this.context.authenticatedUser(wrapper).validateHasPermissionTo(wrapper.getTaskPermissionName());
}
validateIsUpdateAllowed();
final String json = wrapper.getJson();
CommandProcessingResult result = null;
JsonCommand command;
int numberOfRetries = 0; // <1>
int maxNumberOfRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxRetriesOnDeadlock();
int maxIntervalBetweenRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxIntervalBetweenRetries();
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(json);
command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(),
wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(),
wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId(), wrapper.getCreditBureauId(),
wrapper.getOrganisationCreditBureauId(), wrapper.getJobName());
while (numberOfRetries <= maxNumberOfRetries) { // <2>
try {
result = this.processAndLogCommandService.executeCommand(wrapper, command, isApprovedByChecker);
numberOfRetries = maxNumberOfRetries + 1; // <3>
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
log.debug("The following command {} has been retried {} time(s)", command.json(), numberOfRetries);
/***
* Fail if the transaction has been retired for maxNumberOfRetries
**/
if (numberOfRetries >= maxNumberOfRetries) {
log.warn("The following command {} has been retried for the max allowed attempts of {} and will be rolled back",
command.json(), numberOfRetries);
throw exception;
}
/***
* Else sleep for a random time (between 1 to 10 seconds) and continue
**/
try {
int randomNum = RANDOM.nextInt(maxIntervalBetweenRetries + 1);
Thread.sleep(1000 + (randomNum * 1000));
numberOfRetries = numberOfRetries + 1; // <4>
} catch (InterruptedException e) {
throw exception;
}
} catch (final RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
numberOfRetries = maxNumberOfRetries + 1; // <3>
result = this.processAndLogCommandService.logCommand(e.getCommandSourceResult());
}
}
return result;
}
----
<1> counter
<2> `while` loop
<3> increment to abort
<4> increment
For better code quality and readability we introduced https://resilience4j.readme.io/docs[Resilience4j]:
.Annotation based retry
[source,java]
----
include::{rootdir}/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java[lines=49..76]
----