001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.plugins.transfer.features; 019 020import java.io.File; 021import java.util.Map; 022import java.util.logging.Level; 023import java.util.logging.Logger; 024 025import org.syncany.config.Config; 026import org.syncany.plugins.transfer.StorageException; 027import org.syncany.plugins.transfer.StorageMoveException; 028import org.syncany.plugins.transfer.StorageTestResult; 029import org.syncany.plugins.transfer.TransferManager; 030import org.syncany.plugins.transfer.files.RemoteFile; 031 032/** 033 * The retriable transfer manager implements a simple try-sleep-retry mechanism 034 * for regular {@link org.syncany.plugins.transfer.TransferManager}s. 035 * 036 * <p>It encapsules a single transfer manager and proxies all of its methods. If a 037 * method fails with a {@link org.syncany.plugins.transfer.StorageException}, the 038 * method is retried N times before the exception is actually thrown to the caller. 039 * Between retries, the method waits M seconds. 040 * 041 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 042 */ 043public class RetriableFeatureTransferManager implements FeatureTransferManager { 044 private static final Logger logger = Logger.getLogger(RetriableFeatureTransferManager.class.getSimpleName()); 045 046 private interface RetriableMethod { 047 public Object execute() throws StorageException; 048 } 049 050 private TransferManager underlyingTransferManager; 051 private int retryMaxCount; 052 private int retrySleepMillis; 053 054 private int tryCount; 055 056 public RetriableFeatureTransferManager(TransferManager originalTransferManager, TransferManager underlyingTransferManager, Config config, Retriable retriableAnnotation) { 057 this.underlyingTransferManager = underlyingTransferManager; 058 this.retryMaxCount = retriableAnnotation.numberRetries(); 059 this.retrySleepMillis = retriableAnnotation.sleepInterval(); 060 061 this.tryCount = 0; 062 } 063 064 @Override 065 public void connect() throws StorageException { 066 retryMethod(new RetriableMethod() { 067 @Override 068 public Object execute() throws StorageException { 069 underlyingTransferManager.connect(); 070 return null; 071 } 072 }); 073 } 074 075 @Override 076 public void disconnect() throws StorageException { 077 retryMethod(new RetriableMethod() { 078 @Override 079 public Object execute() throws StorageException { 080 underlyingTransferManager.disconnect(); 081 return null; 082 } 083 }); 084 } 085 086 @Override 087 public void init(final boolean createIfRequired) throws StorageException { 088 retryMethod(new RetriableMethod() { 089 @Override 090 public Object execute() throws StorageException { 091 underlyingTransferManager.init(createIfRequired); 092 return null; 093 } 094 }); 095 } 096 097 @Override 098 public void download(final RemoteFile remoteFile, final File localFile) throws StorageException { 099 retryMethod(new RetriableMethod() { 100 @Override 101 public Object execute() throws StorageException { 102 underlyingTransferManager.download(remoteFile, localFile); 103 return null; 104 } 105 }); 106 } 107 108 @Override 109 public void move(final RemoteFile sourceFile, final RemoteFile targetFile) throws StorageException { 110 retryMethod(new RetriableMethod() { 111 @Override 112 public Object execute() throws StorageException { 113 underlyingTransferManager.move(sourceFile, targetFile); 114 return null; 115 } 116 }); 117 } 118 119 @Override 120 public void upload(final File localFile, final RemoteFile remoteFile) throws StorageException { 121 retryMethod(new RetriableMethod() { 122 @Override 123 public Object execute() throws StorageException { 124 underlyingTransferManager.upload(localFile, remoteFile); 125 return null; 126 } 127 }); 128 } 129 130 @Override 131 public boolean delete(final RemoteFile remoteFile) throws StorageException { 132 return (Boolean) retryMethod(new RetriableMethod() { 133 @Override 134 public Object execute() throws StorageException { 135 return underlyingTransferManager.delete(remoteFile); 136 } 137 }); 138 } 139 140 @Override 141 @SuppressWarnings("unchecked") 142 public <T extends RemoteFile> Map<String, T> list(final Class<T> remoteFileClass) throws StorageException { 143 return (Map<String, T>) retryMethod(new RetriableMethod() { 144 @Override 145 public Object execute() throws StorageException { 146 return underlyingTransferManager.list(remoteFileClass); 147 } 148 }); 149 } 150 151 @Override 152 public String getRemoteFilePath(Class<? extends RemoteFile> remoteFileClass) { 153 return underlyingTransferManager.getRemoteFilePath(remoteFileClass); 154 } 155 156 @Override 157 public StorageTestResult test(boolean testCreateTarget) { 158 return underlyingTransferManager.test(testCreateTarget); 159 } 160 161 @Override 162 public boolean testTargetExists() throws StorageException { 163 return (Boolean) retryMethod(new RetriableMethod() { 164 @Override 165 public Object execute() throws StorageException { 166 return underlyingTransferManager.testTargetExists(); 167 } 168 }); 169 } 170 171 @Override 172 public boolean testTargetCanWrite() throws StorageException { 173 return (Boolean) retryMethod(new RetriableMethod() { 174 @Override 175 public Object execute() throws StorageException { 176 return underlyingTransferManager.testTargetCanWrite(); 177 } 178 }); 179 } 180 181 @Override 182 public boolean testTargetCanCreate() throws StorageException { 183 return (Boolean) retryMethod(new RetriableMethod() { 184 @Override 185 public Object execute() throws StorageException { 186 return underlyingTransferManager.testTargetCanCreate(); 187 } 188 }); 189 } 190 191 @Override 192 public boolean testRepoFileExists() throws StorageException { 193 return (Boolean) retryMethod(new RetriableMethod() { 194 @Override 195 public Object execute() throws StorageException { 196 return underlyingTransferManager.testRepoFileExists(); 197 } 198 }); 199 } 200 201 private Object retryMethod(RetriableMethod retryableMethod) throws StorageException { 202 tryCount = 0; 203 204 while (true) { 205 try { 206 if (tryCount > 0) { 207 logger.log(Level.WARNING, "Retrying method: " + tryCount + "/" + retryMaxCount + " ..."); 208 } 209 210 Object result = retryableMethod.execute(); 211 212 tryCount = 0; 213 return result; 214 } 215 catch (StorageMoveException e) { 216 // StorageFileNotFoundException used to be caught here. It no longer is, 217 // since the transaction concept can cause some very ephemeral discrepancies. 218 // These can be caught by simply trying again. 219 // The reason this exists is a fuzzy stress test (#433) 220 logger.log(Level.INFO, "StorageException caused by missing file, not the connection. Not retrying."); 221 throw e; 222 } 223 catch (StorageException e) { 224 tryCount++; 225 226 if (tryCount >= retryMaxCount) { 227 logger.log(Level.WARNING, "Transfer method failed. No retries left. Throwing exception.", e); 228 throw e; 229 } 230 else { 231 logger.log(Level.WARNING, "Transfer method failed. " + tryCount + "/" + retryMaxCount + " retries. Sleeping " 232 + retrySleepMillis 233 + "ms ...", e); 234 235 try { 236 Thread.sleep(retrySleepMillis); 237 } 238 catch (Exception e1) { 239 throw new StorageException(e1); 240 } 241 } 242 } 243 } 244 } 245}