#681

Bound the sync batch wait by the policy's totalTimeout

Openaltaieziorwants to merge intofrom

Fixes #680.

Problem

BatchExecutor waited for its sub-commands by leaving a try-with-resources block, which calls the default ExecutorService.close(). That default waits in awaitTermination(1L, TimeUnit.DAYS) inside a while (!terminated) loop, so it re-arms indefinitely and cannot be bounded. BatchPolicy.totalTimeout was never consulted on this path, so a sub-command that outlives its timeout holds the calling thread for as long as it runs.

Change

Wait explicitly instead: shutdown(), awaitTermination() for the remaining time, then shutdownNow() and record a client timeout.

  • BatchExecutor.execute bounds the wait by policy.totalTimeout.
  • Batch.retryBatch bounds the split-retry wait by what remains of the command's deadline.

BatchStatus keeps the first exception, so a genuine sub-command error still takes precedence over the synthetic timeout. A totalTimeout of zero still means no client-side limit, so that wait stays unbounded — this matters for scan and query, which default to zero.

Executor and QueryPartitionExecutor share the pattern but are left alone here: Executor.execute() takes no policy, so bounding it needs a signature change across ~10 call sites in AerospikeClient, and both default totalTimeout to 0. Happy to follow up if you want them covered.

Test

test/src/com/aerospike/client/BatchExecutorTimeoutTest.java, registered in SuiteSync. It needs no server — it drives BatchExecutor directly with sub-commands that stall, so the wait is the only thing under test.

  • batchStopsWaitingAtTotalTimeout — before this change it returned after 10,035 ms with totalTimeout = 500 and threw nothing; now it throws AerospikeException.Timeout promptly.
  • batchWaitsWhenTotalTimeoutIsZero — guards the documented unbounded behaviour.

Verification

Against Aerospike Community 8.1.2.4 on Temurin JDK 21.0.1:

  • SuiteSync — 507 run, 0 failures, 0 errors, 37 skipped
  • SuiteAsync — 30 run, 0 failures, 0 errors

Notes

The async batch path already behaves this way: every sub-command carries its own HashedWheelTimeout armed against min(totalDeadline, socketDeadline), so a listener is always notified on time. This brings sync in line with it.

StructuredTaskScope.joinUntil(Instant) would be the natural fit, but it is still @PreviewFeature in both JDK 21 and 25 and the API changed shape between them.

The Java 8 line is unaffected — no virtual threads on this path — so I have not prepared a stage-jdk8 port. Let me know if you would still like one for symmetry.

This change was prepared with AI assistance.

🤖 Generated with Claude Code

Activity

2
altaiezior1mo ago

@mirzakaracic would you be able to check this