Java harness reliability / six-guide learning path · Guide 2 of 6. Begin with the stated failure and test the resource or state boundary before changing concurrency or JVM settings.
Name the two lifetimes before adding a timeout
A harness caller has waited long enough for a synthetic tool result and returns a timeout. The task may nevertheless still be running. These are two lifetimes: the result that the caller is prepared to accept, and the operation that consumes resources or creates effects. A correct design records both. A timeout response alone says nothing conclusive about the state of a socket, child process or tool action.
Use one request deadline and pass the remaining budget through admission, queuing, execution and cleanup. Within one process, elapsed-time budgets should use a monotonic clock such as System.nanoTime. Do not send a nanoTime value to another process as an absolute timestamp; its origin is local. At a remote boundary, define a protocol for duration or wall-clock deadlines and account for its assumptions.
- STEP 1Caller deadline expires
- STEP 2Owned worker cancellation
- STEP 3Observe cleanup and reject late results
Read the completion primitive carefully
CompletableFuture.orTimeout completes the stage exceptionally if it remains incomplete when the timeout expires. It does not establish ownership of the supplier thread or automatically stop that supplier. CompletableFuture cancellation also treats cancellation as exceptional completion; its mayInterruptIfRunning argument does not provide the thread-control behavior a reader might expect from another Future implementation.
A Future returned by submitting a task to an executor can be used to request cancellation of that owned task. Passing true permits an interruption attempt when the task is running. That is still a request rather than proof of termination. The task, blocking API and client library determine how promptly it can stop. A timed Future.get limits a wait; after a TimeoutException, cancellation remains a separate operation.
Connect timeout to the resource owner
Retain the worker handle, transport cancellation hook or process handle at the boundary that created the operation. When the parent expires, reject late delivery and ask that owner to cancel. The two actions solve different problems: delivery fencing protects the parent state, while cancellation tries to recover resources and avoid further work. Neither action reverses an effect already committed elsewhere.
The Java exercise first demonstrates a timed-out CompletableFuture whose worker remains held on a latch. It then submits a separate interruptible worker and cancels its actual Future handle. A second latch confirms that cooperative cleanup ran. The program does not infer worker death from isCancelled. Its bounded waits are test failure limits, not measured latency promises.
Budget cleanup and executor shutdown explicitly
A task that catches InterruptedException should either propagate cancellation through its API or restore interrupt status when it cannot rethrow. Silently continuing can turn a cooperative cancellation path into an indefinitely running task. At the same time, cleanup must release owned resources exactly once, even when both a timeout handler and a worker completion callback execute.
Executor shutdown is another ownership boundary. shutdown rejects future submissions but permits accepted work to finish; shutdownNow attempts interruption and returns queued tasks that never started. A try-with-resources executor close can wait for termination, which is useful for a small exercise but can undermine an outer response deadline if used without thinking about ownership. A shared service executor needs an application lifecycle, not a new close operation for every timed request.
Fence late effects and test the stubborn worker
Suppose a tool increments a synthetic counter before its caller times out. Cancelling the local wait cannot undo the increment. For effectful tools, keep an operation identifier and an outcome-reconciliation path. Withhold stale results from a completed parent and distinguish unknown remote outcome from confirmed failure. Automatically retrying an unknown effect is a separate correctness decision.
The browser lab compares a waiting timeout, a cooperative worker and a worker that ignores interruption. It also covers cancellation while still queued. Change one variable at a time and inspect the event timeline rather than treating a green cancelled state as sufficient. A production test should deliberately use a non-cooperative fixture, confirm that its lingering work is observable, and check that the harness does not publish its late result as success.
Run the Java boundary check
Use a JDK with Java 21 APIs or later. Save the original TimeoutDemo.java into an empty local directory. The example uses only the JDK and synthetic fixtures; no model, network service or credentials are required.
javac --release 21 TimeoutDemo.java
java -ea TimeoutDemo// Author: Sandeep Belgavi. Controlled waits distinguish future state from worker lifetime.
import java.util.concurrent.*;
public class TimeoutDemo {
public static void main(String[] args) throws Exception {
var executor = Executors.newFixedThreadPool(2);
var release = new CountDownLatch(1);
var started = new CountDownLatch(1);
var done = new CountDownLatch(1);
try {
var stage = CompletableFuture.supplyAsync(() -> {
started.countDown();
try { release.await(); return "finished"; }
catch (InterruptedException e) { Thread.currentThread().interrupt(); return "interrupted"; }
finally { done.countDown(); }
}, executor);
if (!started.await(5, TimeUnit.SECONDS)) throw new AssertionError("Not started");
try { stage.orTimeout(1, TimeUnit.MILLISECONDS).join(); }
catch (CompletionException e) {
if (!(e.getCause() instanceof TimeoutException)) throw e;
}
if (!stage.isCompletedExceptionally() || done.getCount() != 1)
throw new AssertionError("Timeout did not leave worker running");
System.out.println("stage_timed_out=true worker_still_waiting=true");
release.countDown();
if (!done.await(5, TimeUnit.SECONDS)) throw new AssertionError("Cleanup stalled");
var workerStarted = new CountDownLatch(1);
var workerStopped = new CountDownLatch(1);
Future<?> handle = executor.submit(() -> {
workerStarted.countDown();
try { new CountDownLatch(1).await(); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
finally { workerStopped.countDown(); }
});
if (!workerStarted.await(5, TimeUnit.SECONDS)) throw new AssertionError("Not started");
if (!handle.cancel(true)) throw new AssertionError("Cancellation not accepted");
if (!workerStopped.await(5, TimeUnit.SECONDS)) throw new AssertionError("Worker ignored interrupt");
System.out.println("owned_worker_cancelled=true cooperative_cleanup_observed=true");
} finally {
release.countDown(); executor.shutdownNow();
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) throw new AssertionError("Executor still alive");
}
}
}
Compatibility check: the examples were compiled with --release 21 and executed on local HotSpot JDK 23.0.2. This checks the demonstrated invariants on that runtime; it is not a JDK performance comparison. APIs and diagnostic descriptions are linked to the Java 25 documentation, accessed 30 October 2026. No preview API is required.
Try the companion browser lab
Open Timeout ownership: inspect work after the caller leaves. Predict whether a worker with duration twenty and deadline five can still create its final synthetic effect. Compare wait-only, cooperative cancellation and ignored interruption.
Expected outcome: Wait-only mode leaves fifteen ticks of work after deadline five. Cooperative cancellation finishes its modeled cleanup two ticks after the deadline and avoids the final synthetic effect. Ignoring interruption allows late completion.
Scope: Cancellation is modeled at explicit boundaries. There is no real interrupt, network socket, operating-system process or rollback. The final effect is assumed to happen only at worker completion; an actual remote effect can happen before cancellation is observed.
Primary sources and evidence limits
The scenarios, diagrams, arithmetic and code are original educational examples. Browser ticks are synthetic. Java fixture success is not evidence of production throughput, latency, memory use or model quality.
Views are my own and do not represent any employer, past or present.
