FINERACT-2672: Isolate standing-instruction execution so one failure cannot revert the whole run#6078
Open
oluexpert99 wants to merge 1 commit into
Open
Conversation
…cannot revert the whole run - The Execute Standing Instruction job ran every due instruction inside a single transaction: the step is wired with the real JPA transaction manager, and AccountTransfersWritePlatformService.transferFunds is @transactional (REQUIRED), so it joined that one shared transaction. When an instruction failed (e.g. InsufficientAccountBalanceException from the withdrawal leg), Spring marked the shared transaction rollback-only, so at step commit the ENTIRE run rolled back -- every already-successful transfer was reverted and no history row (success or failed) was persisted. The tasklet then threw JobExecutionException, failing the whole job. The bug was self-concealing because the failure history was itself rolled back. - Move each instruction's work into a new StandingInstructionExecutionService with REQUIRES_NEW propagation. A successful execution (transfer + last_run_date stamp + success history) is one atomic boundary; a failure is recorded in a separate committed boundary, so a rolled-back transfer still leaves a durable failed record. Because the tasklet catches the failure before it reaches an outer transactional boundary, a failing instruction can no longer mark a sibling's transaction rollback-only. - Replace the raw string-concatenated history INSERT with a StandingInstructionHistory JPA entity and repository. The logged amount is now the amount actually transferred (zero on failure) rather than the attempted amount, and BigDecimal is preserved instead of being narrowed to double. - Stop throwing JobExecutionException for expected per-instruction outcomes (insufficient funds is a normal per-mandate result recorded in history): log a processed/succeeded/failed run summary instead, and guard the failure-history write so one history-write error cannot abort the remaining instructions. - Fix the inverted priority ordering: the retrieval query sorted ORDER BY atsi.priority DESC while the enum is URGENT(1)..LOW(4), so it processed LOW first; change it to ASC so URGENT runs first. - Add a unit test asserting a mixed run (one failing instruction among successes) completes without aborting and records every outcome, and an integration test that runs the actual scheduler job with an under-funded source and asserts the successful transfer persists while the failing instruction leaves a durable failed history row. Signed-off-by: oluexpert99 <farooq@techservicehub.io>
12f13fc to
f994c43
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
wired with the real JPA transaction manager, and AccountTransfersWritePlatformService.transferFunds is
@transactional (REQUIRED), so it joined that one shared transaction. When an instruction failed (e.g.
InsufficientAccountBalanceException from the withdrawal leg), Spring marked the shared transaction
rollback-only, so at step commit the ENTIRE run rolled back -- every already-successful transfer was
reverted and no history row (success or failed) was persisted. The tasklet then threw JobExecutionException,
failing the whole job. The bug was self-concealing because the failure history was itself rolled back.
- Move each instruction's work into a new StandingInstructionExecutionService with REQUIRES_NEW propagation.
A successful execution (transfer + last_run_date stamp + success history) is one atomic boundary; a failure
is recorded in a separate committed boundary, so a rolled-back transfer still leaves a durable failed record.
Because the tasklet catches the failure before it reaches an outer transactional boundary, a failing
instruction can no longer mark a sibling's transaction rollback-only.
- Replace the raw string-concatenated history INSERT with a StandingInstructionHistory JPA entity and
repository. The logged amount is now the amount actually transferred (zero on failure) rather than the
attempted amount, and BigDecimal is preserved instead of being narrowed to double.
- Stop throwing JobExecutionException for expected per-instruction outcomes (insufficient funds is a normal
per-mandate result recorded in history): log a processed/succeeded/failed run summary instead, and guard the
failure-history write so one history-write error cannot abort the remaining instructions.
- Fix the inverted priority ordering: the retrieval query sorted ORDER BY atsi.priority DESC while the enum is
URGENT(1)..LOW(4), so it processed LOW first; change it to ASC so URGENT runs first.
- Add a unit test asserting a mixed run (one failing instruction among successes) completes without aborting
and records every outcome, and an integration test that runs the actual scheduler job with an under-funded
source and asserts the successful transfer persists while the failing instruction leaves a durable failed
history row.
Description
Describe the changes made and why they were made. (Ignore if these details are present on the associated Apache Fineract JIRA ticket.)
Checklist
Please make sure these boxes are checked before submitting your pull request - thanks!
Your assigned reviewer(s) will follow our guidelines for code reviews.