com.google.walkaround.util.server.RetryHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.google.walkaround.util.server.RetryHelper.java

Source

/*
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * Licensed 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.google.walkaround.util.server;

import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;

import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Helps retry datastore transactions.
 *
 * @author ohler@google.com (Christian Ohler)
 */
public class RetryHelper {

    @SuppressWarnings("unused")
    private static final Logger log = Logger.getLogger(RetryHelper.class.getName());

    public static class PermanentFailure extends Exception {
        private static final long serialVersionUID = 593754888234966439L;

        public PermanentFailure() {
        }

        public PermanentFailure(String message) {
            super(message);
        }

        public PermanentFailure(Throwable cause) {
            super(cause);
        }

        public PermanentFailure(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public static class RetryableFailure extends Exception {
        private static final long serialVersionUID = 881520674168876801L;

        public RetryableFailure() {
        }

        public RetryableFailure(String message) {
            super(message);
        }

        public RetryableFailure(Throwable cause) {
            super(cause);
        }

        public RetryableFailure(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public interface Body<R> {
        R run() throws RetryableFailure, PermanentFailure;
    }

    public interface VoidBody {
        void run() throws RetryableFailure, PermanentFailure;
    }

    public interface RetryStrategy {
        /**
         * Returns how long to sleep before retrying after the given exception has
         * occurred.  Negative values are not permitted.  This should throw
         * PermanentFailure if no more retries should be attempted.
         *
         * @param numRetries how many times the job has already been retried
         *        (not counting the initial attempt)
         * @param millisSoFar how much total time has elapsed so far running this job
         * @param exception the exception that was thrown
         */
        long delayMillisBeforeRetry(int numRetries, long millisSoFar, RetryableFailure exception)
                throws PermanentFailure;
    }

    public static RetryStrategy backoffStrategy(final long startDelayMillis, final long maxDelayMillis,
            final long maxTotalTime) {
        return new RetryStrategy() {
            Random random = new Random();

            long randomLong(long limit) {
                return (long) (random.nextDouble() * limit);
            }

            @Override
            public long delayMillisBeforeRetry(int numRetries, long millisSoFar, RetryableFailure e)
                    throws PermanentFailure {
                long nextDelay = randomLong(Math.min(maxDelayMillis, startDelayMillis * (1 << numRetries)) + 1);
                if (millisSoFar + nextDelay < maxTotalTime) {
                    return nextDelay;
                } else {
                    throw new PermanentFailure("Retry timeout exceeded: millisSoFar=" + millisSoFar + ", nextDelay="
                            + nextDelay + ", maxTotalTime=" + maxTotalTime, e);
                }
            }
        };
    }

    public static final RetryHelper NO_RETRY = new RetryHelper(new RetryStrategy() {
        @Override
        public long delayMillisBeforeRetry(int numRetries, long millisSoFar, RetryableFailure exception)
                throws PermanentFailure {
            throw new PermanentFailure("Retryable failure with NO_RETRY strategy", exception);
        }
    });

    private final RetryStrategy retryStrategy;

    public RetryHelper(RetryStrategy retryStrategy) {
        Preconditions.checkNotNull(retryStrategy, "Null retryStrategy");
        this.retryStrategy = retryStrategy;
    }

    public RetryHelper() {
        this(backoffStrategy(5, 200, 15000));
    }

    private <R> R runBodyOnce(Body<R> b) throws RetryableFailure, PermanentFailure {
        Stopwatch stopwatch = new Stopwatch().start();
        log.info("Running body " + b);
        boolean normalExit = false;
        try {
            R result = b.run();
            normalExit = true;
            return result;
        } finally {
            long duration = stopwatch.elapsedMillis();
            log.info("Body exited " + (normalExit ? "normally" : "abnormally") + ", run time: " + duration + "ms");
        }
    }

    public <R> R run(Body<R> b) throws PermanentFailure {
        Stopwatch stopwatch = new Stopwatch().start();
        for (int retries = 0; true; retries++) {
            try {
                return runBodyOnce(b);
            } catch (RetryableFailure e) {
                long elapsedMillis = stopwatch.elapsedMillis();
                log.log(Level.WARNING, "Problem on retry " + retries + ", millis elapsed so far: " + elapsedMillis,
                        e);
                long delayMillis = retryStrategy.delayMillisBeforeRetry(retries, elapsedMillis, e);
                if (delayMillis < 0) {
                    log.warning("Negative delay: " + delayMillis);
                    delayMillis = 100;
                }
                log.info("Sleeping for " + delayMillis + " millis");
                try {
                    Thread.sleep(delayMillis);
                } catch (InterruptedException e2) {
                    Thread.currentThread().interrupt();
                    throw new PermanentFailure("Interrupted while waiting to retry; " + retries + " tries total, "
                            + stopwatch.elapsedMillis() + " millis elapsed", e2);
                }
            }
        }
    }

    public void run(final VoidBody b) throws PermanentFailure {
        run(new Body<Void>() {
            @Override
            public Void run() throws PermanentFailure, RetryableFailure {
                b.run();
                return null;
            }

            @Override
            public String toString() {
                return "VoidBodyWrapper(" + b + ")";
            }
        });
    }

}