FINERACT-1678: Improvements around locking

This commit is contained in:
Adam Saghy
2023-03-31 12:17:04 +02:00
parent 5a18f00e2b
commit e7268997bc
25 changed files with 105 additions and 84 deletions
@@ -32,6 +32,8 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@@ -69,7 +71,8 @@ public class InternalLoanAccountLockApiResource implements InitializingBean {
log.warn(" ");
log.warn("------------------------------------------------------------");
LoanAccountLock loanAccountLock = new LoanAccountLock(loanId, LockOwner.valueOf(lockOwner));
LoanAccountLock loanAccountLock = new LoanAccountLock(loanId, LockOwner.valueOf(lockOwner),
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
loanAccountLockRepository.save(loanAccountLock);
return Response.status(Response.Status.ACCEPTED).build();
}
@@ -29,9 +29,7 @@ import javax.persistence.Table;
import javax.persistence.Version;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
@Entity
@Table(name = "m_loan_account_locks")
@@ -63,11 +61,11 @@ public class LoanAccountLock {
@Column(name = "lock_placed_on_cob_business_date")
private LocalDate lockPlacedOnCobBusinessDate;
public LoanAccountLock(Long loanId, LockOwner lockOwner) {
public LoanAccountLock(Long loanId, LockOwner lockOwner, LocalDate lockPlacedOnCobBusinessDate) {
this.loanId = loanId;
this.lockOwner = lockOwner;
this.lockPlacedOn = DateUtils.getOffsetDateTimeOfTenant();
this.lockPlacedOnCobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
this.lockPlacedOnCobBusinessDate = lockPlacedOnCobBusinessDate;
}
public void setError(String errorMessage, String stacktrace) {
@@ -78,6 +76,5 @@ public class LoanAccountLock {
public void setNewLockOwner(LockOwner newLockOwner) {
this.lockOwner = newLockOwner;
this.lockPlacedOn = DateUtils.getOffsetDateTimeOfTenant();
this.lockPlacedOnCobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
}
}
@@ -23,7 +23,6 @@ import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.exceptions.LoanAccountWasAlreadyLockedOrProcessed;
import org.apache.fineract.cob.exceptions.LoanReadException;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@@ -51,9 +50,6 @@ public abstract class AbstractLoanItemReader implements ItemReader<Loan> {
try {
if (remainingData.size() > 0) {
loanId = remainingData.remove(0);
if (alreadyLockedOrProcessedAccounts != null && alreadyLockedOrProcessedAccounts.remove(loanId)) {
throw new LoanAccountWasAlreadyLockedOrProcessed(loanId);
}
return loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
}
} catch (Exception e) {
@@ -30,12 +30,14 @@ import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.jdbc.core.JdbcTemplate;
@Slf4j
@RequiredArgsConstructor
@@ -43,6 +45,7 @@ public class ApplyLoanLockTasklet implements Tasklet {
private final LoanAccountLockRepository accountLockRepository;
private final FineractProperties fineractProperties;
private final JdbcTemplate jdbcTemplate;
@Override
public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
@@ -61,23 +64,28 @@ public class ApplyLoanLockTasklet implements Tasklet {
List<Long> toBeProcessedLoanIds = new ArrayList<>(alreadySoftLockedAccountsMap.keySet());
for (Long loanId : toBeProcessedLoanIds) {
upgradeToHardLock(loanId, alreadySoftLockedAccountsMap);
}
upgradeToHardLock(toBeProcessedLoanIds);
toBeProcessedLoanIds.addAll(alreadyLockedByChunkProcessingAccountIds);
List<Long> alreadyLockedByInlineCOBOrProcessedLoanIds = new ArrayList<>(loanIds);
alreadyLockedByInlineCOBOrProcessedLoanIds.removeAll(toBeProcessedLoanIds);
executionContext.put(LoanCOBConstant.ALREADY_LOCKED_BY_INLINE_COB_OR_PROCESSED_LOAN_IDS,
new ArrayList<>(alreadyLockedByInlineCOBOrProcessedLoanIds));
loanIds.removeAll(alreadyLockedByInlineCOBOrProcessedLoanIds);
return RepeatStatus.FINISHED;
}
private void upgradeToHardLock(Long loanId, Map<Long, LoanAccountLock> alreadySoftLockedAccountsMap) {
LoanAccountLock loanAccountLock = alreadySoftLockedAccountsMap.get(loanId);
// Upgrade lock
loanAccountLock.setNewLockOwner(LockOwner.LOAN_COB_CHUNK_PROCESSING);
accountLockRepository.save(loanAccountLock);
private void upgradeToHardLock(List<Long> accountsToLock) {
jdbcTemplate.batchUpdate("""
UPDATE m_loan_account_locks SET version= version + 1, lock_owner = ?, lock_placed_on = ? WHERE loan_id = ?
""", accountsToLock, getInClauseParameterSizeLimit(), (ps, id) -> {
ps.setString(1, LockOwner.LOAN_COB_CHUNK_PROCESSING.name());
ps.setObject(2, DateUtils.getOffsetDateTimeOfTenant());
ps.setLong(3, id);
});
}
private int getInClauseParameterSizeLimit() {
return fineractProperties.getQuery().getInClauseParameterSizeLimit();
}
}
@@ -38,7 +38,6 @@ import org.springframework.batch.core.configuration.annotation.StepBuilderFactor
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.listener.ExecutionContextPromotionListener;
import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -46,6 +45,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.support.TransactionTemplate;
@Configuration
@@ -78,6 +78,8 @@ public class LoanCOBWorkerConfiguration {
@Autowired
private FineractProperties fineractProperties;
@Autowired
private JdbcTemplate jdbcTemplate;
@Bean(name = LoanCOBConstant.LOAN_COB_WORKER_STEP)
public Step loanCOBWorkerStep() {
@@ -103,13 +105,13 @@ public class LoanCOBWorkerConfiguration {
.<Loan, Loan>chunk(propertyService.getChunkSize(JobName.LOAN_COB.name())).reader(cobWorkerItemReader())
.processor(cobWorkerItemProcessor()).writer(cobWorkerItemWriter()).faultTolerant().skip(Exception.class)
.skipLimit(propertyService.getChunkSize(JobName.LOAN_COB.name()) + 1).listener(loanItemListener())
.listener(promotionListener()).listener(samplingStepExecutionListener()).build();
.listener(samplingStepExecutionListener()).build();
}
@Bean
@StepScope
public Step applyLockStep(@Value("#{stepExecutionContext['partition']}") String partitionName) {
return localStepBuilderFactory.get("Apply lock - Step:" + partitionName).tasklet(applyLock()).listener(promotionListener()).build();
return localStepBuilderFactory.get("Apply lock - Step:" + partitionName).tasklet(applyLock()).build();
}
@Bean
@@ -130,7 +132,7 @@ public class LoanCOBWorkerConfiguration {
@Bean
public ApplyLoanLockTasklet applyLock() {
return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties);
return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate);
}
@Bean
@@ -158,13 +160,6 @@ public class LoanCOBWorkerConfiguration {
return repositoryItemWriter;
}
@Bean
public ExecutionContextPromotionListener promotionListener() {
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
listener.setKeys(new String[] { LoanCOBConstant.ALREADY_LOCKED_BY_INLINE_COB_OR_PROCESSED_LOAN_IDS });
return listener;
}
@Bean
public SamplingStepExecutionListener samplingStepExecutionListener() {
SamplingStepExecutionListener listener = new SamplingStepExecutionListener(samplingServiceFactory, samplingDataPrinter);
@@ -36,10 +36,7 @@ public class LoanItemReader extends AbstractLoanItemReader {
@SuppressWarnings({ "unchecked" })
public void beforeStep(@NotNull StepExecution stepExecution) {
ExecutionContext executionContext = stepExecution.getExecutionContext();
ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext();
List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
setAlreadyLockedOrProcessedAccounts(
(List<Long>) jobExecutionContext.get(LoanCOBConstant.ALREADY_LOCKED_BY_INLINE_COB_OR_PROCESSED_LOAN_IDS));
setRemainingData(new ArrayList<>(loanIds));
}
}
@@ -129,7 +129,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
}
private void execute(List<Long> loanIds, String jobName, LocalDate businessDate) {
lockLoanAccounts(loanIds);
lockLoanAccounts(loanIds, businessDate);
Job inlineLoanCOBJob;
try {
inlineLoanCOBJob = jobLocator.getJob(jobName);
@@ -167,7 +167,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
return loanIdAndLastClosedBusinessDates;
}
private List<LoanAccountLock> getLoanAccountLocks(List<Long> loanIds) {
private List<LoanAccountLock> getLoanAccountLocks(List<Long> loanIds, LocalDate businessDate) {
List<LoanAccountLock> loanAccountLocks = new ArrayList<>();
List<Long> alreadyLockedLoanIds = new ArrayList<>();
loanIds.forEach(loanId -> {
@@ -180,7 +180,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
alreadyLockedLoanIds.add(loanId);
}
} else {
loanAccountLocks.add(new LoanAccountLock(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING));
loanAccountLocks.add(new LoanAccountLock(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING, businessDate));
}
});
if (!alreadyLockedLoanIds.isEmpty()) {
@@ -207,13 +207,13 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
return jobParameterMap;
}
private void lockLoanAccounts(List<Long> loanIds) {
private void lockLoanAccounts(List<Long> loanIds, LocalDate businessDate) {
transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@NotNull TransactionStatus status) {
List<LoanAccountLock> loanAccountLocks = getLoanAccountLocks(loanIds);
List<LoanAccountLock> loanAccountLocks = getLoanAccountLocks(loanIds, businessDate);
loanAccountLocks.forEach(loanAccountLock -> {
loanAccountLock.setNewLockOwner(LockOwner.LOAN_INLINE_COB_PROCESSING);
loanAccountLockRepository.save(loanAccountLock);