Skip to content

Commit

Permalink
Added support for CheckedRunnable.
Browse files Browse the repository at this point in the history
Documented Listeners.
Documented RecurrentFuture callbacks.
  • Loading branch information
jhalterman committed Mar 31, 2016
1 parent 2584512 commit 9df9c42
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 75 deletions.
19 changes: 15 additions & 4 deletions CHANGES.md
@@ -1,3 +1,14 @@
# 0.5.0

### New Features

* Added support for synchronous and asynchronous event listeners
* Added support for `CheckedRunnable`

### API Changes

* The `Recurrent.run` methods now require a `CheckedRunnable` rather than `Runnable`. This allows Recurrent to be used on code that throws checked exceptions without having to wrap the code in try/catch blocks.

# 0.4.0

### New Features
Expand All @@ -6,18 +17,18 @@

### API Changes

* New Invocation and AsyncInvocation APIs
* New Invocation and `AsyncInvocation` APIs

# 0.3.3

### New Features

* Add Scheduler API
* Make RetryPolicy copyable
* Add `Scheduler` API
* Make `RetryPolicy` copyable

### Behavior Changes

* Require ContextualCallable and ContextualRunnable to be manually retried
* Require `ContextualCallable` and `ContextualRunnable` to be manually retried
* Add support for checking multiple retry policy conditions

### API Changes
Expand Down
62 changes: 31 additions & 31 deletions README.md
Expand Up @@ -12,8 +12,8 @@ Recurrent is a simple, zero-dependency library for performing retries. It featur
* [Synchronous](synchronous-retries) and [asynchronous retries](#asynchronous-retries)
* [Asynchronous API integration](#asynchronous-api-integration)
* [CompletableFuture](#completablefuture-integration) and [Java 8 functional interface](#java-8-functional-interfaces) integration
* [Invocation Tracking](#invocation-tracking)
* [Event Listeners](#event-listeners)
* [Invocation Tracking](#invocation-tracking)

Supports Java 6+ though the documentation uses lambdas for simplicity.

Expand Down Expand Up @@ -135,34 +135,6 @@ CompletableFuture.supplyAsync(() -> Recurrent.get(() -> "foo", retryPolicy))
.thenApplyAsync(value -> Recurrent.get(() -> value + "bar", retryPolicy));
```

#### Invocation Tracking

In addition to automatically performing retries, Recurrent can be used to track invocations for you, allowing you to manually retry as needed:

```java
Invocation invocation = new Invocation(retryPolicy);
while (!invocation.isComplete()) {
try {
doSomething();
invocation.complete()
} catch (ConnectException e) {
invocation.recordFailure(e);
}
}
```

Invocation tracking is also useful for integrating with APIs that have their own retry mechanism:

```java
Invocation invocation = new Invocation(retryPolicy);

// On failure
if (invocation.canRetryOn(someFailure))
service.scheduleRetry(invocation.getWaitMillis(), TimeUnit.MILLISECONDS);
```

See the [RxJava example][RxJava] for a more detailed implementation.

#### Event Listeners

Recurrent supports event listeners that can be notified when retries are performed and when invocations complete:
Expand All @@ -185,14 +157,42 @@ Recurrent.get(() -> connect(), retryPolicy, new Listeners<Connection>() {
Java 8 users can register individual listeners using lambdas:

```java
Recurrent.get(() -> connect(), retryPolicy, new Listeners()
Recurrent.get(() -> connect(), retryPolicy, new Listeners<Connection>()
.whenRetry((c, f, stats) -> log.warn("Failure #{}. Retrying.", stats.getAttemptCount()))
.whenFailure((cxn, failure) -> log.error("Connection attempts failed", failure)))
.whenFailure((cxn, failure) -> log.error("Connection attempts failed", failure))
.whenSuccess(cxn -> log.info("Connected to {}", cxn)));
```

Additional listeners are available via the [Listeners] and [AsyncListeners] classes. Asynchronous completion listeners can be registered via [RecurrentFuture].

#### Invocation Tracking

In addition to automatically performing retries, Recurrent can be used to track invocations for you, allowing you to manually retry as needed:

```java
Invocation invocation = new Invocation(retryPolicy);
while (!invocation.isComplete()) {
try {
doSomething();
invocation.complete()
} catch (ConnectException e) {
invocation.recordFailure(e);
}
}
```

Invocation tracking is also useful for integrating with APIs that have their own retry mechanism:

```java
Invocation invocation = new Invocation(retryPolicy);

// On failure
if (invocation.canRetryOn(someFailure))
service.scheduleRetry(invocation.getWaitMillis(), TimeUnit.MILLISECONDS);
```

See the [RxJava example][RxJava] for a more detailed implementation.

## Example Integrations

Recurrent was designed to integrate nicely with existing libraries. Here are some example integrations:
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/net/jodah/recurrent/AsyncCallable.java
Expand Up @@ -82,6 +82,24 @@ public T call() throws Exception {
}
};
}

static <T> AsyncCallable<T> of(final CheckedRunnable runnable) {
Assert.notNull(runnable, "runnable");
return new AsyncCallable<T>() {
@Override
public T call() throws Exception {
try {
invocation.reset();
runnable.run();
invocation.completeOrRetry(null, null);
} catch (Exception e) {
invocation.completeOrRetry(null, e);
}

return null;
}
};
}

static <T> AsyncCallable<T> ofFuture(final Callable<java.util.concurrent.CompletableFuture<T>> callable) {
Assert.notNull(callable, "callable");
Expand Down
33 changes: 31 additions & 2 deletions src/main/java/net/jodah/recurrent/AsyncListeners.java
Expand Up @@ -2,15 +2,16 @@

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

import net.jodah.recurrent.event.ContextualResultListener;
import net.jodah.recurrent.event.ResultListener;
import net.jodah.recurrent.internal.util.Assert;

/**
* Recurrent event listeners that are called asynchronously. To handle completion events asynchronously, see
* {@link RecurrentFuture}.
* Recurrent event listeners that are called asynchronously on the {@link Scheduler} or {@link ScheduledExecutorService}
* associated with the Recurrent call. To handle completion events asynchronously, see {@link RecurrentFuture}.
*
* @author Jonathan Halterman
* @param <T> result type
Expand Down Expand Up @@ -81,44 +82,72 @@ private static void call(Callable<?> callable, ExecutorService executor, Schedul
scheduler.schedule(callable, 0, TimeUnit.MILLISECONDS);
}

/**
* Registers the {@code listener} to be called asynchronously after a failed invocation attempt.
*/
public AsyncListeners<T> whenFailedAttemptAsync(ContextualResultListener<? super T, ? extends Throwable> listener) {
asyncCtxFailedAttemptListener = new AsyncCtxResultListener<T>(listener);
return this;
}

/**
* Registers the {@code listener} to be called asynchronously after a failed invocation attempt.
*/
public AsyncListeners<T> whenFailedAttemptAsync(ContextualResultListener<? super T, ? extends Throwable> listener,
ExecutorService executor) {
asyncCtxFailedAttemptListener = new AsyncCtxResultListener<T>(listener, executor);
return this;
}

/**
* Registers the {@code listener} to be called asynchronously after a failed invocation attempt.
*/
public AsyncListeners<T> whenFailedAttemptAsync(ResultListener<? super T, ? extends Throwable> listener) {
asyncFailedAttemptListener = new AsyncResultListener<T>(listener);
return this;
}

/**
* Registers the {@code listener} to be called asynchronously after a failed invocation attempt.
*/
public AsyncListeners<T> whenFailedAttemptAsync(ResultListener<? super T, ? extends Throwable> listener,
ExecutorService executor) {
asyncFailedAttemptListener = new AsyncResultListener<T>(listener, executor);
return this;
}

/**
* Registers the {@code listener} to be called asynchronously when the retry policy is exceeded and the result is a
* failure.
*/
public AsyncListeners<T> whenRetryAsync(ContextualResultListener<? super T, ? extends Throwable> listener) {
asyncCtxRetryListener = new AsyncCtxResultListener<T>(listener);
return this;
}

/**
* Registers the {@code listener} to be called asynchronously when the retry policy is exceeded and the result is a
* failure.
*/
public AsyncListeners<T> whenRetryAsync(ContextualResultListener<? super T, ? extends Throwable> listener,
ExecutorService executor) {
asyncCtxRetryListener = new AsyncCtxResultListener<T>(listener, executor);
return this;
}

/**
* Registers the {@code listener} to be called asynchronously when the retry policy is exceeded and the result is a
* failure.
*/
public AsyncListeners<T> whenRetryAsync(ResultListener<? super T, ? extends Throwable> listener) {
asyncRetryListener = new AsyncResultListener<T>(listener);
return this;
}

/**
* Registers the {@code listener} to be called asynchronously when the retry policy is exceeded and the result is a
* failure.
*/
public AsyncListeners<T> whenRetryAsync(ResultListener<? super T, ? extends Throwable> listener,
ExecutorService executor) {
asyncRetryListener = new AsyncResultListener<T>(listener, executor);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/net/jodah/recurrent/Callables.java
Expand Up @@ -43,4 +43,19 @@ public Void call() {
}
};
}

static Callable<Object> of(final CheckedRunnable runnable) {
Assert.notNull(runnable, "runnable");
return new Callable<Object>() {
@Override
public Void call() {
try {
runnable.run();
return null;
} catch (Exception e) {
throw new RecurrentException(e);
}
}
};
}
}
10 changes: 10 additions & 0 deletions src/main/java/net/jodah/recurrent/CheckedRunnable.java
@@ -0,0 +1,10 @@
package net.jodah.recurrent;

/**
* A Runnable that throws checked exceptions.
*
* @author Jonathan Halterman
*/
public interface CheckedRunnable {
void run() throws Exception;
}

0 comments on commit 9df9c42

Please sign in to comment.