Was checking out your repo and do some comparisons against something i built and noticed that it breaks in some ways even without concurrency.
Appends and reads permanently fail on any stream where a single WAL-callback latency metric throws. Reproducible from a cold start with zero concurrency.
Root cause
S3Storage.append() (stream/src/main/java/io/streamstack/s3/S3Storage.java:328-337) times itself with System.nanoTime() and records the delta in a whenComplete callback that runs on a different thread (AUTOMQ_S3STREAM_APPEND_CALLBACK-N):
finallongstartTime = System.nanoTime();
...
returncf.whenComplete((nil, ex) -> {
streamRecord.release();
APPEND_STORAGE_LATENCY.record(TimerUtil.timeElapsedSince(startTime, TimeUnit.NANOSECONDS));
});Cross-thread nanoTime() subtraction occasionally goes negative on this host. DeltaHistogram.record() -> HdrHistogram Recorder.recordValue() throws:
java.lang.ArrayIndexOutOfBoundsException: Histogram recorded value cannot be negative.
at org.HdrHistogram.AbstractHistogram.recordSingleValue(AbstractHistogram.java:559)
at org.HdrHistogram.Recorder.recordValue(Recorder.java:136)
at io.streamstack.s3.metrics.wrapper.DeltaHistogram.record(DeltaHistogram.java:108)
at io.streamstack.s3.S3Storage.lambda$19(S3Storage.java:337)
- CompletableFuture.whenComplete semantics turn this into a false failure. whenComplete returns a new future; if the action throws, the new future completes exceptionally even though the antecedent cf succeeded. The WAL write is durable, the caller is told it failed anyway, solely because the metrics callback threw.
- S3Stream.append0() (stream/src/main/java/io/streamstack/s3/S3Stream.java:267-279) treats any exception, regardless of type, as grounds for permanent fencing:
returncf.whenComplete((rst, ex) -> {
if (ex == null) return;
status.markFenced(); // unconditional
...
});- Status.isWritable() / isReadable() both just check status == 0. There is no unfencing path. Once one bad sample fences a stream, every subsequent append and read on it fails permanently with:
io.streamstack.api.exceptions.StreamClientException: code: 1, [streamId=N epoch=1] stream is already closed
Impact
- Single writer, zero concurrency: 23.5% of appends failed (6,508/8,513 succeeded).
- 200 concurrent writers, 8 streams: 47.5% succeeded (
- All bounded catch-up reads failed (0/5,619, 0/9,350 across two runs).
- Reproducible on a fresh, empty in-memory (mem://) in
Suggested fix
- Don't let a whenComplete side effect (metrics) deterthe returned future, record latency without re-throwing into the chain (e.g. wrap record() in try/catch, or move it off the completion path).
- In S3Stream.append0, only fence on exceptions that amClientException with EXPIRED_STREAM_EPOCH); let otherfailures fail that single request without poisoning the stream.
Environment
- Commit: 88c9910 (2026-08-12)
- JDK 17, in-memory (mem://) storage/WAL backend, sing.mem.yaml
Was checking out your repo and do some comparisons against something i built and noticed that it breaks in some ways even without concurrency.
Appends and reads permanently fail on any stream where a single WAL-callback latency metric throws. Reproducible from a cold start with zero concurrency.
Root cause
S3Storage.append()(stream/src/main/java/io/streamstack/s3/S3Storage.java:328-337) times itself withSystem.nanoTime()and records the delta in awhenCompletecallback that runs on a different thread (AUTOMQ_S3STREAM_APPEND_CALLBACK-N):Cross-thread nanoTime() subtraction occasionally goes negative on this host. DeltaHistogram.record() -> HdrHistogram Recorder.recordValue() throws:
io.streamstack.api.exceptions.StreamClientException: code: 1, [streamId=N epoch=1] stream is already closedImpact
Suggested fix
Environment