adamsaghy commented on PR #6363:
URL: https://github.com/apache/fineract/pull/6363#issuecomment-5539916888

   ## Verdict
   
   Correct fix, and the approach is better than a local guard: it replaces the 
`ThreadLocal<Boolean>` with `ThreadLocal<Integer> eventRecordingDepth`, makes 
`start`/`stop`/`reset` **private**, and exposes only 
`withExternalEventRecording(Supplier|Runnable)`. Since 
`BusinessEventNotifierServiceImpl` is the only implementor and all four call 
sites (COB, loan replay, WC publisher, tests) are migrated, callers can no 
longer reintroduce the non-reentrant shape. CI is fully green.
   
   ## One correction to the framing: this is live, not latent
   
   The "latent today (only command paths call reprocessing)" reading holds for 
the WC publisher, but the identical 
`ReplayedTransactionBusinessEventServiceImpl` shape **is** reachable from 
inside COB on `develop` today:
   
   ```
   LoanInterestRecalculationCOBBusinessStep:51
     -> LoanWritePlatformServiceJpaRepositoryImpl.recalculateInterest(Loan)
     -> LoanScheduleService:89  
reprocessLoanTransactionsService.reprocessTransactions(loan)   (non-progressive 
branch)
     -> ReprocessLoanTransactionsServiceImpl.handleChangedDetail
     -> ReprocessLoanTransactionsServiceImpl:155  raiseTransactionReplayedEvents
   ```
   
   So with `enable-cob-bulk-event` on, a cumulative non-progressive 
interest-recalculating loan already flushes COB's window mid-chain, and every 
event from the steps after `LOAN_INTEREST_RECALCULATION` escapes as an 
individual external event. That is what the new 
`FeignCobBulkEventRecordingWindowTest` reproduces end-to-end (with a good 
negative control).
   
   Worth stating in the JIRA/PR description — it raises this from hardening to 
a bug fix, and it means consumers see a behavior change: replay events during 
COB now arrive inside COB's bulk event instead of their own.
   
   ## Findings
   
   ### 1. `resetEventRecording()` zeroes the counter instead of decrementing it
   
   **Severity: low — hardening, not reachable today.**
   
   The failure path calls `eventRecordingDepth.remove()`, justified by the 
comment "the exception propagates past every enclosing window too, so there is 
nothing left for them to post either." Nothing enforces that — it is a claim 
about callers, which is precisely the kind of assumption that produced the 
original bug.
   
   If any frame between an inner window and an enclosing one ever catches and 
continues, the enclosing window's depth is gone: its remaining events post 
individually, and the *next* nested window becomes "outermost" and flushes a 
premature bulk mid-chain.
   
   Verified unreachable right now: `executeBusinessSteps` rethrows as 
`BusinessStepException`, the interest-recalc step's `try` has only a `finally`, 
and every `@Retry(fallbackMethod=…)` sits in a command handler above any 
window. But making the counter symmetric costs nothing:
   
   ```java
   private void resetEventRecording() {
       int depth = eventRecordingDepth.get();
       if (depth <= 1) {
           eventRecordingDepth.remove();
       } else {
           eventRecordingDepth.set(depth - 1);
       }
       recordedEvents.remove();
   }
   ```
   
   The `<= 1` branch keeps the ThreadLocal entry from leaking on pooled 
threads, which a plain decrement would not. Behavior on today's paths is 
unchanged, and `testARecordingWindowShouldBeUsableAgainAfterAPreviousOneFailed` 
still passes.
   
   ### 2. Stale comment in the new integration test
   
   In `runCobAndFetchLoanCategoryEvents`, the comment says the Loan-category 
filter is needed because "this test-only endpoint cannot render a bulk event 
holding more than one item" — but this PR is what makes it able to, and 
`bulkEventItemTypes` two methods below reads the multi-item `datas` list. The 
filter is still needed (to exclude the `Bulk`-category event from the "escaped" 
list); only the stated reason is wrong.
   
   ### 3. Cosmetic: test names now overstate what they check
   
   
`COBBulkEventConfigurationTest.testGivenBulkEventEnabledWhenCOBRunExceptionThenEventRecordingReset`
 and the Cucumber step definition now only assert the wrapper was invoked, not 
that recording was reset. That is the right call — the behavior is asserted 
directly in 
`BusinessEventNotifierServiceImplTest.testFailingRecordingWindowShouldAbandonTheRecordingAndRethrow`,
 and the PR adds a comment saying so — but the method names still promise 
"…ThenEventRecordingReset".
   
   ## Worth mentioning in the description: the bundled 
`InternalExternalEventService` fix
   
   The bulk-rendering change is unrelated to the title but is a genuine fix, 
not just refactoring. The old code appended each item's JSON separated by 
`System.lineSeparator()` and passed the whole blob to `mapper.readValue`; with 
`FAIL_ON_TRAILING_TOKENS` off by default, that silently returned only the 
**first** item, so the internal endpoint never rendered a multi-item bulk event 
correctly. The new `Map.of("datas", bulkItems)` shape fixes it and is what 
makes the integration test assertable.
   
   It is an API-shape change, but only to `/v1/internal/externalevents`, which 
is `@Profile(TEST)`-gated; no integration test reads bulk payload contents 
today, so nothing breaks. It just deserves a line in the description rather 
than arriving unannounced.
   
   ## Also worth noting
   
   The `Runnable`/`Supplier` overload pair resolves as intended (JLS 15.12.2.5 
makes the non-void function type more specific, so COB's value-returning lambda 
binds to `Supplier`), but it means a `Runnable`-intended lambda whose body 
happens to be a value-returning expression — `() -> list.add(x)` — silently 
binds to `Supplier`. Harmless here, since both overloads do the same thing.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to