From 32cfd9a5babc3d663498c13b9dec2002ee3d9b98 Mon Sep 17 00:00:00 2001 From: Dhriti Chopra Date: Thu, 29 Oct 2026 08:53:17 +0000 Subject: [PATCH] feat(storage): reject fast open configs that are not a single read Third step of fast open BiDi reads (go/fast-open-bidi-read, b/552263062). A fast open read is sent as one range in the open request, so only configs that make a single read on the stream (asFutureBytes, asFutureByteString, asChannel) can be one. asSeekableChannel issues reads as the caller seeks, so it cannot. readSession now rejects it with an IllegalArgumentException before anything is sent, instead of failing with an UnsupportedOperationException while creating the read. --- .../cloud/storage/StorageDataClient.java | 4 ++++ .../cloud/storage/ITFastOpenFakeTest.java | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageDataClient.java b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageDataClient.java index cd3570a3a679..a531d55974cf 100644 --- a/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageDataClient.java +++ b/java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageDataClient.java @@ -84,6 +84,10 @@ ApiFuture readSession( req.getReadRangesList().isEmpty(), "ranges included in the initial request are not supported"); checkArgument(fastOpenConfigs.size() <= 1, "at most one fast open read is supported"); + checkArgument( + fastOpenConfigs.stream().allMatch(c -> c.getType() == ProjectionType.STREAM_READ), + "a fast open read must be asFutureBytes(), asFutureByteString() or asChannel(), but was %s", + fastOpenConfigs); ObjectReadSessionState state = new ObjectReadSessionState(ctx, req); ZeroCopyBidiStreamingCallable callable = diff --git a/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/ITFastOpenFakeTest.java b/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/ITFastOpenFakeTest.java index cf7df5384127..9300cbc25734 100644 --- a/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/ITFastOpenFakeTest.java +++ b/java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/ITFastOpenFakeTest.java @@ -268,6 +268,24 @@ public void readSession_withMoreThanOneFastOpenConfig_isRejected() throws Except assertThat(requestsReceived).isEmpty(); } + @Test + public void readSession_withASeekableChannelConfig_isRejected() throws Exception { + FakeStorage server = fakeServer(ImmutableMap.of()); + + try (FakeServer fakeServer = FakeServer.of(server); + Storage storage = fakeServer.getGrpcStorageOptions().getService()) { + StorageDataClient dataClient = maybeGetStorageDataClient(storage); + + assertThrows( + IllegalArgumentException.class, + () -> + dataClient.readSession( + OPEN, CTX, ImmutableList.of(ReadProjectionConfigs.asSeekableChannel()))); + } + + assertThat(requestsReceived).isEmpty(); + } + /** * A fake server that answers each request in {@code responses} and adds it to {@link * #requestsReceived}. Any other request is answered with an error.