Full Code of nurkiewicz/async-retry for AI

master 409df8058921 cached
49 files
130.7 KB
34.2k tokens
296 symbols
1 requests
Download .txt
Repository: nurkiewicz/async-retry
Branch: master
Commit: 409df8058921
Files: 49
Total size: 130.7 KB

Directory structure:
gitextract_azfu50lp/

├── .gitignore
├── .travis.yml
├── README.md
├── license.txt
├── pom.xml
└── src/
    ├── main/
    │   └── java/
    │       └── com/
    │           └── nurkiewicz/
    │               └── asyncretry/
    │                   ├── AsyncRetryContext.java
    │                   ├── AsyncRetryExecutor.java
    │                   ├── AsyncRetryJob.java
    │                   ├── RetryContext.java
    │                   ├── RetryExecutor.java
    │                   ├── RetryJob.java
    │                   ├── SyncRetryExecutor.java
    │                   ├── SyncRetryJob.java
    │                   ├── backoff/
    │                   │   ├── Backoff.java
    │                   │   ├── BackoffWrapper.java
    │                   │   ├── BoundedMaxBackoff.java
    │                   │   ├── BoundedMinBackoff.java
    │                   │   ├── ExponentialDelayBackoff.java
    │                   │   ├── FirstRetryNoDelayBackoff.java
    │                   │   ├── FixedIntervalBackoff.java
    │                   │   ├── ProportionalRandomBackoff.java
    │                   │   ├── RandomBackoff.java
    │                   │   └── UniformRandomBackoff.java
    │                   ├── function/
    │                   │   ├── RetryCallable.java
    │                   │   └── RetryRunnable.java
    │                   └── policy/
    │                       ├── AbortRetryException.java
    │                       └── RetryPolicy.java
    └── test/
        ├── java/
        │   └── com/
        │       └── nurkiewicz/
        │           └── asyncretry/
        │               ├── AbstractBaseTestCase.java
        │               ├── AsyncRetryContextTest.java
        │               ├── AsyncRetryExecutorHappyTest.java
        │               ├── AsyncRetryExecutorManualAbortTest.java
        │               ├── AsyncRetryExecutorManyFailuresTest.java
        │               ├── AsyncRetryExecutorOneFailureTest.java
        │               ├── AsyncRetryJobTest.java
        │               ├── FaultyService.java
        │               ├── SyncRetryExecutorTest.java
        │               ├── backoff/
        │               │   ├── BoundedMaxBackoffTest.java
        │               │   ├── BoundedMinBackoffTest.java
        │               │   ├── ExponentialDelayBackoffTest.java
        │               │   ├── FirstRetryNoDelayBackoffTest.java
        │               │   └── RandomBackoffTest.java
        │               └── policy/
        │                   ├── AbstractRetryPolicyTest.java
        │                   ├── RetryPolicyBlackListTest.java
        │                   ├── RetryPolicyBothBlackAndWhiteTest.java
        │                   ├── RetryPolicyDefaultsTest.java
        │                   ├── RetryPolicyMaxRetriesTest.java
        │                   ├── RetryPolicyPredicatesTest.java
        │                   └── RetryPolicyWhiteListTest.java
        └── resources/
            └── logback-test.xml

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
target
*.iml
.idea


================================================
FILE: .travis.yml
================================================
language: java
install: mvn install -DskipTests=true -Dgpg.skip=true
jdk:
  - oraclejdk8
after_success:
  - mvn clean test jacoco:report coveralls:jacoco

================================================
FILE: README.md
================================================
[![Build Status](https://travis-ci.org/nurkiewicz/async-retry.svg?branch=master)](https://travis-ci.org/nurkiewicz/async-retry) [![Build Status](https://drone.io/github.com/nurkiewicz/async-retry/status.png)](https://drone.io/github.com/nurkiewicz/async-retry/latest) [![Coverage Status](https://img.shields.io/coveralls/nurkiewicz/async-retry.svg)](https://coveralls.io/r/nurkiewicz/async-retry) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.asyncretry/asyncretry/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.asyncretry/asyncretry)

# Asynchronous retry pattern

When you have a piece of code that often fails and must be retried, this Java 7/8 library provides rich and unobtrusive API with fast and scalable solution to this problem:

```java
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
RetryExecutor executor = new AsyncRetryExecutor(scheduler).
	retryOn(SocketException.class).
	withExponentialBackoff(500, 2).     //500ms times 2 after each retry
	withMaxDelay(10_000).               //10 seconds
	withUniformJitter().                //add between +/- 100 ms randomly
	withMaxRetries(20);
```

You can now run arbitrary block of code and the library will retry it for you in case it throws `SocketException`:

```java
final CompletableFuture<Socket> future = executor.getWithRetry(() ->
		new Socket("localhost", 8080)
);

future.thenAccept(socket ->
		System.out.println("Connected! " + socket)
);
```

Please look carefully! `getWithRetry()` does not block. It returns `CompletableFuture` immediately and invokes given function asynchronously. You can listen for that `Future` or even for multiple futures at once and do other work in the meantime. So what this code does is: trying to connect to `localhost:8080` and if it fails with `SocketException` it will retry after 500 milliseconds (with some random jitter), doubling delay after each retry, but not above 10 seconds.

Equivalent but more concise syntax:

```java
executor.
		getWithRetry(() -> new Socket("localhost", 8080)).
		thenAccept(socket -> System.out.println("Connected! " + socket));
```

This is a sample output that you might expect:

    TRACE | Retry 0 failed after 3ms, scheduled next retry in 508ms (Sun Jul 21 21:01:12 CEST 2013)
    java.net.ConnectException: Connection refused
    	at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]
    	//...
    
    TRACE | Retry 1 failed after 0ms, scheduled next retry in 934ms (Sun Jul 21 21:01:13 CEST 2013)
    java.net.ConnectException: Connection refused
    	at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]
    	//...
    
    TRACE | Retry 2 failed after 0ms, scheduled next retry in 1919ms (Sun Jul 21 21:01:15 CEST 2013)
    java.net.ConnectException: Connection refused
    	at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]
    	//...
    
    TRACE | Successful after 2 retries, took 0ms and returned: Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]

    Connected! Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]
    
Imagine you connect to two different systems, one is *slow*, second *unreliable* and fails often:

```java
CompletableFuture<String> stringFuture = executor.getWithRetry(ctx -> unreliable());
CompletableFuture<Integer> intFuture = executor.getWithRetry(ctx -> slow());

stringFuture.thenAcceptBoth(intFuture, (String s, Integer i) -> {
	//both done after some retries
});
```

`thenAcceptBoth()` callback is executed asynchronously when both slow and unreliable systems finally reply without any failure. Similarly (using `CompletableFuture.acceptEither()`) you can call two or more unreliable servers asynchronously at the same time and be notified when the first one succeeds after some number of retries.

I can't emphasize this enough - retries are executed asynchronously and effectively use thread pool, rather than sleeping blindly.

## Rationale

Often we are forced to [retry](http://servicedesignpatterns.com/WebServiceInfrastructures/IdempotentRetry) given piece of code because it failed and we must try again, typically with a small delay to spare CPU. This requirement is quite common and there are few ready-made generic implementations with [retry support in Spring Batch](http://static.springsource.org/spring-batch/reference/html/retry.html) through [`RetryTemplate`](http://static.springsource.org/spring-batch/2.1.x/apidocs/org/springframework/batch/retry/support/RetryTemplate.html) class being best known. But there are few other, quite similar approaches ([[1]](http://fahdshariff.blogspot.no/2009/08/retrying-operations-in-java.html), [[2]](https://github.com/Ninja-Squad/ninja-core/tree/master/src/main/java/com/ninja_squad/core/retry)). All of these attempts (and I bet many of you implemented similar tool yourself!) suffer the same issue - they are blocking, thus wasting a lot of resources and not scaling well.

This is not bad *per se* because it makes programming model much simpler - the library takes care of retrying and you simply have to wait for return value longer than usual. But not only it creates leaky abstraction (method that is typically very fast suddenly becomes slow due to retries and delay), but also wastes valuable threads since such facility will spend most of the time sleeping between retries. Therefore [`Async-Retry`](https://github.com/nurkiewicz/async-retry) utility was created, targeting **Java 8** (with [Java 7 backport](https://github.com/nurkiewicz/async-retry/tree/java7) existing) and addressing issues above.

The main abstraction is [`RetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/RetryExecutor.java) that provides simple API:

```java
public interface RetryExecutor {

	CompletableFuture<Void> doWithRetry(RetryRunnable action);

	<V> CompletableFuture<V> getWithRetry(Callable<V> task);

	<V> CompletableFuture<V> getWithRetry(RetryCallable<V> task);

	<V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task);
}
```

Don't worry about [`RetryRunnable`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/function/RetryRunnable.java) and [`RetryCallable`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/function/RetryCallable.java) - they allow checked exceptions for your convenience and most of the time we will use lambda expressions anyway.

Please note that it returns [`CompletableFuture`](http://nurkiewicz.blogspot.no/2013/05/java-8-definitive-guide-to.html). We no longer pretend that calling faulty method is fast. If the library encounters an exception it will retry our block of code with preconfigured backoff delays. The invocation time will sky-rocket from milliseconds to several seconds. `CompletableFuture` clearly indicates that. Moreover it's not a dumb [`java.util.concurrent.Future`](http://nurkiewicz.blogspot.no/2013/02/javautilconcurrentfuture-basics.html) we all know - [`CompletableFuture` in Java 8 is very powerful](http://nurkiewicz.blogspot.no/2013/05/java-8-completablefuture-in-action.html) and most importantly - non-blocking by default.

If you need blocking result after all, just call `.get()` on `Future` object.

## Basic API

The API is very simple. You provide a block of code and the library will run it multiple times until it returns normally rather than throwing an exception. It may also put configurable delays between retries:

```java
RetryExecutor executor = //see "Creating an instance of RetryExecutor" below

executor.getWithRetry(() -> new Socket("localhost", 8080));
```

Returned `CompletableFuture<Socket>` will be resolved once connecting to `localhost:8080` succeeds. Optionally we can consume [`RetryContext`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/RetryContext.java) to get extra context like which retry is currently being executed:

```java
executor.
	getWithRetry(ctx -> new Socket("localhost", 8080 + ctx.getRetryCount())).
	thenAccept(System.out::println);
```

This code is more clever than it looks. During first execution `ctx.getRetryCount()` returns `0`, therefore we try to connect to `localhost:8080`. If this fails, next retry will try `localhost:8081` (`8080 + 1`) and so on. And if you realize that all of this happens asynchronously you can scan ports of several machines and be notified about first responding port on each host:

```java
Arrays.asList("host-one", "host-two", "host-three").
	stream().
	forEach(host ->
		executor.
			getWithRetry(ctx -> new Socket(host, 8080 + ctx.getRetryCount())).
			thenAccept(System.out::println)
	);
```

For each host `RetryExecutor` will attempt to connect to port 8080 and retry with higher ports. 

`getFutureWithRetry()` requires special attention. I you want to retry method that already returns `CompletableFuture<V>`: e.g. result of asynchronous HTTP call:

```java
private CompletableFuture<String> asyncHttp(URL url) { /*...*/}

//...

final CompletableFuture<CompletableFuture<String>> response =
	executor.getWithRetry(ctx ->
		asyncHttp(new URL("http://example.com")));
```

Passing `asyncHttp()` to `getWithRetry()` will yield `CompletableFuture<CompletableFuture<V>>`. Not only it's awkward to work with, but also broken. The library will barely call `asyncHttp()` and retry only if it fails, but not if returned `CompletableFuture<String>` fails. The solution is simple:

```java
final CompletableFuture<String> response =
	executor.getFutureWithRetry(ctx ->
		asyncHttp(new URL("http://example.com")));
```

In this case `RetryExecutor` will understand that whatever was returned from `asyncHttp()` is actually just a `Future` and will (asynchronously) wait for result or failure. Speaking of which, in some cases despite retrying `RetryExecutor` will fail to obtain successful result. In general there are three possible outcomes of returned `CompletableFuture`:

1. *successful* - (possibly after some number of retries) - when our function eventually returns rather than throws

2. *exceptional* due to excessive retries - if you configure finite number of retries, `RetryExecutor` will eventually give up. In that case `Future` is completed exceptionally, providing last encountered exception

3. *exceptional* due to exception that should not be retried (see e.g. `abortOn()` and `abortIf()`) - just as above last encountered exception completes `Future`.

You can handle all these cases with the following by using [`CompletableFuture.whenComplete()`](http://download.java.net/lambda/b102/docs/api/java/util/concurrent/CompletableFuture.html#whenComplete(java.util.function.BiConsumer)) or [`CompletableFuture.handle()`](http://download.java.net/lambda/b102/docs/api/java/util/concurrent/CompletableFuture.html#handle(java.util.function.BiFunction)):

```java
executor.
	getWithRetry(() -> new Socket("localhost", 8080)).
	whenComplete((socket, error) -> {
		if (socket != null) {
			//connected OK, proceed
		} else {
			log.error("Can't connect, last error:", error);
		}
	});
```

## Configuration options

In general there are two important factors you can configure: [`RetryPolicy`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/policy/RetryPolicy.java) that controls whether next retry attempt should be made and [`Backoff`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/backoff/Backoff.java) - that optionally adds delay between subsequent retry attempts.

By default `RetryExecutor` repeats user task infinitely on every `	Throwable` and adds 1 second delay between retry attempts.

### Creating an instance of `RetryExecutor`

Default implementation of `RetryExecutor` is [`AsyncRetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/AsyncRetryExecutor.java) which you can create directly:

```java
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

RetryExecutor executor = new AsyncRetryExecutor(scheduler);

//...

scheduler.shutdownNow();
```

The only required dependency is standard [`ScheduledExecutorService` from JDK](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). One thread is enough in many cases but if you want to concurrently handle retries of hundreds or more tasks, consider increasing the pool size.

Notice that the `AsyncRetryExecutor` does not take care of shutting down the `ScheduledExecutorService`. This is a conscious design decision which will be explained later.

`AsyncRetryExecutor` has few other constructors but most of the time altering the behaviour of this class is most convenient with calling chained `with*()` methods. You will see plenty of examples written this way. Later on we will simply use `executor` reference without defining it. Assume it's of `RetryExecutor` type.

### Retrying policy

#### Exception classes

By default every `Throwable` (except special `AbortRetryException`) thrown from user task causes retry. Obviously this is configurable. For example in JPA you may want to retry a transaction that failed due to [`OptimisticLockException`](http://docs.oracle.com/javaee/6/api/javax/persistence/OptimisticLockException.html) - but every other exception should fail immediately:

```java
executor.
	retryOn(OptimisticLockException.class).
	withNoDelay().
	getWithRetry(ctx -> dao.optimistic());
```

Where `dao.optimistic()` may throw `OptimisticLockException`. In that case you probably don't want any delay between retries, more on that later. If you don't like the default of retrying on every `Throwable`, just restrict that using `retryOn()`:

```java
executor.retryOn(Exception.class)
```

Of course the opposite might also be desired - to abort retrying and fail immediately in case of certain exception being thrown rather than retrying. It's that simple:

```java
executor.
	abortOn(NullPointerException.class).
	abortOn(IllegalArgumentException.class).
	getWithRetry(ctx -> dao.optimistic());
```

Clearly you don't want to retry `NullPointerException` or `IllegalArgumentException` as they indicate programming bug rather than transient failure. And finally you can combine both retry and abort policies. User code will retry in case of any `retryOn()` exception (or subclass) unless it should `abortOn()` specified exception. For example we want to retry every `IOException` or `SQLException` but abort if `FileNotFoundException` or `java.sql.DataTruncation` is encountered (order of declarations is irrelevant):

```java
executor.
	retryOn(IOException.class).
	abortOn(FileNotFoundException.class).
	retryOn(SQLException.class).
	abortOn(DataTruncation.class).
	getWithRetry(ctx -> dao.load(42));
```
#### Exception predicates

If this is not enough you can provide custom predicate that will be invoked on each failure:

```java
executor.
	abortIf(throwable ->
		throwable instanceof SQLException &&
				throwable.getMessage().contains("ORA-00911")	
	).
	retryIf(t -> t.getCause() != null);
```

If any of `abortIf()` or `retryIf()` predicates return `true` task is aborted or retried respectively. Keep in mind that `abortIf()`/`retryIf()` take priority over `abortOn()`/`retryOn()` thus the following piece of code will retry on `FileNotFoundException("Access denied")`:

```java
executor.
		abortOn(FileNotFoundException.class).
		abortOn(NullPointerException.class).
		retryIf(e -> e.getMessage().contains("denied"))
``` 

If more than one `abortIf()` predicate passes as well as more than one `retryIf()` predicate then computation is aborted.

#### Max number of retries

Another way of interrupting retrying "loop" (remember that this process is asynchronous, there is no blocking *loop*) is by specifying maximum number of retries:

```java
executor.withMaxRetries(5)
```

In rare cases you may want to disable retries and barely take advantage from asynchronous execution. In that case try:

```java
executor.dontRetry()
```

Max number of retries takes precedence over `*On()` and `*If()` family of methods.

### Delays between retries (backoff)

Retrying immediately after failure is sometimes desired (see `OptimisticLockException` example) but in most cases it's a bad idea. If you can't connect to external system, waiting a little bit before next attempt sounds reasonably. You save CPU, bandwidth and other server's resources. But there are quite a few options to consider: 

* should we retry with constant intervals or [increase delay after each failure](http://en.wikipedia.org/wiki/Exponential_backoff)?

* should there be a lower and upper limit on waiting time?

* should we add random "jitter" to delay times to spread retries of many tasks in time?

This library answers all these questions.

#### Fixed interval between retries

By default each retry is preceded by 1 second waiting time. So if initial attempt fails, first retry will be executed after 1 second. Of course we can change that default, e.g. to 200 milliseconds:

```java
executor.withFixedBackoff(200)
```

If we are already here, by default backoff is applied after executing user task. If user task itself consumes some time, retries will be less frequent. For example with retry delay of 200ms and average time it takes before user task fails at about 50ms `RetryExecutor` will retry about 4 times per second (50ms + 200ms). However if you want to keep retry frequency at more predictable level you can use `fixedRate` flag:

```java
executor.
	withFixedBackoff(200).
	withFixedRate()
```

This is similar to "fixed rate" vs. "fixed delay" approaches in [`ScheduledExecutorService`](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). BTW don't expect `RetryExecutor` to be very precise, it does it's best but it heavily depends on aforementioned `ScheduledExecutorService` accuracy.

#### Exponentially growing intervals between retries

It's probably an active research subject, but in general you may wish to expand retry delay over time, assuming that if the user task fails several times we should try less frequently. For example let's say we start with 100ms delay until first retry attempt is made but if that one fails as well, we should wait two times more (200ms). And later 400ms, 800ms... You get the idea:

```java
executor.withExponentialBackoff(100, 2)
```

This is an exponential function that can grow very fast. Thus it's useful to set maximum backoff time at some reasonable level, e.g. 10 seconds:

```java
executor.
	withExponentialBackoff(100, 2).
	withMaxDelay(10_000)      //10 seconds
```

#### Random jitter

One phenomena often observed during major outages is that systems tend to synchronize. Imagine a busy system that suddenly stops responding. Hundreds or thousands of requests fail and are retried. It depends on your backoff but by default all these requests will retry exactly after one second producing huge wave of traffic at one point in time. Finally such failures are propagated to other systems that, in turn, synchronize as well.

To avoid this problem it's useful to spread retries over time, flattening the load. A simple solution is to add random jitter to delay time so that not all request are scheduled for retry at the exact same time. You have choice between uniform jitter (random value from -100ms to 100ms):

```java
executor.withUniformJitter(100)     //ms
```

...and proportional jitter, multiplying delay time by random factor, by default between 0.9 and 1.1 (10%):

```java
executor.withProportionalJitter(0.1)        //10%
```

You may also put hard lower limit on delay time to avoid to short retry times being scheduled:

```java
executor.withMinDelay(50)   //ms
```

#### No initial delay

There are cases when you want to run first retry immediately and fall back to configured backoff mechanism for subsequent requests. You can use `firstRetryNoDelay()` method:

```java
executor
	.withExponentialBackoff(100, 2)
	.firstRetryNoDelay();
CompletableFuture<String> future = executor.getWithRetry(this::someTask);
```

With the above configuration first retry is executed immediately, second retry after 100 ms, third after 200 ms and so on. Without `firstRetryNoDelay()` first retry would be delayed by 100 ms already.

## Implementation details

This library was built with Java 8 in mind to take advantage of lambdas and new `CompletableFuture` abstraction (but [Java 7 port with Guava dependency exists](https://github.com/nurkiewicz/async-retry/tree/java7)). It uses `ScheduledExecutorService` underneath to run tasks and schedule retries in the future - which allows best thread utilization.

But what is really interesting is that the whole library is fully immutable, there is no single mutable field, at all. This might be counter-intuitive at first, take for example this trivial code sample:

```java
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

AsyncRetryExecutor first = new AsyncRetryExecutor(scheduler).
	retryOn(Exception.class).
	withExponentialBackoff(500, 2);

AsyncRetryExecutor second = first.abortOn(FileNotFoundException.class);

AsyncRetryExecutor third = second.withMaxRetries(10);
```

It might seem that all `with*()` methods or `retryOn()`/`abortOn()` mutate existing executor. But that's not the case, each configuration change **creates new instance**, leaving the old one untouched. So for example while `first` executor will retry on `FileNotFoundException`, the `second` and `third` won't. However they all share the same `scheduler`. This is the reason why `AsyncRetryExecutor` does not shut down `ScheduledExecutorService` (it doesn't even have any `close()` method). Since we have no idea how many copies of `AsyncRetryExecutor` exist pointing to the same scheduler, we don't even try to manage its lifecycle. However this is typically not a problem (see *Spring integration* below).

You might be wondering, why such an awkward design decision? There are three reasons:

* when writing a concurrent code immutability can greatly reduce risk of multi-threading bugs. For example `RetryContext` holds number of retries. But instead of mutating it we simply create new instance (copy) with incremented but `final` counter. No race condition or visibility can ever occur.

* if you are given an existing `RetryExecutor` which is almost exactly what you want but you need one minor tweak, you simply call `executor.with...()` and get a fresh copy. You don't have to worry about other places where the same executor was used (see: *Spring integration* for further examples)

* functional programming and immutable data structures are *sexy* these days ;-).

N.B.: `AsyncRetryExecutor` is **not** marked `final`, does you can break immutability by subclassing it and adding mutable state. Please don't do this, subclassing is only permitted to alter behaviour.

## Dependencies

This library requires Java 8 and [SLF4J](http://www.slf4j.org/) for logging. Java 7 port additionally depends on [Guava](http://code.google.com/p/guava-libraries/).

## Spring integration

If you are just about to use `RetryExecutor` in Spring - feel free, but the configuration API might not work for you. Spring promotes (or used to promote) the convention of mutable services with plenty of setters. In XML you define bean and invoke setters (via `<property name="..."/>`) on it. This convention assumes the existence of mutating setters. But I found this approach error-prone and counter-intuitive under some circumstances.

Let's say we globally defined [`org.springframework.transaction.support.TransactionTemplate`](http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/transaction/support/TransactionTemplate.html) bean and injected it in multiple places. Great. Now there is this one single request that requires slightly different timeout:

```java
@Autowired
private TransactionTemplate template;
```

and later in the same class:
    
```java
final int oldTimeout = template.getTimeout();
template.setTimeout(10_000);
//do the work
template.setTimeout(oldTimeout);
```

This code is wrong on so many levels! First of all if something fails we never restore `oldTimeout`. OK, `finally` to the rescue. But also notice how we changed global, shared `TransactionTemplate` instance. Who knows how many other beans and threads are just about to use it, unaware of changed configuration?

And even if you do want to globally change the transaction timeout, fair enough, but it's still wrong way to do this. `private timeout` field is not `volatile` and thus changes made to it may or may not be visible to other threads. What a mess! The same problem appears with many other classes like [`JmsTemplate`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/jms/core/JmsTemplate.html).

You see where I'm going? Just create one, immutable service class and safely adjust it by creating copies whenever you need it. And using such services is equally simple these days:

```java
@Configuration
class Beans {

	@Bean
	public RetryExecutor retryExecutor() {
		return new AsyncRetryExecutor(scheduler()).
			retryOn(SocketException.class).
			withExponentialBackoff(500, 2);
	}

	@Bean(destroyMethod = "shutdownNow")
	public ScheduledExecutorService scheduler() {
		return Executors.newSingleThreadScheduledExecutor();
	}

}
```

Hey! It's 21st century, we don't need XML in Spring any more. Bootstrap is simple as well:

```java
final ApplicationContext context = new AnnotationConfigApplicationContext(Beans.class);
final RetryExecutor executor = context.getBean(RetryExecutor.class);
//...
context.close();
```

As you can see integrating modern, immutable services with Spring is just as simple. BTW if you are not prepared for such a big change when designing your own services, at least consider [constructor injection](http://nurkiewicz.blogspot.no/2011/09/evolution-of-spring-dependency.html).

## Maturity

This library is covered with a strong battery of unit tests ([![Build Status](https://travis-ci.org/nurkiewicz/async-retry.svg?branch=master)](https://travis-ci.org/nurkiewicz/async-retry)). However it wasn't yet used in any production code and the API is subject to change. Of course you are encouraged to submit [bugs, feature requests](https://github.com/nurkiewicz/async-retry/issues) and [pull requests](https://github.com/nurkiewicz/async-retry/pulls). It was developed with Java 8 in mind but [Java 7 backport](https://github.com/nurkiewicz/async-retry/tree/java7) exists with slightly more verbose API and mandatory Guava dependency ([`ListenableFuture`](http://nurkiewicz.blogspot.no/2013/02/listenablefuture-in-guava.html) instead of [`CompletableFuture` from Java 8](http://nurkiewicz.blogspot.no/2013/05/java-8-definitive-guide-to.html)).

## Using

### Maven

This library is available in [Maven Central Repository](http://search.maven.org):

```xml
<dependency>
    <groupId>com.nurkiewicz.asyncretry</groupId>
    <artifactId>asyncretry</artifactId>
    <version>0.0.7</version>
</dependency>
```


### Maven (Java 7)

Because backport to Java 7 has different API, it is maintained in a [separate branch](https://github.com/nurkiewicz/async-retry/tree/java7). It is also deployed to Maven Central under:

```xml
<dependency>
    <groupId>com.nurkiewicz.asyncretry</groupId>
    <artifactId>asyncretry-jdk7</artifactId>
    <version>0.0.7</version>
</dependency>
```

### Troubleshooting: `invalid target release: 1.8` during maven build

If you see this error message during maven build:

	[INFO] BUILD FAILURE
	...
	[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project lazyseq: 
	Fatal error compiling: invalid target release: 1.8 -> [Help 1]

it means you are not compiling using Java 8. [Download JDK 8 with lambda support](https://jdk8.java.net/lambda/) and let maven use it:

	$ export JAVA_HOME=/path/to/jdk8


## Version history

### 0.0.7 (24-05-2015)

* [`firstRetryNoDelay()` method](https://github.com/nurkiewicz/async-retry/issues/6)
* [Add withNoRetries() and withInfiniteRetries()](https://github.com/nurkiewicz/async-retry/issues/5)
* Fixed [*Errors from predicates are silently ignored*](https://github.com/nurkiewicz/async-retry/pull/7)

### 0.0.6 (26-11-2014)

* [`SyncRetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/0.0.6/src/main/java/com/nurkiewicz/asyncretry/SyncRetryExecutor.java) convenience class.

### 0.0.5 (31-05-2014)

* Bringing back Java 7 support

### 0.0.4 (30-05-2014)

* First official release into [Maven Central repository](http://central.maven.org/maven2/com/nurkiewicz/asyncretry/asyncretry).

### 0.0.3 (05-01-2014)

* Fixed [#3 *RetryOn ignored due to wrong command order*](https://github.com/nurkiewicz/async-retry/issues/3)
* `AbortRetryException` class was moved from `com.nurkiewicz.asyncretry.policy.exception` to `com.nurkiewicz.asyncretry.policy`
* Java 7 backport is no longer maintained starting from this version

### 0.0.2 (28-07-2013)

* Ability to specify multiple exception classes in `retryOn()`/`abortON()` using varargs

### 0.0.1 (23-07-2013)

* Initial revision


================================================
FILE: license.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   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.


================================================
FILE: pom.xml
================================================
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.sonatype.oss</groupId>
		<artifactId>oss-parent</artifactId>
		<version>7</version>
	</parent>
	<name>Async-Retry</name>
	<groupId>com.nurkiewicz.asyncretry</groupId>
	<artifactId>asyncretry</artifactId>
	<version>0.0.8-SNAPSHOT</version>
	<packaging>jar</packaging>
	<description>Library that asynchronously retries failed invocations of arbitrary code</description>
	<url>https://github.com/nurkiewicz/async-retry</url>
	<licenses>
		<license>
			<name>Apache License, Version 2.0</name>
			<url>http://www.apache.org/licenses/LICENSE-2.0</url>
		</license>
	</licenses>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<scm>
		<connection>scm:git:git@github.com:nurkiewicz/async-retry.git</connection>
		<url>scm:git:git@github.com:nurkiewicz/async-retry.git</url>
		<developerConnection>scm:git:git@github.com:nurkiewicz/async-retry.git</developerConnection>
		<tag>HEAD</tag>
	</scm>

	<developers>
		<developer>
			<name>Tomasz Nurkiewicz</name>
			<url>http://nurkiewicz.com</url>
		</developer>
	</developers>

	<distributionManagement>
		<repository>
			<id>sonatype-nexus-staging</id>
			<name>Nexus Release Repository</name>
			<url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
		</repository>
	</distributionManagement>

	<dependencies>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.5</version>
		</dependency>

		<!-- Testing -->
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>6.8.1</version>
			<exclusions>
				<exclusion>
					<groupId>junit</groupId>
					<artifactId>junit</artifactId>
				</exclusion>
			</exclusions>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.assertj</groupId>
			<artifactId>assertj-core</artifactId>
			<version>1.6.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-all</artifactId>
			<version>1.9.5</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.6</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.0.7</version>
			<scope>test</scope>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-release-plugin</artifactId>
				<version>2.5.1</version>
				<configuration>
					<tagNameFormat>@{project.version}</tagNameFormat>
					<autoVersionSubmodules>true</autoVersionSubmodules>
					<goals>deploy</goals>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-source-plugin</artifactId>
				<version>2.2.1</version>
				<executions>
					<execution>
						<id>attach-sources</id>
						<goals>
							<goal>jar</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<artifactId>maven-javadoc-plugin</artifactId>
				<version>2.9.1</version>
				<executions>
					<execution>
						<id>attach-javadocs</id>
						<goals>
							<goal>jar</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<artifactId>maven-gpg-plugin</artifactId>
				<version>1.5</version>
				<executions>
					<execution>
						<id>sign-artifacts</id>
						<phase>verify</phase>
						<goals>
							<goal>sign</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.sonatype.plugins</groupId>
				<artifactId>nexus-staging-maven-plugin</artifactId>
				<version>1.6.5</version>
				<extensions>true</extensions>
				<configuration>
					<serverId>sonatype-nexus-staging</serverId>
					<nexusUrl>https://oss.sonatype.org/</nexusUrl>
					<autoReleaseAfterClose>true</autoReleaseAfterClose>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.eluder.coveralls</groupId>
				<artifactId>coveralls-maven-plugin</artifactId>
				<version>2.2.0</version>
			</plugin>
			<plugin>
				<groupId>org.jacoco</groupId>
				<artifactId>jacoco-maven-plugin</artifactId>
				<version>0.7.2.201409121644</version>
				<executions>
					<execution>
						<id>prepare-agent</id>
						<goals>
							<goal>prepare-agent</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

</project>


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/AsyncRetryContext.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.policy.RetryPolicy;

import java.util.Objects;

public class AsyncRetryContext implements RetryContext {

	private final RetryPolicy retryPolicy;
	private final int retry;
	private final Throwable lastThrowable;

	public AsyncRetryContext(RetryPolicy retryPolicy) {
		this(retryPolicy, 0, null);
	}

	public AsyncRetryContext(RetryPolicy retryPolicy, int retry, Throwable lastThrowable) {
		this.retryPolicy = Objects.requireNonNull(retryPolicy);
		this.retry = retry;
		this.lastThrowable = lastThrowable;
	}

	@Override
	public boolean willRetry() {
		return retryPolicy.shouldContinue(this.nextRetry(new Exception()));
	}

	@Override
	public int getRetryCount() {
		return retry;
	}

	@Override
	public Throwable getLastThrowable() {
		return lastThrowable;
	}

	public AsyncRetryContext nextRetry(Throwable cause) {
		return new AsyncRetryContext(retryPolicy, retry + 1, cause);
	}

	public AsyncRetryContext prevRetry() {
		return new AsyncRetryContext(retryPolicy, retry - 1, lastThrowable);
	}

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/AsyncRetryExecutor.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.backoff.Backoff;
import com.nurkiewicz.asyncretry.backoff.ExponentialDelayBackoff;
import com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff;
import com.nurkiewicz.asyncretry.function.RetryCallable;
import com.nurkiewicz.asyncretry.function.RetryRunnable;
import com.nurkiewicz.asyncretry.policy.RetryPolicy;

import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/15/13, 11:06 PM
 */
public class AsyncRetryExecutor implements RetryExecutor {

	private final ScheduledExecutorService scheduler;
	private final boolean fixedDelay;
	private final RetryPolicy retryPolicy;
	private final Backoff backoff;

	public AsyncRetryExecutor(ScheduledExecutorService scheduler) {
		this(scheduler, RetryPolicy.DEFAULT, Backoff.DEFAULT);
	}

	public AsyncRetryExecutor(ScheduledExecutorService scheduler, Backoff backoff) {
		this(scheduler, RetryPolicy.DEFAULT, backoff);
	}

	public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy) {
		this(scheduler, retryPolicy, Backoff.DEFAULT);
	}

	public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy, Backoff backoff) {
		this(scheduler, retryPolicy, backoff, false);
	}

	public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy, Backoff backoff, boolean fixedDelay) {
		this.scheduler = Objects.requireNonNull(scheduler);
		this.retryPolicy = Objects.requireNonNull(retryPolicy);
		this.backoff = Objects.requireNonNull(backoff);
		this.fixedDelay = fixedDelay;
	}

	@Override
	public CompletableFuture<Void> doWithRetry(RetryRunnable action) {
		return getWithRetry(context -> {
			action.run(context);
			return null;
		});
	}

	@Override
	public <V> CompletableFuture<V> getWithRetry(Callable<V> task) {
		return getWithRetry(ctx -> task.call());
	}

	@Override
	public <V> CompletableFuture<V> getWithRetry(RetryCallable<V> task) {
		return scheduleImmediately(createTask(task));
	}

	@Override
	public <V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task) {
		return scheduleImmediately(createFutureTask(task));
	}

	private <V> CompletableFuture<V> scheduleImmediately(RetryJob<V> job) {
		scheduler.schedule(job, 0, MILLISECONDS);
		return job.getFuture();
	}

	protected <V> RetryJob<V> createTask(RetryCallable<V> function) {
		return new SyncRetryJob<>(function, this);
	}

	protected <V> RetryJob<V> createFutureTask(RetryCallable<CompletableFuture<V>> function) {
		return new AsyncRetryJob<>(function, this);
	}

	public ScheduledExecutorService getScheduler() {
		return scheduler;
	}

	public boolean isFixedDelay() {
		return fixedDelay;
	}

	public RetryPolicy getRetryPolicy() {
		return retryPolicy;
	}

	public Backoff getBackoff() {
		return backoff;
	}

	public AsyncRetryExecutor withScheduler(ScheduledExecutorService scheduler) {
		return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);
	}

	public AsyncRetryExecutor withRetryPolicy(RetryPolicy retryPolicy) {
		return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);
	}

	public AsyncRetryExecutor withExponentialBackoff(long initialDelayMillis, double multiplier) {
		final ExponentialDelayBackoff backoff = new ExponentialDelayBackoff(initialDelayMillis, multiplier);
		return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);
	}

	public AsyncRetryExecutor withFixedBackoff(long delayMillis) {
		final FixedIntervalBackoff backoff = new FixedIntervalBackoff(delayMillis);
		return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);
	}

	public AsyncRetryExecutor withBackoff(Backoff backoff) {
		return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);
	}

	public AsyncRetryExecutor withFixedRate() {
		return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, true);
	}

	public AsyncRetryExecutor withFixedRate(boolean fixedDelay) {
		return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay);
	}

	@SafeVarargs
	public final AsyncRetryExecutor retryOn(Class<? extends Throwable>... retryOnThrowables) {
		return this.withRetryPolicy(retryPolicy.retryOn(retryOnThrowables));
	}

	@SafeVarargs
	public final AsyncRetryExecutor abortOn(Class<? extends Throwable>... abortOnThrowable) {
		return this.withRetryPolicy(retryPolicy.abortOn(abortOnThrowable));
	}

	public AsyncRetryExecutor retryIf(Predicate<Throwable> retryPredicate) {
		return this.withRetryPolicy(retryPolicy.retryIf(retryPredicate));
	}

	public AsyncRetryExecutor abortIf(Predicate<Throwable> abortPredicate) {
		return this.withRetryPolicy(retryPolicy.abortIf(abortPredicate));
	}

	public AsyncRetryExecutor withUniformJitter() {
		return this.withBackoff(this.backoff.withUniformJitter());
	}

	public AsyncRetryExecutor withUniformJitter(long range) {
		return this.withBackoff(this.backoff.withUniformJitter(range));
	}

	public AsyncRetryExecutor withProportionalJitter() {
		return this.withBackoff(this.backoff.withProportionalJitter());
	}

	public AsyncRetryExecutor withProportionalJitter(double multiplier) {
		return this.withBackoff(this.backoff.withProportionalJitter(multiplier));
	}

	public AsyncRetryExecutor withMinDelay(long minDelayMillis) {
		return this.withBackoff(this.backoff.withMinDelay(minDelayMillis));
	}

	public AsyncRetryExecutor withMaxDelay(long maxDelayMillis) {
		return this.withBackoff(this.backoff.withMaxDelay(maxDelayMillis));
	}

	public AsyncRetryExecutor withMaxRetries(int times) {
		return this.withRetryPolicy(this.retryPolicy.withMaxRetries(times));
	}

	public AsyncRetryExecutor dontRetry() {
		return this.withRetryPolicy(this.retryPolicy.dontRetry());
	}

	public AsyncRetryExecutor retryInfinitely() {
		return this.withMaxRetries(Integer.MAX_VALUE);
	}

	public AsyncRetryExecutor withNoDelay() {
		return this.withBackoff(new FixedIntervalBackoff(0));
	}

	public AsyncRetryExecutor firstRetryNoDelay() {
		return this.withBackoff(this.backoff.withFirstRetryNoDelay());
	}

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/AsyncRetryJob.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.function.RetryCallable;

import java.util.concurrent.CompletableFuture;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/21/13, 6:37 PM
 */
public class AsyncRetryJob<V> extends RetryJob<V> {

	private final RetryCallable<CompletableFuture<V>> userTask;

	public AsyncRetryJob(RetryCallable<CompletableFuture<V>> userTask, AsyncRetryExecutor parent) {
		this(userTask, parent, new AsyncRetryContext(parent.getRetryPolicy()), new CompletableFuture<>());
	}

	public AsyncRetryJob(RetryCallable<CompletableFuture<V>> userTask, AsyncRetryExecutor parent, AsyncRetryContext context, CompletableFuture<V> future) {
		super(context, parent, future);
		this.userTask = userTask;
	}

	@Override
	public void run(long startTime) {
		try {
			userTask.call(context).handle((result, throwable) -> {
				final long stopTime = System.currentTimeMillis() - startTime;
				if (throwable != null) {
					handleThrowable(throwable, stopTime);
				} else {
					complete(result, stopTime);
				}
				return null;
			});
		} catch (Throwable t) {
			handleThrowable(t, System.currentTimeMillis() - startTime);
		}
	}

	@Override
	protected RetryJob<V> nextTask(AsyncRetryContext nextRetryContext) {
		return new AsyncRetryJob<>(userTask, parent, nextRetryContext, future);
	}


}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/RetryContext.java
================================================
package com.nurkiewicz.asyncretry;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 6:36 PM
 */
public interface RetryContext {
	boolean willRetry();

	/**
	 * Which retry is being executed right now
	 * @return 1 means it's the first retry, i.e. action is executed for the second time
	 */
	int getRetryCount();

	Throwable getLastThrowable();

	default boolean isFirstRetry() {
		return getRetryCount() == 1;
	}

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/RetryExecutor.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.function.RetryCallable;
import com.nurkiewicz.asyncretry.function.RetryRunnable;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/17/13, 7:25 PM
 */
public interface RetryExecutor {

	CompletableFuture<Void> doWithRetry(RetryRunnable action);

	<V> CompletableFuture<V> getWithRetry(Callable<V> task);

	<V> CompletableFuture<V> getWithRetry(RetryCallable<V> task);

	<V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task);
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/RetryJob.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.backoff.Backoff;
import com.nurkiewicz.asyncretry.policy.AbortRetryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.concurrent.CompletableFuture;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/21/13, 6:10 PM
 */
public abstract class RetryJob<V> implements Runnable {
	private static final Logger log = LoggerFactory.getLogger(RetryJob.class);
	protected final CompletableFuture<V> future;
	protected final AsyncRetryContext context;
	protected final AsyncRetryExecutor parent;

	public RetryJob(AsyncRetryContext context, AsyncRetryExecutor parent, CompletableFuture<V> future) {
		this.context = context;
		this.parent = parent;
		this.future = future;
	}

	protected void logSuccess(RetryContext context, V result, long duration) {
		log.trace("Successful after {} retries, took {}ms and returned: {}", context.getRetryCount(), duration, result);
	}

	protected void handleManualAbort(AbortRetryException abortEx) {
		logAbort(context);
		if (context.getLastThrowable() != null) {
			future.completeExceptionally(context.getLastThrowable());
		} else {
			future.completeExceptionally(abortEx);
		}
	}

	protected void logAbort(RetryContext context) {
		log.trace("Aborted by user after {} retries", context.getRetryCount() + 1);
	}

	protected void handleThrowable(Throwable t, long duration) {
		if (t instanceof AbortRetryException) {
			handleManualAbort((AbortRetryException) t);
		} else {
			handleUserThrowable(t, duration);
		}
	}

	protected void handleUserThrowable(Throwable t, long duration) {
		final AsyncRetryContext nextRetryContext = context.nextRetry(t);

		try {
			retryOrAbort(t, duration, nextRetryContext);
		} catch (Throwable predicateError) {
			log.error("Threw while trying to decide on retry {} after {}",
					nextRetryContext.getRetryCount(),
					duration,
					predicateError);
			future.completeExceptionally(t);
		}
	}

	private void retryOrAbort(Throwable t, long duration, AsyncRetryContext nextRetryContext) {
		if (parent.getRetryPolicy().shouldContinue(nextRetryContext)) {
			final long delay = calculateNextDelay(duration, nextRetryContext, parent.getBackoff());
			retryWithDelay(nextRetryContext, delay, duration);
		} else {
			logFailure(nextRetryContext, duration);
			future.completeExceptionally(t);
		}
	}

	protected void logFailure(AsyncRetryContext nextRetryContext, long duration) {
		log.trace("Giving up after {} retries, last run took: {}ms, last exception: ",
				context.getRetryCount(),
				duration,
				nextRetryContext.getLastThrowable());
	}

	private long calculateNextDelay(long taskDurationMillis, AsyncRetryContext nextRetryContext, Backoff backoff) {
		final long delay = backoff.delayMillis(nextRetryContext);
		return delay - (parent.isFixedDelay()? taskDurationMillis : 0);
	}

	private void retryWithDelay(AsyncRetryContext nextRetryContext, long delay, long duration) {
		final RetryJob<V> nextTask = nextTask(nextRetryContext);
		parent.getScheduler().schedule(nextTask, delay, MILLISECONDS);
		logRetry(nextRetryContext, delay, duration);
	}

	protected void logRetry(AsyncRetryContext nextRetryContext, long delay, long duration) {
		final Date nextRunDate = new Date(System.currentTimeMillis() + delay);
		log.trace("Retry {} failed after {}ms, scheduled next retry in {}ms ({})",
				context.getRetryCount(),
				duration,
				delay,
				nextRunDate,
				nextRetryContext.getLastThrowable());
	}

	@Override
	public void run() {
		run(System.currentTimeMillis());
	}

	protected abstract void run(long startTime);

	protected abstract RetryJob<V> nextTask(AsyncRetryContext nextRetryContext);

	protected void complete(V result, long duration) {
		logSuccess(context, result, duration);
		future.complete(result);
	}

	public CompletableFuture<V> getFuture() {
		return future;
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/SyncRetryExecutor.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.function.RetryCallable;
import com.nurkiewicz.asyncretry.function.RetryRunnable;
import com.nurkiewicz.asyncretry.policy.RetryPolicy;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

/**
 * Singleton instance of {@link RetryExecutor} that executes tasks in the same thread and without retry.
 * Useful for testing or when no-op implementation of {@link RetryExecutor} is needed.
 * This implementation, while implements the API, runs all tasks synchronously so that returned futures are already completed.
 * Exceptions are not thrown but wrapped in Future as well.
 * @since 0.0.6
 */
public enum SyncRetryExecutor implements RetryExecutor {

	INSTANCE;

	private static final AsyncRetryContext RETRY_CONTEXT = new AsyncRetryContext(RetryPolicy.DEFAULT);

	@Override
	public CompletableFuture<Void> doWithRetry(RetryRunnable action) {
		try {
			action.run(RETRY_CONTEXT);
			return CompletableFuture.completedFuture(null);
		} catch (Exception e) {
			return failedFuture(e);
		}
	}

	@Override
	public <V> CompletableFuture<V> getWithRetry(Callable<V> task) {
		try {
			return CompletableFuture.completedFuture(task.call());
		} catch (Exception e) {
			return failedFuture(e);
		}
	}

	@Override
	public <V> CompletableFuture<V> getWithRetry(RetryCallable<V> task) {
		try {
			return CompletableFuture.completedFuture(task.call(RETRY_CONTEXT));
		} catch (Exception e) {
			return failedFuture(e);
		}
	}

	@Override
	public <V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task) {
		try {
			return task.call(RETRY_CONTEXT);
		} catch (Exception e) {
			return failedFuture(e);
		}
	}

	private static <T> CompletableFuture<T> failedFuture(Exception e) {
		final CompletableFuture<T> promise = new CompletableFuture<>();
		promise.completeExceptionally(e);
		return promise;
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/SyncRetryJob.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.function.RetryCallable;

import java.util.concurrent.CompletableFuture;

class SyncRetryJob<V> extends RetryJob<V> {

	private final RetryCallable<V> userTask;

	public SyncRetryJob(RetryCallable<V> userTask, AsyncRetryExecutor parent) {
		this(userTask, parent, new AsyncRetryContext(parent.getRetryPolicy()), new CompletableFuture<>());
	}

	public SyncRetryJob(RetryCallable<V> userTask, AsyncRetryExecutor parent, AsyncRetryContext context, CompletableFuture<V> future) {
		super(context, parent, future);
		this.userTask = userTask;
	}

	@Override
	public void run(long startTime) {
		try {
			final V result = userTask.call(context);
			complete(result, System.currentTimeMillis() - startTime);
		} catch (Throwable t) {
			handleThrowable(t, System.currentTimeMillis() - startTime);
		}
	}

	protected RetryJob<V> nextTask(AsyncRetryContext nextRetryContext) {
		return new SyncRetryJob<>(userTask, parent, nextRetryContext, future);
	}

}

================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/Backoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.RetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/17/13, 11:15 PM
 */
public interface Backoff {

	Backoff DEFAULT = new FixedIntervalBackoff();

	long delayMillis(RetryContext context);

	default Backoff withUniformJitter() {
		return new UniformRandomBackoff(this);
	}

	default Backoff withUniformJitter(long range) {
		return new UniformRandomBackoff(this, range);
	}

	default Backoff withProportionalJitter() {
		return new ProportionalRandomBackoff(this);
	}

	default Backoff withProportionalJitter(double multiplier) {
		return new ProportionalRandomBackoff(this, multiplier);
	}

	default Backoff withMinDelay(long minDelayMillis) {
		return new BoundedMinBackoff(this, minDelayMillis);
	}

	default Backoff withMinDelay() {
		return new BoundedMinBackoff(this);
	}

	default Backoff withMaxDelay(long maxDelayMillis) {
		return new BoundedMaxBackoff(this, maxDelayMillis);
	}

	default Backoff withMaxDelay() {
		return new BoundedMaxBackoff(this);
	}

	default Backoff withFirstRetryNoDelay() {
		return new FirstRetryNoDelayBackoff(this);
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/BackoffWrapper.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import java.util.Objects;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/17/13, 11:16 PM
 */
public abstract class BackoffWrapper implements Backoff {

	protected final Backoff target;

	public BackoffWrapper(Backoff target) {
		this.target = Objects.requireNonNull(target);
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.RetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 7:04 PM
 */
public class BoundedMaxBackoff extends BackoffWrapper {

	public static final long DEFAULT_MAX_DELAY_MILLIS = 10_000;

	private final long maxDelayMillis;

	public BoundedMaxBackoff(Backoff target) {
		this(target, DEFAULT_MAX_DELAY_MILLIS);
	}

	public BoundedMaxBackoff(Backoff target, long maxDelayMillis) {
		super(target);
		this.maxDelayMillis = maxDelayMillis;
	}

	@Override
	public long delayMillis(RetryContext context) {
		return Math.min(target.delayMillis(context), maxDelayMillis);
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.RetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 7:04 PM
 */
public class BoundedMinBackoff extends BackoffWrapper {

	public static final long DEFAULT_MIN_DELAY_MILLIS = 100;

	private final long minDelayMillis;

	public BoundedMinBackoff(Backoff target) {
		this(target, DEFAULT_MIN_DELAY_MILLIS);
	}

	public BoundedMinBackoff(Backoff target, long minDelayMillis) {
		super(target);
		this.minDelayMillis = minDelayMillis;
	}

	@Override
	public long delayMillis(RetryContext context) {
		return Math.max(target.delayMillis(context), minDelayMillis);
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.RetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 6:17 PM
 */
public class ExponentialDelayBackoff implements Backoff {

	private final long initialDelayMillis;
	private final double multiplier;

	public ExponentialDelayBackoff(long initialDelayMillis, double multiplier) {
		if (initialDelayMillis <= 0) {
			throw new IllegalArgumentException("Initial delay must be positive but was: " + initialDelayMillis);
		}
		this.initialDelayMillis = initialDelayMillis;
		this.multiplier = multiplier;
	}

	@Override
	public long delayMillis(RetryContext context) {
		return (long) (initialDelayMillis * Math.pow(multiplier, context.getRetryCount() - 1));
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.AsyncRetryContext;
import com.nurkiewicz.asyncretry.RetryContext;

public class FirstRetryNoDelayBackoff extends BackoffWrapper {

	public FirstRetryNoDelayBackoff(Backoff target) {
		super(target);
	}

	@Override
	public long delayMillis(RetryContext context) {
		if (context.isFirstRetry()) {
			return 0;
		} else {
			return target.delayMillis(decrementRetryCount(context));
		}
	}

	private RetryContext decrementRetryCount(RetryContext context) {
		return ((AsyncRetryContext) context).prevRetry();
	}

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/FixedIntervalBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.RetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 6:14 PM
 */
public class FixedIntervalBackoff implements Backoff {

	public static final long DEFAULT_PERIOD_MILLIS = 1000;

	private final long intervalMillis;

	public FixedIntervalBackoff() {
		this(DEFAULT_PERIOD_MILLIS);
	}

	public FixedIntervalBackoff(long intervalMillis) {
		this.intervalMillis = intervalMillis;
	}

	@Override
	public long delayMillis(RetryContext context) {
		return intervalMillis;
	}

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/ProportionalRandomBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import java.util.Random;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 7:49 PM
 */
public class ProportionalRandomBackoff extends RandomBackoff {

	/**
	 * Randomly up to +/- 10%
	 */
	public static final double DEFAULT_MULTIPLIER = 0.1;

	private final double multiplier;

	public ProportionalRandomBackoff(Backoff target) {
		this(target, DEFAULT_MULTIPLIER);
	}

	public ProportionalRandomBackoff(Backoff target, Random random) {
		this(target, DEFAULT_MULTIPLIER, random);
	}

	public ProportionalRandomBackoff(Backoff target, double multiplier) {
		super(target);
		this.multiplier = multiplier;
	}

	public ProportionalRandomBackoff(Backoff target, double multiplier, Random random) {
		super(target, random);
		this.multiplier = multiplier;
	}

	@Override
	long addRandomJitter(long initialDelay) {
		final double randomMultiplier = (1 - 2 * random().nextDouble()) * multiplier;
		return (long) (initialDelay * (1 + randomMultiplier));
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/RandomBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.RetryContext;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 7:30 PM
 */
abstract public class RandomBackoff extends BackoffWrapper {

	private final Supplier<Random> randomSource;

	protected RandomBackoff(Backoff target) {
		this(target, ThreadLocalRandom::current);
	}

	protected RandomBackoff(Backoff target, Random randomSource) {
		this(target, () -> randomSource);
	}

	private RandomBackoff(Backoff target, Supplier<Random> randomSource) {
		super(target);
		this.randomSource = randomSource;
	}

	@Override
	public long delayMillis(RetryContext context) {
		final long initialDelay = target.delayMillis(context);
		final long randomDelay = addRandomJitter(initialDelay);
		return Math.max(randomDelay, 0);
	}

	abstract long addRandomJitter(long initialDelay);

	protected Random random() {
		return randomSource.get();
	}
}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/UniformRandomBackoff.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import java.util.Random;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 7:26 PM
 */
public class UniformRandomBackoff extends RandomBackoff {

	/**
	 * Randomly between +/- 100ms
	 */
	public static final long DEFAULT_RANDOM_RANGE_MILLIS = 100;

	private final long range;

	public UniformRandomBackoff(Backoff target) {
		this(target, DEFAULT_RANDOM_RANGE_MILLIS);
	}

	public UniformRandomBackoff(Backoff target, Random random) {
		this(target, DEFAULT_RANDOM_RANGE_MILLIS, random);
	}

	public UniformRandomBackoff(Backoff target, final long range) {
		super(target);
		this.range = range;
	}

	public UniformRandomBackoff(Backoff target, final long range, Random random) {
		super(target, random);
		this.range = range;
	}

	@Override
	long addRandomJitter(long initialDelay) {
		final double uniformRandom = (1 - random().nextDouble() * 2) * range;
		return (long) (initialDelay + uniformRandom);
	}


}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/function/RetryCallable.java
================================================
package com.nurkiewicz.asyncretry.function;

import com.nurkiewicz.asyncretry.RetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 9:34 PM
 */
@FunctionalInterface
public interface RetryCallable<V> {

	V call(RetryContext context) throws Exception;

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/function/RetryRunnable.java
================================================
package com.nurkiewicz.asyncretry.function;

import com.nurkiewicz.asyncretry.RetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 9:36 PM
 */
@FunctionalInterface
public interface RetryRunnable {

	void run(RetryContext context) throws Exception;

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/policy/AbortRetryException.java
================================================
package com.nurkiewicz.asyncretry.policy;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 10:23 PM
 */
public class AbortRetryException extends RuntimeException {

	public AbortRetryException() {
	}

}


================================================
FILE: src/main/java/com/nurkiewicz/asyncretry/policy/RetryPolicy.java
================================================
package com.nurkiewicz.asyncretry.policy;

import com.nurkiewicz.asyncretry.RetryContext;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 6:05 PM
 */
public class RetryPolicy {

	public static final RetryPolicy DEFAULT = new RetryPolicy();

	private final int maxRetries;
	private final Set<Class<? extends Throwable>> retryOn;
	private final Set<Class<? extends Throwable>> abortOn;
	private final Predicate<Throwable> retryPredicate;
	private final Predicate<Throwable> abortPredicate;

	public RetryPolicy retryOn(Class<? extends Throwable>... retryOnThrowables) {
		return new RetryPolicy(maxRetries, setPlusElems(retryOn, retryOnThrowables), abortOn, retryPredicate, abortPredicate);
	}

	public RetryPolicy abortOn(Class<? extends Throwable>... abortOnThrowables) {
		return new RetryPolicy(maxRetries, retryOn, setPlusElems(abortOn, abortOnThrowables), retryPredicate, abortPredicate);
	}

	public RetryPolicy abortIf(Predicate<Throwable> abortPredicate) {
		return new RetryPolicy(maxRetries, retryOn, abortOn, retryPredicate, this.abortPredicate.or(abortPredicate));
	}

	public RetryPolicy retryIf(Predicate<Throwable> retryPredicate) {
		return new RetryPolicy(maxRetries, retryOn, abortOn, this.retryPredicate.or(retryPredicate), abortPredicate);
	}

	public RetryPolicy dontRetry() {
		return new RetryPolicy(0, retryOn, abortOn, retryPredicate, abortPredicate);
	}

	public RetryPolicy withMaxRetries(int times) {
		return new RetryPolicy(times, retryOn, abortOn, retryPredicate, abortPredicate);
	}

	public RetryPolicy(int maxRetries, Set<Class<? extends Throwable>> retryOn, Set<Class<? extends Throwable>> abortOn, Predicate<Throwable> retryPredicate, Predicate<Throwable> abortPredicate) {
		this.maxRetries = maxRetries;
		this.retryOn = retryOn;
		this.abortOn = abortOn;
		this.retryPredicate = retryPredicate;
		this.abortPredicate = abortPredicate;
	}

	public RetryPolicy() {
		this(Integer.MAX_VALUE, Collections.emptySet(), Collections.emptySet(), th -> false, th -> false);
	}

	public boolean shouldContinue(RetryContext context) {
		if (tooManyRetries(context)) {
			return false;
		}
		if (abortPredicate.test(context.getLastThrowable())) {
			return false;
		}
		if (retryPredicate.test(context.getLastThrowable())) {
			return true;
		}
		return exceptionClassRetryable(context);
	}

	private boolean tooManyRetries(RetryContext context) {
		return context.getRetryCount() > maxRetries;
	}

	private boolean exceptionClassRetryable(RetryContext context) {
		if (context.getLastThrowable() == null) {
			return false;
		}
		final Class<? extends Throwable> e = context.getLastThrowable().getClass();
		if (abortOn.isEmpty()) {
			return matches(e, retryOn);
		} else {
			return !matches(e, abortOn) && matches(e, retryOn);
		}
	}

	private static boolean matches(Class<? extends Throwable> throwable, Set<Class<? extends Throwable>> set) {
		return set.isEmpty() || set.stream().anyMatch(c -> c.isAssignableFrom(throwable));
	}

	private static <T> Set<T> setPlusElems(Set<T> initial, T... newElement) {
		final HashSet<T> copy = new HashSet<>(initial);
		copy.addAll(Arrays.asList(newElement));
		return Collections.unmodifiableSet(copy);
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/AbstractBaseTestCase.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.policy.RetryPolicy;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.notNull;

/**
 * @author Tomasz Nurkiewicz
 * @since 5/10/13, 9:56 PM
 */
public class AbstractBaseTestCase {

	public static final String DON_T_PANIC = "Don't panic!";

	@Mock
	protected ScheduledExecutorService schedulerMock;

	@Mock
	protected FaultyService serviceMock;

	@BeforeMethod(alwaysRun=true)
	public void injectMocks() {
		MockitoAnnotations.initMocks(this);
		setupMocks();
	}

	private void setupMocks() {
		given(schedulerMock.schedule(notNullRunnable(), anyLong(), eq(TimeUnit.MILLISECONDS))).willAnswer(invocation -> {
			((Runnable) invocation.getArguments()[0]).run();
			return null;
		});
	}

	protected Runnable notNullRunnable() {
		return (Runnable) notNull();
	}

	protected RetryContext notNullRetryContext() {
		return (RetryContext) notNull();
	}

	protected TimeUnit millis() {
		return eq(TimeUnit.MILLISECONDS);
	}

	protected RetryContext anyRetry() {
		return retry(1);
	}

	protected RetryContext retry(int ret) {
		return new AsyncRetryContext(RetryPolicy.DEFAULT, ret, new Exception());
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryContextTest.java
================================================
package com.nurkiewicz.asyncretry;

import org.mockito.InOrder;
import org.testng.annotations.Test;

import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/17/13, 9:34 PM
 */
public class AsyncRetryContextTest extends AbstractBaseTestCase {

	@Test
	public void shouldNotRetryIfRetriesForbidden() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();

		//when
		executor.doWithRetry(ctx -> serviceMock.withFlag(ctx.willRetry()));

		//then
		verify(serviceMock).withFlag(false);
	}

	@Test
	public void shouldSayItWillRetryIfUnlimitedNumberOfRetries() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);

		//when
		executor.doWithRetry(ctx -> serviceMock.withFlag(ctx.willRetry()));

		//then
		verify(serviceMock).withFlag(true);
	}

	@Test
	public void shouldSayItWillRetryOnFirstFewCases() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(2);
		doThrow(IllegalStateException.class).when(serviceMock).withFlag(anyBoolean());

		//when
		executor.doWithRetry(ctx -> serviceMock.withFlag(ctx.willRetry()));

		//then
		final InOrder order = inOrder(serviceMock);
		order.verify(serviceMock, times(2)).withFlag(true);
		order.verify(serviceMock).withFlag(false);
		order.verifyNoMoreInteractions();
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorHappyTest.java
================================================
package com.nurkiewicz.asyncretry;

import org.testng.annotations.Test;

import java.util.concurrent.CompletableFuture;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 10:51 PM
 */
public class AsyncRetryExecutorHappyTest extends AbstractBaseTestCase {

	@Test
	public void shouldNotRetryIfCompletesAfterFirstExecution() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);

		//when
		executor.doWithRetry(ctx -> serviceMock.alwaysSucceeds());

		//then
		verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());
		verifyNoMoreInteractions(schedulerMock);
	}

	@Test
	public void shouldCallUserTaskOnlyOnceIfItDoesntFail() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);

		//when
		executor.doWithRetry(ctx -> serviceMock.alwaysSucceeds());

		//then
		verify(serviceMock).alwaysSucceeds();
	}

	@Test
	public void shouldReturnResultOfFirstSuccessfulCall() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.alwaysSucceeds()).willReturn(42);

		//when
		final CompletableFuture<Integer> future = executor.getWithRetry(serviceMock::alwaysSucceeds);

		//then
		assertThat(future.get()).isEqualTo(42);
	}

	@Test
	public void shouldReturnEvenIfNoRetryPolicy() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();
		given(serviceMock.alwaysSucceeds()).willReturn(42);

		//when
		final CompletableFuture<Integer> future = executor.getWithRetry(serviceMock::alwaysSucceeds);

		//then
		assertThat(future.get()).isEqualTo(42);
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManualAbortTest.java
================================================
package com.nurkiewicz.asyncretry;

import org.assertj.core.api.Assertions;
import org.mockito.InOrder;
import org.testng.annotations.Test;

import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 10:51 PM
 */
public class AsyncRetryExecutorManualAbortTest extends AbstractBaseTestCase {

	@Test
	public void shouldRethrowIfFirstExecutionThrowsAnExceptionAndNoRetry() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();
		given(serviceMock.sometimesFails()).
				willThrow(new IllegalStateException(DON_T_PANIC));

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();
		try {
			future.get();
			Assertions.failBecauseExceptionWasNotThrown(IllegalStateException.class);
		} catch (ExecutionException e) {
			final Throwable actualCause = e.getCause();
			assertThat(actualCause).isInstanceOf(IllegalStateException.class);
			assertThat(actualCause.getMessage()).isEqualToIgnoringCase(DON_T_PANIC);
		}
	}

	@Test
	public void shouldRetryAfterOneExceptionAndReturnValue() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class).
				willReturn("Foo");

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.get()).isEqualTo("Foo");
	}

	@Test
	public void shouldSucceedWhenOnlyOneRetryAllowed() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(1);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class).
				willReturn("Foo");

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.get()).isEqualTo("Foo");
	}

	@Test
	public void shouldRetryOnceIfFirstExecutionThrowsException() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class).
				willReturn("Foo");

		//when
		executor.getWithRetry(serviceMock::sometimesFails);

		//then
		verify(serviceMock, times(2)).sometimesFails();
	}

	@Test
	public void shouldScheduleRetryWithDefaultDelay() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class).
				willReturn("Foo");

		//when
		executor.getWithRetry(serviceMock::sometimesFails);

		//then
		final InOrder inOrder = inOrder(schedulerMock);
		inOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());
		inOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(DEFAULT_PERIOD_MILLIS), millis());
		inOrder.verifyNoMoreInteractions();
	}

	@Test
	public void shouldPassCorrectRetryCountToEachInvocationInContext() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.calculateSum(0)).willThrow(IllegalStateException.class);
		given(serviceMock.calculateSum(1)).willReturn(BigDecimal.ONE);

		//when
		executor.getWithRetry(ctx -> serviceMock.calculateSum(ctx.getRetryCount()));

		//then
		final InOrder order = inOrder(serviceMock);
		order.verify(serviceMock).calculateSum(0);
		order.verify(serviceMock).calculateSum(1);
		order.verifyNoMoreInteractions();
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManyFailuresTest.java
================================================
package com.nurkiewicz.asyncretry;

import org.assertj.core.api.Assertions;
import org.mockito.InOrder;
import org.testng.annotations.Test;

import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 10:51 PM
 */
public class AsyncRetryExecutorManyFailuresTest extends AbstractBaseTestCase {

	@Test
	public void shouldRethrowIfFirstFewExecutionsThrow() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(2);
		given(serviceMock.sometimesFails()).willThrow(new IllegalStateException(DON_T_PANIC));

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();
		try {
			future.get();
			Assertions.failBecauseExceptionWasNotThrown(IllegalStateException.class);
		} catch (ExecutionException e) {
			final Throwable actualCause = e.getCause();
			assertThat(actualCause).isInstanceOf(IllegalStateException.class);
			assertThat(actualCause.getMessage()).isEqualToIgnoringCase(DON_T_PANIC);
		}
	}

	@Test
	public void shouldRetryAfterManyExceptionsAndReturnValue() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).
				willReturn("Foo");

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.get()).isEqualTo("Foo");
	}

	@Test
	public void shouldSucceedWhenTheSameNumberOfRetriesAsFailuresAllowed() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(3);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).
				willReturn("Foo");

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.get()).isEqualTo("Foo");
	}

	@Test
	public void shouldRetryManyTimesIfFirstExecutionsThrowException() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).
				willReturn("Foo");

		//when
		executor.getWithRetry(serviceMock::sometimesFails);

		//then
		verify(serviceMock, times(4)).sometimesFails();
	}

	@Test
	public void shouldScheduleRetryWithDefaultDelay() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(IllegalStateException.class, IllegalStateException.class, IllegalStateException.class).
				willReturn("Foo");

		//when
		executor.getWithRetry(serviceMock::sometimesFails);

		//then
		final InOrder inOrder = inOrder(schedulerMock);
		inOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());
		inOrder.verify(schedulerMock, times(3)).schedule(notNullRunnable(), eq(DEFAULT_PERIOD_MILLIS), millis());
		inOrder.verifyNoMoreInteractions();
	}

	@Test
	public void shouldPassCorrectRetryCountToEachInvocationInContext() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.calculateSum(0)).willThrow(IllegalStateException.class);
		given(serviceMock.calculateSum(1)).willThrow(IllegalStateException.class);
		given(serviceMock.calculateSum(2)).willThrow(IllegalStateException.class);
		given(serviceMock.calculateSum(3)).willReturn(BigDecimal.ONE);

		//when
		executor.getWithRetry(ctx -> serviceMock.calculateSum(ctx.getRetryCount()));

		//then
		final InOrder order = inOrder(serviceMock);
		order.verify(serviceMock).calculateSum(0);
		order.verify(serviceMock).calculateSum(1);
		order.verify(serviceMock).calculateSum(2);
		order.verify(serviceMock).calculateSum(3);
		order.verifyNoMoreInteractions();
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorOneFailureTest.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.policy.AbortRetryException;
import org.testng.annotations.Test;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/16/13, 10:51 PM
 */
public class AsyncRetryExecutorOneFailureTest extends AbstractBaseTestCase {

	@Test
	public void shouldNotRetryIfAbortThrown() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(AbortRetryException.class);

		//when
		executor.getWithRetry(serviceMock::sometimesFails);

		//then
		verify(serviceMock).sometimesFails();
	}

	@Test
	public void shouldRethrowAbortExceptionIfFirstIterationThrownIt() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(AbortRetryException.class);

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();
		try {
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			assertThat(e.getCause()).isInstanceOf(AbortRetryException.class);
		}
	}

	@Test
	public void shouldCompleteWithExceptionIfFirstIterationThrownIt() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();
		given(serviceMock.sometimesFails()).
				willThrow(new IllegalStateException(DON_T_PANIC));

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		AtomicReference<Throwable> error = new AtomicReference<>();
		future.whenComplete((res, t) -> {
			if (res == null) {
				error.set(t);       //schedulerMock is synchronous anyway
			}
		});
		assertThat(error.get()).
				isNotNull().
				isInstanceOf(IllegalStateException.class).
				hasMessage(DON_T_PANIC);
	}

	@Test
	public void shouldRethrowLastThrownExceptionWhenAbortedInSubsequentIteration() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.sometimesFails()).
				willThrow(
						new IllegalArgumentException("First"),
						new IllegalStateException("Second"),
						new AbortRetryException()
				);

		//when
		final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();
		try {
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
			assertThat(e.getCause().getMessage()).isEqualTo("Second");
		}
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryJobTest.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.policy.AbortRetryException;
import org.assertj.core.api.Assertions;
import org.mockito.InOrder;
import org.testng.annotations.Test;

import java.io.IOException;
import java.net.SocketException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/21/13, 7:07 PM
 */
public class AsyncRetryJobTest extends AbstractBaseTestCase {

	@Test
	public void shouldUnwrapUserFutureAndReturnIt() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.safeAsync()).willReturn(CompletableFuture.completedFuture("42"));

		//when
		final CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());

		//then
		assertThat(future.get()).isEqualTo("42");
	}

	@Test
	public void shouldSucceedAfterFewAsynchronousRetries() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.safeAsync()).willReturn(
				failedAsync(new SocketException("First")),
				failedAsync(new IOException("Second")),
				CompletableFuture.completedFuture("42")
		);

		//when
		final CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());

		//then
		assertThat(future.get()).isEqualTo("42");
	}

	@Test
	public void shouldScheduleTwoTimesWhenRetries() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.safeAsync()).willReturn(
				failedAsync(new SocketException("First")),
				failedAsync(new IOException("Second")),
				CompletableFuture.completedFuture("42")
		);

		//when
		final CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());

		//then
		future.get();

		final InOrder order = inOrder(schedulerMock);
		order.verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());
		order.verify(schedulerMock, times(2)).schedule(notNullRunnable(), eq(DEFAULT_PERIOD_MILLIS), millis());
	}

	private CompletableFuture<String> failedAsync(Throwable throwable) {
		final CompletableFuture<String> future = new CompletableFuture<>();
		future.completeExceptionally(throwable);
		return future;
	}

	@Test
	public void shouldRethrowOriginalExceptionFromUserFutureCompletion() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).
				abortOn(SocketException.class);
		given(serviceMock.safeAsync()).willReturn(
				failedAsync(new SocketException(DON_T_PANIC))
		);

		//when
		final CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();

		try {
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			final Throwable cause = e.getCause();
			assertThat(cause).isInstanceOf(SocketException.class);
			assertThat(cause).hasMessage(DON_T_PANIC);
		}
	}

	@Test
	public void shouldRethrowOriginalExceptionFromUserFutureCompletionAndAbortWhenTestFails() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).
				abortIf(t -> { throw new RuntimeException("test invalid"); });

		given(serviceMock.safeAsync()).willReturn(
				failedAsync(new SocketException(DON_T_PANIC))
		);

		//when
		final CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();

		try {
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			final Throwable cause = e.getCause();
			assertThat(cause).isInstanceOf(SocketException.class);
			assertThat(cause).hasMessage(DON_T_PANIC);
		}
	}

	@Test
	public void shouldAbortWhenTargetFutureWantsToAbort() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
		given(serviceMock.safeAsync()).willReturn(
				failedAsync(new AbortRetryException())
		);

		//when
		final CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();

		try {
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			final Throwable cause = e.getCause();
			assertThat(cause).isInstanceOf(AbortRetryException.class);
		}
	}

	@Test
	public void shouldRethrowExceptionThatWasThrownFromUserTaskBeforeReturningFuture() throws Exception {
		//given
		final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).
				abortOn(IllegalArgumentException.class);
		given(serviceMock.safeAsync()).willThrow(new IllegalArgumentException(DON_T_PANIC));

		//when
		final CompletableFuture<String> future = executor.getFutureWithRetry(ctx -> serviceMock.safeAsync());

		//then
		assertThat(future.isCompletedExceptionally()).isTrue();

		try {
			future.get();
			Assertions.failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
			assertThat(e.getCause()).hasMessage(DON_T_PANIC);
		}
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/FaultyService.java
================================================
package com.nurkiewicz.asyncretry;

import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/17/13, 7:09 PM
 */
public interface FaultyService {

	int alwaysSucceeds();

	String sometimesFails();

	BigDecimal calculateSum(int retry);

	void withFlag(boolean flag);

	CompletableFuture<String> safeAsync();

	CompletableFuture<String> alwaysFailsAsync();

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/SyncRetryExecutorTest.java
================================================
package com.nurkiewicz.asyncretry;

import com.nurkiewicz.asyncretry.function.RetryCallable;
import com.nurkiewicz.asyncretry.function.RetryRunnable;
import org.testng.annotations.Test;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;

public class SyncRetryExecutorTest extends AbstractBaseTestCase {

	RetryExecutor executor = SyncRetryExecutor.INSTANCE;

	@Test
	void shouldReturnCompletedFutureWhenDoWithRetryCalled() throws ExecutionException, InterruptedException {
		//given
		String mainThread = Thread.currentThread().getName();
		AtomicReference<String> poolThread = new AtomicReference<>();

		//when
		CompletableFuture<Void> result = executor.doWithRetry(ctx -> poolThread.set(Thread.currentThread().getName()));

		//then
		assertThat(poolThread.get()).isEqualTo(mainThread);
	}

	@Test
	void shouldWrapExceptionInFutureRatherThanThrowingIt() throws InterruptedException {
		//given
		RetryRunnable block = context -> {
			throw new IllegalArgumentException(DON_T_PANIC);
		};
		CompletableFuture<Void> future = executor.doWithRetry(block);

		try {
			//when
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			//then
			assertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);
			assertThat(e.getCause()).hasMessage(DON_T_PANIC);
		}
	}

	@Test
	void shouldReturnCompletedFutureWhenGetWithRetryCalled() throws ExecutionException, InterruptedException {
		//given
		String mainThread = Thread.currentThread().getName();

		//when
		CompletableFuture<String> result = executor.getWithRetry(() -> Thread.currentThread().getName());

		//then
		assertThat(result.get()).isEqualTo(mainThread);
	}

	@Test
	void shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetry() throws InterruptedException {
		//given
		Callable<Void> block = () -> {
			throw new IllegalArgumentException(DON_T_PANIC);
		};
		CompletableFuture<Void> future = executor.getWithRetry(block);

		try {
			//when
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			//then
			assertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);
			assertThat(e.getCause()).hasMessage(DON_T_PANIC);
		}
	}

	@Test
	void shouldReturnCompletedFutureWhenGetWithRetryCalledContext() throws ExecutionException, InterruptedException {
		//given
		String mainThread = Thread.currentThread().getName();

		//when
		CompletableFuture<String> result = executor.getWithRetry(ctx -> Thread.currentThread().getName());

		//then
		assertThat(result.get()).isEqualTo(mainThread);
	}

	@Test
	void shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetryContext() throws InterruptedException {
		//given
		RetryCallable<Void> block = ctx -> {
			throw new IllegalArgumentException(DON_T_PANIC);
		};
		CompletableFuture<Void> future = executor.getWithRetry(block);

		try {
			//when
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			//then
			assertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);
			assertThat(e.getCause()).hasMessage(DON_T_PANIC);
		}
	}

	@Test
	void shouldReturnCompletedFutureWhenGetWithRetryOnFutureCalled() throws ExecutionException, InterruptedException {
		//given
		String mainThread = Thread.currentThread().getName();

		//when
		CompletableFuture<String> result = executor.getFutureWithRetry(ctx ->
				CompletableFuture.completedFuture(Thread.currentThread().getName()));

		//then
		assertThat(result.get()).isEqualTo(mainThread);
	}

	@Test
	void shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetryOnFuture() throws InterruptedException {
		//given
		RetryCallable<CompletableFuture<String>> block = ctx -> {
			final CompletableFuture<String> failedFuture = new CompletableFuture<>();
			failedFuture.completeExceptionally(new IllegalArgumentException(DON_T_PANIC));
			return failedFuture;
		};
		CompletableFuture<String> future = executor.getFutureWithRetry(block);

		try {
			//when
			future.get();
			failBecauseExceptionWasNotThrown(ExecutionException.class);
		} catch (ExecutionException e) {
			//then
			assertThat(e).hasCauseInstanceOf(IllegalArgumentException.class);
			assertThat(e.getCause()).hasMessage(DON_T_PANIC);
		}
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoffTest.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.AbstractBaseTestCase;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 5:44 PM
 */
public class BoundedMaxBackoffTest extends AbstractBaseTestCase {

	@Test
	public void shouldReturnOriginalBackoffDelayIfBelowMax() throws Exception {
		final Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay();

		assertThat(backoff.delayMillis(retry(1))).isEqualTo(1);
		assertThat(backoff.delayMillis(retry(2))).isEqualTo(2);
		assertThat(backoff.delayMillis(retry(3))).isEqualTo(4);
		assertThat(backoff.delayMillis(retry(4))).isEqualTo(8);
	}

	@Test
	public void shouldCapBackoffAtDefaultLevel() throws Exception {
		final Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay();

		assertThat(backoff.delayMillis(retry(100))).isEqualTo(BoundedMaxBackoff.DEFAULT_MAX_DELAY_MILLIS);
	}

	@Test
	public void shouldCapBackoffAtGivenLevel() throws Exception {
		final Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay(1234);

		assertThat(backoff.delayMillis(retry(100))).isEqualTo(1234);
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoffTest.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.AbstractBaseTestCase;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 6:32 PM
 */
public class BoundedMinBackoffTest extends AbstractBaseTestCase {

	@Test
	public void shouldReturnOriginalBackoffDelayIfAboveMin() throws Exception {
		final Backoff backoff = new ExponentialDelayBackoff(1000, 2.0).withMinDelay();

		assertThat(backoff.delayMillis(retry(1))).isEqualTo(1000);
		assertThat(backoff.delayMillis(retry(2))).isEqualTo(2000);
		assertThat(backoff.delayMillis(retry(3))).isEqualTo(4000);
		assertThat(backoff.delayMillis(retry(4))).isEqualTo(8000);
	}

	@Test
	public void shouldCapBackoffAtDefaultLevel() throws Exception {
		final Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMinDelay();

		assertThat(backoff.delayMillis(retry(1))).isEqualTo(BoundedMinBackoff.DEFAULT_MIN_DELAY_MILLIS);
	}

	@Test
	public void shouldCapBackoffAtGivenLevel() throws Exception {
		final Backoff backoff = new ExponentialDelayBackoff(1, 2.0).withMaxDelay(250);

		assertThat(backoff.delayMillis(retry(100))).isEqualTo(250);
	}

	@Test
	public void shouldApplyBothMinAndMaxBound() throws Exception {
		final Backoff backoff = new ExponentialDelayBackoff(1, 2.0).
				withMinDelay(5).
				withMaxDelay(10);

		assertThat(backoff.delayMillis(retry(2))).isEqualTo(5);
		assertThat(backoff.delayMillis(retry(3))).isEqualTo(5);
		assertThat(backoff.delayMillis(retry(4))).isEqualTo(8);
		assertThat(backoff.delayMillis(retry(5))).isEqualTo(10);
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoffTest.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.AbstractBaseTestCase;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 5:30 PM
 */
public class ExponentialDelayBackoffTest extends AbstractBaseTestCase {

	@Test
	public void shouldThrowWhenNotPositiveInitialDelay() throws Exception {
		//given
		final int initialDelayMillis = 0;

		try {
			//when
			new ExponentialDelayBackoff(initialDelayMillis, 2.0);
			failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
		} catch (IllegalArgumentException e) {
			//then
			assertThat(e.getMessage()).endsWith("0");
		}

	}

	@Test
	public void shouldReturnPowersOfTwo() throws Exception {
		//given
		final ExponentialDelayBackoff backoff = new ExponentialDelayBackoff(1, 2.0);

		//when
		final long first = backoff.delayMillis(retry(1));
		final long second = backoff.delayMillis(retry(2));
		final long third = backoff.delayMillis(retry(3));
		final long fourth = backoff.delayMillis(retry(4));

		//then
		assertThat(first).isEqualTo(1);
		assertThat(second).isEqualTo(2);
		assertThat(third).isEqualTo(2 * 2);
		assertThat(fourth).isEqualTo(2 * 2 * 2);
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoffTest.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.AbstractBaseTestCase;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class FirstRetryNoDelayBackoffTest extends AbstractBaseTestCase {

	@Test
	public void firstRetryShouldHaveNoDelay() {
		//given
		final Backoff backoff = new FixedIntervalBackoff(1_000).withFirstRetryNoDelay();

		//when
		final long first = backoff.delayMillis(retry(1));
		final long second = backoff.delayMillis(retry(2));
		final long third = backoff.delayMillis(retry(3));

		//then
		assertThat(first).isEqualTo(0);
		assertThat(second).isEqualTo(1_000);
		assertThat(third).isEqualTo(1_000);
	}

	@Test
	public void secondRetryShouldCalculateDelayAsIfItWasFirst() {
		//given
		final Backoff backoff = new ExponentialDelayBackoff(100, 2).withFirstRetryNoDelay();

		//when
		final long first = backoff.delayMillis(retry(1));
		final long second = backoff.delayMillis(retry(2));
		final long third = backoff.delayMillis(retry(3));

		//then
		assertThat(first).isEqualTo(0);
		assertThat(second).isEqualTo(100);
		assertThat(third).isEqualTo(200);
	}

}

================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/RandomBackoffTest.java
================================================
package com.nurkiewicz.asyncretry.backoff;

import com.nurkiewicz.asyncretry.AbstractBaseTestCase;
import org.mockito.Mock;
import org.testng.annotations.Test;

import java.util.Random;

import static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;
import static com.nurkiewicz.asyncretry.backoff.UniformRandomBackoff.DEFAULT_RANDOM_RANGE_MILLIS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 6:57 PM
 */
public class RandomBackoffTest extends AbstractBaseTestCase {

	@Mock
	private Random randomMock;

	@Test
	public void shouldApplyRandomUniformDistributionWithDefaultRange() throws Exception {
		//given
		final Backoff backoff = new FixedIntervalBackoff().withUniformJitter();

		//when
		final long delay = backoff.delayMillis(anyRetry());

		//then
		assertThat(delay).
				isGreaterThanOrEqualTo(DEFAULT_PERIOD_MILLIS - DEFAULT_RANDOM_RANGE_MILLIS).
				isLessThanOrEqualTo(DEFAULT_PERIOD_MILLIS + DEFAULT_RANDOM_RANGE_MILLIS);
	}

	@Test
	public void shouldApplyRandomUniformDistribution() throws Exception {
		//given
		final int range = 300;
		final Backoff backoff = new FixedIntervalBackoff().withUniformJitter(range);

		//when
		final long delay = backoff.delayMillis(anyRetry());

		//then
		assertThat(delay).
				isGreaterThanOrEqualTo(DEFAULT_PERIOD_MILLIS - range).
				isLessThanOrEqualTo(DEFAULT_PERIOD_MILLIS + range);
	}

	@Test
	public void shouldApplyRandomUniformDistributionWithCustomRandomSource() throws Exception {
		//given
		final Backoff backoff = new UniformRandomBackoff(new FixedIntervalBackoff(), randomMock);
		given(randomMock.nextDouble()).willReturn(0.5);

		//when
		final long delay = backoff.delayMillis(anyRetry());

		//then
		assertThat(delay).isEqualTo(DEFAULT_PERIOD_MILLIS);
	}

	@Test
	public void shouldApplyRandomProportionalDistributionWithDefaultRange() throws Exception {
		//given
		final Backoff backoff = new FixedIntervalBackoff().withProportionalJitter();

		//when
		final long delay = backoff.delayMillis(anyRetry());

		//then
		assertThat(delay).
				isGreaterThanOrEqualTo((long) (DEFAULT_PERIOD_MILLIS * (1 - ProportionalRandomBackoff.DEFAULT_MULTIPLIER))).
				isLessThan((long) (DEFAULT_PERIOD_MILLIS * (1 + ProportionalRandomBackoff.DEFAULT_MULTIPLIER)));
	}

	@Test
	public void shouldApplyRandomProportionalDistribution() throws Exception {
		//given
		final double range = 0.3;
		final Backoff backoff = new FixedIntervalBackoff().withProportionalJitter(range);

		//when
		final long delay = backoff.delayMillis(anyRetry());

		//then
		assertThat(delay).
				isGreaterThanOrEqualTo((long) (DEFAULT_PERIOD_MILLIS * (1 - range))).
				isLessThan((long) (DEFAULT_PERIOD_MILLIS * (1 + range)));
	}

	@Test
	public void shouldApplyRandomProportionalDistributionWithCustomRandomSource() throws Exception {
		//given
		final Backoff backoff = new ProportionalRandomBackoff(new FixedIntervalBackoff(), randomMock);
		given(randomMock.nextDouble()).willReturn(0.5);

		//when
		final long delay = backoff.delayMillis(anyRetry());

		//then
		assertThat(delay).isEqualTo(DEFAULT_PERIOD_MILLIS);
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/policy/AbstractRetryPolicyTest.java
================================================
package com.nurkiewicz.asyncretry.policy;

import com.nurkiewicz.asyncretry.AbstractBaseTestCase;
import com.nurkiewicz.asyncretry.AsyncRetryContext;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/18/13, 11:27 PM
 */
public class AbstractRetryPolicyTest extends AbstractBaseTestCase {

	private static final int ANY_RETRY = 7;

	protected boolean shouldRetryOn(RetryPolicy policy, Throwable lastThrowable) {
		return policy.shouldContinue(new AsyncRetryContext(policy, ANY_RETRY, lastThrowable));
	}
}

class OptimisticLockException extends RuntimeException {}

================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBlackListTest.java
================================================
package com.nurkiewicz.asyncretry.policy;

import org.testng.annotations.Test;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketException;
import java.util.concurrent.TimeoutException;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/18/13, 11:25 PM
 */
public class RetryPolicyBlackListTest extends AbstractRetryPolicyTest {

	@Test
	public void shouldAbortOnSpecifiedException() throws Exception {
		final RetryPolicy policy = new RetryPolicy().abortOn(ConnectException.class);

		assertThat(shouldRetryOn(policy, new ConnectException())).isFalse();
	}

	@Test
	public void shouldRetryIfExceptionNotAborting() throws Exception {
		final RetryPolicy policy = new RetryPolicy().abortOn(ConnectException.class);

		assertThat(shouldRetryOn(policy, new Exception())).isTrue();
		assertThat(shouldRetryOn(policy, new RuntimeException())).isTrue();
		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
		assertThat(shouldRetryOn(policy, new SocketException())).isTrue();
		assertThat(shouldRetryOn(policy, new ClassCastException())).isTrue();
		assertThat(shouldRetryOn(policy, new NullPointerException())).isTrue();
		assertThat(shouldRetryOn(policy, new IllegalArgumentException())).isTrue();
		assertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();
		assertThat(shouldRetryOn(policy, new TimeoutException())).isTrue();
	}

	@Test
	public void shouldRetryIfErrorNotAborting() throws Exception {
		final RetryPolicy policy = new RetryPolicy().abortOn(ConnectException.class);

		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isTrue();
		assertThat(shouldRetryOn(policy, new StackOverflowError())).isTrue();
		assertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isTrue();
	}

	@Test
	public void shouldAbortIfBlackListedException() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				abortOn(NullPointerException.class);

		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
	}

	@Test
	public void shouldAbortOnSubclassesOfBlackListedException() throws Exception {
		final RetryPolicy policy = new RetryPolicy().abortOn(IOException.class);

		assertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();
		assertThat(shouldRetryOn(policy, new SocketException())).isFalse();
		assertThat(shouldRetryOn(policy, new ConnectException())).isFalse();
	}

	@Test
	public void shouldAbortOnAnyBlackListedExceptions() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				abortOn(NullPointerException.class).
				abortOn(OutOfMemoryError.class).
				abortOn(StackOverflowError.class);

		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();
	}

	@Test
	public void shouldAbortOnAnyBlackListedExceptionsInOneList() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				abortOn(NullPointerException.class, OutOfMemoryError.class, StackOverflowError.class);

		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();
	}

	@Test
	public void shouldAbortOnSubclassesOfAnyOfBlackListedExceptions() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				abortOn(IOException.class).
				abortOn(RuntimeException.class);

		assertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();
		assertThat(shouldRetryOn(policy, new ConnectException())).isFalse();
		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
	}

	@Test
	public void shouldAbortOnSubclassesOfAnyOfBlackListedExceptionsInOneList() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				abortOn(IOException.class, RuntimeException.class);

		assertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();
		assertThat(shouldRetryOn(policy, new ConnectException())).isFalse();
		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBothBlackAndWhiteTest.java
================================================
package com.nurkiewicz.asyncretry.policy;

import org.testng.annotations.Test;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketException;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/18/13, 11:25 PM
 */
public class RetryPolicyBothBlackAndWhiteTest extends AbstractRetryPolicyTest {

	@Test
	public void shouldRetryOnGivenException() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(IOException.class).
				abortOn(NullPointerException.class);

		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
		assertThat(shouldRetryOn(policy, new ConnectException())).isTrue();
	}

	@Test
	public void shouldAbortOnGivenException() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				abortOn(IOException.class).
				retryOn(NullPointerException.class);

		assertThat(shouldRetryOn(policy, new NullPointerException())).isTrue();
		assertThat(shouldRetryOn(policy, new IOException())).isFalse();
		assertThat(shouldRetryOn(policy, new ConnectException())).isFalse();
	}

	@Test
	public void shouldRetryUnlessGivenSubclass() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(IOException.class).
				abortOn(FileNotFoundException.class);

		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
		assertThat(shouldRetryOn(policy, new SocketException())).isTrue();
		assertThat(shouldRetryOn(policy, new ConnectException())).isTrue();
		assertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();
	}

	@Test
	public void shouldRetryUnlessGivenSubclassWithReversedDeclarationOrder() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				abortOn(FileNotFoundException.class).
				retryOn(IOException.class);

		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
		assertThat(shouldRetryOn(policy, new SocketException())).isTrue();
		assertThat(shouldRetryOn(policy, new ConnectException())).isTrue();
		assertThat(shouldRetryOn(policy, new FileNotFoundException())).isFalse();
	}

	@Test
	public void shouldUnderstandManyWhiteAndBlackListedExceptions() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(Exception.class).
				retryOn(LinkageError.class).
				abortOn(IncompatibleClassChangeError.class).
				abortOn(ClassCastException.class).
				abortOn(ConnectException.class);

		assertThat(shouldRetryOn(policy, new Exception())).isTrue();
		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
		assertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();
		assertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isTrue();
		assertThat(shouldRetryOn(policy, new UnsupportedClassVersionError())).isTrue();

		assertThat(shouldRetryOn(policy, new NoSuchFieldError())).isFalse();
		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();
		assertThat(shouldRetryOn(policy, new ConnectException())).isFalse();
	}

	@Test
	public void shouldUnderstandManyWhiteAndBlackListedExceptionsInOneList() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(Exception.class, LinkageError.class).
				abortOn(IncompatibleClassChangeError.class, ClassCastException.class, ConnectException.class);

		assertThat(shouldRetryOn(policy, new Exception())).isTrue();
		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
		assertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();
		assertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isTrue();
		assertThat(shouldRetryOn(policy, new UnsupportedClassVersionError())).isTrue();

		assertThat(shouldRetryOn(policy, new NoSuchFieldError())).isFalse();
		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();
		assertThat(shouldRetryOn(policy, new ConnectException())).isFalse();
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyDefaultsTest.java
================================================
package com.nurkiewicz.asyncretry.policy;

import org.testng.annotations.Test;

import java.io.IOException;
import java.net.SocketException;
import java.util.concurrent.TimeoutException;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/18/13, 10:56 PM
 */
public class RetryPolicyDefaultsTest extends AbstractRetryPolicyTest {

	@Test
	public void byDefaultShouldRetryOnAllExceptions() throws Exception {
		assertThat(shouldRetryOn(new RetryPolicy(), new Exception())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new RuntimeException())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new IOException())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new ClassCastException())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new NullPointerException())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new IllegalArgumentException())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new IllegalStateException())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new TimeoutException())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new SocketException())).isTrue();
	}

	@Test
	public void byDefaultShouldRetryOnAllThrowables() throws Exception {
		assertThat(shouldRetryOn(new RetryPolicy(), new OutOfMemoryError())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new StackOverflowError())).isTrue();
		assertThat(shouldRetryOn(new RetryPolicy(), new NoClassDefFoundError())).isTrue();
	}
}



================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyMaxRetriesTest.java
================================================
package com.nurkiewicz.asyncretry.policy;

import com.nurkiewicz.asyncretry.AbstractBaseTestCase;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 7:16 PM
 */
public class RetryPolicyMaxRetriesTest extends AbstractBaseTestCase {

	@Test
	public void shouldStopAfterConfiguredNumberOfRetries() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().withMaxRetries(7);

		//when
		final boolean firstRetry = retryPolicy.shouldContinue(retry(1));
		final boolean lastRetry = retryPolicy.shouldContinue(retry(7));
		final boolean tooManyRetries = retryPolicy.shouldContinue(retry(8));

		//then
		assertThat(firstRetry).isTrue();
		assertThat(lastRetry).isTrue();
		assertThat(tooManyRetries).isFalse();
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyPredicatesTest.java
================================================
package com.nurkiewicz.asyncretry.policy;

import com.nurkiewicz.asyncretry.AsyncRetryContext;
import com.nurkiewicz.asyncretry.RetryContext;
import org.mockito.Mock;
import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/20/13, 5:17 PM
 */
public class RetryPolicyPredicatesTest extends AbstractRetryPolicyTest {

	@Mock
	private RetryContext retryContextMock;

	@Test
	public void shouldAbortIfAbortPredicateTrue() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> true);

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isFalse();
	}

	@Test
	public void shouldRetryIfRetryPredicateTrue() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().retryIf(t -> true);

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isTrue();
	}

	@Test
	public void shouldRetryIfBothPredicatesAbstainButClassShouldRetry() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().
				retryIf(t -> false).
				abortIf(t -> false);
		given(retryContextMock.getLastThrowable()).willReturn(new RuntimeException());

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isTrue();
	}

	@Test
	public void shouldAbortIfBothPredicatesAbstainButClassShouldAbort() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().
				abortOn(NullPointerException.class).
				retryIf(t -> false).
				abortIf(t -> false);
		given(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isFalse();
	}

	@Test
	public void shouldRetryIfPredicateTrueEvenIfClassShouldAbort() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().
				abortOn(NullPointerException.class).
				retryIf(t -> true);
		given(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isTrue();
	}

	@Test
	public void shouldAbortIfPredicateTrueEvenIfClassShouldRetry() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().
				retryOn(NullPointerException.class).
				abortIf(t -> true);
		given(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isFalse();
	}

	@Test
	public void whenAbortAndRetryPredicatesBothYieldTrueThenAbortWins() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().
				retryOn(NullPointerException.class).
				retryIf(t -> t.getMessage().contains("Foo")).
				abortIf(t -> t.getMessage().contains("Foo"));
		given(retryContextMock.getLastThrowable()).willReturn(new NullPointerException("Foo"));

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isFalse();
	}

	@Test
	public void shouldProceedIfPredicateFalseAndChildAccepts() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> false);
		given(retryContextMock.getLastThrowable()).willReturn(new RuntimeException());

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isTrue();
	}

	@Test
	public void shouldAbortIfPredicateFalseButShouldNotRetry() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> false).dontRetry();

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isFalse();
	}

	@Test
	public void shouldAbortIfPredicateTrueButShouldNotRetry() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().
				retryIf(t -> true).
				dontRetry();
		given(retryContextMock.getLastThrowable()).willReturn(new NullPointerException());
		given(retryContextMock.getRetryCount()).willReturn(1);

		//when
		final boolean shouldRetry = retryPolicy.shouldContinue(retryContextMock);

		//then
		assertThat(shouldRetry).isFalse();
	}

	@Test
	public void shouldExamineExceptionAndDecide() throws Exception {
		//given
		final RetryPolicy retryPolicy = new RetryPolicy().abortIf(t -> t.getMessage().contains("abort"));

		//when
		final boolean abort = retryPolicy.shouldContinue(new AsyncRetryContext(retryPolicy, 1, new RuntimeException("abort")));
		final boolean retry = retryPolicy.shouldContinue(new AsyncRetryContext(retryPolicy, 1, new RuntimeException("normal")));

		//then
		assertThat(abort).isFalse();
		assertThat(retry).isTrue();
	}

}


================================================
FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyWhiteListTest.java
================================================
package com.nurkiewicz.asyncretry.policy;

import org.testng.annotations.Test;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketException;
import java.util.concurrent.TimeoutException;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Tomasz Nurkiewicz
 * @since 7/18/13, 11:25 PM
 */
public class RetryPolicyWhiteListTest extends AbstractRetryPolicyTest {

	@Test
	public void retryOnExceptionExplicitly() throws Exception {
		final RetryPolicy policy = new RetryPolicy().retryOn(Exception.class);

		assertThat(shouldRetryOn(policy, new Exception())).isTrue();
		assertThat(shouldRetryOn(policy, new RuntimeException())).isTrue();
		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
		assertThat(shouldRetryOn(policy, new ClassCastException())).isTrue();
		assertThat(shouldRetryOn(policy, new NullPointerException())).isTrue();
		assertThat(shouldRetryOn(policy, new IllegalArgumentException())).isTrue();
		assertThat(shouldRetryOn(policy, new IllegalStateException())).isTrue();
		assertThat(shouldRetryOn(policy, new TimeoutException())).isTrue();
		assertThat(shouldRetryOn(policy, new SocketException())).isTrue();
	}

	@Test
	public void retryOnExceptionShouldNotRetryOnError() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(Exception.class);

		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();
		assertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();
	}

	@Test
	public void shouldRetryOnOnlyOneSpecificException() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class);

		assertThat(shouldRetryOn(policy, new OptimisticLockException())).isTrue();
	}

	@Test
	public void shouldNotRetryOnOtherExceptionsIfOneGivenExplicitly() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class);

		assertThat(shouldRetryOn(policy, new Exception())).isFalse();
		assertThat(shouldRetryOn(policy, new RuntimeException())).isFalse();
		assertThat(shouldRetryOn(policy, new IOException())).isFalse();
		assertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();
		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
		assertThat(shouldRetryOn(policy, new IllegalArgumentException())).isFalse();
		assertThat(shouldRetryOn(policy, new IllegalStateException())).isFalse();
		assertThat(shouldRetryOn(policy, new TimeoutException())).isFalse();
		assertThat(shouldRetryOn(policy, new SocketException())).isFalse();
	}

	@Test
	public void shouldNotRetryOnErrorsIfExceptionGivenExplicitly() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class);

		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();
		assertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();
	}

	@Test
	public void shouldRetryOnAnyOfProvidedExceptions() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class).
				retryOn(IOException.class);

		assertThat(shouldRetryOn(policy, new OptimisticLockException())).isTrue();
		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
	}

	@Test
	public void shouldRetryOnAnyOfProvidedExceptionsInOneList() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class, IOException.class);

		assertThat(shouldRetryOn(policy, new OptimisticLockException())).isTrue();
		assertThat(shouldRetryOn(policy, new IOException())).isTrue();
	}

	@Test
	public void shouldNotRetryOnOtherExceptionsIfFewGivenExplicitly() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class).
				retryOn(IOException.class);

		assertThat(shouldRetryOn(policy, new Exception())).isFalse();
		assertThat(shouldRetryOn(policy, new RuntimeException())).isFalse();
		assertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();
		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
		assertThat(shouldRetryOn(policy, new IllegalArgumentException())).isFalse();
		assertThat(shouldRetryOn(policy, new IllegalStateException())).isFalse();
		assertThat(shouldRetryOn(policy, new TimeoutException())).isFalse();
	}

	@Test
	public void shouldNotRetryOnOtherExceptionsIfFewGivenExplicitlyInOneList() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class, IOException.class);

		assertThat(shouldRetryOn(policy, new Exception())).isFalse();
		assertThat(shouldRetryOn(policy, new RuntimeException())).isFalse();
		assertThat(shouldRetryOn(policy, new ClassCastException())).isFalse();
		assertThat(shouldRetryOn(policy, new NullPointerException())).isFalse();
		assertThat(shouldRetryOn(policy, new IllegalArgumentException())).isFalse();
		assertThat(shouldRetryOn(policy, new IllegalStateException())).isFalse();
		assertThat(shouldRetryOn(policy, new TimeoutException())).isFalse();
	}

	@Test
	public void shouldNotRetryOnErrorsIfFewExceptionsGivenExplicitly() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class).
				retryOn(IOException.class);

		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();
		assertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();
	}

	@Test
	public void shouldNotRetryOnErrorsIfFewExceptionsGivenExplicitlyInOneList() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(OptimisticLockException.class, IOException.class);

		assertThat(shouldRetryOn(policy, new OutOfMemoryError())).isFalse();
		assertThat(shouldRetryOn(policy, new StackOverflowError())).isFalse();
		assertThat(shouldRetryOn(policy, new NoClassDefFoundError())).isFalse();
	}

	@Test
	public void shouldRetryWhenSubclassOfGivenExceptionThrown() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(IOException.class);

		assertThat(shouldRetryOn(policy, new FileNotFoundException())).isTrue();
		assertThat(shouldRetryOn(policy, new SocketException())).isTrue();
		assertThat(shouldRetryOn(policy, new ConnectException())).isTrue();
	}

	@Test
	public void shouldNotRetryOnSiblilngExceptions() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(FileNotFoundException.class);

		assertThat(shouldRetryOn(policy, new SocketException())).isFalse();
	}

	@Test
	public void shouldNotRetryOnSuperClassesOfGivenClass() throws Exception {
		final RetryPolicy policy = new RetryPolicy().
				retryOn(FileNotFoundException.class);

		assertThat(shouldRetryOn(policy, new IOException())).isFalse();
	}

}


================================================
FILE: src/test/resources/logback-test.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
	<root level="ALL">
		<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
			<encoder>
				<pattern>%d{HH:mm:ss.SSS} | %-5level | %thread | %logger{1} | %m%n%rEx</pattern>
			</encoder>
		</appender>
	</root>
</configuration>
Download .txt
gitextract_azfu50lp/

├── .gitignore
├── .travis.yml
├── README.md
├── license.txt
├── pom.xml
└── src/
    ├── main/
    │   └── java/
    │       └── com/
    │           └── nurkiewicz/
    │               └── asyncretry/
    │                   ├── AsyncRetryContext.java
    │                   ├── AsyncRetryExecutor.java
    │                   ├── AsyncRetryJob.java
    │                   ├── RetryContext.java
    │                   ├── RetryExecutor.java
    │                   ├── RetryJob.java
    │                   ├── SyncRetryExecutor.java
    │                   ├── SyncRetryJob.java
    │                   ├── backoff/
    │                   │   ├── Backoff.java
    │                   │   ├── BackoffWrapper.java
    │                   │   ├── BoundedMaxBackoff.java
    │                   │   ├── BoundedMinBackoff.java
    │                   │   ├── ExponentialDelayBackoff.java
    │                   │   ├── FirstRetryNoDelayBackoff.java
    │                   │   ├── FixedIntervalBackoff.java
    │                   │   ├── ProportionalRandomBackoff.java
    │                   │   ├── RandomBackoff.java
    │                   │   └── UniformRandomBackoff.java
    │                   ├── function/
    │                   │   ├── RetryCallable.java
    │                   │   └── RetryRunnable.java
    │                   └── policy/
    │                       ├── AbortRetryException.java
    │                       └── RetryPolicy.java
    └── test/
        ├── java/
        │   └── com/
        │       └── nurkiewicz/
        │           └── asyncretry/
        │               ├── AbstractBaseTestCase.java
        │               ├── AsyncRetryContextTest.java
        │               ├── AsyncRetryExecutorHappyTest.java
        │               ├── AsyncRetryExecutorManualAbortTest.java
        │               ├── AsyncRetryExecutorManyFailuresTest.java
        │               ├── AsyncRetryExecutorOneFailureTest.java
        │               ├── AsyncRetryJobTest.java
        │               ├── FaultyService.java
        │               ├── SyncRetryExecutorTest.java
        │               ├── backoff/
        │               │   ├── BoundedMaxBackoffTest.java
        │               │   ├── BoundedMinBackoffTest.java
        │               │   ├── ExponentialDelayBackoffTest.java
        │               │   ├── FirstRetryNoDelayBackoffTest.java
        │               │   └── RandomBackoffTest.java
        │               └── policy/
        │                   ├── AbstractRetryPolicyTest.java
        │                   ├── RetryPolicyBlackListTest.java
        │                   ├── RetryPolicyBothBlackAndWhiteTest.java
        │                   ├── RetryPolicyDefaultsTest.java
        │                   ├── RetryPolicyMaxRetriesTest.java
        │                   ├── RetryPolicyPredicatesTest.java
        │                   └── RetryPolicyWhiteListTest.java
        └── resources/
            └── logback-test.xml
Download .txt
SYMBOL INDEX (296 symbols across 43 files)

FILE: src/main/java/com/nurkiewicz/asyncretry/AsyncRetryContext.java
  class AsyncRetryContext (line 7) | public class AsyncRetryContext implements RetryContext {
    method AsyncRetryContext (line 13) | public AsyncRetryContext(RetryPolicy retryPolicy) {
    method AsyncRetryContext (line 17) | public AsyncRetryContext(RetryPolicy retryPolicy, int retry, Throwable...
    method willRetry (line 23) | @Override
    method getRetryCount (line 28) | @Override
    method getLastThrowable (line 33) | @Override
    method nextRetry (line 38) | public AsyncRetryContext nextRetry(Throwable cause) {
    method prevRetry (line 42) | public AsyncRetryContext prevRetry() {

FILE: src/main/java/com/nurkiewicz/asyncretry/AsyncRetryExecutor.java
  class AsyncRetryExecutor (line 22) | public class AsyncRetryExecutor implements RetryExecutor {
    method AsyncRetryExecutor (line 29) | public AsyncRetryExecutor(ScheduledExecutorService scheduler) {
    method AsyncRetryExecutor (line 33) | public AsyncRetryExecutor(ScheduledExecutorService scheduler, Backoff ...
    method AsyncRetryExecutor (line 37) | public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPol...
    method AsyncRetryExecutor (line 41) | public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPol...
    method AsyncRetryExecutor (line 45) | public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPol...
    method doWithRetry (line 52) | @Override
    method getWithRetry (line 60) | @Override
    method getWithRetry (line 65) | @Override
    method getFutureWithRetry (line 70) | @Override
    method scheduleImmediately (line 75) | private <V> CompletableFuture<V> scheduleImmediately(RetryJob<V> job) {
    method createTask (line 80) | protected <V> RetryJob<V> createTask(RetryCallable<V> function) {
    method createFutureTask (line 84) | protected <V> RetryJob<V> createFutureTask(RetryCallable<CompletableFu...
    method getScheduler (line 88) | public ScheduledExecutorService getScheduler() {
    method isFixedDelay (line 92) | public boolean isFixedDelay() {
    method getRetryPolicy (line 96) | public RetryPolicy getRetryPolicy() {
    method getBackoff (line 100) | public Backoff getBackoff() {
    method withScheduler (line 104) | public AsyncRetryExecutor withScheduler(ScheduledExecutorService sched...
    method withRetryPolicy (line 108) | public AsyncRetryExecutor withRetryPolicy(RetryPolicy retryPolicy) {
    method withExponentialBackoff (line 112) | public AsyncRetryExecutor withExponentialBackoff(long initialDelayMill...
    method withFixedBackoff (line 117) | public AsyncRetryExecutor withFixedBackoff(long delayMillis) {
    method withBackoff (line 122) | public AsyncRetryExecutor withBackoff(Backoff backoff) {
    method withFixedRate (line 126) | public AsyncRetryExecutor withFixedRate() {
    method withFixedRate (line 130) | public AsyncRetryExecutor withFixedRate(boolean fixedDelay) {
    method retryOn (line 134) | @SafeVarargs
    method abortOn (line 139) | @SafeVarargs
    method retryIf (line 144) | public AsyncRetryExecutor retryIf(Predicate<Throwable> retryPredicate) {
    method abortIf (line 148) | public AsyncRetryExecutor abortIf(Predicate<Throwable> abortPredicate) {
    method withUniformJitter (line 152) | public AsyncRetryExecutor withUniformJitter() {
    method withUniformJitter (line 156) | public AsyncRetryExecutor withUniformJitter(long range) {
    method withProportionalJitter (line 160) | public AsyncRetryExecutor withProportionalJitter() {
    method withProportionalJitter (line 164) | public AsyncRetryExecutor withProportionalJitter(double multiplier) {
    method withMinDelay (line 168) | public AsyncRetryExecutor withMinDelay(long minDelayMillis) {
    method withMaxDelay (line 172) | public AsyncRetryExecutor withMaxDelay(long maxDelayMillis) {
    method withMaxRetries (line 176) | public AsyncRetryExecutor withMaxRetries(int times) {
    method dontRetry (line 180) | public AsyncRetryExecutor dontRetry() {
    method retryInfinitely (line 184) | public AsyncRetryExecutor retryInfinitely() {
    method withNoDelay (line 188) | public AsyncRetryExecutor withNoDelay() {
    method firstRetryNoDelay (line 192) | public AsyncRetryExecutor firstRetryNoDelay() {

FILE: src/main/java/com/nurkiewicz/asyncretry/AsyncRetryJob.java
  class AsyncRetryJob (line 11) | public class AsyncRetryJob<V> extends RetryJob<V> {
    method AsyncRetryJob (line 15) | public AsyncRetryJob(RetryCallable<CompletableFuture<V>> userTask, Asy...
    method AsyncRetryJob (line 19) | public AsyncRetryJob(RetryCallable<CompletableFuture<V>> userTask, Asy...
    method run (line 24) | @Override
    method nextTask (line 41) | @Override

FILE: src/main/java/com/nurkiewicz/asyncretry/RetryContext.java
  type RetryContext (line 7) | public interface RetryContext {
    method willRetry (line 8) | boolean willRetry();
    method getRetryCount (line 14) | int getRetryCount();
    method getLastThrowable (line 16) | Throwable getLastThrowable();
    method isFirstRetry (line 18) | default boolean isFirstRetry() {

FILE: src/main/java/com/nurkiewicz/asyncretry/RetryExecutor.java
  type RetryExecutor (line 13) | public interface RetryExecutor {
    method doWithRetry (line 15) | CompletableFuture<Void> doWithRetry(RetryRunnable action);
    method getWithRetry (line 17) | <V> CompletableFuture<V> getWithRetry(Callable<V> task);
    method getWithRetry (line 19) | <V> CompletableFuture<V> getWithRetry(RetryCallable<V> task);
    method getFutureWithRetry (line 21) | <V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableF...

FILE: src/main/java/com/nurkiewicz/asyncretry/RetryJob.java
  class RetryJob (line 17) | public abstract class RetryJob<V> implements Runnable {
    method RetryJob (line 23) | public RetryJob(AsyncRetryContext context, AsyncRetryExecutor parent, ...
    method logSuccess (line 29) | protected void logSuccess(RetryContext context, V result, long duratio...
    method handleManualAbort (line 33) | protected void handleManualAbort(AbortRetryException abortEx) {
    method logAbort (line 42) | protected void logAbort(RetryContext context) {
    method handleThrowable (line 46) | protected void handleThrowable(Throwable t, long duration) {
    method handleUserThrowable (line 54) | protected void handleUserThrowable(Throwable t, long duration) {
    method retryOrAbort (line 68) | private void retryOrAbort(Throwable t, long duration, AsyncRetryContex...
    method logFailure (line 78) | protected void logFailure(AsyncRetryContext nextRetryContext, long dur...
    method calculateNextDelay (line 85) | private long calculateNextDelay(long taskDurationMillis, AsyncRetryCon...
    method retryWithDelay (line 90) | private void retryWithDelay(AsyncRetryContext nextRetryContext, long d...
    method logRetry (line 96) | protected void logRetry(AsyncRetryContext nextRetryContext, long delay...
    method run (line 106) | @Override
    method run (line 111) | protected abstract void run(long startTime);
    method nextTask (line 113) | protected abstract RetryJob<V> nextTask(AsyncRetryContext nextRetryCon...
    method complete (line 115) | protected void complete(V result, long duration) {
    method getFuture (line 120) | public CompletableFuture<V> getFuture() {

FILE: src/main/java/com/nurkiewicz/asyncretry/SyncRetryExecutor.java
  type SyncRetryExecutor (line 17) | public enum SyncRetryExecutor implements RetryExecutor {
    method doWithRetry (line 23) | @Override
    method getWithRetry (line 33) | @Override
    method getWithRetry (line 42) | @Override
    method getFutureWithRetry (line 51) | @Override
    method failedFuture (line 60) | private static <T> CompletableFuture<T> failedFuture(Exception e) {

FILE: src/main/java/com/nurkiewicz/asyncretry/SyncRetryJob.java
  class SyncRetryJob (line 7) | class SyncRetryJob<V> extends RetryJob<V> {
    method SyncRetryJob (line 11) | public SyncRetryJob(RetryCallable<V> userTask, AsyncRetryExecutor pare...
    method SyncRetryJob (line 15) | public SyncRetryJob(RetryCallable<V> userTask, AsyncRetryExecutor pare...
    method run (line 20) | @Override
    method nextTask (line 30) | protected RetryJob<V> nextTask(AsyncRetryContext nextRetryContext) {

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/Backoff.java
  type Backoff (line 9) | public interface Backoff {
    method delayMillis (line 13) | long delayMillis(RetryContext context);
    method withUniformJitter (line 15) | default Backoff withUniformJitter() {
    method withUniformJitter (line 19) | default Backoff withUniformJitter(long range) {
    method withProportionalJitter (line 23) | default Backoff withProportionalJitter() {
    method withProportionalJitter (line 27) | default Backoff withProportionalJitter(double multiplier) {
    method withMinDelay (line 31) | default Backoff withMinDelay(long minDelayMillis) {
    method withMinDelay (line 35) | default Backoff withMinDelay() {
    method withMaxDelay (line 39) | default Backoff withMaxDelay(long maxDelayMillis) {
    method withMaxDelay (line 43) | default Backoff withMaxDelay() {
    method withFirstRetryNoDelay (line 47) | default Backoff withFirstRetryNoDelay() {

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/BackoffWrapper.java
  class BackoffWrapper (line 9) | public abstract class BackoffWrapper implements Backoff {
    method BackoffWrapper (line 13) | public BackoffWrapper(Backoff target) {

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoff.java
  class BoundedMaxBackoff (line 9) | public class BoundedMaxBackoff extends BackoffWrapper {
    method BoundedMaxBackoff (line 15) | public BoundedMaxBackoff(Backoff target) {
    method BoundedMaxBackoff (line 19) | public BoundedMaxBackoff(Backoff target, long maxDelayMillis) {
    method delayMillis (line 24) | @Override

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoff.java
  class BoundedMinBackoff (line 9) | public class BoundedMinBackoff extends BackoffWrapper {
    method BoundedMinBackoff (line 15) | public BoundedMinBackoff(Backoff target) {
    method BoundedMinBackoff (line 19) | public BoundedMinBackoff(Backoff target, long minDelayMillis) {
    method delayMillis (line 24) | @Override

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoff.java
  class ExponentialDelayBackoff (line 9) | public class ExponentialDelayBackoff implements Backoff {
    method ExponentialDelayBackoff (line 14) | public ExponentialDelayBackoff(long initialDelayMillis, double multipl...
    method delayMillis (line 22) | @Override

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoff.java
  class FirstRetryNoDelayBackoff (line 6) | public class FirstRetryNoDelayBackoff extends BackoffWrapper {
    method FirstRetryNoDelayBackoff (line 8) | public FirstRetryNoDelayBackoff(Backoff target) {
    method delayMillis (line 12) | @Override
    method decrementRetryCount (line 21) | private RetryContext decrementRetryCount(RetryContext context) {

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/FixedIntervalBackoff.java
  class FixedIntervalBackoff (line 9) | public class FixedIntervalBackoff implements Backoff {
    method FixedIntervalBackoff (line 15) | public FixedIntervalBackoff() {
    method FixedIntervalBackoff (line 19) | public FixedIntervalBackoff(long intervalMillis) {
    method delayMillis (line 23) | @Override

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/ProportionalRandomBackoff.java
  class ProportionalRandomBackoff (line 9) | public class ProportionalRandomBackoff extends RandomBackoff {
    method ProportionalRandomBackoff (line 18) | public ProportionalRandomBackoff(Backoff target) {
    method ProportionalRandomBackoff (line 22) | public ProportionalRandomBackoff(Backoff target, Random random) {
    method ProportionalRandomBackoff (line 26) | public ProportionalRandomBackoff(Backoff target, double multiplier) {
    method ProportionalRandomBackoff (line 31) | public ProportionalRandomBackoff(Backoff target, double multiplier, Ra...
    method addRandomJitter (line 36) | @Override

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/RandomBackoff.java
  class RandomBackoff (line 13) | abstract public class RandomBackoff extends BackoffWrapper {
    method RandomBackoff (line 17) | protected RandomBackoff(Backoff target) {
    method RandomBackoff (line 21) | protected RandomBackoff(Backoff target, Random randomSource) {
    method RandomBackoff (line 25) | private RandomBackoff(Backoff target, Supplier<Random> randomSource) {
    method delayMillis (line 30) | @Override
    method addRandomJitter (line 37) | abstract long addRandomJitter(long initialDelay);
    method random (line 39) | protected Random random() {

FILE: src/main/java/com/nurkiewicz/asyncretry/backoff/UniformRandomBackoff.java
  class UniformRandomBackoff (line 9) | public class UniformRandomBackoff extends RandomBackoff {
    method UniformRandomBackoff (line 18) | public UniformRandomBackoff(Backoff target) {
    method UniformRandomBackoff (line 22) | public UniformRandomBackoff(Backoff target, Random random) {
    method UniformRandomBackoff (line 26) | public UniformRandomBackoff(Backoff target, final long range) {
    method UniformRandomBackoff (line 31) | public UniformRandomBackoff(Backoff target, final long range, Random r...
    method addRandomJitter (line 36) | @Override

FILE: src/main/java/com/nurkiewicz/asyncretry/function/RetryCallable.java
  type RetryCallable (line 9) | @FunctionalInterface
    method call (line 12) | V call(RetryContext context) throws Exception;

FILE: src/main/java/com/nurkiewicz/asyncretry/function/RetryRunnable.java
  type RetryRunnable (line 9) | @FunctionalInterface
    method run (line 12) | void run(RetryContext context) throws Exception;

FILE: src/main/java/com/nurkiewicz/asyncretry/policy/AbortRetryException.java
  class AbortRetryException (line 7) | public class AbortRetryException extends RuntimeException {
    method AbortRetryException (line 9) | public AbortRetryException() {

FILE: src/main/java/com/nurkiewicz/asyncretry/policy/RetryPolicy.java
  class RetryPolicy (line 15) | public class RetryPolicy {
    method retryOn (line 25) | public RetryPolicy retryOn(Class<? extends Throwable>... retryOnThrowa...
    method abortOn (line 29) | public RetryPolicy abortOn(Class<? extends Throwable>... abortOnThrowa...
    method abortIf (line 33) | public RetryPolicy abortIf(Predicate<Throwable> abortPredicate) {
    method retryIf (line 37) | public RetryPolicy retryIf(Predicate<Throwable> retryPredicate) {
    method dontRetry (line 41) | public RetryPolicy dontRetry() {
    method withMaxRetries (line 45) | public RetryPolicy withMaxRetries(int times) {
    method RetryPolicy (line 49) | public RetryPolicy(int maxRetries, Set<Class<? extends Throwable>> ret...
    method RetryPolicy (line 57) | public RetryPolicy() {
    method shouldContinue (line 61) | public boolean shouldContinue(RetryContext context) {
    method tooManyRetries (line 74) | private boolean tooManyRetries(RetryContext context) {
    method exceptionClassRetryable (line 78) | private boolean exceptionClassRetryable(RetryContext context) {
    method matches (line 90) | private static boolean matches(Class<? extends Throwable> throwable, S...
    method setPlusElems (line 94) | private static <T> Set<T> setPlusElems(Set<T> initial, T... newElement) {

FILE: src/test/java/com/nurkiewicz/asyncretry/AbstractBaseTestCase.java
  class AbstractBaseTestCase (line 20) | public class AbstractBaseTestCase {
    method injectMocks (line 30) | @BeforeMethod(alwaysRun=true)
    method setupMocks (line 36) | private void setupMocks() {
    method notNullRunnable (line 43) | protected Runnable notNullRunnable() {
    method notNullRetryContext (line 47) | protected RetryContext notNullRetryContext() {
    method millis (line 51) | protected TimeUnit millis() {
    method anyRetry (line 55) | protected RetryContext anyRetry() {
    method retry (line 59) | protected RetryContext retry(int ret) {

FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryContextTest.java
  class AsyncRetryContextTest (line 16) | public class AsyncRetryContextTest extends AbstractBaseTestCase {
    method shouldNotRetryIfRetriesForbidden (line 18) | @Test
    method shouldSayItWillRetryIfUnlimitedNumberOfRetries (line 30) | @Test
    method shouldSayItWillRetryOnFirstFewCases (line 42) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorHappyTest.java
  class AsyncRetryExecutorHappyTest (line 17) | public class AsyncRetryExecutorHappyTest extends AbstractBaseTestCase {
    method shouldNotRetryIfCompletesAfterFirstExecution (line 19) | @Test
    method shouldCallUserTaskOnlyOnceIfItDoesntFail (line 32) | @Test
    method shouldReturnResultOfFirstSuccessfulCall (line 44) | @Test
    method shouldReturnEvenIfNoRetryPolicy (line 57) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManualAbortTest.java
  class AsyncRetryExecutorManualAbortTest (line 23) | public class AsyncRetryExecutorManualAbortTest extends AbstractBaseTestC...
    method shouldRethrowIfFirstExecutionThrowsAnExceptionAndNoRetry (line 25) | @Test
    method shouldRetryAfterOneExceptionAndReturnValue (line 47) | @Test
    method shouldSucceedWhenOnlyOneRetryAllowed (line 62) | @Test
    method shouldRetryOnceIfFirstExecutionThrowsException (line 77) | @Test
    method shouldScheduleRetryWithDefaultDelay (line 92) | @Test
    method shouldPassCorrectRetryCountToEachInvocationInContext (line 110) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManyFailuresTest.java
  class AsyncRetryExecutorManyFailuresTest (line 23) | public class AsyncRetryExecutorManyFailuresTest extends AbstractBaseTest...
    method shouldRethrowIfFirstFewExecutionsThrow (line 25) | @Test
    method shouldRetryAfterManyExceptionsAndReturnValue (line 46) | @Test
    method shouldSucceedWhenTheSameNumberOfRetriesAsFailuresAllowed (line 61) | @Test
    method shouldRetryManyTimesIfFirstExecutionsThrowException (line 76) | @Test
    method shouldScheduleRetryWithDefaultDelay (line 91) | @Test
    method shouldPassCorrectRetryCountToEachInvocationInContext (line 109) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorOneFailureTest.java
  class AsyncRetryExecutorOneFailureTest (line 19) | public class AsyncRetryExecutorOneFailureTest extends AbstractBaseTestCa...
    method shouldNotRetryIfAbortThrown (line 21) | @Test
    method shouldRethrowAbortExceptionIfFirstIterationThrownIt (line 35) | @Test
    method shouldCompleteWithExceptionIfFirstIterationThrownIt (line 55) | @Test
    method shouldRethrowLastThrownExceptionWhenAbortedInSubsequentIteration (line 78) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/AsyncRetryJobTest.java
  class AsyncRetryJobTest (line 25) | public class AsyncRetryJobTest extends AbstractBaseTestCase {
    method shouldUnwrapUserFutureAndReturnIt (line 27) | @Test
    method shouldSucceedAfterFewAsynchronousRetries (line 40) | @Test
    method shouldScheduleTwoTimesWhenRetries (line 57) | @Test
    method failedAsync (line 78) | private CompletableFuture<String> failedAsync(Throwable throwable) {
    method shouldRethrowOriginalExceptionFromUserFutureCompletion (line 84) | @Test
    method shouldRethrowOriginalExceptionFromUserFutureCompletionAndAbortWhenTestFails (line 109) | @Test
    method shouldAbortWhenTargetFutureWantsToAbort (line 135) | @Test
    method shouldRethrowExceptionThatWasThrownFromUserTaskBeforeReturningFuture (line 158) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/FaultyService.java
  type FaultyService (line 10) | public interface FaultyService {
    method alwaysSucceeds (line 12) | int alwaysSucceeds();
    method sometimesFails (line 14) | String sometimesFails();
    method calculateSum (line 16) | BigDecimal calculateSum(int retry);
    method withFlag (line 18) | void withFlag(boolean flag);
    method safeAsync (line 20) | CompletableFuture<String> safeAsync();
    method alwaysFailsAsync (line 22) | CompletableFuture<String> alwaysFailsAsync();

FILE: src/test/java/com/nurkiewicz/asyncretry/SyncRetryExecutorTest.java
  class SyncRetryExecutorTest (line 15) | public class SyncRetryExecutorTest extends AbstractBaseTestCase {
    method shouldReturnCompletedFutureWhenDoWithRetryCalled (line 19) | @Test
    method shouldWrapExceptionInFutureRatherThanThrowingIt (line 32) | @Test
    method shouldReturnCompletedFutureWhenGetWithRetryCalled (line 51) | @Test
    method shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetry (line 63) | @Test
    method shouldReturnCompletedFutureWhenGetWithRetryCalledContext (line 82) | @Test
    method shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetryContext (line 94) | @Test
    method shouldReturnCompletedFutureWhenGetWithRetryOnFutureCalled (line 113) | @Test
    method shouldWrapExceptionInFutureRatherThanThrowingItInGetWithRetryOnFuture (line 126) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoffTest.java
  class BoundedMaxBackoffTest (line 12) | public class BoundedMaxBackoffTest extends AbstractBaseTestCase {
    method shouldReturnOriginalBackoffDelayIfBelowMax (line 14) | @Test
    method shouldCapBackoffAtDefaultLevel (line 24) | @Test
    method shouldCapBackoffAtGivenLevel (line 31) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoffTest.java
  class BoundedMinBackoffTest (line 12) | public class BoundedMinBackoffTest extends AbstractBaseTestCase {
    method shouldReturnOriginalBackoffDelayIfAboveMin (line 14) | @Test
    method shouldCapBackoffAtDefaultLevel (line 24) | @Test
    method shouldCapBackoffAtGivenLevel (line 31) | @Test
    method shouldApplyBothMinAndMaxBound (line 38) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoffTest.java
  class ExponentialDelayBackoffTest (line 13) | public class ExponentialDelayBackoffTest extends AbstractBaseTestCase {
    method shouldThrowWhenNotPositiveInitialDelay (line 15) | @Test
    method shouldReturnPowersOfTwo (line 31) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoffTest.java
  class FirstRetryNoDelayBackoffTest (line 8) | public class FirstRetryNoDelayBackoffTest extends AbstractBaseTestCase {
    method firstRetryShouldHaveNoDelay (line 10) | @Test
    method secondRetryShouldCalculateDelayAsIfItWasFirst (line 26) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/backoff/RandomBackoffTest.java
  class RandomBackoffTest (line 18) | public class RandomBackoffTest extends AbstractBaseTestCase {
    method shouldApplyRandomUniformDistributionWithDefaultRange (line 23) | @Test
    method shouldApplyRandomUniformDistribution (line 37) | @Test
    method shouldApplyRandomUniformDistributionWithCustomRandomSource (line 52) | @Test
    method shouldApplyRandomProportionalDistributionWithDefaultRange (line 65) | @Test
    method shouldApplyRandomProportionalDistribution (line 79) | @Test
    method shouldApplyRandomProportionalDistributionWithCustomRandomSource (line 94) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/policy/AbstractRetryPolicyTest.java
  class AbstractRetryPolicyTest (line 10) | public class AbstractRetryPolicyTest extends AbstractBaseTestCase {
    method shouldRetryOn (line 14) | protected boolean shouldRetryOn(RetryPolicy policy, Throwable lastThro...
  class OptimisticLockException (line 19) | class OptimisticLockException extends RuntimeException {}

FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBlackListTest.java
  class RetryPolicyBlackListTest (line 17) | public class RetryPolicyBlackListTest extends AbstractRetryPolicyTest {
    method shouldAbortOnSpecifiedException (line 19) | @Test
    method shouldRetryIfExceptionNotAborting (line 26) | @Test
    method shouldRetryIfErrorNotAborting (line 41) | @Test
    method shouldAbortIfBlackListedException (line 50) | @Test
    method shouldAbortOnSubclassesOfBlackListedException (line 58) | @Test
    method shouldAbortOnAnyBlackListedExceptions (line 67) | @Test
    method shouldAbortOnAnyBlackListedExceptionsInOneList (line 79) | @Test
    method shouldAbortOnSubclassesOfAnyOfBlackListedExceptions (line 89) | @Test
    method shouldAbortOnSubclassesOfAnyOfBlackListedExceptionsInOneList (line 100) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBothBlackAndWhiteTest.java
  class RetryPolicyBothBlackAndWhiteTest (line 16) | public class RetryPolicyBothBlackAndWhiteTest extends AbstractRetryPolic...
    method shouldRetryOnGivenException (line 18) | @Test
    method shouldAbortOnGivenException (line 29) | @Test
    method shouldRetryUnlessGivenSubclass (line 40) | @Test
    method shouldRetryUnlessGivenSubclassWithReversedDeclarationOrder (line 52) | @Test
    method shouldUnderstandManyWhiteAndBlackListedExceptions (line 64) | @Test
    method shouldUnderstandManyWhiteAndBlackListedExceptionsInOneList (line 85) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyDefaultsTest.java
  class RetryPolicyDefaultsTest (line 15) | public class RetryPolicyDefaultsTest extends AbstractRetryPolicyTest {
    method byDefaultShouldRetryOnAllExceptions (line 17) | @Test
    method byDefaultShouldRetryOnAllThrowables (line 30) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyMaxRetriesTest.java
  class RetryPolicyMaxRetriesTest (line 12) | public class RetryPolicyMaxRetriesTest extends AbstractBaseTestCase {
    method shouldStopAfterConfiguredNumberOfRetries (line 14) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyPredicatesTest.java
  class RetryPolicyPredicatesTest (line 15) | public class RetryPolicyPredicatesTest extends AbstractRetryPolicyTest {
    method shouldAbortIfAbortPredicateTrue (line 20) | @Test
    method shouldRetryIfRetryPredicateTrue (line 32) | @Test
    method shouldRetryIfBothPredicatesAbstainButClassShouldRetry (line 44) | @Test
    method shouldAbortIfBothPredicatesAbstainButClassShouldAbort (line 59) | @Test
    method shouldRetryIfPredicateTrueEvenIfClassShouldAbort (line 75) | @Test
    method shouldAbortIfPredicateTrueEvenIfClassShouldRetry (line 90) | @Test
    method whenAbortAndRetryPredicatesBothYieldTrueThenAbortWins (line 105) | @Test
    method shouldProceedIfPredicateFalseAndChildAccepts (line 121) | @Test
    method shouldAbortIfPredicateFalseButShouldNotRetry (line 134) | @Test
    method shouldAbortIfPredicateTrueButShouldNotRetry (line 146) | @Test
    method shouldExamineExceptionAndDecide (line 162) | @Test

FILE: src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyWhiteListTest.java
  class RetryPolicyWhiteListTest (line 17) | public class RetryPolicyWhiteListTest extends AbstractRetryPolicyTest {
    method retryOnExceptionExplicitly (line 19) | @Test
    method retryOnExceptionShouldNotRetryOnError (line 34) | @Test
    method shouldRetryOnOnlyOneSpecificException (line 44) | @Test
    method shouldNotRetryOnOtherExceptionsIfOneGivenExplicitly (line 52) | @Test
    method shouldNotRetryOnErrorsIfExceptionGivenExplicitly (line 68) | @Test
    method shouldRetryOnAnyOfProvidedExceptions (line 78) | @Test
    method shouldRetryOnAnyOfProvidedExceptionsInOneList (line 88) | @Test
    method shouldNotRetryOnOtherExceptionsIfFewGivenExplicitly (line 97) | @Test
    method shouldNotRetryOnOtherExceptionsIfFewGivenExplicitlyInOneList (line 112) | @Test
    method shouldNotRetryOnErrorsIfFewExceptionsGivenExplicitly (line 126) | @Test
    method shouldNotRetryOnErrorsIfFewExceptionsGivenExplicitlyInOneList (line 137) | @Test
    method shouldRetryWhenSubclassOfGivenExceptionThrown (line 147) | @Test
    method shouldNotRetryOnSiblilngExceptions (line 157) | @Test
    method shouldNotRetryOnSuperClassesOfGivenClass (line 165) | @Test
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (146K chars).
[
  {
    "path": ".gitignore",
    "chars": 19,
    "preview": "target\n*.iml\n.idea\n"
  },
  {
    "path": ".travis.yml",
    "chars": 153,
    "preview": "language: java\ninstall: mvn install -DskipTests=true -Dgpg.skip=true\njdk:\n  - oraclejdk8\nafter_success:\n  - mvn clean te"
  },
  {
    "path": "README.md",
    "chars": 29349,
    "preview": "[![Build Status](https://travis-ci.org/nurkiewicz/async-retry.svg?branch=master)](https://travis-ci.org/nurkiewicz/async"
  },
  {
    "path": "license.txt",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "pom.xml",
    "chars": 4852,
    "preview": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocat"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/AsyncRetryContext.java",
    "chars": 1073,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.RetryPolicy;\n\nimport java.util.Objects;\n\npub"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/AsyncRetryExecutor.java",
    "chars": 6286,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.backoff.Backoff;\nimport com.nurkiewicz.asyncretry.b"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/AsyncRetryJob.java",
    "chars": 1327,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\n\nimport java.util.concurren"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/RetryContext.java",
    "chars": 421,
    "preview": "package com.nurkiewicz.asyncretry;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 6:36 PM\n */\npublic interface Ret"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/RetryExecutor.java",
    "chars": 609,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\nimport com.nurkiewicz.async"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/RetryJob.java",
    "chars": 3953,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.backoff.Backoff;\nimport com.nurkiewicz.asyncretry.p"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/SyncRetryExecutor.java",
    "chars": 1925,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\nimport com.nurkiewicz.async"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/SyncRetryJob.java",
    "chars": 1015,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\n\nimport java.util.concurren"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/Backoff.java",
    "chars": 1144,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkie"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/BackoffWrapper.java",
    "chars": 323,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport java.util.Objects;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/17/1"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoff.java",
    "chars": 657,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkie"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoff.java",
    "chars": 654,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkie"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoff.java",
    "chars": 748,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkie"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoff.java",
    "chars": 588,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AsyncRetryContext;\nimport com.nurkiewicz.as"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/FixedIntervalBackoff.java",
    "chars": 561,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurkie"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/ProportionalRandomBackoff.java",
    "chars": 999,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport java.util.Random;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/RandomBackoff.java",
    "chars": 1027,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\nimport java.util.Random;\nimp"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/backoff/UniformRandomBackoff.java",
    "chars": 959,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport java.util.Random;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/function/RetryCallable.java",
    "chars": 266,
    "preview": "package com.nurkiewicz.asyncretry.function;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurki"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/function/RetryRunnable.java",
    "chars": 265,
    "preview": "package com.nurkiewicz.asyncretry.function;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\n/**\n * @author Tomasz Nurki"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/policy/AbortRetryException.java",
    "chars": 207,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\n/**\n * @author Tomasz Nurkiewicz\n * @since 7/16/13, 10:23 PM\n */\npublic class"
  },
  {
    "path": "src/main/java/com/nurkiewicz/asyncretry/policy/RetryPolicy.java",
    "chars": 3325,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.RetryContext;\n\nimport java.util.Arrays;\nimpo"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AbstractBaseTestCase.java",
    "chars": 1484,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.RetryPolicy;\nimport org.mockito.Mock;\nimport"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryContextTest.java",
    "chars": 1587,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport org.mockito.InOrder;\nimport org.testng.annotations.Test;\n\nimport static org.m"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorHappyTest.java",
    "chars": 1923,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport org.testng.annotations.Test;\n\nimport java.util.concurrent.CompletableFuture;\n"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManualAbortTest.java",
    "chars": 4057,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport org.assertj.core.api.Assertions;\nimport org.mockito.InOrder;\nimport org.testn"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorManyFailuresTest.java",
    "chars": 4554,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport org.assertj.core.api.Assertions;\nimport org.mockito.InOrder;\nimport org.testn"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryExecutorOneFailureTest.java",
    "chars": 3150,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.AbortRetryException;\nimport org.testng.annot"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/AsyncRetryJobTest.java",
    "chars": 5786,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.policy.AbortRetryException;\nimport org.assertj.core"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/FaultyService.java",
    "chars": 422,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport java.math.BigDecimal;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * "
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/SyncRetryExecutorTest.java",
    "chars": 4559,
    "preview": "package com.nurkiewicz.asyncretry;\n\nimport com.nurkiewicz.asyncretry.function.RetryCallable;\nimport com.nurkiewicz.async"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMaxBackoffTest.java",
    "chars": 1203,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.ann"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/BoundedMinBackoffTest.java",
    "chars": 1629,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.ann"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/ExponentialDelayBackoffTest.java",
    "chars": 1328,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.ann"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/FirstRetryNoDelayBackoffTest.java",
    "chars": 1162,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.ann"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/backoff/RandomBackoffTest.java",
    "chars": 3206,
    "preview": "package com.nurkiewicz.asyncretry.backoff;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.mockito.Mo"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/AbstractRetryPolicyTest.java",
    "chars": 559,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport com.nurkiewicz."
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBlackListTest.java",
    "chars": 4273,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.FileNotFoundException;\nim"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyBothBlackAndWhiteTest.java",
    "chars": 4144,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.FileNotFoundException;\nim"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyDefaultsTest.java",
    "chars": 1532,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.IOException;\nimport java."
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyMaxRetriesTest.java",
    "chars": 831,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.AbstractBaseTestCase;\nimport org.testng.anno"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyPredicatesTest.java",
    "chars": 5064,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport com.nurkiewicz.asyncretry.AsyncRetryContext;\nimport com.nurkiewicz.asy"
  },
  {
    "path": "src/test/java/com/nurkiewicz/asyncretry/policy/RetryPolicyWhiteListTest.java",
    "chars": 7018,
    "preview": "package com.nurkiewicz.asyncretry.policy;\n\nimport org.testng.annotations.Test;\n\nimport java.io.FileNotFoundException;\nim"
  },
  {
    "path": "src/test/resources/logback-test.xml",
    "chars": 299,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<configuration>\n\t<root level=\"ALL\">\n\t\t<appender name=\"STDOUT\" class=\"ch.qos.logb"
  }
]

About this extraction

This page contains the full source code of the nurkiewicz/async-retry GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (130.7 KB), approximately 34.2k tokens, and a symbol index with 296 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!