Conversation
4d7beb0 to
fee78f9
Compare
He-Pin
left a comment
There was a problem hiding this comment.
Verified the new bulk overrides match java.io.ByteArrayInputStream's JDK 17 behavior and never read past eod, so the shared-array ByteString1C path cannot leak sibling bytes; the skip-clamping and constructor-overflow fixes are correct. One test suggestion inline.
| class UnsynchronizedByteArrayInputStreamSpec extends AnyWordSpec with Matchers { | ||
|
|
||
| private def bytes(s: String): Array[Byte] = s.getBytes(StandardCharsets.UTF_8) | ||
| private def str(b: Array[Byte]): String = new String(b, StandardCharsets.UTF_8) |
There was a problem hiding this comment.
Consider adding a differential test that runs randomized operation sequences against both this stream and a ByteArrayInputStream, asserting identical outputs except the two documented deviations (zero-length read at EOF returns 0, not -1; negative skip throws IllegalArgumentException). It is the strongest guard for a serialization-path class.
There was a problem hiding this comment.
Added a randomized differential test against java.io.ByteArrayInputStream in 7339b14 (rebased on main as well). It compares return values, exceptions, buffer contents and available() after every operation, special-casing only the two documented deviations.
Motivation: UnsynchronizedByteArrayInputStream only overrode the basic read/skip methods, so readAllBytes, readNBytes, skipNBytes and transferTo fell through to the generic InputStream defaults. Those allocate 16 KiB scratch buffers in a loop and then concatenate, which is wasteful when the whole payload already sits in a byte[]. skip also advanced the offset unconditionally with Math.addExact/toIntExact, so it could throw ArithmeticException for large values and leave offset past eod. Modification: - Override readAllBytes, readNBytes(int), readNBytes(byte[],int,int), skipNBytes and transferTo to operate directly on the backing array (one Arrays.copyOfRange / OutputStream.write each). - Clamp skip to the remaining bytes so offset never exceeds eod and large skips cannot overflow; simplify available() and readLocal() accordingly. - Simplify the (data, offset, length) constructor so offset/eod are clamped to the array with long arithmetic (no int overflow), and use Objects.checkFromIndexSize for the read(byte[],int,int) bounds check. - Expand UnsynchronizedByteArrayInputStreamSpec to cover every method and edge case (EOF, zero-length reads, clamping, overflow). - Add readAllBytes and transferTo cases to ByteString_asInputStream_Benchmark. Result: readAllBytes/readNBytes/transferTo on ByteString.asInputStream copy the data once instead of chunking through temporary buffers. Local JMH (-f 1 -wi 2 -i 3) for single_bs_as_input_stream_read_all_bytes: 10 KB 201k -> 503k ops/s, 1000 KB 3.3k -> 5.5k ops/s. Tests: - sbt "actor-tests/testOnly org.apache.pekko.util.UnsynchronizedByteArrayInputStreamSpec org.apache.pekko.util.ByteStringSpec" (226 passed) - sbt "actor/mimaReportBinaryIssues" (no issues) - sbt "bench-jmh/Jmh/compile" and Jmh/run of the new benchmarks - scalafmt on changed Scala files, sbt actor/javafmtAll (JDK 17) - git diff --check References: None - follow-up to apache#2300 which introduced this class
Run randomized operation sequences against both UnsynchronizedByteArrayInputStream and java.io.ByteArrayInputStream and assert identical results, buffer contents and available(), allowing only the two documented deviations (zero-length read at EOF returns 0, negative skip throws IllegalArgumentException).
fee78f9 to
7339b14
Compare
Motivation
UnsynchronizedByteArrayInputStream(used byByteString.asInputStreamand the Java/Jackson serializers) only overrode the basicread/skipmethods.readAllBytes,readNBytes,skipNBytesandtransferTofell through to the genericInputStreamdefaults, which allocate 16 KiB scratch buffers in a loop and then concatenate — wasteful when the whole payload already sits in abyte[].skipalso advancedoffsetunconditionally viaMath.addExact(offset, Math.toIntExact(n)), so a largencould throwArithmeticExceptionandoffsetcould end up pasteod.Modification
readAllBytes,readNBytes(int),readNBytes(byte[],int,int),skipNBytesandtransferToto operate directly on the backing array (oneArrays.copyOfRange/OutputStream.writeeach) — the same set of overridesjava.io.ByteArrayInputStreamhas.skipto the remaining bytes sooffset <= eodalways holds and large skips cannot overflow; simplifyavailable()andreadLocal()accordingly.(data, offset, length)constructor sooffset/eodare clamped to the array using long arithmetic (no int overflow), and useObjects.checkFromIndexSizefor theread(byte[],int,int)bounds check.UnsynchronizedByteArrayInputStreamSpecto cover every method and edge case (EOF, zero-length reads, clamping, overflow, negative args).java.io.ByteArrayInputStream, asserting identical results except for the two documented deviations (zero-length read at EOF returns 0; negativeskipthrowsIllegalArgumentException).readAllBytesandtransferTocases toByteString_asInputStream_Benchmark.Result
readAllBytes/readNBytes/transferToonByteString.asInputStreamcopy the data once instead of chunking through temporary buffers. Observable behaviour is otherwise unchanged (negativeskipstill throws, as before).Local JMH (
-f 1 -wi 2 -i 3, short run so error bars are wide):single_bs_as_input_stream_read_all_bytessingle_bs_as_input_stream_read_all_bytessingle_bs_as_input_stream_transfer_tosingle_bs_as_input_stream_transfer_totransferTois dominated by theByteArrayOutputStreamcopy on the receiving side, so the difference there is within noise.Tests
sbt "actor-tests/testOnly org.apache.pekko.util.UnsynchronizedByteArrayInputStreamSpec org.apache.pekko.util.ByteStringSpec"— 226 passedsbt "actor-tests/testOnly org.apache.pekko.util.UnsynchronizedByteArrayInputStreamSpec"after adding the differential test — 14 passed; injecting a bug intoreadNBytesmakes the differential test failsbt "actor/mimaReportBinaryIssues"— no issues (class is@InternalApi; only methods added)sbt "bench-jmh/Jmh/compile"andJmh/runof the new benchmarksscalafmton changed Scala files,sbt actor/javafmtAllon JDK 17git diff --checkReferences
None - follow-up to #2300 which introduced this class