Git Product home page Git Product logo

guava-retrying's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

guava-retrying's Issues

Project appears to be dead: it has been forked to rhuffman/re-retrying

The project appears to be dead. There are 13 open pull requests, and 36 open issues, yet the last commit to master was in May 2016.

I would like to become maintainer of this repository. If there is no response from the current maintainer, I intend to make a public fork of this project within the week and start working the open issues.

Lock to RELEASE version of com.google.guava:guava dependency

With the current configuration of the gradle build file, the dependency on the google guava library currently resolves to 20.0-SNAPSHOT

dependencies {
    compile 'com.google.guava:guava:[10.+,)'
    compile 'com.google.code.findbugs:jsr305:2.0.2'

    // junit testing
    testCompile 'junit:junit:4.11'
    testCompile 'org.mockito:mockito-all:1.9.5'
}

Can we freeze the release version of guava-retrying on a release version of guava? Current setup leads to potential instabilities in case there is a breaking change in the edge version of the underlying dependency.

The workaround in my project pom.xml was to exclude the guava dependency as defined

            <dependency>
                <groupId>com.github.rholder</groupId>
                <artifactId>guava-retrying</artifactId>
                <version>2.0.0</version>
                <exclusions>
                    <exclusion>
                        <groupId>com.google.guava</groupId>
                        <artifactId>guava</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>19.0</version>
            </dependency>

Is there a way to do this on something that is not a callable?

I have this requirement where I have around 4 methods all of which need to retry of certain exception. But since the execution sequence of these methods is specified, they cannot be retried on separate callables. Is there a solution for this problem through this library? That is retry methods and not callables.

The lib is crashing when used in Ratpack (guava-20.0rc1)

Last Thursday (10/6/2016), Google updated guava to that release candidate.

The project build.gradle contains this import:

compile 'com.google.guava:guava:[10.+,)'

And thus, right now guava-retrying is compiling with that guava-20.0rc1, instead of the stable guava-19.0. That's causing some crashes in projects that use Ratpack Server.

I suggest changing that compile so only stable versions are used i.e. 19.X, 20.X, without rcX.

New strategy

How about mixing more than one strategy ? For example I have a case where in multiple clients of a module try to invoke the module concurrently and none of them will succeed because of the reason that the module doesn't allow concurrency (The reason doesn't matter I believe).
So the clients retry the transactions, but may fail again, because any strategy except the RandomWaitStrategy will give the same result for every client, hence the next retry also happens at the same time for all clients. Do you think it'll be a nice feature to have an option to mix two strategies (For example I want the retry to happen in Fibonacci wait strategy, but some randomness added to the wait time calculated by Fibonacci strategy) ?
If you think it's a nice to have feature, I can send a PR.

Can I retry on arbitrary predicate?

The retry is cool.
I'm wondering if I can have retry if arbitrary condition is met.

Here's my scenario:

  • Delete a folder in Amazon
  • retry if the folder doesn't exist.

So the callable does the delete and I want to retry if a predicate that checks existence shows that the folder still exists.

What's the way to do that?

Additional criteria: .retryIfCause(Predicate<Throwable>), .retryIfCauseOfType(Class<? extends Throwable>)

First of all, @rholder thank you for creating/improving this very useful library! Exactly what I'm looking for :)

I'd like to suggest improvements:

  1. .retryIfCause(Predicate<Throwable>)
  2. .retryIfCauseOfType(Class<? extends Throwable>)

They work just like the normal counterparts, but use the output of Throwables.getCausalChain() on the thrown (topmost) exception.

It's possible to workaround this by catching the exception, doing Throwables.getCausalChain() processing then re-throwing a retryable Exception but with this improvement it'd be much more concise.

Multiple builders constructed from the same builder always use the same listeners

Context: Just read some code and stumble across this. Let's say I do this:

RetryerBuilder<Object> myBuilder = RetryerBuilder
            .<Object> newBuilder()
            .withRetryListener(myFirstListener)
Retryer<Object> firstRetryer = myBuilder.build();
myBuilder.withRetryListener(mySecondListener);
Retryer<Object> secondRetryer = myBuilder.build(); // For use some other time.

Problem:

firstRetryer.call(...);

will call both listeners since they are based on the same mutable list.

Proposed fix: Create an ImmutableList using #copyOf (from Guava) when creating a builder.

artifact has a compile time dependency on jsr305

The generated Maven pom.xml contains a compile dependency on jsr305. This dependency is only needed at compile time, but <scope>compile</scope> will drag it as a runtime dependency. This should be moved to <scope>optional</scope> to ensure it is available only at compile time.

using RetryListener, do I have to trigger the retry-predicate ?

I create a retryer using RetryBuild.
I pass it a shouldRetry() predicate.
I pass it a retryListener

what si the flow?
an onRetry event is triggered to the listener ---> then the predicate is called without the need of the listener to call it(?) ---> if returns "true" the calleable is called again

Or
the predicate is called without the need of the listener to call it(?) ---> if returns "true" the calleable is called again ---> an onRetry event is triggered to the listener

Or

an onRetry event is triggered to the listener, in the onRetry body I have to call explicitly to the retry pre-defined predicate ---> then the predicate is called ---> if returns "true" the calleable is called again

Thanks for clarifying

Retry on success

I have a use case where I need to retry few times even on successful result and fail immediately if there were any unexpected results in between.

For instance my callable returns an Integer, I'd like to retry 5 times even when callable returns expected Integer, but fail immediately when it returns unexpected result.

I couldn't find an easy way to do this with current API.

`retryIf(BooleanSupplier)`: when your boolean logic does not depend on the return of the Callable

If I'm not mistaken, the Retryer assumes that our boolean logic always depends on the return of the Callable.
But what if it didn't?

A workaround could be to have the Predicate to simply ignore its parameter:

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
        .retryIfResult(
                     (ignored) -> { return myBooleanLogic(); }
        )
        ...

Or, better, give the user the option to build a Retryer that takes a BooleanSupplier.

BooleanSupplier myBS;

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
        .retryIf(myBS) // **** new RetryerBuilder.retryIf
        .retryIfResult(Predicates.<Boolean>isNull())
        .retryIfExceptionOfType(IOException.class)
        .retryIfRuntimeException()
        .withStopStrategy(StopStrategies.stopAfterAttempt(3))
        .build();

It doesn't need to be "either a Predicate or a BooleanSupplier": I think it should be legal to build a Retryer with both:

  • the predicate to check that the Callable doesn't return weird values like null
  • the BooleanSupplier to check some other condition, not related to (the result of) the Callable.
public V call(Callable<V> callable) throws ExecutionException, RetryException {

....

if (!rejectionPredicate.apply(attempt)) {
return attempt.get();
}

if (!retryBooleanSupplier.getAsBoolean()){ // <---
return attempt.get();
}
...

Use case (Selenium UI testing)

I want to keep clicking on the 'refresh' button until some other element changes, e.g. the text of a div changes to "LOADED".

Here:

  • my callable is "keep clicking on the 'refresh' button"
refreshBtn.click();
  • my predicate is "has the text of the div changed to 'LOADED'?"
div.getText().contains("LOADED");

=> the predicate does not depend on the return of the Callable

If you agree with this I'll try to submit a PR.
Thanks

./gradlew build on macOS Sierra with JDK 1.8 throwing a Java compile error

Running ./gradlew build on macOSierra with JDK 1.8 is throwing a Java compile error.

17:04:05.868 [INFO] [org.gradle.api.internal.tasks.compile.JdkJavaCompiler] Compiling with JDK Java compiler API.
17:04:05.990 [ERROR] [system.err] warning: [options] bootstrap class path not set in conjunction with -source 1.6
17:04:06.327 [ERROR] [system.err] /Users/username/code/guava-retrying/src/main/java/com/github/rholder/retry/RetryerBuilder.java:199: error: ExceptionClassPredicate is not abstract and does not override abstract method test(Attempt<V>) in Predicate
17:04:06.328 [ERROR] [system.err]     private static final class ExceptionClassPredicate<V> implements Predicate<Attempt<V>> {
17:04:06.328 [ERROR] [system.err]                          ^
17:04:06.328 [ERROR] [system.err]   where V is a type-variable:
17:04:06.328 [ERROR] [system.err]     V extends Object declared in class ExceptionClassPredicate
17:04:06.332 [ERROR] [system.err] /Users/username/code/guava-retrying/src/main/java/com/github/rholder/retry/RetryerBuilder.java:216: error: ResultPredicate is not abstract and does not override abstract method test(Attempt<V>) in Predicate
17:04:06.332 [ERROR] [system.err]     private static final class ResultPredicate<V> implements Predicate<Attempt<V>> {
17:04:06.332 [ERROR] [system.err]                          ^
17:04:06.333 [ERROR] [system.err]   where V is a type-variable:
17:04:06.333 [ERROR] [system.err]     V extends Object declared in class ResultPredicate
17:04:06.334 [ERROR] [system.err] /Users/username/code/guava-retrying/src/main/java/com/github/rholder/retry/RetryerBuilder.java:234: error: ExceptionPredicate is not abstract and does not override abstract method test(Attempt<V>) in Predicate
17:04:06.334 [ERROR] [system.err]     private static final class ExceptionPredicate<V> implements Predicate<Attempt<V>> {
17:04:06.335 [ERROR] [system.err]                          ^
17:04:06.335 [ERROR] [system.err]   where V is a type-variable:
17:04:06.335 [ERROR] [system.err]     V extends Object declared in class ExceptionPredicate
17:04:06.357 [ERROR] [system.err] 3 errors
17:04:06.358 [ERROR] [system.err] 1 warning
17:04:06.359 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Finished executing task ':compileJava'
17:04:06.359 [LIFECYCLE] [class org.gradle.TaskExecutionLogger] :compileJava FAILED
17:04:06.360 [INFO] [org.gradle.execution.taskgraph.AbstractTaskPlanExecutor] :compileJava (Thread[main,5,main]) completed. Took 0.763 secs.
17:04:06.360 [DEBUG] [org.gradle.execution.taskgraph.AbstractTaskPlanExecutor] Task worker [Thread[main,5,main]] finished, busy: 0.763 secs, idle: 0.001 secs
17:04:06.364 [ERROR] [org.gradle.BuildExceptionReporter] 
17:04:06.365 [ERROR] [org.gradle.BuildExceptionReporter] FAILURE: Build failed with an exception.
17:04:06.366 [ERROR] [org.gradle.BuildExceptionReporter] 
17:04:06.366 [ERROR] [org.gradle.BuildExceptionReporter] * What went wrong:
17:04:06.366 [ERROR] [org.gradle.BuildExceptionReporter] Execution failed for task ':compileJava'.
17:04:06.366 [ERROR] [org.gradle.BuildExceptionReporter] > Compilation failed; see the compiler error output for details.
17:04:06.367 [ERROR] [org.gradle.BuildExceptionReporter] 
17:04:06.367 [ERROR] [org.gradle.BuildExceptionReporter] * Exception is:
17:04:06.368 [ERROR] [org.gradle.BuildExceptionReporter] org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':compileJava'.
17:04:06.368 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
17:04:06.368 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
17:04:06.368 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
17:04:06.369 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
17:04:06.369 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
17:04:06.369 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:42)
17:04:06.369 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
17:04:06.369 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
17:04:06.369 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
17:04:06.369 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.AbstractTask.executeWithoutThrowingTaskFailure(AbstractTask.java:310)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.executeTask(AbstractTaskPlanExecutor.java:79)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:63)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:51)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:23)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:88)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
17:04:06.370 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
17:04:06.371 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:68)
17:04:06.371 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
17:04:06.371 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
17:04:06.372 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:55)
17:04:06.372 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:149)
17:04:06.372 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:106)
17:04:06.372 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:86)
17:04:06.372 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:90)
17:04:06.373 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
17:04:06.373 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
17:04:06.373 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)
17:04:06.373 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:28)
17:04:06.374 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:50)
17:04:06.374 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:27)
17:04:06.374 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.RunBuildAction.run(RunBuildAction.java:40)
17:04:06.374 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.internal.Actions$RunnableActionAdapter.execute(Actions.java:169)
17:04:06.374 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:237)
17:04:06.374 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:210)
17:04:06.375 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:35)
17:04:06.375 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:24)
17:04:06.375 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:206)
17:04:06.375 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:169)
17:04:06.375 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:33)
17:04:06.375 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:22)
17:04:06.376 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.Main.doAction(Main.java:33)
17:04:06.376 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.bootstrap.EntryPoint.run(EntryPoint.java:45)
17:04:06.376 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.bootstrap.ProcessBootstrap.runNoExit(ProcessBootstrap.java:54)
17:04:06.376 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.bootstrap.ProcessBootstrap.run(ProcessBootstrap.java:35)
17:04:06.377 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.launcher.GradleMain.main(GradleMain.java:23)
17:04:06.377 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:30)
17:04:06.377 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:127)
17:04:06.377 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)
17:04:06.377 [ERROR] [org.gradle.BuildExceptionReporter] Caused by: org.gradle.api.internal.tasks.compile.CompilationFailedException: Compilation failed; see the compiler error output for details.
17:04:06.377 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:47)
17:04:06.378 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:33)
17:04:06.378 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.delegateAndHandleErrors(NormalizingJavaCompiler.java:101)
17:04:06.378 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:50)
17:04:06.378 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:36)
17:04:06.378 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:34)
17:04:06.379 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:25)
17:04:06.379 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.tasks.compile.JavaCompile.performCompilation(JavaCompile.java:157)
17:04:06.379 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:137)
17:04:06.379 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:91)
17:04:06.379 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
17:04:06.380 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:243)
17:04:06.380 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:219)
17:04:06.380 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:230)
17:04:06.380 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:208)
17:04:06.381 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
17:04:06.381 [ERROR] [org.gradle.BuildExceptionReporter] 	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
17:04:06.381 [ERROR] [org.gradle.BuildExceptionReporter] 	... 49 more
17:04:06.381 [ERROR] [org.gradle.BuildExceptionReporter] 
17:04:06.381 [LIFECYCLE] [org.gradle.BuildResultLogger] 
17:04:06.381 [LIFECYCLE] [org.gradle.BuildResultLogger] BUILD FAILED
17:04:06.381 [LIFECYCLE] [org.gradle.BuildResultLogger] 
17:04:06.382 [LIFECYCLE] [org.gradle.BuildResultLogger] Total time: 2.906 secs
17:04:06.382 [DEBUG] [org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager] Stopping 0 compiler daemon(s).
17:04:06.383 [INFO] [org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager] Stopped 0 compiler daemon(s).
17:04:06.383 [DEBUG] [org.gradle.cache.internal.btree.BTreePersistentIndexedCache] Closing cache module-versions.bin (/Users/username/.gradle/caches/modules-2/metadata-2.15/module-versions.bin)
17:04:06.383 [DEBUG] [org.gradle.cache.internal.btree.BTreePersistentIndexedCache] Closing cache module-metadata.bin (/Users/username/.gradle/caches/modules-2/metadata-2.15/module-metadata.bin)
17:04:06.383 [DEBUG] [org.gradle.cache.internal.btree.BTreePersistentIndexedCache] Closing cache artifact-at-repository.bin (/Users/username/.gradle/caches/modules-2/metadata-2.15/artifact-at-repository.bin)
17:04:06.384 [DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Releasing lock on artifact cache (/Users/username/.gradle/caches/modules-2).
17:04:06.385 [DEBUG] [org.gradle.cache.internal.DefaultCacheAccess] Cache Plugin Resolution Cache (/Users/username/.gradle/caches/2.4/plugin-resolution) was closed 0 times.
17:04:06.385 [DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Releasing lock on buildscript class cache for build file '/Users/username/code/guava-retrying/build.gradle' (/Users/username/.gradle/caches/2.4/scripts/build_e4vf1qwu4qu1xvf2j35n44s0t/ProjectScript/buildscript).
17:04:06.385 [DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Releasing lock on no_buildscript class cache for build file '/Users/username/code/guava-retrying/build.gradle' (/Users/username/.gradle/caches/2.4/scripts/build_e4vf1qwu4qu1xvf2j35n44s0t/ProjectScript/no_buildscript).
17:04:06.385 [DEBUG] [org.gradle.cache.internal.btree.BTreePersistentIndexedCache] Closing cache outputFileStates.bin (/Users/username/code/guava-retrying/.gradle/2.4/taskArtifacts/outputFileStates.bin)
17:04:06.385 [DEBUG] [org.gradle.cache.internal.btree.BTreePersistentIndexedCache] Closing cache taskArtifacts.bin (/Users/username/code/guava-retrying/.gradle/2.4/taskArtifacts/taskArtifacts.bin)
17:04:06.386 [DEBUG] [org.gradle.cache.internal.btree.BTreePersistentIndexedCache] Closing cache fileHashes.bin (/Users/username/code/guava-retrying/.gradle/2.4/taskArtifacts/fileHashes.bin)
17:04:06.386 [DEBUG] [org.gradle.cache.internal.DefaultFileLockManager] Releasing lock on task history cache (/Users/username/code/guava-retrying/.gradle/2.4/taskArtifacts).
17:04:06.386 [DEBUG] [org.gradle.api.internal.artifacts.ivyservice.ivyresolve.memcache.InMemoryCachedRepositoryFactory] In-memory dependency metadata cache closed. Repos cached: 1, cache instances: 1, modules served from cache: 0, artifacts: 0
17:04:06.386 [DEBUG] [org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.CachedStoreFactory] Resolution result cache closed. Cache reads: 0, disk reads: 0 (avg: 0.0 secs, total: 0.0 secs)
17:04:06.387 [DEBUG] [org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.CachedStoreFactory] Resolved configuration cache closed. Cache reads: 0, disk reads: 0 (avg: 0.0 secs, total: 0.0 secs)
17:04:06.387 [DEBUG] [org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.ResolutionResultsStoreFactory] Deleted 2 resolution results binary files in 0.001 secs

Any interest in a Future's retryer?

I have use for a Retryer that retries methods that returns futures. If there is any interest I would be happy to work on contributing it back to this project.

package com.github.rholder.retry;

import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.WaitStrategies;
import com.github.rholder.retry.WaitStrategy;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.StopStrategy;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Retries calls that returns a {@link ListenableFuture} by itself returning a {@link ListenableFuture}.
 */
public class FutureRetryer<I, O> {
    private static final Logger logger = LoggerFactory.getLogger(FutureRetryer.class);

    private final Function<I, ListenableFuture<O>> wrappedCall;
    private final I input;
    private final WaitStrategy waitStrategy;
    private final StopStrategy stopStrategy;
    private final Predicate<Attempt<Object>> retryableExceptionPredicate;
    private final ScheduledExecutorService scheduledExecutorService;
    private final SettableFuture<O> responseFuture;
    private final AtomicInteger attemptCount = new AtomicInteger(0);
    private final long startTime;

    public FutureRetryer(Function<I, ListenableFuture<O>> wrappedCall, I input, WaitStrategy waitStrategy,
                         StopStrategy stopStrategy, Predicate<Attempt<Object>> rejectionPredicate,
                         ScheduledExecutorService scheduledExecutorService) {
        this.wrappedCall = wrappedCall;
        this.input = input;
        this.waitStrategy = waitStrategy;
        this.stopStrategy = stopStrategy;
        this.retryableExceptionPredicate = rejectionPredicate;
        this.scheduledExecutorService = scheduledExecutorService;
        this.responseFuture = SettableFuture.create();
        this.startTime = System.nanoTime();
    }

    public ListenableFuture<O> performAction() {
        performActionImpl();
        return this.responseFuture;
    }

    public int getAttemptCount() {
        return this.attemptCount.get();
    }

    private void performActionImpl() {
        ListenableFuture<O> callResponseFuture = wrappedCall.apply(this.input);
        Futures.addCallback(callResponseFuture, new FutureCallback<O>() {
            @Override
            public void onSuccess(final O result) {
                handleSuccessfulResponse(result);
            }

            @Override
            public void onFailure(final Throwable throwable) {
                handleFailureResponse(throwable);
            }
        });
    }

    /**
     * Handles successful response by writing to the responseFuture.
     */
    private void handleSuccessfulResponse(O result) {
        this.responseFuture.set(result);
    }

    /**
     * Handles failure response by retrying if the failure is retryable and the all attempts have not been
     * exhausted. In case a retry is not possible the last exception is set on the responseFuture.
     */
    private void handleFailureResponse(Throwable throwable) {
        int currentAttemptCount = this.attemptCount.get();
        ExceptionAttempt attempt = new ExceptionAttempt(throwable, currentAttemptCount,
            System.nanoTime() - this.startTime);
        // If the retryable exception predicate does not allow the last attempt then set the exception on the
        // response future and end all further retries.
        if (!this.retryableExceptionPredicate.apply((Attempt<Object>) attempt)) {
            this.responseFuture.setException(throwable);
            return;
        }
        // check if the no of retries is exhausted
        if (stopStrategy.shouldStop(attempt)) {
            this.responseFuture.setException(throwable);
            return;
        }
        // increment after it is known that a retry should happen
        this.attemptCount.incrementAndGet();

        // schedule retry based after some delay
        long delayTime = this.waitStrategy.computeSleepTime(attempt);

        // schedule for delayed execution.
        this.scheduledExecutorService.schedule(() -> this.performActionImpl(), delayTime, TimeUnit.MILLISECONDS);
    }

    public static class Builder<IB, OB> {

        private static final int DEFAULT_STOP_ATTEMPT = 10;
        private static final int DEFAULT_WAIT_MULTIPLIER = 300;
        private static final int DEFAULT_WAIT_MAX = 60;
        private static final TimeUnit DEFAULT_WAIT_MAX_UNIT = TimeUnit.SECONDS;

        private Function<IB, ListenableFuture<OB>> wrappedCall;
        private IB input;
        private WaitStrategy waitStrategy;
        private StopStrategy stopStrategy;
        private Predicate<Attempt<Object>> rejectionPredicate = Predicates.alwaysFalse();
        private ScheduledExecutorService scheduledExecutorService;

        public Builder<IB, OB> setWrappedCall(Function<IB, ListenableFuture<OB>> wrappedCall) {
            this.wrappedCall = wrappedCall;
            return this;
        }

        public Builder<IB, OB> setInput(IB input) {
            this.input = input;
            return this;
        }

        public Builder<IB, OB> setWaitStrategy(WaitStrategy waitStrategy) {
            this.waitStrategy = waitStrategy;
            return this;
        }

        public Builder<IB, OB> setStopStrategy(StopStrategy stopStrategy) {
            this.stopStrategy = stopStrategy;
            return this;
        }

        public Builder<IB, OB> retryIfExceptionOfType(Class<? extends Throwable> exceptionClass) {
            this.rejectionPredicate = (Predicate<Attempt<Object>>) Predicates.or(this.rejectionPredicate,
                new ExceptionClassPredicate(exceptionClass));
            return this;
        }

        public Builder<IB, OB> setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
            this.scheduledExecutorService = scheduledExecutorService;
            return this;
        }

        public FutureRetryer<IB, OB> build() {
            Preconditions.checkNotNull(this.wrappedCall, "Wrapped call is required.");
            Preconditions.checkNotNull(this.scheduledExecutorService, "Scheduled executor service is required.");

            if (this.stopStrategy == null) {
                logger.warn("No StopStrategy provided, using default strategy.");
                this.stopStrategy = StopStrategies.stopAfterAttempt(DEFAULT_STOP_ATTEMPT);
            }

            if (this.waitStrategy == null) {
                logger.warn("No WaitStrategy provided, using default strategy.");
                this.waitStrategy = WaitStrategies.exponentialWait(DEFAULT_WAIT_MULTIPLIER, DEFAULT_WAIT_MAX,
                    DEFAULT_WAIT_MAX_UNIT);
            }

            return new FutureRetryer<>(
                this.wrappedCall,
                this.input,
                this.waitStrategy,
                this.stopStrategy,
                this.rejectionPredicate,
                this.scheduledExecutorService
            );
        }
    }

    /**
     * Attempt impl to be used with an Exception.
     */
    static final class ExceptionAttempt implements Attempt<Object> {

        private final ExecutionException e;
        private final long attemptNumber;
        private final long delaySinceFirstAttempt;

        ExceptionAttempt(Throwable cause, long attemptNumber, long delaySinceFirstAttempt) {
            this.e = new ExecutionException(cause);
            this.attemptNumber = attemptNumber;
            this.delaySinceFirstAttempt = delaySinceFirstAttempt;
        }

        @Override
        public Object get() throws ExecutionException {
            throw this.e;
        }

        @Override
        public boolean hasResult() {
            return false;
        }

        @Override
        public boolean hasException() {
            return true;
        }

        @Override
        public Object getResult() throws IllegalStateException {
            throw new IllegalStateException("The attempt resulted in an exception, not in a result");
        }

        @Override
        public Throwable getExceptionCause() throws IllegalStateException {
            return this.e.getCause();
        }

        @Override
        public long getAttemptNumber() {
            return attemptNumber;
        }

        @Override
        public long getDelaySinceFirstAttempt() {
            return delaySinceFirstAttempt;
        }
    }

    static final class ExceptionClassPredicate implements Predicate<Attempt<Object>> {

        private Class<? extends Throwable> exceptionClass;

        ExceptionClassPredicate(Class<? extends Throwable> exceptionClass) {
            this.exceptionClass = exceptionClass;
        }

        @Override
        public boolean apply(Attempt<Object> attempt) {
            if (!attempt.hasException()) {
                return false;
            }
            return exceptionClass.isAssignableFrom(attempt.getExceptionCause().getClass());
        }
    }
}

I also have it in a gist https://gist.github.com/manasdk/ea816f45b26ff4a74f99e2c99116b0b5. If there is interest I can create a PR and send it out for review. I also wouldn't mind if someone took over the code and wrote it in a cleaner way that fits better with this project.

Remove use of Maven dependency ranges for Guava dependency

Minor quibble here - but would it be possible to avoid using the fixed dependency range of [10.0+,15.0] for Guava in the guava-retrying POM?

It kind-of forces people to use an exclude on this transitive dependency in order to use updated guava versons in their own code because the end of the range in the POM is currently fixed at v15.0. On the general assumption that the guava team don't make API-breaking changes; I think it's a reasonable assumption that guava-retrying will work on forthcoming Guava releases (we're now up to 18.0 and we're still using guava-retrying without issue) so is making this dependency open-ended such as [10.0+,) an option?

[Question] Modify callable in case of a particular exception using RetryListener

I have a scenario where I have to run a Database batch (insert/update). Sometimes, I receive "Connection reset" from the database connection. In this case, closing and retrieving a new connection from the pool solved the issue. I wrote a custom retry handler where I did this using try-catch loops and then I wanted to migrate my code to use this utility.

What I have done is that I have filtered using retryifException on a predicate and then added a listener to assign the connection to the callable instance. This is the snippet:

public void batch(String query, Object[][] params) throws ExecutionException, RetryException
{
    if (params == null)
        return;

    RetryCallables.DatabaseBatch callable = new RetryCallables.DatabaseBatch(queryRunner, connection, query,
            params);
    Predicate<Throwable> exceptionPredicate = e -> (e.getMessage()
            .contains(ExceptionMessages.CONNECTION_RESET.toString()) || !(e instanceof SQLException));
    RetryListener listener = new RetryListener() {
        @Override
        public <V> void onRetry(Attempt<V> attempt)
        {
            if (attempt.hasException())
            {
                Throwable cause = attempt.getExceptionCause();
                if (cause != null && cause.getMessage().contains(ExceptionMessages.CONNECTION_RESET.toString()))
                {
                    try
                    {
                        callable.getConnection().close();
                        callable.setConnection(Options.dbPool.getConnection());
                    }
                    catch (SQLException e)
                    {
                        logger.error(e);
                    }
                }
            }
        }
    };

    Retryer<int[]> retryer = RetryerBuilder.<int[]> newBuilder().retryIfException(exceptionPredicate)
            .withRetryListener(listener).withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
            .withStopStrategy(StopStrategies.stopAfterAttempt(Options.NRETRIES)).build();
    retryer.call(callable);
}

But I am not sure whether this would work since the utility does not modify callable. How do I modify the callable in case of a particular exception?

Expose Attempt implementations publicly

Previously it was possible to re-use the useful implementation of WaitStrategy without being locked into the Retryer's notion of doing this synchronously. Since the library of WaitStrategy implementations was a nice feature of the library it was convenient to be able to use them to computeSleepTime and then use that with a different synchronization approach. Perhaps this wasn't intended by design; but was useful nonetheless.

However since 2.0.0 the implementation of computeSleepTime takes an Attempt parameter. These aren't particularly complicated and we can define our own impl; however I'd rather re-use than supply exactly the same implementation as already existing internally in the library.

Do you have any objection to promoting these to be public (and probably non-nested) so people can re-use them; or do you feel that this would violate the design?

Use Clock/Timer abstraction to read nanoTime

It would be lovely if Retryer read nanoTime from some sort of Clock abstraction. Mostly for sensible integration testing from library client perspective. Since this is jdk 1.6 project I assume we can't use java.time.Clock and have to implement something custom. It will try to prepare pull request today.

AttemptTimeLimiter does not have to be generic

public interface AttemptTimeLimiter<V> {
    V call(Callable<V> callable) throws Exception;
}

can be changed to

public interface AttemptTimeLimiter {
    <V> V call(Callable<V> callable) throws Exception;
}

So that we don't have to specify a generic type when creating the Retryer

Support WaitStrategy.randomExponentialWait()

A combination between randomWait() and exponentialWait(), as described in http://en.wikipedia.org/wiki/Exponential_backoff , where the exponential wait time is used as the random range.

This is how I do it:

@Immutable
private static final class RandomExponentialWaitStrategy implements WaitStrategy {
    private static final Random RANDOM = new Random();
    private final long multiplier;
    private final long maximumWait;

    public RandomExponentialWaitStrategy(long multiplier,
                                   long maximumWait) {
        Preconditions.checkArgument(multiplier > 0L, "multiplier must be > 0 but is %d", multiplier);
        Preconditions.checkArgument(maximumWait >= 0L, "maximumWait must be >= 0 but is %d", maximumWait);
        Preconditions.checkArgument(multiplier < maximumWait, "multiplier must be < maximumWait but is %d", multiplier);
        this.multiplier = multiplier;
        this.maximumWait = maximumWait;
    }

    @Override
    public long computeSleepTime(int previousAttemptNumber, long delaySinceFirstAttemptInMillis) {
        long upperExp = Math.round(Math.pow(2, previousAttemptNumber));
        long exp = Math.abs(RANDOM.nextLong()) % upperExp;
        long result = Math.round(multiplier * exp);
        if (result > maximumWait) {
            result = maximumWait;
        }
        return result >= 0L ? result : 0L;
    }
}

Logging retry attempts before retry is cumbersome

Usecase: This is what I'd like to log:

Retrying yet another time because we failed last time...

What I could do is use a RetryListener and log as soon as an attempt failed. However, I would need access to my StopStrategy to know whether a retry will actually be executed.

Proposed solution: Have two methods on a RetryListener:

  • RetryListener#preAttempt(Attempt previousAttempt). Called only if an attempt is to be made.
  • RetryListener#postAttempt(Attempt attempt). Basically the same as #onRetry.

...or can I do this easy some other way?

Where to write the my business handling after executing the stop strategy?

Now i want to do sth after the retryer execute the stop strategy, such as below code.

withStopStrategy(StopStrategies.stopAfterAttempt(3))

after 3 times, i want to update some status to DB, after viewed the source code.

if (stopStrategy.shouldStop(attemptNumber, delaySinceFirstAttemptInMillis)) {
        throw new RetryException(attemptNumber, attempt);
}

seems there is only way to write my business code(update status to DB) in the RetryException, may i know if there is any strategy to take the continue action after executing the stop strategy?

best regards.

Support creating Retryer instance using a spec string

Hi,

This library is awesome. One small thing will improve is even further. The way guava cache can be created using a spec string, it would be awesome if I can create a retryer using a spec.
Advantages:
In prod deployments, I just need to configure 1 config property which can tweak the retryer to any extent. Currently I would need to have different configs properties for different strategies. If we can come up with some sort of DSL, it would be possible. Please suggest a format I will try to raise a PR.

Be consistent about "duration" method parameters

The AttemptTimeLimiters and WaitStrategies classes have methods that take a (long, TimeUnit) pair, whereas StopStrategies#stopAfterDelay accepts only a long. Not a big deal, but slightly unfortunate from an API point of view.

How about aligning StopStrategies with the other two classes by introducing StopStrategies#stopAfterDelay(long, TimeUnit) and deprecating StopStrategies#stopAfterDelay(long)?

(I understand the some of the strategy interfaces also take only a long, but most users will not implement those themselves, and in any case those types are in some sense more "low level".)

Calculate sleep time from failed attempt

It would be good to pass failed attempt to wait strategy to have a way to use in sleep time calculation. New wait strategy could be added to produce sleep time using the exception occurred.

Failed try event which is called after every failed try

It would be useful to be able to log failed tries.

FailedTryHandler handler = new FailedTryHandler() {
      public void handle(FailedTryEvent event) {
           logger.warn("Failed to do stuff, retrying", event.getException()); //or event.getResult()
      }
};

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
        .retryIfException()
        .withStopStrategy(...)
        .withFailedTryHandler(handler)
        .build();

Retry following timeout

As it stands (in version 1.0.6), any exception thrown by the AttemptTimeLimiter will cause Retryer to abort. This is includes TimeoutException. To me this was very surprising and unintuitive. Let's consider the following code:

Retryer<T> retryer = RetryerBuilder.<T> newBuilder()
  .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(1, MINUTES))
  .withWaitStrategy(WaitStrategies.fixedWait(10, SECONDS))
  .withStopStrategy(StopStrategies.stopAfterDelay(MINUTES.toMillis(15)));

The Retryer produced here will not retry an operation if it times out after one minute. I would have expected it to wait for 10 seconds and try again, until the time limit of 15 minutes was reached.

This behavior should either be fixed or documented. I believe it's a bug, though, because the documentation for RetryerBuilder#withAttemptTimeLimiter reads:

Configures the retryer to limit the duration of any particular attempt by the given duration.

One can get the desired behavior by configuring the builder with #retryIfExceptionOfType(com.google.common.util.concurrent.UncheckedTimeoutException.class), but this is unintuitive and dependent on the implementation of AttemptTimeLimiter used.

One way to proceed would be the following:

  • Add to the contract of AttemptTimeLimiter that upon timeout a java.util.concurrent.TimeoutException is thrown, whereas an ExecutionException is thrown otherwise. (The ExceptionClassPredicate implementation already seems to assume that every exception originating from the original Callable is wrapped.)
  • Update the FixedAttemptTimeLimit implentation accordingly.
  • Modify Retryer#call to handle TimeoutException differently from ExecutionException.

Would like to hear your thoughts.

`RetryListener`s are not only called on retry

Annoyance: This is a small annoyance; A RetryListener is not only called on a retry. It is called on each attempt. That includes the first call.

Proposed fix: Rename

  • RetryListener to AttemptListener.
  • RetryerBuilder#withRetryListener to RetryerBuilder#withAttemptListener.

I understand that this will introduce backward incompatible change. I therefor propose this is a fix introduced for the next major version.

Getting attempt number on success

Currently there is no way to retrieve last attempt number on success, only on error.

It'd be nice to either be able to have a method on Retryer instance that would give you back attempt number after successful call to call() method or modify call() to return attempt number as well as successful result wrapped in some sort of result object.

Why WaitStrategy use BlockStrategy#block to sleep ?

long sleepTime = waitStrategy.computeSleepTime(attempt);
try {
    blockStrategy.block(sleepTime);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    throw new RetryException(attemptNumber, attempt);
}

as you see in the code above, waitStrategy use blockStrategy#block to sleep a thread .

if i create a class which implements BlockStrategy but it does not sleep , then the waitStrategy does not work ? so when i create a BlockStrategy implement class , it must sleep in the method block() !!!

example:

static class TaskBlockStrategy implements BlockStrategy{
    @Override
    public void block(long sleepTime) throws InterruptedException {
        System.out.println("print something.");
    }
}

it will runs forever...

sorry for my poor english and strange question !

Guava version

Can we get release with more recent guava version?

Make RetryListener use a generic type on the interface not on method.

public interface RetryListener {
    <V> void onRetry(Attempt<V> var1);
}

should be:

public interface RetryListener<V> {
    void onRetry(Attempt<V> var1);
}

with this change, withRetryListener would work much more naturally.

Because this is technically a breaking change also a new interface could be introduced and a new method called like withTypedRetryListener

Reuse Retryer across threads

Are the Retryer's thread safe?

For example, I would like to use a single retry strategy across my web application. I built the Retryer once, and reuse it in any classes that requires a retrying call?

Retry Listener

What are your thoughts on adding a listener framework to the the Retryer?

My use case is that I would like to log each failed retry attempt but I don't see any way to inspect the results

I was thinking we could add a simple interface

interface RetryListener {
    void attempt(Attempt attempt, int attemptNumber);
}

That should cover most use cases. Spring retry has a different interface but I think just logging each Attempt should cover onClose/onError.

Happy to create a pull request for this if you're interested.

Non-generic Retryer?

I wanted to start a discussion before submitting a PR. I think it would pretty useful to have a non-generic Retryer that would allow you to use the Retryer with any generic callable.

A quick look through the code seems to suggest the only reason for the generics at the class-level is to retry on a specific return value. However, if this is not needed, but the same retry logic (on exception) is needed for multiple methods, each with different return types, then a non-generic Retryer seems to be a cleaner approach.

Thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.