Jose Alberto Hernandez created FINERACT-2995:
------------------------------------------------

             Summary: WC COB worker shares reader and processor across 
concurrent partitions
                 Key: FINERACT-2995
                 URL: https://issues.apache.org/jira/browse/FINERACT-2995
             Project: Apache Fineract
          Issue Type: Bug
          Components: Loan
            Reporter: Jose Alberto Hernandez
             Fix For: 1.17.0


```
h2. Background
The Working Capital (WC) close-of-business job 
(\{{WORKING_CAPITAL_LOAN_COB_JOB}}) is a remote-partitioned Spring Batch job: a 
manager step splits the eligible loans into partitions of 
\{{WC_LOAN_COB_PARTITION_SIZE}} (default 100) and the worker step 
(\{{workingCapitalLoanCOBWorkerStep}}) processes each partition on a scheduling 
thread. Several partitions run concurrently on the same instance whenever a 
tenant has more eligible loans than the partition size, or when several tenants 
run WC COB at the same time.

h2. Problem
{\{WorkingCapitalLoanCOBWorkerConfiguration}} builds the chunk reader, 
processor and writer with \{{new}} inside the singleton worker step bean, so 
one instance of each serves every partition of every tenant in the JVM. These 
components hold per-partition state in instance fields:
* \{{WorkingCapitalLoanCOBWorkerItemReader#remainingData}} - the partition's 
loan ids, filled by \{{@BeforeStep}};
* \{{AbstractItemProcessor#executionContext}} and \{{#businessDate}} - the 
step's execution context and COB business date.

When two partitions start at the same time their \{{@BeforeStep}} calls 
overwrite each other's queue. One partition then polls an empty queue, reads 
nothing, finishes \{{COMPLETED}} with \{{read_count = 0}}, and the job ends 
\{{COMPLETED}} with no error. The loan of the lost partition keeps its old 
\{{last_closed_business_date}}, so the regular partition query 
(\{{last_closed_business_date = COB_DATE - 1}}) never selects it again, and the 
\{{LOAN_COB_CHUNK_PROCESSING}} lock placed before the read stays in 
\{{m_wc_loan_account_locks}} because the unlock step only releases processed 
loans. Unlike LOAN_COB, WC COB has no stayed-locked step, so nothing reports 
the loan. With different business dates per tenant the shared processor could 
also stamp a loan with another tenant's COB date.

{\{LoanCOBWorkerConfiguration}} declares the same three components as 
\{{@StepScope}} beans and does not show the problem under the same load.

h2. Steps to reproduce
# Single tenant on develop (observed @ 537e19aaa9), start Fineract in batch 
manager mode with \{{WC_LOAN_COB_PARTITION_SIZE=1}} so every loan is its own 
partition.
# Create a WC product and disburse 4 WC loans on business date 01 January 2024.
# For 5 days: move the business date one day forward and run the WC COB job 
(\{{POST /v1/jobs/short-name/WC_COB?command=executeJob}}).
# Inspect \{{m_wc_loan.last_closed_business_date}}, 
\{{m_wc_loan_account_locks}} and \{{BATCH_STEP_EXECUTION}} for the worker step.

h2. Expected behaviour
All 4 loans end with \{{last_closed_business_date = 2024-01-05}}, every worker 
partition has \{{read_count = 1}}, \{{m_wc_loan_account_locks}} is empty after 
each job.

h2. Actual behaviour
* Loans 1 and 3 end at 2024-01-05; loan 4 stays at 2024-01-02 and loan 2 at 
2024-01-03.
* Worker step executions 4, 19 and 27 finished \{{COMPLETED}} with 
\{{read_count = 0}}; in every case the sibling partition started on another 
\{{scheduling-N}} thread in the same millisecond.
* \{{m_wc_loan_account_locks}} keeps rows for loans 2 and 4 
(\{{LOAN_COB_CHUNK_PROCESSING}}, COB dates 2024-01-04 and 2024-01-03).
* 0 exceptions, 0 ERROR log lines, all 5 job executions \{{COMPLETED}}.

The same pattern appeared earlier with 3 tenants and 2 WC loans each: 2 of 6 
loans left behind, both with a stale lock.

h2. Proposed fix
* Declare the WC worker reader, processor and writer as \{{@Bean @StepScope}} 
factory methods returning the concrete classes (so Spring Batch finds the 
\{{@BeforeStep}} listener on the scoped proxy's target class) and inject them 
into the worker step, mirroring \{{LoanCOBWorkerConfiguration}}.
* Add a unit test that pins the three components to step scope 
(\{{WorkingCapitalLoanCOBWorkerStepScopeTest}}).
* Follow-up (separate ticket): a stayed-locked step for WC COB, as LOAN_COB 
has, so a loan left locked after the partitions is reported instead of staying 
behind silently. It needs a new WC business event.

h2. Affected modules
* \{{fineract-working-capital-loan}} - 
\{{WorkingCapitalLoanCOBWorkerConfiguration}} (step-scoped components), new 
unit test.

h2. Acceptance criteria
* With \{{WC_LOAN_COB_PARTITION_SIZE=1}} and 4 eligible WC loans in one tenant, 
5 consecutive WC COB runs leave all loans at the expected 
\{{last_closed_business_date}} and no rows in \{{m_wc_loan_account_locks}}.
* Every worker partition step execution reports \{{read_count}} equal to the 
number of loans in its partition.
* \{{CobSequentialProcessingTest}} still passes (no task executor introduced on 
the worker step).



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

Reply via email to