/* * Syncany, www.syncany.org * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.syncany.plugins.transfer.features; import java.io.File; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.syncany.config.Config; import org.syncany.plugins.transfer.StorageException; import org.syncany.plugins.transfer.StorageMoveException; import org.syncany.plugins.transfer.StorageTestResult; import org.syncany.plugins.transfer.TransferManager; import org.syncany.plugins.transfer.files.RemoteFile; /** * The retriable transfer manager implements a simple try-sleep-retry mechanism * for regular {@link org.syncany.plugins.transfer.TransferManager}s. * * <p>It encapsules a single transfer manager and proxies all of its methods. If a * method fails with a {@link org.syncany.plugins.transfer.StorageException}, the * method is retried N times before the exception is actually thrown to the caller. * Between retries, the method waits M seconds. * * @author Philipp C. Heckel <philipp.heckel@gmail.com> */ public class RetriableFeatureTransferManager implements FeatureTransferManager { private static final Logger logger = Logger.getLogger(RetriableFeatureTransferManager.class.getSimpleName()); private interface RetriableMethod { public Object execute() throws StorageException; } private TransferManager underlyingTransferManager; private int retryMaxCount; private int retrySleepMillis; private int tryCount; public RetriableFeatureTransferManager(TransferManager originalTransferManager, TransferManager underlyingTransferManager, Config config, Retriable retriableAnnotation) { this.underlyingTransferManager = underlyingTransferManager; this.retryMaxCount = retriableAnnotation.numberRetries(); this.retrySleepMillis = retriableAnnotation.sleepInterval(); this.tryCount = 0; } @Override public void connect() throws StorageException { retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { underlyingTransferManager.connect(); return null; } }); } @Override public void disconnect() throws StorageException { retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { underlyingTransferManager.disconnect(); return null; } }); } @Override public void init(final boolean createIfRequired) throws StorageException { retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { underlyingTransferManager.init(createIfRequired); return null; } }); } @Override public void download(final RemoteFile remoteFile, final File localFile) throws StorageException { retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { underlyingTransferManager.download(remoteFile, localFile); return null; } }); } @Override public void move(final RemoteFile sourceFile, final RemoteFile targetFile) throws StorageException { retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { underlyingTransferManager.move(sourceFile, targetFile); return null; } }); } @Override public void upload(final File localFile, final RemoteFile remoteFile) throws StorageException { retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { underlyingTransferManager.upload(localFile, remoteFile); return null; } }); } @Override public boolean delete(final RemoteFile remoteFile) throws StorageException { return (Boolean) retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { return underlyingTransferManager.delete(remoteFile); } }); } @Override @SuppressWarnings("unchecked") public <T extends RemoteFile> Map<String, T> list(final Class<T> remoteFileClass) throws StorageException { return (Map<String, T>) retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { return underlyingTransferManager.list(remoteFileClass); } }); } @Override public String getRemoteFilePath(Class<? extends RemoteFile> remoteFileClass) { return underlyingTransferManager.getRemoteFilePath(remoteFileClass); } @Override public StorageTestResult test(boolean testCreateTarget) { return underlyingTransferManager.test(testCreateTarget); } @Override public boolean testTargetExists() throws StorageException { return (Boolean) retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { return underlyingTransferManager.testTargetExists(); } }); } @Override public boolean testTargetCanWrite() throws StorageException { return (Boolean) retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { return underlyingTransferManager.testTargetCanWrite(); } }); } @Override public boolean testTargetCanCreate() throws StorageException { return (Boolean) retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { return underlyingTransferManager.testTargetCanCreate(); } }); } @Override public boolean testRepoFileExists() throws StorageException { return (Boolean) retryMethod(new RetriableMethod() { @Override public Object execute() throws StorageException { return underlyingTransferManager.testRepoFileExists(); } }); } private Object retryMethod(RetriableMethod retryableMethod) throws StorageException { tryCount = 0; while (true) { try { if (tryCount > 0) { logger.log(Level.WARNING, "Retrying method: " + tryCount + "/" + retryMaxCount + " ..."); } Object result = retryableMethod.execute(); tryCount = 0; return result; } catch (StorageMoveException e) { // StorageFileNotFoundException used to be caught here. It no longer is, // since the transaction concept can cause some very ephemeral discrepancies. // These can be caught by simply trying again. // The reason this exists is a fuzzy stress test (#433) logger.log(Level.INFO, "StorageException caused by missing file, not the connection. Not retrying."); throw e; } catch (StorageException e) { tryCount++; if (tryCount >= retryMaxCount) { logger.log(Level.WARNING, "Transfer method failed. No retries left. Throwing exception.", e); throw e; } else { logger.log(Level.WARNING, "Transfer method failed. " + tryCount + "/" + retryMaxCount + " retries. Sleeping " + retrySleepMillis + "ms ...", e); try { Thread.sleep(retrySleepMillis); } catch (Exception e1) { throw new StorageException(e1); } } } } } }