├── .gitignore
├── .travis.yml
├── HISTORY.md
├── LICENSE
├── NOTICE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
├── main
└── java
│ └── com
│ └── github
│ └── rholder
│ └── retry
│ ├── Attempt.java
│ ├── AttemptTimeLimiter.java
│ ├── AttemptTimeLimiters.java
│ ├── BlockStrategies.java
│ ├── BlockStrategy.java
│ ├── RetryException.java
│ ├── RetryListener.java
│ ├── Retryer.java
│ ├── RetryerBuilder.java
│ ├── StopStrategies.java
│ ├── StopStrategy.java
│ ├── WaitStrategies.java
│ └── WaitStrategy.java
└── test
└── java
└── com
└── github
└── rholder
└── retry
├── AttemptTimeLimiterTest.java
├── RetryerBuilderTest.java
├── StopStrategiesTest.java
└── WaitStrategiesTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .idea
3 | build
4 | out
5 | *.iml
6 | *.ipr
7 | *.iws
8 | gradle.properties
9 | .DS_Store
10 | classes/
11 | gradle/.DS_STORE
12 | local.properties
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: java
3 | jdk:
4 | - oraclejdk8
5 | - oraclejdk7
6 | - openjdk7
7 | - openjdk6
8 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | ##2.0.0 - 2015-06-30
2 | * Calculate sleep time from failed attempt #25 (yaroslavm)
3 | * Be consistent about "duration" method parameters #30 (Stephan202)
4 | * Use an open Maven dependency range for Guava dependency #32 (voiceinsideyou)
5 | * Add @Beta RetryListener support #36 (kevinconaway)
6 | * Update to Gradle 2.x #38
7 | * Minimal breaking 1.0.x to 2.0.x API changes for Attempt state, hence the major version update
8 |
9 | ##1.0.7 - 2015-01-20
10 | * New composite wait strategy #12 (shasts)
11 | * Adding block strategies to the Retryer to decide how to block (tchdp)
12 |
13 | ##1.0.6 - 2014-03-26
14 | * Javadoc updates for Java 8 (shasts)
15 | * Bug from System.nanoTime() (fror), fix in #15
16 | * Travis CI testing now working for Java 8
17 |
18 | ##1.0.5 - 2013-12-04
19 | * Added Javadoc for all versions
20 | * Added FibonacciWaitStrategy (joschi)
21 | * Updated tested Guava version range from 10.x.x - 15.0 (joschi)
22 | * Updated all dependencies (joschi)
23 | * Updated to Gradle 1.9 (joschi)
24 |
25 | ##1.0.4 - 2013-07-08
26 | * Added tested Guava version range from 10.x.x - 14.0.1
27 | * Added Exception cause propagation to RetryException to fix #3
28 |
29 | ##1.0.3 - 2013-01-16
30 | * Added time limit per attempt in a Retryer (dirkraft)
31 | * Added license text
32 |
33 | ##1.0.2 - 2012-11-22
34 | * Added Gradle wrapper support
35 | * Updated top-level package to com.github.rholder.retry
36 |
37 | ##1.0.1 - 2012-08-29
38 | * Added Javadoc links
39 | * Added exponential wait strategy and unit tests
40 |
41 | ##1.0.0 - 2012-08-26
42 | * Initial stable release, packaging for Maven central, no changes from original source
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright 2012-2015 Ray Holder
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/rholder/guava-retrying) [](https://github.com/rholder/guava-retrying/releases/tag/v2.0.0) [](https://github.com/rholder/guava-retrying/blob/master/LICENSE)
2 |
3 |
4 | ##What is this?
5 | The guava-retrying module provides a general purpose method for retrying arbitrary Java code with specific stop, retry,
6 | and exception handling capabilities that are enhanced by Guava's predicate matching.
7 |
8 | This is a fork of the excellent RetryerBuilder code posted [here](http://code.google.com/p/guava-libraries/issues/detail?id=490)
9 | by Jean-Baptiste Nizet (JB). I've added a Gradle build for pushing it up to my little corner of Maven Central so that
10 | others can easily pull it into their existing projects with minimal effort. It also includes
11 | exponential and Fibonacci backoff [WaitStrategies](http://rholder.github.io/guava-retrying/javadoc/2.0.0/com/github/rholder/retry/WaitStrategies.html)
12 | that might be useful for situations where more well-behaved service polling is preferred.
13 |
14 | ##Maven
15 | ```xml
16 |
17 | com.github.rholder
18 | guava-retrying
19 | 2.0.0
20 |
21 | ```
22 |
23 | ##Gradle
24 | ```groovy
25 | compile "com.github.rholder:guava-retrying:2.0.0"
26 | ```
27 |
28 | ##Quickstart
29 | A minimal sample of some of the functionality would look like:
30 |
31 | ```java
32 | Callable callable = new Callable() {
33 | public Boolean call() throws Exception {
34 | return true; // do something useful here
35 | }
36 | };
37 |
38 | Retryer retryer = RetryerBuilder.newBuilder()
39 | .retryIfResult(Predicates.isNull())
40 | .retryIfExceptionOfType(IOException.class)
41 | .retryIfRuntimeException()
42 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
43 | .build();
44 | try {
45 | retryer.call(callable);
46 | } catch (RetryException e) {
47 | e.printStackTrace();
48 | } catch (ExecutionException e) {
49 | e.printStackTrace();
50 | }
51 | ```
52 |
53 | This will retry whenever the result of the `Callable` is null, if an `IOException` is thrown, or if any other
54 | `RuntimeException` is thrown from the `call()` method. It will stop after attempting to retry 3 times and throw a
55 | `RetryException` that contains information about the last failed attempt. If any other `Exception` pops out of the
56 | `call()` method it's wrapped and rethrown in an `ExecutionException`.
57 |
58 | ##Exponential Backoff
59 |
60 | Create a `Retryer` that retries forever, waiting after every failed retry in increasing exponential backoff intervals
61 | until at most 5 minutes. After 5 minutes, retry from then on in 5 minute intervals.
62 |
63 | ```java
64 | Retryer retryer = RetryerBuilder.newBuilder()
65 | .retryIfExceptionOfType(IOException.class)
66 | .retryIfRuntimeException()
67 | .withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES))
68 | .withStopStrategy(StopStrategies.neverStop())
69 | .build();
70 | ```
71 | You can read more about [exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff) and the historic role
72 | it played in the development of TCP/IP in [Congestion Avoidance and Control](http://ee.lbl.gov/papers/congavoid.pdf).
73 |
74 | ##Fibonacci Backoff
75 |
76 | Create a `Retryer` that retries forever, waiting after every failed retry in increasing Fibonacci backoff intervals
77 | until at most 2 minutes. After 2 minutes, retry from then on in 2 minute intervals.
78 |
79 | ```java
80 | Retryer retryer = RetryerBuilder.newBuilder()
81 | .retryIfExceptionOfType(IOException.class)
82 | .retryIfRuntimeException()
83 | .withWaitStrategy(WaitStrategies.fibonacciWait(100, 2, TimeUnit.MINUTES))
84 | .withStopStrategy(StopStrategies.neverStop())
85 | .build();
86 | ```
87 |
88 | Similar to the `ExponentialWaitStrategy`, the `FibonacciWaitStrategy` follows a pattern of waiting an increasing amount
89 | of time after each failed attempt.
90 |
91 | Instead of an exponential function it's (obviously) using a
92 | [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_numbers) to calculate the wait time.
93 |
94 | Depending on the problem at hand, the `FibonacciWaitStrategy` might perform better and lead to better throughput than
95 | the `ExponentialWaitStrategy` - at least according to
96 | [A Performance Comparison of Different Backoff Algorithms under Different Rebroadcast Probabilities for MANETs](http://www.comp.leeds.ac.uk/ukpew09/papers/12.pdf).
97 |
98 | The implementation of `FibonacciWaitStrategy` is using an iterative version of the Fibonacci because a (naive) recursive
99 | version will lead to a [StackOverflowError](http://docs.oracle.com/javase/7/docs/api/java/lang/StackOverflowError.html)
100 | at a certain point (although very unlikely with useful parameters for retrying).
101 |
102 | Inspiration for this implementation came from [Efficient retry/backoff mechanisms](https://paperairoplane.net/?p=640).
103 |
104 | ##Documentation
105 | Javadoc can be found [here](http://rholder.github.io/guava-retrying/javadoc/2.0.0).
106 |
107 | ##Building from source
108 | The guava-retrying module uses a [Gradle](http://gradle.org)-based build system. In the instructions
109 | below, [`./gradlew`](http://vimeo.com/34436402) is invoked from the root of the source tree and serves as
110 | a cross-platform, self-contained bootstrap mechanism for the build. The only
111 | prerequisites are [Git](https://help.github.com/articles/set-up-git) and JDK 1.6+.
112 |
113 | ### check out sources
114 | `git clone git://github.com/rholder/guava-retrying.git`
115 |
116 | ### compile and test, build all jars
117 | `./gradlew build`
118 |
119 | ### install all jars into your local Maven cache
120 | `./gradlew install`
121 |
122 | ##License
123 | The guava-retrying module is released under version 2.0 of the
124 | [Apache License](http://www.apache.org/licenses/LICENSE-2.0).
125 |
126 | ##Contributors
127 | * Jean-Baptiste Nizet (JB)
128 | * Jason Dunkelberger (dirkraft)
129 | * Diwaker Gupta (diwakergupta)
130 | * Jochen Schalanda (joschi)
131 | * Shajahan Palayil (shasts)
132 | * Olivier Grégoire (fror)
133 | * Andrei Savu (andreisavu)
134 | * (tchdp)
135 | * (squalloser)
136 | * Yaroslav Matveychuk (yaroslavm)
137 | * Stephan Schroevers (Stephan202)
138 | * Chad (voiceinsideyou)
139 | * Kevin Conaway (kevinconaway)
140 | * Alberto Scotto (alb-i986)
141 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | import org.gradle.api.artifacts.maven.MavenDeployment
2 |
3 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
4 | // Settings
5 | ext {
6 | project_group = 'com.github.rholder'
7 | project_version = '2.0.0'
8 | project_jdk = '1.6'
9 | project_pom = {
10 | name 'guava-retrying'
11 | packaging 'jar'
12 | description "This is a small extension to Google's Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime."
13 | url 'http://rholder.github.com'
14 |
15 | scm {
16 | url 'scm:git@github.com:rholder/guava-retrying.git'
17 | connection 'scm:git@github.com:rholder/guava-retrying.git'
18 | developerConnection 'scm:git@github.com:rholder/guava-retrying.git'
19 | }
20 |
21 | licenses {
22 | license {
23 | name 'The Apache Software License, Version 2.0'
24 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
25 | distribution 'repo'
26 | }
27 | }
28 |
29 | developers {
30 | developer {
31 | id 'rholder'
32 | name 'Ray Holder'
33 | }
34 | }
35 | }
36 |
37 | /** Function always returns a new manifest that can be customized */
38 | defaultManifest = {
39 | return manifest {
40 | def git_cmd = "git rev-parse HEAD"
41 | def git_proc = git_cmd.execute()
42 | attributes 'SCM-Revision': git_proc.text.trim()
43 | attributes 'Timestamp': String.valueOf(System.currentTimeMillis())
44 | attributes 'Build-Host': InetAddress.localHost.hostName
45 | }
46 | }
47 |
48 | defaultBlank = { closure ->
49 | try {
50 | closure()
51 | } catch (MissingPropertyException e) {
52 | ''
53 | }
54 | }
55 |
56 | }
57 |
58 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
59 | // Identifiers
60 |
61 | group = project_group
62 | version = project_version
63 | ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
64 |
65 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
66 | // Plugins
67 |
68 | apply plugin: 'java'
69 | sourceCompatibility = project_jdk
70 | targetCompatibility = project_jdk
71 |
72 | apply plugin: 'idea'
73 | apply plugin: 'maven'
74 | apply plugin: 'signing'
75 |
76 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
77 | // Dependencies
78 |
79 | repositories {
80 | mavenCentral()
81 | }
82 |
83 | dependencies {
84 | compile 'com.google.guava:guava:[10.+,)'
85 | compile 'com.google.code.findbugs:jsr305:2.0.2'
86 |
87 | // junit testing
88 | testCompile 'junit:junit:4.11'
89 | testCompile 'org.mockito:mockito-all:1.9.5'
90 | }
91 |
92 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
93 | // Artifacts
94 |
95 | jar {
96 | doFirst {
97 | // Timestamp changes on every build. By evaluating it later, won't needlessly fail up-to-date checks.
98 | manifest = defaultManifest()
99 | }
100 | }
101 |
102 | task javadocJar(type: Jar, dependsOn: javadoc) {
103 | classifier = 'javadoc'
104 | from 'build/docs/javadoc'
105 | doFirst {
106 | // Timestamp changes on every build. By evaluating it later, won't needlessly fail up-to-date checks.
107 | manifest = defaultManifest()
108 | }
109 | }
110 |
111 | task sourcesJar(type: Jar) {
112 | classifier = 'sources'
113 | from sourceSets.main.allSource
114 | doFirst {
115 | // Timestamp changes on every build. By evaluating it later, won't needlessly fail up-to-date checks.
116 | manifest = defaultManifest()
117 | }
118 | }
119 |
120 | artifacts {
121 | archives jar
122 | archives javadocJar
123 | archives sourcesJar
124 | }
125 |
126 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
127 | // Publishing
128 |
129 | def promptPasswords() {
130 | def console = System.console()
131 | if (console == null) {
132 | throw new RuntimeException("Console is not available. Try running from a terminal or with --no-daemon")
133 | }
134 |
135 | def sonatypeOssPassword = console.readPassword("\nEnter Sonatype password: ")
136 | sonatypeOssPassword = new String(sonatypeOssPassword)
137 | if (sonatypeOssPassword.size() <= 0) {
138 | throw new InvalidUserDataException("Empty Sonatype password")
139 | }
140 |
141 | def signingKeyPassword = console.readPassword("\nEnter signing key password: ")
142 | signingKeyPassword = new String(signingKeyPassword)
143 | if (signingKeyPassword.size() <= 0) {
144 | throw new InvalidUserDataException("Empty signing key password")
145 | }
146 |
147 | // set passwords globally for access elsewhere
148 | allprojects { ext."signing.password" = signingKeyPassword }
149 | allprojects { ext."sonatypePassword" = sonatypeOssPassword }
150 |
151 | }
152 |
153 | if(isReleaseVersion && gradle.startParameter.taskNames.contains("uploadArchives")) {
154 | signing {
155 | sign configurations.archives
156 | }
157 | } else {
158 | task signArchives {
159 | // do nothing
160 | }
161 | }
162 |
163 | uploadArchives {
164 | repositories {
165 | if (gradle.startParameter.taskNames.contains("uploadArchives")) {
166 | mavenDeployer {
167 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
168 |
169 | // for snapshots https://oss.sonatype.org/content/repositories/snapshots
170 | // for staging/release https://oss.sonatype.org/service/local/staging/deploy/maven2
171 | repository(
172 | url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
173 | ) {
174 | promptPasswords()
175 | authentication(userName: sonatypeUsername, password: sonatypePassword)
176 | }
177 |
178 | pom.project project_pom
179 | }
180 | }
181 | }
182 | }
183 |
184 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
185 | // Utilities
186 |
187 | // enable Gradle Wrapper support for bootstrapping with specific version
188 | task wrapper(type: Wrapper) {
189 | gradleVersion = '2.4'
190 | }
191 |
192 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
193 | // IDE
194 |
195 | idea {
196 | module {
197 | downloadSources = true
198 | }
199 | project {
200 | languageLevel = project_jdk
201 | vcs = 'Git'
202 | }
203 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rholder/guava-retrying/177b6c9b9f3e7957f404f0bdb8e23374cb1de43f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat May 09 20:16:02 CDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/Attempt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import java.util.concurrent.ExecutionException;
20 |
21 | /**
22 | * An attempt of a call, which resulted either in a result returned by the call,
23 | * or in a Throwable thrown by the call.
24 | *
25 | * @param The type returned by the wrapped callable.
26 | * @author JB
27 | */
28 | public interface Attempt {
29 |
30 | /**
31 | * Returns the result of the attempt, if any.
32 | *
33 | * @return the result of the attempt
34 | * @throws ExecutionException if an exception was thrown by the attempt. The thrown
35 | * exception is set as the cause of the ExecutionException
36 | */
37 | public V get() throws ExecutionException;
38 |
39 | /**
40 | * Tells if the call returned a result or not
41 | *
42 | * @return true
if the call returned a result, false
43 | * if it threw an exception
44 | */
45 | public boolean hasResult();
46 |
47 | /**
48 | * Tells if the call threw an exception or not
49 | *
50 | * @return true
if the call threw an exception, false
51 | * if it returned a result
52 | */
53 | public boolean hasException();
54 |
55 | /**
56 | * Gets the result of the call
57 | *
58 | * @return the result of the call
59 | * @throws IllegalStateException if the call didn't return a result, but threw an exception,
60 | * as indicated by {@link #hasResult()}
61 | */
62 | public V getResult() throws IllegalStateException;
63 |
64 | /**
65 | * Gets the exception thrown by the call
66 | *
67 | * @return the exception thrown by the call
68 | * @throws IllegalStateException if the call didn't throw an exception,
69 | * as indicated by {@link #hasException()}
70 | */
71 | public Throwable getExceptionCause() throws IllegalStateException;
72 |
73 | /**
74 | * The number, starting from 1, of this attempt.
75 | *
76 | * @return the attempt number
77 | */
78 | public long getAttemptNumber();
79 |
80 | /**
81 | * The delay since the start of the first attempt, in milliseconds.
82 | *
83 | * @return the delay since the start of the first attempt, in milliseconds
84 | */
85 | public long getDelaySinceFirstAttempt();
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/AttemptTimeLimiter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import java.util.concurrent.Callable;
20 |
21 | /**
22 | * A rule to wrap any single attempt in a time limit, where it will possibly be interrupted if the limit is exceeded.
23 | *
24 | * @param return type of Callable
25 | * @author Jason Dunkelberger (dirkraft)
26 | */
27 | public interface AttemptTimeLimiter {
28 | /**
29 | * @param callable to subject to the time limit
30 | * @return the return of the given callable
31 | * @throws Exception any exception from this invocation
32 | */
33 | V call(Callable callable) throws Exception;
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/AttemptTimeLimiters.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.google.common.base.Preconditions;
20 | import com.google.common.util.concurrent.SimpleTimeLimiter;
21 | import com.google.common.util.concurrent.TimeLimiter;
22 |
23 | import javax.annotation.Nonnull;
24 | import javax.annotation.concurrent.Immutable;
25 | import java.util.concurrent.Callable;
26 | import java.util.concurrent.ExecutorService;
27 | import java.util.concurrent.TimeUnit;
28 |
29 | /**
30 | * Factory class for instances of {@link AttemptTimeLimiter}
31 | *
32 | * @author Jason Dunkelberger (dirkraft)
33 | */
34 | public class AttemptTimeLimiters {
35 |
36 | private AttemptTimeLimiters() {
37 | }
38 |
39 | /**
40 | * @param The type of the computation result.
41 | * @return an {@link AttemptTimeLimiter} impl which has no time limit
42 | */
43 | public static AttemptTimeLimiter noTimeLimit() {
44 | return new NoAttemptTimeLimit();
45 | }
46 |
47 | /**
48 | * For control over thread management, it is preferable to offer an {@link ExecutorService} through the other
49 | * factory method, {@link #fixedTimeLimit(long, TimeUnit, ExecutorService)}. See the note on
50 | * {@link SimpleTimeLimiter#SimpleTimeLimiter(ExecutorService)}, which this AttemptTimeLimiter uses.
51 | *
52 | * @param duration that an attempt may persist before being circumvented
53 | * @param timeUnit of the 'duration' arg
54 | * @param the type of the computation result
55 | * @return an {@link AttemptTimeLimiter} with a fixed time limit for each attempt
56 | */
57 | public static AttemptTimeLimiter fixedTimeLimit(long duration, @Nonnull TimeUnit timeUnit) {
58 | Preconditions.checkNotNull(timeUnit);
59 | return new FixedAttemptTimeLimit(duration, timeUnit);
60 | }
61 |
62 | /**
63 | * @param duration that an attempt may persist before being circumvented
64 | * @param timeUnit of the 'duration' arg
65 | * @param executorService used to enforce time limit
66 | * @param the type of the computation result
67 | * @return an {@link AttemptTimeLimiter} with a fixed time limit for each attempt
68 | */
69 | public static AttemptTimeLimiter fixedTimeLimit(long duration, @Nonnull TimeUnit timeUnit, @Nonnull ExecutorService executorService) {
70 | Preconditions.checkNotNull(timeUnit);
71 | return new FixedAttemptTimeLimit(duration, timeUnit, executorService);
72 | }
73 |
74 | @Immutable
75 | private static final class NoAttemptTimeLimit implements AttemptTimeLimiter {
76 | @Override
77 | public V call(Callable callable) throws Exception {
78 | return callable.call();
79 | }
80 | }
81 |
82 | @Immutable
83 | private static final class FixedAttemptTimeLimit implements AttemptTimeLimiter {
84 |
85 | private final TimeLimiter timeLimiter;
86 | private final long duration;
87 | private final TimeUnit timeUnit;
88 |
89 | public FixedAttemptTimeLimit(long duration, @Nonnull TimeUnit timeUnit) {
90 | this(new SimpleTimeLimiter(), duration, timeUnit);
91 | }
92 |
93 | public FixedAttemptTimeLimit(long duration, @Nonnull TimeUnit timeUnit, @Nonnull ExecutorService executorService) {
94 | this(new SimpleTimeLimiter(executorService), duration, timeUnit);
95 | }
96 |
97 | private FixedAttemptTimeLimit(@Nonnull TimeLimiter timeLimiter, long duration, @Nonnull TimeUnit timeUnit) {
98 | Preconditions.checkNotNull(timeLimiter);
99 | Preconditions.checkNotNull(timeUnit);
100 | this.timeLimiter = timeLimiter;
101 | this.duration = duration;
102 | this.timeUnit = timeUnit;
103 | }
104 |
105 | @Override
106 | public V call(Callable callable) throws Exception {
107 | return timeLimiter.callWithTimeout(callable, duration, timeUnit, true);
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/BlockStrategies.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import javax.annotation.concurrent.Immutable;
20 |
21 | /**
22 | * Factory class for {@link BlockStrategy} instances.
23 | */
24 | public final class BlockStrategies {
25 |
26 | private static final BlockStrategy THREAD_SLEEP_STRATEGY = new ThreadSleepStrategy();
27 |
28 | private BlockStrategies() {
29 | }
30 |
31 | /**
32 | * Returns a block strategy that puts the current thread to sleep between
33 | * retries.
34 | *
35 | * @return a block strategy that puts the current thread to sleep between retries
36 | */
37 | public static BlockStrategy threadSleepStrategy() {
38 | return THREAD_SLEEP_STRATEGY;
39 | }
40 |
41 | @Immutable
42 | private static class ThreadSleepStrategy implements BlockStrategy {
43 |
44 | @Override
45 | public void block(long sleepTime) throws InterruptedException {
46 | Thread.sleep(sleepTime);
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/BlockStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | /**
20 | * This is a strategy used to decide how a retryer should block between retry
21 | * attempts. Normally this is just a Thread.sleep(), but implementations can be
22 | * something more elaborate if desired.
23 | */
24 | public interface BlockStrategy {
25 |
26 | /**
27 | * Attempt to block for the designated amount of time. Implementations
28 | * that don't block or otherwise delay the processing from within this
29 | * method for the given sleep duration can significantly modify the behavior
30 | * of any configured {@link com.github.rholder.retry.WaitStrategy}. Caution
31 | * is advised when generating your own implementations.
32 | *
33 | * @param sleepTime the computed sleep duration in milliseconds
34 | * @throws InterruptedException
35 | */
36 | void block(long sleepTime) throws InterruptedException;
37 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/RetryException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import javax.annotation.Nonnull;
20 | import javax.annotation.concurrent.Immutable;
21 |
22 | import static com.google.common.base.Preconditions.checkNotNull;
23 |
24 | /**
25 | * An exception indicating that none of the attempts of the {@link Retryer}
26 | * succeeded. If the last {@link Attempt} resulted in an Exception, it is set as
27 | * the cause of the {@link RetryException}.
28 | *
29 | * @author JB
30 | */
31 | @Immutable
32 | public final class RetryException extends Exception {
33 |
34 | private final int numberOfFailedAttempts;
35 | private final Attempt> lastFailedAttempt;
36 |
37 | /**
38 | * If the last {@link Attempt} had an Exception, ensure it is available in
39 | * the stack trace.
40 | *
41 | * @param numberOfFailedAttempts times we've tried and failed
42 | * @param lastFailedAttempt what happened the last time we failed
43 | */
44 | public RetryException(int numberOfFailedAttempts, @Nonnull Attempt> lastFailedAttempt) {
45 | this("Retrying failed to complete successfully after " + numberOfFailedAttempts + " attempts.", numberOfFailedAttempts, lastFailedAttempt);
46 | }
47 |
48 | /**
49 | * If the last {@link Attempt} had an Exception, ensure it is available in
50 | * the stack trace.
51 | *
52 | * @param message Exception description to be added to the stack trace
53 | * @param numberOfFailedAttempts times we've tried and failed
54 | * @param lastFailedAttempt what happened the last time we failed
55 | */
56 | public RetryException(String message, int numberOfFailedAttempts, Attempt> lastFailedAttempt) {
57 | super(message, checkNotNull(lastFailedAttempt, "Last attempt was null").hasException() ? lastFailedAttempt.getExceptionCause() : null);
58 | this.numberOfFailedAttempts = numberOfFailedAttempts;
59 | this.lastFailedAttempt = lastFailedAttempt;
60 | }
61 |
62 | /**
63 | * Returns the number of failed attempts
64 | *
65 | * @return the number of failed attempts
66 | */
67 | public int getNumberOfFailedAttempts() {
68 | return numberOfFailedAttempts;
69 | }
70 |
71 | /**
72 | * Returns the last failed attempt
73 | *
74 | * @return the last failed attempt
75 | */
76 | public Attempt> getLastFailedAttempt() {
77 | return lastFailedAttempt;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/RetryListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.google.common.annotations.Beta;
20 |
21 | /**
22 | * This listener provides callbacks for several events that occur when running
23 | * code through a {@link Retryer} instance.
24 | */
25 | @Beta
26 | public interface RetryListener {
27 |
28 | /**
29 | * This method with fire no matter what the result is and before the
30 | * rejection predicate and stop strategies are applied.
31 | *
32 | * @param attempt the current {@link Attempt}
33 | * @param the type returned by the retryer callable
34 | */
35 | void onRetry(Attempt attempt);
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/Retryer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.google.common.annotations.Beta;
20 | import com.google.common.base.Preconditions;
21 | import com.google.common.base.Predicate;
22 |
23 | import javax.annotation.Nonnull;
24 | import javax.annotation.concurrent.Immutable;
25 | import java.util.ArrayList;
26 | import java.util.Collection;
27 | import java.util.concurrent.Callable;
28 | import java.util.concurrent.ExecutionException;
29 | import java.util.concurrent.TimeUnit;
30 |
31 | /**
32 | * A retryer, which executes a call, and retries it until it succeeds, or
33 | * a stop strategy decides to stop retrying. A wait strategy is used to sleep
34 | * between attempts. The strategy to decide if the call succeeds or not is
35 | * also configurable.
36 | *
37 | * A retryer can also wrap the callable into a RetryerCallable, which can be submitted to an executor.
38 | *
39 | * Retryer instances are better constructed with a {@link RetryerBuilder}. A retryer
40 | * is thread-safe, provided the arguments passed to its constructor are thread-safe.
41 | *
42 | * @param the type of the call return value
43 | * @author JB
44 | * @author Jason Dunkelberger (dirkraft)
45 | */
46 | public final class Retryer {
47 | private final StopStrategy stopStrategy;
48 | private final WaitStrategy waitStrategy;
49 | private final BlockStrategy blockStrategy;
50 | private final AttemptTimeLimiter attemptTimeLimiter;
51 | private final Predicate> rejectionPredicate;
52 | private final Collection listeners;
53 |
54 | /**
55 | * Constructor
56 | *
57 | * @param stopStrategy the strategy used to decide when the retryer must stop retrying
58 | * @param waitStrategy the strategy used to decide how much time to sleep between attempts
59 | * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
60 | * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
61 | * strategy indicates otherwise or the thread is interrupted.
62 | */
63 | public Retryer(@Nonnull StopStrategy stopStrategy,
64 | @Nonnull WaitStrategy waitStrategy,
65 | @Nonnull Predicate> rejectionPredicate) {
66 |
67 | this(AttemptTimeLimiters.noTimeLimit(), stopStrategy, waitStrategy, BlockStrategies.threadSleepStrategy(), rejectionPredicate);
68 | }
69 |
70 | /**
71 | * Constructor
72 | *
73 | * @param attemptTimeLimiter to prevent from any single attempt from spinning infinitely
74 | * @param stopStrategy the strategy used to decide when the retryer must stop retrying
75 | * @param waitStrategy the strategy used to decide how much time to sleep between attempts
76 | * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
77 | * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
78 | * strategy indicates otherwise or the thread is interrupted.
79 | */
80 | public Retryer(@Nonnull AttemptTimeLimiter attemptTimeLimiter,
81 | @Nonnull StopStrategy stopStrategy,
82 | @Nonnull WaitStrategy waitStrategy,
83 | @Nonnull Predicate> rejectionPredicate) {
84 | this(attemptTimeLimiter, stopStrategy, waitStrategy, BlockStrategies.threadSleepStrategy(), rejectionPredicate);
85 | }
86 |
87 | /**
88 | * Constructor
89 | *
90 | * @param attemptTimeLimiter to prevent from any single attempt from spinning infinitely
91 | * @param stopStrategy the strategy used to decide when the retryer must stop retrying
92 | * @param waitStrategy the strategy used to decide how much time to sleep between attempts
93 | * @param blockStrategy the strategy used to decide how to block between retry attempts; eg, Thread#sleep(), latches, etc.
94 | * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
95 | * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
96 | * strategy indicates otherwise or the thread is interrupted.
97 | */
98 | public Retryer(@Nonnull AttemptTimeLimiter attemptTimeLimiter,
99 | @Nonnull StopStrategy stopStrategy,
100 | @Nonnull WaitStrategy waitStrategy,
101 | @Nonnull BlockStrategy blockStrategy,
102 | @Nonnull Predicate> rejectionPredicate) {
103 | this(attemptTimeLimiter, stopStrategy, waitStrategy, blockStrategy, rejectionPredicate, new ArrayList());
104 | }
105 |
106 | /**
107 | * Constructor
108 | *
109 | * @param attemptTimeLimiter to prevent from any single attempt from spinning infinitely
110 | * @param stopStrategy the strategy used to decide when the retryer must stop retrying
111 | * @param waitStrategy the strategy used to decide how much time to sleep between attempts
112 | * @param blockStrategy the strategy used to decide how to block between retry attempts; eg, Thread#sleep(), latches, etc.
113 | * @param rejectionPredicate the predicate used to decide if the attempt must be rejected
114 | * or not. If an attempt is rejected, the retryer will retry the call, unless the stop
115 | * strategy indicates otherwise or the thread is interrupted.
116 | * @param listeners collection of retry listeners
117 | */
118 | @Beta
119 | public Retryer(@Nonnull AttemptTimeLimiter attemptTimeLimiter,
120 | @Nonnull StopStrategy stopStrategy,
121 | @Nonnull WaitStrategy waitStrategy,
122 | @Nonnull BlockStrategy blockStrategy,
123 | @Nonnull Predicate> rejectionPredicate,
124 | @Nonnull Collection listeners) {
125 | Preconditions.checkNotNull(attemptTimeLimiter, "timeLimiter may not be null");
126 | Preconditions.checkNotNull(stopStrategy, "stopStrategy may not be null");
127 | Preconditions.checkNotNull(waitStrategy, "waitStrategy may not be null");
128 | Preconditions.checkNotNull(blockStrategy, "blockStrategy may not be null");
129 | Preconditions.checkNotNull(rejectionPredicate, "rejectionPredicate may not be null");
130 | Preconditions.checkNotNull(listeners, "listeners may not null");
131 |
132 | this.attemptTimeLimiter = attemptTimeLimiter;
133 | this.stopStrategy = stopStrategy;
134 | this.waitStrategy = waitStrategy;
135 | this.blockStrategy = blockStrategy;
136 | this.rejectionPredicate = rejectionPredicate;
137 | this.listeners = listeners;
138 | }
139 |
140 | /**
141 | * Executes the given callable. If the rejection predicate
142 | * accepts the attempt, the stop strategy is used to decide if a new attempt
143 | * must be made. Then the wait strategy is used to decide how much time to sleep
144 | * and a new attempt is made.
145 | *
146 | * @param callable the callable task to be executed
147 | * @return the computed result of the given callable
148 | * @throws ExecutionException if the given callable throws an exception, and the
149 | * rejection predicate considers the attempt as successful. The original exception
150 | * is wrapped into an ExecutionException.
151 | * @throws RetryException if all the attempts failed before the stop strategy decided
152 | * to abort, or the thread was interrupted. Note that if the thread is interrupted,
153 | * this exception is thrown and the thread's interrupt status is set.
154 | */
155 | public V call(Callable callable) throws ExecutionException, RetryException {
156 | long startTime = System.nanoTime();
157 | for (int attemptNumber = 1; ; attemptNumber++) {
158 | Attempt attempt;
159 | try {
160 | V result = attemptTimeLimiter.call(callable);
161 | attempt = new ResultAttempt(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
162 | } catch (Throwable t) {
163 | attempt = new ExceptionAttempt(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
164 | }
165 |
166 | for (RetryListener listener : listeners) {
167 | listener.onRetry(attempt);
168 | }
169 |
170 | if (!rejectionPredicate.apply(attempt)) {
171 | return attempt.get();
172 | }
173 | if (stopStrategy.shouldStop(attempt)) {
174 | throw new RetryException(attemptNumber, attempt);
175 | } else {
176 | long sleepTime = waitStrategy.computeSleepTime(attempt);
177 | try {
178 | blockStrategy.block(sleepTime);
179 | } catch (InterruptedException e) {
180 | Thread.currentThread().interrupt();
181 | throw new RetryException(attemptNumber, attempt);
182 | }
183 | }
184 | }
185 | }
186 |
187 | /**
188 | * Wraps the given {@link Callable} in a {@link RetryerCallable}, which can
189 | * be submitted to an executor. The returned {@link RetryerCallable} uses
190 | * this {@link Retryer} instance to call the given {@link Callable}.
191 | *
192 | * @param callable the callable to wrap
193 | * @return a {@link RetryerCallable} that behaves like the given {@link Callable} with retry behavior defined by this {@link Retryer}
194 | */
195 | public RetryerCallable wrap(Callable callable) {
196 | return new RetryerCallable(this, callable);
197 | }
198 |
199 | @Immutable
200 | static final class ResultAttempt implements Attempt {
201 | private final R result;
202 | private final long attemptNumber;
203 | private final long delaySinceFirstAttempt;
204 |
205 | public ResultAttempt(R result, long attemptNumber, long delaySinceFirstAttempt) {
206 | this.result = result;
207 | this.attemptNumber = attemptNumber;
208 | this.delaySinceFirstAttempt = delaySinceFirstAttempt;
209 | }
210 |
211 | @Override
212 | public R get() throws ExecutionException {
213 | return result;
214 | }
215 |
216 | @Override
217 | public boolean hasResult() {
218 | return true;
219 | }
220 |
221 | @Override
222 | public boolean hasException() {
223 | return false;
224 | }
225 |
226 | @Override
227 | public R getResult() throws IllegalStateException {
228 | return result;
229 | }
230 |
231 | @Override
232 | public Throwable getExceptionCause() throws IllegalStateException {
233 | throw new IllegalStateException("The attempt resulted in a result, not in an exception");
234 | }
235 |
236 | @Override
237 | public long getAttemptNumber() {
238 | return attemptNumber;
239 | }
240 |
241 | @Override
242 | public long getDelaySinceFirstAttempt() {
243 | return delaySinceFirstAttempt;
244 | }
245 | }
246 |
247 | @Immutable
248 | static final class ExceptionAttempt implements Attempt {
249 | private final ExecutionException e;
250 | private final long attemptNumber;
251 | private final long delaySinceFirstAttempt;
252 |
253 | public ExceptionAttempt(Throwable cause, long attemptNumber, long delaySinceFirstAttempt) {
254 | this.e = new ExecutionException(cause);
255 | this.attemptNumber = attemptNumber;
256 | this.delaySinceFirstAttempt = delaySinceFirstAttempt;
257 | }
258 |
259 | @Override
260 | public R get() throws ExecutionException {
261 | throw e;
262 | }
263 |
264 | @Override
265 | public boolean hasResult() {
266 | return false;
267 | }
268 |
269 | @Override
270 | public boolean hasException() {
271 | return true;
272 | }
273 |
274 | @Override
275 | public R getResult() throws IllegalStateException {
276 | throw new IllegalStateException("The attempt resulted in an exception, not in a result");
277 | }
278 |
279 | @Override
280 | public Throwable getExceptionCause() throws IllegalStateException {
281 | return e.getCause();
282 | }
283 |
284 | @Override
285 | public long getAttemptNumber() {
286 | return attemptNumber;
287 | }
288 |
289 | @Override
290 | public long getDelaySinceFirstAttempt() {
291 | return delaySinceFirstAttempt;
292 | }
293 | }
294 |
295 | /**
296 | * A {@link Callable} which wraps another {@link Callable} in order to add
297 | * retrying behavior from a given {@link Retryer} instance.
298 | *
299 | * @author JB
300 | */
301 | public static class RetryerCallable implements Callable {
302 | private Retryer retryer;
303 | private Callable callable;
304 |
305 | private RetryerCallable(Retryer retryer,
306 | Callable callable) {
307 | this.retryer = retryer;
308 | this.callable = callable;
309 | }
310 |
311 | /**
312 | * Makes the enclosing retryer call the wrapped callable.
313 | *
314 | * @see Retryer#call(Callable)
315 | */
316 | @Override
317 | public X call() throws ExecutionException, RetryException {
318 | return retryer.call(callable);
319 | }
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/RetryerBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.google.common.base.Preconditions;
20 | import com.google.common.base.Predicate;
21 | import com.google.common.base.Predicates;
22 |
23 | import javax.annotation.Nonnull;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | /**
28 | * A builder used to configure and create a {@link Retryer}.
29 | *
30 | * @param result of a {@link Retryer}'s call, the type of the call return value
31 | * @author JB
32 | * @author Jason Dunkelberger (dirkraft)
33 | */
34 | public class RetryerBuilder {
35 | private AttemptTimeLimiter attemptTimeLimiter;
36 | private StopStrategy stopStrategy;
37 | private WaitStrategy waitStrategy;
38 | private BlockStrategy blockStrategy;
39 | private Predicate> rejectionPredicate = Predicates.alwaysFalse();
40 | private List listeners = new ArrayList();
41 |
42 | private RetryerBuilder() {
43 | }
44 |
45 | /**
46 | * Constructs a new builder
47 | *
48 | * @param result of a {@link Retryer}'s call, the type of the call return value
49 | * @return the new builder
50 | */
51 | public static RetryerBuilder newBuilder() {
52 | return new RetryerBuilder();
53 | }
54 |
55 | /**
56 | * Adds a listener that will be notified of each attempt that is made
57 | *
58 | * @param listener Listener to add
59 | * @return this
60 | */
61 | public RetryerBuilder withRetryListener(@Nonnull RetryListener listener) {
62 | Preconditions.checkNotNull(listener, "listener may not be null");
63 | listeners.add(listener);
64 | return this;
65 | }
66 |
67 | /**
68 | * Sets the wait strategy used to decide how long to sleep between failed attempts.
69 | * The default strategy is to retry immediately after a failed attempt.
70 | *
71 | * @param waitStrategy the strategy used to sleep between failed attempts
72 | * @return this
73 | * @throws IllegalStateException if a wait strategy has already been set.
74 | */
75 | public RetryerBuilder withWaitStrategy(@Nonnull WaitStrategy waitStrategy) throws IllegalStateException {
76 | Preconditions.checkNotNull(waitStrategy, "waitStrategy may not be null");
77 | Preconditions.checkState(this.waitStrategy == null, "a wait strategy has already been set %s", this.waitStrategy);
78 | this.waitStrategy = waitStrategy;
79 | return this;
80 | }
81 |
82 | /**
83 | * Sets the stop strategy used to decide when to stop retrying. The default strategy is to not stop at all .
84 | *
85 | * @param stopStrategy the strategy used to decide when to stop retrying
86 | * @return this
87 | * @throws IllegalStateException if a stop strategy has already been set.
88 | */
89 | public RetryerBuilder withStopStrategy(@Nonnull StopStrategy stopStrategy) throws IllegalStateException {
90 | Preconditions.checkNotNull(stopStrategy, "stopStrategy may not be null");
91 | Preconditions.checkState(this.stopStrategy == null, "a stop strategy has already been set %s", this.stopStrategy);
92 | this.stopStrategy = stopStrategy;
93 | return this;
94 | }
95 |
96 |
97 | /**
98 | * Sets the block strategy used to decide how to block between retry attempts. The default strategy is to use Thread#sleep().
99 | *
100 | * @param blockStrategy the strategy used to decide how to block between retry attempts
101 | * @return this
102 | * @throws IllegalStateException if a block strategy has already been set.
103 | */
104 | public RetryerBuilder withBlockStrategy(@Nonnull BlockStrategy blockStrategy) throws IllegalStateException {
105 | Preconditions.checkNotNull(blockStrategy, "blockStrategy may not be null");
106 | Preconditions.checkState(this.blockStrategy == null, "a block strategy has already been set %s", this.blockStrategy);
107 | this.blockStrategy = blockStrategy;
108 | return this;
109 | }
110 |
111 |
112 | /**
113 | * Configures the retryer to limit the duration of any particular attempt by the given duration.
114 | *
115 | * @param attemptTimeLimiter to apply to each attempt
116 | * @return this
117 | */
118 | public RetryerBuilder withAttemptTimeLimiter(@Nonnull AttemptTimeLimiter attemptTimeLimiter) {
119 | Preconditions.checkNotNull(attemptTimeLimiter);
120 | this.attemptTimeLimiter = attemptTimeLimiter;
121 | return this;
122 | }
123 |
124 | /**
125 | * Configures the retryer to retry if an exception (i.e. any Exception
or subclass
126 | * of Exception
) is thrown by the call.
127 | *
128 | * @return this
129 | */
130 | public RetryerBuilder retryIfException() {
131 | rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate(Exception.class));
132 | return this;
133 | }
134 |
135 | /**
136 | * Configures the retryer to retry if a runtime exception (i.e. any RuntimeException
or subclass
137 | * of RuntimeException
) is thrown by the call.
138 | *
139 | * @return this
140 | */
141 | public RetryerBuilder retryIfRuntimeException() {
142 | rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate(RuntimeException.class));
143 | return this;
144 | }
145 |
146 | /**
147 | * Configures the retryer to retry if an exception of the given class (or subclass of the given class) is
148 | * thrown by the call.
149 | *
150 | * @param exceptionClass the type of the exception which should cause the retryer to retry
151 | * @return this
152 | */
153 | public RetryerBuilder retryIfExceptionOfType(@Nonnull Class extends Throwable> exceptionClass) {
154 | Preconditions.checkNotNull(exceptionClass, "exceptionClass may not be null");
155 | rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate(exceptionClass));
156 | return this;
157 | }
158 |
159 | /**
160 | * Configures the retryer to retry if an exception satisfying the given predicate is
161 | * thrown by the call.
162 | *
163 | * @param exceptionPredicate the predicate which causes a retry if satisfied
164 | * @return this
165 | */
166 | public RetryerBuilder retryIfException(@Nonnull Predicate exceptionPredicate) {
167 | Preconditions.checkNotNull(exceptionPredicate, "exceptionPredicate may not be null");
168 | rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionPredicate(exceptionPredicate));
169 | return this;
170 | }
171 |
172 | /**
173 | * Configures the retryer to retry if the result satisfies the given predicate.
174 | *
175 | * @param resultPredicate a predicate applied to the result, and which causes the retryer
176 | * to retry if the predicate is satisfied
177 | * @return this
178 | */
179 | public RetryerBuilder retryIfResult(@Nonnull Predicate resultPredicate) {
180 | Preconditions.checkNotNull(resultPredicate, "resultPredicate may not be null");
181 | rejectionPredicate = Predicates.or(rejectionPredicate, new ResultPredicate(resultPredicate));
182 | return this;
183 | }
184 |
185 | /**
186 | * Builds the retryer.
187 | *
188 | * @return the built retryer.
189 | */
190 | public Retryer build() {
191 | AttemptTimeLimiter theAttemptTimeLimiter = attemptTimeLimiter == null ? AttemptTimeLimiters.noTimeLimit() : attemptTimeLimiter;
192 | StopStrategy theStopStrategy = stopStrategy == null ? StopStrategies.neverStop() : stopStrategy;
193 | WaitStrategy theWaitStrategy = waitStrategy == null ? WaitStrategies.noWait() : waitStrategy;
194 | BlockStrategy theBlockStrategy = blockStrategy == null ? BlockStrategies.threadSleepStrategy() : blockStrategy;
195 |
196 | return new Retryer(theAttemptTimeLimiter, theStopStrategy, theWaitStrategy, theBlockStrategy, rejectionPredicate, listeners);
197 | }
198 |
199 | private static final class ExceptionClassPredicate implements Predicate> {
200 |
201 | private Class extends Throwable> exceptionClass;
202 |
203 | public ExceptionClassPredicate(Class extends Throwable> exceptionClass) {
204 | this.exceptionClass = exceptionClass;
205 | }
206 |
207 | @Override
208 | public boolean apply(Attempt attempt) {
209 | if (!attempt.hasException()) {
210 | return false;
211 | }
212 | return exceptionClass.isAssignableFrom(attempt.getExceptionCause().getClass());
213 | }
214 | }
215 |
216 | private static final class ResultPredicate implements Predicate> {
217 |
218 | private Predicate delegate;
219 |
220 | public ResultPredicate(Predicate delegate) {
221 | this.delegate = delegate;
222 | }
223 |
224 | @Override
225 | public boolean apply(Attempt attempt) {
226 | if (!attempt.hasResult()) {
227 | return false;
228 | }
229 | V result = attempt.getResult();
230 | return delegate.apply(result);
231 | }
232 | }
233 |
234 | private static final class ExceptionPredicate implements Predicate> {
235 |
236 | private Predicate delegate;
237 |
238 | public ExceptionPredicate(Predicate delegate) {
239 | this.delegate = delegate;
240 | }
241 |
242 | @Override
243 | public boolean apply(Attempt attempt) {
244 | if (!attempt.hasException()) {
245 | return false;
246 | }
247 | return delegate.apply(attempt.getExceptionCause());
248 | }
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/StopStrategies.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import java.util.concurrent.TimeUnit;
20 |
21 | import javax.annotation.Nonnull;
22 | import javax.annotation.concurrent.Immutable;
23 |
24 | import com.google.common.base.Preconditions;
25 |
26 | /**
27 | * Factory class for {@link StopStrategy} instances.
28 | *
29 | * @author JB
30 | */
31 | public final class StopStrategies {
32 | private static final StopStrategy NEVER_STOP = new NeverStopStrategy();
33 |
34 | private StopStrategies() {
35 | }
36 |
37 | /**
38 | * Returns a stop strategy which never stops retrying. It might be best to
39 | * try not to abuse services with this kind of behavior when small wait
40 | * intervals between retry attempts are being used.
41 | *
42 | * @return a stop strategy which never stops
43 | */
44 | public static StopStrategy neverStop() {
45 | return NEVER_STOP;
46 | }
47 |
48 | /**
49 | * Returns a stop strategy which stops after N failed attempts.
50 | *
51 | * @param attemptNumber the number of failed attempts before stopping
52 | * @return a stop strategy which stops after {@code attemptNumber} attempts
53 | */
54 | public static StopStrategy stopAfterAttempt(int attemptNumber) {
55 | return new StopAfterAttemptStrategy(attemptNumber);
56 | }
57 |
58 | /**
59 | * Returns a stop strategy which stops after a given delay. If an
60 | * unsuccessful attempt is made, this {@link StopStrategy} will check if the
61 | * amount of time that's passed from the first attempt has exceeded the
62 | * given delay amount. If it has exceeded this delay, then using this
63 | * strategy causes the retrying to stop.
64 | *
65 | * @param delayInMillis the delay, in milliseconds, starting from first attempt
66 | * @return a stop strategy which stops after {@code delayInMillis} time in milliseconds
67 | * @deprecated Use {@link #stopAfterDelay(long, TimeUnit)} instead.
68 | */
69 | @Deprecated
70 | public static StopStrategy stopAfterDelay(long delayInMillis) {
71 | return stopAfterDelay(delayInMillis, TimeUnit.MILLISECONDS);
72 | }
73 |
74 | /**
75 | * Returns a stop strategy which stops after a given delay. If an
76 | * unsuccessful attempt is made, this {@link StopStrategy} will check if the
77 | * amount of time that's passed from the first attempt has exceeded the
78 | * given delay amount. If it has exceeded this delay, then using this
79 | * strategy causes the retrying to stop.
80 | *
81 | * @param duration the delay, starting from first attempt
82 | * @param timeUnit the unit of the duration
83 | * @return a stop strategy which stops after {@code delayInMillis} time in milliseconds
84 | */
85 | public static StopStrategy stopAfterDelay(long duration, @Nonnull TimeUnit timeUnit) {
86 | Preconditions.checkNotNull(timeUnit, "The time unit may not be null");
87 | return new StopAfterDelayStrategy(timeUnit.toMillis(duration));
88 | }
89 |
90 | @Immutable
91 | private static final class NeverStopStrategy implements StopStrategy {
92 | @Override
93 | public boolean shouldStop(Attempt failedAttempt) {
94 | return false;
95 | }
96 | }
97 |
98 | @Immutable
99 | private static final class StopAfterAttemptStrategy implements StopStrategy {
100 | private final int maxAttemptNumber;
101 |
102 | public StopAfterAttemptStrategy(int maxAttemptNumber) {
103 | Preconditions.checkArgument(maxAttemptNumber >= 1, "maxAttemptNumber must be >= 1 but is %d", maxAttemptNumber);
104 | this.maxAttemptNumber = maxAttemptNumber;
105 | }
106 |
107 | @Override
108 | public boolean shouldStop(Attempt failedAttempt) {
109 | return failedAttempt.getAttemptNumber() >= maxAttemptNumber;
110 | }
111 | }
112 |
113 | @Immutable
114 | private static final class StopAfterDelayStrategy implements StopStrategy {
115 | private final long maxDelay;
116 |
117 | public StopAfterDelayStrategy(long maxDelay) {
118 | Preconditions.checkArgument(maxDelay >= 0L, "maxDelay must be >= 0 but is %d", maxDelay);
119 | this.maxDelay = maxDelay;
120 | }
121 |
122 | @Override
123 | public boolean shouldStop(Attempt failedAttempt) {
124 | return failedAttempt.getDelaySinceFirstAttempt() >= maxDelay;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/StopStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | /**
20 | * A strategy used to decide if a retryer must stop retrying after a failed attempt or not.
21 | *
22 | * @author JB
23 | */
24 | public interface StopStrategy {
25 |
26 | /**
27 | * Returns true
if the retryer should stop retrying.
28 | *
29 | * @param failedAttempt the previous failed {@code Attempt}
30 | * @return true
if the retryer must stop, false
otherwise
31 | */
32 | boolean shouldStop(Attempt failedAttempt);
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/WaitStrategies.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.google.common.base.Function;
20 | import com.google.common.base.Preconditions;
21 | import com.google.common.collect.Lists;
22 |
23 | import javax.annotation.Nonnull;
24 | import javax.annotation.concurrent.Immutable;
25 | import java.util.List;
26 | import java.util.Random;
27 | import java.util.concurrent.TimeUnit;
28 |
29 | /**
30 | * Factory class for instances of {@link WaitStrategy}.
31 | *
32 | * @author JB
33 | */
34 | public final class WaitStrategies {
35 |
36 | private static final WaitStrategy NO_WAIT_STRATEGY = new FixedWaitStrategy(0L);
37 |
38 | private WaitStrategies() {
39 | }
40 |
41 | /**
42 | * Returns a wait strategy that doesn't sleep at all before retrying. Use this at your own risk.
43 | *
44 | * @return a wait strategy that doesn't wait between retries
45 | */
46 | public static WaitStrategy noWait() {
47 | return NO_WAIT_STRATEGY;
48 | }
49 |
50 | /**
51 | * Returns a wait strategy that sleeps a fixed amount of time before retrying.
52 | *
53 | * @param sleepTime the time to sleep
54 | * @param timeUnit the unit of the time to sleep
55 | * @return a wait strategy that sleeps a fixed amount of time
56 | * @throws IllegalStateException if the sleep time is < 0
57 | */
58 | public static WaitStrategy fixedWait(long sleepTime, @Nonnull TimeUnit timeUnit) throws IllegalStateException {
59 | Preconditions.checkNotNull(timeUnit, "The time unit may not be null");
60 | return new FixedWaitStrategy(timeUnit.toMillis(sleepTime));
61 | }
62 |
63 | /**
64 | * Returns a strategy that sleeps a random amount of time before retrying.
65 | *
66 | * @param maximumTime the maximum time to sleep
67 | * @param timeUnit the unit of the maximum time
68 | * @return a wait strategy with a random wait time
69 | * @throws IllegalStateException if the maximum sleep time is <= 0.
70 | */
71 | public static WaitStrategy randomWait(long maximumTime, @Nonnull TimeUnit timeUnit) {
72 | Preconditions.checkNotNull(timeUnit, "The time unit may not be null");
73 | return new RandomWaitStrategy(0L, timeUnit.toMillis(maximumTime));
74 | }
75 |
76 | /**
77 | * Returns a strategy that sleeps a random amount of time before retrying.
78 | *
79 | * @param minimumTime the minimum time to sleep
80 | * @param minimumTimeUnit the unit of the minimum time
81 | * @param maximumTime the maximum time to sleep
82 | * @param maximumTimeUnit the unit of the maximum time
83 | * @return a wait strategy with a random wait time
84 | * @throws IllegalStateException if the minimum sleep time is < 0, or if the
85 | * maximum sleep time is less than (or equals to) the minimum.
86 | */
87 | public static WaitStrategy randomWait(long minimumTime,
88 | @Nonnull TimeUnit minimumTimeUnit,
89 | long maximumTime,
90 | @Nonnull TimeUnit maximumTimeUnit) {
91 | Preconditions.checkNotNull(minimumTimeUnit, "The minimum time unit may not be null");
92 | Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
93 | return new RandomWaitStrategy(minimumTimeUnit.toMillis(minimumTime),
94 | maximumTimeUnit.toMillis(maximumTime));
95 | }
96 |
97 | /**
98 | * Returns a strategy that sleeps a fixed amount of time after the first
99 | * failed attempt and in incrementing amounts of time after each additional
100 | * failed attempt.
101 | *
102 | * @param initialSleepTime the time to sleep before retrying the first time
103 | * @param initialSleepTimeUnit the unit of the initial sleep time
104 | * @param increment the increment added to the previous sleep time after each failed attempt
105 | * @param incrementTimeUnit the unit of the increment
106 | * @return a wait strategy that incrementally sleeps an additional fixed time after each failed attempt
107 | */
108 | public static WaitStrategy incrementingWait(long initialSleepTime,
109 | @Nonnull TimeUnit initialSleepTimeUnit,
110 | long increment,
111 | @Nonnull TimeUnit incrementTimeUnit) {
112 | Preconditions.checkNotNull(initialSleepTimeUnit, "The initial sleep time unit may not be null");
113 | Preconditions.checkNotNull(incrementTimeUnit, "The increment time unit may not be null");
114 | return new IncrementingWaitStrategy(initialSleepTimeUnit.toMillis(initialSleepTime),
115 | incrementTimeUnit.toMillis(increment));
116 | }
117 |
118 | /**
119 | * Returns a strategy which sleeps for an exponential amount of time after the first failed attempt,
120 | * and in exponentially incrementing amounts after each failed attempt up to Long.MAX_VALUE.
121 | *
122 | * @return a wait strategy that increments with each failed attempt using exponential backoff
123 | */
124 | public static WaitStrategy exponentialWait() {
125 | return new ExponentialWaitStrategy(1, Long.MAX_VALUE);
126 | }
127 |
128 | /**
129 | * Returns a strategy which sleeps for an exponential amount of time after the first failed attempt,
130 | * and in exponentially incrementing amounts after each failed attempt up to the maximumTime.
131 | *
132 | * @param maximumTime the maximum time to sleep
133 | * @param maximumTimeUnit the unit of the maximum time
134 | * @return a wait strategy that increments with each failed attempt using exponential backoff
135 | */
136 | public static WaitStrategy exponentialWait(long maximumTime,
137 | @Nonnull TimeUnit maximumTimeUnit) {
138 | Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
139 | return new ExponentialWaitStrategy(1, maximumTimeUnit.toMillis(maximumTime));
140 | }
141 |
142 | /**
143 | * Returns a strategy which sleeps for an exponential amount of time after the first failed attempt,
144 | * and in exponentially incrementing amounts after each failed attempt up to the maximumTime.
145 | * The wait time between the retries can be controlled by the multiplier.
146 | * nextWaitTime = exponentialIncrement * {@code multiplier}.
147 | *
148 | * @param multiplier multiply the wait time calculated by this
149 | * @param maximumTime the maximum time to sleep
150 | * @param maximumTimeUnit the unit of the maximum time
151 | * @return a wait strategy that increments with each failed attempt using exponential backoff
152 | */
153 | public static WaitStrategy exponentialWait(long multiplier,
154 | long maximumTime,
155 | @Nonnull TimeUnit maximumTimeUnit) {
156 | Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
157 | return new ExponentialWaitStrategy(multiplier, maximumTimeUnit.toMillis(maximumTime));
158 | }
159 |
160 | /**
161 | * Returns a strategy which sleeps for an increasing amount of time after the first failed attempt,
162 | * and in Fibonacci increments after each failed attempt up to {@link Long#MAX_VALUE}.
163 | *
164 | * @return a wait strategy that increments with each failed attempt using a Fibonacci sequence
165 | */
166 | public static WaitStrategy fibonacciWait() {
167 | return new FibonacciWaitStrategy(1, Long.MAX_VALUE);
168 | }
169 |
170 | /**
171 | * Returns a strategy which sleeps for an increasing amount of time after the first failed attempt,
172 | * and in Fibonacci increments after each failed attempt up to the {@code maximumTime}.
173 | *
174 | * @param maximumTime the maximum time to sleep
175 | * @param maximumTimeUnit the unit of the maximum time
176 | * @return a wait strategy that increments with each failed attempt using a Fibonacci sequence
177 | */
178 | public static WaitStrategy fibonacciWait(long maximumTime,
179 | @Nonnull TimeUnit maximumTimeUnit) {
180 | Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
181 | return new FibonacciWaitStrategy(1, maximumTimeUnit.toMillis(maximumTime));
182 | }
183 |
184 | /**
185 | * Returns a strategy which sleeps for an increasing amount of time after the first failed attempt,
186 | * and in Fibonacci increments after each failed attempt up to the {@code maximumTime}.
187 | * The wait time between the retries can be controlled by the multiplier.
188 | * nextWaitTime = fibonacciIncrement * {@code multiplier}.
189 | *
190 | * @param multiplier multiply the wait time calculated by this
191 | * @param maximumTime the maximum time to sleep
192 | * @param maximumTimeUnit the unit of the maximum time
193 | * @return a wait strategy that increments with each failed attempt using a Fibonacci sequence
194 | */
195 | public static WaitStrategy fibonacciWait(long multiplier,
196 | long maximumTime,
197 | @Nonnull TimeUnit maximumTimeUnit) {
198 | Preconditions.checkNotNull(maximumTimeUnit, "The maximum time unit may not be null");
199 | return new FibonacciWaitStrategy(multiplier, maximumTimeUnit.toMillis(maximumTime));
200 | }
201 |
202 | /**
203 | * Returns a strategy which sleeps for an amount of time based on the Exception that occurred. The
204 | * {@code function} determines how the sleep time should be calculated for the given
205 | * {@code exceptionClass}. If the exception does not match, a wait time of 0 is returned.
206 | *
207 | * @param function function to calculate sleep time
208 | * @param exceptionClass class to calculate sleep time from
209 | * @return a wait strategy calculated from the failed attempt
210 | */
211 | public static WaitStrategy exceptionWait(@Nonnull Class exceptionClass,
212 | @Nonnull Function function) {
213 | Preconditions.checkNotNull(exceptionClass, "exceptionClass may not be null");
214 | Preconditions.checkNotNull(function, "function may not be null");
215 | return new ExceptionWaitStrategy(exceptionClass, function);
216 | }
217 |
218 | /**
219 | * Joins one or more wait strategies to derive a composite wait strategy.
220 | * The new joined strategy will have a wait time which is total of all wait times computed one after another in order.
221 | *
222 | * @param waitStrategies Wait strategies that need to be applied one after another for computing the sleep time.
223 | * @return A composite wait strategy
224 | */
225 | public static WaitStrategy join(WaitStrategy... waitStrategies) {
226 | Preconditions.checkState(waitStrategies.length > 0, "Must have at least one wait strategy");
227 | List waitStrategyList = Lists.newArrayList(waitStrategies);
228 | Preconditions.checkState(!waitStrategyList.contains(null), "Cannot have a null wait strategy");
229 | return new CompositeWaitStrategy(waitStrategyList);
230 | }
231 |
232 | @Immutable
233 | private static final class FixedWaitStrategy implements WaitStrategy {
234 | private final long sleepTime;
235 |
236 | public FixedWaitStrategy(long sleepTime) {
237 | Preconditions.checkArgument(sleepTime >= 0L, "sleepTime must be >= 0 but is %d", sleepTime);
238 | this.sleepTime = sleepTime;
239 | }
240 |
241 | @Override
242 | public long computeSleepTime(Attempt failedAttempt) {
243 | return sleepTime;
244 | }
245 | }
246 |
247 | @Immutable
248 | private static final class RandomWaitStrategy implements WaitStrategy {
249 | private static final Random RANDOM = new Random();
250 | private final long minimum;
251 | private final long maximum;
252 |
253 | public RandomWaitStrategy(long minimum, long maximum) {
254 | Preconditions.checkArgument(minimum >= 0, "minimum must be >= 0 but is %d", minimum);
255 | Preconditions.checkArgument(maximum > minimum, "maximum must be > minimum but maximum is %d and minimum is", maximum, minimum);
256 |
257 | this.minimum = minimum;
258 | this.maximum = maximum;
259 | }
260 |
261 | @Override
262 | public long computeSleepTime(Attempt failedAttempt) {
263 | long t = Math.abs(RANDOM.nextLong()) % (maximum - minimum);
264 | return t + minimum;
265 | }
266 | }
267 |
268 | @Immutable
269 | private static final class IncrementingWaitStrategy implements WaitStrategy {
270 | private final long initialSleepTime;
271 | private final long increment;
272 |
273 | public IncrementingWaitStrategy(long initialSleepTime,
274 | long increment) {
275 | Preconditions.checkArgument(initialSleepTime >= 0L, "initialSleepTime must be >= 0 but is %d", initialSleepTime);
276 | this.initialSleepTime = initialSleepTime;
277 | this.increment = increment;
278 | }
279 |
280 | @Override
281 | public long computeSleepTime(Attempt failedAttempt) {
282 | long result = initialSleepTime + (increment * (failedAttempt.getAttemptNumber() - 1));
283 | return result >= 0L ? result : 0L;
284 | }
285 | }
286 |
287 | @Immutable
288 | private static final class ExponentialWaitStrategy implements WaitStrategy {
289 | private final long multiplier;
290 | private final long maximumWait;
291 |
292 | public ExponentialWaitStrategy(long multiplier,
293 | long maximumWait) {
294 | Preconditions.checkArgument(multiplier > 0L, "multiplier must be > 0 but is %d", multiplier);
295 | Preconditions.checkArgument(maximumWait >= 0L, "maximumWait must be >= 0 but is %d", maximumWait);
296 | Preconditions.checkArgument(multiplier < maximumWait, "multiplier must be < maximumWait but is %d", multiplier);
297 | this.multiplier = multiplier;
298 | this.maximumWait = maximumWait;
299 | }
300 |
301 | @Override
302 | public long computeSleepTime(Attempt failedAttempt) {
303 | double exp = Math.pow(2, failedAttempt.getAttemptNumber());
304 | long result = Math.round(multiplier * exp);
305 | if (result > maximumWait) {
306 | result = maximumWait;
307 | }
308 | return result >= 0L ? result : 0L;
309 | }
310 | }
311 |
312 | @Immutable
313 | private static final class FibonacciWaitStrategy implements WaitStrategy {
314 | private final long multiplier;
315 | private final long maximumWait;
316 |
317 | public FibonacciWaitStrategy(long multiplier, long maximumWait) {
318 | Preconditions.checkArgument(multiplier > 0L, "multiplier must be > 0 but is %d", multiplier);
319 | Preconditions.checkArgument(maximumWait >= 0L, "maximumWait must be >= 0 but is %d", maximumWait);
320 | Preconditions.checkArgument(multiplier < maximumWait, "multiplier must be < maximumWait but is %d", multiplier);
321 | this.multiplier = multiplier;
322 | this.maximumWait = maximumWait;
323 | }
324 |
325 | @Override
326 | public long computeSleepTime(Attempt failedAttempt) {
327 | long fib = fib(failedAttempt.getAttemptNumber());
328 | long result = multiplier * fib;
329 |
330 | if (result > maximumWait || result < 0L) {
331 | result = maximumWait;
332 | }
333 |
334 | return result >= 0L ? result : 0L;
335 | }
336 |
337 | private long fib(long n) {
338 | if (n == 0L) return 0L;
339 | if (n == 1L) return 1L;
340 |
341 | long prevPrev = 0L;
342 | long prev = 1L;
343 | long result = 0L;
344 |
345 | for (long i = 2L; i <= n; i++) {
346 | result = prev + prevPrev;
347 | prevPrev = prev;
348 | prev = result;
349 | }
350 |
351 | return result;
352 | }
353 | }
354 |
355 | @Immutable
356 | private static final class CompositeWaitStrategy implements WaitStrategy {
357 | private final List waitStrategies;
358 |
359 | public CompositeWaitStrategy(List waitStrategies) {
360 | Preconditions.checkState(!waitStrategies.isEmpty(), "Need at least one wait strategy");
361 | this.waitStrategies = waitStrategies;
362 | }
363 |
364 | @Override
365 | public long computeSleepTime(Attempt failedAttempt) {
366 | long waitTime = 0L;
367 | for (WaitStrategy waitStrategy : waitStrategies) {
368 | waitTime += waitStrategy.computeSleepTime(failedAttempt);
369 | }
370 | return waitTime;
371 | }
372 | }
373 |
374 | @Immutable
375 | private static final class ExceptionWaitStrategy implements WaitStrategy {
376 | private final Class exceptionClass;
377 | private final Function function;
378 |
379 | public ExceptionWaitStrategy(@Nonnull Class exceptionClass, @Nonnull Function function) {
380 | this.exceptionClass = exceptionClass;
381 | this.function = function;
382 | }
383 |
384 | @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "ConstantConditions", "unchecked"})
385 | @Override
386 | public long computeSleepTime(Attempt lastAttempt) {
387 | if (lastAttempt.hasException()) {
388 | Throwable cause = lastAttempt.getExceptionCause();
389 | if (exceptionClass.isAssignableFrom(cause.getClass())) {
390 | return function.apply((T) cause);
391 | }
392 | }
393 | return 0L;
394 | }
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/src/main/java/com/github/rholder/retry/WaitStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | /**
20 | * A strategy used to decide how long to sleep before retrying after a failed attempt.
21 | *
22 | * @author JB
23 | */
24 | public interface WaitStrategy {
25 |
26 | /**
27 | * Returns the time, in milliseconds, to sleep before retrying.
28 | *
29 | * @param failedAttempt the previous failed {@code Attempt}
30 | * @return the sleep time before next attempt
31 | */
32 | long computeSleepTime(Attempt failedAttempt);
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/com/github/rholder/retry/AttemptTimeLimiterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.google.common.util.concurrent.UncheckedTimeoutException;
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 |
23 | import java.util.concurrent.Callable;
24 | import java.util.concurrent.ExecutionException;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | /**
28 | * @author Jason Dunkelberger (dirkraft)
29 | */
30 | public class AttemptTimeLimiterTest {
31 |
32 | Retryer r = RetryerBuilder.newBuilder()
33 | .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(1, TimeUnit.SECONDS))
34 | .build();
35 |
36 | @Test
37 | public void testAttemptTimeLimit() throws ExecutionException, RetryException {
38 | try {
39 | r.call(new SleepyOut(0L));
40 | } catch (ExecutionException e) {
41 | Assert.fail("Should not timeout");
42 | }
43 |
44 | try {
45 | r.call(new SleepyOut(10 * 1000L));
46 | Assert.fail("Expected timeout exception");
47 | } catch (ExecutionException e) {
48 | // expected
49 | Assert.assertEquals(UncheckedTimeoutException.class, e.getCause().getClass());
50 | }
51 | }
52 |
53 | static class SleepyOut implements Callable {
54 |
55 | final long sleepMs;
56 |
57 | SleepyOut(long sleepMs) {
58 | this.sleepMs = sleepMs;
59 | }
60 |
61 | @Override
62 | public Void call() throws Exception {
63 | Thread.sleep(sleepMs);
64 | System.out.println("I'm awake now");
65 | return null;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/github/rholder/retry/RetryerBuilderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.github.rholder.retry.Retryer.RetryerCallable;
20 | import com.google.common.base.Predicate;
21 | import com.google.common.base.Predicates;
22 | import org.junit.Test;
23 |
24 | import java.io.IOException;
25 | import java.util.HashMap;
26 | import java.util.Map;
27 | import java.util.concurrent.Callable;
28 | import java.util.concurrent.CountDownLatch;
29 | import java.util.concurrent.ExecutionException;
30 | import java.util.concurrent.TimeUnit;
31 | import java.util.concurrent.atomic.AtomicBoolean;
32 | import java.util.concurrent.atomic.AtomicInteger;
33 |
34 | import static org.junit.Assert.assertEquals;
35 | import static org.junit.Assert.assertFalse;
36 | import static org.junit.Assert.assertNull;
37 | import static org.junit.Assert.assertTrue;
38 | import static org.junit.Assert.fail;
39 |
40 | public class RetryerBuilderTest {
41 |
42 | @Test
43 | public void testWithWaitStrategy() throws ExecutionException, RetryException {
44 | Callable callable = notNullAfter5Attempts();
45 | Retryer retryer = RetryerBuilder.newBuilder()
46 | .withWaitStrategy(WaitStrategies.fixedWait(50L, TimeUnit.MILLISECONDS))
47 | .retryIfResult(Predicates.isNull())
48 | .build();
49 | long start = System.currentTimeMillis();
50 | boolean result = retryer.call(callable);
51 | assertTrue(System.currentTimeMillis() - start >= 250L);
52 | assertTrue(result);
53 | }
54 |
55 | @Test
56 | public void testWithMoreThanOneWaitStrategyOneBeingFixed() throws ExecutionException, RetryException {
57 | Callable callable = notNullAfter5Attempts();
58 | Retryer retryer = RetryerBuilder.newBuilder()
59 | .withWaitStrategy(WaitStrategies.join(
60 | WaitStrategies.fixedWait(50L, TimeUnit.MILLISECONDS),
61 | WaitStrategies.fibonacciWait(10, Long.MAX_VALUE, TimeUnit.MILLISECONDS)))
62 | .retryIfResult(Predicates.isNull())
63 | .build();
64 | long start = System.currentTimeMillis();
65 | boolean result = retryer.call(callable);
66 | assertTrue(System.currentTimeMillis() - start >= 370L);
67 | assertTrue(result);
68 | }
69 |
70 | @Test
71 | public void testWithMoreThanOneWaitStrategyOneBeingIncremental() throws ExecutionException, RetryException {
72 | Callable callable = notNullAfter5Attempts();
73 | Retryer retryer = RetryerBuilder.newBuilder()
74 | .withWaitStrategy(WaitStrategies.join(
75 | WaitStrategies.incrementingWait(10L, TimeUnit.MILLISECONDS, 10L, TimeUnit.MILLISECONDS),
76 | WaitStrategies.fibonacciWait(10, Long.MAX_VALUE, TimeUnit.MILLISECONDS)))
77 | .retryIfResult(Predicates.isNull())
78 | .build();
79 | long start = System.currentTimeMillis();
80 | boolean result = retryer.call(callable);
81 | assertTrue(System.currentTimeMillis() - start >= 270L);
82 | assertTrue(result);
83 | }
84 |
85 | private Callable notNullAfter5Attempts() {
86 | return new Callable() {
87 | int counter = 0;
88 |
89 | @Override
90 | public Boolean call() throws Exception {
91 | if (counter < 5) {
92 | counter++;
93 | return null;
94 | }
95 | return true;
96 | }
97 | };
98 | }
99 |
100 | @Test
101 | public void testWithStopStrategy() throws ExecutionException {
102 | Callable callable = notNullAfter5Attempts();
103 | Retryer retryer = RetryerBuilder.newBuilder()
104 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
105 | .retryIfResult(Predicates.isNull())
106 | .build();
107 | try {
108 | retryer.call(callable);
109 | fail("RetryException expected");
110 | } catch (RetryException e) {
111 | assertEquals(3, e.getNumberOfFailedAttempts());
112 | }
113 | }
114 |
115 | @Test
116 | public void testWithBlockStrategy() throws ExecutionException, RetryException {
117 | Callable callable = notNullAfter5Attempts();
118 | final AtomicInteger counter = new AtomicInteger();
119 | BlockStrategy blockStrategy = new BlockStrategy() {
120 | @Override
121 | public void block(long sleepTime) throws InterruptedException {
122 | counter.incrementAndGet();
123 | }
124 | };
125 |
126 | Retryer retryer = RetryerBuilder.newBuilder()
127 | .withBlockStrategy(blockStrategy)
128 | .retryIfResult(Predicates.isNull())
129 | .build();
130 | final int retryCount = 5;
131 | boolean result = retryer.call(callable);
132 | assertTrue(result);
133 | assertEquals(counter.get(), retryCount);
134 | }
135 |
136 | @Test
137 | public void testRetryIfException() throws ExecutionException, RetryException {
138 | Callable callable = noIOExceptionAfter5Attempts();
139 | Retryer retryer = RetryerBuilder.newBuilder()
140 | .retryIfException()
141 | .build();
142 | boolean result = retryer.call(callable);
143 | assertTrue(result);
144 |
145 | callable = noIOExceptionAfter5Attempts();
146 | retryer = RetryerBuilder.newBuilder()
147 | .retryIfException()
148 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
149 | .build();
150 | try {
151 | retryer.call(callable);
152 | fail("RetryException expected");
153 | } catch (RetryException e) {
154 | assertEquals(3, e.getNumberOfFailedAttempts());
155 | assertTrue(e.getLastFailedAttempt().hasException());
156 | assertTrue(e.getLastFailedAttempt().getExceptionCause() instanceof IOException);
157 | assertTrue(e.getCause() instanceof IOException);
158 | }
159 |
160 | callable = noIllegalStateExceptionAfter5Attempts();
161 | retryer = RetryerBuilder.newBuilder()
162 | .retryIfException()
163 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
164 | .build();
165 | try {
166 | retryer.call(callable);
167 | fail("RetryException expected");
168 | } catch (RetryException e) {
169 | assertEquals(3, e.getNumberOfFailedAttempts());
170 | assertTrue(e.getLastFailedAttempt().hasException());
171 | assertTrue(e.getLastFailedAttempt().getExceptionCause() instanceof IllegalStateException);
172 | assertTrue(e.getCause() instanceof IllegalStateException);
173 | }
174 | }
175 |
176 | private Callable noIllegalStateExceptionAfter5Attempts() {
177 | return new Callable() {
178 | int counter = 0;
179 |
180 | @Override
181 | public Boolean call() throws Exception {
182 | if (counter < 5) {
183 | counter++;
184 | throw new IllegalStateException();
185 | }
186 | return true;
187 | }
188 | };
189 | }
190 |
191 | private Callable noIOExceptionAfter5Attempts() {
192 | return new Callable() {
193 | int counter = 0;
194 |
195 | @Override
196 | public Boolean call() throws IOException {
197 | if (counter < 5) {
198 | counter++;
199 | throw new IOException();
200 | }
201 | return true;
202 | }
203 | };
204 | }
205 |
206 | @Test
207 | public void testRetryIfRuntimeException() throws ExecutionException, RetryException {
208 | Callable callable = noIOExceptionAfter5Attempts();
209 | Retryer retryer = RetryerBuilder.newBuilder()
210 | .retryIfRuntimeException()
211 | .build();
212 | try {
213 | retryer.call(callable);
214 | fail("ExecutionException expected");
215 | } catch (ExecutionException e) {
216 | assertTrue(e.getCause() instanceof IOException);
217 | }
218 |
219 | callable = noIllegalStateExceptionAfter5Attempts();
220 | assertTrue(retryer.call(callable));
221 |
222 | callable = noIllegalStateExceptionAfter5Attempts();
223 | retryer = RetryerBuilder.newBuilder()
224 | .retryIfRuntimeException()
225 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
226 | .build();
227 | try {
228 | retryer.call(callable);
229 | fail("RetryException expected");
230 | } catch (RetryException e) {
231 | assertEquals(3, e.getNumberOfFailedAttempts());
232 | assertTrue(e.getLastFailedAttempt().hasException());
233 | assertTrue(e.getLastFailedAttempt().getExceptionCause() instanceof IllegalStateException);
234 | assertTrue(e.getCause() instanceof IllegalStateException);
235 | }
236 | }
237 |
238 | @Test
239 | public void testRetryIfExceptionOfType() throws RetryException, ExecutionException {
240 | Callable callable = noIOExceptionAfter5Attempts();
241 | Retryer retryer = RetryerBuilder.newBuilder()
242 | .retryIfExceptionOfType(IOException.class)
243 | .build();
244 | assertTrue(retryer.call(callable));
245 |
246 | callable = noIllegalStateExceptionAfter5Attempts();
247 | try {
248 | retryer.call(callable);
249 | fail("ExecutionException expected");
250 | } catch (ExecutionException e) {
251 | assertTrue(e.getCause() instanceof IllegalStateException);
252 | }
253 |
254 | callable = noIOExceptionAfter5Attempts();
255 | retryer = RetryerBuilder.newBuilder()
256 | .retryIfExceptionOfType(IOException.class)
257 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
258 | .build();
259 | try {
260 | retryer.call(callable);
261 | fail("RetryException expected");
262 | } catch (RetryException e) {
263 | assertEquals(3, e.getNumberOfFailedAttempts());
264 | assertTrue(e.getLastFailedAttempt().hasException());
265 | assertTrue(e.getLastFailedAttempt().getExceptionCause() instanceof IOException);
266 | assertTrue(e.getCause() instanceof IOException);
267 | }
268 | }
269 |
270 | @Test
271 | public void testRetryIfExceptionWithPredicate() throws RetryException, ExecutionException {
272 | Callable callable = noIOExceptionAfter5Attempts();
273 | Retryer retryer = RetryerBuilder.newBuilder()
274 | .retryIfException(new Predicate() {
275 | @Override
276 | public boolean apply(Throwable t) {
277 | return t instanceof IOException;
278 | }
279 | })
280 | .build();
281 | assertTrue(retryer.call(callable));
282 |
283 | callable = noIllegalStateExceptionAfter5Attempts();
284 | try {
285 | retryer.call(callable);
286 | fail("ExecutionException expected");
287 | } catch (ExecutionException e) {
288 | assertTrue(e.getCause() instanceof IllegalStateException);
289 | }
290 |
291 | callable = noIOExceptionAfter5Attempts();
292 | retryer = RetryerBuilder.newBuilder()
293 | .retryIfException(new Predicate() {
294 | @Override
295 | public boolean apply(Throwable t) {
296 | return t instanceof IOException;
297 | }
298 | })
299 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
300 | .build();
301 | try {
302 | retryer.call(callable);
303 | fail("RetryException expected");
304 | } catch (RetryException e) {
305 | assertEquals(3, e.getNumberOfFailedAttempts());
306 | assertTrue(e.getLastFailedAttempt().hasException());
307 | assertTrue(e.getLastFailedAttempt().getExceptionCause() instanceof IOException);
308 | assertTrue(e.getCause() instanceof IOException);
309 | }
310 | }
311 |
312 | @Test
313 | public void testRetryIfResult() throws ExecutionException, RetryException {
314 | Callable callable = notNullAfter5Attempts();
315 | Retryer retryer = RetryerBuilder.newBuilder()
316 | .retryIfResult(Predicates.isNull())
317 | .build();
318 | assertTrue(retryer.call(callable));
319 |
320 | callable = notNullAfter5Attempts();
321 | retryer = RetryerBuilder.newBuilder()
322 | .retryIfResult(Predicates.isNull())
323 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
324 | .build();
325 | try {
326 | retryer.call(callable);
327 | fail("RetryException expected");
328 | } catch (RetryException e) {
329 | assertEquals(3, e.getNumberOfFailedAttempts());
330 | assertTrue(e.getLastFailedAttempt().hasResult());
331 | assertNull(e.getLastFailedAttempt().getResult());
332 | assertNull(e.getCause());
333 | }
334 | }
335 |
336 | @Test
337 | public void testMultipleRetryConditions() throws ExecutionException, RetryException {
338 | Callable callable = notNullResultOrIOExceptionOrRuntimeExceptionAfter5Attempts();
339 | Retryer retryer = RetryerBuilder.newBuilder()
340 | .retryIfResult(Predicates.isNull())
341 | .retryIfExceptionOfType(IOException.class)
342 | .retryIfRuntimeException()
343 | .withStopStrategy(StopStrategies.stopAfterAttempt(3))
344 | .build();
345 | try {
346 | retryer.call(callable);
347 | fail("RetryException expected");
348 | } catch (RetryException e) {
349 | assertTrue(e.getLastFailedAttempt().hasException());
350 | assertTrue(e.getLastFailedAttempt().getExceptionCause() instanceof IllegalStateException);
351 | assertTrue(e.getCause() instanceof IllegalStateException);
352 | }
353 |
354 | callable = notNullResultOrIOExceptionOrRuntimeExceptionAfter5Attempts();
355 | retryer = RetryerBuilder.newBuilder()
356 | .retryIfResult(Predicates.isNull())
357 | .retryIfExceptionOfType(IOException.class)
358 | .retryIfRuntimeException()
359 | .build();
360 | assertTrue(retryer.call(callable));
361 | }
362 |
363 | private Callable notNullResultOrIOExceptionOrRuntimeExceptionAfter5Attempts() {
364 | return new Callable() {
365 | int counter = 0;
366 |
367 | @Override
368 | public Boolean call() throws IOException {
369 | if (counter < 1) {
370 | counter++;
371 | return null;
372 | } else if (counter < 2) {
373 | counter++;
374 | throw new IOException();
375 | } else if (counter < 5) {
376 | counter++;
377 | throw new IllegalStateException();
378 | }
379 | return true;
380 | }
381 | };
382 | }
383 |
384 | @Test
385 | public void testInterruption() throws InterruptedException, ExecutionException {
386 | final AtomicBoolean result = new AtomicBoolean(false);
387 | final CountDownLatch latch = new CountDownLatch(1);
388 | Runnable r = new Runnable() {
389 | @Override
390 | public void run() {
391 | Retryer retryer = RetryerBuilder.newBuilder()
392 | .withWaitStrategy(WaitStrategies.fixedWait(1000L, TimeUnit.MILLISECONDS))
393 | .retryIfResult(Predicates.isNull())
394 | .build();
395 | try {
396 | retryer.call(alwaysNull(latch));
397 | fail("RetryException expected");
398 | } catch (RetryException e) {
399 | assertTrue(!e.getLastFailedAttempt().hasException());
400 | assertNull(e.getCause());
401 | assertTrue(Thread.currentThread().isInterrupted());
402 | result.set(true);
403 | } catch (ExecutionException e) {
404 | fail("RetryException expected");
405 | }
406 | }
407 | };
408 | Thread t = new Thread(r);
409 | t.start();
410 | latch.countDown();
411 | t.interrupt();
412 | t.join();
413 | assertTrue(result.get());
414 | }
415 |
416 | @Test
417 | public void testWrap() throws ExecutionException, RetryException {
418 | Callable callable = notNullAfter5Attempts();
419 | Retryer retryer = RetryerBuilder.newBuilder()
420 | .retryIfResult(Predicates.isNull())
421 | .build();
422 | RetryerCallable wrapped = retryer.wrap(callable);
423 | assertTrue(wrapped.call());
424 | }
425 |
426 | @Test
427 | public void testWhetherBuilderFailsForNullStopStrategy() {
428 | try {
429 | RetryerBuilder.newBuilder()
430 | .withStopStrategy(null)
431 | .build();
432 | fail("Exepcted to fail for null stop strategy");
433 | } catch (NullPointerException exception) {
434 | assertTrue(exception.getMessage().contains("stopStrategy may not be null"));
435 | }
436 | }
437 |
438 | @Test
439 | public void testWhetherBuilderFailsForNullWaitStrategy() {
440 | try {
441 | RetryerBuilder.newBuilder()
442 | .withWaitStrategy(null)
443 | .build();
444 | fail("Exepcted to fail for null wait strategy");
445 | } catch (NullPointerException exception) {
446 | assertTrue(exception.getMessage().contains("waitStrategy may not be null"));
447 | }
448 | }
449 |
450 | @Test
451 | public void testWhetherBuilderFailsForNullWaitStrategyWithCompositeStrategies() {
452 | try {
453 | RetryerBuilder.newBuilder()
454 | .withWaitStrategy(WaitStrategies.join(null, null))
455 | .build();
456 | fail("Exepcted to fail for null wait strategy");
457 | } catch (IllegalStateException exception) {
458 | assertTrue(exception.getMessage().contains("Cannot have a null wait strategy"));
459 | }
460 | }
461 |
462 | @Test
463 | public void testRetryListener_SuccessfulAttempt() throws Exception {
464 | final Map attempts = new HashMap();
465 |
466 | RetryListener listener = new RetryListener() {
467 | @Override
468 | public void onRetry(Attempt attempt) {
469 | attempts.put(attempt.getAttemptNumber(), attempt);
470 | }
471 | };
472 |
473 | Callable callable = notNullAfter5Attempts();
474 |
475 | Retryer retryer = RetryerBuilder.newBuilder()
476 | .retryIfResult(Predicates.isNull())
477 | .withRetryListener(listener)
478 | .build();
479 | assertTrue(retryer.call(callable));
480 |
481 | assertEquals(6, attempts.size());
482 |
483 | assertResultAttempt(attempts.get(1L), true, null);
484 | assertResultAttempt(attempts.get(2L), true, null);
485 | assertResultAttempt(attempts.get(3L), true, null);
486 | assertResultAttempt(attempts.get(4L), true, null);
487 | assertResultAttempt(attempts.get(5L), true, null);
488 | assertResultAttempt(attempts.get(6L), true, true);
489 | }
490 |
491 | @Test
492 | public void testRetryListener_WithException() throws Exception {
493 | final Map attempts = new HashMap();
494 |
495 | RetryListener listener = new RetryListener() {
496 | @Override
497 | public void onRetry(Attempt attempt) {
498 | attempts.put(attempt.getAttemptNumber(), attempt);
499 | }
500 | };
501 |
502 | Callable callable = noIOExceptionAfter5Attempts();
503 |
504 | Retryer retryer = RetryerBuilder.newBuilder()
505 | .retryIfResult(Predicates.isNull())
506 | .retryIfException()
507 | .withRetryListener(listener)
508 | .build();
509 | assertTrue(retryer.call(callable));
510 |
511 | assertEquals(6, attempts.size());
512 |
513 | assertExceptionAttempt(attempts.get(1L), true, IOException.class);
514 | assertExceptionAttempt(attempts.get(2L), true, IOException.class);
515 | assertExceptionAttempt(attempts.get(3L), true, IOException.class);
516 | assertExceptionAttempt(attempts.get(4L), true, IOException.class);
517 | assertExceptionAttempt(attempts.get(5L), true, IOException.class);
518 | assertResultAttempt(attempts.get(6L), true, true);
519 | }
520 |
521 | @Test
522 | public void testMultipleRetryListeners() throws Exception {
523 | Callable callable = new Callable() {
524 | @Override
525 | public Boolean call() throws Exception {
526 | return true;
527 | }
528 | };
529 |
530 | final AtomicBoolean listenerOne = new AtomicBoolean(false);
531 | final AtomicBoolean listenerTwo = new AtomicBoolean(false);
532 |
533 | Retryer retryer = RetryerBuilder.newBuilder()
534 | .withRetryListener(new RetryListener() {
535 | @Override
536 | public void onRetry(Attempt attempt) {
537 | listenerOne.set(true);
538 | }
539 | })
540 | .withRetryListener(new RetryListener() {
541 | @Override
542 | public void onRetry(Attempt attempt) {
543 | listenerTwo.set(true);
544 | }
545 | })
546 | .build();
547 |
548 | assertTrue(retryer.call(callable));
549 | assertTrue(listenerOne.get());
550 | assertTrue(listenerTwo.get());
551 | }
552 |
553 | private void assertResultAttempt(Attempt actualAttempt, boolean expectedHasResult, Object expectedResult) {
554 | assertFalse(actualAttempt.hasException());
555 | assertEquals(expectedHasResult, actualAttempt.hasResult());
556 | assertEquals(expectedResult, actualAttempt.getResult());
557 | }
558 |
559 | private void assertExceptionAttempt(Attempt actualAttempt, boolean expectedHasException, Class> expectedExceptionClass) {
560 | assertFalse(actualAttempt.hasResult());
561 | assertEquals(expectedHasException, actualAttempt.hasException());
562 | assertTrue(expectedExceptionClass.isInstance(actualAttempt.getExceptionCause()));
563 | }
564 |
565 | private Callable alwaysNull(final CountDownLatch latch) {
566 | return new Callable() {
567 | @Override
568 | public Boolean call() throws Exception {
569 | latch.countDown();
570 | return null;
571 | }
572 | };
573 | }
574 | }
575 |
--------------------------------------------------------------------------------
/src/test/java/com/github/rholder/retry/StopStrategiesTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import static org.junit.Assert.assertFalse;
20 | import static org.junit.Assert.assertTrue;
21 |
22 | import java.util.concurrent.TimeUnit;
23 |
24 | import org.junit.Test;
25 |
26 | public class StopStrategiesTest {
27 |
28 | @Test
29 | public void testNeverStop() {
30 | assertFalse(StopStrategies.neverStop().shouldStop(failedAttempt(3, 6546L)));
31 | }
32 |
33 | @Test
34 | public void testStopAfterAttempt() {
35 | assertFalse(StopStrategies.stopAfterAttempt(3).shouldStop(failedAttempt(2, 6546L)));
36 | assertTrue(StopStrategies.stopAfterAttempt(3).shouldStop(failedAttempt(3, 6546L)));
37 | assertTrue(StopStrategies.stopAfterAttempt(3).shouldStop(failedAttempt(4, 6546L)));
38 | }
39 |
40 | @Test
41 | public void testStopAfterDelayWithMilliseconds() {
42 | assertFalse(StopStrategies.stopAfterDelay(1000L).shouldStop(failedAttempt(2, 999L)));
43 | assertTrue(StopStrategies.stopAfterDelay(1000L).shouldStop(failedAttempt(2, 1000L)));
44 | assertTrue(StopStrategies.stopAfterDelay(1000L).shouldStop(failedAttempt(2, 1001L)));
45 | }
46 |
47 | @Test
48 | public void testStopAfterDelayWithTimeUnit() {
49 | assertFalse(StopStrategies.stopAfterDelay(1, TimeUnit.SECONDS).shouldStop(failedAttempt(2, 999L)));
50 | assertTrue(StopStrategies.stopAfterDelay(1, TimeUnit.SECONDS).shouldStop(failedAttempt(2, 1000L)));
51 | assertTrue(StopStrategies.stopAfterDelay(1, TimeUnit.SECONDS).shouldStop(failedAttempt(2, 1001L)));
52 | }
53 |
54 | public Attempt failedAttempt(long attemptNumber, long delaySinceFirstAttempt) {
55 | return new Retryer.ExceptionAttempt(new RuntimeException(), attemptNumber, delaySinceFirstAttempt);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/com/github/rholder/retry/WaitStrategiesTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2015 Ray Holder
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.rholder.retry;
18 |
19 | import com.google.common.base.Function;
20 | import com.google.common.collect.Sets;
21 | import org.junit.Test;
22 |
23 | import java.util.Set;
24 | import java.util.concurrent.TimeUnit;
25 |
26 | import static org.junit.Assert.assertEquals;
27 | import static org.junit.Assert.assertTrue;
28 |
29 | public class WaitStrategiesTest {
30 |
31 |
32 | @Test
33 | public void testNoWait() {
34 | WaitStrategy noWait = WaitStrategies.noWait();
35 | assertEquals(0L, noWait.computeSleepTime(failedAttempt(18, 9879L)));
36 | }
37 |
38 | @Test
39 | public void testFixedWait() {
40 | WaitStrategy fixedWait = WaitStrategies.fixedWait(1000L, TimeUnit.MILLISECONDS);
41 | assertEquals(1000L, fixedWait.computeSleepTime(failedAttempt(12, 6546L)));
42 | }
43 |
44 | @Test
45 | public void testIncrementingWait() {
46 | WaitStrategy incrementingWait = WaitStrategies.incrementingWait(500L, TimeUnit.MILLISECONDS, 100L, TimeUnit.MILLISECONDS);
47 | assertEquals(500L, incrementingWait.computeSleepTime(failedAttempt(1, 6546L)));
48 | assertEquals(600L, incrementingWait.computeSleepTime(failedAttempt(2, 6546L)));
49 | assertEquals(700L, incrementingWait.computeSleepTime(failedAttempt(3, 6546L)));
50 | }
51 |
52 | @Test
53 | public void testRandomWait() {
54 | WaitStrategy randomWait = WaitStrategies.randomWait(1000L, TimeUnit.MILLISECONDS, 2000L, TimeUnit.MILLISECONDS);
55 | Set times = Sets.newHashSet();
56 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
57 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
58 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
59 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
60 | assertTrue(times.size() > 1); // if not, the random is not random
61 | for (long time : times) {
62 | assertTrue(time >= 1000L);
63 | assertTrue(time <= 2000L);
64 | }
65 | }
66 |
67 | @Test
68 | public void testRandomWaitWithoutMinimum() {
69 | WaitStrategy randomWait = WaitStrategies.randomWait(2000L, TimeUnit.MILLISECONDS);
70 | Set times = Sets.newHashSet();
71 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
72 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
73 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
74 | times.add(randomWait.computeSleepTime(failedAttempt(1, 6546L)));
75 | assertTrue(times.size() > 1); // if not, the random is not random
76 | for (long time : times) {
77 | assertTrue(time >= 0L);
78 | assertTrue(time <= 2000L);
79 | }
80 | }
81 |
82 | @Test
83 | public void testExponential() {
84 | WaitStrategy exponentialWait = WaitStrategies.exponentialWait();
85 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(1, 0)) == 2);
86 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(2, 0)) == 4);
87 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(3, 0)) == 8);
88 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(4, 0)) == 16);
89 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(5, 0)) == 32);
90 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(6, 0)) == 64);
91 | }
92 |
93 | @Test
94 | public void testExponentialWithMaximumWait() {
95 | WaitStrategy exponentialWait = WaitStrategies.exponentialWait(40, TimeUnit.MILLISECONDS);
96 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(1, 0)) == 2);
97 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(2, 0)) == 4);
98 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(3, 0)) == 8);
99 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(4, 0)) == 16);
100 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(5, 0)) == 32);
101 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(6, 0)) == 40);
102 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(7, 0)) == 40);
103 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(Integer.MAX_VALUE, 0)) == 40);
104 | }
105 |
106 | @Test
107 | public void testExponentialWithMultiplierAndMaximumWait() {
108 | WaitStrategy exponentialWait = WaitStrategies.exponentialWait(1000, 50000, TimeUnit.MILLISECONDS);
109 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(1, 0)) == 2000);
110 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(2, 0)) == 4000);
111 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(3, 0)) == 8000);
112 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(4, 0)) == 16000);
113 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(5, 0)) == 32000);
114 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(6, 0)) == 50000);
115 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(7, 0)) == 50000);
116 | assertTrue(exponentialWait.computeSleepTime(failedAttempt(Integer.MAX_VALUE, 0)) == 50000);
117 | }
118 |
119 | @Test
120 | public void testFibonacci() {
121 | WaitStrategy fibonacciWait = WaitStrategies.fibonacciWait();
122 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(1, 0L)) == 1L);
123 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(2, 0L)) == 1L);
124 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(3, 0L)) == 2L);
125 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(4, 0L)) == 3L);
126 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(5, 0L)) == 5L);
127 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(6, 0L)) == 8L);
128 | }
129 |
130 | @Test
131 | public void testFibonacciWithMaximumWait() {
132 | WaitStrategy fibonacciWait = WaitStrategies.fibonacciWait(10L, TimeUnit.MILLISECONDS);
133 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(1, 0L)) == 1L);
134 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(2, 0L)) == 1L);
135 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(3, 0L)) == 2L);
136 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(4, 0L)) == 3L);
137 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(5, 0L)) == 5L);
138 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(6, 0L)) == 8L);
139 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(7, 0L)) == 10L);
140 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(Integer.MAX_VALUE, 0L)) == 10L);
141 | }
142 |
143 | @Test
144 | public void testFibonacciWithMultiplierAndMaximumWait() {
145 | WaitStrategy fibonacciWait = WaitStrategies.fibonacciWait(1000L, 50000L, TimeUnit.MILLISECONDS);
146 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(1, 0L)) == 1000L);
147 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(2, 0L)) == 1000L);
148 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(3, 0L)) == 2000L);
149 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(4, 0L)) == 3000L);
150 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(5, 0L)) == 5000L);
151 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(6, 0L)) == 8000L);
152 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(7, 0L)) == 13000L);
153 | assertTrue(fibonacciWait.computeSleepTime(failedAttempt(Integer.MAX_VALUE, 0L)) == 50000L);
154 | }
155 |
156 | @Test
157 | public void testExceptionWait() {
158 | WaitStrategy exceptionWait = WaitStrategies.exceptionWait(RuntimeException.class, zeroSleepFunction());
159 | assertEquals(0L, exceptionWait.computeSleepTime(failedAttempt(42, 7227)));
160 |
161 | WaitStrategy oneMinuteWait = WaitStrategies.exceptionWait(RuntimeException.class, oneMinuteSleepFunction());
162 | assertEquals(3600 * 1000L, oneMinuteWait.computeSleepTime(failedAttempt(42, 7227)));
163 |
164 | WaitStrategy noMatchRetryAfterWait = WaitStrategies.exceptionWait(RetryAfterException.class, customSleepFunction());
165 | assertEquals(0L, noMatchRetryAfterWait.computeSleepTime(failedAttempt(42, 7227)));
166 |
167 | WaitStrategy retryAfterWait = WaitStrategies.exceptionWait(RetryAfterException.class, customSleepFunction());
168 | assertEquals(29L, retryAfterWait.computeSleepTime(failedRetryAfterAttempt(42, 7227)));
169 | }
170 |
171 | public Attempt failedAttempt(long attemptNumber, long delaySinceFirstAttempt) {
172 | return new Retryer.ExceptionAttempt(new RuntimeException(), attemptNumber, delaySinceFirstAttempt);
173 | }
174 |
175 | public Attempt failedRetryAfterAttempt(long attemptNumber, long delaySinceFirstAttempt) {
176 | return new Retryer.ExceptionAttempt(new RetryAfterException(), attemptNumber, delaySinceFirstAttempt);
177 | }
178 |
179 | public Function zeroSleepFunction() {
180 | return new Function() {
181 | @Override
182 | public Long apply(RuntimeException input) {
183 | return 0L;
184 | }
185 | };
186 | }
187 |
188 | public Function oneMinuteSleepFunction() {
189 | return new Function() {
190 | @Override
191 | public Long apply(RuntimeException input) {
192 | return 3600 * 1000L;
193 | }
194 | };
195 | }
196 |
197 | public Function customSleepFunction() {
198 | return new Function() {
199 | @Override
200 | public Long apply(RetryAfterException input) {
201 | return input.getRetryAfter();
202 | }
203 | };
204 | }
205 |
206 | public class RetryAfterException extends RuntimeException {
207 | private final long retryAfter = 29L;
208 |
209 | public long getRetryAfter() {
210 | return retryAfter;
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------