All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yandex.ydb.core.grpc.GrpcOperationTray Maven / Gradle / Ivy

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());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy