Repository navigation
Conversation
#### Motivation Serializers and Jersey request streams duplicate Buffer-to-byte-array conversion. Each copy must handle readable regions and backing-array reuse, making buffer edge cases harder to keep consistent. #### Modifications - Consolidate conversion while preserving each caller's reader-index behavior and the serializer's forced-copy option. - Retain Jackson's offset-aware backing-array path to avoid additional heap-buffer copies. #### Result Empty, heap, direct, and sliced buffers share conversion handling. Validation: 690 tests passed across the eight affected modules; nine tests were skipped. Affected-module quality checks and `./gradlew quality` passed, including dependency analysis and documentation validation. Closes apple#863.
bryce-anderson
left a comment
There was a problem hiding this comment.
Thank you! This is looking pretty good. I just have a few minor suggestions.
| * @param forceCopy whether to always copy into a new array | ||
| * @return an array containing exactly the readable bytes | ||
| */ | ||
| public static byte[] toByteArray(final Buffer buffer, final boolean forceCopy) { |
There was a problem hiding this comment.
@bryce-anderson WDYT if we promote this to Buffer.toByteArray() and Buffer.toByteArray(int, int) API instead of an internal utility? It will be similar to Buffer.toNioBuffer() and Buffer.toString(Charset) API, and will have to follow semantics of not moving indexes.
I've seen this as quite often need to get a byte-array without worrying about offsets. This is why we created ByteArraySerializer, but its discoverability is questionable. A method on Buffer would be much easier for users.
The only trick is that in many other java use-cases it always makes a copy: ByteArrayOutputStream.toByteArray(), ByteString.toByteArray() (protobuf), BigInteger.toByteArray(), etc. So, the default behavior will need to be forceCopy == true and we may have an overload where it skips copying.
Other benefits would be:
- We can avoid class name clash with
io.servicetalk.buffer.netty.BufferUtils. servicetalk-buffer-apiwon't become an API dependency on this module.
There was a problem hiding this comment.
Thank you for your notice! This actually removes the circular dependency.
There was a problem hiding this comment.
Implemented this proposal in the public API commit: Buffer now has readable-region and indexed toByteArray overloads, copies by default, offers explicit optional backing-array reuse, validates bounds, and never moves indices. The caller migration commit removes the internal helper and the utility-module dependencies.
…internal/BufferUtils.java Co-authored-by: Bryce Anderson <bl_anderson@apple.com>
#### Motivation Applications need byte arrays from buffers without duplicating offset calculations or depending on the buffer's storage type. An API on Buffer makes this operation easier to discover than an internal conversion utility. #### Modifications - Add readable-region and indexed toByteArray methods that copy by default and preserve reader and writer indices. - Allow callers to reuse an exactly matching backing array explicitly, with shared empty arrays for all zero-length results. #### Result Callers can obtain byte arrays from heap, direct, sliced, and read-only buffers through the same public API.
Motivation: Callers should use the public Buffer API instead of a duplicated internal conversion helper. Modifications: - Migrate byte-array serialization, Jackson, and Jersey callers. - Preserve each caller's existing consumption and array-sharing behavior. - Remove BufferUtils, its tests, dependencies, and unrelated varint changes. Result: Buffer conversion is centralized without introducing utility-module dependencies.
idelpivnitskiy
left a comment
There was a problem hiding this comment.
Looks great! A few more comments for the new approach:
idelpivnitskiy
left a comment
There was a problem hiding this comment.
Looks great! Thank you!
2 minor nits that I will apply via UI:
|
|
||
| assertThat(stringStreamingSerializer(typeRef).deserialize(from(EMPTY_BUFFER, buffer, EMPTY_BUFFER), alloc) | ||
| .toIterable(), contains("value")); | ||
| assertThat(buffer.readerIndex(), is(buffer.hasArray() ? 1 : 8)); |
There was a problem hiding this comment.
FYI, these assertions capture existing behavior that violates the Deserializer contract (heap buffers keep readerIndex, direct are consumed). Fine to keep the test as-is, it will be addressed separately in #3731 after this PR is merged.
There was a problem hiding this comment.
Thanks for pointing that out and for merging! I'll follow along with #3731
| try (StreamingDeserializer<String> deserializer = serializationProvider.getDeserializer(String.class)) { | ||
| assertThat(deserializer.deserialize(buffer), contains("value")); | ||
| } | ||
| assertThat(buffer.readerIndex(), is(direct ? 8 : 1)); |
Motivation
Getting a buffer's bytes currently requires callers to account for readable
offsets and the underlying storage type. A discoverable API on
Buffercentralizes this conversion without requiring an internal utility dependency.
Modifications
Buffer.toByteArrayoverloads. They copy bydefault, preserve reader and writer indices, and validate indexed regions
against the buffer capacity.
array and returns a shared array for all zero-length results.
shared-array cases.
ByteArraySerializer, Jackson deserializers, and Jersey whilepreserving each caller's existing consumption and zero-copy behavior.
BufferUtils, its tests, and its unnecessarydependencies. Restore the unrelated varint test to the PR base version.
Result
Applications and internal callers can obtain byte arrays through a consistent
public
BufferAPI, with copying as the safe default and explicit backing-arrayreuse when requested.
Validation: 1,027 tests passed across the ten affected modules; nine tests were
skipped. Repository compilation, affected-module quality checks, dependency
analysis, documentation validation, and
./gradlew qualitypassed.Closes #863.