Piotr Wargulak created FINERACT-3004:
----------------------------------------

             Summary: COB silently rolls back a whole chunk when one loan's 
business step fails: loans stay locked with no error and the job reports success
                 Key: FINERACT-3004
                 URL: https://issues.apache.org/jira/browse/FINERACT-3004
             Project: Apache Fineract
          Issue Type: Bug
            Reporter: Piotr Wargulak


When a business step throws for one loan during a scheduled Loan COB or Working 
Capital Loan COB run, every loan of that chunk is left behind without any trace:
 * the loans keep their {{LOAN_COB_CHUNK_PROCESSING}} lock with {{error}} 
empty, so the lock is never overrulable;
 * none of them gets {{last_closed_business_date}} moved, and none of the 
chunk's work is kept, including the loans that had no problem;
 * the step ends {{COMPLETED}} and the job run is recorded as {{{}success{}}}.

What happens, step by step (Spring Batch 6.0.5, {{{}ChunkOrientedStep{}}}):
 # The item processor runs {{{}COBBusinessStepServiceImpl#run{}}}, which is 
{{@Transactional}} and joins the chunk transaction. The step's exception leaves 
that method, so the JPA transaction manager marks the chunk transaction 
rollback-only ("Participating transaction failed - marking existing transaction 
as rollback-only").
 # The worker step is fault tolerant with an immediate retry policy, so 
{{doProcess}} retries the item in the same transaction and persistence context. 
Two outcomes:
 ** the retry succeeds (it can: the first attempt's changes are still in the 
persistence context) and nothing is reported at all;
 ** the retries are used up: {{onProcessError}} is called, the item is skipped 
({{{}doSkipInProcess{}}}), and the chunk goes on.
 # After the chunk, {{doExecute}} finds 
{{{}transactionStatus.isRollbackOnly(){}}}, rolls back and returns 
({{{}ChunkOrientedStep:380-389{}}}). No exception, no listener, no scan; the 
next chunk starts and the step completes.

A process skip therefore cannot work while processing writes in the chunk 
transaction: the skipped item has already doomed the transaction of the items 
processed with it. 
In the retries-used-up case, 
{{AbstractLoanItemListener#updateAccountLockWithError}} also failed for us with 
{{UnexpectedRollbackException}} (Working Capital, on our build); we have not 
established why its {{REQUIRES_NEW}} transaction is rollback-only.

*Steps to reproduce* 
_(a GL closure on the COB date makes one loan's business step fail)_

Two attached integration tests, each with two loans of one chunk: one whose 
step posts a journal entry on the COB date, and one with no accounting, which 
posts nothing and so is not concerned by the closure.
 * {{LoanCobBusinessStepFailureTest}} (Loan COB, based on 
{{{}LoanAccrualTransactionOnChargeSubmittedDateTest{}}}): business date 
2023-03-03 with {{charge-accrual-date}} = {{{}submitted-date{}}}; a 
single-repayment loan with periodic accrual accounting and a penalty and a fee 
charge, and a second loan on a product from the same template with accounting 
none. Business date 2023-03-04, {{POST /glclosures}} for office 1 on 
2023-03-03, run the Loan COB job. 
{{LoanAccrualsProcessingServiceImpl#addPeriodicAccruals}} throws 
{{{}JournalEntryInvalidException{}}}.
 * {{FeignWorkingCapitalLoanCobBusinessStepFailureTest}} (Working Capital Loan 
COB): a loan on an accrual ({{{}ACC_DEF_REV_AM{}}}) product with a discount and 
a repayment on 2026-01-02, and a loan on a product without accounting. Business 
date 2026-01-03, closure on 2026-01-02, run the Working Capital Loan COB job. 
{{WorkingCapitalLoanDiscountFeeAmortizationServiceImpl#processDiscountFeeAmortization}}
 throws {{{}JournalEntryInvalidException{}}}.

On develop 537e19aaa9 (PostgreSQL) both tests fail on the loan with nothing to 
post: {{expected: <2023-03-03> but was: <null>}} and {{{}expected: <2026-01-02> 
but was: <null>{}}}. After the runs both loans of each job keep 
{{LOAN_COB_CHUNK_PROCESSING}} with no error, and both job runs are recorded as 
{{{}success{}}}. The application log shows the exception, then "Transactional 
code has requested rollback".

{*}Possible directions{*}:
 * run each loan's business steps in their own transaction (a nested one, or 
REQUIRES_NEW plus writing the loan in it), so one loan's failure does not doom 
the chunk; this changes the semantics FINERACT-2684 settled;
 * on a process failure, roll the chunk back and process it again without the 
failed loan, recording the error on its lock in an independent transaction (as 
Spring Batch 5's fault-tolerant chunk processor did for a process skip);
 * at the very least, fail the step when a chunk is rolled back this way, so 
the job does not report success while loans are left locked.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to