// Copyright 2017 JanusGraph Authors // // 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 org.janusgraph.diskstorage.util; import com.google.common.base.Preconditions; import org.janusgraph.core.JanusGraphException; import org.janusgraph.diskstorage.util.time.TimestampProvider; import org.janusgraph.diskstorage.PermanentBackendException; import org.janusgraph.diskstorage.BackendException; import org.janusgraph.diskstorage.TemporaryBackendException; import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.Random; import java.util.concurrent.Callable; /** * @author Matthias Broecheler (me@matthiasb.com) */ public class BackendOperation { private static final Logger log = LoggerFactory.getLogger(BackendOperation.class); private static final Random random = new Random(); private static final Duration BASE_REATTEMPT_TIME= Duration.ofMillis(50); private static final double PERTURBATION_PERCENTAGE = 0.2; private static final Duration pertubateTime(Duration duration) { Duration newDuration = duration.dividedBy((int)(2.0 / (1 + (random.nextDouble() * 2 - 1.0) * PERTURBATION_PERCENTAGE))); assert !duration.isZero() : duration; return newDuration; } public static final<V> V execute(Callable<V> exe, Duration totalWaitTime) throws JanusGraphException { try { return executeDirect(exe,totalWaitTime); } catch (BackendException e) { throw new JanusGraphException("Could not execute operation due to backend exception",e); } } public static final<V> V executeDirect(Callable<V> exe, Duration totalWaitTime) throws BackendException { Preconditions.checkArgument(!totalWaitTime.isZero(),"Need to specify a positive waitTime: %s",totalWaitTime); long maxTime = System.currentTimeMillis()+totalWaitTime.toMillis(); Duration waitTime = pertubateTime(BASE_REATTEMPT_TIME); BackendException lastException; while (true) { try { return exe.call(); } catch (final Throwable e) { //Find inner-most StorageException Throwable ex = e; BackendException storeEx = null; do { if (ex instanceof BackendException) storeEx = (BackendException)ex; } while ((ex=ex.getCause())!=null); if (storeEx!=null && storeEx instanceof TemporaryBackendException) { lastException = storeEx; } else if (e instanceof BackendException) { throw (BackendException)e; } else { throw new PermanentBackendException("Permanent exception while executing backend operation "+exe.toString(),e); } } //Wait and retry assert lastException!=null; if (System.currentTimeMillis()+waitTime.toMillis()<maxTime) { log.info("Temporary exception during backend operation ["+exe.toString()+"]. Attempting backoff retry.",lastException); try { Thread.sleep(waitTime.toMillis()); } catch (InterruptedException r) { // added thread interrupt signal to support traversal interruption Thread.currentThread().interrupt(); throw new PermanentBackendException("Interrupted while waiting to retry failed backend operation", r); } } else { break; } waitTime = pertubateTime(waitTime.multipliedBy(2)); } throw new TemporaryBackendException("Could not successfully complete backend operation due to repeated temporary exceptions after "+totalWaitTime,lastException); } // private static final double WAITTIME_PERTURBATION_PERCENTAGE = 0.5; // private static final double WAITTIME_PERTURBATION_PERCENTAGE_HALF = WAITTIME_PERTURBATION_PERCENTAGE/2; // // public static final<V> V execute(Callable<V> exe, int maxRetryAttempts, Duration waitBetweenRetries) throws JanusGraphException { // long retryWaittime = waitBetweenRetries.getLength(TimeUnit.MILLISECONDS); // Preconditions.checkArgument(maxRetryAttempts>0,"Retry attempts must be positive"); // Preconditions.checkArgument(retryWaittime>=0,"Retry wait time must be non-negative"); // int retryAttempts = 0; // StorageException lastException = null; // do { // try { // return exe.call(); // } catch (StorageException e) { // if (e instanceof TemporaryStorageException) { // lastException = e; // log.debug("Temporary exception during backend operation", e); // } else { // throw new JanusGraphException("Permanent exception during backend operation",e); //Its permanent // } // } catch (Throwable e) { // throw new JanusGraphException("Unexpected exception during backend operation",e); // } // //Wait and retry // retryAttempts++; // Preconditions.checkNotNull(lastException); // if (retryAttempts<maxRetryAttempts) { // long waitTime = Math.round(retryWaittime+((Math.random()*WAITTIME_PERTURBATION_PERCENTAGE-WAITTIME_PERTURBATION_PERCENTAGE_HALF)*retryWaittime)); // Preconditions.checkArgument(waitTime>=0,"Invalid wait time: %s",waitTime); // log.info("Temporary storage exception during backend operation [{}]. Attempting incremental retry",exe.toString(),lastException); // try { // Thread.sleep(waitTime); // } catch (InterruptedException r) { // throw new JanusGraphException("Interrupted while waiting to retry failed backend operation", r); // } // } // } while (retryAttempts<maxRetryAttempts); // throw new JanusGraphException("Could not successfully complete backend operation due to repeated temporary exceptions after "+maxRetryAttempts+" attempts",lastException); // } public static<R> R execute(Transactional<R> exe, TransactionalProvider provider, TimestampProvider times) throws BackendException { StoreTransaction txh = null; try { txh = provider.openTx(); if (!txh.getConfiguration().hasCommitTime()) txh.getConfiguration().setCommitTime(times.getTime()); return exe.call(txh); } catch (BackendException e) { if (txh!=null) txh.rollback(); txh=null; throw e; } finally { if (txh!=null) txh.commit(); } } public static<R> R execute(final Transactional<R> exe, final TransactionalProvider provider, final TimestampProvider times, Duration maxTime) throws JanusGraphException { return execute(new Callable<R>() { @Override public R call() throws Exception { return execute(exe,provider,times); } @Override public String toString() { return exe.toString(); } },maxTime); } public static interface Transactional<R> { public R call(StoreTransaction txh) throws BackendException; } public static interface TransactionalProvider { public StoreTransaction openTx() throws BackendException; public void close() throws BackendException; } }