85 lines
4.2 KiB
Plaintext
85 lines
4.2 KiB
Plaintext
= 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]
|
|
----
|