Skip to content

Commit c857619

Browse files
authored
chore: make ReadProjectionConfigs#asFuture{Bytes,ByteString} public (googleapis#3026)
Additional javadocs, test added to ensure getting a future after a session has closed results in an exception.
1 parent 4a9c3e4 commit c857619

11 files changed

Lines changed: 93 additions & 22 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.api.core.BetaApi;
20+
21+
/**
22+
* Root exception for async tasks which fail due to a session being closed.
23+
*
24+
* @see BlobReadSession
25+
*/
26+
@BetaApi
27+
public final class AsyncSessionClosedException extends RuntimeException {
28+
AsyncSessionClosedException(String msg) {
29+
super(msg);
30+
}
31+
}

google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadSession.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public interface BlobReadSession extends AutoCloseable, Closeable {
6262
*
6363
* <p>If a projection is not fully consumed/resolved it will be transitioned to a failed state.
6464
*
65-
* <p>This method MUST be closed to ensure cleanup of any inflight buffers, and to avoid a memory
65+
* <p>This method MUST be called to ensure cleanup of any inflight buffers, and to avoid a memory
6666
* leak.
6767
*
6868
* @since 2.51.0 This new api is in preview and is subject to breaking changes.

google-cloud-storage/src/main/java/com/google/cloud/storage/ObjectReadSessionImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public Object getResource() {
8383
public <Projection> Projection readAs(ReadProjectionConfig<Projection> config) {
8484
lock.lock();
8585
try {
86-
checkState(open, "stream already closed");
86+
checkState(open, "Session already closed");
8787
switch (config.getType()) {
8888
case STREAM_READ:
8989
long readId = state.newReadId();

google-cloud-storage/src/main/java/com/google/cloud/storage/ObjectReadSessionStream.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import io.grpc.Status.Code;
4848
import io.grpc.StatusRuntimeException;
4949
import java.io.IOException;
50-
import java.nio.channels.AsynchronousCloseException;
5150
import java.util.List;
5251
import java.util.concurrent.ExecutionException;
5352
import java.util.concurrent.Executor;
@@ -124,7 +123,7 @@ public ApiFuture<Void> closeAsync() {
124123
}
125124
int updatedLeaseCount = openLeases.decrementAndGet();
126125
if (updatedLeaseCount == 0) {
127-
AsynchronousCloseException cause = new AsynchronousCloseException();
126+
AsyncSessionClosedException cause = new AsyncSessionClosedException("Session already closed");
128127
ApiFuture<?> f = failAll(() -> new StorageException(0, "Parent stream shutdown", cause));
129128
return ApiFutures.transformAsync(f, ignore -> ApiFutures.immediateFuture(null), executor);
130129
} else {

google-cloud-storage/src/main/java/com/google/cloud/storage/ReadAsFutureByteString.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
*/
4343
@BetaApi
4444
@Immutable
45-
final class ReadAsFutureByteString
45+
public final class ReadAsFutureByteString
4646
extends BaseConfig<ApiFuture<DisposableByteString>, AccumulatingRead<DisposableByteString>> {
4747

4848
static final ReadAsFutureByteString INSTANCE =

google-cloud-storage/src/main/java/com/google/cloud/storage/ReadAsFutureBytes.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
*/
3838
@BetaApi
3939
@Immutable
40-
final class ReadAsFutureBytes extends BaseConfig<ApiFuture<byte[]>, AccumulatingRead<byte[]>> {
40+
public final class ReadAsFutureBytes
41+
extends BaseConfig<ApiFuture<byte[]>, AccumulatingRead<byte[]>> {
4142

4243
static final ReadAsFutureBytes INSTANCE =
4344
new ReadAsFutureBytes(RangeSpec.all(), Hasher.enabled());

google-cloud-storage/src/main/java/com/google/cloud/storage/ReadProjectionConfigs.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static ReadAsChannel asChannel() {
8383
* @since 2.51.0 This new api is in preview and is subject to breaking changes.
8484
*/
8585
@BetaApi
86-
static ReadAsFutureBytes asFutureBytes() {
86+
public static ReadAsFutureBytes asFutureBytes() {
8787
return ReadAsFutureBytes.INSTANCE;
8888
}
8989

@@ -104,7 +104,7 @@ static ReadAsFutureBytes asFutureBytes() {
104104
* @since 2.51.0 This new api is in preview and is subject to breaking changes.
105105
*/
106106
@BetaApi
107-
static ReadAsFutureByteString asFutureByteString() {
107+
public static ReadAsFutureByteString asFutureByteString() {
108108
return ReadAsFutureByteString.INSTANCE;
109109
}
110110

google-cloud-storage/src/main/java/com/google/cloud/storage/ZeroCopySupport.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,45 @@
1616

1717
package com.google.cloud.storage;
1818

19-
import com.google.api.core.InternalApi;
19+
import com.google.api.core.BetaApi;
2020
import com.google.api.core.InternalExtensionOnly;
2121
import com.google.protobuf.ByteString;
2222
import java.io.Closeable;
2323
import java.io.IOException;
2424

25-
@InternalApi
26-
@InternalExtensionOnly
27-
interface ZeroCopySupport {
25+
/**
26+
* Public components which exist to support zero-copy data access
27+
*
28+
* @since 2.51.0 This new api is in preview and is subject to breaking changes.
29+
*/
30+
@BetaApi
31+
public abstract class ZeroCopySupport {
32+
33+
private ZeroCopySupport() {}
2834

29-
@InternalApi
35+
/**
36+
* Represents an object that can be accessed as a {@link ByteString}, but has a lifecycle that
37+
* requires being explicitly closed in order to free up resources.
38+
*
39+
* <p>Instances of this class should be used in a try-with-resources to ensure they are released.
40+
*
41+
* <pre>{@code
42+
* try (DisposableByteString disposableByteString = ...) {
43+
* System.out.println(disposableByteString.byteString().size());
44+
* }
45+
* }</pre>
46+
*
47+
* @see ReadProjectionConfigs#asFutureByteString()
48+
* @since 2.51.0 This new api is in preview and is subject to breaking changes.
49+
*/
50+
@BetaApi
3051
@InternalExtensionOnly
31-
interface DisposableByteString extends AutoCloseable, Closeable {
52+
public interface DisposableByteString extends AutoCloseable, Closeable {
3253

54+
/** Get the ByteString representation of the underlying resources */
3355
ByteString byteString();
3456

57+
/** Signal the underlying resources that they can be released. */
3558
@Override
3659
void close() throws IOException;
3760
}

google-cloud-storage/src/test/java/com/google/cloud/storage/ITObjectReadSessionFakeTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
import io.grpc.stub.StreamObserver;
7979
import java.io.ByteArrayOutputStream;
8080
import java.nio.ByteBuffer;
81-
import java.nio.channels.AsynchronousCloseException;
8281
import java.nio.channels.Channels;
8382
import java.nio.channels.ScatteringByteChannel;
8483
import java.nio.channels.WritableByteChannel;
@@ -1045,11 +1044,13 @@ public void onNext(BidiReadObjectRequest request) {
10451044
assertAll(
10461045
() -> assertThat(se2).isNotSameInstanceAs(se3),
10471046
() ->
1048-
assertThat(se2).hasCauseThat().isInstanceOf(AsynchronousCloseException.class),
1047+
assertThat(se2)
1048+
.hasCauseThat()
1049+
.isInstanceOf(AsyncSessionClosedException.class),
10491050
() ->
10501051
assertThat(se3)
10511052
.hasCauseThat()
1052-
.isInstanceOf(AsynchronousCloseException.class));
1053+
.isInstanceOf(AsyncSessionClosedException.class));
10531054
});
10541055
}
10551056
}

google-cloud-storage/src/test/java/com/google/cloud/storage/ITObjectReadSessionTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,23 @@ public void bytes()
101101
}
102102
}
103103

104+
@Test
105+
public void attemptingToGetFutureOutSizeSessionFails() throws Throwable {
106+
ObjectAndContent obj512KiB = objectsFixture.getObj512KiB();
107+
BlobId blobId = obj512KiB.getInfo().getBlobId();
108+
109+
ApiFuture<byte[]> future;
110+
try (BlobReadSession session = storage.blobReadSession(blobId).get(30, TimeUnit.SECONDS)) {
111+
future = session.readAs(ReadProjectionConfigs.asFutureBytes());
112+
}
113+
114+
ExecutionException ee =
115+
assertThrows(ExecutionException.class, () -> future.get(1, TimeUnit.SECONDS));
116+
117+
assertThat(ee).hasCauseThat().isInstanceOf(StorageException.class);
118+
assertThat(ee).hasCauseThat().hasCauseThat().isInstanceOf(AsyncSessionClosedException.class);
119+
}
120+
104121
@Test
105122
public void lotsOfBytes() throws Exception {
106123
ChecksummedTestContent testContent =

0 commit comments

Comments
 (0)