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

com.aliyun.odps.tunnel.io.TunnelRetryHandler Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.aliyun.odps.tunnel.io;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.IntConsumer;

import com.aliyun.odps.commons.transport.HttpStatus;
import com.aliyun.odps.rest.RestClient;
import com.aliyun.odps.tunnel.Configuration;
import com.aliyun.odps.tunnel.TunnelException;

/**
 * retry strategy for table tunnel
 * 429: Server-side current limiting strategy, infinite exponential retry (max 64s)
 * 4xx: Client-side error, default not retry
 * 502, 504: should trigger reload session and retry exponential (1s - 64s)
 * 5xx: Server-side error,  retry exponential (1s - 64s)
 * 308: only upsert session flush use, update slot map and infinite exponential retry (max 64s)
 */
public class TunnelRetryHandler {

    private RetryPolicy defaultRetryPolicy;
    private RestClient.RetryLogger retryLogger;

    public TunnelRetryHandler() {
        this(NoRetryPolicy.INSTANCE, null);
    }

    public TunnelRetryHandler(Configuration configuration) {
        this(configuration.getRetryPolicy(), configuration.getRetryLogger());
    }

    public TunnelRetryHandler(RetryPolicy defaultRetryPolicy, RestClient.RetryLogger retryLogger) {
        if (defaultRetryPolicy == null) {
            this.defaultRetryPolicy = NoRetryPolicy.INSTANCE;
        } else {
            this.defaultRetryPolicy = defaultRetryPolicy;
        }
        this.retryLogger = retryLogger;
    }

    public boolean onFailure(Exception e, int attempt) {
        RetryPolicy policy;
        if (e instanceof TunnelException) {
            policy = getRetryPolicy(((TunnelException) e).getStatus());
        } else {
            policy = defaultRetryPolicy;
        }
        if (policy.shouldRetry(e, attempt)) {
            if (retryLogger != null) {
                retryLogger.onRetryLog(e, attempt, policy.getRetryWaitTime(attempt));
            }
            try {
                policy.waitForNextRetry(attempt);
            } catch (InterruptedException i) {
                Thread.currentThread().interrupt();
            }
            return true;
        } else {
            return false;
        }
    }

    private RetryPolicy getRetryPolicy(Integer errorCode) {
        if (errorCode == null) {
            return defaultRetryPolicy;
        }
        if (errorCode == HttpStatus.SLOT_REASSIGNMENT || errorCode == HttpStatus.FLOW_EXCEEDED) {
            return InfiniteExponentialWaitRetryPolicy.INSTANCE;
        } else if (HttpStatus.isServerError(errorCode)) {
            return ExponentialWaitRetryPolicy.INSTANCE;
        } else {
            return defaultRetryPolicy;
        }
    }

    public  T executeWithRetry(Callable action) throws Exception {
        return executeWithRetry(action, null);
    }

    public  T executeWithRetry(Callable action, IntConsumer errorCodeHandler)
        throws Exception {
        int attempt = 1;
        while (true) {
            try {
                return action.call();
            } catch (Exception e) {
                RetryPolicy policy;
                if (e instanceof TunnelException) {
                    policy = getRetryPolicy(((TunnelException) e).getStatus());
                } else {
                    policy = defaultRetryPolicy;
                }
                if (!policy.shouldRetry(e, attempt)) {
                    throw e;
                }
                if (retryLogger != null) {
                    retryLogger.onRetryLog(e, attempt, policy.getRetryWaitTime(attempt));
                }
                try {
                    policy.waitForNextRetry(attempt);
                    if (errorCodeHandler != null && e instanceof TunnelException) {
                        errorCodeHandler.accept(((TunnelException) e).getStatus());
                    }
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw e;
                }
                attempt++;
            }
        }
    }

    public interface RetryPolicy {

        boolean shouldRetry(Exception e, int attempt);

        long getRetryWaitTime(int attempt);

        default void waitForNextRetry(int attempt) throws InterruptedException {
            TimeUnit.MILLISECONDS.sleep(getRetryWaitTime(attempt));
        }
    }

    static class NoRetryPolicy implements RetryPolicy {

        static final NoRetryPolicy INSTANCE = new NoRetryPolicy();

        @Override
        public boolean shouldRetry(Exception e, int attempt) {
            return false;
        }

        @Override
        public long getRetryWaitTime(int attempt) {
            return 0;
        }

        @Override
        public void waitForNextRetry(int attempt) {
            // No wait needed
        }
    }

    static class InfiniteExponentialWaitRetryPolicy implements RetryPolicy {

        static final InfiniteExponentialWaitRetryPolicy
            INSTANCE =
            new InfiniteExponentialWaitRetryPolicy();

        @Override
        public boolean shouldRetry(Exception e, int attempt) {
            return true;
        }

        @Override
        public long getRetryWaitTime(int attempt) {
            if (attempt < 7) {
                int waitTime = (int) Math.pow(2, attempt - 1);
                return TimeUnit.SECONDS.toMillis(waitTime);
            } else {
                return TimeUnit.SECONDS.toMillis(64);
            }
        }
    }

    static class ExponentialWaitRetryPolicy implements RetryPolicy {

        static final ExponentialWaitRetryPolicy INSTANCE = new ExponentialWaitRetryPolicy();

        @Override
        public boolean shouldRetry(Exception e, int attempt) {
            return attempt <= 7; // max retry 7 t 7 times, 1s, 2s, 4s, 8s, 16s, 32s, 64s
        }

        @Override
        public long getRetryWaitTime(int attempt) {
            int waitTime = (int) Math.pow(2, attempt - 1);
            return TimeUnit.SECONDS.toMillis(waitTime);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy