You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2023/02/08 19:21:31 UTC

[fineract] branch develop updated: FINERACT-1724: Fixed Generate Loss Loan provisioning job + enhanced the stuck job listener to be smarter

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 0e96b71f0 FINERACT-1724: Fixed Generate Loss Loan provisioning job + enhanced the stuck job listener to be smarter
0e96b71f0 is described below

commit 0e96b71f0ef3514a4b75caecaea53a39e560fe8b
Author: Arnold Galovics <ga...@gmail.com>
AuthorDate: Wed Feb 8 16:44:57 2023 +0100

    FINERACT-1724: Fixed Generate Loss Loan provisioning job + enhanced the stuck job listener to be smarter
---
 .../core/config/FineractProperties.java            |   9 ++
 .../jobs/domain/JobExecutionRepository.java        | 174 +++++++++++++++------
 .../jobs/service/StuckJobExecutorService.java      |   2 +-
 .../jobs/service/StuckJobExecutorServiceImpl.java  |  32 ++--
 .../jobs/service/StuckJobListener.java             |  16 +-
 .../GenerateLoanlossProvisioningTasklet.java       |  16 +-
 .../src/main/resources/application.properties      |   2 +
 .../GenerateLoanlossProvisioningTaskletTest.java   | 137 ++++++++++++++++
 8 files changed, 308 insertions(+), 80 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
index 85fbef4b7..9a2299d9d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
@@ -50,6 +50,8 @@ public class FineractProperties {
 
     private FineractReportProperties report;
 
+    private FineractJobProperties job;
+
     @Getter
     @Setter
     public static class FineractTenantProperties {
@@ -225,4 +227,11 @@ public class FineractProperties {
         private String bucketName;
         private Boolean enabled;
     }
+
+    @Getter
+    @Setter
+    public static class FineractJobProperties {
+
+        private int stuckRetryThreshold;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
index 016a92b0f..0b4753059 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
@@ -22,10 +22,12 @@ import static org.springframework.batch.core.BatchStatus.COMPLETED;
 import static org.springframework.batch.core.BatchStatus.FAILED;
 import static org.springframework.batch.core.BatchStatus.STARTED;
 import static org.springframework.batch.core.BatchStatus.STARTING;
+import static org.springframework.batch.core.BatchStatus.UNKNOWN;
 
 import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
 import org.springframework.stereotype.Component;
 
@@ -34,68 +36,148 @@ import org.springframework.stereotype.Component;
 public class JobExecutionRepository {
 
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+    private final FineractProperties fineractProperties;
 
     public List<String> getStuckJobNames(NamedParameterJdbcTemplate jdbcTemplate) {
-        return jdbcTemplate.queryForList(
-                "SELECT bji.JOB_NAME as STUCK_JOB_NAME FROM BATCH_JOB_INSTANCE bji "
-                        + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                        + "WHERE bje.STATUS IN (:statuses) AND bje.JOB_INSTANCE_ID NOT IN ("
-                        + "SELECT bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji "
-                        + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                        + "WHERE bje.STATUS  = :completedStatus)",
-                Map.of("statuses", List.of(STARTED.name(), FAILED.name()), "completedStatus", COMPLETED.name()), String.class);
+        int threshold = fineractProperties.getJob().getStuckRetryThreshold();
+        return jdbcTemplate.queryForList("""
+                SELECT DISTINCT(bji.JOB_NAME) as STUCK_JOB_NAME
+                FROM BATCH_JOB_INSTANCE bji
+                INNER JOIN BATCH_JOB_EXECUTION bje
+                ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                WHERE
+                    bje.STATUS IN (:statuses)
+                    AND
+                    bje.JOB_INSTANCE_ID NOT IN (
+                        SELECT bje.JOB_INSTANCE_ID
+                        FROM BATCH_JOB_INSTANCE bji
+                        INNER JOIN BATCH_JOB_EXECUTION bje
+                        ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                        WHERE bje.STATUS IN (:completedStatuses)
+                    )
+                GROUP BY BJI.JOB_INSTANCE_ID
+                HAVING COUNT(BJI.JOB_INSTANCE_ID) <= :threshold
+                """, Map.of("statuses", List.of(STARTED.name()), "completedStatuses",
+                List.of(COMPLETED.name(), FAILED.name(), UNKNOWN.name()), "threshold", threshold), String.class);
     }
 
     public Long getStuckJobCountByJobName(String jobName) {
-        return namedParameterJdbcTemplate.queryForObject(
-                "SELECT COUNT(*) as STUCK_JOB_COUNT FROM BATCH_JOB_INSTANCE bji "
-                        + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                        + "WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = :jobName AND bje.JOB_INSTANCE_ID NOT IN ("
-                        + "SELECT bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji "
-                        + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                        + "WHERE bje.STATUS  = :completedStatus AND bji.JOB_NAME = :jobName)",
-                Map.of("statuses", List.of(STARTED.name(), FAILED.name()), "jobName", jobName, "completedStatus", COMPLETED.name()),
-                Long.class);
+        int threshold = fineractProperties.getJob().getStuckRetryThreshold();
+        return namedParameterJdbcTemplate.queryForObject("""
+                    SELECT COUNT(DISTINCT bji.JOB_NAME) as STUCK_JOB_COUNT
+                    FROM BATCH_JOB_INSTANCE bji
+                    INNER JOIN BATCH_JOB_EXECUTION bje
+                    ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                    WHERE
+                        bje.STATUS IN (:statuses)
+                        AND
+                        bji.JOB_NAME = :jobName
+                        AND
+                        bje.JOB_INSTANCE_ID NOT IN (
+                            SELECT bje.JOB_INSTANCE_ID
+                            FROM BATCH_JOB_INSTANCE bji
+                            INNER JOIN BATCH_JOB_EXECUTION bje
+                            ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                            WHERE
+                                bje.STATUS IN (:completedStatuses)
+                                AND
+                                bji.JOB_NAME = :jobName
+                        )
+                    GROUP BY BJI.JOB_INSTANCE_ID
+                    HAVING COUNT(BJI.JOB_INSTANCE_ID) <= :threshold
+                """, Map.of("statuses", List.of(STARTED.name()), "jobName", jobName, "completedStatuses",
+                List.of(COMPLETED.name(), FAILED.name(), UNKNOWN.name()), "threshold", threshold), Long.class);
     }
 
     public List<Long> getStuckJobIdsByJobName(String jobName) {
-        return namedParameterJdbcTemplate.queryForList(
-                "SELECT bje.JOB_EXECUTION_ID FROM BATCH_JOB_INSTANCE bji "
-                        + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                        + "WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = :jobName AND bje.JOB_INSTANCE_ID NOT IN ("
-                        + "SELECT bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji "
-                        + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                        + "WHERE bje.STATUS = :completedStatus AND bji.JOB_NAME = :jobName)",
-                Map.of("statuses", List.of(STARTED.name(), FAILED.name()), "jobName", jobName, "completedStatus", COMPLETED.name()),
-                Long.class);
+        int threshold = fineractProperties.getJob().getStuckRetryThreshold();
+        return namedParameterJdbcTemplate.queryForList("""
+                    SELECT bje.JOB_EXECUTION_ID
+                    FROM BATCH_JOB_INSTANCE bji
+                    INNER JOIN BATCH_JOB_EXECUTION bje
+                    ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                    WHERE
+                        bje.STATUS IN (:statuses)
+                        AND
+                        bji.JOB_NAME = :jobName
+                        AND
+                        bje.JOB_INSTANCE_ID NOT IN (
+                            SELECT bje.JOB_INSTANCE_ID
+                            FROM BATCH_JOB_INSTANCE bji
+                            INNER JOIN BATCH_JOB_EXECUTION bje
+                            ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                            WHERE
+                            bje.STATUS IN (:completedStatuses)
+                            AND
+                            bji.JOB_NAME = :jobName
+                        )
+                    GROUP BY BJI.JOB_INSTANCE_ID, BJE.JOB_EXECUTION_ID
+                    HAVING COUNT(BJI.JOB_INSTANCE_ID) <= :threshold
+                """, Map.of("statuses", List.of(STARTED.name()), "jobName", jobName, "completedStatuses",
+                List.of(COMPLETED.name(), FAILED.name(), UNKNOWN.name()), "threshold", threshold), Long.class);
     }
 
     public Long getNotCompletedPartitionsCount(Long jobExecutionId, String partitionerStepName) {
-        return namedParameterJdbcTemplate.queryForObject(
-                "SELECT COUNT(bse.STEP_EXECUTION_ID) FROM BATCH_STEP_EXECUTION bse "
-                        + "WHERE bse.JOB_EXECUTION_ID = :jobExecutionId AND bse.STEP_NAME <> :stepName AND bse.status <> :status",
-                Map.of("jobExecutionId", jobExecutionId, "stepName", partitionerStepName, "status", COMPLETED.name()), Long.class);
+        return namedParameterJdbcTemplate.queryForObject("""
+                    SELECT COUNT(bse.STEP_EXECUTION_ID)
+                    FROM BATCH_STEP_EXECUTION bse
+                    WHERE
+                        bse.JOB_EXECUTION_ID = :jobExecutionId
+                        AND
+                        bse.STEP_NAME <> :stepName
+                        AND
+                        bse.status <> :status
+                """, Map.of("jobExecutionId", jobExecutionId, "stepName", partitionerStepName, "status", COMPLETED.name()), Long.class);
     }
 
     public void updateJobStatusToFailed(Long stuckJobId, String partitionerStepName) {
-        namedParameterJdbcTemplate.update(
-                "UPDATE BATCH_STEP_EXECUTION SET STATUS = :status WHERE JOB_EXECUTION_ID = :jobExecutionId AND STEP_NAME = :stepName",
-                Map.of("status", FAILED.name(), "jobExecutionId", stuckJobId, "stepName", partitionerStepName));
-        namedParameterJdbcTemplate.update(
-                "UPDATE BATCH_JOB_EXECUTION SET STATUS = :status, START_TIME = null, END_TIME = null WHERE JOB_EXECUTION_ID = :jobExecutionId",
-                Map.of("status", FAILED.name(), "jobExecutionId", stuckJobId));
+        namedParameterJdbcTemplate.update("""
+                    UPDATE BATCH_STEP_EXECUTION
+                    SET STATUS = :status
+                    WHERE
+                        JOB_EXECUTION_ID = :jobExecutionId
+                        AND
+                        STEP_NAME = :stepName
+                """, Map.of("status", FAILED.name(), "jobExecutionId", stuckJobId, "stepName", partitionerStepName));
+        namedParameterJdbcTemplate.update("""
+                    UPDATE BATCH_JOB_EXECUTION
+                    SET
+                        STATUS = :status,
+                        START_TIME = null,
+                        END_TIME = null
+                    WHERE
+                        JOB_EXECUTION_ID = :jobExecutionId
+                """, Map.of("status", FAILED.name(), "jobExecutionId", stuckJobId));
     }
 
     public List<Long> getRunningJobsByExecutionParameter(String jobName, String parameterKeyName, String parameterValue) {
-        return namedParameterJdbcTemplate.queryForList("SELECT bje.JOB_EXECUTION_ID FROM BATCH_JOB_INSTANCE bji "
-                + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                + "INNER JOIN BATCH_JOB_EXECUTION_PARAMS bjep ON bje.JOB_EXECUTION_ID = bjep.JOB_EXECUTION_ID "
-                + "WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = :jobName AND bjep.KEY_NAME = :parameterKeyName AND bjep.STRING_VAL = :parameterValue AND bje.JOB_INSTANCE_ID NOT IN ("
-                + "SELECT bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji "
-                + "INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID "
-                + "WHERE bje.STATUS = :completedStatus AND bji.JOB_NAME = :jobName)",
-                Map.of("statuses", List.of(STARTED.name(), STARTING.name()), "jobName", jobName, "completedStatus", COMPLETED.name(),
-                        "parameterKeyName", parameterKeyName, "parameterValue", parameterValue),
-                Long.class);
+        return namedParameterJdbcTemplate.queryForList("""
+                    SELECT bje.JOB_EXECUTION_ID
+                    FROM BATCH_JOB_INSTANCE bji
+                    INNER JOIN BATCH_JOB_EXECUTION bje
+                    ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                    INNER JOIN BATCH_JOB_EXECUTION_PARAMS bjep
+                    ON bje.JOB_EXECUTION_ID = bjep.JOB_EXECUTION_ID
+                    WHERE
+                        bje.STATUS IN (:statuses)
+                        AND
+                        bji.JOB_NAME = :jobName
+                        AND
+                        bjep.KEY_NAME = :parameterKeyName
+                        AND
+                        bjep.STRING_VAL = :parameterValue
+                        AND
+                        bje.JOB_INSTANCE_ID NOT IN (
+                            SELECT bje.JOB_INSTANCE_ID
+                            FROM BATCH_JOB_INSTANCE bji
+                            INNER JOIN BATCH_JOB_EXECUTION bje
+                            ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID
+                            WHERE
+                                bje.STATUS = :completedStatus
+                                AND
+                                bji.JOB_NAME = :jobName
+                        )
+                """, Map.of("statuses", List.of(STARTED.name(), STARTING.name()), "jobName", jobName, "completedStatus", COMPLETED.name(),
+                "parameterKeyName", parameterKeyName, "parameterValue", parameterValue), Long.class);
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorService.java
index 70e365308..8fe2c3346 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorService.java
@@ -20,5 +20,5 @@ package org.apache.fineract.infrastructure.jobs.service;
 
 public interface StuckJobExecutorService {
 
-    void executeStuckJob(String jobName, Long jobId);
+    void resumeStuckJob(String jobName);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorServiceImpl.java
index d69aed9f5..5f46c88f7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobExecutorServiceImpl.java
@@ -25,7 +25,6 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.infrastructure.jobs.data.partitionedjobs.PartitionedJob;
 import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository;
-import org.jetbrains.annotations.NotNull;
 import org.springframework.batch.core.launch.JobOperator;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.TransactionStatus;
@@ -40,20 +39,33 @@ public class StuckJobExecutorServiceImpl implements StuckJobExecutorService {
     private final JobExecutionRepository jobExecutionRepository;
     private final TransactionTemplate transactionTemplate;
     private final JobOperator jobOperator;
-    private final JobRegisterService jobRegisterService;
 
     @Override
-    public void executeStuckJob(String jobName, Long jobId) {
+    public void resumeStuckJob(String jobName) {
+        List<Long> stuckJobIds = getStuckJobIds(jobName);
         if (isPartitionedJob(jobName) && areThereStuckJobs(jobName)) {
-            // Restarting stuck Partitioned jobs
-            List<Long> stuckJobIds = getStuckJobIds(jobName);
-            stuckJobIds.forEach(stuckJobId -> handleStuckJob(stuckJobId, getPartitionerStepName(jobName)));
+            restartPartitionedJobs(jobName, stuckJobIds);
         } else {
-            // Executing stuck Tasklet jobs
-            jobRegisterService.executeJobWithParameters(jobId, null);
+            restartTaskletJobs(stuckJobIds);
         }
     }
 
+    private void restartTaskletJobs(List<Long> stuckJobIds) {
+        stuckJobIds.forEach(this::handleStuckTaskletJob);
+    }
+
+    private void handleStuckTaskletJob(Long stuckJobId) {
+        try {
+            jobOperator.restart(stuckJobId);
+        } catch (Exception e) {
+            throw new RuntimeException("Exception while handling a stuck job", e);
+        }
+    }
+
+    private void restartPartitionedJobs(String jobName, List<Long> stuckJobIds) {
+        stuckJobIds.forEach(stuckJobId -> handleStuckPartitionedJob(stuckJobId, getPartitionerStepName(jobName)));
+    }
+
     private boolean isPartitionedJob(String jobName) {
         return PartitionedJob.existsByJobName(jobName);
     }
@@ -71,14 +83,14 @@ public class StuckJobExecutorServiceImpl implements StuckJobExecutorService {
         return jobExecutionRepository.getStuckJobIdsByJobName(jobName);
     }
 
-    private void handleStuckJob(Long stuckJobId, String partitionerStepName) {
+    private void handleStuckPartitionedJob(Long stuckJobId, String partitionerStepName) {
         try {
             waitUntilAllPartitionsFinished(stuckJobId, partitionerStepName);
             transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW);
             transactionTemplate.execute(new TransactionCallbackWithoutResult() {
 
                 @Override
-                protected void doInTransactionWithoutResult(@NotNull TransactionStatus status) {
+                protected void doInTransactionWithoutResult(TransactionStatus status) {
                     jobExecutionRepository.updateJobStatusToFailed(stuckJobId, partitionerStepName);
                 }
             });
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java
index 948f7be59..353c5c914 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java
@@ -21,8 +21,6 @@ package org.apache.fineract.infrastructure.jobs.service;
 import java.time.LocalDate;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
@@ -31,7 +29,6 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.infrastructure.event.external.service.JdbcTemplateFactory;
 import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository;
-import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameData;
 import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameProvider;
 import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
 import org.springframework.batch.core.configuration.JobRegistry;
@@ -67,18 +64,9 @@ public class StuckJobListener implements ApplicationListener<ContextRefreshedEve
                         HashMap<BusinessDateType, LocalDate> businessDates = businessDateReadPlatformService.getBusinessDates();
                         ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
                         ThreadLocalContextUtil.setBusinessDates(businessDates);
-                        stuckJobNames.forEach(stuckJobName -> {
-                            String sql = "select id from job where name = :jobName";
-                            Optional<JobNameData> jobNameDataOptional = jobNameProvider.provide().stream()
-                                    .filter(jobNameData -> stuckJobName.equals(jobNameData.getEnumStyleName())).findFirst();
-                            JobNameData jobNameData = jobNameDataOptional
-                                    .orElseThrow(() -> new IllegalArgumentException("Job not found by name: " + stuckJobName));
-                            Long stuckJobId = namedParameterJdbcTemplate.queryForObject(sql,
-                                    Map.of("jobName", jobNameData.getHumanReadableName()), Long.class);
-                            stuckJobExecutorService.executeStuckJob(stuckJobName, stuckJobId);
-                        });
+                        stuckJobNames.forEach(stuckJobExecutorService::resumeStuckJob);
                     } catch (Exception e) {
-                        throw new RuntimeException(e);
+                        throw new RuntimeException("Error while trying to restart stuck jobs", e);
                     } finally {
                         ThreadLocalContextUtil.reset();
                     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningTasklet.java
index 2cb450608..a444c4542 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningTasklet.java
@@ -22,6 +22,7 @@ import java.time.LocalDate;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.fineract.accounting.provisioning.exception.ProvisioningEntryAlreadyCreatedException;
 import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -31,8 +32,6 @@ 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.repeat.RepeatStatus;
-import org.springframework.dao.DataIntegrityViolationException;
-import org.springframework.orm.jpa.JpaSystemException;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -48,14 +47,13 @@ public class GenerateLoanlossProvisioningTasklet implements Tasklet {
         try {
             Collection<ProvisioningCriteriaData> criteriaCollection = provisioningCriteriaReadPlatformService
                     .retrieveAllProvisioningCriterias();
-            if (criteriaCollection == null || criteriaCollection.size() == 0) {
-                throw new NoProvisioningCriteriaDefinitionFoundException();
+            if (CollectionUtils.isNotEmpty(criteriaCollection)) {
+                provisioningEntriesWritePlatformService.createProvisioningEntry(currentDate, addJournalEntries);
             }
-            provisioningEntriesWritePlatformService.createProvisioningEntry(currentDate, addJournalEntries);
-        } catch (ProvisioningEntryAlreadyCreatedException peace) {
-            log.error("Provisioning Entry already created", peace);
-        } catch (final JpaSystemException | DataIntegrityViolationException dve) {
-            log.error("Problem occurred in generateLoanLossProvisioningAmount function", dve);
+        } catch (ProvisioningEntryAlreadyCreatedException e) {
+            log.error("Provisioning entry already created", e);
+        } catch (Exception e) {
+            log.error("Problem occurred when generating provisioning entries", e);
         }
         return RepeatStatus.FINISHED;
     }
diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties
index c7f8078ab..170ff1cee 100644
--- a/fineract-provider/src/main/resources/application.properties
+++ b/fineract-provider/src/main/resources/application.properties
@@ -43,6 +43,8 @@ fineract.mode.batch-manager-enabled=${FINERACT_MODE_BATCH_MANAGER_ENABLED:true}
 fineract.correlation.enabled=${FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED:false}
 fineract.correlation.header-name=${FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME:X-Correlation-ID}
 
+fineract.job.stuck-retry-threshold=${FINERACT_JOB_STUCK_RETRY_THRESHOLD:5}
+
 fineract.partitioned-job.partitioned-job-properties[0].job-name=LOAN_COB
 fineract.partitioned-job.partitioned-job-properties[0].chunk-size=${LOAN_COB_CHUNK_SIZE:100}
 fineract.partitioned-job.partitioned-job-properties[0].partition-size=${LOAN_COB_PARTITION_SIZE:100}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningTaskletTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningTaskletTest.java
new file mode 100644
index 000000000..2a5462c5d
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/jobs/generateloanlossprovisioning/GenerateLoanlossProvisioningTaskletTest.java
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.jobs.generateloanlossprovisioning;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.springframework.batch.repeat.RepeatStatus.FINISHED;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fineract.accounting.provisioning.exception.ProvisioningEntryAlreadyCreatedException;
+import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaData;
+import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.repeat.RepeatStatus;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class GenerateLoanlossProvisioningTaskletTest {
+
+    public static final LocalDate BUSINESS_DATE = LocalDate.of(2022, 6, 12);
+
+    @Mock
+    private StepContribution stepContribution;
+
+    @Mock
+    private ChunkContext chunkContext;
+
+    @Mock
+    private ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService;
+    @Mock
+    private ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService;
+
+    @InjectMocks
+    private GenerateLoanlossProvisioningTasklet underTest;
+
+    @BeforeEach
+    public void setUp() {
+        ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, BUSINESS_DATE)));
+    }
+
+    @Test
+    public void testExecuteShouldCreateProvisioningEntry() throws Exception {
+        // given
+        Collection<ProvisioningCriteriaData> provisioningCriterias = List.of(new ProvisioningCriteriaData());
+        given(provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias()).willReturn(provisioningCriterias);
+        // when
+        RepeatStatus result = underTest.execute(stepContribution, chunkContext);
+        // then
+        verify(provisioningEntriesWritePlatformService).createProvisioningEntry(BUSINESS_DATE, true);
+        assertThat(result).isEqualTo(FINISHED);
+    }
+
+    @Test
+    public void testExecuteShouldNotCreateProvisioningEntryWhenNoProvisioningCriteriasArePresent() throws Exception {
+        // given
+        Collection<ProvisioningCriteriaData> provisioningCriterias = List.of();
+        given(provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias()).willReturn(provisioningCriterias);
+        // when
+        RepeatStatus result = underTest.execute(stepContribution, chunkContext);
+        // then
+        verifyNoInteractions(provisioningEntriesWritePlatformService);
+        assertThat(result).isEqualTo(FINISHED);
+    }
+
+    @Test
+    public void testExecuteShouldNotCreateProvisioningEntryWhenNullProvisioningCriteriasArePresent() throws Exception {
+        // given
+        given(provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias()).willReturn(null);
+        // when
+        RepeatStatus result = underTest.execute(stepContribution, chunkContext);
+        // then
+        verifyNoInteractions(provisioningEntriesWritePlatformService);
+        assertThat(result).isEqualTo(FINISHED);
+    }
+
+    @Test
+    public void testExecuteShouldNotFailWhenProvisioningEntryIsAlreadyCreated() throws Exception {
+        // given
+        Collection<ProvisioningCriteriaData> provisioningCriterias = List.of(new ProvisioningCriteriaData());
+        given(provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias()).willReturn(provisioningCriterias);
+        given(provisioningEntriesWritePlatformService.createProvisioningEntry(BUSINESS_DATE, true))
+                .willThrow(new ProvisioningEntryAlreadyCreatedException(1L, BUSINESS_DATE));
+        // when
+        RepeatStatus result = underTest.execute(stepContribution, chunkContext);
+        // then
+        verify(provisioningEntriesWritePlatformService).createProvisioningEntry(BUSINESS_DATE, true);
+        assertThat(result).isEqualTo(FINISHED);
+    }
+
+    @Test
+    public void testExecuteShouldNotFailWhenExceptionIsThrownInProvisioningEntryCreation() throws Exception {
+        // given
+        Collection<ProvisioningCriteriaData> provisioningCriterias = List.of(new ProvisioningCriteriaData());
+        given(provisioningCriteriaReadPlatformService.retrieveAllProvisioningCriterias()).willReturn(provisioningCriterias);
+        given(provisioningEntriesWritePlatformService.createProvisioningEntry(BUSINESS_DATE, true)).willThrow(new RuntimeException("Test"));
+        // when
+        RepeatStatus result = underTest.execute(stepContribution, chunkContext);
+        // then
+        verify(provisioningEntriesWritePlatformService).createProvisioningEntry(BUSINESS_DATE, true);
+        assertThat(result).isEqualTo(FINISHED);
+    }
+
+}