com.yandex.ydb.core.grpc.GrpcOperationTray Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ydb-sdk-jdbc-uberjar Show documentation
Show all versions of ydb-sdk-jdbc-uberjar Show documentation
JDBC client implementation over Table client, single jar
The newest version!
package com.yandex.ydb.core.grpc;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.annotation.concurrent.GuardedBy;
import com.google.protobuf.Message;
import com.yandex.ydb.OperationProtos;
import com.yandex.ydb.OperationProtos.GetOperationRequest;
import com.yandex.ydb.OperationProtos.GetOperationResponse;
import com.yandex.ydb.core.Operations;
import com.yandex.ydb.core.Result;
import com.yandex.ydb.core.Status;
import com.yandex.ydb.core.rpc.OperationTray;
import com.yandex.ydb.operation.v1.OperationServiceGrpc;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.DefaultThreadFactory;
/**
* @author Sergey Polovko
*/
final class GrpcOperationTray implements OperationTray {
private static final long INITIAL_DELAY_MILLIS = 10; // The delay before first operations service call, ms
private static final long MAX_DELAY_MILLIS = 10_000; // The max delay between getOperation calls for one operation, ms
// From https://netty.io/4.1/api/io/netty/util/HashedWheelTimer.html
// HashedWheelTimer creates a new thread whenever it is instantiated and started.
// Therefore, you should make sure to create only one instance and share it across
// your application. One of the common mistakes, that makes your application unresponsive,
// is to create a new instance for every connection.
private static final Timer WHEEL_TIMER = new HashedWheelTimer(
new DefaultThreadFactory("SharedOperationTrayTimer"),
INITIAL_DELAY_MILLIS,
TimeUnit.MILLISECONDS
);
private final GrpcTransport transport;
@GuardedBy("this")
private final Set timeouts = new HashSet<>();
@GuardedBy("this")
private CancellationException cancelEx = null;
GrpcOperationTray(GrpcTransport transport) {
this.transport = transport;
}
@Override
public CompletableFuture waitStatus(OperationProtos.Operation operation, GrpcRequestSettings settings) {
CompletableFuture promise = new CompletableFuture<>();
if (operation.getReady()) {
try {
promise.complete(Operations.status(operation));
} catch (Throwable t) {
promise.completeExceptionally(t);
}
} else {
new WaitStatusTask(operation.getId(), promise, settings)
.scheduleNext(WHEEL_TIMER);
}
return promise;
}
@Override
public CompletableFuture> waitResult(
OperationProtos.Operation operation,
Class resultClass,
Function mapper,
GrpcRequestSettings settings)
{
CompletableFuture> promise = new CompletableFuture<>();
if (operation.getReady()) {
try {
Status status = Operations.status(operation);
Double consumedRu = null;
if (operation.hasCostInfo()) {
consumedRu = operation.getCostInfo().getConsumedUnits();
}
if (status.isSuccess()) {
M resultMessage = Operations.unpackResult(operation, resultClass);
promise.complete(Result.success(mapper.apply(resultMessage), consumedRu, status.getIssues()));
} else {
promise.complete(Result.fail(status));
}
} catch (Throwable t) {
promise.completeExceptionally(t);
}
} else {
new WaitResultTask<>(operation.getId(), promise, resultClass, mapper, settings)
.scheduleNext(WHEEL_TIMER);
}
return promise;
}
private CompletableFuture> callGetOperation(GetOperationRequest request,
GrpcRequestSettings settings) {
return transport.unaryCall(OperationServiceGrpc.getGetOperationMethod(), request, settings);
}
@Override
public void close() {
synchronized (this) {
this.cancelEx = new CancellationException();
for (Timeout timeout : timeouts) {
TimerTask task = timeout.task();
if (task instanceof BaseTask) {
((BaseTask) task).promise.completeExceptionally(cancelEx);
}
}
}
}
/**
* Base waiting task.
*/
public abstract class BaseTask implements TimerTask {
private final GetOperationRequest request;
private final CompletableFuture promise;
private final GrpcRequestSettings settings;
private long delayMillis = INITIAL_DELAY_MILLIS / 2;
BaseTask(String id, CompletableFuture promise, GrpcRequestSettings settings) {
this.request = GetOperationRequest.newBuilder().setId(id).build();
this.promise = promise;
this.settings = settings;
}
protected abstract T mapFailedRpc(Result> result);
protected abstract T mapReadyOperation(OperationProtos.Operation operation);
@Override
public void run(Timeout timeout) {
synchronized (GrpcOperationTray.this) {
// Check if operation tray is stopped and all tasks already completed with exception
if (cancelEx != null) {
return;
}
timeouts.remove(timeout);
}
if (promise.isCancelled()) {
return;
}
callGetOperation(request, settings)
.whenComplete((response, throwable) -> {
if (throwable != null) {
promise.completeExceptionally(throwable);
return;
}
if (!response.isSuccess()) {
promise.complete(mapFailedRpc(response));
return;
}
OperationProtos.Operation operation = response.expect("getOperation()").getOperation();
if (operation.getReady()) {
try {
promise.complete(mapReadyOperation(operation));
} catch (Throwable t) {
promise.completeExceptionally(t);
}
} else {
scheduleNext(timeout.timer());
}
});
}
void scheduleNext(Timer timer) {
synchronized (GrpcOperationTray.this) {
// Check if operation tray is stopped and all tasks already completed with exception
if (cancelEx != null) {
return;
}
// exponentially growing delay
delayMillis = Math.min(delayMillis * 2, MAX_DELAY_MILLIS);
timeouts.add(timer.newTimeout(this, delayMillis, TimeUnit.MILLISECONDS));
}
}
}
/**
* Task for waiting status.
*/
public final class WaitStatusTask extends BaseTask {
WaitStatusTask(String id, CompletableFuture promise, GrpcRequestSettings settings) {
super(id, promise, settings);
}
@Override
protected Status mapFailedRpc(Result> result) {
return result.toStatus();
}
@Override
protected Status mapReadyOperation(OperationProtos.Operation operation) {
return Operations.status(operation);
}
}
/**
* Task for waiting result.
*/
public final class WaitResultTask extends BaseTask> {
private final Class resultClass;
private final Function mapper;
WaitResultTask(
String id,
CompletableFuture> promise,
Class resultClass,
Function mapper,
GrpcRequestSettings settings)
{
super(id, promise, settings);
this.resultClass = resultClass;
this.mapper = mapper;
}
@Override
protected Result mapFailedRpc(Result> result) {
return result.cast();
}
@Override
protected Result mapReadyOperation(OperationProtos.Operation operation) {
Status status = Operations.status(operation);
if (!status.isSuccess()) {
return Result.fail(status);
}
Double consumedRu = null;
if (operation.hasCostInfo()) {
consumedRu = operation.getCostInfo().getConsumedUnits();
}
M resultMessage = Operations.unpackResult(operation, resultClass);
return Result.success(mapper.apply(resultMessage), consumedRu, status.getIssues());
}
}
}