Repository navigation
Conversation
When the database was down during a reward job, saving the failure state threw out of processJob and processJobs moved on to the next job. The failed epoch's job was lost until a restart, the next epoch was calculated from an incomplete AdaPot (reserves 0), and when that failed the loop returned and no later job ran. - AdaPotJobManager runs each job until it completes, becomes stale or is cancelled by a rollback, retrying with backoff (10 s doubling, max 5 min). The loop no longer exits on a failure, so epoch N+1 cannot start before epoch N completes. - AdaPotJobProcessor saves the failure state best-effort and returns false if it cannot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUCWhJpLCQR5Asv7nDb
|
Initial automated review · Aster Hi @satran004, thanks for the PR! 🙏 This is an automatic first check by an AI reviewer. It does not replace maintainer review and may be wrong. Reviewed commit: Issues found:
Not tested: I read the code and tests without running a build or simulating an outage. |
- AdaPotJobProcessor makes one attempt per call. Its own loop (4 full runs with 5 s pauses) is removed now that AdaPotJobManager retries, so a failure that repeats, such as a verification mismatch, no longer reruns the whole job 4 times per retry. - Retry backoff is capped at 30 min instead of 5 min. - The retry loop also catches errors (e.g. StackOverflowError), which would otherwise end the job thread. - The backoff wait checks for a rollback cancel every second. - The job thread is interrupted on shutdown. - Tests: next epoch not started before the failed one completes (real job thread), an Error, a rollback while the job throws and during the backoff, interrupt, a failing save of the failure state, one attempt per processJob call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUCWhJpLCQR5Asv7nDb
Review round 1 (before requesting review) · head 9ced88dThree independent reviews of 09c2c0f: two Claude review agents (one on the retry loop with rollbacks and interrupts, one on whether rerunning a half-finished job is safe) and Codex. None found a blocker. The data-safety review traced every write a job makes (AdaPot, fees, deposits, the PreAdaPot listeners, the reward replace, the stake/DRep snapshots, proposal status). It found each one an overwrite or a delete-then-insert keyed by epoch, so running a half-finished job again leaves no duplicate or double-counted rows.
Tests: |
The rollback in the backoff test now waits for the first attempt instead of a fixed 300 ms sleep. New test: stop() ends the real job thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJrQUCWhJpLCQR5Asv7nDb
Review round 2 · head 3c15ae1A second Codex pass on 9ced88d found no production regression. It confirmed:
It flagged one test as timing-dependent: the rollback-during-backoff test used a fixed 300 ms sleep instead of waiting for the job to start. In 3c15ae1:
The 23 job tests passed 3 reruns on End-to-end check: a preprod instance built from |
|
|
End-to-end check on benchmark-vm: L03 and L18 both pass Build:
The JVM was never restarted: PID 2736717 runs through both scenarios. The
L18 log excerpt (times are UTC+1):
Without the fix, the same two scenarios left the job 🤖 Generated with Claude Code |



Fixes #1352. Part of #1328.
Problem
If the database went down while an AdaPot reward job was running, saving the job's failure state also failed. The exception escaped
processJob, andAdaPotJobManager.processJobs()moved on to the next job:STARTED, itsadapotrow keptreserves = 0);processJobs()returned, and no later job ran until a restart.Past the end of the bundled expected-values file, nothing would catch the wrong reserves, and the next epoch would be saved as
COMPLETED. Details and logs are in #1352.Change
AdaPotJobManager:runUntilDone(job)runs a job until it completes, becomes stale, or is cancelled by a rollback.StackOverflowError) is retried with backoff: 10 s, doubling, capped at 30 min. The loop no longer exits on a failure, so jobs stay in epoch order: epoch N+1 can't start before epoch N completes.handleRollbackcan still cancel it. The wait checks for a cancel every second.@PreDestroy).AdaPotJobProcessor:processJobreturns false instead of throwing.A job that keeps failing for a reason other than an outage (for example a real calculation mismatch) is now retried at most every 30 min, with an error logged each time, instead of stopping the job thread.
adapot_jobs.error_messageshows the last error.Not changed here
Found in review. These existed before this change; a restart reruns the job the same way.
PostgresPartitionManagermarks an epoch's partition as created before the snapshot transaction commits. If that transaction rolls back, retries in the same JVM write to the default partition until a restart.take()andactivateJob(), or whose database deletes then fail, isn't fully coordinated with the in-memory cancel.Tests
AdaPotJobManagerTest:falseand anError;AdaPotJobProcessorTest:falseinstead of an exception when the failure state can't be saved, or when the job lookup fails../gradlew :aggregates:adapot:test: 59 tests, 0 failures (JDK 21). The job tests passed 3 more reruns.The fault-injection scenarios L03 and L18 (#1338) cover this end to end. I'll rerun them with this change on the 2.0.x backport.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MJrQUCWhJpLCQR5Asv7nDb