* Spatial partition * Retry with exponential backoff * retry exponential backoff * branch error
This commit is contained in:
		
				
					committed by
					
						 Ilkka Seppälä
						Ilkka Seppälä
					
				
			
			
				
	
			
			
			
						parent
						
							a6749cb63e
						
					
				
				
					commit
					10cb191533
				
			| @@ -71,6 +71,7 @@ public final class App { | ||||
|     noErrors(); | ||||
|     errorNoRetry(); | ||||
|     errorWithRetry(); | ||||
|     errorWithRetryExponentialBackoff(); | ||||
|   } | ||||
|  | ||||
|   private static void noErrors() throws Exception { | ||||
| @@ -102,4 +103,19 @@ public final class App { | ||||
|         + "the result %s after a number of attempts %s", customerId, retry.attempts() | ||||
|     )); | ||||
|   } | ||||
|    | ||||
|   private static void errorWithRetryExponentialBackoff() throws Exception { | ||||
|     final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>( | ||||
|         new FindCustomer("123", new CustomerNotFoundException("not found")), | ||||
|         6,  //6 attempts | ||||
|         30000, //30 s max delay between attempts | ||||
|         e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) | ||||
|         ); | ||||
|     op = retry; | ||||
|     final String customerId = op.perform(); | ||||
|     LOG.info(String.format( | ||||
|                 "However, retrying the operation while ignoring a recoverable error will eventually yield " | ||||
|                         + "the result %s after a number of attempts %s", customerId, retry.attempts() | ||||
|         )); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,115 @@ | ||||
| /* | ||||
|  * The MIT License (MIT) | ||||
|  * | ||||
|  * Copyright (c) 2014-2016 Ilkka Seppälä | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in all | ||||
|  * copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package com.iluwatar.retry; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Random; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| import java.util.function.Predicate; | ||||
|  | ||||
| /** | ||||
|  * Decorates {@link BusinessOperation business operation} with "retry" capabilities. | ||||
|  * | ||||
|  * @author George Aristy (george.aristy@gmail.com) | ||||
|  * @param <T> the remote op's return type | ||||
|  */ | ||||
| public final class RetryExponentialBackoff<T> implements BusinessOperation<T> { | ||||
|   private final BusinessOperation<T> op; | ||||
|   private final int maxAttempts; | ||||
|   private final long maxDelay; | ||||
|   private final AtomicInteger attempts; | ||||
|   private final Predicate<Exception> test; | ||||
|   private final List<Exception> errors; | ||||
|  | ||||
|     /** | ||||
|      * Ctor. | ||||
|      * | ||||
|      * @param op the {@link BusinessOperation} to retry | ||||
|      * @param maxAttempts number of times to retry | ||||
|      * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions | ||||
|      *     will be ignored if no tests are given | ||||
|      */ | ||||
|   @SafeVarargs | ||||
|   public RetryExponentialBackoff( | ||||
|             BusinessOperation<T> op, | ||||
|             int maxAttempts, | ||||
|             long maxDelay, | ||||
|             Predicate<Exception>... ignoreTests | ||||
|   ) { | ||||
|     this.op = op; | ||||
|     this.maxAttempts = maxAttempts; | ||||
|     this.maxDelay = maxDelay; | ||||
|     this.attempts = new AtomicInteger(); | ||||
|     this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false); | ||||
|     this.errors = new ArrayList<>(); | ||||
|   } | ||||
|  | ||||
|     /** | ||||
|      * The errors encountered while retrying, in the encounter order. | ||||
|      * | ||||
|      * @return the errors encountered while retrying | ||||
|      */ | ||||
|   public List<Exception> errors() { | ||||
|     return Collections.unmodifiableList(this.errors); | ||||
|   } | ||||
|  | ||||
|     /** | ||||
|      * The number of retries performed. | ||||
|      * | ||||
|      * @return the number of retries performed | ||||
|      */ | ||||
|   public int attempts() { | ||||
|     return this.attempts.intValue(); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public T perform() throws BusinessException { | ||||
|     do { | ||||
|       try { | ||||
|         return this.op.perform(); | ||||
|       } catch (BusinessException e) { | ||||
|         this.errors.add(e); | ||||
|  | ||||
|         if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { | ||||
|           throw e; | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|           Random rand = new Random(); | ||||
|           long testDelay = (long) Math.pow(2, this.attempts()) * 1000 + rand.nextInt(1000); | ||||
|           long delay = testDelay < this.maxDelay ? testDelay : maxDelay; | ||||
|           Thread.sleep(delay); | ||||
|         } catch (InterruptedException f) { | ||||
|           //ignore | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     while (true); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,115 @@ | ||||
| /* | ||||
|  * The MIT License (MIT) | ||||
|  * | ||||
|  * Copyright (c) 2014-2016 Ilkka Seppälä | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in all | ||||
|  * copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package com.iluwatar.retry; | ||||
|  | ||||
| import org.junit.jupiter.api.Test; | ||||
| import static org.hamcrest.CoreMatchers.hasItem; | ||||
| import static org.hamcrest.CoreMatchers.is; | ||||
| import static org.hamcrest.MatcherAssert.assertThat; | ||||
|  | ||||
| /** | ||||
|  * Unit tests for {@link Retry}. | ||||
|  * | ||||
|  * @author George Aristy (george.aristy@gmail.com) | ||||
|  */ | ||||
| public class RetryExponentialBackoffTest { | ||||
|     /** | ||||
|      * Should contain all errors thrown. | ||||
|      */ | ||||
|   @Test | ||||
|   public void errors() throws Exception { | ||||
|     final BusinessException e = new BusinessException("unhandled"); | ||||
|     final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>( | ||||
|         () -> {  | ||||
|           throw e;  | ||||
|         }, | ||||
|         2, | ||||
|         0 | ||||
|     ); | ||||
|     try { | ||||
|       retry.perform(); | ||||
|     } catch (BusinessException ex) { | ||||
|       //ignore | ||||
|     } | ||||
|  | ||||
|     assertThat( | ||||
|         retry.errors(), | ||||
|         hasItem(e) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|     /** | ||||
|      * No exceptions will be ignored, hence final number of attempts should be 1 even if we're asking | ||||
|      * it to attempt twice. | ||||
|      */ | ||||
|   @Test | ||||
|   public void attempts() { | ||||
|     final BusinessException e = new BusinessException("unhandled"); | ||||
|     final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>( | ||||
|         () -> {  | ||||
|           throw e;  | ||||
|         }, | ||||
|         2, | ||||
|         0 | ||||
|     ); | ||||
|     try { | ||||
|       retry.perform(); | ||||
|     } catch (BusinessException ex) { | ||||
|       //ignore | ||||
|     } | ||||
|  | ||||
|     assertThat( | ||||
|         retry.attempts(), | ||||
|         is(1) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|     /** | ||||
|      * Final number of attempts should be equal to the number of attempts asked because we are | ||||
|      * asking it to ignore the exception that will be thrown. | ||||
|      */ | ||||
|   @Test | ||||
|   public void ignore() throws Exception { | ||||
|     final BusinessException e = new CustomerNotFoundException("customer not found"); | ||||
|     final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>( | ||||
|         () -> {  | ||||
|           throw e;  | ||||
|         }, | ||||
|         2, | ||||
|         0, | ||||
|         ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()) | ||||
|     ); | ||||
|     try { | ||||
|       retry.perform(); | ||||
|     } catch (BusinessException ex) { | ||||
|       //ignore | ||||
|     } | ||||
|  | ||||
|     assertThat( | ||||
|         retry.attempts(), | ||||
|         is(2) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user