Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,25 @@ abstract class BaseStorageReadChannel<T> implements StorageReadChannel {
private BufferHandle bufferHandle;
private LazyReadChannel<?, T> lazyReadChannel;
protected final ReentrantLock lock;
@Nullable private final StorageMetricsContext metricsContext;
@Nullable private final StorageClientMetrics storageClientMetrics;
@Nullable private Throwable lastOperationError;

protected BaseStorageReadChannel(Decoder<T, BlobInfo> objectDecoder) {
this(objectDecoder, null, null);
}

protected BaseStorageReadChannel(
Decoder<T, BlobInfo> objectDecoder,
@Nullable StorageMetricsContext metricsContext,
@Nullable StorageClientMetrics storageClientMetrics) {
this.objectDecoder = objectDecoder;
this.result = SettableApiFuture.create();
this.open = true;
this.byteRangeSpec = ByteRangeSpec.nullRange();
this.lock = new ReentrantLock();
this.metricsContext = metricsContext;
this.storageClientMetrics = storageClientMetrics;
}

@Override
Expand All @@ -76,14 +88,27 @@ public final boolean isOpen() {
@Override
public final void close() {
lock.lock();
Throwable error = null;
try {
open = false;
if (internalGetLazyChannel().isOpen()) {
ReadableByteChannel channel = internalGetLazyChannel().getChannel();
StorageException.wrapIOException(channel::close);
if (open) {
open = false;
if (internalGetLazyChannel().isOpen()) {
ReadableByteChannel channel = internalGetLazyChannel().getChannel();
StorageException.wrapIOException(channel::close);
}
}
} catch (Throwable t) {
error = t;
throw t;
} finally {
lock.unlock();
try {
if (metricsContext != null && storageClientMetrics != null) {
metricsContext.recordOperation(
storageClientMetrics, error != null ? error : lastOperationError);
}
} finally {
lock.unlock();
}
}
}

Expand Down Expand Up @@ -140,12 +165,16 @@ public final int read(ByteBuffer dst) throws IOException {
// be removed in the next major version.
return -1;
} else {
lastOperationError = e;
throw new IOException(e);
}
} catch (IOException e) {
lastOperationError = e;
throw e;
} catch (Exception e) {
throw new IOException(StorageException.coalesce(e));
Throwable coalesced = StorageException.coalesce(e);
lastOperationError = coalesced;
throw new IOException(coalesced);
}
} finally {
lock.unlock();
Expand Down Expand Up @@ -177,6 +206,10 @@ protected final T getResolvedObject() {
}
}

protected final @Nullable StorageMetricsContext getMetricsContext() {
return metricsContext;
}

protected abstract LazyReadChannel<?, T> newLazyReadChannel();

private void maybeResetChannel(boolean freeBuffer) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ abstract class BaseStorageWriteChannel<T> implements StorageWriteChannel {
private int chunkSize;
private LazyWriteChannel<T> lazyWriteChannel;
private BufferHandle bufferHandle;
@Nullable private final StorageMetricsContext metricsContext;
@Nullable private final StorageClientMetrics storageClientMetrics;
@Nullable private Throwable lastOperationError;

/**
* This is tracked for compatibility with BlobWriteChannel, such that simply creating a writer
Expand All @@ -55,12 +58,21 @@ abstract class BaseStorageWriteChannel<T> implements StorageWriteChannel {
protected boolean writeCalledAtLeastOnce;

protected BaseStorageWriteChannel(Decoder<T, BlobInfo> objectDecoder) {
this(objectDecoder, null, null);
}

protected BaseStorageWriteChannel(
Decoder<T, BlobInfo> objectDecoder,
@Nullable StorageMetricsContext metricsContext,
@Nullable StorageClientMetrics storageClientMetrics) {
this.objectDecoder = objectDecoder;
this.result = SettableApiFuture.create();
this.lock = new ReentrantLock();
this.open = true;
this.chunkSize = _16MiB;
this.writeCalledAtLeastOnce = false;
this.metricsContext = metricsContext;
this.storageClientMetrics = storageClientMetrics;
}

@Override
Expand Down Expand Up @@ -90,16 +102,27 @@ public final boolean isOpen() {
@Override
public final void close() throws IOException {
lock.lock();
Throwable error = null;
try {
if (open && !writeCalledAtLeastOnce) {
this.write(ByteBuffer.allocate(0));
}
if (internalGetLazyChannel().isOpen()) {
StorageException.wrapIOException(internalGetLazyChannel().getChannel()::close);
}
} catch (Throwable t) {
error = t;
throw t;
} finally {
open = false;
lock.unlock();
try {
if (metricsContext != null && storageClientMetrics != null) {
metricsContext.recordOperation(
storageClientMetrics, error != null ? error : lastOperationError);
}
} finally {
lock.unlock();
}
}
}

Expand All @@ -119,11 +142,15 @@ public final int write(ByteBuffer src) throws IOException {
int write = tmp.write(src);
return write;
} catch (StorageException e) {
lastOperationError = e;
throw new IOException(e);
} catch (IOException e) {
lastOperationError = e;
throw e;
} catch (Exception e) {
throw new IOException(StorageException.coalesce(e));
Throwable coalesced = StorageException.coalesce(e);
lastOperationError = coalesced;
throw new IOException(coalesced);
}
} finally {
lock.unlock();
Expand Down Expand Up @@ -167,6 +194,10 @@ protected final void setOpen(boolean isOpen) {
this.open = isOpen;
}

protected final @Nullable StorageMetricsContext getMetricsContext() {
return metricsContext;
}

protected abstract LazyWriteChannel<T> newLazyWriteChannel();

private LazyWriteChannel<T> internalGetLazyChannel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;

final class BlobReadChannelV2 extends BaseStorageReadChannel<StorageObject> {

Expand All @@ -40,7 +41,20 @@ final class BlobReadChannelV2 extends BaseStorageReadChannel<StorageObject> {
StorageObject storageObject,
Map<StorageRpc.Option, ?> opts,
BlobReadChannelContext blobReadChannelContext) {
super(Conversions.json().blobInfo());
this(storageObject, opts, blobReadChannelContext, null);
}

BlobReadChannelV2(
StorageObject storageObject,
Map<StorageRpc.Option, ?> opts,
BlobReadChannelContext blobReadChannelContext,
@Nullable StorageMetricsContext metricsContext) {
super(
Conversions.json().blobInfo(),
metricsContext,
blobReadChannelContext.getStorageOptions() != null
? blobReadChannelContext.getStorageOptions().getStorageClientMetrics()
: null);
this.storageObject = storageObject;
this.opts = opts;
this.blobReadChannelContext = blobReadChannelContext;
Expand Down Expand Up @@ -69,7 +83,11 @@ protected LazyReadChannel<?, StorageObject> newLazyReadChannel() {
ReadableByteChannelSessionBuilder b =
ResumableMedia.http()
.read()
.byteChannel(blobReadChannelContext)
.byteChannel(
blobReadChannelContext.withRetrier(
blobReadChannelContext
.getRetrier()
.withMetricsContext(getMetricsContext())))
.setAutoGzipDecompression(autoGzipDecompression);
BufferHandle bufferHandle = getBufferHandle();
// because we're erasing the specific type of channel, we need to declare it here.
Expand Down Expand Up @@ -109,7 +127,11 @@ private BlobReadChannelV2State(
public ReadChannel restore() {
BlobReadChannelV2 channel =
new BlobReadChannelV2(
request.getObject(), request.getOptions(), BlobReadChannelContext.from(options));
request.getObject(),
request.getOptions(),
BlobReadChannelContext.from(options),
StorageClientMetrics.createMultiStepContext(
options.getStorageClientMetrics(), "ReadObject"));
channel.setByteRangeSpec(request.getByteRangeSpec());
if (chunkSize != null) {
channel.setChunkSize(chunkSize);
Expand Down Expand Up @@ -186,6 +208,11 @@ public Retrier getRetrier() {
return retrier;
}

BlobReadChannelContext withRetrier(Retrier retrier) {
return new BlobReadChannelContext(
storageOptions, retryAlgorithmManager, httpClientContext, apiaryClient, retrier);
}

/**
* This method is pretty unsafe, but so is all of the Capture/Restore API, and it leaks its
* sludge all over everything. In general, prefer {@link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,27 @@
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;

final class BlobWriteChannelV2 extends BaseStorageWriteChannel<StorageObject> {

private final BlobReadChannelContext blobChannelContext;
private final JsonResumableWrite start;

BlobWriteChannelV2(BlobReadChannelContext blobChannelContext, JsonResumableWrite start) {
super(Conversions.json().blobInfo());
this(blobChannelContext, start, null);
}

BlobWriteChannelV2(
BlobReadChannelContext blobChannelContext,
JsonResumableWrite start,
@Nullable StorageMetricsContext metricsContext) {
super(
Conversions.json().blobInfo(),
metricsContext,
blobChannelContext.getStorageOptions() != null
? blobChannelContext.getStorageOptions().getStorageClientMetrics()
: null);
this.start = start;
this.blobChannelContext = blobChannelContext;
}
Expand Down Expand Up @@ -78,7 +91,9 @@ protected LazyWriteChannel<StorageObject> newLazyWriteChannel() {
.withRetryConfig(
blobChannelContext
.getRetrier()
.withAlg(blobChannelContext.getRetryAlgorithmManager().idempotent()))
.withAlg(
blobChannelContext.getRetryAlgorithmManager().idempotent(),
getMetricsContext()))
.setHasher(start.getHasher())
.buffered(getBufferHandle())
.setStartAsync(ApiFutures.immediateFuture(start))
Expand Down Expand Up @@ -120,7 +135,11 @@ public WriteChannel restore() {
resumableWrite = resumableWrite.withBeginOffset(position);
}
BlobWriteChannelV2 channel =
new BlobWriteChannelV2(BlobReadChannelContext.from(options), resumableWrite);
new BlobWriteChannelV2(
BlobReadChannelContext.from(options),
resumableWrite,
StorageClientMetrics.createMultiStepContext(
options.getStorageClientMetrics(), "WriteObject"));
if (chunkSize != null) {
channel.setChunkSize(chunkSize);
}
Expand Down
Loading
Loading