Repository navigation
Bug-hunt fixes: safeWrite, trim, write/close race, statistics - #189
Merged
Merged
Conversation
writeSingleByte() switched the shared field ignoreSafeWrite on around its own write. A write(byte[]) on another thread that took the lock inside that window skipped the safeWrite clone and stored the caller's array by reference, so a later change to the array reached the reader. The skip is now a parameter of the write path instead of a field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
BUGHUNT-FOUND.md records every bug found on this working branch with its file, severity, reproduction test, status and commit. BUGHUNT-CHECKED.md lists every production class that was read in full, with the blob hash it was checked at, so a later pass re-checks only files that changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
trim() drained the buffer through its own input stream and wrote the bytes back, so the volatile availableBytes fell to zero and rose again under bufferLock. waitForAtLeast reads it twice without the lock and could return fewer bytes than requested on an open stream; read() then reported the end of a stream that still held data. trim now copies the entries directly and leaves the count untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Add the missing anchors the overview links point at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
The Javadoc of clampToMaxInt still explained the clamp through the trim() loop that read the buffer back through its own input stream; since 89d2173 trim() copies straight from the deque and the only caller is available(). The README named Integer.MAX_VALUE as the maxAllocationSize default; since f88be77 it is MAX_ARRAY_LENGTH (Integer.MAX_VALUE - 8), and the setter caps larger values to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
A write that raced close() from another thread passed requireNonClosed() before taking bufferLock, while closeAll() set the flag without the lock. The write could then append after close() had returned and still return normally, while a reader in between had already reported the end of the stream. The check now runs under bufferLock and closeAll() sets the flag under it, so such a write is either buffered before the close or fails with "Stream closed". The test parks a second writer on the buffer lock from the trim-start signal and closes while it waits; it fails without the change. A jcstress test for the same race is added for the opt-in profile run. Also document that read(byte[], off, len) waits for all len bytes, the design since the first version, and how to read what is available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Everything the bug hunt left open - decided but not implemented, waiting for a decision, and the CI and hardware runs - is listed in TODO.md with a pointer to its ledger entry, so it survives the end of the session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Every measurement and CI or hardware run the bug hunt left open, with the commands, the hypothesis, what the result decides and an answer field, so an agent can run them all at once and answer in the repository. TODO.md points to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
All four points ran on a throwaway VM (8 vCPU). - S1: jcstress 604/604; WriteRacingCloseRace never shows the forbidden "1, -1" in 3.54 billion samples, so F149 holds. - S2: vmlens green (19 interleavings). - S3: ESC 14/14, JPF green, RAC green for 287 of 288 tests including the F149 test and the F73 trim. The excluded test is the 2-million-write concurrent trim test, which under RAC prints a stack trace per arraycopy (21 GB of log) and needs about two hours; the openjml-rac job would time out on it without any contract being violated. - S4: a throwaway JPF harness with separate writer, closer and reader threads finds no error with the fix and finds the race before F149. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bvDWEYcnzbZvnHgzS2KuX
JpfStreamBufferHarness writes and closes from one thread, so it could not see bug-hunt finding F149 (a write passing the closed check outside the lock and adding its bytes after the reader saw the end). The new harness runs writer, closer and reader in three threads: no errors over 75,438 states with the fix, and against the classes before F149 it reports "reader got 1 bytes, but 2 writes returned normally" after 3,956 states. The jpf-interleavings job runs and gates both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
… timeout Under RAC every System.arraycopy and clone prints a ~10 KB stack trace for an uncompiled model method of the bundled JDK specification, so the concurrent-trim test's 2 million writes took 7,344 s and 21 GB of log and the safeWrite test 753 s, with every contract holding (measured). The jml-rac profile sets streambuffer.test.rac, and both loops run 5,000 and 2,000 iterations there: 30 s and 24 s under RAC; the default build keeps 2,000,000 and 200,000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
StreamBufferProperties matched none of Surefire's default include patterns, so its 5 properties ran in no build, though CLAUDE.md and the jml-rac profile counted on them. The default patterns plus **/*Properties.java are listed now: 299 -> 304 tests, all green. Under RAC they run with 100 tries instead of 1000 (434 s -> 21 s); the full RAC suite, 293 tests, takes 4 min 19 s on 4 cores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
…t item S1-S4 are measured (BUGHUNT-MEASUREMENTS.md): jcstress, vmlens, ESC, RAC and JPF are green with the F149 and F73 changes, so the item goes. Two OpenJML suspicions from the RAC run are written down to be verified and filed with the awaited release: a signals clause evaluated for an exception of another type, and a stack trace per call for an uncompiled model method of the bundled JDK spec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Both are confirmed, identically on OpenJML 21.0.28 and on a dev-21 build (529ac454da). - signals cast: RAC's call-site check casts the thrown exception to the type of a signals clause without an instanceof test when the clause's predicate folds to false for the actual arguments (waitForAtLeast(1L)). It is only reported, never thrown, even under rac=exception. Located in JmlAssertionAdder.applyHelper (21.0.28 l. 12326/12341, dev-21 l. 12581/12596). Minimal reproducer and counter-checks included. - stack trace per call: every System.arraycopy/clone prints the skipped Arrays.equalArrays clause with a full stack trace (1000 calls, 1000 traces) from runtime/Utils.java:296-304; compiling with -quiet removes it while real violations are still thrown. Two issue drafts in the style of OpenJML#1012 are included; nothing was reported upstream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bvDWEYcnzbZvnHgzS2KuX
Both were confirmed on 21.0.28 and dev-21 and filed together on 2026-10-19 as OpenJML/OpenJML#1050. TODO.md and the measurement record now say so instead of "suspected, not yet filed". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015bvDWEYcnzbZvnHgzS2KuX
The stress-loop scaling and the reduced jqwik tries under -Pjml-rac exist only because of the per-call stack trace filed as #1050. The test helper, the profile comment, CLAUDE.md and the OpenJML upgrade checklist in TODO.md now name the issue, so the workaround is dropped when a release fixes it and not before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
S1-S4 are measured and acted on (JPF write/close harness, RAC scaling, jqwik includes), and the two OpenJML defects are filed as #1050; the brief with all results stays in the history at 5d4819b, which TODO.md names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
A review of the whole branch found no defect, only text the fixes had left behind: - requireNonClosed() said it must not be called under the lock; since F149 it is, on purpose. The Javadoc says so now, and @GuardedBy("bufferLock") states it for Error Prone. - isTrimRunning and shouldTrim() explained the flag by trim's own stream reads and writes, which F73 removed. They now name what is left: a trim signal whose release() writes on the trim thread (bufferLock is reentrant). - close() and the README "Thread Safety" section did not say that close() waits for a write or trim in progress (F149); one sentence each. - StreamBufferTest still described the removed ignoreSafeWrite field and trim's read/write phases. Four tests are renamed after what they check (write_wrappingStreamThrows_bufferStaysUsable, trim_triggeredByWrite_flagIsResetAfterwards, trim_ofSeveralChunks_flagIsResetAndDataIntact, trim_withSafeWrite_allDataPreserved), their comments and the section's requirement list rewritten; no assertion changed. - src/test/jpf/README.md: maxDepth 127, as measured (was 130). - The ledger: F02, F73 and F149 get a "Nachgemessen" line with the results of the measurement brief (jcstress, vmlens, ESC 14/14, RAC, JPF), and BUGHUNT-CHECKED names the former open suspicion as F149. mvn test 304/304, spotbugs 0, package incl. javadoc without warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Since F73 trim() copies straight from the FIFO and does no stream I/O,
so the two isTrimRunning guards around the statistics no longer kept
any internal I/O out of the counters. The only I/O they still excluded
was a write or read a trim start signal makes in its release() on the
trim thread: such a write raised availableBytes but neither
totalBytesWritten nor maxObservedBytes, such a read lowered
availableBytes without counting, contrary to the getter Javadoc, the
README and the invariant written == read + available. The guards are
removed; the one in shouldTrim() stays and still blocks a nested trim.
The new StatisticsTests cases were red before the change ("Expected:
is <12L> but: was <9L>" for the hook write, "Expected: is <1L> but:
was <0L>" for the hook read) and are green with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
The F212 entry was added without its row in the overview list and without the anchor the list links to, unlike every other entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
safeWrite_concurrentSingleByteWrite_arrayStillCloned only called
Waiter.resume(), which in ConcurrentUnit 0.4.6 never rethrows (only
await() does), so a reader that died early left the mutated-byte count
at zero and the test passed without checking anything. Both background
threads now record any Throwable in an AtomicReference, the writer and
the reader count their bytes, and the test asserts no failure and
bytes read == 8 * array writes + single bytes written. The older
concurrentReadWrite stress test now calls await() after resume(), so
the cause of a dead reader reaches the test thread, and its comment no
longer claims resume() re-raises.
With an IOException injected into the read path the old version stayed
green and the new one is red ("Expected: is null but: was
<java.io.IOException: injected reader failure>", "Expected: is
<1812184L> but: was <0L>"); without the injection it is green.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
write_wrappingStreamThrows_bufferStaysUsable (formerly trim_ignoreSafeWriteFlagResetDuringWriteException_streamRecoverable) overrode getOutputStream() with a wrapper that threw before it called the real stream, so the act step never entered StreamBuffer and the asserted message was the test's own. It was already vacuous before F73: trim() wrote through the private os field and never saw the wrapper. The branch renamed it and kept it; it is removed instead. Its remaining recovery steps are covered more strictly by trim_withSafeWriteEnabled_preservesDataIntegrity and trim_withSafeWrite_allDataPreserved, and a failure inside the real trim, with isTrimRunning reset and the stream recovering, by the two trim_signalReleaseException* tests. Test-only change; StreamBufferTest 289/289 green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
F149 has two halves: requireNonClosed() under bufferLock in
writeFromArray, and streamClosed set under the same lock in closeAll().
The only deterministic test calls close() from the trim-start signal on
the thread that already holds the lock, so it stays green when only the
closeAll() half is reverted; only the opt-in jcstress race and the
scheduled JPF harness would see that regression.
close_fromAnotherThreadDuringAWrite_waitsUntilTheWriteIsBuffered starts
the closer as a thread of its own inside the signal, records its state,
isClosed() and the buffered count there, and asserts after the write
that the closer was BLOCKED, the stream still open with 2 bytes, and
closed with 2 bytes afterwards.
With the lock removed from closeAll() alone it is red in 3 of 3 runs
("Expected: is <BLOCKED> but: was <TERMINATED>", "Expected: is <false>
but: was <true>") while the existing test stays green; with the fix it
is green in 5 of 5 runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Since F73 trim() copies straight from the FIFO and never reads or writes through the streams, but the texts of the close-during-trim tests still described a read phase, a write-back through os.write() and a requireNonClosed() check inside trim. A failure of one of them would have sent the reader after a mechanism that no longer exists. trim_closeDuringTrim_writeBackDoesNotThrowAndPreservesData is renamed to trim_closeDuringTrim_triggeringWriteDoesNotThrowAndPreservesData; its Javadoc, comments, display name and assertion message now say what it guards: a trim hook closes the stream re-entrantly inside trim's own critical section, and the triggering write, which already passed requireNonClosed(), must not throw nor lose any of the 9 bytes. The description of trim_closeCalledDuringTrim_handlesGracefully, the trim signal tests and the robustness header drop the stale source line numbers and trim phases; requireNonClosed() is called private and checked under bufferLock. Text only, the assertions are unchanged; StreamBufferTest 290/290 green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
BUGHUNT-CHECKED.md counts a class as checked only while its blob hash still matches HEAD, yet no line named a StreamBuffer.java blob after F73: the F149 fix, the Javadoc follow-up and F212 were never recorded, and the 5e0f51d statistics line still explained isTrimRunning by trim's own stream reads and writes, which F73 removed. That line is replaced by one for blob cbb7f64 that states the post-F73 facts: trim() copies straight from the FIFO, the only I/O during a trim is a trim start signal's release() on the trim thread, the statistics guards that hid it were F212, and isTrimRunning now only blocks a nested trim in shouldTrim(). A second line records the re-check of the F149 write/close path at the same blob. Documentation only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
[Unreleased] gains a Fixed entry for the trim start signal I/O that the statistics left out, and Changed entries for the test work of the branch: the jqwik properties running in every build, the safeWrite test surfacing background failures, the removed wrapping-stream test, the new close-on-another-thread pin, the close-during-trim test texts, the third JPF harness and the RAC scaling of the stress loops. TODO.md was checked and needs no change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
The Unreleased section named the CI fixes, the statistics fix and the test work, but not the three user-visible StreamBuffer fixes the bug hunt made on this branch: the safeWrite bypass by a concurrent write(int), the half-emptied buffer a reader could see during trim(), and the write that could land after a close() from another thread. The documented read(byte[], off, len) semantics are listed under Changed, since consumers may have relied on the undocumented form. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
The branch was rebased onto main (two commits there: the dependency, plugin and shared-pin bumps), which gave every commit of the branch a new hash. The bug-hunt ledgers, the measurement notes, the TODO and the JPF harness documentation cite commits of this branch by hash; each citation now names the rebased commit with the same change (matched by author date and subject, 1:1 for every non-merge commit). Nothing else changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 21:14 — with
GitHub Actions
Failure
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 21:14 — with
GitHub Actions
Failure
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #189 +/- ##
============================================
+ Coverage 96.99% 98.89% +1.90%
- Complexity 116 119 +3
============================================
Files 1 1
Lines 266 271 +5
Branches 29 28 -1
============================================
+ Hits 258 268 +10
+ Misses 2 0 -2
+ Partials 6 3 -3 ☔ View full report in Codecov by Harness. |
PR #188 (OpenJML 21.0.29 fixups v3: no bundled spec deleted at install any more) merged first; the CHANGELOG and CLAUDE.md entries of both sides are kept, main's corrected facts replacing the branch's "ArrayDeque.jml still deleted" bullet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
bernardladenthin
had a problem deploying
to
startgate
October 21, 2026 22:42 — with
GitHub Actions
Error
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 22:42 — with
GitHub Actions
Failure
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 22:42 — with
GitHub Actions
Failure
…e spec-deletion re-try from TODO.md close() takes bufferLock since the write/close fix, and a trim start or end signal is released under that lock: a release() that waits for a read, write or close of this buffer would hold close() on another thread behind it. The Javadoc of close() and of the two add*Signal methods say so. TODO.md's "on every OpenJML upgrade" bullet still asked to re-try dropping "the two spec-file deletions"; since PR #188 (fixups v3) the setup action deletes none. Both found by the review of PR #189. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
bernardladenthin
had a problem deploying
to
startgate
October 21, 2026 22:44 — with
GitHub Actions
Error
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 22:44 — with
GitHub Actions
Failure
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 22:44 — with
GitHub Actions
Failure
The consolidation allocated its chunks one at a time, each after the entries of the previous chunk had already been polled from the deque, so an OutOfMemoryError on a later chunk left the buffer with those entries gone while availableBytes still counted them: a reader then failed on an empty deque or read the wrong bytes. trim() now allocates every chunk first and copies afterwards, so the error propagates out of the write with the buffer exactly as that write left it, and the next write trims again. The one allocation goes through the package-private seam allocateTrimChunk(int), which the new test overrides to fail the second allocation: red against the old loop (element count 1 instead of 3), green after the rework. Suite 307/307, javadoc, SpotBugs and PIT clean; no JML change (trim() is not declared in the spec file, the seam is outside the ESC list). README (maxAllocationSize), CHANGELOG and the bug-hunt ledger updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 23:06 — with
GitHub Actions
Failure
bernardladenthin
had a problem deploying
to
maven-central
October 21, 2026 23:06 — with
GitHub Actions
Failure
|
This branch had an error being deployed
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.




Summary
BUGHUNT-FOUND.md):safeWritecould be bypassed by a concurrentwrite(int)(the instance-wide clone flag is gone, cloning is a parameter of the private write path; F02); a concurrenttrim()let a reader see a half-emptied buffer (it now copies straight from the FIFO into blocks of at mostmaxAllocationSizeand leaves the count unchanged; F73); awriteracingclose()could land after the close (the closed check runs underbufferLock,close()sets the flag under the same lock and waits for a write or trim in progress; F149); atrim()whose block allocation failed with anOutOfMemoryErrorlost the bytes it had already copied whileavailable()still counted them -- every block is now allocated before the FIFO is touched, through the one package-private seamallocateTrimChunk(int), which is how the test provokes the failure (F268). The statistics count the I/O of a trim start signal (F212), and theclampToMaxIntJavadoc and themaxAllocationSizedefault are corrected (F110).safeWritetest surfaces background failures, a test that never reached the buffer is removed, a new test pins thatclose()waits for a write on another thread, a jcstressWriteRacingCloseRaceis added, and the close-during-trim tests describe the currenttrim().WriteClose.jpf, writer, closer and reader in three threads) model-checks the order of write and close, which the existing harness cannot see; under RAC the two stress loops and the jqwik tries are scaled down (RAC: spuriousClassCastExceptionfrom a callee'ssignalsclause at the call site, and a stack trace per call for skippedArrays.equalArraysclauses (from streambuffer) OpenJML/OpenJML#1050 prints a stack trace perSystem.arraycopy), and the two suspected OpenJML RAC defects are recorded with their upstream reference.read(byte[], off, len)waiting for alllenbytes is now stated in the Javadoc and the README, with thewaitForAnyData()+available()pattern; the trim signals are documented as released under the buffer lock (arelease()must not wait for this buffer), and the README'smaxAllocationSizesection says what happens when an allocation fails during a trim.main(PR Drop the ArrayDeque spec workaround, OpenJML 21.0.29 has the fix #188 merged into it, conflicts inCHANGELOG.md/CLAUDE.mdresolved by keeping both sides' entries).Test plan
StreamBufferTest,StreamBufferProperties,StreamBufferLincheckTest(307/307), PIT 198/198 (mutationThresholdheld),spotbugs:check,spotless:check, the javadoc jar; the OpenJML ESC/RAC and JPF jobs run in the separate formal-verification workflow.1204a58: Build, Test, vmlens, Report/PIT, smoke, shared files, code style, OpenJML ESC + RAC, CodeQL, osv; the two GPG jobs are red by design on a non-mainbranch, SonarCloud's single finding onpositionAtCurrentBufferEntry += toCopyis a false positive -- the update runs underbufferLock).CHANGELOG.md,README.md,CLAUDE.md,TODO.md,src/test/jpf/README.md,BUGHUNT-FOUND.md/BUGHUNT-CHECKED.md.Related issues / PRs
ClassCastExceptionfrom a callee'ssignalsclause at the call site, and a stack trace per call for skippedArrays.equalArraysclauses (from streambuffer) OpenJML/OpenJML#1050 (the RAC stack traces behind the scaled-down loops).Checklist
CONTRIBUTING.mdandCODE_OF_CONDUCT.mdtype(scope):prefixed.Generated by Claude Code